A construction site material and waste tracker with multi-site support, entry history, waste efficiency metrics, and live 3D and trend visualisations — built as a full-stack portfolio demo targeting Autodesk's frontend engineering standards.
apps/
web/ React + Vite frontend
api/ Hono Node.js API
packages/
domain/ Shared TypeScript interfaces and Zod schemas
components/ Design system components
tokens/ Style Dictionary token pipeline
pnpm install
# API (port 3000)
pnpm nx run api:dev
# Web (port 5173)
pnpm nx run web:devBoth servers must be running for the app to function. The API seeds three sites on first boot.
# Unit tests (all projects)
pnpm nx run-many -t test
# Component story tests (real Chromium)
pnpm nx run @sitetrack/components:test
# E2E (requires both servers running)
pnpm nx run web:e2esafeFetch + neverthrow instead of RTK Query
Server state is fetched with ofetch wrapped in ResultAsync. Each callsite pattern-matches on the Result with ts-pattern — abort, HTTP error, and network failure are all handled explicitly. This trades RTK Query's cache invalidation for explicit control over every error path, which is the right trade-off for a domain where incorrect data display has real-world consequences.
URL-driven filter state
Entry history filters (type, category) and the dashboard trend interval (day/week) live in URL search params via React Router. Filters survive refresh, are shareable as links, and work correctly with the browser back button — behaviour that useState cannot provide.
Three.js update pattern
The 3D bar chart mutates bar.scale.y and bar.position.y on data change rather than rebuilding the scene. Meshes are created once and reused. The trend line chart is a single render (no animation loop) — a rotating line chart is noise, not insight.
Dependency injection for API routes
Route factories (createEntriesRouter(db)) accept the database instance rather than importing it at module level. Tests pass an in-memory SQLite instance; production passes the WAL-mode file database. No mocking required.
data-severity attribute pattern
WasteRatio sets data-severity="low|medium|high" on the element. CSS attribute selectors handle colour without className toggling or CSS-in-JS. Easy to override, zero JS logic in the style layer.
Used AI tooling throughout: generating initial test skeletons, drafting the GitHub Actions config, and sanity-checking design decisions (the three.js update-not-rebuild approach, the neverthrow Result pattern, DI for Hono routes). In each case the output was reviewed and edited — the generated Playwright selectors used CSS classes that broke on restyle, so they were replaced with role-based queries; the CI config needed fetch-depth: 0 for nx affected to work correctly. AI is fast at scaffold and slow at project-specific context; knowing which is which is the skill.
