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>
This commit is contained in:
Devon Bontrager
2026-06-08 11:19:48 -04:00
parent 3c3ef02f20
commit e5ed1d3c9f
24 changed files with 25160 additions and 12 deletions
+10 -1
View File
@@ -14,7 +14,16 @@ const mimeTypes = {
};
const server = http.createServer((req, res) => {
const filePath = path.join(__dirname, req.url === '/' ? 'index.html' : req.url);
// Strip query string / hash and decode URI components before resolving,
// then confirm the resolved path stays inside __dirname (no ../ escapes).
const urlPath = decodeURIComponent(req.url.split('?')[0].split('#')[0]);
const reqPath = urlPath === '/' ? 'index.html' : urlPath.replace(/^\/+/, '');
const filePath = path.join(__dirname, reqPath);
if (!filePath.startsWith(__dirname + path.sep) && filePath !== __dirname) {
res.writeHead(403, { 'Content-Type': 'text/plain' });
res.end('403 Forbidden');
return;
}
const ext = path.extname(filePath);
const contentType = mimeTypes[ext] || 'application/octet-stream';