feat(composer): type into the terminal (completes #77) - #84
Merged
Conversation
Squashed from #77 (five commits; the 0.26.0 release commit is dropped — main cuts one release when the queued work is done). Adds the machinery: an ordered one-in-flight pump with bounded 64-key batches, so concurrent send_keys over Herdr's one-connection-per-RPC transport cannot reorder characters; Android beforeinput capture for Backspace/Enter and IME-committed strings; and a fail-stop that discards the queue, disarms, and blurs rather than letting continued typing become a reply with an Enter attached. The entry gesture and armed presentation are reworked in the commit that follows; this one is the mechanism as submitted.
…ane view Reworks the entry point and armed presentation of the mode added in the commit before this one. The pump, the Android/IME capture and the fail-stop are untouched. Entry is a named toggle beside Keys, not a gesture on Send. The submitted version armed on a bare long press, which reads as a saving of one tap — but the tap is per session, not per keystroke, and what it actually buys is an accidental hold quietly pointing the phone keyboard at a live terminal. A split Send button was tried next and was worse in a different way: a third of the primary action's width, spent every day on a mode used on a few of them. Keys is the real sibling — it exists because a phone keyboard can't send Esc/Tab/arrows/chords, this exists because it can't send bare printable letters — so someone hunting for `b` finds it where they looked. The mode now also dies on a hidden page and on a composer lock (gone pane, read-only device, idle pause): the mirror is polled, and in each of those the poll has stopped, so the next keystroke would land in a terminal nobody is watching. A lost connection is deliberately not a separate trigger — it already surfaces as a failed batch. Armed state gets a strip in the document flow above the input, because the button and textarea are exactly what a user stops looking at once typing starts. Also lifts the "Controls" tag out of the row's flex flow — a fixed ~60px of a 390px phone spent on a word that never changes — so four labelled toggles fit unclipped. Renamed throughout from "Immediate mode", which named the latency rather than the destination. Raises the ADR bar in .adr/README.md: this rework's lifecycle rule was a candidate and didn't clear it, so the argument sits in use-direct-typing.ts instead.
…e the field Three passes over the composer's layout, once the "Type" toggle made the row four-wide. An "on" control now carries a light-sky tint (--control-on / -foreground) instead of the secondary surface. Its own token pair because the two say different things: secondary is a quieter surface, this is a STATE — an open dock, an armed mode — and the row has to make that legible at a glance. Authored once so a dock and a mode can't drift apart, with hover pinned to the same tint (otherwise hovering an on control repaints it with the ghost hover and reads as switching off). The light half is tuned by chroma, not luminance: at "very light sky" lightness nothing clears a useful WCAG ratio against the page, and what makes it visible is being the one saturated thing in a neutral row. Text on the tint is the number that matters and clears it — 7.3:1 light, 9.2:1 dark. Attach moves inside the field, messenger-style. It had a full-height slot to the left of the input, which spent the widest part of the composer on the least-used action. Pinned bottom-right rather than centred: the field grows upward as a draft wraps, and a centred button would drift away from the thumb. `pr-11` keeps a long line from running under it, and `block` on the textarea stops the wrapper inheriting the inline baseline gap that left the button hanging past the field's bottom edge. Send gains a little air (gap-2 → gap-3): with attach gone from the row it is just the field and Send, and the old spacing left the two looking joined.
tyamanak
added a commit
to tyamanak/collie
that referenced
this pull request
Aug 10, 2026
Composer Type toggle (AltanS#84) merges cleanly; its two new tests gain the fork's reflow pref field and setReflow prop (DisplayPrefs grew them in the mirror-reflow work). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vw9GsWBy94wL4wNHP3JQbs
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.
Completes @aspiers' #77. Their commit is cherry-picked with authorship preserved; the entry point and armed presentation are reworked on top.
Fixes #74.
What ships
A "Type" toggle in the Controls row, beside Keys. While on, what you type goes to the pane as keystrokes with no trailing Enter, so a TUI that wants bare letters (
b,q) can be driven from a phone.What changed from #77, and why
Entry is a named toggle, not a gesture. The submitted version armed on a bare long press of Send. That reads as saving a tap, but the tap is per session — you arm once and type many keys — and what it actually buys is an accidental hold quietly pointing the phone keyboard at a live terminal. A split Send button was built next and rejected too: a third of the primary action's width, spent every day, for a mode used in bursts. Keys is the real sibling — it exists because a phone keyboard can't send Esc/Tab/arrows/chords, this exists because it can't send bare printable letters — so someone hunting for
bfinds it where they already looked.It dies with the pane view. Added disarms on a hidden page and on a composer lock (gone pane, read-only device, idle pause), alongside #77's pane-switch and failed-batch cases. The mirror is polled and the poll stops in all of them, so the next keystroke would land in a terminal nobody is watching. A lost connection is deliberately not a separate trigger: it already surfaces as a failed batch, observed directly rather than inferred from a timer.
Armed state gets a strip in the document flow above the input — the button and textarea are exactly what you stop looking at once typing starts.
Renamed from "Immediate mode", which named the latency rather than the destination.
The pump (ordered, bounded 64-key batches over Herdr's one-connection-per-RPC transport), the Android/IME capture, and the fail-stop are @aspiers' work, untouched.
Also in here
A composer layout pass the fourth toggle forced: an "on" control carries a light-sky state tint (
--control-on), the "Controls" tag floats out of the row's flex flow to buy back ~60px of a 390px phone, and attach moves inside the field messenger-style with Send gaining a little air.Verification
Backend 534 · ctl lifecycle passed · web 104 files / 1716 tests · both typechecks clean. Driven on a real browser at 390px in both themes; geometry and contrast measured rather than eyeballed.
Untested: Android/Gboard IME. jsdom only simulates that event stream — swipe-typing a word and Backspace mid-word are the paths to exercise on a real device.
No version bump; the release commit follows.