feat(sessions): an arrow pad on the session page - #441
Merged
Conversation
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
ThreatCrush Security Scan0 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**: 6Not introduced by this pull request. The full set is in the Security tab.
…and 45 more. Full results in the Security tab. Snippets are redacted; ThreatCrush never prints matched credential material. |
This was referenced Aug 29, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
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 (newcli_sessions.featurescolumn, migration017). Declared rather than inferred fromversion, 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/pwadeploys itself, but nothing reaches an installedmoshuntil 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 insessions-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'sdecodeKeyso the two halves cannot drift apart.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 toHEAD.🤖 Generated with Claude Code
https://claude.ai/code/session_014aJJP9VHyi3cjKNQezV52e