6.9 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Commands
npm install
npm run dev # http://localhost:5175
npm run build # → dist/
npm run lint # eslint (flat config, ignores dist/)
npm run preview
There are no tests and no test runner in this project.
npm run lint currently reports 12 pre-existing errors — 8 react-hooks/refs, 3
react-hooks/set-state-in-effect, 1 react-refresh/only-export-components (the provider
file exports both a component and a hook). A clean run is not the baseline; compare against
this count rather than against zero.
Local dev reads VITE_ZINO_API_URL from .env (https://dev.getzino.in). A deployed
build does not — see "Runtime config" below.
What this is
A React + Vite operator console (no state library, no UI framework — plain CSS files
next to their components) for the Zurich Kotak "Lead to Policy" demo on the Zino
platform. Leads arrive through three channels — direct, bancassurance, agency — and run
one workflow state machine to policy issuance. AI employees perform almost every step;
a human appears at exactly one queue, Referred to Underwriting.
The frontend owns no business logic. The workflow (org 84, app 536, workflow
zk_wf_lead) is the source of truth; this repo is a thin renderer over its API.
Workflow config is seeded outside this repo, from sm2/custom-apps/zurich-kotak/
(SQL migrations 02_workflow.sql, 04_views.sql, …). Design doc:
sm2/app-designs/zurich-kotak/design.html.
Architecture
src/api/client.js— every gateway call lives here (ZinoClient). JWT + user inlocalStorage, 401 clears the session via an auth-error handler.src/api/config.js— the environment identifiers and the hand-maintained mirrors of workflow tables (STAGES,ACTIONS,ENTRY).src/api/provider.jsx—ZinoProvider/useZino(): one client instance plus session state. Restores an identity, never a permission set.src/layout/Shell.jsx— header + the pipeline sidebar (the sidebar isSTAGES).src/screens/Pipeline.jsx— a queue: one record view filtered server-side oncurrent_state_name.src/screens/Lead.jsx— one lead: detail view, allowed activities, timeline, then field groups (GROUPS, in the order the lead is worked).src/screens/AddLead.jsx— the INIT doors fromENTRY; skips the chooser when only one is surfaced.src/components/ActivityForm.jsx— renders the live activity schema and submits it back.src/components/FileField.jsxhandlesfile/ocrfields;src/components/Timeline.jsxrenders the audit trail as a story.
Routing: /stage/:stageUid, /lead/:instanceId, /add; unauthenticated renders
Login for every path.
The invariants — break these and it fails in ways tests would not catch
Forms are never defined in this repo. POST /view/form-screens returns labels,
types, select options and which fields are mandatory; ActivityForm renders whatever
comes back. Adding a field in Studio and redeploying surfaces it here with no frontend
change. Do not hardcode a form — it would silently drift from the workflow.
Permissions are never enforced here. Nothing hides a button to stop someone. Every
decision is the workflow's, server-side, on each submission; the console reports the
refusal. /activity refuses with 403, /start with 400, both carrying
permission denied. Showing an action the signed-in role cannot perform is deliberate —
the refusal is the demo.
Runtime config, not build-time. VITE_ZINO_API_URL is read at runtime off
window.__RUNTIME_CONFIG__, from a config.js the server writes next to the artifact at
placement. One artifact is promoted between environments unchanged, so a compiled-in URL
would point every environment at whichever backend built it. requireConfigValue throws
rather than falling back — a production build with no config.js must fail loudly. The
dev-only .env fallback is gated on import.meta.env.DEV.
One build serves any mount path. vite.config.js sets base: './'; the router
basename comes from basePath(), which reads the <base href> the server writes into the
<!--BASE_HREF--> placeholder in index.html. Do not reintroduce a build-time base, and
do not remove that placeholder or the config.js script tag. import.meta.env.BASE_URL
is unusable here (it resolves to ./).
Bundled assets must live in src/assets/, not public/. Vite rewrites bundled asset
URLs to be relative; a public/ file referenced as /fonts/… stays absolute and 404s
under the /zurich-kotak/ mount. The favicon is the one exception — it stays in
public/brand/ and is referenced relatively.
STAGES and ACTIONS are hand-mirrored from workflow.tbl_wf_states and
workflow.tbl_wf_state_allowed_activities, because the API returns neither the order nor
the labels the sidebar needs. An activity added to the workflow but not added to ACTIONS
is simply invisible — the platform allows it, the console never offers it, nothing errors.
Keep both in step with the seed SQL.
API shapes that have already bitten
org_idonPOST /usr/loginmust be a string; a number returnscannot unmarshal number into Go struct field LoginRequest.org_id.POST /view/recordviewnames the viewrv_template_uid(the GET spelling isrv_id), and paging/sort/filters live insidesearch_query. Top-level gives400 Missing param: rv_template_uid (body).- Row key varies by source type: workflow views return
data, rdbms views have returnedrecords— hence thedata ?? records ?? rowsfallbacks. - Audit must use the app-scoped path
/app/536/view/audit. Bare/view/auditis not an API route; it falls through to the SPA and returns HTML with a 200. POST /uploadrequiresworkflow_uuid(taken from config, not from the caller's ctx). The field context (activity_id,field_id,instance_id) is how the backend resolves allowed types, size limit andocr_config— it ignores anything the client claims about them./ocr-extracttakes a reference to an already-uploaded file, not bytes, and answers{ extracted, raw }. Files therefore upload on pick, not on submit.- On submit,
ActivityFormsends only fields the activity defines, drops empties (an unknown field is fatal to schema validation) and skipsid_gen— that reference is issued server-side. - OCR results fill only fields still blank; never overwrite what a person typed with what a model read.
Deployment
Registered with frontgen by sm2/custom-apps/zurich-kotak/05_frontgen_project.sql (slug
zurich-kotak, repo_name zurich_kotak, org 84). A push to main builds and serves at
https://preview-dev.getzino.in/zurich-kotak/. The webhook fires only on a new push.
Demo logins (all password ZurichKotak@2026, org 84) are listed in README.md; sign in
as sanjay.uw@zurichkotak.example to land in the one human queue.