feat(ui): let the prompt-cache notice be acknowledged per Session - #5496
Merged
Astro-Han merged 2 commits intoSep 19, 2026
Merged
Conversation
The in-session model switcher leads its list with the prompt-cache notice (apache#2945, restored as a Selector row in apache#5444) as a disabled first row that comes back on every open; nothing dismisses it. The row is now a regular option whose activation acknowledges the notice for that Session. A nested close button would be unreachable: Selector option children are presentational, Tab closes the panel, and arrow keys skip disabled rows. Activating the row instead reaches a click, Enter on the highlight, and assistive activation alike. Selecting any option closes the Selector, so the acknowledgement bumps a nonce and the remount keyed on it lands open through `isDefaultOpen`, the path `openNonce` recovery already uses; the pending flag is consumed after that mount so a later fresh mount in the same Session stays closed. Acknowledgements are kept per Session for the life of the switcher, and the sentinel never reaches the pending selection or `onChange`. The notice label is passed as a node so it wraps as a sentence instead of start-ellipsizing like a model id. New copy `switchWarningDismiss` in all three locales; the story's play test now acknowledges via ArrowDown and Enter, and a linkedom test covers the click path, the reopen, and the per-Session memory.
Astro-Han
approved these changes
Sep 19, 2026
Astro-Han
left a comment
Contributor
There was a problem hiding this comment.
Verified the mechanism end to end against the real Selector source — clean.
- Sentinel guard holds on every activation path: click / Enter / Space / SR activate all funnel through
selectItem → commitValue → onChange, which is the newonSelectwrapper — the sentinel is intercepted beforeselection.onChange, and even past that thechoiceByValuelookup returns undefined and early-returns. Real values always contain:so no collision. - Nonce-remount is correct: the post-select close lands on the instance being unmounted anyway; the fresh Selector's mount-only effect sees
reopen.pending→surface.show(); consumption is idempotent so StrictMode double-mount can't reopen twice;isReadOnlystill wins (acknowledgement dismisses without reopening). - Discoverability is adequate: warning-tinted glyph + wrapped sentence label + the
switchWarningDismissdescription + X endContent — visually distinct from model rows, and search filtering uses the string label so a model query filters the notice out (can't hit it accidentally mid-search). ArrowDown→Enter acknowledging is strictly better than main, where that chord hit a disabled dead-end. - Tests are real: linkedom mounts the actual switcher through the real Selector/combobox — fails on main twice over, no fixtures.
P3s:
- Acknowledgement doesn't survive the no-session round-trip:
ChatModelSwitcherunmounts wheneveractiveSessionis undefined (composer.tsx rendersNewChatModelPickerinstead), so session-A → home/new-task → session-A re-raises the notice once. Trivial and arguably even desirable, but the code comment's "returning to a Session does not re-raise" overstates the guarantee — either reword or hoist the set to the Composer. - Double-click hazard: the second click of a fast double-click lands on the reopened list where the first model row now sits under the cursor → unintended (but visible and reversible) model switch. Inherent to self-removing rows; just worth knowing.
- Remount artifacts: loses search query/highlight/scroll, replays the popover entry animation, and SRs get a fresh listbox with no "dismissed" announcement — all inherent to the
openNonceworkaround since Selector has nocloseOnSelectopt-out. - Nit: the
openNoncedoc comment doesn't mention the key's new third segment.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The in-session model switcher leads its list with the prompt-cache notice from #2945 (restored as a Selector row in #5444) as a disabled first row. It comes back on every open and nothing dismisses it, so a user who has read it once keeps paying a row of the popup for it in every session with history.
The row is now a regular option whose activation acknowledges the notice for that Session while the switcher remains mounted:
Button/IconButtondo not forwardtabIndex/onMouseDown. The row as its own acknowledgement is operable by every input.isDefaultOpen— the same pathopenNoncerecovery already uses. The pending flag is consumed right after that mount, so a later fresh mount in the same Session (a presentation flip) does not open on its own.ChatModelSwitcherremains mounted, so direct Session switches preserve them. Visiting home/new-task unmounts the switcher and clears this local state; returning from there shows the notice again. The sentinel value never reaches the pending selection oronChange.descriptionline is the newswitchWarningDismisscopy ("Select to dismiss" / "选择即可关闭" / "選擇即可關閉").Refs #2939 (the notice's original design).
Verification
npm run astryx:surface-inventory— passes; generated inventory includesSelectorOption. Inventory tests — 19/19.@maka/uibuild and the focused notice regression test — pass. Scoped Biome lint, repositoryformat:check, andgit diff --check— clean.packages/ui/src/__tests__/chat-model-switcher-notice.test.tsx(linkedom): the notice is the first, activatable row; clicking it removes it without a model switch and the list is open again; a Selector remount in the same Session stays acknowledged while the switcher remains mounted; another Session raises its own; switching directly back to the first stays quiet. Fails onmainatthe notice row is activatable, passes here.@maka/uitest:dist— 528/528.apps/desktoptypecheck:storiesandtsc -p tsconfig.renderer.json --noEmit— clean.biome lint— clean.Product/Model Picker › ExistingConversationplay, en and zh-CN: 20/20 steps, now acknowledging via{ArrowDown}+{Enter}(the pointer path is the unit test), then asserting the list is open, the model rows remain, the trigger still names the current model, and close/reopen stays quiet.smoke:storybook— 380 stories / 412 renders pass.openNoncerecovery.AI use
Select exactly one:
Tool(s) and scope: Claude Code implemented the switcher change, copy, test, story update, and ran the behavior verification under the author's direction and review. Codex regenerated the Astryx inventory, clarified the acknowledgement lifetime and remount documentation, and verified the inventory check/tests, UI build, focused notice test, lint, and formatting.
Checklist
Does this PR entail a change in behavior?