feat: drive a tracer bullet through spec, layout, svg and pdf #28

Merged
TJC merged 10 commits from feat/m1-tracer-bullet into main 2026-08-18 11:13:44 +01:00
Collaborator

Summary

  • drive one hardcoded template end to end: spec tree, fixed-size layout, flat
    primitives, SVG preview, pdf-lib download
  • layout emits the decision 20 baseline once; a text primitive carries no box
    top, so neither renderer can derive one independently
  • eslint keeps react and the dom out of everything outside src/ui, before the
    first line of core
  • src/widgets is not created here; it holds the bundled JSON specs of decision
    13 and M1 has no widget to put in it
  • closes #3, closes #4, closes #5, closes #6, closes #7

Test plan

  • pnpm test green: 22 tests over layout, both renderers and the boundary
  • pnpm lint, pnpm build green; pnpm dev serves the sheet
  • parity checked against the emitted PDF by hand: MediaBox 595.266 x 841.8762,
    every coordinate mm * 2.8346 of its SVG value, /Helvetica not embedded, 969
    bytes
  • print the download and confirm it matches the preview on paper
## Summary - drive one hardcoded template end to end: spec tree, fixed-size layout, flat primitives, SVG preview, pdf-lib download - layout emits the decision 20 baseline once; a text primitive carries no box top, so neither renderer can derive one independently - eslint keeps react and the dom out of everything outside src/ui, before the first line of core - src/widgets is not created here; it holds the bundled JSON specs of decision 13 and M1 has no widget to put in it - closes #3, closes #4, closes #5, closes #6, closes #7 ## Test plan - `pnpm test` green: 22 tests over layout, both renderers and the boundary - `pnpm lint`, `pnpm build` green; `pnpm dev` serves the sheet - parity checked against the emitted PDF by hand: MediaBox 595.266 x 841.8762, every coordinate mm * 2.8346 of its SVG value, /Helvetica not embedded, 969 bytes - [x] print the download and confirm it matches the preview on paper
- one package under src, not a workspace
- strict tsconfig with the @/ alias onto src

Closes #3
- decision 22: the core runs headless, so the boundary lands before the
  first line of it
- pin typescript to 6.x; typescript-eslint has no TS 7 support yet

Closes #4
- primitives are flat, mm-only and stamped with the emitting node id
- the baseline is computed once here so neither renderer derives it
- layout takes a box; the caller turns a page size into one

Closes #5
- the renderer emits elements, not markup, so it stays clear of the dom
- mm viewBox at sheet size; primitive values go through unchanged

Closes #6
- vitest runs without globals, so testing-library never registers its own
  afterEach and renders pile up in one document
- mm -> pt and the origin flip happen here and nowhere else
- ops are split from the pdf-lib calls so the arithmetic is assertable
  without parsing a PDF back
- both renderers place text on the baseline layout emitted; neither can
  derive one, since a text primitive carries no box top
- export takes a list of sheets, per decision 16

Closes #7
Bolt added this to the M1 tracer bullet milestone 2026-08-18 11:04:49 +01:00
- the truncated 2.8346 sized the mediabox at 595.266pt, not a4
- heightOf reserves fontSize * lineHeight, the baseline ignored it
- at the default 1.2 every glyph sat flush at the top of its box
- firefox and safari need the anchor in the document to download
- revoking in the click tick invalidated the blob first
- a css colour name rendered in svg and threw at the pdf boundary
TJC requested review from TJC 2026-08-18 11:12:46 +01:00
Author
Collaborator

Applied the review findings in a0c1b7b..af06934, one commit each. Tests (22), tsc and eslint all pass.

  • PT_PER_MM now derives from 72 / 25.4. The truncated 2.8346 sized the MediaBox at 595.266pt rather than exact A4, which can trigger fit-to-page scaling on print.
  • The text baseline adds half the leading. heightOf reserves fontSize * lineHeight, so at the default 1.2 every glyph sat flush at the top of its line box with all the spacing below it.
  • The download anchor is appended to the document and the object URL is revoked off-tick. Firefox and Safari never started the download otherwise.
  • Ink is a hex template-literal type, and inkToColor rejects malformed hex with a named error. ink: 'red' used to render fine in SVG and blow up inside rgb() at export.

Left alone: void download(sheet) in App.tsx still drops render failures on the floor. That wants real error surfacing, not a .catch that logs.

Applied the review findings in a0c1b7b..af06934, one commit each. Tests (22), tsc and eslint all pass. - `PT_PER_MM` now derives from `72 / 25.4`. The truncated 2.8346 sized the MediaBox at 595.266pt rather than exact A4, which can trigger fit-to-page scaling on print. - The text baseline adds half the leading. `heightOf` reserves `fontSize * lineHeight`, so at the default 1.2 every glyph sat flush at the top of its line box with all the spacing below it. - The download anchor is appended to the document and the object URL is revoked off-tick. Firefox and Safari never started the download otherwise. - `Ink` is a hex template-literal type, and `inkToColor` rejects malformed hex with a named error. `ink: 'red'` used to render fine in SVG and blow up inside `rgb()` at export. Left alone: `void download(sheet)` in `App.tsx` still drops render failures on the floor. That wants real error surfacing, not a `.catch` that logs.
TJC merged commit 1c347bab30 into main 2026-08-18 11:13:44 +01:00
Sign in to join this conversation.
No reviewers
TJC
No labels
chore
core
render
ui
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TJC/daily-planner-template!28
No description provided.