Commit Graph

15 Commits

Author SHA1 Message Date
171aa4c2fa console: a live trail, faces for the agents, and the thread as a thread
Five of the six asks. The sixth — the whole row as the click target —
shipped in 704afe1 and is already live on both grids.

THE AUDIT TRAIL WAS FROZEN. Timeline fetched the rows once on mount and
never again, so a lead being worked by five agents in three minutes
showed the trail as it was when the page opened — on the one screen whose
job is watching work happen. The rows move to the lead page, which
already polls every 12s, and Timeline becomes presentational. One fetch,
one poll, and the conversation view reads the same rows so the two cannot
disagree.

THE ACCEPTANCE BUTTON OUTLIVED THE ACCEPTANCE. Recording an acceptance
twice is not a loop, a recovery or an alternative — it is meaningless,
and offering it invites someone to overwrite a customer's WhatsApp
acceptance with a worse record of the same event. Gone once
acceptance_ref is set, replaced by a line saying who accepted and when.

THE AGENTS HAD NO FACE. Five of them carry this workflow and the console
named them three different ways — "ai_engage" here, "Engage" there,
"Engage AI" elsewhere — so nobody could see that the thing which called
the customer and the thing which wrote the quote were one worker. One
roster now (api/agents.js): a short name, a colour and two initials each,
used wherever an agent is named. People get a disc too, in grey — a trail
where the machines are decorated and the humans are plain text reads as
though the machines are the important ones, which is backwards on a
screen built for oversight. A "Worked by" strip above the trail shows the
team at a glance.

THE CONVERSATION WAS A LOG, NOT A THREAD. Each turn sat as its own entry
among twenty others. Now one button opens it as a thread — theirs left,
ours right, oldest first — and the footer says plainly which parts it
holds: the quote, the read receipt and the acceptance confirmation are
sent by trigger nodes and never written to a field, so they are not
there. A thread that quietly omitted them would be worse than one that
admits what it is.

CONFIRMING A PREMIUM IS A SIGN-OFF, NOT A TASK. It is the only step
locked to one role and the only one that touches money — an employee that
could mark a premium received could put a customer on risk for a policy
nobody paid for. It sat fourth in a list of six queues. It gets its own
band now, shown only to the role that owns it, amber only when something
is actually waiting: a permanent alert colour on an empty queue teaches
people to stop seeing it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 15:29:02 +05:30
927d335280 console: show the WhatsApp exchange, and let ops answer through it
Engage can now answer a customer's question from the policy wordings
(82), so the lead file has a conversation in it rather than a single
recorded reply.

The audit trail renders both halves — "The customer said", then "We
replied" — so the exchange reads as one, which is what an operator
reviewing an acceptance nobody in the office made actually needs.

The lead file gets a Conversation group beside Proposal: the question,
the answer, and the count. Acceptance stays in Proposal, one tab away
from the words that produced it.

"Reply to the customer" is offered to ops_admin only. Engage declines
anything it cannot answer from the wordings, anything that is a
negotiation, and everything past three answers — so a person needs a way
to take over, and it has to be through the same thread or the reply
lands somewhere the lead file never sees.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 13:32:12 +05:30
e5b413ecac console: the customer accepts from their own phone
Recording the acceptance was the third human touchpoint, and it was a
person in the office typing what a customer had said somewhere else.
The customer's WhatsApp reply now drives it (81), so this stage has no
step of its own.

"Record the acceptance" drops from a step to a folded recovery lever —
kept, because a customer who says yes on a CALL still needs somebody to
record it, and because a reply Engage judged ambiguous has to be
actionable by a person.

That left Quote Presented with an empty screen at exactly the stage an
operator is most likely to wonder whether something has broken, so the
waiting state now says so. Grey, and no pulse: nothing is happening, and
that is the correct state — unlike the AI strip, where a still dot would
mean something is wrong.

The reply itself surfaces in two places: the audit trail carries it as
"The customer said", and the lead file files it under Proposal beside the
acceptance it produced. The evidence and the decision it justifies are
one line apart, which matters when nobody in the office made it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 13:26:46 +05:30
5ff74ea22d console: numbers in the navigation, and reasoning behind one click
THE SIDEBAR NOW CARRIES COUNTS. It deliberately did not, on the argument
that a tally could only come from a second full list call that would then
disagree with the queue's own total. Right about the cost, wrong about
the conclusion: with no numbers the only way to learn whether anything
was waiting on you was to open all nine queues in turn, which is the
question navigation exists to answer.

There is no second call. The overview's existing fetch moved into a
PortfolioProvider that both surfaces read, so this is one call fewer than
before and the two agree by construction. Bounded at 200 rows, as the
overview always was; past that the honest answer is an aggregate
endpoint, not a bigger limit.

What the badge counts is what needs a PERSON — phaseOf again, so a lead
whose documents are in and whose AI chain is running is reported beside
the badge rather than inside it. An amber dot marks a queue holding a
renewal inside a week, which is the only reason to open one queue before
another and was previously invisible. Zero is shown rather than hidden:
"nothing here" is an answer, and a queue that disappears when it empties
makes the sidebar move under the cursor.

Closed is folded into a summary. Three of the twelve queues, opened about
once a week, and at equal weight they made the live ones harder to find.

REASONING OPENS IN A DIALOG, NOT INLINE. The audit rail is 320px wide
with one entry per step, and a 1,500-character rationale expanding in
place pushed a lead's whole history off screen to read one sentence of
it. Nobody reads a paragraph in a sidebar. The finding — the AI's own
first sentence, which is already the conclusion — stays on the line; the
working is one click away and one Escape back, headed by what is being
read ("Call — Log Contact") rather than by nothing. Escape closes, the
page behind does not scroll, and focus returns to the button that opened
it so a keyboard reader keeps their place.

Two headings that described the container rather than the contents:
Record → "Lead file", and the audit trail's subtitle now says what a
reader gets from it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 12:42:40 +05:30
d6756b4d2e console: show what the workflow actually did, and when it stopped
Traced the console against the workflow it renders. Five gaps, all of
which made a working chain look like a broken one.

THE AUDIT TRAIL WAS READING THE WRONG KEYS. An audit row's data is keyed
by the ACTIVITY's field ids, which carry a per-form suffix — the call
notes arrive as contact_notes_2, the document request as
documents_notes_3 — and the timeline matched the unsuffixed global ids.
Almost nothing ever matched, so the trail was a list of activity names
with the agents' reasoning invisible behind it. The one narrative line
that did appear was an accident. It now reads the platform's own typed
`fields[]` (label, data_type, value) and matches on the base id.

That is also what lets it answer the question that was asked: an upload
now renders as "3 documents received" with each one named and openable,
instead of a bare "Collect Documents".

DATA_UPDATE ROWS WERE CLASSED AS AGENT WORK. A bookkeeping row inherits
the roles of whoever caused it, and the AI check ran first — so an AI's
field write appeared in the trail as an entry titled "Data updated",
while the toggle underneath still offered to reveal the others. The
activity id says a row is bookkeeping; the roles say who triggered it.

DOCUMENT PENDING IS TWO SITUATIONS. Before the upload a person has to
act; after it the lead stays in the same state while three AI steps run.
Both rendered as "waiting on the partner agent", so a lead that had just
been served showed an Upload documents button under a panel saying the
documents had been received, and counted against the Action-required
queue. phaseOf() derives the difference once, from documents_status, and
the header, the queue count, the row status and the action list all read
it. The upload demotes to recovery — "Replace or add a document", folded
away with the other levers.

A STOPPED CHAIN LOOKED IDENTICAL TO A RUNNING ONE. The rating engine
refuses to price without an IDV and writes so into quoted_breakup;
Engage then declines to raise a quote it would have to fabricate. Both
are right, and nobody was told: the refusal sat in a field on a tab and
the lead never moved again. blockedOn() surfaces it as an amber strip
that names the missing value and opens the form that carries it.

THE STALL MEASURE WAS DATED FROM THE WRONG COLUMN. progress() fell back
to created_at when updated_at was absent — which it always was, because
neither view returned it — so every lead older than half an hour would
have reported stalled. It reads updated_at only; the view supplies it as
of 76.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 12:17:52 +05:30
6a6801d2bd console: all-leads view, document form prefill, and two things that should not be offered
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.
2026-09-07 19:26:13 +05:30
07d4608888 lead: show the next step, not the permission list
The app read as a control panel. Every activity a stage allowed was
rendered as an equal button, so operating it required already knowing
the workflow — which is the opposite of what a workflow tool is for.

Two concrete failures, both visible in the data:

Ops on Document collection got SIX equal buttons. Exactly one was the
next step: upload the three documents. Capture Motor Risk, Capture SME
Risk, AI Cover Recommendation and Generate Quote are the AI's own chain,
present only so a stalled lead can be pushed by hand.

And FOUR stages offered a partner agent nothing but "Mark Lost" — New
Lead, Contacted, Underwriting referral, Premium confirmation. At each of
those the system was actively working the lead, and the only thing the
app suggested was giving up on it.

So an activity now carries what it IS at a stage, not only who may press
it:

  do      the step this stage is waiting on. Usually one. Referred has
          two, because clear and decline are a decision pair rather than
          a step and the option of not taking it.
  again   a bounded loop — retry, reminder, re-quote. Legitimate, never
          the answer to "what now".
  force   an AI employee's own job, offered to a person only so a stalled
          lead can be moved. Folded behind "Lead not moving?".
  exit    Mark Lost. Always reachable, never presented as a step.

The panel is "Next step". With a step, one button at full weight. Without
one it says what is actually happening and who has it, which is the true
answer at six of the nine working stages and was previously rendered as
an empty bar or a lone Mark Lost.

Result per role, checked rather than assumed: an underwriter sees two
buttons, on one stage, and nothing anywhere else. A partner agent sees a
step at three stages. Ops sees a step at four and can still force any
AI activity from the folded section.

Nothing about permissions changed, and the action list still matches
tbl_wf_state_allowed_activities exactly — this is only about which of
them is presented as the thing to do.
2026-09-07 17:55:51 +05:30
aca5d1a043 console: role-scoped access, portfolio analytics, enterprise register
Four things, and the first is the one that mattered.

ROLE-BASED ACCESS. The console showed every action to everyone on
purpose — "the refusal is the demo" — and the workflow refused
server-side. That is a defensible engineering position and a poor
product: an underwriter saw a row of six buttons, five of which 403.

src/api/permissions.js now mirrors tbl_wf_activity_permissions, and the
session already carries user.roles, so nothing new had to be fetched.
Action buttons, sidebar queues and the entry doors are all filtered.
It remains presentation only — the platform still refuses, and a role
added there but missing here hides a button that would have worked,
which is the failure mode to watch.

Each role's app now looks like their job. Ops sees the whole board and
still cannot clear a referral. An underwriter sees one queue and two
buttons. Agents see the three queues they work in. Reporting is NOT
gated: the overview counts the whole portfolio for everyone, because an
agent tracking leads they filed is reasonable and acting on them is not.

One deliberate divergence from the workflow, commented where it lives:
the two scheduled activities carry no roles at all, because that is the
only configuration under which the scheduler can perform them. Open to
the SYSTEM is not open to everyone signing in, so the UI narrows them —
otherwise an underwriter is offered "Send Document Reminder".

ANALYTICS. The overview was three lists. It is now a measured page:
open leads, pipeline value, written premium, conversion, commission —
then renewal exposure in three buckets, the queues needing a person with
the age of the oldest item in each, and stage distribution as bars.

Checked against the live book rather than assumed, which found a real
bug before it shipped: Policy Issued is written business AND not yet
terminal, so its premium was counted in pipeline and production both.
₹42,912 double counted on 37 leads. Pipeline now excludes anything
already on risk.

The lead page gained the same treatment: renewal countdown, lead age,
time in current stage, premium, commission, AI confidence. Age and
dwell are computed — neither is on the record, and they are the two
figures that answer "is this moving?", which no field could.

TERMINOLOGY. The previous pass over-corrected: fixing builder jargon
("the workflow decides, server-side") produced chat ("What you can do",
"The agents have it", "Nothing here right now"). Neither is how an
operations console reads. Now: Actions · Record · Audit trail · Action
required · Customer response · Automated · Scheduled · Document
collection · Underwriting referral · Premium confirmation.

LAYOUT. Prose subtitles cut to one line or removed. Measures render as
tabular figures in a bordered strip instead of label/value pairs. The
overview splits into work on the left and distribution on the right.
Stage bars replace a wall of equal-weight cards.

Pre-existing lint errors unchanged at 11; none in the new files.
2026-09-07 16:31:04 +05:30
4c210c7e09 console: a page to monitor from, and words an operator would use
Three problems, and the first one is the reason the other two were hard
to see.

THERE WAS NO DASHBOARD. "/" redirected to the underwriting queue — one
stage, usually empty, and belonging to somebody else. Every screen in
the app answered "show me this queue"; none answered "how is the book
doing, and is anything waiting on me?", which is the only question a
person has before they have picked a queue.

So: a Today page. Three blocks, in the order somebody cares about them.
What is waiting on a person, as three cards that colour only when they
have something in them. Renewals running out, worst first, capped at 30
days because a renewal six months away is not a thing to look at today.
Then every stage with its count, zeroes included — a stage quietly
receiving nothing is only visible if its zero is on screen.

It is one list call, counted in the browser. That is honest at this size
and it is the same call the queues already make. If the book outgrows it
the answer is a counts endpoint, not a bigger page.

THE WORDS WERE WRITTEN FOR WHOEVER BUILT THE WORKFLOW. "What happens
next" was followed by "these are the only activities this state allows
— but the workflow decides, server-side, whether you may". A failed list
said "this queue has no record view yet". Panels were called "Flow
details". A closed lead read "nothing runs from here". None of that is
wrong; all of it is addressed to the wrong reader.

Now: "What you can do", "Lead details", "History", "This queue could not
be loaded". Queue names say what they want — "Documents needed",
"Underwriter to review", "Payment to confirm". The sidebar group called
"Not yet" says "Not due yet", and "Running by itself" says "The agents
have it", which is the actual claim being made.

THE QUEUE HAD NINE COLUMNS AND LED WITH THE WRONG ONE. Two were internal
vocabulary: attribution status renders "clear" or "contested" and means
nothing to an operator, and the channel is background rather than
something anyone scans a queue for. Both fold into a subtitle under the
customer. That leaves six columns and puts the renewal countdown — the
number that decides whether to act today — second instead of fifth.

Also: the timeline printed a raw slug when a step came back without a
name. It is the screen this product is demonstrated on, so a
"zk-act-doc-reminder" in the middle of an otherwise readable story is
expensive. It now reads as English.
2026-09-07 16:13:13 +05:30
Likith K R
66ea08e317 Updated to new frontend specifications 2026-09-04 13:26:20 +05:30
Likith K R
ae548a3a10 UI Enhancements in recordsview and detailsview 2026-09-02 15:33:26 +05:30
1fb8d0dc98 Put dates where the work is decided
The timeline carried timestamps; the two screens people actually act on
carried none. A queue with no dates cannot tell you which lead is going
stale, and on a renewal book the countdown to expiry is the number that
decides whether anyone should act today — it was buried in a field group.

Queue gains "Renewal due" and "Added", both absolute plus relative: the
absolute answers "when exactly", the relative answers "is this stale".
The countdown is toned — lapsed, urgent, soon, later — on the same
thresholds the workflow itself uses to decide whether to call, so the
screen and the agent are reading the same bands.

Lead header gains the countdown, the current insurer, the registration,
and when the lead arrived and was last touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 12:52:26 +05:30
49c51c24d7 Show what happened on a lead, and who did it
The lead file listed the current field values and nothing about how they
got there. On an app whose whole claim is that agents do the work, that
is the wrong thing to show: you could see the outcome and not whether a
machine or a person produced it.

Adds a timeline from the audit trail — every activity in order, badged by
actor, carrying the reasoning the agent actually wrote (attribution,
eligibility, call notes, recommendation rationale, the premium breakup)
rather than the raw field dump that is already below it.

Colour encodes something real: machine work is blue, people amber, the
platform grey, so a file reads at a glance for how much of it was done by
hand.

Also fixes the audit path — the bare /view/audit is not an API route and
falls through to the SPA, returning HTML with a 200 that parses as a JSON
error rather than an HTTP one. It has to be app-scoped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 12:40:30 +05:30
5ea627b064 Make the console operable: add a lead, work a lead
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>
2026-08-24 16:11:28 +05:30
9210681488 Wire the console to the platform and make it deployable
The scaffold had the brand right and nothing behind it: the login was a
600ms setTimeout with a TODO, and three pieces of the frontgen deploy
contract were missing.

- Sign in against POST /usr/login (org_id as a STRING — a number is
  rejected by the gateway) with the session in a provider; surface the
  gateway's own message rather than a generic "invalid credentials",
  because a wrong password and a user without access to this app look
  identical from here and are not.
- Runtime config: the API URL is read from the config.js the server
  writes at placement, never compiled in, and requireConfigValue throws
  so a build with no config.js fails loudly instead of calling whichever
  backend built it.
- base: './' plus a router basename taken from <base href>, so one build
  serves any mount path.
- Move the fonts and logo from public/ into src/assets/ — Vite rewrites
  bundled asset URLs to be relative, while a public/ file referenced as
  "/fonts/..." stays absolute and 404s under the /zurich-kotak/ mount.
- API client for recordview / detailview / form-screens / start /
  activity, and the pipeline shell whose sidebar is the state machine in
  the order a lead moves.

Queue and lead-file data wait on the record and detail views.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 15:20:13 +05:30