Fix glyph cache poisoning across renders; add CLAUDE.md and handoff.md
fontkit caches glyphs per font for the whole process and pdfkit embeds composite components with no code points, so setting ':' before '.' in Poppins corrupted every later render in the same process (text layer showed '1,200;00', line breaks shifted). primeFontGlyphs() now creates the component glyphs with their cmap code point at font registration, awaited by renderCore and browser init. Regression test and Quirk 13 added; the warm-up workarounds in the template tests are removed.
This commit is contained in:
+87
@@ -0,0 +1,87 @@
|
||||
# Voiced: handoff
|
||||
|
||||
Written 2026-10-04. Voiced is the invoicing app built by Bongbetic for vendor Test Vendor (Tauri v2, React 19, IBM Carbon, @react-pdf/renderer 4.9.0, SQLite via rusqlite).
|
||||
|
||||
## Where things are
|
||||
|
||||
| Item | Location |
|
||||
|---|---|
|
||||
| Repository (execute from here) | `/media/Toto/Documents/bongbetic/invoice` |
|
||||
| Approved plan | `/home/soubarna/.claude/plans/take-a-look-at-logical-sunbeam.md` |
|
||||
| Working branch | `phase-c-templates` (remote `origin`, git.bongbetic.com/xavierk/Voiced) |
|
||||
| Template specs (13, measured from Canva PDFs) | `docs/templates/` (`README.md` plus `01`–`13`) |
|
||||
| Canva source PDFs (never commit) | `templates/Modern Neutral Invoice Template/1.pdf`–`13.pdf` |
|
||||
| Bongbetic logos | `/media/Toto/Documents/bongbetic/Logo/bongbetic-brand/` |
|
||||
| Full prior conversation | `/home/soubarna/.claude/projects/-media-Toto-Documents-bongbetic-invoice/1cd7ecdc-a943-4728-bbf5-efb12a36b04e.jsonl` |
|
||||
|
||||
**Continue all work from `/media/Toto/Documents/bongbetic/invoice`, on branch `phase-c-templates`.** Read the plan first. It holds the phase breakdown, quirk list, GST rules, ERPNext design, risks and the Verification section. Create new phase branches from the previous one (the existing branches are stacked: `phase-0-hotfix` → `phase-a-foundation` → `phase-b-engine` → `phase-c-templates`). Never stage `templates/` or `.commandcode/`.
|
||||
|
||||
## Progress
|
||||
|
||||
The plan totals about 108 developer-days (0: 1.5, A: 10.5, B: 33, C: 34, D: 5, E: 12, F: 10, G: 2). Phases 0, A, B and C hold about 79 of those days of scope, roughly 73 %. D to G remain, about 29 days of scope.
|
||||
|
||||
| Phase | Scope | State |
|
||||
|---|---|---|
|
||||
| 0 | Header-overlap and logo hotfix | Done, pushed (`phase-0-hotfix`) |
|
||||
| A | DB migrations M1–M5, issue/cancel lifecycle, GST derivation with integer-paise maths, drafts, shortcuts | Done, pushed (`phase-a-foundation`) |
|
||||
| B | Render engine (worker, pdf.js preview, searchable and flattened export, archive store, history re-export, unattended self-test) | Done, pushed (`phase-b-engine`) |
|
||||
| C | Logo pipeline and Branding settings, template picker, Classic plus all 13 Canva templates, glyph-cache engine fix | Done. Templates pushed through `5795087`; the glyph fix is in the commit that follows it |
|
||||
| D | Font import for paid fonts, Void font packaging | Not started |
|
||||
| E | Workflow QoL | Not started |
|
||||
| F | ERPNext integration | Not started |
|
||||
| G | Release | Not started |
|
||||
|
||||
Templates implemented: Classic, Neutral, Ledger (family), Band (family), Northline family (Purple Pop, Citrus Split), and the standalone Monolith, Cobalt Stripe, Marble and Highlighter. That is 14 in total. Each has tests, an "Implementation notes" section in its spec, and a thumbnail in `public/templates/`.
|
||||
|
||||
Other gates last run at `5795087`: `npm run fonts:verify` OK (11 families, 33 fonts); `npm run logo:measure` idempotent.
|
||||
|
||||
## Glyph-cache fix (done)
|
||||
|
||||
fontkit caches glyph objects per font for the whole process, and pdfkit creates a composite glyph's components at embed time with empty code points. Poppins Regular's ":" contains ".", so setting ":" before any "." corrupted the period for every later render in that process (amounts extracted as "₹1,200;00", some line breaks shifted). The render worker lives for the whole app session, so the app was affected, not only the tests. Every bundled face has composite glyphs, not just Poppins.
|
||||
|
||||
Fix: `primeFontGlyphs()` in `src/pdf/fonts/register.ts` loads every registered face and creates, with their cmap code point, only the glyphs that composites use as components (private-use code points skipped, lowest code point wins). It is memoised and awaited in `renderCore` (`src/pdf/render/core.ts`) and in `initBrowserRendering` (`src/pdf/render/browserInit.ts`). A first attempt that primed every cmap glyph broke Inter's contextual hyphen (the hyphen in `INV/2026-001` vanished from the text layer), so do not widen it. It uses fontkit private APIs (`_getBaseGlyph`, `_decode`, `_cmapProcessor`, `_glyphs`): re-check it on any fontkit upgrade.
|
||||
|
||||
Cost: about 620 ms once per process under Node. Per-render cost is unchanged. Worker init time was not measured separately; the whole real-webview self-test took 3.4 s.
|
||||
|
||||
Regression coverage: `src/pdf/fonts/glyphPriming.test.tsx` (new) and Quirk 13 in `src/pdf/quirks.test.tsx`. The warm-up workarounds were removed from the Highlighter test and the standalone sweep. Monolith-then-Highlighter and Highlighter-then-Monolith now give identical layout fingerprints. No golden changed.
|
||||
|
||||
Last verified gates (with the fix, 2026-10-04): `npm run build` OK; `npm test` 46 files, 1539 passed, 6 skipped, 385 s; real-webview self-test `ok: true` with both fingerprint-vs-golden checks passing.
|
||||
|
||||
## Next steps, in order
|
||||
|
||||
1. Small template fixes from the C6a/C6b reports:
|
||||
- Monolith and Cobalt Stripe: when only the closing block moves to page 2, the page-1 squeeze can fall a few points short. Marble and Highlighter already have the fix.
|
||||
- Monolith: the flow notes block can collapse to zero-height lines when it starts with less than a line of room. Marble guards against this.
|
||||
2. Phase D:
|
||||
- Font import for Now, Gotham and Open Sauce overrides: a `user_fonts` store, with WOFF2 and variable fonts refused.
|
||||
- Void xbps font install to `/usr/share/fonts/voiced` with licences, `font_dirs` plus INSTALL/REMOVE scripts that run `fc-cache`. Verify that `xbps-create` honours INSTALL/REMOVE.
|
||||
- Final visual review of all 14 templates against Canva.
|
||||
3. Phase E: clients page (structured addresses, GST category), item presets, invoice detail view, History DataTable with filters, payments with TDS, settings tabs and toasts, backup/restore (db, assets, archive, fonts, with a round-trip test), CSV and JSON export.
|
||||
4. Phase F: ERPNext (Rust `integrations/erpnext`, reqwest 0.13.5 with rustls ring, config UI, push as draft or submit, PDF attachment, idempotency, payment entries, `voiced.invoice.v1` JSON export). End-to-end test with podman frappe_docker, with and without India Compliance.
|
||||
5. Phase G: README (data paths, fonts and licences, ERPNext recipe), version bump, xbps template refresh, NSIS build within the size budget, self-test on both installed builds.
|
||||
|
||||
## Open decisions and manual checks
|
||||
|
||||
- Canva licence for re-creating the templates is unconfirmed. Ship only in this vendor's build until it is.
|
||||
- Wordmark replaces the whole logo lockup (a toggle is planned).
|
||||
- A CA should confirm the SAC and signature rules.
|
||||
- Open Sauce One is not bundled (Poppins 800 stands in). Plex and Noto are installed as identical duplicates of Void's packages.
|
||||
- Picker family label: Monolith, Cobalt Stripe, Marble and Highlighter show as "Standalone". Change if wanted.
|
||||
- Monolith shows the supplier's full address only on the last page of a multi-page invoice (as in Canva). Say if page 1 should carry it too.
|
||||
- Not yet checked: whether the app surfaces layout audit errors to the user.
|
||||
- Never tested: Windows/WebView2 (no Windows machine); the app window UI has only been exercised headless, never opened visually (Branding UI and template picker need a look); the B4/B5 manual checklists on an installed build.
|
||||
- The P2 extras are deferred (copies, watermarks, PDF/A, accent override, print-friendly mode, Wide/Custom margins, user textures, SVG logo import, PNG flatten, UTGST labels beyond the supplier-state rule, CESS, composition and SEZ).
|
||||
|
||||
## How to run things
|
||||
|
||||
- Build: `npm run build`. Tests: `npm test` (about 7 minutes; run on a quiet machine, since load can starve the sweep tests).
|
||||
- Fonts: `npm run fonts:verify`. Logo placeholders: `npm run logo:measure` (must leave `slots.generated.ts` unchanged on re-run). Thumbnails: `npm run thumbnails`.
|
||||
- Template fidelity: `scripts/templates/compare.mjs` renders Canva and Voiced side by side (default output `/tmp/invoice-c6b`).
|
||||
- Real-webview self-test: build with `npm run tauri -- build --debug --no-bundle --features custom-protocol`, then run `bash /tmp/run-selftest.sh`. The report goes to `/tmp/voiced-e2e/report.json`. If `/tmp/run-selftest.sh` is gone, recreate it from the self-test notes in the plan (it launches the built binary with the selftest config and waits for the report).
|
||||
- Void package: `packaging/void/build-xbps.sh`. Windows NSIS: cross-compile with cargo-xwin (see the earlier commit "Document the Linux -> Windows NSIS cross-compile setup").
|
||||
|
||||
## Working conventions
|
||||
|
||||
- Chat uses caveman mode. Commits, docs and this file are normal English.
|
||||
- Explore with the `code-explorer` agent, implement with `code-runner`. Use `podman`, not docker. Use `npx ctx7@latest` for library docs.
|
||||
- Commit locally after the gates pass, then push the feature branch. Do not relaunch an agent the user stopped without asking.
|
||||
Reference in New Issue
Block a user