Skip to content

feat(keyboard): leader-sequence runtime in the keymap dispatcher (RIG-2709) - #616

Merged
mattwilkinsonn merged 2 commits into
mainfrom
compass-ux/rig-2709-leader-runtime
Aug 26, 2026
Merged

feat(keyboard): leader-sequence runtime in the keymap dispatcher (RIG-2709)#616
mattwilkinsonn merged 2 commits into
mainfrom
compass-ux/rig-2709-leader-runtime

Conversation

@rigel-mintaka

@rigel-mintaka rigel-mintaka commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

This PR is part of a stack containing 3 PRs:

  1. main
  2. feat(keyboard): four G-leader sequence rows + display/aria hardening (RIG-2708) #613
  3. "feat(keyboard): leader-sequence runtime in the keymap dispatcher (RIG-2709)" (this PR)
  4. docs(keyboard): document the leader-sequence runtime in the dispatch header (RIG-2710) #619

Arm-and-complete "press G, then " sequences inside the one keydown
handler installKeymap already owns — no second listener, no module global.

  • Per-install pending leader state + a disarm() that clears the live
    timeout; leader set is leaderPrefixes(DEFAULT_KEYMAP, platform), computed
    once at install (table-derived, never hard-coded).
  • Per-keydown order (§A3): normalize → editable-guard FIRST (modifier-less
    keys only) → completion (pure-modifier keys ignored, Escape disarms+consumes,
    else disarm then resolve the "<leader> <chord>" sequence rows; a dead
    sequence falls through and re-enters arming) → arming (leader prefix,
    no command modifier, not event.repeat; preventDefault+stopPropagation) →
    single-chord path (unchanged).
  • Tier resolution factored into a resolve(matching, event) closure so a
    completed sequence and a single chord run byte-identical (active group →
    scoped → global, scope gate intact).
  • isEditableTarget extended to HTMLSelectElement and
    [role=combobox|listbox|menu] so a bare leader key never steals native
    <select> typeahead or an ARIA widget's keys.
  • Uninstaller clears any live disarm timer (no leak against a torn-down
    closure on test teardown / HMR).
  • LEADER_TIMEOUT_MS = 1000 (OQ1 ratified) exported for runtime + tests.

Also folds the two deferred RIG-2707 review lows into leaderPrefixes's unit
coverage (first runtime caller): per-platform resolution of a Mod leader
segment, and distinct-leader accumulation with same-leader dedup.

Tests: dispatch.test.ts + keyboard-e2e.test.tsx (51 pass) cover arm/complete,
timeout disarm, editable + <select> + ARIA-widget guards, dead-sequence
fall-through, re-arm, Escape, lone-Shift-does-not-disarm, Mod+B-mid-sequence
disarm-and-resolve, held-key no-arm, and uninstall-clears-timer; the two e2e
cases drive G S/G L through real App wiring. keymap.test.ts 20 pass.

Ledger-impact: none (decisions landed with the design PR #544; DL-248..252).

RIG-2709

@linear-code

linear-code Bot commented Aug 25, 2026

Copy link
Copy Markdown

RIG-2709

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-ux-rig-2709-leader-r.compass-eng-docs.pages.dev

Deployed from compass-ux/rig-2709-leader-runtime at 7477dd0.

@rigel-mintaka
rigel-mintaka force-pushed the compass-ux/rig-2709-leader-runtime branch from c956910 to 761ad0f Compare August 25, 2026 18:25
@rigel-mintaka
rigel-mintaka marked this pull request as ready for review August 25, 2026 18:38
@rigel-mintaka
rigel-mintaka force-pushed the compass-ux/rig-2708-leader-sequence-rows branch from 21555fa to b10833f Compare August 26, 2026 03:31
@rigel-mintaka
rigel-mintaka force-pushed the compass-ux/rig-2709-leader-runtime branch from 761ad0f to 391ad7e Compare August 26, 2026 03:32
@mattwilkinsonn
mattwilkinsonn force-pushed the compass-ux/rig-2708-leader-sequence-rows branch from b10833f to 8b976fe Compare August 26, 2026 04:26
@mattwilkinsonn
mattwilkinsonn force-pushed the compass-ux/rig-2709-leader-runtime branch from 391ad7e to 328d0d6 Compare August 26, 2026 04:26
rigel-mintaka and others added 2 commits August 26, 2026 00:40
…(RIG-2708)

Adds the four Go-to leader sequences from the frozen leader-chords design (`docs/designs/product/compass-leader-chords/design.md`, T2 / §A4-A5) to `DEFAULT_KEYMAP` and hardens the two shipped chord scanners so a sequence renders correctly everywhere and never reaches `aria-keyshortcuts`.

- `DEFAULT_KEYMAP`: a new `// Go-to sequences (RIG-2484)` block with `G B → view.bridge`, `G L → view.backlog`, `G D → view.done`, `G S → view.settings`, each placed after any existing modifier row for the same command so the modifier chord stays the first match.
- `shortcutForAria` now skips sequence rows (`chordSegments(entry.chord).length === 1`): a sequence-only command returns `undefined` (its writer omits the attribute) and a dual-bound command returns its modifier chord. A press-then-press sequence has no valid WAI-ARIA representation, so the skip is mandatory and makes the result row-order-independent.
- `shortcutFor` now returns `formatChordForDisplay` of the first matching row and does NOT skip sequences, so `view.backlog`/`view.done` display `"G then L"`/`"G then D"` and keep their point-of-use chip and title; a dual-bound command still shows its modifier chord because that row is first.
- The `?` overlay join formats via `formatChordForDisplay`, so a sequence row reads `"G then B"` instead of the misleading `"G B"`.

Tests: `keymap.test.ts` moves `view.backlog` out of the `shortcutFor` miss group, adds a sequence-only-display case, a dual-bound modifier-wins case, and a `DEFAULT_KEYMAP` authoring-invariant block (every sequence is two modifier-less segments; no leader prefix is also a complete single chord); `ShortcutsOverlay.test.tsx` updates the bridge-filter row count 1→2 and asserts the formatted `"G then B"`; `Palette.test.tsx` asserts Backlog/Done carry no `aria-keyshortcuts` but a `"Backlog (G then L)"`/`"Done (G then D)"` title while Bridge keeps `aria-keyshortcuts="Control+B"`.

Stacked on RIG-2707 (the sequence-grammar helpers). The dispatcher leader runtime that makes these chords fire is T3 (RIG-2709); until it lands the rows are advertised only through the discoverability net, which is why T2 and T3 ship in one stack.

Ledger-impact: none. DL-248..DL-252 for this record landed with the design PR (#544).

Refs RIG-2708

Co-authored-by: Matt Wilkinson <matt@rigel.build>
…-2709)

Arm-and-complete "press G, then <key>" sequences inside the one `keydown`
handler `installKeymap` already owns — no second listener, no module global.

- Per-install `pending` leader state + a `disarm()` that clears the live
  timeout; leader set is `leaderPrefixes(DEFAULT_KEYMAP, platform)`, computed
  once at install (table-derived, never hard-coded).
- Per-keydown order (§A3): normalize → editable-guard FIRST (modifier-less
  keys only) → completion (pure-modifier keys ignored, Escape disarms+consumes,
  else disarm then resolve the `"<leader> <chord>"` sequence rows; a dead
  sequence falls through and re-enters arming) → arming (leader prefix,
  no command modifier, not `event.repeat`; preventDefault+stopPropagation) →
  single-chord path (unchanged).
- Tier resolution factored into a `resolve(matching, event)` closure so a
  completed sequence and a single chord run byte-identical (active group →
  scoped → global, scope gate intact).
- `isEditableTarget` extended to `HTMLSelectElement` and
  `[role=combobox|listbox|menu]` so a bare leader key never steals native
  `<select>` typeahead or an ARIA widget's keys.
- Uninstaller clears any live disarm timer (no leak against a torn-down
  closure on test teardown / HMR).
- `LEADER_TIMEOUT_MS = 1000` (OQ1 ratified) exported for runtime + tests.

Also folds the two deferred RIG-2707 review lows into `leaderPrefixes`'s unit
coverage (first runtime caller): per-platform resolution of a `Mod` leader
segment, and distinct-leader accumulation with same-leader dedup.

Tests: dispatch.test.ts + keyboard-e2e.test.tsx (51 pass) cover arm/complete,
timeout disarm, editable + `<select>` + ARIA-widget guards, dead-sequence
fall-through, re-arm, Escape, lone-Shift-does-not-disarm, Mod+B-mid-sequence
disarm-and-resolve, held-key no-arm, and uninstall-clears-timer; the two e2e
cases drive `G S`/`G L` through real App wiring. keymap.test.ts 20 pass.

Ledger-impact: none (decisions landed with the design PR #544; DL-248..252).

RIG-2709
@rigel-mintaka
rigel-mintaka force-pushed the compass-ux/rig-2708-leader-sequence-rows branch from 8b976fe to 5c93e2e Compare August 26, 2026 04:40
@rigel-mintaka
rigel-mintaka force-pushed the compass-ux/rig-2709-leader-runtime branch from 328d0d6 to 7477dd0 Compare August 26, 2026 04:40
Base automatically changed from compass-ux/rig-2708-leader-sequence-rows to main August 26, 2026 05:20
@mattwilkinsonn
mattwilkinsonn merged commit 29c4a7f into main Aug 26, 2026
25 checks passed
@mattwilkinsonn
mattwilkinsonn deleted the compass-ux/rig-2709-leader-runtime branch August 26, 2026 05:20
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