Public: anyone can read this; the crew can change it.

A small forge loads only what it uses: lazy-load the TUI, boards, studio and the like #12355

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

The terminal UI's screens now load on the first shell session instead of at boot. That is only the terminal UI part of #12349, though; boards and studio are not lazy yet.

shipped production is serving it deploy d003798

opened this

The terminal UI's screens now load on the first shell session instead of at boot. That is only the terminal UI part of #12349, though; boards and studio are not lazy yet.

What changed

  • src/tui/ssh.ts: attachTui is now async and imports ./forge.ts when a shell opens. Git over SSH, tell and talk do not need the screens, and the forge itself no longer holds them.
  • src/actions/tell_answer.ts (new): tellAnswer moved here from src/tui/screens/tell.ts, because ssh-tell.ts imported it and so dragged the whole screen tree in at boot. The screen re-exports it, and answers.ts and ssh-tell.ts import it from the new file. Nothing else changed.
  • test/boot-lazy.test.ts (new): fails if src/main.ts's static imports ever reach src/tui/screens/* or src/tui/forge.ts again.

Memory I booted bun run src/main.ts on a fresh data directory and read RssAnon after 25 s, with the SSH port on:

BeforeAfter
RssAnon at restabout 232 MiBabout 215 MiB

That is about 12–17 MiB lower across the runs I did, from roughly 290 fewer modules loaded at boot. I did not run the release proof's footprint reading in the sandbox, so that number is not in this pull.

What I left out

  • Boards, studio and the web pages: src/http/server.ts and src/http/settings_routes.ts import about 150 page modules statically. Lazy-loading them means a route-level dynamic-import change that touches every route, so it is a separate pull. The boards sampler is also started by the boot and by keeper/wire.ts, so it is not a clean lazy candidate.
  • A forge with no SSH port: SSH defaults to port 2222 (off for hosted children and read-only forges), so this saves the most there.

Checks

  • tsc --noEmit is clean.
  • The test/boot-lazy.test.ts, tui-tell and ssh tests pass, except test/ssh-port-taken.test.ts, which fails the same way on the base branch in this sandbox.
  • bun scripts/gate.ts printed nothing; I did not capture its exit status.
  • The first push was refused by the pre-push hook, which wants a Try: commit line, so there is an empty commit for it.

To try it: ssh -t into the forge and the terminal still opens, loading its screens on that first connection.

🤖 Generated with Claude Code

Closes #12349

4 earlier comments · 1 event
commented

The terminal UI's screens now load on the first shell session instead of at boot. That is only the terminal UI part of #12349, though; boards and studio are not lazy yet.

What changed

  • src/tui/ssh.ts: attachTui is now async and imports ./forge.ts when a shell opens. Git over SSH, tell and talk do not need the screens, and the forge itself no longer holds them.
  • src/actions/tell_answer.ts (new): tellAnswer moved here from src/tui/screens/tell.ts, because ssh-tell.ts imported it and so dragged the whole screen tree in at boot. The screen re-exports it, and answers.ts and ssh-tell.ts import it from the new file. Nothing else changed.
  • test/boot-lazy.test.ts (new): fails if src/main.ts's static imports ever reach src/tui/screens/* or src/tui/forge.ts again.

Memory I booted bun run src/main.ts on a fresh data directory and read RssAnon after 25 s, with the SSH port on:

BeforeAfter
RssAnon at restabout 232 MiBabout 215 MiB

That is about 12–17 MiB lower across the runs I did, from roughly 290 fewer modules loaded at boot. I did not run the release proof's footprint reading in the sandbox, so that number is not in this pull.

What I left out

  • Boards, studio and the web pages: src/http/server.ts and src/http/settings_routes.ts import about 150 page modules statically. Lazy-loading them means a route-level dynamic-import change that touches every route, so it is a separate pull. The boards sampler is also started by the boot and by keeper/wire.ts, so it is not a clean lazy candidate.
  • A forge with no SSH port: SSH defaults to port 2222 (off for hosted children and read-only forges), so this saves the most there.

Checks

  • tsc --noEmit is clean.
  • The test/boot-lazy.test.ts, tui-tell and ssh tests pass, except test/ssh-port-taken.test.ts, which fails the same way on the base branch in this sandbox.
  • bun scripts/gate.ts printed nothing; I did not capture its exit status.
  • The first push was refused by the pre-push hook, which wants a Try: commit line, so there is an empty commit for it.

To try it: ssh -t into the forge and the terminal still opens, loading its screens on that first connection.

🤖 Generated with Claude Code

commented

The terminal UI's screens now load when the first shell opens, not at boot. Behaviour is otherwise unchanged. The failed preview log showed no test or boot failure. It only showed the forge draining, so I ran the gate myself and it was red.

The cause: bun scripts/gate.ts rejected src/actions/tell_answer.ts. Its one-line header comment was 138 columns, over the 100-column rule. I rewrapped it. The gate reads committed content, so it only went green after I committed the change.

What changed:

  • src/actions/tell_answer.ts is new. It holds the words of one tell, refused or told, with the tone they are shown in. The terminal tell screen and the ssh tell command both use it.
  • src/tui/screens/tell.ts, src/tui/screens/answers.ts, src/tui/ssh-tell.ts and src/tui/ssh.ts now import the terminal screens when a shell opens, not at boot.
  • test/boot-lazy.test.ts checks that the static imports from src/main.ts never reach src/tui/screens/ or src/tui/forge.ts.

Checks run here:

  • bun scripts/gate.ts and bun x tsc --noEmit are clean.
  • Seven covering test files pass: 39 pass, 2 skipped, 0 fail. The two skips are in test/app-tell-fit.test.ts, because no chromium starts in this sandbox.
  • I did not run the 448 test files scripts/fast.ts named for this diff (about 37 minutes, over its 5-minute budget); the forge's check runs the full suite.

What I chose not to do: I did not touch other boot-time imports. This pull covers only the terminal UI.

To try it: open the forge over ssh with a tty and the terminal UI loads then. / on the preview answers as before.

Changes requested — before the latest push

Not a pipeline pull, and only a fraction of the issue: the title says TUI, boards, studio and the like, the diff lazy-loads the TUI only (the author says so). The ~12-17 MiB figure is self-measured and not in a test. Changes for one round:

  1. Race in attachTui: it is now async and awaits import(forge.ts) before app.start() and before the session is wired. Keystrokes or window-change events arriving during the first import (hundreds of modules, the slowest on the first shell) may be dropped or the pty size missed. Buffer or attach the session handlers before the await, and add a test that a shell opened cold still gets its first input and size.
  2. The fault path (.catch in forgeSshSession) is untested; add a test that a failing import ends the session with exit 1 and a log, not a hung terminal.
  3. tell.ts: export { tellAnswer } re-export sits between imports and keeps a second path to the helper; drop it and import from actions/tell_answer.ts in the callers (answers.ts, ssh-tell.ts already do). Put the imports together.
  4. boot-lazy.test.ts only proves the static closure; add a check that the dynamic import is reached on first shell, and fix the header grammar ("the terminal UI are").
  5. Either do the boards/studio lazy-loading the issue names or narrow the pull and issue so the title is true; open the follow-up issue for the rest.
  6. Remove the empty Try: commit noise by folding it into a real commit message.
commented

This pull now lazy-loads only the terminal UI, and I narrowed it to that. The other lazy-loading in the title is moved to #12372. bun x tsc --noEmit, scripts/gate.ts and the tests touching this code are green, but not the full suite, which is left to the forge's check.

What changed

  • Race on the first shell: attachTui in src/tui/ssh.ts now takes the session's input before it awaits the import of the screens. Keystrokes typed meanwhile are replayed in order to the screens' handler once it registers. Size and window-change are read live from the session, so start() sees the window as it is after the import.
  • Fault path: a failing import ends the session with exit 1 and logs "tui session fault". forgeSshSession takes an optional loader, which is what the tests use.
  • Tests: the new test/ssh-shell-cold.test.ts covers three cases.

- Input and a resize during a delayed import still arrive, in order. - A rejected import ends the session with exit 1. - The real dynamic import is reached on a shell and draws a first frame.

  • tell.ts: I dropped the tellAnswer re-export and put the imports together. test/tui-tell.test.ts now imports tellAnswer from actions/tell_answer.ts.
  • boot-lazy.test.ts: the header grammar is fixed. The "dynamic import is reached" check lives in the new test file.

What I chose not to do

  • Boards and studio: I did not lazy-load them. Rather than widen this pull, I opened #12372 for them and for measuring the rest of the boot. The pull title and the issue still say "boards, studio and the like"; I can't edit them, so a person should narrow them to the TUI.
  • Folding the "Try:" commit: I couldn't, because rewriting history needs a force push, which is refused. It stays a separate commit, and the new fix is a commit on top of it. Squash on merge if the noise matters.
  • Log assertion: the fault test checks exit 1 but not the log line, because the suite silences logs. The log call is unchanged.
  • The memory figure: the ~12–17 MiB number is still my own measurement and has no test. boot-lazy.test.ts only proves the boot's static imports no longer reach the TUI.

To try it: ssh -t <forge> opens the terminal UI, loading it on the first shell. bun test test/ssh-shell-cold.test.ts test/boot-lazy.test.ts runs the new checks.

🤖 Generated with Claude Code

commented

I fixed the four gate violations in test/ssh-shell-cold.test.ts and pushed. The gate, tsc --noEmit and the file's three tests pass locally. I did not run the full suite; the forge runs it on the push.

  • Fake loader: the cast as never is now as unknown as typeof CreateForgeApp, with a type-only import of createForgeApp from src/tui/forge.ts. I did not build a full record, because the test only needs the stub to answer onInput, size, start and stop.
  • Sleeps: the three Bun.sleep lines now end with // real-timer: <why>. I kept the sleeps rather than injecting a clock. The first two only wait for a settled import promise. The third waits on the real dynamic import, which has no hook to await.

Behaviour of the shipped code is unchanged. The gate reads committed content, so it only reflected the fix once I committed.

commented

The terminal UI's screens now load on the first shell instead of at boot. Last check: 9 tests failed, in shards 10 and 15. I could not reproduce any of them here, so I changed no code and pushed an empty commit to run the check again.

What changed

  • Lazy load: the terminal UI's screens load when the first ssh shell opens, not at boot.
  • Cold shell: input typed before the UI has loaded is held until it does.
  • tell re-export: dropped.
  • tell_answer: its header comment is wrapped to 100 columns.
  • Tests added: test/ssh-shell-cold.test.ts and test/boot-lazy.test.ts.

Why I think the red is not this pull

  • The failing tests cover the engine thread's stop-and-restart, clone URLs, and the internal-address and trusted-host refusals. None of them touch the terminal UI or ssh code.
  • test/engine-split.test.ts passes here, and the check log says it is green on main at 4345bb2.
  • The seven shard-15 failures took 0.4 to 13 ms each, so they failed immediately rather than timing out. I ran the packs, trusted-hosts, migrate, fork-flow and related files together with the two new test files: 324 pass, 0 fail.
  • The new tests set no globals, env vars or module mocks, so they cannot leak into other files.

If the same shard-15 tests fail again after the re-run, the cause is in the check environment or ordering and needs a person to look at it.

What I chose not to do

  • I did not change or skip any existing test to get a green run.

How to try it

  • Open an ssh shell to the preview with a tty. The terminal UI loads on that first shell, and typing straight away works.
  • Locally, run bun test test/ssh-shell-cold.test.ts test/boot-lazy.test.ts.

🤖 Generated with Claude Code

Approved

Earlier asks (input typed during the import must not be lost, a failed import must end the session, tellAnswer moved so the boot does not reach the screens, a static-import guard test) are met: held() replays input in order, the failure path exits 1 and logs, and boot-lazy.test.ts pins the boot closure. Preview / answers 200. No auth, body-cap or escaping surface changes; the terminal gating is unchanged since the screens are the same code, loaded later. I could not diff against 5fb80837dd94 (commit absent in the clone), so I judged the change as shown.

Non-blocking: 1) in src/tui/ssh.ts attachTui, if the client closes during the import, the screens still start on an ended session; check session.ended after await load() and return. 2) The title still says boards and studio; #12372 carries those, so a person should narrow the title. 3) The fault test asserts exit 1 but not the log line.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs