Conversation
…x menu Co-authored-by: Cursor <cursoragent@cursor.com>
|
@Astro-Han Can you take a look? |
hqhq1025
left a comment
There was a problem hiding this comment.
I reviewed the current head. It replaces the native composer model and thinking controls with one Astryx menu and adds a Fast toggle backed by the per-model override update path. I found one P2 in the new Fast write path; see the inline comment. Please add a regression with a pending first save, a second toggle, and a switch to another Host before the queued write resumes.
I built core and ran four focused core/UI test files from emitted JS: 50/50 passed. The local UI TypeScript build failed on component-contract errors involving settledText, autoScroll, menuAnchorRef, and trailingAction; menuAnchorRef is present on the PR base, but I did not establish the cause of the complete failure set. Fresh-main merge-tree and diff check are clean. The current head has a successful label check but no hosted test result, so the gate is not green. I did not run Electron interaction or the full suite. This is not a merge approval.
Automated review notice: This comment was posted by an automated review agent operated by hqhq1025. It is not an independent human review and does not replace one.
| if (!model) return Promise.resolve(); | ||
| const key = `${model.connectionId}\u0000${model.model}`; | ||
| const task = tailRef.current.then(async () => { | ||
| const connection = latest.current.connections.find( |
There was a problem hiding this comment.
[P2] Keep queued Fast writes bound to the Host selected at click time. The click captures model, but the deferred task looks up latest.current.connections here and later passes latest.current.host to connections.update. Reachable ordering: on session A, click Fast on and then off while the first update is pending; switch to session B on another Host before the second task starts. AppShell replaces connections with the B snapshot (app-shell.tsx:523-526). If the A connection is absent, this branch returns successfully without saving off or reporting an error. If identities overlap, the write instead targets B. Capture the Host and connection context for each queued intent, or explicitly reject a stale intent rather than silently completing it.
hqhq1025
left a comment
There was a problem hiding this comment.
Reviewed commit 5d366e845db4bf96646a5b63caf66cdbd48d7e2d. I found no substantiated P0–P3 issue in the reviewed paths.
The follow-up fix captures the model, connection snapshot, Host reference, and callbacks when Fast is clicked, before the queued task runs (use-composer-model-options.ts:58-85). This removes the previously reported path where a queued Fast write could follow the composer to a different Host. I also checked the model/effort/Fast menu, the service-tier override gate and optimistic update path, and the new copy. There is no schema or migration change.
Node 24 build:test passed after applying the repository's dependency patches; 36 focused core/UI/Desktop tests, focused Biome, ASF headers, and git diff --check passed. The branch merges cleanly with main 03237142. There are no hosted checks on this head. I did not run an Electron end-to-end session-switch race, so the click-time Host behavior is supported by code inspection, not a dynamic regression test. This is a review, not merge approval.
Automated review notice: This comment was posted by an automated review agent operated by hqhq1025. It is not an independent human review and does not replace one.
There was a problem hiding this comment.
Correction to my earlier review on this same commit: I missed the repository architecture check. npm run check:app-shell-hooks fails on the new hook call in AppShellContent; the hosted test check is red for the same reason. This is one P2 merge blocker, so my prior “no P0–P3” conclusion is withdrawn. The click-time Host binding assessment and other reported focused checks remain unchanged. This is not merge approval.
Automated review notice: This comment was posted by an automated review agent operated by hqhq1025. It is not an independent human review and does not replace one.
| const composerOptionsHost = activeId && activeSession?.profileId && activeSession.runtimeHostId | ||
| ? { profileId: activeSession.profileId, hostId: activeSession.runtimeHostId } | ||
| : newTaskHost; | ||
| const composerModelOptions = useComposerModelOptions({ |
There was a problem hiding this comment.
[P2] The new useComposerModelOptions call fails npm run check:app-shell-hooks: the shell hook inventory rejects a new hook in AppShellContent without a feature-provider move or an explicitly justified inventory entry (per #4109). The current-head hosted test check is red, so this cannot merge as-is.
hqhq1025
left a comment
There was a problem hiding this comment.
Reviewed commit 0c93898818d18c3d3cec65ef288b3d55d083af35. I found no new substantiated P0–P3 issue in the reviewed paths.
The previous P2 architecture-check failure is fixed: the Fast write hook now sits behind useShellChatModel, uses the conversation services port, and is no longer called directly from AppShellContent. npm run check:app-shell-hooks passes. The click-time model, connection and Host binding from the prior fix remains in place. The connection update emits connection_list_changed, which the shell handles by refreshing projections. I also checked the recovery-picker test adaptation and the Astryx inventory update. No schema or migration change.
Node 24 build:test, 40 focused core/UI/Desktop tests, focused Biome, ASF headers, git diff --check, and the app-shell hook check passed. The branch merges cleanly with main ae71ab31. No hosted checks are reported on this head yet. I did not run a real Electron session-switch race or a full packaged Desktop test; these remain validation gaps. This is not merge approval.
Automated review notice: This comment was posted by an automated review agent operated by hqhq1025. It is not an independent human review and does not replace one.
Astro-Han
left a comment
There was a problem hiding this comment.
Reviewed exact head 0c93898818d18c3d3cec65ef288b3d55d083af35.
P2: the Fast toggle can get permanently stuck after an outside edit (use-composer-model-options.ts:73-75, inline). When the stored override differs from the composer's last saved value, the hook assumes its snapshot is stale and sends that last save as expected. The same difference appears when something else really changed the override, for example the context window or Fast in Settings, or another window. The Host rejects the write because expected doesn't match, and rememberedRef only updates on success. Every later toggle then fails with "Model parameters changed" until reload. I reproduced this by running the compiled hook against a fake Host that does the same expected check.
P3s (inline):
- The only new test covers
modelOverrideForServiceTier; the new hook and Fast row have none. - A new-task Fast write can target a different Host than the one whose connections the menu shows.
- The menu isn't keyed by session, so an in-flight pick carries over briefly.
- The model list lost search and grouping (a product question).
Session and Host pinning at click time, the Host's rejection of outdated writes, keeping the other override fields, failure handling, and the architecture boundaries all look correct.
Checks run, all passing: check:app-shell-hooks, check:asf-headers, check:renderer-architecture, check:locale-hygiene, git diff --check, build:test, a desktop renderer type-check, and targeted tests (23/23). These ran on Node 22, not the pinned 24.18.1. I didn't run the full npm test or exercise real-app keyboard navigation.
Automated review notice: This comment was posted by an automated review agent (Claude) operating on behalf of @Astro-Han. It is not an independent human review and does not replace one.
| if (!connection) throw new Error(`Connection is no longer available: ${model.slug}`); | ||
| const stored = modelOverride(connection, model.model) ?? null; | ||
| const remembered = rememberedRef.current?.key === key ? rememberedRef.current.override : undefined; | ||
| const expected = remembered !== undefined && JSON.stringify(stored) !== JSON.stringify(remembered) |
There was a problem hiding this comment.
P2: stored !== remembered is treated as "snapshot is stale", but it's also what a real outside edit looks like (for example, changing this model's context window or Fast in Settings after toggling Fast here). In that case we keep sending our old save as expected, the Host CAS rejects it, and since rememberedRef only updates on success, every later toggle fails with "Model parameters changed" until reload. I reproduced this with the compiled hook. Could we remember {before, after} and prefer after only while stored still equals before, and also drop rememberedRef on failure? A hook test for "outside edit after a composer save" would pin this down.
| }} | ||
| > | ||
| {choice?.supportsFast && props.onFastChange ? ( | ||
| <DropdownMenuCheckboxItem |
There was a problem hiding this comment.
P3: Nothing tests this Fast row, the "Model default" → undefined mapping in the effort submenu, or useComposerModelOptions. The only new test is for modelOverrideForServiceTier. Could we add a render test that opens the menu with a supportsFast choice and checks the checkbox calls onFastChange, plus a hook test for serialized writes and the stale/outside-edit expected case?
| ? (activeSession.profileId && activeSession.runtimeHostId | ||
| ? { profileId: activeSession.profileId, hostId: activeSession.runtimeHostId } | ||
| : undefined) | ||
| : (options.executorTarget |
There was a problem hiding this comment.
P3: For new tasks, the connection list comes from taskEntry.selectors.selectedHost (app-shell newTaskHost), but this Host comes from executorTarget, which is undefined when the selected Host has no resolvable project. Then preload falls back to the active runtime Host, not the one whose connections we're showing. Could this use the same Host that feeds newTaskConnections, or hide Fast when it's unknown?
| ) | ||
| : undefined); | ||
| const renderComposerOptions = (): ReactNode => ( | ||
| <ComposerOptionsMenu |
There was a problem hiding this comment.
P3: The old ChatModelSwitcher selector was keyed by session id, so a session switch reset its pending pick and closed it. This menu isn't keyed, so an in-flight model or Fast pick from session A shows on session B's trigger until it settles, and re-picking that value in B is a no-op during that window. Would key={props.activeSession?.id ?? 'new-task'} restore the old behavior?
| }} | ||
| /> | ||
| ) : null} | ||
| <DropdownMenuRadioGroup |
There was a problem hiding this comment.
P3: The previous session picker had search and per-connection groups. This submenu is a flat radio list with neither. Is that intended for users with many enabled models? If Astryx DropdownMenu has typeahead, that may be enough; otherwise it may be worth keeping search for long lists.


Summary
The composer footer currently shows the model and the thinking level as
separate selectors. For native Maka sessions, this PR replaces them with a
single Astryx
DropdownMenu:DropdownMenuSubMenu+DropdownMenuRadioGroup) for thethinking level, including "Model default".
prompt-cache warning stays as the first row when the session has history.
service tier (
supportsCustomFastServiceTier). It writesmodelOverrides[model].serviceTierthrough the existing connection updatepath, using the same optimistic
expectedcheck as Settings.(when not default) and Fast (when on) in muted text, for example
Claude Opus 5.5 1M Medium Fast.The composer model-picker recovery handle (
openModelPicker) now opens thismenu.
The menu is built only from existing Astryx components. Where Astryx lacks
something, the gap is listed under Remaining work rather than worked around.
The one stopgap:
DropdownMenuSubMenuhas noendContent, so the row's currentvalue is rendered inside its
label.Refs #5787
Remaining work (draft)
The replacement is intentionally partial. The old selectors are still used in
these cases:
ExecutorModelPicker+ its thinkingselector) still use the executor popover.
DropdownMenuonly supportspopoverin compound mode.ChatModelSwitcher,NewChatModelPicker,ThinkingLevelSelectorand
ModelChipStaticonce every case above uses the new menu.Astryx capabilities needed first (to be raised before building our own):
endContent/valueonDropdownMenuSubMenu.SelectorhashasSearch).DropdownMenuSubMenuin Electron (it is gated on(hover: hover)with a 150 ms delay).Verification
packages/ui:node --test dist/__tests__/composer-model-picker-recovery.test.js dist/__tests__/executor-model-picker.test.js→ 22 pass, 0 failpackages/core:node --test dist/__tests__/model-thinking.test.js dist/__tests__/llm-connections.test.js→ 28 pass, 0 failapps/desktop:tsc -p tsconfig.renderer.json --noEmit→ cleanpackages/corefiles; the pre-commit hooks (ASFheader audit, protocol epoch guard) pass.
Not run: the full repository test suite, repository-wide lint, and a manual
check in the Electron app (no screenshot attached yet).
AI use
Tool(s) and scope: Cursor agent (Claude). It drafted the unified menu component,
the Fast override helper and the desktop wiring, and updated the tests. I
reviewed and directed the design, including removing the context-window
setting and the custom hover workaround so that only Astryx components are
used.
Checklist
Does this PR entail a change in behavior?