- 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>
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:
- Electron (original, root of repo) — heavier (~70MB installer) but mature/familiar
- 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 (loadsindex.htmlviamain.js)npm run serve— run a plain Node HTTP server (server.js) at http://localhost:3010, serving the sameindex.htmlfor browser-based testingnpm run build— package the Windows installer viaelectron-builder(NSIS, output goes todist/)
Tauri commands (run from tauri/)
npm run dev— sync the frontend intotauri/dist/and launch the Tauri dev shellnpm run build— sync the frontend and produce MSI + NSIS installers (output intauri/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 inindex.htmlis what runs.main.js— minimal Electron bootstrap (creates theBrowserWindow, loadsindex.html, withnodeIntegration: true/contextIsolation: false)server.js— minimal static file server used only fornpm 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 byindex.htmlvia 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 byelectron-builderautomatically. Do not switch these back to CDN URLs — if a library needs upgrading, download the new version intovendor/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:
- Data —
densityRanges(density → freight class lookup table with descriptions) andnmfcData(searchable list of NMFC codes/descriptions/classes) - State —
appState, persisted tolocalStorageunder the keyfreightCalcStateviasaveState(). Defaults are merged withObject.assignso 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 workingpiecesarray (current shipment line items being entered) is ephemeral and NOT persisted. - Init —
init(), wires up the page on load, applies branding, populates settings/defaults forms, renders the density reference table - Theme — light/dark toggle
- Branding — company name/color/logo customization, applied to the page header and PDF export
- Defaults tab — user-configurable default piece dimensions/weight pre-filled on new entries
- Modal — settings modal open/close/revert logic (
pendingBrandingholds unsaved edits so Cancel can discard them) - Tabs — settings modal tab switching (Branding / Defaults / History)
- 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) - Unit toggles — switching between in/cm and lbs/kg; conversions are applied at calculation time via
dimFactor/wtFactor, not by mutating stored values - Pieces — managing the dynamic list of shipment pieces (
renderPieces,addPiece) - Validation & Clear — input validation/error display, form reset
- Density gauge — visual gauge showing where the calculated density falls
- Calculate —
calculateFreightClass(): the core calculation. Converts all dimensions/weights to inches/lbs, computes total cubic feet and total weight, derives density, looks up the class fromdensityRanges(first range wheredensity >= minwins), saves a history entry, and updates the UI/gauge - Copy to clipboard — copies the result summary as text
- PDF Export —
exportPDF()builds a branded PDF report usingjsPDF+jspdf-autotable(loaded from localvendor/copies via<script>tags in the<head>— seevendor/note above), including logo, company branding, pieces table, and footer - NMFC Search — live search over
nmfcDataby 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
appStateobject inlocalStorage; always go throughsaveState()after mutating it. - Unit conversions (
in↔cm,lbs↔kg) happen only at calculation/display time — stored values keep their original units alongside aunitsfield so they can be restored exactly. - External libraries (
lucideicons,jsPDF,jspdf-autotable) are vendored locally invendor/and loaded via relative<script>tags in the<head>— not from CDNs — so the app works completely offline.