130 lines
6.9 KiB
Markdown
130 lines
6.9 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Commands
|
|
|
|
```bash
|
|
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.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 *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.
|