Intent. The terminal shipped with the tables (#7) and has no test at all — it opens the database at module scope, draws straight to stdout, and there is no src/tty.test.ts. AGENTS.md says every behaviour gets a test that would fail without it; do that for the door that #14 is about to build a whole game into, while it is still ninety lines.
Done looks like
[ ] src/tty.ts splits in two along the line that already exists: a pure screen builder — state in, a string of lines out, no database, no process.stdout, no environment — and a thin loop that reads keys, calls actions and prints what the builder returns.
[ ] The loop opens the database and reads whoAtTty(); the builder is handed what it needs. Nothing decides a rule in either half: a refusal from an action is printed in the action's own words on the status line.
[ ] src/tty.test.ts (new) has a test per screen: the lobby with no tables, with one open table, with a table the viewer is seated at, and as a guest; the key legend for each; and the status line after a named refusal.
[ ] The header prints FORGE_APP_SSH verbatim when it is set and says the terminal is off when it is not — tested both ways with an injected environment, never by reading process.env inside the builder.
[ ] Escape sequences live in one place with names, so a test asserts the text of a screen rather than a soup of \x1b[.
[ ] What a person can do on the page they can do here and nothing more: a test asserts the guest screen offers no key that writes.
Filessrc/tty.ts, src/tty.test.ts (new), AGENTS.md (one line: the terminal's screen is a pure function).
Risks This is a refactor of working code, so the pull should read as a move rather than a rewrite — same output, now testable. Do not change what the terminal does; #14 does that next, on top of these tests. src/tty.ts is free: no open pull touches it.
Brief that fits @hearth — a console that is honest about what it is doing. (A person assigns.)
Intent. The terminal shipped with the tables (#7) and has no test at all — it opens the database at module scope, draws straight to stdout, and there is no
src/tty.test.ts.AGENTS.mdsays every behaviour gets a test that would fail without it; do that for the door that #14 is about to build a whole game into, while it is still ninety lines.Done looks like
src/tty.tssplits in two along the line that already exists: a pure screen builder — state in, a string of lines out, no database, noprocess.stdout, no environment — and a thin loop that reads keys, calls actions and prints what the builder returns.whoAtTty(); the builder is handed what it needs. Nothing decides a rule in either half: a refusal from an action is printed in the action's own words on the status line.src/tty.test.ts(new) has a test per screen: the lobby with no tables, with one open table, with a table the viewer is seated at, and as a guest; the key legend for each; and the status line after a named refusal.FORGE_APP_SSHverbatim when it is set and says the terminal is off when it is not — tested both ways with an injected environment, never by readingprocess.envinside the builder.\x1b[.Files
src/tty.ts,src/tty.test.ts(new),AGENTS.md(one line: the terminal's screen is a pure function).Risks This is a refactor of working code, so the pull should read as a move rather than a rewrite — same output, now testable. Do not change what the terminal does; #14 does that next, on top of these tests.
src/tty.tsis free: no open pull touches it.Brief that fits @hearth — a console that is honest about what it is doing. (A person assigns.)