8.7 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Commands
npm run start:staging # dev server (loads .env.staging via env-cmd)
npm run build:staging # production build — USE THIS, see "Environment" below
npm test # CRA/Jest, watch mode
CI=true npx react-scripts test --watchAll=false # single run
CI=true npx react-scripts test --watchAll=false -t "step ordering" # one test by name
CI=true npx react-scripts test --watchAll=false --testPathPattern=api # one file
Lint is not wired to a script. ESLint is deliberately disabled in the webpack pipeline
(config-overrides.js filters out ESLintWebpackPlugin; .env sets DISABLE_ESLINT_PLUGIN),
so lint errors never fail a build — run ESLint explicitly if you need it.
Environment — read this before building
REACT_APP_URL (v1) and REACT_APP_URL2 (v2) are the Jupiter API bases. They live in
.env, .env.development, and .env.staging. All three files are gitignored, so a
fresh clone or a CI/build host has none of them.
npm run build does not load the env-cmd files — only build:dev / build:staging do.
CRA auto-loads .env alone. A build missing REACT_APP_URL does not fail; it inlines
undefined, every request becomes the relative path "undefined/orders/...", and those
silently resolve against whatever origin the console is served from. This shipped to
production once and looked like "the API base URL changed to the front-end's own host".
config-overrides.js now hard-fails a production build when REACT_APP_URL/REACT_APP_URL2
are missing. Add any new required var to REQUIRED_PROD_ENV there.
REACT_APP_URL3 is referenced but defined nowhere (Dispatch.js, the
getdeliverylogs fetch). Actual-GPS trails and all Compare deltas silently render nothing
as a result. Whether that endpoint is v1 or v2 is unconfirmed.
Architecture
CRA + react-app-rewired on the Mantis MUI template. The template's own pages/theme are
mostly inert scaffolding — all real work lives in src/pages/nearle/. Data fetching is
TanStack Query v5; maps are Leaflet + OSRM. Redux is vestigial — store/reducers/index.js
combines only menu and snackbar (the auth.js reducer file is not registered), so no
domain data flows through it. Server state belongs in TanStack Query.
Auth is localStorage, and it is not enforced
login.js writes tenantid, applocationid, locationid, userid, authname etc. to
localStorage; everything else reads them from there. AuthGuard/GuestGuard exist but are
commented out in both route files, so every route is publicly reachable. App.js does a
soft redirect to /login when authname is absent and clears localStorage after a 1-hour
session window.
The API layer has a module-load trap
pages/nearle/api/api.js reads const tenid = localStorage.getItem('tenantid') at module
scope. It is captured once on first import, so a login that happens afterwards leaves every
tenid-using query pinned to the stale value until a full reload. Prefer reading
localStorage inside the query function for anything new.
Several endpoints bypass the env vars and hardcode absolute hosts — the solver calls
(routes.workolik.com, routemate.workolik.com), finalCreatedeliveries, and
fetchRidersLogs. finalCreatedeliveries hardcodes the live Jupiter host, so a staging
build still commits deliveries to production.
Dispatch.js has two personalities
pages/nearle/dispatch/Dispatch.js (~6k lines, plus a ~10k-line CSS file) is one component
serving two modes, switched by whether a data prop is passed:
standalone /nearle/dispatch |
embedded (data passed) |
|
|---|---|---|
| source | live API polling | a solver response |
shouldFetchLive |
true |
false |
default viewMode |
riders |
zones |
| header / batch / date bar | shown | hidden |
Both paths converge on the same shape: { zones: [{ zone_name, riders: [{ rider_id, rider_name, orders[] }] }], zone_summary[], details[] }. The live path builds it in the
liveData memo by re-bucketing flat delivery rows by deliverysuburb; the embedded path
receives it already shaped. Anything that produces data for Dispatch must match that
shape — that is what normaliseAssignResponse in api.js exists to guarantee.
View modes: By Location / By Zone / By Rider / Active / Rider Info. "Active" is the live-ops
view — only riders whose latest GPS log is active/pending, collapsed to their single
in-progress delivery, polling deliveries every 15s.
Orders bucket into three fixed batches (Morning <08:00, Afternoon 09:00–12:30, Evening
16:00–19:00) on assigntime. Gaps between them are intentional. Layout persists under
dispatch.slots.v8; bump the key and add the old one to LEGACY_SLOTS_STORAGE_KEYS when the
shape changes. The operator-editable slot editor and the status-wise time-field dropdown are
both commented out in the JSX, not deleted.
Shared constants and pure helpers live in dispatchShared.js specifically to avoid a circular
import between Dispatch.js and its children (CompareDataPanel, ActiveSection).
Assign flow: two clicks, two endpoints
/nearle/orders "Assign Orders (N)"
→ POST https://routes.workolik.com/api/v1/optimization/nagercoil/riderassign
(fixed-rider Nagercoil solver; body {deliveries:[...]}, or {} to let the server
pull created orders itself. Empty result → info snackbar, no navigation.)
→ navigate('/nearle/dispatch/preview', { state: { dispatchPreviewData, ... } })
/nearle/dispatch/preview "Assign Orders" ← this is the commit
→ POST https://jupiter.nearle.app/live/api/v1/deliveries/createdeliveries
→ navigate('/nearle/deliveries')
The first click writes nothing — it only produces a preview. Preview.js deliberately wipes
history.state.usr after consuming the response so a reload cannot resurrect a stale plan
(it bounces to /nearle/orders instead).
Two de-duplication rules matter here and were both written to fix real duplicate-delivery
bugs: extractRiders de-dupes by orderid across zones (one rider legitimately appears once
per delivery suburb), and applyReconcileResponse wipes a rider's orders from every zone
before re-placing them.
normaliseAssignResponse assigns step per rider across the whole response, before zone
bucketing. Numbering within each zone instead would restart the sequence at every suburb and
misrepresent the solver's ordering on map pins and route lines.
Dead code that looks live
Several features are fully implemented but unreachable. Do not assume a feature works because the code exists:
- Compare mode (
CompareDataPanel, compare map, delta engine, Kalman/OSRM track snapping) —compareOpenonly becomestrueviapendingCompareRef.current, which nothing ever sets. There is no Compare button in the JSX. - Analysis view (
#dispatch-analysis, batch efficiency) —setTopViewis never called, sotopViewis permanently'live'. Its tenant is also hardcoded (ANALYSIS_TENANT_ID = 916). - Preview.js reconcile / re-optimise / CSV export / tuning —
handleReconcile,handleCreateDelivery,tabValue,csvExportData,tuningTypesand their imports each appear exactly once (their own declaration). The render is now just a header plus an embedded<Dispatch>.reconcileStepsandcreateOptimisationDeliveriesremain wired inapi.jsbut nothing invokes them. /nearle/orders/preview(OrdersPreview.js, ~770 lines) — routed, but nothing navigates to it. Superseded by the dispatch Preview.- Rider push notification —
Preview.jsonly callsnotifyRiderwhenstateData.rider?.userfcmtokenexists, andorders.jsnever passes ariderkey. The assigned rider is never notified.
Tests
Two suites, 67 tests:
src/pages/nearle/api/api.test.js—normaliseAssignResponseacross every response shape,createNagercoilDeliveriesrequest wiring, and the!data?.details?.lengthcontract thatorders.jsbranches on.axiosis mocked with a module factory (CRA automock breaks onresponse.data).src/utils/route-guard/authState.test.js— session validity, which gates every protected route. Covers the 1-hour boundary, a missing start time (accepts, rather than logging the user out) and storage throwing (fails closed).
Component tests do not exist. The large page components have heavy provider requirements
(react-query, notistack, router, theme), so prefer testing extracted pure logic in api.js /
dispatchShared.js over mounting them.
Conventions
Prettier: single quotes, no trailing comma, 140 print width, 2-space indent. Files use CRLF —
multi-line string matching in scripts needs \r?\n.