Files
Devon Bontrager e5ed1d3c9f Add lightweight Tauri shell, vendor offline libs, fix bugs and race conditions
- Add tauri/ subfolder: a Tauri-based desktop shell that loads the same
  index.html as the Electron app, producing a ~2MB installer (vs ~71MB for
  Electron) by using the OS WebView2 instead of bundled Chromium. Includes
  sync-frontend.js to keep tauri/dist/ in sync with the canonical index.html
  and vendor/ at build time (no UI duplication/drift).
- Vendor lucide, jsPDF, and jspdf-autotable locally under vendor/ instead of
  loading from CDNs, so the app works fully offline in both shells.
- Fix server.js path traversal vulnerability (sanitize/contain request paths).
- Fix exportPDF() using stale/edited piece data that could produce
  Infinity/NaN in generated PDFs; add a recalculate guard.
- Fix race conditions: overlapping copy/save-defaults button revert timers,
  and FileReader callbacks for logo/CSV import that could apply out of order.
- Minor cleanups: explicit array index parsing, hasResult() helper, consistent
  event binding for NMFC search results, cached DOM lookups.
- Document the dual-shell architecture and vendoring conventions in CLAUDE.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 11:19:48 -04:00

6.3 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Two desktop shells — same frontend

This repo ships two interchangeable desktop shells that both load the exact same index.html:

  1. Electron (original, root of repo) — heavier (~70MB installer) but mature/familiar
  2. Tauri (tauri/ subfolder) — much lighter (~2MB installer, ~8MB exe) using the OS's built-in WebView2 instead of bundled Chromium

index.html (plus vendor/) is the single source of truth for the UI — never duplicate or fork it. The Tauri shell's tauri/dist/ is a generated copy (gitignored, rebuilt by tauri/sync-frontend.js before each dev/buildrun) — edit index.html at the repo root, never inside tauri/dist/.

Electron commands (run from repo root)

  • npm start — run the app as an Electron desktop window (loads index.html via main.js)
  • npm run serve — run a plain Node HTTP server (server.js) at http://localhost:3010, serving the same index.html for browser-based testing
  • npm run build — package the Windows installer via electron-builder (NSIS, output goes to dist/)

Tauri commands (run from tauri/)

  • npm run dev — sync the frontend into tauri/dist/ and launch the Tauri dev shell
  • npm run build — sync the frontend and produce MSI + NSIS installers (output in tauri/src-tauri/target/release/bundle/)
  • npm run serve — same static server as the Electron side, for browser-based testing
  • Requires the Rust toolchain (rustup/cargo) plus MSVC Build Tools and WebView2 (Tauri prerequisites) — see https://tauri.app/start/prerequisites/

There is no test suite, linter, or build step for the app code itself — index.html is loaded directly (as a file in Electron/Tauri, or statically served). To verify a change, just reload the app/page in whichever shell you're using.

Architecture

This is a single-page Electron desktop app (also runnable as a static page) for calculating NMFC freight class from shipment dimensions and weight. Almost all logic lives in one file:

  • index.html — contains everything: markup, CSS (<style> block), and all application JavaScript (<script> block starting around line 370). There are no separate JS/CSS files or build/bundling steps; what you see in index.html is what runs.
  • main.js — minimal Electron bootstrap (creates the BrowserWindow, loads index.html, with nodeIntegration: true / contextIsolation: false)
  • server.js — minimal static file server used only for npm run serve (browser-based dev/testing instead of Electron)
  • vendor/ — local copies of third-party JS libraries (lucide.js, jspdf.umd.min.js, jspdf.plugin.autotable.min.js), referenced by index.html via relative <script src="vendor/..."> tags so the app runs fully offline with no CDN dependency. Not gitignored — these files are committed and get bundled into the installer by electron-builder automatically. Do not switch these back to CDN URLs — if a library needs upgrading, download the new version into vendor/ and update the filename/reference together.

Structure inside index.html's script

The script is organized into clearly marked sections (look for the // ─── Section ─── banner comments) in this order:

  1. DatadensityRanges (density → freight class lookup table with descriptions) and nmfcData (searchable list of NMFC codes/descriptions/classes)
  2. StateappState, persisted to localStorage under the key freightCalcState via saveState(). Defaults are merged with Object.assign so new fields can be added without breaking existing saved state. Shape: { theme, branding: {name, color, logo}, history[], units: {dim, wt}, defaults: {L, W, H, Weight} }. The working pieces array (current shipment line items being entered) is ephemeral and NOT persisted.
  3. Initinit(), wires up the page on load, applies branding, populates settings/defaults forms, renders the density reference table
  4. Theme — light/dark toggle
  5. Branding — company name/color/logo customization, applied to the page header and PDF export
  6. Defaults tab — user-configurable default piece dimensions/weight pre-filled on new entries
  7. Modal — settings modal open/close/revert logic (pendingBranding holds unsaved edits so Cancel can discard them)
  8. Tabs — settings modal tab switching (Branding / Defaults / History)
  9. History — calculation history stored in appState.history (capped at 50 entries), CSV export/import, restore-from-history (re-applies the exact units the entry was saved in)
  10. Unit toggles — switching between in/cm and lbs/kg; conversions are applied at calculation time via dimFactor/wtFactor, not by mutating stored values
  11. Pieces — managing the dynamic list of shipment pieces (renderPieces, addPiece)
  12. Validation & Clear — input validation/error display, form reset
  13. Density gauge — visual gauge showing where the calculated density falls
  14. CalculatecalculateFreightClass(): the core calculation. Converts all dimensions/weights to inches/lbs, computes total cubic feet and total weight, derives density, looks up the class from densityRanges (first range where density >= min wins), saves a history entry, and updates the UI/gauge
  15. Copy to clipboard — copies the result summary as text
  16. PDF ExportexportPDF() builds a branded PDF report using jsPDF + jspdf-autotable (loaded from local vendor/ copies via <script> tags in the <head> — see vendor/ note above), including logo, company branding, pieces table, and footer
  17. NMFC Search — live search over nmfcData by code or description; selecting a result highlights the matching row in the on-page density reference table

Key conventions

  • All persisted user data lives in one appState object in localStorage; always go through saveState() after mutating it.
  • Unit conversions (incm, lbskg) happen only at calculation/display time — stored values keep their original units alongside a units field so they can be restored exactly.
  • External libraries (lucide icons, jsPDF, jspdf-autotable) are vendored locally in vendor/ and loaded via relative <script> tags in the <head>not from CDNs — so the app works completely offline.