feat(frontend): Switch, rename, and archive playground sessions from the keyboard - #6005
Conversation
…sessions Alt+1..9 switches to the Nth open session and puts the caret in its composer, Alt+R opens the inline rename on the active session, Alt+A archives it. One binding for every OS. Matched on `event.code`, since macOS reports Option+1 as a glyph, and Ctrl+Alt is excluded so European AltGr keeps typing. Cmd/Ctrl+digit is left alone - every browser reserves it for tab switching. The switch and rename verbs live inside per-session components, so the panel asks for them through scope-tagged request atoms, mirroring pendingSessionOpen. The listener sits in a null-rendering SessionShortcuts so its subscriptions cannot re-render every conversation pane, and only one of the two live panels owns it (the drawer while open, the playground otherwise).
Alt+Z and Alt+X move to the previous/next open session, wrapping at both ends, so switching no longer needs the digit row or a target position in mind. Z and X sit directly above Alt/Option: thumb on the modifier, index finger on the key, hand never leaves the bottom-left corner. Bound by physical position (event.code) like the rest, so the reach is the same on a non-QWERTY layout. Stepping reuses the jump path, so the caret still lands in the destination session's composer. Key repeat stays blocked - holding the key would tear through sessions and mount a conversation pane for each.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe PR adds cross-platform session keyboard shortcuts, scoped UI request atoms, composer focus handling, inline rename routing, search focus handling, and shortcut metadata for session surfaces. ChangesSession shortcut workflow
Estimated code review effort: 4 (Complex) | ~45 minutes Mergeability Score: 🟡 Moderate · up to The PR adds keyboard controls for playground sessions, but the Alt+F search binding conflicts with browser or OS menu behavior on Windows and Linux, so search may not work reliably there; this needs owner follow-up before merge. Sequence Diagram(s)sequenceDiagram
participant User
participant useSessionShortcuts
participant AgentChatPanel
participant UIRequestAtoms
participant AgentConversation
User->>useSessionShortcuts: Press an accepted Alt shortcut
useSessionShortcuts->>AgentChatPanel: Invoke the mapped session action
AgentChatPanel->>UIRequestAtoms: Publish a scoped focus or rename request
UIRequestAtoms->>AgentConversation: Deliver the matching composer-focus request
AgentConversation->>AgentConversation: Consume the nonce and focus the composer
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…needs The first pass added structure the feature did not ask for. This removes it. - SessionShortcuts, a null-rendering component, existed to avoid a re-render that was never measured. The hook now sits in the panel. - useComposerFocusRequest was a 12-line effect extracted only to be testable. It is back inline in AgentConversation, keeping the nonce-in-the-frame ordering that StrictMode requires. - ownsSessionShortcuts was a one-line boolean with its own test file, and shortcutPosition bounds-checked a constant that will not change. Both inlined. - Four test files collapse into one, and a test for a one-line platform ternary goes away. 21 files to 15, and roughly 590 lines removed against 330 added. Behaviour is unchanged: same bindings, same focus handling, same single-owner rule.
Closes the gaps a keyboard user hits most: creating and closing sessions, finding one in a long list, stopping a run, and answering a tool approval. Alt+C new session Alt+F toggle the session list, caret in search Alt+W close active session Alt+B toggle the config panel Esc stop the running response Alt+G approve the pending tool call Stop and approve live with the conversation rather than the panel hook, because only it knows whether a run is in flight and what it waits on. Escape binds on the bubble phase so an open picker or dialog answers it first, and Alt+G approves a single gate, never the dock's "Approve all" - a mis-press must not grant a tool the user never read. Alt+W refuses to close the last session, and Alt+C respects the onboarding lock.
|
@mmabrouk Let me know if you have any suggestions |
There was a problem hiding this comment.
Actionable comments posted: 5
🔇 Additional comments (12)
web/oss/src/components/AgentChatSlice/state/scope.tsx (1)
42-43: LGTM!web/packages/agenta-shared/src/utils/platform.ts (1)
19-21: LGTM!web/packages/agenta-shared/src/utils/index.ts (1)
16-16: LGTM!web/oss/src/components/AgentChatSlice/AgentChatPanel.tsx (1)
210-215: 🗄️ Data Integrity & Integration
⚠️ Unverified finding
Sandbox verification was unavailable.Verify the archive identifier for drawer sessions.
Line 213 passes
scopeasappId. A drawer scope has the formdrawer:${entityId}. Confirm thatsetArchivedaccepts this scope key. If the archive request requires the raw application ID, passentityIdinstead.web/oss/src/components/AgentChatSlice/hooks/useSessionShortcuts.test.ts (1)
72-278: LGTM!web/packages/agenta-shared/src/hooks/useAltKey.ts (1)
1-10: LGTM!web/packages/agenta-shared/src/hooks/index.ts (1)
12-12: LGTM!web/oss/src/components/AgentChatSlice/hooks/useInlineRenameRequest.ts (1)
1-34: LGTM!web/oss/src/components/AgentChatSlice/components/SessionRail.tsx (1)
1-1: LGTM!Also applies to: 12-18, 37-37, 83-83, 340-340
web/oss/src/components/AgentChatSlice/components/SessionTagBar.tsx (1)
3-3: LGTM!Also applies to: 12-14, 139-163, 265-268, 393-403, 589-589
web/oss/src/components/AgentChatSlice/components/SessionTabLabel.tsx (1)
22-32: LGTM!Also applies to: 86-86
web/oss/src/components/AgentChatSlice/hooks/useSessionActions.tsx (1)
11-11: LGTM!
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: d5ebb82e-bd83-4cc9-b8b6-8ea22928597a
📒 Files selected for processing (15)
web/oss/src/components/AgentChatSlice/AgentChatPanel.tsxweb/oss/src/components/AgentChatSlice/AgentConversation.tsxweb/oss/src/components/AgentChatSlice/components/SessionRail.tsxweb/oss/src/components/AgentChatSlice/components/SessionTabLabel.tsxweb/oss/src/components/AgentChatSlice/components/SessionTagBar.tsxweb/oss/src/components/AgentChatSlice/hooks/useInlineRenameRequest.tsweb/oss/src/components/AgentChatSlice/hooks/useSessionActions.tsxweb/oss/src/components/AgentChatSlice/hooks/useSessionShortcuts.test.tsweb/oss/src/components/AgentChatSlice/hooks/useSessionShortcuts.tsweb/oss/src/components/AgentChatSlice/state/scope.tsxweb/oss/src/components/AgentChatSlice/state/uiRequests.tsweb/packages/agenta-shared/src/hooks/index.tsweb/packages/agenta-shared/src/hooks/useAltKey.tsweb/packages/agenta-shared/src/utils/index.tsweb/packages/agenta-shared/src/utils/platform.ts
Railway Preview Environment
Updated at 2026-08-13T14:23:36.016Z |
|
This is awesome! Thanks @ashrafchowdury ! I guess follow up is to make this configurable in the user preference |
mmabrouk
left a comment
There was a problem hiding this comment.
@ashrafchowdury please address coderabbit review comments
- Keyboard approve went straight to addToolApprovalResponse, skipping the markLiveGate call the dock button makes. A restored pending approval could stay blocked by the queue resume gate. Routed through handleApprovalResponse. - The session-search request carried only a nonce, so the drawer panel and the playground panel could each answer the other. It now carries its scope, like the focus and rename requests. - Escape stopped a run while an IME composition was active, where it means "cancel composition". Guarded with isComposing. - Shortcut hints printed "Alt1" and "AltR" off macOS. The separator now lives in altKeyPrefix (was altKeyLabel), so every call site gets "Alt+1" or the macOS "⌥1" without repeating the conditional.
a652521 to
d9b20a3
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 1e35f144-702a-4404-819a-7e259ea93c2a
📒 Files selected for processing (8)
web/oss/src/components/AgentChatSlice/AgentChatPanel.tsxweb/oss/src/components/AgentChatSlice/AgentConversation.tsxweb/oss/src/components/AgentChatSlice/components/SessionRail.tsxweb/oss/src/components/AgentChatSlice/components/SessionTagBar.tsxweb/oss/src/components/AgentChatSlice/state/uiRequests.tsweb/packages/agenta-shared/src/hooks/useAltKey.tsweb/packages/agenta-shared/src/utils/index.tsweb/packages/agenta-shared/src/utils/platform.ts
🚧 Files skipped from review as they are similar to previous changes (7)
- web/packages/agenta-shared/src/utils/index.ts
- web/packages/agenta-shared/src/hooks/useAltKey.ts
- web/oss/src/components/AgentChatSlice/state/uiRequests.ts
- web/oss/src/components/AgentChatSlice/components/SessionRail.tsx
- web/oss/src/components/AgentChatSlice/AgentConversation.tsx
- web/oss/src/components/AgentChatSlice/components/SessionTagBar.tsx
- web/oss/src/components/AgentChatSlice/AgentChatPanel.tsx
Context
Working across several open playground sessions means reaching for the mouse every time. Switching is a click on a chip or a rail row. Renaming, archiving, creating and closing all need a hover button or a right-click menu, and stopping a run or answering a tool approval needs a click too. Issue #5981 asks for keyboard equivalents that behave the same on macOS, Windows, and Linux.
The one-hand rule
Every binding has to be reachable with the left hand alone, resting on Alt/Option, without the hand leaving the bottom-left of the keyboard. Thumb on the modifier, finger on the key. Nothing on the right half, nothing that needs a second hand, nothing that needs you to look down.
That constraint did most of the design work. It ruled out arrow keys,
[and], and,and.for prev/next, all of which sit on the right while the modifier sits on the left. What survives is the digit row and the left letter block:Q W E T,S D F G,Z X C V B.The shortcuts
⌥1…⌥9Alt+1…Alt+9⌥Z/⌥XAlt+Z/Alt+XZandXsit directly above Alt/Option, so stepping repeatedly costs no movement. Left key goes back, right key goes forward.⌥CAlt+C⌘T/⌘N, both of which break the one-hand rule (and⌥Nis a dead key on macOS, see below).Ckeeps the hand in the same cluster as the step keys.⌥WAlt+W⌘W/Ctrl+Wis the near-universal close-tab binding, so the letter transfers directly. Refuses to close the last remaining session.⌥RAlt+R⌥AAlt+A⌥FAlt+F⌘Kis the industry-standard search binding but Chrome owns it for the address bar, so it was unavailable.⌥BAlt+B⌘Bis the sidebar toggle in VS Code and many editors, so the letter carries over.EscEsc⌥GAlt+GGfor "go", on the left home row. Approves one gate only, never "Approve all".After any switch the caret lands in the destination session's composer, so you can keep typing.
Why Alt, and why physical keys
Why Alt. Every browser reserves
⌘/Ctrl+1-9for its own tab switching, so that family was unavailable for the positional jumps, and splitting the set across two modifiers would have been worse. Alt (Option on macOS) is free on all three platforms, which gives one binding everywhere instead of a per-OS table. This also matches how the rest of the codebase binds shortcuts: the key is the same on every platform and only the printed label changes.Why physical keys. The matcher reads
event.code, notevent.key. On macOS,Option+1produces¡andOption+Rproduces®, so matching characters would break on exactly the platform the hints are written for. Reading positions also keeps the one-handed reach identical on AZERTY and other non-QWERTY layouts, where the letters move but the physical keys do not.Two macOS traps that shaped the choices.
⌥Nis a dead key (it typesñon the next vowel), which is why new session is not⌥Ndespite being the obvious pick.⌥E,⌥Uand⌥Iare dead keys too, and are avoided for the same reason. Every other Option combo here merely shadows a symbol nobody types in a chat composer:⌥Cisç,⌥Fisƒ,⌥Gis©.Typing is never swallowed.
Ctrl+Altis excluded from every binding, because that is what AltGr reports on European layouts.AltGr+3still types³andAltGr+Estill types€. The shortcuts do fire while the composer has focus, which is the point of a modifier combo here, and they stay quiet while a modal or confirm dialog is open.Escis the only unmodified binding, and it acts only while a run is in flight.How it is wired
The verbs the panel cannot perform itself travel as requests on scope-tagged atoms, the same shape
pendingSessionOpenalready uses:The composer's input handle lives in
AgentConversationand the tab label's edit mode is local state inside each row, so neither is reachable from where the keypress is heard. Thescopefield matters because the revision drawer mounts a secondAgentChatPanelover the playground's; consumers match on both fields, so a drawer request cannot reach a playground pane. For the same reason exactly one panel holds the listener: the drawer's while it is open, the playground's the rest of the time.Stop and approve are the exception. They live with
AgentConversation, because only it knows whether a run is in flight and what it is waiting on.Escbinds on the bubble phase so an open picker, dialog, or the inline rename editor answers it first.Archive reuses
useSessionActions().setArchived, the same path the sidebar and the sessions page call, so the local tab cache and the server stay in step.Tests
pnpm lint-fixclean, type check clean, and the slice suite passes.useSessionShortcuts.test.ts: every binding, both wrap edges, a stale active id, single-session and empty-list guards, and the refusal to close the last session.Ctrl+Alt,Alt+ShiftandAlt+Cmdmust not match, or European layouts lose characters.What to QA
⌥Xand⌥Zstep through them in tab order and wrap at the ends, and the caret is in the composer each time.⌥2on a tab you have never opened. It still lands focused (antd mounts that pane lazily, so this is the case most likely to break).⌥Copens a session,⌥Wcloses it.⌥Won the last remaining session does nothing.⌥Ropens the inline rename on the active chip. Enter commits, Escape reverts.⌥Aarchives and the next tab takes over.⌥Fopens the session list with the caret in the search box, and⌥Fagain puts it away.⌥Btoggles the config panel both ways.Escmid-stream. The run stops. PressEscwith no run in flight and nothing happens.⌥G. One gate is approved. If the turn requested several, the others still wait.⌥2while it is up. Nothing happens.AltGr+3andAltGr+Estill type³and€in the composer.Alt+1does not drop the menu bar and does not switch a browser tab.Notes for the reviewer
⌥R/⌥Asuffixes in the right-click menu and the pencil tooltip). That was a deliberate call to keep the tab strip clean..ant-modal-wrap:not([style*="display: none"])). There is no global dialog state to ask, and these dialogs come from antd'smodal.confirm. It is worth one look in a browser against antd v6, because a false positive would silently disable the shortcuts after the first modal close.useAltKeyresolves its label in a mount effect, which is whatplatform.tsdocuments. Its older twinuseModifierKeyresolves during render. Aligning them touches three existing consumers, so it is left for a separate change.⌥Dis free next to⌥Gif we want the pair, though a keyboard decline is much safer than a keyboard approve.Closes #5981