Repository navigation
Description
Summary
terminalBytesForKey in pkgs/id/web/src/world-keyboard.ts handles modified special keys inconsistently: some are silently dropped, and the rest are silently degraded to the unmodified key — so a world application receives a plain Backspace / Enter / Delete that the user never pressed, while other modified keys vanish entirely.
Evidence (verified on main 5db60876)
The special-key branch (world-keyboard.ts:36-40):
if (event.key in specialKeys) {
if ((event.ctrlKey || event.altKey || event.shiftKey) && event.key.startsWith("Arrow")) return null;
if (event.shiftKey && event.key === "Tab") return null;
return new TextEncoder().encode(specialKeys[event.key]);
}Only modified arrows and Shift-Tab are rejected; every other modifier combination falls through to the plain sequence. A temporary vitest probe driving the real function (removed after the run) produced:
| Input | Result | Plain-key result |
|---|---|---|
| Alt+Backspace | \x7f |
\x7f (identical) |
| Ctrl+Backspace | \x7f |
\x7f (identical) |
| Alt+Enter | \r |
\r (identical) |
| Alt+Tab | \t |
\t (identical) |
| Alt+Escape | \x1b |
\x1b (identical) |
| Ctrl+Delete | \x1b[3~ |
\x1b[3~ (identical) |
| Alt+Delete | \x1b[3~ |
\x1b[3~ (identical) |
| Ctrl+Home | \x1b[H |
\x1b[H (identical) |
| Shift+End | \x1b[F |
\x1b[F (identical) |
| Alt+PageUp | \x1b[5~ |
\x1b[5~ (identical) |
| Alt+ArrowUp | null (dropped) |
\x1b[A |
| Alt+ArrowLeft | null (dropped) |
\x1b[D |
| Shift+Tab | null (dropped) |
\t |
Contrast with printable keys in the same function: Alt+x correctly returns \x1bx — the Alt modifier is preserved (as an ESC prefix) for printable input, just not for special keys.
Why it matters
- Wrong input delivered: Alt+Backspace is word-delete in most editing contexts; the world app instead receives a single-character Backspace. Ctrl+Delete, Alt+Enter, Shift+End etc. all lose their modifier the same way. The caller in
pkgs/id/web/src/world.ts(keydown handler, ~line 240) callspreventDefault()whenever bytes are returned, so the browser's own handling is suppressed and the degraded byte sequence is the only thing that happens. - Dropped input leaks to the browser: when the function returns
null, the caller does notpreventDefault(). Alt+ArrowLeft / Alt+ArrowRight are browser history navigation in several browsers, so a user trying to send a modified arrow to the world can instead navigate away from the world page mid-session. - The decoder's design says nothing should be dropped or misreported: the receiving side,
Key.parseinpkgs/id/examples/roc-world/Key.roc, has no modified-key variants, but it deliberately surfaces unrecognised escape sequences asUnknown(bytes)("nothing is dropped") — e.g. its own test pinsKey.parse("\u(1b)[1;5C") == [Unknown("\u(1b)[1;5C")]for Ctrl+Right. The web encoder is the side that breaks that contract, in two opposite ways at once.
Suggested directions (one consistent policy)
Pick one and apply it to all special keys, not just arrows/Tab:
- Encode the modifier using the standard xterm modifier parameter (
;2=Shift,;3=Alt,;5=Ctrl, …) for CSI sequences (\x1b[3;5~Ctrl+Delete,\x1b[1;3AAlt+Up,\x1b[Zfor Shift-Tab) and the ESC-prefix convention the function already uses for Alt+printable where a CSI form doesn't exist (ESC+\x7ffor Alt+Backspace).Key.parsewill surface these asUnknowntoday — preserved, not misreported — and a laterKeyextension can name them. - Or return
nullconsistently for every modified special key the RocKeymodule cannot represent, so no modified press is ever silently rewritten as the plain key — and document that modified specials are unsupported (accepting that the browser keeps e.g. Alt+Left).
Option 1 matches the decoder's nothing-dropped contract; option 2 is the smaller change. Either is better than the current split.
Acceptance criteria
- One documented policy covers every (special key × modifier) combination in
terminalBytesForKey; no modified special returns the plain key's bytes, and the arrow/Shift-Tab special cases are no longer exceptions to a different rule. - Regression tests in
world-keyboard.test.tspin the policy for at least: Alt+Backspace, Ctrl+Delete, Alt+Enter, Shift+Home/End, Alt+PageUp, Alt+Arrow, Shift+Tab — and the existing plain and Alt+printable expectations still pass. - If option 1 is chosen, the emitted sequences are the standard xterm forms, and the
Key.rocbehaviour for them (currentlyUnknown) is noted in the PR. - Unmodified special keys, control letters, and AltGr (Ctrl+Alt printable) behaviour are unchanged.
Activity
dezren39 commentedon Oct 24, 2026
id train triage (2026-10-24): needs-decision — Muse: assign to Session A (phases 0-10 review); web keyboard encoder drops/degrades modified special keys (world-keyboard.ts:36-40).
dezren39 commentedon Oct 26, 2026
Intake review — fleet monitor, 2026-10-26T08:26:24Z.
Still live — verified on current main this run: the special-key branch of
terminalBytesForKeyinpkgs/id/web/src/world-keyboard.tsis byte-for-byte the code the issue quotes — modified arrows and Shift+Tab returnnull, every other modified special key falls through to the unmodified sequence. No merged PR or commit has touched this behaviour since filing.Related: #724 / #761 are the sibling world-input correctness findings in the same web client; this issue already carries
user/review, and this intake pass is that review's first step — the label stays until the encoding decision below is taken.Suggested approach: decide the modified-special-key encoding once, for the whole matrix: Alt+key as ESC-prefixed sequence (consistent with the printable-key path in the same function), Ctrl+special as the xterm modifier-parameter form (
CSI 3;5~etc.) where one exists, and an explicitnull+ no-preventDefaultleak only where the browser genuinely owns the chord. Add the issue's probe table as unit tests inworld-keyboardtests so the matrix cannot silently regress.Risks: silently degraded input (Alt+Backspace → plain Backspace) is worse than dropped input — it performs an edit the user did not ask for; that is why this stays priority/normal rather than low despite the small surface.
dezren39 commentedon Oct 27, 2026
Re-verified on current main
95cd41f0(2026-10-27, new-issue-creator-code-b): verbatim copy ofpkgs/id/web/src/world-keyboard.ts(sha256cd62a6a8…, identical both sides) driven under Bun on the laptop — Alt+Enter\x0d, Alt+Backspace\x7f, Alt+Tab\x09, Alt+Escape\x1b, Alt+Delete\x1b[3~, Alt+Home\x1b[Hare all byte-identical to the unmodified keys, Alt+ArrowUp still returns null, and Alt+xstill correctly returns\x1bx. The inconsistency is unchanged; no separate issue filed.53 remaining items
dezren39 commentedon Oct 31, 2026
🤖 Agent note · parked (resume check)
dezren39 commentedon Oct 31, 2026
🤖 Agent note · picked up
dezren39 commentedon Oct 31, 2026
🤖 Agent note · picked up (resume → merge)
fix(id/web): encode xterm modifiers for special keys in world keyboar…
dezren39 commentedon Oct 31, 2026
🤖 Agent note · done