Review of #268 turned up two defects, both verified against a real shell
session on an isolated instance.
1. A tap spent the modifier. The onData hook consumed every chunk while
armed, but not every chunk is a keystroke: a shell session keeps the
narrow scrollback strip, so mouse DECSETs reach the browser, and with
vim/htop running a tap arrives as `\x1b[<0;31;23M`. Measured in the
real app: armed, one tap, disarmed, and the Ctrl button read as dead.
The hook now skips mouse and focus reports via a new
`CodemanTerminalInput.isTerminalFocusOrMouseReport()`; they still reach
the PTY, they just no longer stand in for the next key. Focus reports
are covered for the same reason even though FOCUS_ESCAPE_FILTER in
session.ts strips DECSET 1004 today, since the bar refocuses the
terminal after every key and would spend the modifier on its own
`\x1b[I` the moment that filter changed.
2. The armed style did not land on the four light skins. The competing
rule is (0,3,1), not (0,2,1) as the comments claimed: `:is()` takes the
specificity of its most specific argument and that list holds
`.btn-toolbar.btn-shell`, so it outranked the (0,3,0) armed rules in
both stylesheets. Measured across all seven skins at 390px, armed and
resting backgrounds were byte-identical on paper-gray, solarized-light,
catppuccin-latte and rose-pine-dawn. The light-skin rule now excludes
the state as `.accessory-btn:not(.armed)`, which fixes phone and tablet
at once; adding another class to the armed rules would only have moved
the tie.
Tests: 20 more cases in test/mobile-shell-keyboard.test.ts (the report
classifier, the gate's effect on the modifier, and a static guard on the
light-skin selector, since the existing E2E background assertion passes on
a light skin and the browser suite runs the dark default), plus a browser
regression that taps the terminal with mouse reporting on.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>