The workflow dropped four stages on 7 September and renamed two. This table is the console's mirror of it and was still describing the old shape, which breaks quietly rather than loudly. WHAT CHANGED IN THE WORKFLOW Risk Captured, Proposal Accepted, KYC Verified and Underwriting Cleared are gone. A lead entered and left each of them inside a minute, so they were pipeline steps wearing a stage's clothes. Meanwhile the one genuine stall -- a partner agent finding three documents -- had no stage at all and happened inside "Contacted", which meant the queue that most needed watching was the only one that did not exist. zk-state-docs is that queue, and it is where the work those four stages used to represent now happens: upload, capture, advise, quote, all without the lead moving. Quote Presented absorbs the other three the same way -- acceptance, identity and the underwriting screen -- and leaves in one move for Payment Pending, Referred or Declined, decided by a single gate that reads both answers. The two-gate chain it replaces could not express "identity refers but the risk is fine": the first gate routed to Referred before appetite had been asked, so an underwriter always received half a file. Qualified became Awaiting Contact and Quoted became Quote Presented. "Qualified" meant pre-call here and post-call on the whiteboard -- the same word on opposite sides of the dial. WHY THE RENAME IS THE DANGEROUS HALF STAGES.name is compared against current_state_name in three places: the queue filter, the sidebar tally, and the action lookup on the lead detail page. A workflow rename that is not mirrored here does not error anywhere -- the record view returns zero rows, correctly, for a name no lead is in. Both renamed queues would simply have read empty. The header comment now says so. Worse, zk-state-docs was absent from STATE_ACTIVITIES entirely, so a lead sitting there rendered no buttons at all. Document upload is the human step the entire rest of the flow hangs off. ALSO Parked / Nurture has been in the workflow since 4 September and was never added here, so its Resume Outreach button did not exist. Until a scheduler lands, that button is the only thing that wakes a parked lead, so it gets a nav group of its own rather than being filed under "running by itself", which it is not. DROPPABLE follows the new state list. Mark Lost now also reaches Referred, which the workflow had never allowed and now does: a lead whose cover lapses while an underwriter holds the file previously had nowhere to go. doc_vehicle_photos becomes conditional. The comment said no break_in flag existed to test -- one does now, derived at filing on all three doors, so the slot honours its own "break-in only" caveat instead of being shown to every motor renewal. Verified by diffing STAGES.name and ACTIONS against tbl_wf_states and tbl_wf_state_allowed_activities on dev; both match exactly. |
||
|---|---|---|
| public/brand | ||
| src | ||
| .env.example | ||
| .gitignore | ||
| CLAUDE.md | ||
| eslint.config.js | ||
| index.html | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| vite.config.js | ||
Zurich Kotak — Lead Desk
Operator console for the Zurich Kotak "Lead to Policy" demo. Leads arrive through three channels — direct, bancassurance and agency — and run one state machine to policy issuance. The agents do the work; a person appears at one queue.
Workflow config is seeded from sm2/custom-apps/zurich-kotak/ (org 84,
app 536, workflow zk_wf_lead). Design doc:
sm2/app-designs/zurich-kotak/design.html. Workflow spec PDF:
sm2/custom-apps/zurich-kotak/zurich-kotak-workflow.pdf.
Run it
npm install
npm run dev # http://localhost:5175
npm run build # → dist/
.env carries the API host for local dev only:
VITE_ZINO_API_URL=https://dev.getzino.in
Logins (all password ZurichKotak@2026, org 84):
| Role | Can do | |
|---|---|---|
sanjay.uw@zurichkotak.example |
SME Underwriter | Clear / Decline Referral — the only human decision |
meera.rm@zurichkotak.example |
Bancassurance RM | Submit a Partner Bank Lead |
arjun.posp@zurichkotak.example |
POSP / Broker | Submit a Partner Agent Lead |
kavya.csr@zurichkotak.example |
Direct / Call centre | Submit a Self-Serve Lead |
deepa.ops@zurichkotak.example |
Operations | Cross-channel visibility |
Sign in as Sanjay to land in Referred to Underwriting, which is where the demo happens.
How it talks to the platform
Everything goes through src/api/client.js:
POST /usr/login— auth.org_idmust be a string; a number returnscannot unmarshal number into Go struct field LoginRequest.org_id.POST /app/536/view/recordview— every queue is one record view (zk-rv-leads) filtered server-side oncurrent_state_name.POST /app/536/view/form-screensthenPOST /app/536/activity— forms are read from the live activity schema and submitted straight back.
Forms are not defined in this repo
Whatever /view/form-screens returns is what renders — labels, types, select
options, which fields are mandatory. Add a field to an activity in Studio,
redeploy, and it appears here with no frontend change. That is deliberate: the
workflow is the source of truth, and a hardcoded form would quietly diverge
from it.
Permissions are never enforced here
The console does not hide a button to stop someone using it. Every permission
decision is the workflow's, made server-side on each submission — the platform
refuses and this app reports what it said. Two refusals worth knowing:
/activity answers 403, /start answers 400, and both carry
permission denied.
The runtime-config contract
VITE_ZINO_API_URL is read at runtime from a config.js the server writes
when it places the build — never compiled in. One artifact is promoted between
environments unchanged, so a build-time URL would point every environment at
whichever backend happened to build it. requireConfigValue throws if it is
missing, so a production build with no config.js fails loudly rather than
calling the wrong backend.
vite.config.js uses base: './' and the router takes its basename from the
<base href> the server writes, so one build serves any mount path. Do not
reintroduce a build-time base.
This is also why the fonts and logo live under src/assets/ and 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 path. The favicon is the one exception — it stays in public/brand/ and
is referenced relatively from index.html.
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 only fires on a new push — a push made before the frontgen project row existed matched no project and did nothing.
State of the build
| Piece | Status |
|---|---|
| Brand — Zurich Sans, palette, logo | done (kept from the original scaffold) |
| Sign-in against the real gateway | done |
| Runtime config, relative base, router | done |
Pipeline sidebar (mirrors tbl_wf_states) |
done |
| Queue lists | waiting on the zk-rv-leads record view |
| Lead file, activity forms, attribution panel | waiting on zk-dv-lead + form screens |