refactor(composer): make the + menu one entry with one kind of row - #3198
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
Included review availability: Your plan provides up to 3 included reviews per hour; 2 remain after this review. 📝 WalkthroughSummaryThis PR replaces separate Plan, Swarm, and Graph toggles with one mutually exclusive The shared session-mode mapping becomes the source of truth for UI state, slash commands, persisted fields, and IPC updates. The IPC boundary validates legal field pairs and persists both fields atomically. This removes parallel mode-update paths. This is the smallest coherent solution identified in the current diff. The shared mapping and validation are necessary to keep UI, persistence, and IPC behavior consistent. The unified state also removes independent toggle combinations. The PR standardizes menu density, selection indicators, palette styling, divider rendering, and radio-group accessibility labels. It removes mode-specific copy that the unified mode contract no longer needs. The deleted Reported validation includes Storybook measurements, typechecks, formatting, linting, and affected tests. Repository-wide suites were not run. Required-check status remains unverified from direct evidence. Complexity delta
Total maintenance complexity decreases. The added shared authority, validation, and focused tests are justified by the removed parallel paths. Potential simplification is limited. The removed story and obsolete copy are already deleted. Further deletion could weaken coverage of IPC validation, atomic persistence, or divider behavior. Review-relevant risks
WalkthroughThe change replaces separate Plan, Swarm, and Graph state with one ChangesSession mode unification
Estimated code review effort: 4 (Complex) | ~45 minutes Merge Risk: ⚪ Minimal · up to The PR makes the composer modes a single four-way choice and applies both Session fields together, preventing invalid combinations and transient split states. No actionable merge-blocking risk remains after normal checks and review. Sequence Diagram(s)sequenceDiagram
participant Composer
participant AppShell
participant PreloadBridge
participant SessionModeIPC
participant SessionConfiguration
Composer->>AppShell: onSessionModeChange(mode)
AppShell->>PreloadBridge: setSessionMode(sessionId, mode fields)
PreloadBridge->>SessionModeIPC: sessions:setSessionMode
SessionModeIPC->>SessionConfiguration: persist both mode fields
SessionConfiguration-->>AppShell: updated session summary
AppShell-->>Composer: active mode and transition state
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
PR Summary by QodoUnify composer session modes into one exclusive menu
AI Description
Diagram
High-Level Assessment
Files changed (8)
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
apps/desktop/src/renderer/app-shell.tsx (1)
2264-2277: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winRoute slash-command mode changes through
setSessionModeWhen
collaborationModeisplan,/swarm onand/graph onupdate onlyorchestrationMode, leaving Plan enabled. UsesetSessionModeso the existing Plan-exit confirmation and invariant apply to these commands.packages/ui/src/composer.tsx (1)
1357-1371: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMake the
@maka/uistyling ownership explicit.Composerowns the markup, but the radio-indicator override exists only inapps/desktop/src/renderer/styles/composer.css.packages/ui/src/styles.cssdoes not contain the rule and no current host imports it. Move the rule to the shared stylesheet and import that stylesheet in each host. Otherwise, document that thisComposermode UI is desktop-only.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 65055659-dac3-4a76-a9af-540ed407cfea
📒 Files selected for processing (8)
apps/desktop/src/renderer/app-shell.tsxapps/desktop/src/renderer/locales/shell-copy.tsapps/desktop/src/renderer/styles/astryx-mount.cssapps/desktop/src/renderer/styles/composer.cssapps/desktop/stories/app-shell.stories.tsxpackages/ui/src/components.tsxpackages/ui/src/composer.tsxpackages/ui/src/conversation-copy.ts
Included review availability: Your plan includes up to 3 reviews per rolling hour; 1 remains after this review.
Code Review by Qodo
1.
|
There was a problem hiding this comment.
Actionable comments posted: 2
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: b27bf9b5-df0d-439c-9f02-1bc3f8002fdf
📒 Files selected for processing (10)
apps/desktop/src/main/__tests__/session-mode-ipc-main.test.tsapps/desktop/src/main/__tests__/session-mode.test.tsapps/desktop/src/main/runtime-host-session-catalog-ipc-main.tsapps/desktop/src/preload/bridge-contract.d.tsapps/desktop/src/preload/preload.tsapps/desktop/src/renderer/app-shell.tsxapps/desktop/src/renderer/locales/shell-copy.tsapps/desktop/src/renderer/session-mode.tspackages/ui/src/__tests__/composer-plus-menu.test.tsxpackages/ui/src/composer.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/desktop/src/renderer/app-shell.tsx
Included review availability: Your plan includes up to 3 reviews per rolling hour; 2 remain after this review.
9f57be1 to
e050cba
Compare
c05d4c7 to
c759107
Compare
M4n5ter
left a comment
There was a problem hiding this comment.
English
Requesting changes on head c759107.
The radio-group consolidation and atomic Desktop configuration patch are sound. One non-duplicate authority gap remains: the legal mode-pair check exists only in Desktop IPC, while Runtime Host session.create / session.configuration.update and the supported CLI can still persist plan + swarm/graph.
This changes the disposition of the resolved legacy-pair discussion. The remaining problem is not only historical normalization: new illegal pairs can still be created after this PR, and Desktop then masks them as Plan while slash-command status and mutation disagree.
Please enforce the invariant at the shared Runtime Host create/update authority and cover a non-Desktop client path. Existing-record normalization can remain separately tracked if desired.
No other P0–P2 issue was found. All GitHub checks are green; the focused Desktop build, renderer typecheck, mode/IPC tests, and Composer menu tests pass locally.
简体中文
建议在 head c759107 上 Request changes。
Radio group 收敛和 Desktop 原子 configuration patch 本身是合理的。目前还剩一个未与现有 review 重复的 authority 缺口:合法 mode pair 只在 Desktop IPC 校验,而 Runtime Host 的 session.create / session.configuration.update 以及受支持的 CLI 仍可持久化 plan + swarm/graph。
这会改变此前已解决 legacy-pair 讨论的处置结论。剩余问题不只是历史记录 normalization:本 PR 合并后仍能创建新的非法 pair;Desktop 随后将其掩盖为 Plan,slash command 的状态查询和 mutation 也会互相矛盾。
请在所有 client 共用的 Runtime Host create/update authority 强制执行该 invariant,并增加一条非 Desktop client 路径的回归。已有记录的 normalization 如有需要可以继续单独跟踪。
除此之外未发现其他 P0–P2。GitHub checks 全绿;本地 focused Desktop build、renderer typecheck、mode/IPC tests 和 Composer menu tests 均通过。
|
Thanks for pushing on the authority question — it turned out this PR invented the invariant you asked me to enforce. Plan and orchestration are two fields with two lifetimes that Runtime honours together, and fusing them into one choice was quietly clearing the user's Swarm default on the way into Plan. So the fusion is gone rather than pushed down to Runtime Host, and nothing is added there. Details in the thread. Re-requesting review 🙏 感谢指出这个 authority 问题——顺着查下去发现,你要求强制的这条 invariant 是本 PR 自己发明的。Plan 和 orchestration 是两个生命周期不同的字段,Runtime 本来就同时承认它们;把它们合成一个四选一,会在进入 Plan 时悄悄清掉用户的 Swarm 默认值。所以删掉的是这个合并,而不是把它下推到 Runtime Host——Host 一行未加。细节见 thread。 AI disclosure: Claude Code (Opus) wrote the diff, the commit message and this comment. The adjudication of the finding, and the decision not to enforce at Runtime Host, are the human contributor's. Verified locally: four desktop tsconfig projects, |
The menu mixed two Astryx item components and offered a choice the runtime cannot honour. Plan, Swarm and Graph were three independent checkbox items, but Swarm and Graph both write `orchestrationMode`, so turning one on silently turned the other off — and Plan strips subagent-category tools and the agent-graph tools (`plan-mode.ts`), which is what Swarm and Graph are made of. The four states were always one choice. So the modes become one radio group of four, `default` included because it is the way out of the other three. `sessionMode` + `onSessionModeChange` replace the three switch prop trios, and app-shell's `setSessionMode` is the single writer that sequences the two Session fields the choice lands in. Leaving Plan can be refused (a pending proposal asks first), so the chain stops there rather than applying half of the change. The rows now share one rhythm. `DropdownMenuCheckboxItem` never applies the menu's sm density, so its rows stood 36px against the 28px action rows above them, and its marker column pushed their icons 28px to the right. Radio rows already had a product rule for that density; the selection mark moves to `endContent` as Astryx's own `check` indicator — what its Selector puts on a chosen option — and composer.css suppresses the radio circle for this panel alone. Measured live in Storybook: six rows, 28px each, icons on one x. The + and permission menus also join the palette seam in astryx-mount.css. Astryx renders these panels in place rather than portaling them, so without the seam they painted from the neutral theme: the menu's own accent was near-black while the footer's mode mark beside it was Maka blue, for the same state. Generated-by: Claude Code
Review found four defects in the mode control. They are one defect: the mode is one choice out of four, but the authority holds it as two independently-writable fields, and the renderer computed each transition from a value it derived by reading both. - The write depended on the read. While a Session summary was still loading, the placeholder made the derived mode read `default`, so choosing Swarm skipped the "leave Plan" step and wrote Plan-plus-Swarm. - The chain re-read `activeIdRef.current` per step. A Plan-exit confirmation is an await, so opening another Session while it was up finished the transition on that one instead. - `/swarm on` and `/graph on` called the orchestration setter directly, which is a second path to the same fields and could persist the combination the menu had just been rebuilt to prevent. - Two writes are not atomic, and nothing said which order was safe. So `session-mode.ts` now owns the mapping, and every mode names BOTH fields. A transition is computed from where it is going, never from where the renderer thinks it is, and a stale read can no longer produce a between-modes write. `applySessionMode(mode, sessionId)` is the only caller of either setter: it takes its Session as an argument rather than re-reading it, gates on `getPlanState` — the Host, not the projection — and both slash commands route through it. The three per-field writers are gone, and with them the two pending registries and six copy strings. The order is now a stated rule: the field taking a non-neutral value is written last, so no intermediate state is ever an illegal combination. A test walks every starting pair through every target and fails if the order is reversed. Two writes are still two mutations, so a failure on the second leaves the Session in `default` with its prior mode lost and a toast; the opposite order would fail into Plan-plus-orchestration instead. Making that atomic means one persisted field, which is a Runtime Host change, not a renderer one. The mode control is also disabled while the Session summary is loading — now a matter of not showing a mode that is not known yet, rather than of correctness. Generated-by: Claude Code
The divider between the action rows and the mode group rendered on `onSessionModeChange` alone. Every prop feeding this menu is optional, so a host that wires only the mode choice — which `packages/ui` allows and the SSR contracts exercise — opened the menu on a rule with nothing above it to divide. The desktop app always wires attachments, so this was a defect in the component's contract rather than a visible one. The condition now asks what it means: is there an action row above. Generated-by: Claude Code
The mode is one choice held in two persisted fields, and the renderer was writing them one at a time. A failure on the second write left the Session in neither the mode it had nor the one that was asked for. The previous commit could only pick which of the two failures to have — it ordered the writes so the state between them was at least legal. There was nothing to consolidate at the Host: `session.configuration.update` already carries the WHOLE configuration against the Session revision, and `updateSessionConfiguration` already merges a patch into it. The desktop simply never exposed a bridge method that carried both mode fields, so it sent two patches where one would do. Now `sessions:setSessionMode` takes both fields and the pair lands in a single revision-checked mutation. A failure changes nothing, so the ordering rule and its property test are deleted along with the two per-field bridge methods and IPC channels — there is no longer an intermediate state to reason about, and no way to write one field alone. The naming was also wrong. `collaborationMode` and `orchestrationMode` are not two halves of one value: the orchestration field is a per-turn DEFAULT that `turnOrchestration` overrides, which is why an AgentRun records `orchestrationSource: 'session' | 'turn_override'`. They are two fields of different lifetimes that the composer's four modes happen to address together, and that is the only place they are one choice. Generated-by: Claude Code
Review asked whether the radio-indicator override belongs in `packages/ui/src/styles.css` rather than in the host. It does not, and the reason is worth writing next to the workaround: all 40 `.maka-composer-*` rules are the host's, so moving this one would make it the only composer rule on the other side of the split. The comment now says that, so the next reader does not have to measure it. Generated-by: Claude Code
Each field was checked for being a valid value; the pair was not. A frame naming `plan` with `swarm` passed both checks and persisted, which is a Session the runtime cannot honour: Plan strips the subagent-category and agent-graph tools that Swarm and Graph are made of. Today the only caller builds its frame from `SESSION_MODE_FIELDS` and cannot produce that pair. That is the argument for checking it here rather than against it — the boundary that can persist the state is the one that has to refuse it, instead of trusting every present and future caller. `session-mode.ts` moves to `src/shared/`, which main, preload and renderer already share, so the boundary and the control test the same predicate instead of keeping two copies of one rule. Generated-by: Claude Code
…own reason `plusMenu` sliced the markup on the menu's marker class, but `split` returns the whole markup when the marker is absent, so a composer that stopped rendering the menu still satisfied the divider-absence assertion. It now asserts the marker before slicing. The session-mode IPC stub answered `recover()` without `failed`, which the registration iterates. The rejection is swallowed today, so the stub would only surface as an unrelated failure in whatever test touched it next. Generated-by: Claude Code
Review asked for the legal mode pair to be enforced at the Runtime Host authority, because Desktop's IPC check was the only thing preventing `plan + swarm`. Tracing where that invariant should live found that it should live nowhere: this PR invented it. `collaborationMode` and `orchestrationMode` are two fields with two lifetimes. Plan is a temporary excursion Runtime ends by itself — `finalizePlanCollaborationMode` and `finalizePlanAbandonment` write the Session back to `agent` on approval or abandonment and never touch orchestration (`session-manager.ts`). Orchestration is the Session's standing default for how a turn fans out, which `turnOrchestration` overrides per turn. Runtime resolves the overlap by stripping the subagent-category and agent-graph tools while planning (`plan-mode.ts`), validates the two fields independently, and rejects no combination of them anywhere. So `plan + swarm` means "planning now, Swarm for the execution afterwards" — a state the Session can hold. Fusing them into one four-way choice is what made a legal state look illegal, and it cost the user real state: entering Plan from Swarm wrote `orchestrationMode: 'default'`, and since Runtime leaves Plan on its own after approval, Swarm was gone for the execution the plan was written for. `main` did not do that — the Plan switch there wrote its own field only. So the fusion goes, along with everything built on it: `ComposerSessionMode`, `shared/session-mode.ts` and its projection, the pair check at the IPC boundary, and the two-field bridge method. Composer takes a Plan toggle and an orchestration choice; app-shell has one writer per field, each a patch into `updateConfiguration` beside `setPermissionMode`; the renderer derives no mode at all, so there is nothing to keep in sync. Nothing is added at Runtime Host: with the invariant gone there is no illegal pair for it to refuse, and the `/swarm status` versus `/swarm off` disagreement the review named came from the projection that is now deleted. Swarm and Graph stay one radio group — one field, three values, and turning one on silently turned the other off. That defect was real; it just was not Plan's. The menu keeps one rhythm without pretending to be one control. A toggle and a one-of-N choice keep their honest roles (`menuitemcheckbox`, `menuitemradio`); composer.css puts both on the menu's sm density and suppresses both markers, so each row is 28px with the check at its tail. The upstream ask widens from radio items to both selectable item types. `PlanAndSwarmModeOn` comes back. This PR deleted it as unreachable, and it is the state the argument above is about. Verified: `session-mode-ipc-main.test.ts` fails on the Plan write when the handler is made to carry `orchestrationMode` again; `composer-plus-menu.test.tsx` covers both roles and both marks. Four desktop tsconfig projects, `@maka/ui` typecheck, biome format and lint clean. Generated-by: Claude Code
Plan, Swarm and Graph are all things to turn on. "Neither orchestration" is what the Session is when nothing is on, not a third thing to pick, so a `Default` row was answering a question the user never asked — it existed only because a radio group cannot be deselected, which is a control's limit rather than the product's shape. So all three become checkbox rows. Turning Swarm on sends `swarm`, turning it off sends `default`, and because the field holds one value, naming the one being turned on is also what turns the other off. That happens in the open menu with both rows visible, so the exclusivity the field imposes is watched rather than discovered. The menu is now one kind of row throughout, which is what the PR set out to do and could not honestly claim while a one-of-N group was in it. `orchestrationOptions` loses its neutral entry, the radio group and its accessible-name string go, and composer.css drops the radio-circle suppression it no longer has anything to suppress — the density rule keeps both roles, because the permission menu beside this one is still a real radio group. Measured in Storybook (DefaultLayout): five rows, 28px each, every mode icon on the same x as the action rows above, panel 144x167, nothing checked. The PlanAndSwarmModeOn story reads back both rows checked and both footer marks. Generated-by: Claude Code
9d52187 to
4fc463c
Compare
M4n5ter
left a comment
There was a problem hiding this comment.
English
Requesting changes on head 4fc463c.
The previous Runtime Host authority discussion is correctly resolved: Plan and orchestration are independent Session fields, and plan + swarm/graph is valid. One P2 remains in the latest UI revision: Swarm and Graph still share one three-valued field, but they are exposed as independent checkboxes, so the original silent replacement remains for assistive-technology users.
No other P0–P2 issue was found. The rebased patch series is unchanged, and all current required checks pass.
简体中文
在 head 4fc463c 上建议 Request changes。
此前 Runtime Host authority 的讨论已经正确解决:Plan 与 orchestration 是两个独立的 Session 字段,plan + swarm/graph 是合法状态。目前最新 UI revision 仍有一个 P2:Swarm 和 Graph 共享同一个三值字段,却被表达为两个独立 checkbox,导致原先的静默替换问题仍会影响辅助技术用户。
除此之外未发现其他 P0–P2。rebase 前后的 patch series 没有实质变化,当前所需检查全部通过。
Swarm and Graph are the two values of one Session field, and a run carries one orchestration, so choosing one is choosing away from the other. Two checkbox rows said the opposite: each announced itself as an independent boolean, and the row that turned off was never announced at all, so a screen-reader user turning on Swarm was not told Graph had gone. That is the silent replacement this PR set out to remove, still present for the users least able to see it. They are now one radio group, which is the control Astryx directs a one-of-N choice to and the one that carries the relationship: the rows are announced as a named set, and picking one moves the set. No neutral row comes back with it. `default` is not a third way to fan a turn out, it is the absence of one, so the group holds no selection at rest -- `DropdownMenuRadioGroup` takes `value: string | undefined` for exactly that. The way back is the selected row itself: a radio item reports its value on every activation, so re-activating the one already chosen empties the group. A native radio does not do that, but an empty group is a state the role expresses, and the change lands on the row the user is on rather than on the row they left. Nothing moves. Both selectable item types draw a control at the row's start, so the suppression rule that already hid the checkbox box now hides the radio circle too, and the tail check stays the mark: five rows at 28px, every icon on x=532, panel 144x167 -- the same numbers as before this commit. Generated-by: Claude Code
M4n5ter
left a comment
There was a problem hiding this comment.
English
LGTM. The radio-group relationship is now correct, and the remaining reactivation behavior is an intentional design choice. All current checks pass.
简体中文
LGTM。radio group 的关系已经正确,剩余的再次激活行为属于明确的设计选择。当前检查全部通过。
Summary
The composer's + menu offered Plan, Swarm and Graph as three
DropdownMenuCheckboxItems. That was two problems, and they are not the same problem.The rows did not share the menu's rhythm.
DropdownMenuCheckboxItemnever applies the menu'ssmdensity, so its rows stood 36px against the 28px action rows above them, and its marker column pushed the mode icons 28px to the right of the action icons.Swarm and Graph are not independent. They are the two values of one Session field,
orchestrationMode, and a run carries one orchestration — so turning one on turned the other off, with nothing said about it.What the menu is now
Plan keeps its checkbox. It is a Session field of its own (
collaborationMode), a temporary collaboration excursion that Runtime ends by itself on approval or abandonment, and it really is independent of the other two: a Session can be planning with Swarm armed for afterwards, and this PR has a story for that state.Swarm and Graph are one
DropdownMenuRadioGroup— the control Astryx directs a one-of-N choice to, and the one that carries the relationship. The rows are announced as a named set, so picking one moves the set instead of silently moving a row the user is not on.No neutral row.
defaultis not a third way to fan a turn out, it is the absence of one, so the group holds no selection at rest —DropdownMenuRadioGrouptakesvalue: string | undefinedfor exactly this. The way back is the selected row itself: a radio item reports its value on every activation, so re-activating the chosen one empties the group. A native radio does not deselect; an empty group is a state the role expresses, and the change lands on the row the user is standing on rather than on the row they left.Density and alignment. Both selectable item types draw their own control at the row's start, so the selection mark moves to
endContentas Astryx's owncheckindicator — what itsSelectorputs on a chosen option — and a rule scoped to this panel suppresses the box and the circle. Five rows at 28px, every icon on one x.The + and permission menus also join the palette seam in
astryx-mount.css. Astryx renders these panels in place rather than portaling them, so without the seam they painted from the neutral theme: the menu's own accent was near-black while the footer's mode mark beside it was Maka blue, for the same state.Upstream ask
Let a selectable menu item choose its indicator, and the
endContent+ scoped-CSS pair here goes away.Before / after
Shot live in Storybook (
Product/Shell Official AppShell), same viewport, each panel cropped to its own bounds and composited at 1:1 pixel scale — the UI is never resampled.How the design got here
Two things I had wrong, both corrected in review. The diff shows the destination, so the argument is worth stating.
The modes are not one choice. The first version fused Plan and orchestration into a single
sessionModeprop and one radio group of four. That is wrong at the authority: Runtime ends a Plan by writingcollaborationModealone (session-manager.ts), and resolves the Plan/orchestration overlap by stripping the subagent and agent-graph tools while planning (plan-mode.ts). It never writes the two together, and nothing validates the pair —plan + swarmis a legal Session state and was reachable before this PR. Fusing them also destroyed the orchestration default the plan was written to execute with: Runtime leaves Plan by itself after approval, and Swarm never came back. So the two fields get two props, two IPC channels and two writers, and the write that leaves Plan touches nothing else.defaultis not a fourth option. It has no behaviour of its own — it is how the field spells "none". So it has no row, and the group carries it as no selection.Verification
DefaultLayout, 1440×900 at 2× DPR). Before: panel 144×180, rows 28/28/36/36/36, mode icons at x=560 against action icons at x=532. After: panel 144×167, all five rows 28px, every icon at x=532, no indicator painted, nothing checked, and the orchestration pair inside onerole="group"labelled "Orchestration mode".PlanAndSwarmModeOnreads back one checkedmenuitemcheckbox, one checkedmenuitemradio, and two footer marks.packages/ui/src/__tests__/composer-plus-menu.test.tsx: the divider renders only with a row above it; Plan is onemenuitemcheckboxand the orchestration options are twomenuitemradioin one named group; nothing is checked at rest; Plan and Swarm are checked together.apps/desktop/src/main/__tests__/session-mode-ipc-main.test.ts: the Plan write carriescollaborationModealone, the orchestration write carries its own field alone, a Plan Session keeps the orchestration default it was carrying, and an unknown value is refused rather than persisted.orchestrationModeagain turns two tests red, and restoring the unconditional divider turns another red.npm run astryx:surface-inventory, all fourapps/desktoptsconfig projects, the@maka/uisuite,npm run format:checkandbiome linton the touched files — clean.AI use
Tool(s) and scope: Claude Code (Opus) wrote the diff and the commit messages, and measured the result in Storybook. The problem framing, the "one entry, one kind of row" constraint, the rejection of the fused mode field, and the rejection of the neutral row are the human contributor's. Each commit carries a
Generated-by: Claude Codetrailer.Checklist
Does this PR entail a change in behavior?
Not a fast-path candidate
This changes material user-visible behaviour (the + menu the user touches on every turn) and two public contracts (
Composer's props inpackages/ui, andbridge-contract.d.ts), so it needs independent human review per AGENTS.md. It touches none of the protected architecture areas — Runtime Host,@maka/eval, the Harbor/Pier executor andmaka evalare untouched — and it reverts cleanly as one branch, but neither of those makes it low impact. The CodeRabbit and Qodo reviews on this PR do not count as independent human review.Review focus
The two mode fields and their two writers in
app-shell.tsx:applyPlanModecarries the Plan-exit confirmation and thegetPlanStategates,applyOrchestrationModeis a plain write, and neither touches the other's field. Incomposer.tsx, the orchestration group'sonChangeis where "activate the chosen row to leave it" lives.