zurich_kotak/CLAUDE.md
2026-09-02 15:33:26 +05:30

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 in localStorage, 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.jsxZinoProvider / useZino(): one client instance plus session state. Restores an identity, never a permission set.
  • src/layout/Shell.jsx — header + the pipeline sidebar (the sidebar is STAGES).
  • src/screens/Pipeline.jsx — a queue: one record view filtered server-side on current_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 from ENTRY; skips the chooser when only one is surfaced.
  • src/components/ActivityForm.jsx — renders the live activity schema and submits it back. src/components/FileField.jsx handles file/ocr fields; src/components/Timeline.jsx renders 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_id on POST /usr/login must be a string; a number returns cannot unmarshal number into Go struct field LoginRequest.org_id.
  • POST /view/recordview names the view rv_template_uid (the GET spelling is rv_id), and paging/sort/filters live inside search_query. Top-level gives 400 Missing param: rv_template_uid (body).
  • Row key varies by source type: workflow views return data, rdbms views have returned records — hence the data ?? records ?? rows fallbacks.
  • Audit must use the app-scoped path /app/536/view/audit. Bare /view/audit is not an API route; it falls through to the SPA and returns HTML with a 200.
  • POST /upload requires workflow_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 and ocr_config — it ignores anything the client claims about them.
  • /ocr-extract takes a reference to an already-uploaded file, not bytes, and answers { extracted, raw }. Files therefore upload on pick, not on submit.
  • On submit, ActivityForm sends only fields the activity defines, drops empties (an unknown field is fatal to schema validation) and skips id_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.