xavierk d1976499a2 Merge origin/main (CI quality gates) into the 1.1.0 release
# Conflicts:
#	package-lock.json
#	src/views/NewInvoice.tsx
2026-10-06 09:43:47 +05:30
2026-10-06 09:35:26 +05:30
2026-10-06 09:35:26 +05:30
2026-10-05 17:29:03 +05:30
2026-10-05 17:29:03 +05:30
2026-10-06 09:35:26 +05:30

Voiced

An offline-first GST invoicing desktop app built by Bongbetic.

Voiced turns a few details into a clean, print-ready PDF invoice. It keeps a running invoice-number series in a local database, remembers your vendor, client and bank details, and works without an account or a network connection. The only optional network feature is the ERPNext push, which you configure yourself.

Targets: Void Linux (xbps, WebKitGTK 2.50 or newer) and Windows 10/11 (NSIS installer, WebView2).

Current release: 1.1.0. Installers and the Void package are attached to the release on the project's Gitea page (see "Installing").

Features

  • First-run setup. A four-step wizard (business, branding, bank, numbering) starts blank: no vendor, bank or tax details are pre-filled, and a business name and state are required. Settings, Data, Run setup again reopens it with your current details; invoices, clients and backups are kept, and numbering carries on unless you change the series prefix or digits.
  • Invoices. Automatic numbering in a persistent series (for example INV/2026-001) with a one-click new series, drafts, issue and cancel. An issued invoice is immutable: it stores a snapshot of the vendor, client, GST figures and layout. Line items are a fixed amount or rate x quantity in a unit of measure (time, distance, area, weight, volume, a count, or a custom unit; a time typed as 4:30 counts as 4.5 hours), with item presets.
  • GST. All money and tax maths is done in integer paise. CGST+SGST, IGST or UTGST follow from the supplier's state and the place of supply, and a mismatch is rejected. An unregistered vendor gets a plain "Invoice" with no tax. GSTIN, PAN, IFSC, email and phone are validated; amounts are written in Indian words.
  • Templates. 14 templates: Voiced Standard (Classic) and 13 further designs (Linea, Monogram, Serenity, Crimson Grid, Tangerine Ledger, Slate Band, Teal Swoosh, Purple Pop, Citrus Split, Monolith, Cobalt Stripe, Marble, Highlighter). Your logo fills the template's logo slot; a few templates offer an opt-in logo position. Specs are in docs/templates/.
  • Page setup. A4 or Letter, and margin presets (template, narrow 36 pt, normal 54 pt).
  • Export modes. Searchable PDF (text layer kept) or flattened (pages rasterised at 300 DPI and rebuilt as an image-only PDF). Issued PDFs are archived by content hash, and "Original as issued" re-exports the exact archived bytes.
  • Fonts. 11 bundled font families (see "Fonts and licences"). The three commercial template faces (Now, Gotham, Open Sauce One) can be overridden with your own licensed files under Settings, Fonts.
  • Clients and presets. Saved clients with structured addresses, state and GST category; item presets.
  • Payments. Record payments against an issued invoice, including TDS deducted by the client. Outstanding balance is cash plus TDS against the invoice total.
  • History. Filterable invoice list, detail view with payments, and CSV or JSON export of the list.
  • Backup and restore. Manual and automatic daily backups of the database, assets, PDF archive and imported fonts.
  • Export for other software. voiced.invoice.v1 JSON and line-item CSV for selected invoices (see docs/voiced-invoice-v1.md).
  • ERPNext integration. Push an invoice (as draft or submitted) with its PDF attached, and record payments, to an ERPNext v15 site.
  • Light and dark (Carbon g10/g100) themes.

Tech stack

Layer Choice
Shell Tauri v2 (Rust core, WebKitGTK on Linux, WebView2 on Windows)
UI React 19 + TypeScript + IBM Carbon (@carbon/react)
Storage SQLite via rusqlite (bundled), numbered migrations
PDF @react-pdf/renderer 4.9.0 in a Web Worker, pdf.js for preview and flattening
ERPNext reqwest with rustls (ring provider); all HTTP runs in Rust

Prerequisites

  • Node.js 22 and npm
  • Rust stable, via rustup
  • Linux only: WebKitGTK 4.1, libsoup3, GTK3, librsvg, pkg-config, clang/llvm/lld

Development

npm install
npm run app:dev        # tauri dev, served from localhost:1420 (does not exercise the tauri:// protocol)
Command What it does
npm run build tsc --noEmit and vite build (the type-check gate)
npm test vitest run, about 7 minutes; run on a quiet machine because load starves the sweep tests
npm run fonts:verify Checks the bundled fonts against public/fonts/manifest.json (fonts:manifest regenerates it)
npm run logo:measure Regenerates src/pdf/templates/slots.generated.ts from the reference template PDFs (not in the repo); must be idempotent
npm run thumbnails Regenerates public/templates/*.png from the synthetic sample invoice (neutral vendor and bank, generated wordmark)
node scripts/logo/make-sample-logo.mjs Regenerates the synthetic sample logo in src/pdf/testing/fixtures/
npm run templates:compare Reference vs Voiced side-by-side PNGs under /tmp (needs the reference PDFs, not in the repo)
cargo test --manifest-path src-tauri/Cargo.toml Rust unit tests
npm run app:build Production build for the current platform (custom-protocol)

The release procedure, including the manual first-run checklist, is in docs/RELEASE.md.

Installing

Download the files for your platform from the release page of the project on git.bongbetic.com (Releases, then v1.1.0).

  • Windows 10/11: run Voiced_1.1.0_x64-setup.exe. It is a per-user install and is not code-signed, so Windows SmartScreen may warn: choose "More info", then "Run anyway".
  • Void Linux: install voiced-1.1.0_1.x86_64.xbps from a local repository (see "Installing on Void Linux").

On first launch the setup wizard asks for your business details, logo, bank account and invoice numbering. Nothing is pre-filled. Uninstalling does not delete your data (see "Where data lives"); to start from scratch use Settings, Data, Run setup again, or remove the data directory after taking a backup.

Where data lives

The Tauri identifier is com.bongbetic.voiced. Voiced uses two directories, which differ only on Windows:

What Linux Windows
app_data_dir: database voiced.db, assets/ (logos, signatures), backups/ (auto/, pre-restore-*, pre-migration copies) ~/.local/share/com.bongbetic.voiced/ %APPDATA%\com.bongbetic.voiced\
app_local_data_dir: archive/<sha256>.pdf (issued PDFs), fonts/ (fonts you import) ~/.local/share/com.bongbetic.voiced/ %LOCALAPPDATA%\com.bongbetic.voiced\

On Linux XDG_DATA_HOME moves both. On Windows the archive and imported fonts stay out of the roaming profile so a growing archive and licensed font files are not copied between machines. The window size and position are kept by the window-state plugin in the app config directory (~/.config/com.bongbetic.voiced/ on Linux).

Backup and restore

Settings, Data tab.

  • Backup now writes a zip: a consistent snapshot of voiced.db, assets/, archive/, fonts/ and a manifest.json with a sha256 for every file.
  • Automatic backup. Once per calendar day, while the app is open, Voiced writes backups/auto/voiced-auto-YYYYMMDD.zip and keeps the newest 14.
  • Restore validates the zip (manifest, hashes, database integrity check) and stages it. Nothing live changes until the next start: use the "Restart" button or close and reopen the app. On that start the current database, assets, archive and fonts are moved to backups/pre-restore-<timestamp>/ (a safety copy that is never pruned), then the restored files are moved in. If anything fails the app rolls back and starts on the old data. A backup from an older schema is migrated after restoring.
  • Backups contain secrets. The ERPNext API secret is stored in the SQLite database, so every backup (manual, automatic and pre-restore) holds it in plain form, together with your clients, invoices and bank details. Keep backup files on storage you trust, do not email or upload them casually, and rotate the ERPNext API key if a backup is lost.

Fonts and licences

Eleven families are bundled as static TTF files in public/fonts/<family>/, each with its OFL.txt: DM Sans, IBM Plex Mono, IBM Plex Sans, Inter, Jost, Lustria, Montserrat, Noto Sans Devanagari, Noto Sans Kannada, Open Sans and Poppins. All are under the SIL Open Font License 1.1. public/fonts/manifest.json lists every file with its hash and metrics, and npm run fonts:verify checks it. PDFs embed subsets, so the app never depends on system fonts.

The Void package also installs these fonts system-wide under /usr/share/fonts/voiced/<family>/ and each OFL text as /usr/share/licenses/voiced/<family-folder>-OFL.txt.

Now, Gotham and Open Sauce One are not bundled. Voiced draws Now with Jost, Gotham with Montserrat and Open Sauce One with Poppins (Marble, Citrus Split and Purple Pop use these faces). If you hold a licence for the real fonts, import the files under Settings, Fonts (TTF, OTF or WOFF, one file per weight; WOFF2, variable and font-collection files are refused). Importing is your confirmation that your licence covers this use. The files are stored on this computer only, are included in backups, and issued invoices keep the fonts they were issued with.

ERPNext integration

Settings, Integrations. Verified live against ERPNext v15.121.6 (Frappe 15.121), with and without India Compliance 15.32. v14 and v16 have not been tested.

  1. Integration user. In ERPNext create a dedicated user (type System User) with the roles Accounts User and Sales User. Open the user, Settings tab, API Access, Generate Keys, and copy the secret once. Accounts User covers invoices, payments and attachments; Sales User covers customers and addresses (Accounts User alone gets HTTP 403 on Customer). Do not use Administrator or System Manager keys. If you change roles, run bench clear-cache.
  2. Rounding. In ERPNext System Settings set Rounding Method to Commercial Rounding. ERPNext defaults to Banker's Rounding while Voiced rounds half-paise up, so some totals would differ by 1 paise. Voiced detects a mismatch after creating the draft, keeps it as a draft, never submits it, and names this fix.
  3. Connect. Enter the site address, API key and secret and run "Test connection". Use https:// for real servers; plain http:// is accepted only for localhost, 127.0.0.1, *.localhost and *.test. The secret is never shown again, and it is not sent to a different server than the one it was saved for.
  4. Company, accounts and units. Pick the company, the output tax accounts (CGST, SGST, IGST), the bank account for payments and the unit-of-measure mapping from the loaded option lists.
  5. Naming. Mirror (default) names the ERPNext document with the Voiced number, which needs a site with API v2 naming (Frappe 15.73 or newer). Series lets an ERPNext naming series name the document and puts the Voiced number in the remarks.
  6. India Compliance. Detected by the connection test. It requires a 6 or 8 digit HSN/SAC code on each row (checked at submit), limits document names to 16 characters (mirror mode checks this before posting), and Voiced always sends is_reverse_charge as 0 with a warning.

Not covered: overseas and SEZ customers, multi-currency, reverse charge booked on RCM accounts, e-invoice and e-waybill, TLS-terminating proxies beyond plain HTTPS, and Windows. The live test setup is in scripts/erpnext-e2e/README.md.

Building for Windows (NSIS, cross-compiled from Linux)

Tauri supports only the MSVC target. cargo-xwin cross-compiles it and Tauri produces an NSIS installer (a .msi needs a real Windows host).

# one-time setup
rustup target add x86_64-pc-windows-msvc
cargo install --locked cargo-xwin
# plus makensis (NSIS), LLVM/LLD and clang on the host

npm run app:build:windows            # embeds the small WebView2 bootstrapper (needs internet if WebView2 is missing)
npm run app:build:windows:offline    # embeds the full WebView2 runtime (over 100 MB larger), for offline machines

Output: src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/Voiced_<version>_x64-setup.exe, and the portable binary in the sibling release/ folder. The installer is a per-user install (installMode: currentUser). The frontend, fonts and pdf.js are compiled into the executable. Size budget: about 25 MB for the standard installer. cargo tree -i aws-lc-sys --target x86_64-pc-windows-msvc must print nothing (the TLS stack is rustls with the ring provider only, which cross-compiles cleanly).

Hosts without an nsis package (for example Void Linux):

  1. Provide makensis and put it on PATH.

  2. Point it at a full NSIS data directory (3.11 recommended, for Win/RestartManager.nsh) with NSISDIR.

  3. Pre-seed Tauri's plugin cache so it does not have to download it:

    mkdir -p ~/.cache/tauri/NSIS/Plugins/x86-unicode/additional
    # nsis_tauri_utils.dll (SHA1 75197fee3c6a814fe035788d1c34ead39349b860)
    #   from https://github.com/tauri-apps/nsis-tauri-utils/releases
    

The installer has been installed and run once in a Windows 11 virtual machine; it has not been tried on physical hardware.

Installing on Void Linux

Prebuilt binary package (no xbps-src needed)

./packaging/void/build-xbps.sh          # -> build/void/repo/voiced-<version>_1.<arch>.xbps
echo "repository=$(pwd)/build/void/repo" | sudo tee /etc/xbps.d/99-voiced-local.conf
sudo xbps-install -Sy voiced

The script runs npx tauri build --no-bundle --features custom-protocol (using your existing node_modules), stages the install tree and runs xbps-create and xbps-rindex. Launch Voiced from the application menu or run voiced; remove it with sudo xbps-remove voiced.

The package installs /usr/bin/voiced, a desktop entry, hicolor icons, the bundled fonts under /usr/share/fonts/voiced/<family>/, the OFL texts under /usr/share/licenses/voiced/, and INSTALL and REMOVE action scripts that run fc-cache -f /usr/share/fonts/voiced after install and removal. The voiced/ font subdirectory avoids file conflicts with Void's font-ibm-plex-ttf and noto-fonts-ttf. It depends on libwebkit2gtk41>=2.50, fontconfig, xdg-utils, hicolor-icon-theme and desktop-file-utils.

Building through xbps-src

mkdir -p void-packages/srcpkgs/voiced
cp packaging/void/template packaging/void/INSTALL packaging/void/REMOVE void-packages/srcpkgs/voiced/
cd void-packages && ./xbps-src pkg voiced

INSTALL and REMOVE must sit next to the template; xbps-src packs them. The source is a tagged archive of the repository (see the comments at the top of the template; checksum=SKIP until a tarball is published). xbps-src builds in a network-less chroot, so vendor the dependencies first: run cargo vendor in src-tauri/ (with a .cargo/config.toml pointing at vendor/) and populate the npm cache, and include the results in the source archive. The template runs npm ci (not --omit=optional: that removes the native rollup and esbuild binaries the Vite build needs, and @napi-rs/canvas is type-checked by tsc). No OpenSSL is needed (rustls), and the dependencies of the ERPNext and backup code (reqwest, zip) are pure Rust.

Project layout

src/                       React + Carbon frontend
  views/                   Onboarding, NewInvoice, InvoiceHistory, InvoiceDetail, Clients, AppSettings, SeriesSettings
  components/              AppShell, TemplatePicker, PageSetupControls, PdfPreview, LogoBranding,
                           FontOverrideTable, DataBackupPanel, ErpnextSettingsPanel, RecordPaymentModal, ...
  hooks/                   Export, PDF preview, user fonts, shortcuts
  lib/                     Typed Tauri wrapper (api.ts), money, GST helpers, exportFlow, historyExport,
                           invoiceExportV1, ERPNext UI helpers
  pdf/                     PDF engine
    model/                 buildRenderModel (invoice -> render model)
    engine/                geometry (A4/Letter, margins), logo slots, layout fingerprint
    templates/             <family>/ with tokens.ts, plan.ts, Layout.tsx; registry.ts, catalog.ts, contract.ts
    blocks/ decor/         Shared layout blocks and decor
    fonts/                 Font registration, roles, glyph priming
    render/                renderCore, Web Worker, latest-wins client
    flatten.ts assemble.ts Rasterise at 300 DPI and rebuild an image-only PDF
    testing/               Goldens (layout fingerprints), self-test fixtures, synthetic sample logo
src-tauri/src/             Rust backend
  db.rs                    Schema as numbered migrations (copies the DB aside before migrating)
  gst.rs                   Integer-paise money and GST maths
  logo.rs                  Logo trimming, quantising, measuring
  commands/                Tauri commands: invoice, series, clients, payments, presets, settings, assets,
                           archive, fonts, backup, files, raw (binary IPC), erpnext, logo
  integrations/erpnext/    ERPNext client, mapping, discovery, push
  selftest.rs              Unattended real-webview self-test
public/fonts/              Bundled TTF families, OFL texts, manifest.json (plus legacy IBM Plex web fonts)
public/templates/          Template thumbnails (rendered from synthetic sample data)
docs/                      Template specs, voiced.invoice.v1, release checklist
scripts/                   Font, logo, template, thumbnail and ERPNext end-to-end tooling
packaging/void/            xbps-src template, build-xbps.sh, desktop entry, INSTALL and REMOVE scripts

Tests and gates

Before a release (details in docs/RELEASE.md): npm run build, npm test, npm run fonts:verify, cargo test --manifest-path src-tauri/Cargo.toml, and the real-webview self-test on a debug or installed build:

npm run tauri -- build --debug --no-bundle --features custom-protocol
VOICED_SELFTEST_OUT=/tmp/voiced-e2e/report.json XDG_DATA_HOME=/tmp/voiced-e2e/data \
  XDG_CONFIG_HOME=/tmp/voiced-e2e/config timeout 180 src-tauri/target/debug/voiced

The app is inert unless VOICED_SELFTEST_OUT is set; ok: true is expected in the report. The same checks run from Settings, Diagnostics.

Known limitations

  • Windows and WebView2 have only been exercised in a Windows 11 virtual machine, not on physical hardware; the NSIS installer is cross-compiled and unsigned.
  • The SAC code for the vendor's services and the signature rule still need confirmation from a chartered accountant.
  • Now, Gotham and Open Sauce One are replaced by free look-alikes unless you import licensed files.
  • ERPNext: only v15 is verified; HTTP is allowed only for local hosts; see "Not covered" above.
  • Not implemented (deferred): copies and watermarks, PDF/A, custom margins, user background textures, SVG logo import, CESS, composition and SEZ invoices.

Licence and credit

Voiced is released under the Apache License 2.0. Copyright 2026 Bongbetic.

You are free to use, modify, fork and redistribute it. If you do, you must keep the LICENSE and NOTICE files and the copyright notices, and state your changes; please also credit the original: Based on Voiced by Bongbetic. Bundled fonts keep their own SIL Open Font License texts (see "Fonts and licences").

Contributions are welcome, see CONTRIBUTING.md. Contributions are licensed under the same terms.

S
Description
Offline invoicing desktop app by Bongbetic
Readme Apache-2.0
9.7 MiB
Voiced 1.2.0
Latest
2026-10-06 05:59:52 +00:00
Languages
TypeScript 67.2%
Rust 29.2%
JavaScript 2.4%
SCSS 0.6%
Shell 0.3%
Other 0.3%