[0.4.0] — 2026-04-18 · Stop 5 pass 1 — client code extracted to src/client/ (branch stop-5/client-split)
Architectural refactor. Client runtime is no longer embedded inside a template literal inside src/html-generator.ts; it now lives as a real TypeScript file that's compiled alongside the server and inlined at generation time. No user-visible behaviour change.
This is pass 1 of the workflow's Stop 5 — a mechanical extraction that moves the whole ~1,100-line embedded script out in one go. Pass 2+ will carve main.ts into smaller panels/, components/, render/, and state/ modules without needing another bulk-extraction turn.
src/client/main.ts(1,134 lines) — every function, state variable, and bootstrap call that used to live inside the generator's embedded<script>. Written intentionally as a script (no imports, no exports) so tsc emits a plain browser-ready.jsruns top-to-bottom.src/client/globals.d.ts— ambient declarations for the server-injected globals (DATA,MARKDOWN*,REPORT_NAME,APP_VERSION,GENERATED_AT,DaxHighlight) so the extracted code type-checks without pulling server-only types into the client tree.readCompiledClient()helper inhtml-generator.ts— readsdist/client/main.jsat generation time (same three-candidate-path pattern as the DAX vendor files), strips the tsc-insertedexport {};so the inline lands cleanly inside a classic<script>.
src/html-generator.ts: 1,740 → 667 lines (−62%). The generator now contains the HTML shell, the inline CSS, the data-injection block (const DATA = …; const MARKDOWN = …;etc.), and a single${CLIENT_JS}inline for the compiled client bundle. Remaining weight is mostly CSS — a follow-up PR can extract it tosrc/styles/and get us to the workflow's ≤ 250-line target.- No build-pipeline changes. The existing root
tsconfig.jsonalready compilessrc/client/main.ts→dist/client/main.jsbecauserootDiris./src. Zero new scripts, zero new config files.
- Carving
main.tsinto 16 smaller modules (panels, components, render, state). That's mechanically independent of the extraction itself and reviews more cleanly as a sequence of smaller PRs. The file has a// @ts-nocheckheader so TypeScript doesn't flag the untyped client code — each future carve turns that off for the slice being extracted. - CSS extraction (would drop
html-generator.tsto ~250 lines). - A dedicated
src/client/tsconfig.json— not needed because the root config already handles it.
49 / 49 green (unchanged; the existing suite already covered what this refactor could break). Build and typecheck clean. Live smoke against test/Health_and_Safety.Report: HTTP 200, dashboard size 752 KB, all 6 key functions (renderMeasures, addCopyButtons, navigateLineage, highlightDaxBlocks, switchTab, bootstrap) present exactly once, no stray export {}; in the output.
tests/render-dax-highlight.test.tstest 21 (addCopyButtons does not call highlightDaxBlocks first) broke because tsc's emitted formatting adds whitespace between)and{that the inline version didn't have. Relaxed the regex to tolerate whitespace.