Visual and information-architecture pass across every screen. No API call,
payload, poll, permission rule or workflow mirror changes; forms still render
from the live schema and the timeline's merge/grouping is unchanged (only the
display order is reversible).
Design language
- Ground + white cards with one hairline and one radius; sentence-case
headings; shared primitives in index.css (.card .pill .btn .avatar .live).
- One inline icon set (components/Icon.jsx); no more filled navy/red tiles.
- Read-side labels in api/display.js: roles and enum tokens humanized,
customer initials/tones, money and date formatters. Nothing sent changes.
Shell
- 56px header with brand, global lead search (portfolio rows, "/" to focus)
and identity chip with a human role label.
- Sidebar rows carry icons and a clear active bar; AI roster at the foot,
each opening its remit card.
Login
- Operator copy instead of retail copy; centred sign-in card with show/hide
password; demo persona picker that fills the EMAIL only.
Overview
- "Who holds the work" as four quiet cards that warm only when owed.
- Pipeline flow bar with every stage named beneath it; KPI strip in a card.
- Action rows with chevron / view-only eye; expiry bands filter the table.
Queues
- Filter bar (search, product, channel, expiry band, stalled, hide closed)
and sortable columns, all client-side on the fetched page.
- Avatars, product/channel chips, stage chips for closed leads, tinted
stalled rows, count inline with the title.
Lead
- Avatar + chips in the header; metrics card with the stage rail inside,
and a branch node for Referred / Parked / Lost / Declined.
- Timeline newest-first by default (toggle), the worker's face as the node,
one footer row per entry, AI markdown rendered as bold.
- "AI team on this lead" panel with per-agent step counts; PDF tags on docs.
Forms & dialogs
- Required marked with an asterisk + legend; "Select…" placeholder; dd Mon
yyyy hint under date inputs; open activity form in a titled panel;
one-line dialog footnotes.
Fixes: horizontal overflow at 1024px, boxed "in N days" figure, raw tokens
(sme_underwriter, verbal_call, Rsa), findings cut at "1.".
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The lead file put seven AI paragraphs in the same flow as "Mobile", each with
the weight the audit trail gives it, so the card was mostly quotations with the
facts lost between them — and the same prose sat in the trail two feet to the
right. Facts group at the top now; the reasoning is a caption, two lines and a
link to the dialog that already held it. Pairs go side by side rather than
stacked, and the CSS columns become a grid: column flow read top-to-bottom-then-
across, so groups landed in an order nobody could predict and tore raggedly
between 2-field and 12-field groups.
New lead was a route. Pressing it cost you your place in the queue you were
working through and left you three navigations away. It is a dialog over the
page now, with the same modal furniture as the conversation and reasoning ones.
Three of its boxes were answered by the system before anyone saw them — the
channel, the partner code and the submitting agent, all stamped by prefill from
the signed-in identity. They are hidden at RENDER only: `fields` still holds
them, so they validate and submit exactly as before. Filtering them out of
`fields` would have dropped the attribution and 400'd on the mandatory channel.
A stamped field that comes back empty is drawn anyway, so a prefill that does
not resolve cannot fail validation against a box that is not on screen.
Business Name is scoped to SME. An INIT form has no lead to filter on, so
visibility reads the live answer to Product Line first and the saved lead
second — the form narrows as it is filled, and a motor renewal drops from
twelve boxes to nine. It is optional, so hiding it cannot block a submission.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Uploading an RC read the document correctly, said so, and filled
nothing.
The endpoint answers keyed by the ocr_config's extraction_fields[].key
— `reg_no`, `make_model`, `mfg_year`, `engine_cc`, `fuel` — because
that is what the vision prompt was asked to produce. Its own package
doc says so. The form's fields are `motor_reg_no_6`, `motor_cc_3`,
`motor_fuel_3`. The console wrote the response straight into values, so
every extracted value landed on a key no field renders: the read
succeeded, the "we read it for you" panel appeared, and the boxes
stayed empty.
This is the third place the same mismatch has bitten. Prefill hit it
(the pipeline names globals, the form uses suffixed activity keys) and
the validation errors hit it (the workflow reports the machine key).
Different surfaces, one cause: nothing in the stack translates between
a global field name and a form's per-activity id, so every consumer has
to do it and each one forgot.
The bridge was already on the field. ocr_config.field_mappings maps
extraction_key -> target_field (the global), and the form field is that
global plus a suffix. Verified against the live Collect Documents
schema: all five RC keys now resolve to real fields.
Also: which value wins. Seeding the form from the lead record (6a6801d)
made every mirrored field non-empty, so the old "only fill what is
blank" rule would have blocked OCR from writing anything at all. Values
copied off the lead are now tracked, and a document may overwrite them —
it is the better source for what it states. Anything typed by a person
is never overwritten, and stops being overwritable the moment it is
typed.
Four operator-reported issues, one commit because two of them are about
the same form.
ALL LEADS WAS GONE. The team's rewrite of the queue screen dropped the
/stage/all view, so finding a lead meant guessing which of nine queues
it sat in. Restored: no stage filter, closed leads dropped client-side,
one request for the whole open book. Linked under Overview in the
sidebar so it is one click from anywhere.
THE DOCUMENT FORM ASKED FOR WHAT THE LEAD ALREADY KNEW. Collect
Documents mirrors fourteen record fields — registration, make and model,
previous insurer, expiry, policy number, PAN — so the OCR has somewhere
to write. Rendered blank, they read as fourteen more things to type, on
a form whose whole point is uploading three files. They are now seeded
from the lead by base key: an agent uploading for KA01MF6618 sees
KA01MF6618 already there. Server prefill wins, then anything typed;
files and generated ids are never seeded, and an INIT form has no
record to seed from. SME-only fields were already hidden on a motor
lead by fieldLine — that half was working.
"SEND A REMINDER" IS THE SCHEDULER'S JOB, NOT A BUTTON. It fires at
24h, 72h and 120h on its own. Offering it as a loop action invited a
person to send a reminder the platform was already about to send. It
and Retry Call move to `force` — reachable behind "Lead not moving?"
for the day the schedule itself fails, and otherwise out of sight.
THE UPLOAD STAYED THE "NEXT STEP" AFTER THE UPLOAD. Once
documents_status is complete the lead remains in Document Pending while
Engage captures the risk and the quote is built — and the panel kept
saying "Upload documents", which reads as though nothing was received.
Now, when complete, the upload becomes a loop (a fourth document, a
correction) and the panel says what is actually happening: documents
received, Engage AI is capturing the risk and preparing the quote.
Permissions unchanged; ACTIONS still matches
tbl_wf_state_allowed_activities exactly.
Submitting the new-lead form with a blank Product Line produced:
400 The workflow rejected this submission.
validation failed for activity zk-act-init-agent: product_line_4(required)
That asks an operator to know what a field_s_id is, that `_4` is a
disambiguating suffix and not part of a name, that "required" is a rule
rather than a value, and that 400 means them and not us. All four are
ours to know.
Two changes, and the first means the second is rarely reached.
The form now checks required fields before it sends anything. It marks
each empty one — on the label, because somebody scanning a thirteen-field
form is looking for the NAME they missed, not for a red box — scrolls the
first into view, and says "Product Line is required". No round trip, and
nothing to decode. The marking clears as the field is filled, since a
field still flagged after being corrected teaches people to ignore the
flagging.
If the workflow does reject a submission on fields — it validates more
than this form can know about — describeValidation now renders the reply
in the form's own labels. It parses every `key(reason)` pair, maps the
reason to a sentence, and resolves the key through the schema, including
the suffix rule the prefill seeding uses. A key the schema does not carry
keeps its raw name: a wrong label is worse than an ugly one, because the
operator goes looking for a field that is not there.
The status code is dropped from that surface. "400" is the first thing
read and the least useful thing shown; it stays on the errors an operator
genuinely cannot fix by typing.
"Sourced By", "Submitted By" and "Source Channel" rendered empty on the
new-lead form. The prefill pipeline was working the whole time — the
form-screen response carries
partner_code "Shetty Insurance Services (POSP-77341)"
rm_or_agent_id "arjun.posp@zurichkotak.example"
source_channel "agency"
and the form ignored all three.
The two sides key the same field differently and nothing reconciles it.
A form field is the ACTIVITY key, suffixed because one global may be
used on several activities in a version — partner_code_3,
rm_or_agent_id_3, source_channel_4. A fieldMapping node names the
GLOBAL — partner_code, rm_or_agent_id, source_channel. Neither is
wrong. The seeding loop matched on f.id exactly, found nothing, and
seeded nothing, with no error anywhere.
Now it tries the exact id, then the field uid, then the global name with
the numeric suffix stripped. Taking all three means this survives a
pipeline authored in either convention rather than breaking again the
next time one is written the other way. Empty and null are skipped so a
mapping that resolved to nothing does not overwrite a real default.
Worth noting the failure mode rather than just the fix: a prefill that
matches nothing looks exactly like a prefill that was never configured.
It is why the pipeline read as "empty" from the config tables — the
authoritative store is tbl_wf_activity_prefill_nodes, not the
pipeline_config JSONB, and the quickest honest check is neither: POST
/view/form-screens and read prefill_data.
Collect Documents shipped with every document field rendering as a plain
text input — ActivityForm had no case for `file` or `ocr`, so they fell
through to the default. There was a form asking you to type an RC book.
Uploads happen ON PICK rather than on submit, because /ocr-extract takes
a REFERENCE to a stored file: the document crosses the wire once, it
survives a reload (a draft cannot carry a File), and re-extracting costs
no second upload.
For an ocr field the extracted values are shown inline — "read from the
document" — and filled into the fields they map to. Only into fields that
are still blank: a value a person typed is never overwritten by one a
model read.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
lead_ref is now an id_gen, issued server-side and stripped from the
submission — so the form shows it as auto rather than as an empty box
somebody is expected to type a reference code into, and never sends it.
Also seeds the inputs from the prefill the form response carries. The
door's channel and the signed-in agent are stamped by a prefill pipeline,
so the form should show them already filled rather than ask.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pipeline could be looked at but not driven. Adds the three pieces
that make the machine walkable end to end from the UI.
- ActivityForm renders whatever /view/form-screens returns — labels,
types, select options, mandatory flags — and submits it back. No form
is defined in this repo, so a field added in Studio appears here with
no code change.
- AddLead presents the three doors. Each is a separate INIT activity with
its own permissions; the bank one lands further along because the bank
already did CKYC.
- Lead shows the file grouped in the order it was worked, ending with
what the sourcing agent earns, plus the activities this state allows
and who normally performs each.
The "who normally performs this" label is presentational only. Nothing
here enforces anything — the workflow refuses server-side and the form
reports what it said, including a note when a 403 is the platform
declining rather than the console misbehaving.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>