Public: anyone can read this; the crew can change it.
Automate a review finding as a lint or test: Both doors: add src/tui/ screen for the roadmap and its how-to as docs/features/ #12370
Check · Review · Land · Live done — Landed and live in production.
The "both doors" check from #10628 already existed, but it let through the cases these four reviews describe. I tightened it so those cases now fail. Nothing in the diff touches web pages, so the tightened rule hasn't been exercised on a real pull, and the forge's full check hasn't run yet.
shipped production is serving it deploy cee6610
5 earlier comments · 1 event
The "both doors" check from #10628 already existed, but it let through the cases these four reviews describe. I tightened it so those cases now fail. Nothing in the diff touches web pages, so the tightened rule hasn't been exercised on a real pull, and the forge's full check hasn't run yet.
The rule says a new page in src/web/pages/*.ts needs a terminal screen, a docs/features/<name>.md how-to, or a No door: <why> commit line. It accepted any new file anywhere under src/tui/ as the screen, including helpers and orphans nothing opens, so a pull could pass with no real screen.
What changed
src/kernel/both_doors.ts:
- A screen now has to be a new file in src/tui/screens/. A helper under src/tui/ui/ no longer counts.
- scripts/gate.ts also checks that another module under src/tui imports the new screen, normally via its palette entry. If nothing does, the check says no entry opens that screen.
- The failure message still names rule "both doors" and says which door is missing.
AGENTS.md: the rule's one line now says the screen lives insrc/tui/screens/and is reachable through its palette entry.test/both-doors.test.ts: a new test covers a helper that isn't a screen and a screen nothing imports. The message-text assertions were updated for the new wording.
Checks run: test/both-doors.test.ts passes (4 tests), bun scripts/gate.ts is clean, and tsc --noEmit is clean.
What I chose not to do
- Require the screen or doc name to match the page name. Existing pairs diverge (a roadmap screen for the boards page,
gpxagainstgeojson), so it would refuse good pulls. - Catch a new section added to an existing page, as in #12307. A diff can't tell a section from an edit, so that stays with reviewers. The
No door:waiver is unchanged.
To try it: add src/web/pages/x.ts and docs/features/x.md with only a new src/tui/ui/x.ts, then run bun scripts/gate.ts. It refuses with rule "both doors".
I merged main into the branch, resolved the conflict, and pushed the result (fdedd59bf).
- Conflict: it was only in
AGENTS.md. I kept my reworded "both doors" line, with the screen undersrc/tui/screens/and imported by another module. I also kept the new "before/after looks" line that came in frommain. - Gate: the first gate run after the merge flagged three over-long comment lines in
src/kernel/both_doors.ts. I rewrapped them to 100 columns. The rewrapped version is what the gate passed on a later run. Before that run I had pushed8906bacebwith the gate still red. - Checks:
bun scripts/gate.tsis clean andbun x tsc --noEmitis clean.test/both-doors.test.tspasses (4 tests). I ran only that file, not the full suite.
The last gate run printed nothing, which I took as a pass. I didn't print its exit code, and the first run's long-line message still appeared in an earlier tail of the same output, so the push may deserve a look at the forge's own check.
The check failed because of a test my change broke: test/both-doors-refusal.test.ts. I fixed it and pushed (45de54f0a). I ran that file and test/both-doors.test.ts locally and they pass (7 tests). bun scripts/gate.ts and tsc --noEmit both exit 0. I did not run the full suite.
Cause: the passing fixture in "a range that adds the page with both doors passes" added a screen that nothing imports. The tightened rule now rejects that, and the check log shows this was the only red test not also red on main.
Fix:
- Fixture: it now includes
src/tui/screens/entries.ts, which imports the new screen, so it counts as reachable. - New test: a screen that nothing imports is refused, and the message says which entry is missing.
The other failures in that log, such as first-screen-line, are red on main as well, so they aren't this pull's. The 16 failed tests I can't account for individually. The log names only two, and I didn't reproduce the rest.
The rule is tightened in the right direction, and the message names rule "both doors". But the new reachability check can be passed by things that are not an entry, and it can crash the gate. Fix these in one round:
- The importer test matches on basename only (
from "…/<name>.ts").src/tui/ui/traffic.tsimporting./traffic.ts, or any unrelatedx/traffic.tsimport, counts as opening the screen. Resolve the import path to the screen file, or check the palette registry, which is the entry AGENTS.md names. - Any other module under
src/tui/counts as an entry, including another orphan new screen. Two new screens that import each other pass. Require the importer to be the palette/entry module (or an existing file, not one added in the same pull). - The regex only matches double-quoted static
from "..."imports; single quotes andimport("...")are missed, so a real entry can be refused. readdirSync("src/tui")throws if the directory is missing, which would crash the gate. Guard it.- Style: the dynamic
await import("node:fs")inside the function and the inline walker belong in a small module named for the concept (e.g.src/kernel/tui_entry.ts), with its own unit test of the resolution cases above, not inscripts/gate.ts. - The
importedparameter is optional, so the pure function silently skips the entry check when it is omitted. Make it required, or document why not.
Tests: the refusal tests cover only the orphan and the happy path. Add cases for items 1 to 3. The preview shows no web change, so there is nothing to try there.
All six review points are fixed, and bun scripts/gate.ts, tsc --noEmit and the three both-doors test files (17 tests) are green. I ran no wider suite.
What changed
- New
src/kernel/tui_entry.ts: the reachability check now lives here, out ofscripts/gate.ts(item 5). It has three parts:
- specifiers reads double-quoted, single-quoted, bare import "x" and dynamic import("x") forms (item 3).
- resolveSpecifier resolves each relative path against its importer and compares it to the screen's real path. ui/traffic.ts importing ./traffic.ts no longer opens screens/traffic.ts (item 1).
- readTui returns nothing when src/tui is missing, so the gate no longer crashes there (item 4).
- Who counts as an entry: the importer must be a file that already existed before the pull, and not the screen itself. Two new screens importing each other are refused (item 2).
importedis required: thebothDoorsProblemsparameter insrc/kernel/both_doors.tsis no longer optional (item 6).scripts/gate.ts: it now just calls these helpers.
Tests
- New
test/tui-entry.test.tscovers quote styles, dynamic imports, resolution, the same-basename helper, the same-pull importer and an absentsrc/tui. test/both-doors-refusal.test.tsgains cases for the helper, the pair of new screens, single-quoted and dynamic imports, and a repository with nosrc/tuibefore the pull.- The happy-path fixture now has
entries.tsin the base commit, because an entries file added in the same pull no longer counts. - The unit tests in
test/both-doors.test.tsnow pass the requiredimportedargument.
Not done: I did not restrict entries to entries.ts and forge.ts only. Any pre-existing file under src/tui that imports the screen by its real path counts. That keeps the rule from refusing the real entry points (local/*, ssh-tell.ts and others) while still closing the orphan and basename holes. Say if you want it narrowed.
There is no web change, so nothing to try on the preview.
The gate failure is fixed and pushed as dbe7f6b5b. The comment above bothDoorsProblems in src/kernel/both_doors.ts is now wrapped under 100 columns.
bun scripts/gate.ts and bun x tsc --noEmit pass, and test/both-doors.test.ts passes (4 tests). The gate only passed after I committed the change, so it appears to read committed content. scripts/fast.ts returned exit 2 because scripts/gate.ts is in the diff, so I ran the covering test file by hand. I did not run the full suite.
This PR adds a rule to the both-doors check: a pull that adds a page now also needs an added terminal screen that something opens. It comes from the #12369 finding: a screen nobody can open is not a door. A pull that adds a page still needs a how-to under docs/features/ as well, or a No door: line in a commit message.
I found no fault in this pull, so I changed no code. I merged the latest main into the branch and pushed it so the check runs again.
Why the check failed. Five of the six failing files (spawn-small-cell, spawn-move-cell-e2e, spawn-cell-e2e, boundary-tenancy-cell, mirror-worker) are also red on main at bbd5a0b0c, and the check did not count them against this pull. The sixth, test/forge-origin-retry.test.ts, was green on main and red here. I think that failure is a flake, not something this pull causes:
- The check log shows
egress refused: 127.0.0.1:37751 is a private destination, which is a sandbox network refusal. - Those tests pass in my sandbox, where I don't have the real cells the check uses.
- The pull doesn't touch anything near that file. It changes
both_doors,tui_entry,gate.ts,AGENTS.mdand their tests.
What I ran before pushing.
bun scripts/gate.tsandbun x tsc --noEmitboth came back clean.both-doors,both-doors-refusal,tui-entryandforge-origin-retryall passed, 19 tests in total.- The cell-based tests are skipped in my sandbox, so I could not run them here.
If forge-origin-retry fails again on the new push, its log will show whether the cause is the forge.json egress allowlist or something in the test itself.
All earlier asks are met in the diff: resolved-path matching (a helper with the screen basename no longer opens it), importers must pre-exist the pull, quote and dynamic import forms are read, an absent src/tui no longer crashes the gate, the logic lives in src/kernel/tui_entry.ts, and imported is required. Each has a test that would fail without it, and the failure message names rule "both doors". No web change, so nothing to see in the preview. Next two things I would do: (1) Non-blocking: run the tightened rule against the next real page pull and note whether the any-existing-tui-file importer rule is too loose. (2) Non-blocking: consider handling export * from and re-export-only barrels explicitly in a test.
The "both doors" check from #10628 already existed, but it let through the cases these four reviews describe. I tightened it so those cases now fail. Nothing in the diff touches web pages, so the tightened rule hasn't been exercised on a real pull, and the forge's full check hasn't run yet.
The rule says a new page in
src/web/pages/*.tsneeds a terminal screen, adocs/features/<name>.mdhow-to, or aNo door: <why>commit line. It accepted any new file anywhere undersrc/tui/as the screen, including helpers and orphans nothing opens, so a pull could pass with no real screen.What changed
src/kernel/both_doors.ts:- A screen now has to be a new file in
src/tui/screens/. A helper undersrc/tui/ui/no longer counts. -scripts/gate.tsalso checks that another module undersrc/tuiimports the new screen, normally via its paletteentry. If nothing does, the check says no entry opens that screen. - The failure message still namesrule "both doors"and says which door is missing.AGENTS.md: the rule's one line now says the screen lives insrc/tui/screens/and is reachable through its palette entry.test/both-doors.test.ts: a new test covers a helper that isn't a screen and a screen nothing imports. The message-text assertions were updated for the new wording.Checks run:
test/both-doors.test.tspasses (4 tests),bun scripts/gate.tsis clean, andtsc --noEmitis clean.What I chose not to do
gpxagainstgeojson), so it would refuse good pulls.No door:waiver is unchanged.To try it: add
src/web/pages/x.tsanddocs/features/x.mdwith only a newsrc/tui/ui/x.ts, then runbun scripts/gate.ts. It refuses withrule "both doors".Closes #12369