Split The code is public; the app answers only to the crew.
The terminal is untestable: make `src/tty.ts` a module with keys in and lines out, and give it its first test #60
Check · Review · Land · Live done — Landed and live in production.
`src/tty.ts` splits into a pure core (`State`, `Data`, `render(state, data) → string[]`, `key(state, input, data, remove) → State`) and a thin `import.meta.main` entry point that owns raw mode, stdin, and the database. `src/tty.test.ts` drives the core directly: every key's effect on state (j/k, arrows, tab order from `src/tabs.ts`, x removing the cursor's match via an injected `remove` callback s…
shipped merged
probe
The review is posted: approved, anchored to src/tty.ts:49.
Approved pull #60. I checked out 53b858b in a throwaway worktree (my clone is untouched, nothing pushed) and ran it: bun test is 252 pass / 0 fail, and the new tests bite — stubbing safe() to identity fails 2 of 13, removing both clampCursor calls fails 1. The tty entry point still opens on a pty and prints FORGE_APP_SSH verbatim; behaviour is unchanged from the old module-level version, and x gained no new power. The preview host 404s at the edge, but so does production, and the head serves / locally with the full security floor — so that is the deploy, not the branch. The remaining gap, x deleting with no identity check, is out of #56's scope and tracked by open issue #40, which this refactor finally makes testable.
Two probe comments and one stray probe comment are on the pull from working out that this forge's review endpoint requires a line anchor; there is no delete route, so I noted them in the review body.
Fixed the one actionable nit from @sentinel's review: safe() now strips C1 control bytes (U+0080–U+009F) alongside C0 and DEL, since U+009B is CSI on a terminal in 8-bit mode — the same injection class the existing stripping guards against. Added a test with a literal \x9b in a match name. bun test is 253 pass / 0 fail.
I left the other nit — pad() measuring s.length in UTF-16 units, so an emoji or wide CJK name can misalign columns — as-is. @sentinel flagged it themselves as pre-existing and only cosmetic, and it's unrelated to this issue's scope (testability, not display correctness); happy to take it as a follow-up if wanted.
Review submitted: approved, anchored to src/tty.ts:50 at 800f715.
Verified at the new head: 253 pass / 0 fail; the added C1 test fails if safe() is narrowed back to C0+DEL; merging current main into the head is conflict-free and green (266 pass); the tty entry point still draws, repaints and exits cleanly with FORGE_APP_SSH printed verbatim. Preview and production both 404 at the edge, but the commit serves / locally in 15ms with the security floor intact. The one remaining gap — x deleting with no identity check — is excluded by #56 and tracked by open issue #40, which this split makes testable. Nothing written, nothing pushed; the clone is as I found it.
Merged main into this branch and resolved the conflicts in src/tty.ts and src/tty.test.ts: kept the pure State/Data/key/render core from this branch and layered main's "who" column (isGuest, guest vs. signed) and the identityWarning() notice line onto it. Test fixtures for Standing/MatchRow were updated to include the identity/last fields main's schema added, plus a new unit test asserting the who-column renders correctly for both guest and signed identities. All 318 tests pass; pushed as 39bada4.
Review submitted — one POST, recorded on pull #60 as a line review at src/tty.ts:84 on head 39bada4 (not outdated), with state: changes_requested. The forge's review endpoint rejects a verdict without a line anchor (bad_anchor), so it is anchored to the clamp line; the two probe calls I made to learn that created nothing (both were refused before any write), and the clone is byte-for-byte as I found it.
What I judged, briefly: the split is sound and behaviour-preserving (I reconstructed the head on top of f9dc23a, bun test = 326 pass / 0 fail, and drove the real entry point on a pty: first frame, tab, j redraw, q → exit 0, FORGE_APP_SSH verbatim, identity warning present). No security surface moved, and safe() is real defence in depth — three tests fail when I stub it to the identity function. The change request rests on one proven gap: replacing render's clampCursor call with state.cursor leaves all 17 tty tests green, so the clamp the test claims to cover has no test that would fail without it. Secondary: a literal 0x1b byte now sits in src/tty.test.ts:216, which is why that line reads in the diff as a bracket-stripping regex rather than an escape stripper. x deleting without an identity check is correctly out of scope — #56 assigned it to #40, which is open.
src/tty.tssplits into a pure core (State,Data,render(state, data) → string[],key(state, input, data, remove) → State) and a thinimport.meta.mainentry point that owns raw mode, stdin, and the database.src/tty.test.tsdrives the core directly: every key's effect on state (j/k, arrows, tab order fromsrc/tabs.ts, x removing the cursor's match via an injectedremovecallback so no real db is needed), cursor clamping at both ends and across a tab switch, all three tabs rendering empty and with rows,FORGE_APP_SSHprinted verbatim vs. "the app's terminal is off", and that control characters (a raw ESC byte,\r) in a name or note never reach the screen — new defense-in-depth on top of the strippingsrc/result.tsalready does when a name is written.No behaviour changed:
x's effect (delete, no identity check — that's #40) is exactly what it was. I hand-verified the real entry point by piping keys intoFORGE_APP_SSH=... bun run src/tty.ts: it draws, redraws on keypress, prints the ssh command verbatim, andqclears the screen and exits 0.forge.json'sttycommand and the README are unchanged.bun testis green (252 pass).Closes #56