Add rebindable actions for the high-frequency gaps: focus composer, open
model picker, next/prev session, search sessions (⌘⇧F), show files/
terminal tab, and nav→artifacts. Reconcile the duplicate Shift+N new-
session listener into session.new's defaults, and surface the remaining
context-local shortcuts (⌘↵ steer, ⌘L terminal selection, ⌘W close
preview) as read-only rows so the panel is the honest source of truth.
Add a central keybind registry + nanostore so desktop hotkeys are
discoverable and user-rebindable. A titlebar ⌨ button (and ⌘/) opens a
collapsible map grouped by Composer (read-only) / Profiles / Session /
Navigation / View; click any chip to capture a new combo. Overrides
persist to localStorage as a delta against shipped defaults, so future
default changes aren't shadowed by a stored snapshot.
Migrates the previously scattered inline listeners (palette, command
center, new session, sidebar, theme) into the registry, and adds profile
switch/cycle/create + default-profile hotkeys.
The desktop app could only queue while busy — `/steer` was in the palette
but had no first-class affordance, so the "nudge the agent mid-turn without
interrupting" lane was effectively unreachable.
Add a steer action to the composer: while busy with a text-only draft, a
steering-wheel button (and Cmd/Ctrl+Enter) injects the text into the live
turn via the `session.steer` RPC — the gateway folds it into the next tool
result so the model reads it on its next iteration. Plain Enter still queues.
steerPrompt returns false when the gateway has no live tool window (or the
RPC errors), and the composer re-queues the words so nothing is lost — the
same safety net as a plain queue.
* fix(desktop): make composer message queue reliable
The queue felt 'dumb' because of three real bugs:
1. Drained-after-interrupt sends went silent. cancelRun sets
interrupted:true and nothing reset it; submitPromptText's optimistic
seed preserved it, and the message stream drops every delta while
interrupted. So Send-now-while-busy and any interrupt+drain submitted
the next turn into a muted session. Fix: a fresh submit is a new turn —
seed interrupted:false.
2. Back-to-back queue drains stalled. The drain fires on the busy->false
settle edge, but busyRef (synced from the busy store by a separate
effect) can still read true on that same edge, so the drained send hit
the busy guard, returned false, and the entry was never removed. Fix:
fromQueue sends bypass the busyRef guard (the queue drain lock
serializes them); the user path keeps the guard.
3. Double-enter-to-interrupt killed single non-queue turns. The hidden
450ms timer meant a natural double-tap after sending stopped the agent.
Fix: empty Enter while busy is a no-op; interrupting is explicit —
Stop button or Esc.
Also: clean stop (no [interrupted] marker), Send-now works while busy
(promote + interrupt + auto-drain), settle on the interrupted completion
path. Adds regression tests and unblocks the prompt-actions suite by
completing its stale @/hermes mock.
* fix(desktop): float the queue panel as an overlay so the chat doesn't resize
The queue list rendered in-flow inside the composer root, so its height
fed --composer-measured-height (the composer rect drives the thread's
bottom padding + last-message clearance). Queuing a message grew that
rect and the whole chat visibly resized.
Anchor the panel out of flow above the composer (absolute bottom-full,
capped at 40vh with internal scroll). It no longer contributes to the
measured height, so the thread layout stays put and the list overlays the
(already faded) chat. Still collapsible via the panel's own
disclosure header.
* fix(desktop): queue panel collapsed by default + shared border with composer
- Default the queue disclosure to collapsed (compact 'N queued' pill)
instead of expanded.
- Drop the gap and merge the panel into the composer: square bottom
corners, no bottom border/radius, and overlap down by the Root's pt-2
(-mb-2) so the panel's borderless bottom lands on the composer surface's
top border — one continuous bordered shape.
* style(desktop): tighten queue panel padding
* style(desktop): trim queue-ux comments to house style
* style(desktop): drop 'Cursor' references from comments
Introduce a lightweight React context-based i18n layer for the desktop
app and translate the UI into Simplified Chinese.
- New apps/desktop/src/i18n module: typed Translations interface, en + zh
locale tables, I18nProvider/useI18n, localStorage-persisted locale
(defaults to English), and language endonym metadata for the picker.
- Wire I18nProvider at the app root in main.tsx.
- Refactor 24 desktop screens/components to read strings from the `t`
object instead of hard-coded English.
- Add a unit test for the i18n context.