Skip to content

feat(composer): ⌘↩ sends with the other busy action; Settings default (P2) - #9599

Merged
chenmingwei23 merged 1 commit into
mainfrom
feat/shortcuts-send-key-alt-action
Sep 9, 2026
Merged

feat(composer): ⌘↩ sends with the other busy action; Settings default (P2)#9599
chenmingwei23 merged 1 commit into
mainfrom
feat/shortcuts-send-key-alt-action

Conversation

@CrysisDeu

@CrysisDeu CrysisDeu commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Refs #4608 — P2 of the shortcut plan (#4608 (comment), §3). Independent of P1 (#9555); the registry row for this chord follows once P1 lands.

Joseph Dombroski on Slack: "command+return changing the queue v steer approach as with Claude/Codex/etc."

What changes

⌘↩ / Ctrl+Enter = the other busy action, for one send. While a turn is running and the split send button is showing, Enter keeps doing what the split says (steer by default, or queue); the modified Enter does the other one for that message only — the Claude Code / Codex gesture. Idle, the modified Enter is still a plain send. Scope: the enter send mode only. In ctrl-enter the modified Enter is the send key, and in enter-ctrl-newline the user gave it to newline, so those modes keep the split menu as the flip and the Settings copy says so.

  • ChatInput.tsx: fireComposer(alternate?) — strictly === true, because the same callback is an onClick and a MouseEvent must read as default. The flip is ignored whenever the busy split is not available (idle, stopping, no steer path), so it cannot steer a non-steerable slot.
  • BusySendButton.tsx: the split menu names the concrete flip for the current mode ("⌘↵ queues this message instead" / "…steers with this message instead"), and only when the host says the chord is live (altChordAvailable, i.e. the enter send mode); readBusySendDefault / setBusySendDefault expose the global default.

Settings → Chat › Composer: "What Enter does while the agent is working" (Steer / Queue). The default every session inherits when its split button was never touched; sessions with a per-slot choice keep it. Persisted under the existing unscoped key the per-slot fallback already reads (that fallback is exactly "the default"), and mounted composers without a scoped choice move live.

Crew-member DMs: #8852 (steer-only composer) is not merged, so nothing to preserve here; when it lands, a steer-only surface should pass no queue path and the flip becomes a no-op by construction.

Screenshots (fixture harness, scripts/capture-send-key-alt-action.mjs)

before (main) after
Settings → Chat › Composer, light before light after light
Settings → Chat › Composer, dark before dark after dark
Settings → Chat › Composer with "⌘/Ctrl+Enter sends" selected (no-chord description), light / dark ctrl-enter light ctrl-enter dark
Busy split menu with the chord hint, light / dark menu light menu dark

Tests (written, not run locally — CI runs them)

  • ChatInput.test.tsx: Ctrl+Enter queues in steer mode and is one-shot; steers in queue mode; plain send when idle; queues when the slot has no steer path; in ctrl-enter mode it is the send key not a flip; the split button click never flips; the menu names the chord.
  • BusySendButton.slotScope.test.tsx: setBusySendDefault writes the unscoped key, moves consumers without a scoped choice live, leaves scoped ones alone, and later slots inherit it.

Local gates: tsc --noEmit -p tsconfig.app.json clean on touched files, eslint, I18N_BASE_REF=origin/main node scripts/i18n-check.mjs 19/19 PASS, settingsRegistry.gen.ts regenerated (190 entries).

i18n

4 new keys (components.chatInput.alt_action_hint, pages.settings.chatPanel.what_enter_does_while_the_agent_is_working, …busy_alt_action_desc, …busy_alt_action_desc_no_chord) across the 12 hand-maintained catalogs + en-XA regenerated.

Review responses (head 437c897a5)

  • FP — busyAltChordLabel duplicates platformShortcut('Cmd+Enter'): applied; helper deleted, both call sites use platformShortcut, so the chord renders the same way as the existing Cmd+Shift+Enter label in the same composer (⌘↵).
  • UX — menu hint promised a flip two send modes don't have: applied; the hint renders only when the host passes altChordAvailable (the enter mode), and names the concrete action for the current mode ("queues this message instead" / "steers with this message instead"). Test added for both.
  • UX — precedence sentence: reworded to "Chats where you already chose a mode from the send button keep that choice."
  • UX — no screenshot of the no-chord description: added (row above), captured with "⌘/Ctrl+Enter sends" selected.
  • FP watch — repurposing ⌘↩ in enter mode rests on one Slack line: the plan comment on Make built-in keyboard shortcuts rebindable — fixed Option/Ctrl chords collide with macOS cursor movement #4608 (§3) specifies exactly this: "Running turn: Enter performs the default busy action, ⌘↩ / Ctrl+Enter performs the other one for that single send — the Claude Code / Codex gesture." Idle ⌘↩ is unchanged, so the only users affected are those who pressed ⌘↩ while a turn was running meaning "same as Enter" — the split button still does that.

Round 2 (head e2775e069)

  • Frontend Tests ×3 (i18n style gates): my new catalog values broke three locale style rules — ko placeholder 조사 must be written in both forms ({{chord}}(으)로, {{chord}}을(를)), hi must address the user as तुम not आप, zh-CN uses curly quotes not corner brackets. All three fixed in the catalogs.
  • UX — default-vs-per-chat relationship only implied: the Settings description now says "This is the default; chats where you already chose a mode from the send button keep that choice."
  • GPT lane came back "incomplete" on the previous head; it re-runs on this one.

@CrysisDeu
CrysisDeu requested a review from a team September 9, 2026 01:31
@CrysisDeu
CrysisDeu requested a review from a team as a code owner September 9, 2026 01:31
@CrysisDeu
CrysisDeu requested a review from dwu96 September 9, 2026 01:31
@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — ✅ PASS

Design-level review of e2775e0698a61956ec7b5d7357d7c4e8cc61907a — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

Design-Verdict: PASS

A named user gesture (#4608 §3), scoped to the one send mode where the chord is free, with per-slot choices, migration key, and uiPrefs backup all reconciled.

The one shape decision worth noting held up under inspection: reusing the legacy unscoped mc-busy-send-mode key as the Settings-written global default is the right move, not a hack — it is already the per-slot fallback and already in DURABLE_PREF_KEYS, so the default inherits, persists, and survives origin loss with zero new storage surface. The flip degrading to a no-op wherever the busy split is unavailable keeps the keyboard path and the button path from ever diverging.

Suggestions

  • The busySendModeKey comment still declares the unscoped key "READ-ONLY migration source"; now that setBusySendDefault writes it deliberately, reword that pin to "per-slot controls never write it" so the next reader doesn't treat the Settings writer as a bug.

[DESIGN-REVIEWED] e2775e0

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5) — ✅ PASS

Premise-level review of e2775e0698a61956ec7b5d7357d7c4e8cc61907a — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

All counts verified: readBusySendDefault/setBusySendDefault have exactly one real consumer (ChatPanel.tsx), altChordAvailable has two call sites both in ChatInput, the capture script and committed temp-screenshots/ PNGs each have dozens of established siblings, and the flip's provenance is a linked issue plan plus a named user report. Every inventory item lands justified. Final review:

First-Principles-Verdict: PASS

Verify the intent of BusySendButton.tsx:45-49: it still calls the unscoped key a "READ-ONLY migration source" that setBusySendDefault now deliberately writes.

The reuse is declared and the test pins that scoped slots keep their choice, so the old leak the pin feared cannot recur — but the standing comment and the new writer now assert opposite rules to the next reader; confirm the reconciliation in readBusySendDefault's doc comment is the intended story.

What this change ships

Inventory (10 items) — 10 justified

Intent: press ⌘↩/Ctrl+Enter during a running turn to send with the other busy action for that one message, and pick the global busy default in Settings — an ADDITION (issue #4608 plan §3, user report quoted).

  1. ⌘↩/Ctrl+Enter while a turn runs performs the other busy action, one send, Enter-sends mode only — justified
  2. Idle or other send modes: the chord's behavior is unchanged — justified
  3. Busy split menu gains a hint naming the chord's concrete flip, only when the chord is live — justified
  4. New Settings → Chat row "What Enter does while the agent is working" (Steer/Queue) — justified
  5. Changing that default moves open composers without a per-chat choice immediately — justified
  6. The previously read-only unscoped storage key is now written by Settings as the default — justified
  7. Settings description switches to a no-chord variant in the other send modes — justified
  8. New exports readBusySendDefault/setBusySendDefault (1 consumer: ChatPanel.tsx) — justified
  9. New capture script + 8 PNGs under temp-screenshots/ (dozens of sibling scripts/dirs counted) — justified
  10. 5 i18n keys across 13 catalogs + generated command-palette registry entry — justified

[FIRST-PRINCIPLES-REVIEWED] e2775e0

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

UX Review (Fable 5) — 🟡 CONCERNS

UX-level review of e2775e0698a61956ec7b5d7357d7c4e8cc61907a — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

All evidence reconciled: the two new surfaces (Settings picker, busy-menu chord hint) appear in screenshots in both themes, and the blind reader identified both primary controls confidently. The residual findings are copy-level. Final review:

UX-Verdict: CONCERNS

Both new controls read cold, but the reader still can't tell whether the Settings default and the send-button choice affect each other.

Watch

  • Precedence copy only half-lands: despite "chats where you already chose a mode from the send button keep that choice", the blind reader said "I cannot tell whether changing one changes the other… unsure which one wins." The code moves untouched chats live (setBusySendDefault); say so — append "other chats follow this setting."
  • busy_alt_action_desc_no_chord: "The one-message flip shortcut" is the diff's own code vocabulary; the reader called it "an odd phrase I had to puzzle out." Name it concretely: "Sending with the other action via ⌘/Ctrl+Enter works only when Enter sends; here, use the send button's menu."

Evidence gaps

  • The queue-mode hint variant alt_action_hint_steers ("⌘↵ steers with this message instead") appears in no screenshot — shots 05/06 show only steer mode; capture the split menu with Queue selected.

[UX-REVIEWED] e2775e0

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

Reviewed e2775e0698a61956ec7b5d7357d7c4e8cc61907a — this comment is updated in place on each push.

Review details

No findings.

[OPUS-REVIEWED] e2775e0

Verdict parsed from the review's SHA-scoped output markers for commit e2775e0698a61956ec7b5d7357d7c4e8cc61907a.

False positive or not applicable? A repository writer can comment:
/ai-review override fable e2775e0698a61956ec7b5d7357d7c4e8cc61907a: <one-sentence reason>

@CrysisDeu
CrysisDeu force-pushed the feat/shortcuts-send-key-alt-action branch from 8cc845c to 437c897 Compare September 9, 2026 01:44
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings (all downgraded on adjudication)

GPT 5.6 flagged blocking issues on e2775e0698a61956ec7b5d7357d7c4e8cc61907a; Opus 4.8 adjudication downgraded every one of them to advisory. Adjudication downgraded all 1 blocking finding(s) to advisory: the remedy each one requires is disproportionate to the harm it prevents. Read them as advice, not as merge conditions.

This comment is updated in place on each push.

Review details

BLOCKING -- website/src/pages/settings/ChatPanel.tsx:305 -- Storage failure is displayed as a successful save (origin: validation)
const setBusyDefault = (m: BusySendMode) => { setBusySendDefault(m); setBusyDefaultState(m) }
Storage denied/full -> safeSetItem returns false -> return value ignored -> UI shows the new mode, but reload restores the old value.
Anchor: errors-use-error-notice
Fix: Update state only on success; otherwise surface the failure through ErrorNotice.
[BLOCK-MERGE-DOWNGRADED] e2775e0
[GPT-REVIEWED] e2775e0

Adjudication (Opus 4.8) — is blocking on each finding proportionate?

LOW harm — the failing write is a best-effort localStorage UI preference (Enter-while-busy mode), not data/credentials/config. Conditions confirmed: safeSetItem returns false only on storage denied (safeStorage.ts:162) or quota still exhausted after all reclaim tiers (safeStorage.ts:175-184), and setBusyDefault discards that boolean (ChatPanel.tsx:305). Recovery: on reload readBusySendDefault (BusySendButton.tsx:70-72) returns the last-persisted value, defaulting to 'steer' — visible to the user and self-correcting on retry, no loss. The cited errors-use-error-notice rule governs how a surfaced error renders, not a mandate to surface a swallowed best-effort write (safeStorage.ts:137 documents these as ignorable), so the blocking-rule anchor does not bind. The real fix threads new save-error state + an ErrorNotice surface into a write the module deliberately designs as best-effort — permanent UX/maintenance load exceeding a one-off recoverable LOW-rung harm.

[ADJUDICATION] e2775e0 total=1 uphold=0 downgrade=1
DOWNGRADE F1 website/src/pages/settings/ChatPanel.tsx:305 reason=disproportionate-remedy
[GPT-ADJUDICATED] e2775e0

False positive or not applicable? A repository writer can comment:
/ai-review override gpt e2775e0698a61956ec7b5d7357d7c4e8cc61907a: <one-sentence reason>

…he default (P2)

Refs #4608. Joseph Dombroski (Slack): "command+return changing the queue v
steer approach as with Claude/Codex". While the busy split button is showing,
⌘↩ / Ctrl+Enter now performs the OTHER action for that one send — queue when
the split says steer, steer when it says queue. Plain Enter is unchanged; idle
⌘↩ is still a plain send. Only in the `enter` send mode: in `ctrl-enter` the
modified Enter is the send key, and in `enter-ctrl-newline` it is newline.

Settings → Chat gains "What Enter does while the agent is working" (Steer /
Queue) — the global default every session without a per-slot split-button
choice inherits, persisted under the existing unscoped key the per-slot
fallback already reads. The split menu names the flip chord.

fireComposer's flag is strictly `=== true`: the callback is also an onClick,
and a MouseEvent must read as default, never as flip.
@CrysisDeu
CrysisDeu force-pushed the feat/shortcuts-send-key-alt-action branch from 437c897 to e2775e0 Compare September 9, 2026 02:04
@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running labels Sep 9, 2026
@bolichen97
bolichen97 enabled auto-merge (squash) September 9, 2026 07:21
@chenmingwei23
chenmingwei23 merged commit 88a0595 into main Sep 9, 2026
67 of 72 checks passed
@chenmingwei23
chenmingwei23 deleted the feat/shortcuts-send-key-alt-action branch September 9, 2026 08:16
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants