Skip to content

feat(sessions): an arrow pad on the session page - #441

Merged
ralyodio merged 1 commit into
mainfrom
worktree-session-dpad
Aug 29, 2026
Merged

feat(sessions): an arrow pad on the session page#441
ralyodio merged 1 commit into
mainfrom
worktree-session-dpad

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

The session mirror could only be typed at. Everything you sent was a line, and a line is no use against anything that navigates — the reader, a herd bar, a menu all want ↑↓←→ and none of them want text.

What this adds

A five-key pad between the terminal and the prompt on /sessions/:id, plus arrow-key passthrough when the terminal has focus. ← backs out, → drills in, ⏎ selects.

How a key travels

A key rides the existing command queue as a sentinel (ESC + moshkey:<name>) the CLI decodes rather than runs. ESC leads it because it is a byte nobody can put in the send box by typing, so a key can never collide with a real command.

On the CLI side it skips the line queue — a key parked behind pending text lands on a different row by the time it arrives — and is pressed against whatever is reading the terminal right now:

  • at the prompt, readline gets a keypress: ↑↓ walk the history, ←→ move within the line, ⏎ runs it;
  • otherwise something has the tty in raw mode, and it gets the real escape sequence on stdin.

Nothing is echoed for a key. A typed line gets its ▸ (web) note because it would otherwise appear from nowhere; a key's effect is the redraw, and printing over it would shift that out of place.

Not typing the sentinel at somebody's prompt

Sent to a mosh that predates this, the sentinel would be handed to readline and typed at the prompt of a live machine. So the CLI now declares features: ["keys"] when it registers, and the app refuses a key without it (new cli_sessions.features column, migration 017). Declared rather than inferred from version, so the next capability costs a string here instead of a release number the app has to know about — and unknown feature names are dropped on the way in.

The pad renders disabled, and says why, against a CLI that hasn't been updated yet. The pad stays disabled for everyone until a release ships the CLI half — merging apps/pwa deploys itself, but nothing reaches an installed mosh until a GitHub release is cut.

Auto-repeat is dropped: holding a key down would put thirty presses a second on a queue that crosses a network before anything moves.

Checks

  • apps/pwa: 651/651 pass, including 9 new in sessions-keys.test.mjs (queueing, the unknown key, the undeclared CLI, the ended session, the sentinel refused as a typed line, feature filtering, and a register-then-press round trip). One of them imports the CLI's decodeKey so the two halves cannot drift apart.
  • root: 2732/2737 pass, 6 new in mirror-keys.test.mjs. The single failure — business-pit.test.mjs › a client, a rate and a timer, typed at the prompt — is pre-existing on the base commit: it fails identically with both CLI files reverted to HEAD.
  • Rendered the page headless in three states (live + capable, live + too old, 390px). Pad markup, disabled states and notes are correct, no console errors. The horizontal overflow at 390px comes from the app bar and the "← all sessions" link, not the pad, and is left alone.

🤖 Generated with Claude Code

https://claude.ai/code/session_014aJJP9VHyi3cjKNQezV52e

The mirror could only ever be typed at: whatever you sent was a line, and a
line is no use against anything that navigates. A reader, a herd bar, a menu —
all of them want ↑↓←→, and none of them want text.

So a key now rides the command queue as a sentinel the CLI decodes rather than
runs. It skips the line queue on the way in, because a key parked behind
pending text lands on a different row by the time it arrives, and it is pressed
against whatever is reading the terminal right now: readline gets a keypress
(history, cursor, enter runs the line), and anything holding the tty in raw
mode gets the real escape sequence on stdin.

The page grows a five-key pad between the screen and the prompt, and forwards
your own arrow keys when the terminal has focus. Auto-repeat is dropped —
holding a key down would put thirty presses a second on a queue that crosses a
network before anything moves.

Sending a key to a mosh that predates this would type the sentinel at the
prompt of a live machine, so the CLI now declares `features: ["keys"]` when it
registers and the app refuses a key without it. Declared rather than inferred
from the version, so the next capability costs a string instead of a release
number the app has to know about. The pad renders disabled, and says why,
against a CLI that hasn't been updated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014aJJP9VHyi3cjKNQezV52e
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

0 finding(s) in the 6 file(s) this pull request changes.

Nothing in the files this pull request changes.

65 pre-existing finding(s) elsewhere in the repository — **HIGH/CRITICAL**: 5 | **MEDIUM**: 54 | **LOW**: 6

Not introduced by this pull request. The full set is in the Security tab.

Severity Rule Location
HIGH js-ssrf-outbound-request apps/pwa/public/sw.js:45
HIGH tls-verification-disabled apps/pwa/src/lib/moshpit-gateway.mjs:299
HIGH sh-remote-script-execution install.sh:79
HIGH sh-remote-script-execution install.sh:83
HIGH tls-verification-disabled src/dns.mjs:766
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:92
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:106
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:132
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:319
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:323
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:368
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:597
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:778
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:780
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:839
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:885
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:955
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1058
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1081
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1103

…and 45 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 491db74 into main Aug 29, 2026
6 checks passed
@ralyodio
ralyodio deleted the worktree-session-dpad branch August 29, 2026 08:16
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.

1 participant