Replace the web's ad-hoc keydown listeners with a command registry #148
Closed
opened 2026-09-03 13:26:50 +01:00 by naps62-yolo
·
2 comments
No Branch/Tag Specified
main
chorale
upbeat
tritone-2
fermata-4
android-app/271-theme-apk
blitz/android-app
android-app/270-command-bar
android-app/268-app-shell
android-app/267-structured
android-app/266-terminal
android-app/269-spawn-form
android-app/265-sidebar
android-app/264-daemon-connection
android-app/263-api-client
android-app/262-project-scaffold
rhapsody
dominant-2
animato-2
glockenspiel
septet
mandolin
chord
timbre-2
aeolian
cello-2
allegro
lento
lyre
maestoso
cadence-3
modulation-2
modulation
xylophone-2
tuba-2
piccolo-2
grazioso-2
grazioso
hymn
xylophone
blitz/react-tailwind-migration
254-design-kit-react
253-delete-vanilla-wire-react
issue/252-tests-rtl
issue/242-daemon-link-react
issue/240-types-api-router
issue/251-preview-mode-react
issue/250-dialogs-drawer-react
issue/249-layout-system-react
247-command-bar-react
issue/248-spawn-dialog-react
246-structured-pane-react
issue/245-terminal-pane-react
244-sidebar-react
issue/243-theme-tokens-tailwind
issue/241-catalog-connection-zustand
issue/239-scaffold-react-tailwind
timpani
staccato-2
trombone-2
grave-2
harpsichord-2
recorder-4
vivace
sotto
tuba
chaconne
etude
arpeggio
harpsichord
cadence-2
brillante
clarinet
snare-2
staccato
recorder-2
tritone
balalaika
timbre
violin
fix/web-nix-build
blitz/codex-structured-sessions
codex-structured-sessions/203-tui-spawn
codex-structured-sessions/202-web-spawn
codex-structured-sessions/201-codex-lifecycle
codex-structured-sessions/200-select-acp-adapter
blitz/structured-view
vivo
structured-view/199-command-resolution
structured-view/198-viewport
structured-view/197-lock-order
structured-view/196-concurrent-handshakes
structured-view/195-autoscroll
structured-view/194-plan-row
structured-view/193-expanded-reconnect
structured-view/192-pty-gate
structured-view/143-spawn-structured
structured-view/191-accurate-diffs
structured-view/190-web-tool-state
structured-view/189-tui-approval-failure
structured-view/188-web-submit
structured-view/187-tui-spawn-race
structured-view/141-tui-structured
structured-view/140-web-structured
structured-view/186-unknown-update-doc
structured-view/185-lock-order
structured-view/184-approval-persist
structured-view/142-structured-activity
structured-view/137-structured-docs
fix/codex-attach-scroll
structured-view/139-permission-requests
structured-view/138-structured-api
structured-view/136-transcript-projection
structured-view/183-dismissal-race
structured-view/182-natural-exit-race
structured-view/181-recovery-provenance
structured-view/180-shutdown-create
structured-view/179-shutdown-drain
structured-view/177-restart-worktree
structured-view/178-pid-safe
structured-view/135-event-log
structured-view/134-acp-subprocess
10-umbrella-feature-gate-tests
structured-view/133-session-view
contrabass
fermata
interlude
crescendo
reboot/173-timing-assert
reboot/169-tui-dormant
blitz/reboot-resilience
reboot/171-scrollback
reboot/176-session-id-refresh
reboot/175-resume-races
reboot/168-web-dormant
reboot/174-bootid-strand
reboot/167-resume-policy
reboot/166-resume-route
reboot/172-worktree-field
reboot/164-boot-id
reboot/165-resume-command
reboot/163-prune-sidecars
tuplet
reboot/162-dormant
reboot/161-agent-session-id
sarabande
glissando
blitz/command-bar
legato
command-bar/159-web-leader-config
command-bar/157-touch-entry
command-bar/158-literal-leader
command-bar/156-tui-literal-ctrlk
command-bar/155-tui-drillin
command-bar/151-web-drillin
command-bar/154-tui-sigils
command-bar/152-web-pane-ctrlk
command-bar/150-web-sigils
command-bar/153-tui-bar
aubade
command-bar/147-rust-matcher
dominant
command-bar/149-web-bar
command-bar/145-daemon-focus
command-bar/146-ts-matcher
command-bar/148-web-registry
harmony
command-bar/144-contract
coda
oboe
accordion
snare
feroce
cantabile
fix/tui-daemon-url-resolution
haiky
activity
shortcuts
dorian
symphony
colors
flugelhorn
andante
trombone
organ
scherzo
duduk
sostenuto
lullaby
moderato-motif
largo-piano
moderato-bassoon
lento-sonata
animato-trumpet
largo-lute
feroce-violin
grazioso-viola
sostenuto-bassoon
crescendo-rondo
refresh
marcato-nocturne
brillante-fugue
forte-lyre
rename
themes
blitz/tui-shell
issue/124-project-groups
issue/122-theme-presets
issue/123-focused-panels
issue/121-tui-line2
issue/120-web-line2
issue/119-bare-maestro-tui
issue/118-palette-roles
issue/117-path-helpers
issue/116-sidebar-contract
blitz/sidebar-cards
sidebar-cards/115-fourth-flake
sidebar-cards/114-hook-error
sidebar-cards/113-more-flakes
sidebar-cards/106-hooks-install
sidebar-cards/112-detail-render
sidebar-cards/111-flaky-retention
sidebar-cards/105-activity-endpoint
sidebar-cards/110-lenient-activity
sidebar-cards/108-tui-cards
sidebar-cards/107-web-cards
sidebar-cards/104-activity-clock
sidebar-cards/103-session-env
sidebar-cards/102-activity-wire
sidebar-cards/101-sidebar-contract
blitz/spawn-v2
spawn-v2/97-enter-submits
spawn-v2/94-labels
spawn-v2/96-tui-picker
spawn-v2/93-collapse-worktree
spawn-v2/98-flake-ci
spawn-v2/95-project-picker
spawn-v2/74-gitroot-debounce
spawn-v2/76-preset-reset
spawn-v2/73-draft-pane-swap
spawn-v2/91-wt-toggle-default
spawn-v2/92-form-width
spawn-v2/70-fastforward-key
spawn-v2/72-opendraft-reuse
spawn-v2/75-projects-ttl
blitz/worktree-lifecycle
wl/83-blocked-work
wl/88-e2e-lifecycle
wl/87-drop-cleanup-flag
wl/82-natural-exit
wl/84-web-worktrees
wl/81-force-retains
wl/85-tui-worktrees
wl/86-cli-worktrees
wl/79-kill-guard
wl/80-worktrees-api
wl/77-worktree-collisions
wl/78-retained-store
spawn-v2/67-fastforward
spawn-v2/66-e2e
spawn-v2/65-tui-worktree
spawn-v2/68-draft-teardown
spawn-v2/62-drop-modal
spawn-v2/64-tui-fields
spawn-v2/61-wt-defaults
spawn-v2/53-names
spawn-v2/55-branches
spawn-v2/59-projectlist
spawn-v2/54-projects
spawn-v2/60-autocomplete
spawn-v2/63-tui-draft-pane
spawn-v2/56-worktree-place
spawn-v2/58-tui-textinput
spawn-v2/57-draft-pane
nix
refactor
dev-environment
linear/nap-228-daemon-recover-leaves-stale-tmux-pipe-pane-attach-shows-no
No results found.
Milestone
No items
No Milestone
Command bar
Projects
Clear projects
No projects
No Assignees
No due date set.
Reference: naps62/maestro#148
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The web's keybindings are ad-hoc
keydownlisteners scattered acrossmain.ts,help.ts,catalog.tsand the modals, whilehelp.ts'sSHORTCUTSis a hand-maintained parallel list of what we claim is bound. Thecommand bar needs to enumerate every command, so this becomes one registry.
Build a command registry for the web: each entry has a stable id, a
plain-language label, its key chord, and a
run(). Then:n,N,?,Ctrl+Shift+H, and thesession-list keys (
j,k, Enter, Tab, Esc).?help overlay from the registry, deleting the separateSHORTCUTSconstant. A command with no entry can then no longer read asmissing from help, because there is only one list.
ctrl-ato mirror the TUI's, with thechord-resolution behaviour the TUI already has.
ctrl-ais select-all in abrowser; preventDefault it, since select-all over an xterm pane does nothing
useful.
isTypingTarget's existing rule that plain single-key bindings are deafinside form fields and the terminal's hidden textarea. Chorded bindings are
not.
Registry shape and the leader are specified in
docs/COMMAND-BAR.md.Non-goals: no command bar UI (that is #F), no new bindings beyond the leader,
no change to what any existing key does.
Depends on: #144
Registry landed on
command-bar/148-web-registry(328711c). Notes for the issues that build on it, and things found outside this issue's scope.Deliberate behaviour changes, both tiny
Shift+Tabin the session list used to focus the pane, because the old handler comparede.key === "Tab"and ignoredshiftKey. It now canonicalises tobacktab, which nothing binds, soShift+Tabis ordinary reverse focus navigation again. Same forShift+Enteron a row.ctrl-a) is deaf inside real form fields — the spawn form's inputs keep select-all — but live inside xterm's hidden textarea, which is exactly where thectrl-kescape hatch has to work. If #149 wants the leader everywhere, that exemption is one condition inCommandSet.handle.What #149 gets
COMMAND_SPECSinweb/src/commands.tsis the list to feed the>sigil: each entry hasid,label,group,keys,scope.publishedCommands()returns the bound subset in registry order, andkeyCaps(chord)turns a chord into the keycaps a row displays.ctrl-a ctrl-kis a spec entry plus arun()— no dispatcher work.ctrl-kto be intercepted ahead of xterm, so #149 has to move that listener to capture. Left alone here because switching phases would have changed today's behaviour, which this issue was not allowed to do.Out of scope, worth filing
web/src/help.tsis the only place that knows a command exists but not where it is listened for:scopeis data on the spec, but nothing asserts the set that registers an id uses the matching surface. A wrong scope binds fine and only misbehaves inside a form field.DEFAULT_LEADER), while the TUI reads[tui].leaderfrom~/.config/maestro/config.toml. Rebinding one does not rebind the other, so the escape hatch is only "the same key" by coincidence. Probably a[web]section, or a/api/infofield.installHudKeybindandinstallHelpKeybindeach add their own window listener, on top of the two inmain.ts. Four listeners for what is now one registry; a single dispatcher over the published set would be less to keep in step, but it changes install order and Escape precedence, so it is not a refactor to do quietly.Web suite: 320 tests, 38 files, green, plus
tsc -b --noEmit.Merged into
blitz/command-bar. I re-ran the suite on the integration branchrather than taking the report: 320/320 across 38 files, green.
I read
commands.tsend to end. The chord resolution, the leader's form-fieldexemption, and the terminal-textarea carve-out all match what the contract
asks for.
Your three reports are filed:
needs it will see it.
[tui].leader, is #159.scope/listener coupling stays as you left it.One thing your change introduces that the reports do not mention: the leader
branch holds
ctrl-aunconditionally, and the terminal textarea isdeliberately not exempt, so
ctrl-ano longer reaches the shell in any pane —readline's beginning-of-line is gone, with no way back. That is a fair reading
of "mirror the TUI", so I have not bounced it; it is #158, fixed the way tmux
does it, by making a double-tap send the literal leader.