# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. Voiced is an offline-first GST invoicing desktop app (Tauri v2, React 19, IBM Carbon, `@react-pdf/renderer` 4.9.0, SQLite via rusqlite) built by Bongbetic for vendor Arun P. Targets: Void Linux (xbps, WebKitGTK 2.50) and Windows (NSIS, WebView2). `handoff.md` holds the current project state and next steps; the approved plan is `/home/soubarna/.claude/plans/take-a-look-at-logical-sunbeam.md`. The README's "Project layout" section is out of date; trust this file and the code. ## Commands ```bash npm run build # tsc --noEmit && vite build (the type-check gate) npm test # vitest run, ~7 min; run on a quiet machine (load starves the sweep tests) npx vitest run src/pdf/templates/monolith # one folder npx vitest run path/to/file.test.tsx -t "name" # one test npm run fonts:verify # bundled fonts match public/fonts/manifest.json (regenerate with fonts:manifest) npm run logo:measure # regenerate src/pdf/templates/slots.generated.ts from the Canva PDFs; must be idempotent npm run thumbnails # regenerate public/templates/*.png (unchanged templates must stay byte-identical) npm run templates:compare # Canva vs Voiced side-by-side PNGs (scripts/templates/compare.mjs, output under /tmp) cargo test --manifest-path src-tauri/Cargo.toml # Rust unit tests (gst, db migrations, logo, commands) npm run app:dev # tauri dev (served from localhost:1420; does NOT exercise the tauri:// protocol) npm run app:build # production build, custom-protocol npm run app:build:windows # NSIS via cargo-xwin cross-compile (see README) ./packaging/void/build-xbps.sh # Void .xbps into build/void/repo ``` Real-webview self-test (the only check that runs the production build): build with `npm run tauri -- build --debug --no-bundle --features custom-protocol`, then `bash /tmp/run-selftest.sh`. The app is inert unless `VOICED_SELFTEST_OUT` is set; the script runs the debug binary with temp XDG dirs and the report lands in `/tmp/voiced-e2e/report.json` (`ok: true` expected). `/tmp` is not durable: if the script is gone, recreate it with that env var, `XDG_DATA_HOME`/`XDG_CONFIG_HOME` temp dirs and a `timeout 180`. ## Architecture ### Rust backend (`src-tauri/src`) - `db.rs` owns the schema as numbered migrations (rusqlite_migration, M1–M5); it copies the DB aside before migrating. Add schema changes as new migrations, never edit old ones. Issued invoices carry snapshots (vendor, client, GST, layout fingerprint) and are immutable. - `gst.rs` does all money and tax maths in integer paise. Tax heads (CGST+SGST vs IGST vs UTGST) derive from (supplier state, place of supply); a mismatch with the chosen head is rejected. An unregistered vendor gets a plain "Invoice" with no tax. GSTIN is checked with the mod-36 checksum. - `logo.rs` trims, quantises and measures logos (aspect, density, wordmark/mark/tall, knockout variant for dark surfaces). - `commands/*.rs` are the Tauri commands; `raw.rs` carries binary payloads over raw-body IPC (ASCII headers, ArrayBuffer response). `archive.rs` stores each issued PDF content-addressed under `app_local_data_dir/archive/.pdf`. - `selftest.rs` is the unattended end-to-end hook described above. ### Frontend (`src`) `views/` and `components/` are the Carbon UI; `lib/api.ts` is the typed wrapper over the Tauri commands; `lib/exportFlow.ts` and `hooks/useInvoiceExport` drive export (searchable or flattened) and archiving. ### PDF engine (`src/pdf`) — the part that needs several files to understand Pipeline: `model/build` (`buildRenderModel`) → `engine/geometry` (`computeFrame`: A4/Letter plus margin presets) → a template `Layout` → `InvoicePdf`/`InvoicePage` → `render/core` (`renderCore`: bytes, normalized layout tree, `auditLayout` issues, layout fingerprint). Rendering runs in a module Web Worker through a latest-wins `render/client` (FIFO assemble lane, watchdog, main-thread fallback). Preview uses pdf.js; flatten rasterises pages at 300 DPI and re-assembles them as an image-only PDF. - **Templates** live in `templates//` and are registered in `templates/registry.ts` (picker order) and `catalog.ts` (labels, thumbnails). Each has `tokens.ts`, `plan.ts`, `Layout.tsx` and a test. They implement `templates/contract.ts`. 14 exist: Classic plus the 13 Canva templates, specified in `docs/templates/*.md` (measured from the Canva PDFs in `templates/`, which is untracked on purpose: never stage `templates/` or `.commandcode/`). - **Logo placement** is slot-based (`engine/logo.ts`, `templates/slots.*`): the user's logo fills the Canva placeholder box, contained, with a shrink-only weight factor. Template placeholder logos are never reproduced. Some templates use opt-in slots (`slots.optin.ts`), default off. - **Tests that guard layout**: template harness and sweep tests (`harness*.ts`, `harness.sweep*.test.tsx`: 1–45 rows, long text, A4/Letter, no overlap), layout lint, fingerprint goldens (`goldens.test.ts`) and `quirks.test.tsx`. The Node goldens must equal the fingerprints the real webview reports. - **Glyph priming**: fontkit caches glyphs process-wide and pdfkit embeds composite components with no code points, which corrupted later renders (":" before "." gave "₹1,200;00"). `primeFontGlyphs()` in `fonts/register.ts` fixes it and runs in `renderCore` and browser init. Do not widen it to every cmap glyph (breaks Inter's contextual hyphen); it uses fontkit private APIs, so re-check on fontkit upgrades. - **Fonts**: TTF only, bundled in `public/fonts//` with a generated manifest; every family needs normal and italic sources. WOFF2 embeds blank glyphs. Roles/type tokens are in `fonts/roles.ts`. ### react-pdf 4.9.0 rules (all pinned by `quirks.test.tsx`; break them and layouts hang or overlap) - Text lines are cached at first measure, so give flex children pinned widths (`width=minWidth=maxWidth`) with a `{flexGrow:1, flexBasis:0}` fill child. - No `lineHeight` on Page/View/render-prop ancestors; set an absolute `'Npt'` lineHeight on each static `Text`. Hyphenation is off. - `fixed`, `wrap`, `minPresenceAhead` are key-presence checks: spread them only when meaningful (`blocks/pdfProps`). - A non-fixed page-level absolute `Svg`/`Image` hangs pagination. Render props must return `Text`/`View` only. - `maxLines` does not clip a single over-long word (use `FitText`). Trailing `letterSpacing` needs `Tracked` compensation. - A fixed table header as the first child of the table `View` repeats on each page; use `TableGuard`/`BottomSpacer` with `minPresenceAhead`. ## Conventions - Commit only after the gates pass (`npm run build`, full `npm test`, `fonts:verify`, and the self-test for engine/template changes). Work is on stacked phase branches (`phase-0-hotfix` → `phase-a-foundation` → `phase-b-engine` → `phase-c-templates`); create the next phase branch from the previous one. - Windows/WebView2 has never been run on a real machine; the app window UI has only been exercised headless.