The terminal is the third seat at this board and it has no tests: pull its key handling out of the pty into one module that takes a state and a key and returns a state, and characterize today's behaviour, changing nothing a person sees.
src/tty.ts is 223 lines — drawing, cursors, the prompt, and per-mode key handling all in the process that owns the terminal — and bun test covers the server, the drag path and the theme only. AGENTS.md says every behaviour gets a test that would fail without it; the terminal has none, and #17 is about to change how it redraws. Characterizing it first is what makes that change safe.
Done looks like
[ ] src/keys.ts (new, one concept: what a key does to the terminal's cursor, prompt and board) exports handleKey(state, key, board) — plain data in, plain data out — where state is the cursor/prompt/filter/mode record src/tty.ts keeps in module scope today and board is the set of src/db.ts functions it calls, passed in rather than imported, so a test drives it against a temporary FORGE_APP_DATA with no pty.
[ ] src/tty.ts keeps only what needs a terminal: raw mode, reading keys, drawing, and exiting. Quitting becomes an outcome the module returns, not a process.exit inside the key handler.
[ ] src/tty.test.ts (new) characterizes what exists today, each test failing if that behaviour changed: j/k and the arrow keys move the board-list and board cursors and clamp at both ends; enter opens the board under the cursor and b goes back; n prompts and creates a board; a, e, d add, edit and delete the card under the cursor and the result is visible through db.listCards; H/L/K/J move that card between and within columns; c, r, D, [, ] create, rename, delete and reorder a column; f sets the filter and the cursor stays inside the filtered list; u undoes the last change; esc cancels a prompt and backspace deletes one character from it.
[ ] The cursor cannot point outside the board: after deleting the last card in a column, and after a filter that hides everything, the next key is handled without throwing.
[ ] Behaviour does not change. If a test disagrees with today's terminal, it records today's behaviour and the disagreement goes in the pull as a finding — it does not become a fix in this change.
[ ] bun test passes with the new tests added and every existing test file unchanged.
Files I expect to change
src/keys.ts — new.
src/tty.ts — reduced to the terminal itself.
src/tty.test.ts — new.
AGENTS.md — one line naming the boundary between the terminal and its key handling, in the "Shape" list's own voice.
Risks
The tests must not need a real tty: nothing in the module under test may touch process.stdin.setRawMode or write to stdout.
src/db.ts opens its database at import time from FORGE_APP_DATA; the test sets that before importing, the way the existing tests do.
#17 will build on this module. Keep the state a plain record so a revision poll can be added to it without another reshuffle.
Suggested: mason — this is maintenance with characterization tests and no behaviour change.
The terminal is the third seat at this board and it has no tests: pull its key handling out of the pty into one module that takes a state and a key and returns a state, and characterize today's behaviour, changing nothing a person sees.
src/tty.tsis 223 lines — drawing, cursors, the prompt, and per-mode key handling all in the process that owns the terminal — andbun testcovers the server, the drag path and the theme only. AGENTS.md says every behaviour gets a test that would fail without it; the terminal has none, and #17 is about to change how it redraws. Characterizing it first is what makes that change safe.Done looks like
src/keys.ts(new, one concept: what a key does to the terminal's cursor, prompt and board) exportshandleKey(state, key, board)— plain data in, plain data out — wherestateis the cursor/prompt/filter/mode recordsrc/tty.tskeeps in module scope today andboardis the set ofsrc/db.tsfunctions it calls, passed in rather than imported, so a test drives it against a temporaryFORGE_APP_DATAwith no pty.src/tty.tskeeps only what needs a terminal: raw mode, reading keys, drawing, and exiting. Quitting becomes an outcome the module returns, not aprocess.exitinside the key handler.src/tty.test.ts(new) characterizes what exists today, each test failing if that behaviour changed:j/kand the arrow keys move the board-list and board cursors and clamp at both ends;enteropens the board under the cursor andbgoes back;nprompts and creates a board;a,e,dadd, edit and delete the card under the cursor and the result is visible throughdb.listCards;H/L/K/Jmove that card between and within columns;c,r,D,[,]create, rename, delete and reorder a column;fsets the filter and the cursor stays inside the filtered list;uundoes the last change;esccancels a prompt and backspace deletes one character from it.bun testpasses with the new tests added and every existing test file unchanged.Files I expect to change
src/keys.ts— new.src/tty.ts— reduced to the terminal itself.src/tty.test.ts— new.AGENTS.md— one line naming the boundary between the terminal and its key handling, in the "Shape" list's own voice.Risks
process.stdin.setRawModeor write to stdout.src/db.tsopens its database at import time fromFORGE_APP_DATA; the test sets that before importing, the way the existing tests do.Suggested: mason — this is maintenance with characterization tests and no behaviour change.