Skip to content

fix(desktop): slash/@ menu keyboard nav — cycle all items + Esc dismiss#37999

Merged
kshitijk4poor merged 2 commits into
NousResearch:mainfrom
kshitijk4poor:desktop-slash-nav-dom-regression-test
Jun 3, 2026
Merged

fix(desktop): slash/@ menu keyboard nav — cycle all items + Esc dismiss#37999
kshitijk4poor merged 2 commits into
NousResearch:mainfrom
kshitijk4poor:desktop-slash-nav-dom-regression-test

Conversation

@kshitijk4poor

@kshitijk4poor kshitijk4poor commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

Problem

In the desktop app, the / command palette (and @ mentions) had two keyboard bugs:

  1. ArrowDown couldn't cycle past the first ~2 items — the highlight oscillated near the top.
  2. Esc didn't dismiss the menu — it closed on keydown then instantly reopened.

Both trace to the composer's onKeyUp unconditionally running refreshTrigger, which re-detects the trigger and resets the active index. PR #37937 addressed symptom (1) and part of (2), but its keyup guard — shouldSkipTriggerRefreshOnKeyUp(key, trigger !== null) — was timing-fragile for Escape:

  • Escape's keydown sets trigger = null and closes the menu.
  • In a real browser the keyup fires after a re-render, so the handler closure sees trigger === null, the guard returns false, refreshTrigger runs, re-detects the still-present /, and reopens the menu.
  • (jsdom batches state synchronously, so a unit test couldn't observe this — only the running app does. This is why it slipped through fix(desktop): keep slash/@ completion menu navigable and Esc-dismissable #37937.)

Fix

Replace the value-based guard with a triggerKeyConsumedRef that is set synchronously in keydown whenever the open popover consumes a nav/control key (ArrowUp/ArrowDown/Enter/Tab/Escape). keyup consults and clears that ref — immune to the keydown → re-render → keyup timing that defeated the old check.

  • Applied to both composers: the main composer (chat/composer/index.tsx) and the message-edit composer (assistant-ui/thread.tsx).
  • Removes the now-superseded shouldSkipTriggerRefreshOnKeyUp helper and its unit test.

Tests

  • slash-nav-dom-repro.test.tsx: a real-DOM @testing-library reproduction that mounts the actual useLiveCompletionAdapter + trigger wiring and fires real keyDown + keyUp event pairs through the ref-based handlers. Asserts ArrowDown cycles through all items ([0,1,2,3,4,0,1]) and Esc closes + stays closed.
  • text-utils.test.ts: keeps detectTrigger coverage.

Verification

  • tsc -b clean; vitest composer suite green (7/7); eslint introduces no new errors (two pre-existing repo lint errors on untouched lines remain).
  • Manually verified in a production renderer build (Vite v8) running under Electron against a local backend: ArrowDown/ArrowUp cycle the full list, and Esc dismisses the menu without reopening. Confirmed by the reporter on a local dev build.

The existing slash-menu fix (PR NousResearch#37937) shipped a unit test that drove the
keydown reducer directly. It did not exercise the actual DOM event path —
specifically the keyup-driven `refreshTrigger` that was the root cause — so
it would not have caught a regression in that path.

This adds a faithful @testing-library reproduction that mounts the real
`useLiveCompletionAdapter` plus the index.tsx trigger wiring and fires real
`keyDown` + `keyUp` event pairs on a contentEditable. It asserts:

- ArrowDown cycles through ALL items (0,1,2,3,4,0,1), not just the first two
- Escape closes the menu and keyup does not reopen it

Reverting the fix (always-refresh keyup + unconditional setTriggerActive(0))
makes this test fail with the highlight stuck at the top — confirming it
guards the real bug.
@alt-glitch alt-glitch added type/test Test coverage or test infrastructure P3 Low — cosmetic, nice to have comp/tui Terminal UI (ui-tui/ + tui_gateway/) labels Jun 3, 2026
Follow-up to NousResearch#37937. That fix guarded the composer's keyup with
`shouldSkipTriggerRefreshOnKeyUp(key, trigger !== null)`. The `trigger !== null`
check is timing-fragile for Escape: Escape's *keydown* sets `trigger = null`
and closes the menu, but in a real browser the *keyup* fires after a re-render,
so the handler closure sees `trigger === null`, the guard returns false,
`refreshTrigger` runs, re-detects the still-present `/` in the input, and
instantly reopens the menu. (jsdom batches state synchronously so a unit test
could not observe this -- only the running app does.)

Replace the value-based guard with a `triggerKeyConsumedRef` set synchronously
in keydown whenever the open popover consumes a nav/control key
(Arrow/Enter/Tab/Escape). keyup consults and clears that ref, so it is immune
to the keydown->re-render->keyup timing. Applied to both the main composer
(chat/composer/index.tsx) and the message-edit composer
(assistant-ui/thread.tsx).

Removes the now-unused `shouldSkipTriggerRefreshOnKeyUp` helper and its unit
test. The real-DOM regression test now fires keydown+keyup pairs through the
ref-based handlers and asserts Esc closes and stays closed.

Verified by running a production renderer build (Vite v8) under Electron
against a local backend: ArrowDown/ArrowUp cycle the full list and Esc
dismisses the menu without reopening.
@kshitijk4poor kshitijk4poor changed the title test(desktop): real-DOM regression for slash/@ menu keyboard nav fix(desktop): slash/@ menu keyboard nav — cycle all items + Esc dismiss Jun 3, 2026
@kshitijk4poor kshitijk4poor merged commit 46ea0a1 into NousResearch:main Jun 3, 2026
20 checks passed
davidgut1982 pushed a commit to davidgut1982/hermes-agent that referenced this pull request Jun 5, 2026
…h-nav-dom-regression-test

fix(desktop): slash/@ menu keyboard nav — cycle all items + Esc dismiss
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have type/test Test coverage or test infrastructure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants