Files
dailygrubs_console/CLAUDE.md

8.7 KiB
Raw Permalink Blame History

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) — compareOpen only becomes true via pendingCompareRef.current, which nothing ever sets. There is no Compare button in the JSX.
  • Analysis view (#dispatch-analysis, batch efficiency) — setTopView is never called, so topView is permanently 'live'. Its tenant is also hardcoded (ANALYSIS_TENANT_ID = 916).
  • Preview.js reconcile / re-optimise / CSV export / tuning — handleReconcile, handleCreateDelivery, tabValue, csvExportData, tuningTypes and their imports each appear exactly once (their own declaration). The render is now just a header plus an embedded <Dispatch>. reconcileSteps and createOptimisationDeliveries remain wired in api.js but 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.js only calls notifyRider when stateData.rider?.userfcmtoken exists, and orders.js never passes a rider key. The assigned rider is never notified.

Tests

Two suites, 67 tests:

  • src/pages/nearle/api/api.test.js — normaliseAssignResponse across every response shape, createNagercoilDeliveries request wiring, and the !data?.details?.length contract that orders.js branches on. axios is mocked with a module factory (CRA automock breaks on response.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.