Skip to content

fix(tui): pass through Ctrl/Cmd+L so the dashboard redraw byte never inserts a stray 'l' - #87070

Closed
jazzzzmaybe wants to merge 5 commits into
NousResearch:mainfrom
jazzzzmaybe:fix/dashboard-ctrl-l-composer-leak
Closed

jazzzzmaybe wants to merge 5 commits into
NousResearch:mainfrom
jazzzzmaybe:fix/dashboard-ctrl-l-composer-leak

Conversation

@jazzzzmaybe

Copy link
Copy Markdown

Summary

Fixes #87069 — the dashboard Chat tab inserted a stray l into the composer on every session switch.

Root cause: PtySession.attach() injects a raw Ctrl-L byte (0x0c) to force a TUI repaint on keep-alive PTY reattach (#86332 / #68071). The Ink keypress parser maps that byte to a ctrl+l key event which reaches both listeners — the global hotkey handler (which redraws but doesn't stop propagation) and the composer's TextInput, which falls through to the generic insert branch and types a literal l.

Change

One line: add isAction(key, input, 'l') to shouldPassThroughToGlobalHandler so the composer ignores the redraw keypress, mirroring the global handler's own isAction(key, ch, 'l') check in useInputHandlers.ts. Unmodified l still types normally; the existing ctrl+c/x/o pass-through entries are untouched.

Test plan

  • cd ui-tui && npx tsc --noEmit
  • cd ui-tui && npx vitest run src/__tests__/textInputPassThrough.test.ts (7 passed)
  • cd ui-tui && npx vitest run src/__tests__/textInput (107 passed)
  • Manual: with the composer focused, Ctrl+L no longer inserts l; typing l normally is unaffected.

Related

sobiya-22 and others added 5 commits August 15, 2026 11:43
The dashboard PTY forced HERMES_TUI_INLINE=1 while xterm.js tried to scroll
an empty host scrollback buffer. Inline mode leaves the transcript ScrollBox
unbounded, so PageUp/wheel had nothing to scroll and only the last few rows
were visible.

Stop forcing inline mode so AlternateScreen bounds the viewport, route browser
wheel into Shift+Up/Down key sequences over the PTY, and keep burst collapse
in the resume sanitizer without stripping erase codes.
…inserts a stray 'l'

The dashboard PTY reattach path writes a raw Ctrl-L (0x0c) into the
PTY to force a full TUI repaint (TUI_FORCE_REDRAW in pty_session.py).
The Ink keypress parser turns that byte into a ctrl+l key event, which
the global hotkey handler consumes for the redraw but does not stop
propagating, so the composer's TextInput also receives it and inserts
a literal 'l' into the input box on every session switch.

Route ctrl/cmd+l through the existing pass-through predicate so the
composer ignores the redraw keypress (matching the global handler's
own isAction(key, ch, 'l') check) while unmodified 'l' still types.
@jazzzzmaybe
jazzzzmaybe requested a review from a team August 15, 2026 15:18
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/dashboard Web dashboard / control panel UI (dashboard/, landing) comp/tui Terminal UI (ui-tui/ + tui_gateway/) comp/cli CLI entry point, hermes_cli/, setup wizard area/docker Docker image, Compose, packaging sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-automation Sweeper risk: may affect CI, automerge, label sync, or maintainer automation labels Aug 15, 2026
@Enough1122

Copy link
Copy Markdown
Contributor

AI code review — automated review for reference, author can ignore or act on any point.

fix(tui): pass through Ctrl/Cmd+L so the dashboard redraw byte never inserts a stray 'l'

  1. Blocking concern — docker.yml retargets the official image pipeline to a personal namespace: the workflow change (.github/workflows/docker.yml) flips IMAGE_NAME to ghcr.io/jazzzzmaybe/hermes-agent and gates build/publish/merge on github.repository == 'jazzzzmaybe/hermes-agent'. Merged into NousResearch/hermes-agent, this would (a) silently stop all main-branch container publishing (the repository gate is false upstream), and (b) if the gate ever matched, publish under a personal GHCR namespace using the upstream token, plus a "Make package public" PATCH to a user-level package. If this is a fork-local CI change, it should not land in the upstream tree — please scope it out (or confirm the intent to migrate the project's official images, which would need maintainer sign-off and a separate namespace/secret story).
  2. scrollback: 0 on the outer xterm (ChatPage.tsx line 521): with the inner TUI now owning all transcript scroll, a TUI crash/exit leaves the dashboard terminal with zero scrollback — the previous inline mode at least preserved history in xterm. Acceptable given the alt-screen design, but worth confirming a fallback (e.g. transient scrollback during TUI absence) isn't needed.
  3. Wheel→Shift+Up/Down flood control: wheelScrollSequences caps at 6 sequences per tick (web/src/lib/pty-wheel-scroll.ts line 22) — good; note a fast trackpad flick on a long transcript now translates to at most 6 lines per event, so scrolling long histories requires repeated flicks (acceptable trade-off; the TUI may also have its own PageUp handling).
  4. Sanitizer change is clean: stripErase: false for alt-screen (erase codes are legitimate in-place redraws there), burst-collapse kept always-on; the removed PTY_RESUME_SANITIZE_WINDOW_MS timer simplifies the resume path. HERMES_TUI_INLINE removal is documented in both web_server.py and ui-tui/src/config/env.ts. Tests cover the Ctrl+L pass-through, wheel mapping, and sanitizer flag.

@jazzzzmaybe

Copy link
Copy Markdown
Author

Superseded by a clean single-commit PR from the fix/ctrl-l-redraw branch. This PR's branch mixed in personal fork CI changes; sorry for the noise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/docker Docker image, Compose, packaging comp/cli CLI entry point, hermes_cli/, setup wizard comp/dashboard Web dashboard / control panel UI (dashboard/, landing) comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have sweeper:risk-automation Sweeper risk: may affect CI, automerge, label sync, or maintainer automation sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Dashboard Chat] Stray "l" inserted into the composer on every session switch (Ctrl-L force-redraw byte leaks into TextInput)

4 participants