Skip to content

test(desktop): story-cover the model picker's failure and edge states - #4162

Open
liuxiaocs7 wants to merge 1 commit into
apache:mainfrom
liuxiaocs7:liuxiaocs7/model-picker-failure-states
Open

test(desktop): story-cover the model picker's failure and edge states#4162
liuxiaocs7 wants to merge 1 commit into
apache:mainfrom
liuxiaocs7:liuxiaocs7/model-picker-failure-states

Conversation

@liuxiaocs7

@liuxiaocs7 liuxiaocs7 commented Aug 29, 2026

Copy link
Copy Markdown
Member

test(desktop): story-cover the model picker's failure and edge states

Second surface under #3944 (one surface per PR): extend
Product/Model Picker with the failure and edge states that never show
up in normal use, so the Storybook gate and the #3893 a11y audit have
real targets. Every story renders a production component with real
props — no fabricated error UI.

  • CatalogLoading — ModelPicker loading, the catalog request still in
    flight. Distinct from EmptyCatalog (resolved, no providers): the
    trigger wears Astryx's busy affordance, not a disabled husk.
  • NoModelsAvailable — the composer's ModelChipStatic "configure a
    connection" button. A failed / offline / unauthorised catalog fetch
    has no distinct UI on this surface; all three collapse to an empty
    catalog, so one honest "no models" state stands in for them rather
    than inventing three error screens.
  • ManyConnections — seven connection groups (two OpenAI keys exercise
    the slug-suffix heading disambiguation), the breadth refactor(design-system): finish wiring the Maka theme to Astryx's token seams #3446 F5 says a
    single reference screen never probes; a model in the last group stays
    reachable.
  • LongModelNames — a long connection name, model labels, and option
    descriptions at once (very long text).
  • StaleCurrentModel — an existing session pinned to a since-removed
    connection, exercising ChatModelSwitcher's leading-row branch for a
    current model the catalog no longer lists.

Menu/popup states assert in play (open, then query the document.body
portal) so the AX audit covers the open menu; loading and no-models are
mount-visible.

Refs #3944, #3893, #3446

Generative tooling

Claude Code contributed substantially here — it authored these Storybook stories. The human contributor of record reviewed and submitted the change, and each commit carries a Generated-by: Claude Code trailer per CONTRIBUTING.md.

Visual evidence

Storybook stories captured with Playwright (hosted on the fork's release assets).

saving-default-model — normal / light · normal / dark

saving-default-model normal / light saving-default-model normal / dark

many-connections — normal / light · normal / dark

many-connections normal / light many-connections normal / dark

long-model-names — normal / light · normal / dark

long-model-names normal / light long-model-names normal / dark

stale-current-model — normal / light · normal / dark

stale-current-model normal / light stale-current-model normal / dark

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for extending the real model-picker components rather than inventing separate edge-state UI.

I reviewed exact head 4069ca84c70f260f1e277879e61ccedddb4e3ff2. The exact-head checks are green, but the PR body does not include screenshots or recordings of the five new states. Because these stories are intended to become visual review targets, please attach at least the open-menu states for many connections, long names, and stale current model at normal and narrow widths, preferably in both light and dark themes.

I found two P2 story-modeling issues:

  1. CatalogLoading is not the production state described by the story. Settings renders a skeleton while the catalog is unavailable; ModelPicker.loading is used for saving the default model. Please remove this story or model the real saving state with non-empty groups.
  2. StaleCurrentModel bypasses the current recovery path. Missing-connection recovery sets hideUnavailableCurrentModel, while the story calls ChatModelSwitcher directly and supplies a label production does not generate. A real leading-row case would keep the Connection present while removing only the model, or the story should exercise the full recovery state and verify that the stale row is hidden.

A smaller P3 evidence gap remains in the many/long stories: their plays only find menu items and do not prove that the last group is scrollable, focusable, selectable, or that long text stays within the menu. Please strengthen the play assertions or narrow the stated coverage.

There are no P0 or P1 findings, but I would address the two P2s and add visual evidence before approval.

Review analysis was assisted by Codex and an independent @reviewer agent. Astro-Han verified the exact head, current production picker paths, story composition, CI, and severity judgment, and owns this review.

中文对照

谢谢你复用真实 Model Picker 组件来补边界状态,没有另造一套 UI。

我审查了精确 head 4069ca84c70f260f1e277879e61ccedddb4e3ff2,exact-head checks 已通过。但 PR 正文没有提供五个新状态的截图或录屏。既然这些 Story 会成为视觉审查目标,请至少补充多连接、长名称和 stale current model 的打开菜单效果,包括正常宽度、窄宽度,最好同时覆盖浅色和深色主题。

有两个 P2 Story 建模问题:

  1. CatalogLoading 不是 Story 所描述的生产状态。catalog 不可用时 Settings 渲染 skeleton;ModelPicker.loading 实际用于保存默认模型。请删除该 Story,或用非空 groups 表达真实 saving 状态。
  2. StaleCurrentModel 绕过了当前恢复路径。连接缺失时生产代码会设置 hideUnavailableCurrentModel;当前 Story 却直接调用 ChatModelSwitcher,并传入生产不会生成的 label。真实 leading-row 场景可以保留 Connection、只删除 model;或者接入完整恢复状态,验证 stale row 被隐藏。

另有一个较小的 P3 证据缺口:Many/Long 的 play 只找到 menu item,没有证明最后一组可以滚动、聚焦和选择,也没有证明长文本不会撑宽菜单。请加强 play 断言,或者收窄覆盖声明。

没有 P0/P1,但建议先修复两个 P2 并补视觉证据,再考虑 Approve。

本次审查分析由 Codex 和独立的 @reviewer 子代理协助;Astro-Han 核验了精确 head、当前生产 picker 路径、Story composition、CI 和问题分级,并对本次 Review 负责。

@liuxiaocs7
liuxiaocs7 force-pushed the liuxiaocs7/model-picker-failure-states branch from 4069ca8 to ba6d077 Compare August 29, 2026 20:16
@liuxiaocs7

Copy link
Copy Markdown
Member Author

Thanks for the review. Rebased onto latest main and addressed both P2s plus the P3 (force-updated to ba6d077):

  1. CatalogLoading re-modelled as SavingDefaultModel. You're right that ModelPicker.loading is the save-in-flight state, not catalog-unavailable (which renders a skeleton row elsewhere). The story now mirrors general-settings-page.tsx: non-empty groups, a selected value, leadingOption "未设置", disabled + loading, settingsModelPickerTrigger.
  2. StaleCurrentModel keeps the connection. It now pins a model that was dropped from a still-configured connection (anthropic-team), labelled with the raw model id the session carries rather than a hand-written string. The leading row surfaces the dropped model and the connection's remaining models still follow underneath — the "keep the connection, remove only the model" option you suggested.
  3. P3 plays. ManyConnections now selects the last group's model and asserts the trigger reflects it (reachability + selection, not scroll geometry). LongModelNames states its scope: it verifies the long-labelled item is reachable; whether the long text truncates/wraps within the menu is a visual check, not asserted.

Smoke re-run: 201 stories green, per-story AX audit clean (saving-default-model replaces catalog-loading).

On visual evidence: I'm working in a headless/CI environment and can't attach screenshots to the PR directly. The states render deterministically under stable ids (product-model-picker--*); happy to capture and host PNGs somewhere if there's a preferred channel, or you can pull the branch and screenshot the open-menu states at normal/narrow widths in light/dark.

@liuxiaocs7
liuxiaocs7 requested a review from Astro-Han August 29, 2026 20:20
@liuxiaocs7

Copy link
Copy Markdown
Member Author

Added a Visual evidence section to the PR description (screenshots hosted on the fork's release assets): the re-modelled saving-default-model, plus the open-menu states for many-connections, long-model-names, and stale-current-model — normal width, light + dark. The picker's container is a fixed width, so a narrow viewport ≈ normal for this surface; I captured both themes instead of a redundant narrow width.

Second surface under apache#3944 (one surface per PR): extend `Product/Model Picker`
with the failure and edge states that never show up in normal use, driving the
real production components with real props.

- SavingDefaultModel — the Settings default-model row with `ModelPicker.loading`
  while a pick is being saved: catalog present, row disabled (a catalog-
  unavailable row renders a skeleton elsewhere).
- NoModelsAvailable — the composer's "configure a connection" chip; a failed /
  offline / unauthorised catalog fetch collapse to this one honest state.
- ManyConnections — seven connection groups; the play asserts the grouped
  structure and last-group reachability (selection/scroll are contracts left to
  focused tests — review feedback).
- LongModelNames — a long connection name, model labels, and descriptions.
- StaleCurrentModel — a session whose connection is still configured but whose
  pinned model was dropped from that connection's catalog: the leading-row
  branch, labelled with the raw model id, connection kept.

Refs apache#3944, apache#3893

Generated-by: Claude Code
@liuxiaocs7
liuxiaocs7 force-pushed the liuxiaocs7/model-picker-failure-states branch from ba6d077 to 4cc7d9e Compare August 29, 2026 21:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/M Under 500 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants