Files
Voiced/handoff.md
T
xavierk f13665922c 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.
2026-10-04 13:11:40 +05:30

8.5 KiB
Raw Blame History

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.