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

63 lines
6.3 KiB
Markdown

# 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`/`build`run) — 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. **Data**`densityRanges` (density → freight class lookup table with descriptions) and `nmfcData` (searchable list of NMFC codes/descriptions/classes)
2. **State**`appState`, 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. **Init**`init()`, 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. **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 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 Export**`exportPDF()` 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 (`in``cm`, `lbs``kg`) 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.