Move signed-out and lazy-only code out of the initial bundle #354
- MA
## Why
The initial JavaScript entry was at its budget: 255781 B raw / 80560 B gzip against 256000 / 81500. Every design PR that added a few bytes failed the build. Most of the entry is react-dom (~181 KB). The app's own share was bloated mainly because Rollup puts a whole shared module in the entry when the entry imports even one export from it. That brought code used only by lazy chunks (repository picker cache, device sign-in, research calls) into first load.
## What changed
Mechanical moves only. No behaviour or styling changes. The budget is unchanged.
- `repository-cache.ts` now holds only the storage keys and `clearRepositoryCache`, which the entry needs. The snapshot reader and validators moved to `repository-snapshot.ts`, imported only by the lazy Add project dialog and repository picker. - The GitHub sign-in page (`HostedLogin`) moved to `hosted-login.tsx` and loads lazily, like `LocalLogin` already did. Its fallback is the same `Loading` screen that shows while the session loads, so there is no extra flash. - The legacy `/channels/:id` route's `ChannelWorkspace` moved to `channel-workspace.tsx` and loads lazily. Its Suspense fallback is the same "Opening document…" screen it showed while loading. - `childFocusTransition` and its types moved into `child-history.ts`. `anchored-child-surface.tsx` re-exports them, so `AnchoredChildSurface` stays in the lazy document host chunk. - The device-authorization calls moved to `device-authorization.ts`, used only by local login. The research-request calls moved into `research-requests.ts`, their only consumer.
No new idle prefetch was needed. Each new boundary renders the same placeholder the user already saw at that moment.
### Sizes (`bun run build`, bytes)
| Chunk | Before raw | After raw | Δ raw | | --- | ---: | ---: | ---: | | **initial entry (budgeted)** | **255781** (80560 gzip) | **246743** (77694 gzip) | **−9038** (−2866 gzip) | | add-project-dialog (lazy) | 3083 | 7247 | +4164 | | document-workspace-host (lazy) | 8579 | 9661 | +1082 | | local-login (lazy) | 8398 | 8854 | +456 | | room-workspace (lazy) | 172967 | 173386 | +419 | | channel-workspace (new, lazy) | – | 2064 | +2064 | | hosted-login (new, lazy) | – | 1333 | +1333 |
Headroom is now 9257 B raw / 3806 B gzip. Gzip figures for the entry come from the budget plugin. Other chunks changed by under 10 B (minifier noise).
## Screenshots
There is no visual change. These show each new lazy boundary rendering in a real browser on the fake-GitHub server:
Signed out (lazy `hosted-login`): 
Signed in, document open (entry → room workspace): 
Add project (lazy `repository-snapshot` code): 
## Testing
- `bun run build`: `[bundle-budget] initial JavaScript: 246743 B raw / 77694 B gzip` - `bun run types`: pass - `bun test apps/web/src`: 495 pass. `focus.test.ts` "leaves no component suppressing the outline" timed out at 5 s under full-suite load and passes alone (`bun test src/focus.test.ts`: 4 pass). It is unrelated, a file-scan timing issue. - `bun run ci`: dprint, oxlint, tokens, design record, and Impeccable pass. The design-contract hash check fails only for `repository-picker.tsx` (see below). - Browser (Playwright script, own headless context, port 8827): signed-out page; sign in; Add project; new document; account menu; Search dialog; Add project dialog; legacy `/channels/<id>` redirect to the document; phone 390×844 document. Lazy chunks load as expected: `hosted-login` only when signed out, `channel-workspace` only on the legacy route. There were no page errors. The only console errors are the fake avatar URL (`example.invalid`), as on main. - Local E2E is suspended for this run. Relying on CI's browser-integration job.
## Needs a design-contract review
- `apps/web/src/repository-picker.tsx` is pinned in `scripts/design-contract/exceptions/dynamic-web.json` (2 entries, `sourceHash` `dfc75e68e618e2002071f79f1cea48e30bded75c08166cf6c64af3750568ca59`). New hash: `abc9d2a5e3e05c195a599f1ef58f58f0dd1090b9507a0e1c20277fd4d7171a07`. The only change is the module path in two import lines (`./repository-cache` → `./repository-snapshot`). No style or class logic changed. I chose this file over editing the also-pinned `navigation-shell.tsx`, which open PRs #335 and #337 already touch.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
MaggieAppletonreferenced this in Calm entry and error states #344last month on GitHub
MaggieAppletonreferenced this in Add global keyboard shortcuts with hints and a shortcuts sheet #351last month on GitHub
MaggieAppletonreferenced this in Keep workspace chrome still when switching documents #346last month on GitHub
- MA
**Review:** Looks good
Verified on a checkout of the branch (fake GitHub, port 8851) against main (8795).
- `bun run build`: `initial JavaScript: 246743 B raw / 77694 B gzip`, matches the PR body. `hosted-login` and `channel-workspace` are separate chunks. - Consumers: grep for every moved export (`githubLoginHref`, `childFocusTransition` + child-focus types, device-authorization calls, research-request calls, snapshot reader/validators) shows no stale importers. `bun run types` passes. No static import of `hosted-login`, `channel-workspace`, `repository-snapshot`, `device-authorization` or `research-requests` from the entry graph (`app.tsx` and `navigation-shell.tsx` import only `repository-cache`). - `bun test apps/web/src`: 493 pass. The failures are the 5 s timeouts in file-scanning tests (`focus.test.ts`, `tokens.test.ts`) on a machine at load average ~85. They pass or time out at random and are unrelated to this change. - Browser (own headless Playwright, 1440x900 and 390x844, zero page errors or console errors): signed-out page, GitHub sign-in, home, legacy `/channels/:id` (redirects to the canonical document, `channel-workspace` chunk loads, "Opening document…" state shows exactly as on main), parent and child document, Add project dialog (opens, lists repositories, `add-project-dialog` chunk), repository picker. State sequence is the same as main: `Loading` then the sign-in page, with no blank frame or intermediate screen. - Not exercised: local login (server is in GitHub auth mode, but the `Api.*` to `DeviceAuthorization.*` rename is mechanical and type-checked), and the research request card (my seeded request produced no card because it needs a chat message). Its API functions moved verbatim and `research-requests.ts` is the only consumer. - `gh pr checks`: container pass, e2e pass, "format, lint, types, tests" fails only at `scripts/check-design-contract.ts` for `repository-picker.tsx:188/192` (the documented review hash; I did not touch it).
Notes (non-blocking): 1. Signed-out first view now waterfalls: index, session, then the `hosted-login` chunk. The fallback is the same `Loading` screen, so there is no flash, but the sign-in page paints one chunk round trip later (about 1 KB gzip, usually a few ms; it was slower in my run only because of heavy machine load). If that matters, `<link rel="modulepreload">` or a prefetch kicked off while the session loads would remove it. 2. `channel-workspace.tsx` takes `Failure`, `Loading` and `retryable` as props to avoid importing `hosted.tsx` at runtime (which would reintroduce a cycle with `hosted.tsx` lazily importing it). It works and the type-only import is fine, but a short comment on that constraint would help the next reader.
MaggieAppletonhead-ref-force-pushed 11631179f9d8last month on GitHub
MaggieAppletonmerged f949a91a39f5last month on GitHub
MaggieAppletonhead-ref-deletedlast month on GitHub
MaggieAppletonreferenced this in Prefetch the sign-in page and keep stale routes from canonicalizing #362last month on GitHub
- MA
Thanks for the review. #354 merged before the follow-ups landed, so they're in #362: 1. The `hosted-login` chunk now starts loading alongside `/api/session` (trace: both start at 145 ms, and the chunk is done at 208 ms vs the session at 448 ms). It is still outside the entry, which is now 246795 B raw / 77704 B gzip. 2. Added a comment in `channel-workspace.tsx` on why `Failure`/`Loading`/`retryable` are props. Also: the flaky 'adds a Project…' e2e on #351 was a pre-existing stale-route canonicalization race that the lazy `/channels` chunk widened. It's fixed in `DocumentRouteSwap` with a new regression e2e.