Commit Graph

41 Commits

Author SHA1 Message Date
3dd482f180 console: the Needs a Person queue, and what the AI said when it stopped
An AI employee here has no escalate tool — the platform withholds it from
autonomous employees on purpose — so "a person should take this" has to be
an activity in the workflow. It now is: Flag for Review, which lands the
lead in a new stage, Needs a Person.

The console side of that: the stage joins the nav under Needs you, its two
actions are Send back to the workflow / Send to underwriting, the flag
fields lead the lead-file groups, and a flagged lead opens on an amber
banner quoting the AI's own sentence about what stopped it, with the
blocker turned into something an operator can act on.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 12:28:53 +05:30
54333ef847 console: drop the "you" under the next-step button
The banner already says whose turn it is.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 11:44:54 +05:30
97c792a2fc console: the queue card fills the column beside Pipeline by stage
The two cards stretch to one height and the queue rows spread to fill it,
so the shorter card no longer ends in empty canvas.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 11:12:33 +05:30
9e093757d8 console: the risk card lines up with Pipeline by stage
Both right-hand columns are 380px now; the hero's was 300px, so the two
cards stacked with a visible step between them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 10:58:18 +05:30
79863e2a5f console: the Lead Desk redesign — one colour rule for who holds the work
Implements the design in zurich-kotak-leaddesk across every screen.

Foundation: Source Sans 3 bundled from src/assets/fonts (no Google Fonts
request), 15px root, navy/blue/amber/teal/red palette where blue is the AI,
amber a person, teal the customer and red is reserved for risk; flat white
10px cards on a grey canvas, no shadows.

Shell: 56px navy masthead (Z wordmark, Lead Desk, user), 232px white nav
grouped Needs you / Scheduled / History with coloured count pills and the
closed stages folded.

Overview: the hero says who holds each open lead (stacked bar + legend)
beside the renewals-at-risk card; five KPIs incl. Closed to date; Waiting
on a person queue with owner dots and "N more with the AI"; Pipeline by
stage with owner-coloured bars; Renewal exposure with tabs, dropdown
filters and a Holding it column.

Lead: back button, id line, holder pill; one attention banner (amber your
turn / blocked, red stalled, blue with the AI, teal with the customer) that
carries its button; four facts with a confidence bar; navy steps rail
whose current step takes its holder's colour; timeline as an avatar
gutter with title, stage pill, by-line + AI tag and text-link actions
(See reasoning · N steps, Hear the call, Open thread); side column flows.

Lists: card with search + hide-closed toggle, columns Customer / Stage /
Renewal due / Holding it / Premium / Last activity, stalled rows get a red
inset, closed rows dim, person-gated queues split into Your turn / With the
AI group rows.

src/api/holder.js is the single rule for holder tone + label so the table
cells, the overview and the lead header cannot disagree.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 23:56:29 +05:30
f31d6cb866 console: renewal filters as plain dropdowns
The add-a-chip builder read as a stray pill button. Replace it with three
labelled dropdowns above the table — Stage, Time to expiry, Premium range — plus
a Clear when any is set. Empty option means no filter on that field; they narrow
together. Standard, quiet table-filter UI instead of a build-your-own control.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-09 16:33:35 +05:30
b46faa70e2 console: replace bucket-click filter with a build-your-own filter
The renewal-exposure buckets are back to plain counts. Above the table is now
a filter builder: press Add filter, pick a field (Stage, Time to expiry,
Premium, or Lead name), give it a value, and add it. Each condition lands as a
removable chip and they AND together; Clear all drops them.

- Stage: choose from the stages actually present in the exposure set.
- Time to expiry: lapsed / within 7 days / 8-30 days.
- Premium: at least / at most an amount (a lead with no premium never matches).
- Lead name: substring over name and reference.

One matcher and one describer per field, so the chip label and the filtering
cannot drift. The table shows more rows once any filter is on.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-09 16:29:11 +05:30
e0d51159f6 console: play the call recording alongside the transcript
The voice agent uploads a dual-channel WAV of each call to blob storage; until
now the console showed only the transcript's conclusions, not the audio. Add a
player wherever the call is read:

- THE CALL panel on the lead gets an inline "Call recording" player.
- The transcript dialog gets the player above the turns, so a reviewer can
  listen while reading — the words and the audio are the same evidence.

Both read row.call_recording_url (the platform's own field for this, surfaced
on the lead by backend 107) and use preload="none", so a multi-MB WAV is only
fetched when someone actually presses play. The player simply does not render
when a lead has no recording.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-09 16:19:26 +05:30
bd4ced41ac console: make the renewal-exposure buckets filter the table
The three buckets (Lapsed / Within 7 days / 8 to 30 days) were static counts
above a combined table. They now double as the filter: click one to narrow the
table to that bucket, click again — or use the Clear affordance — to show every
open renewal by soonest due. The active bucket fills with its own tint, an
empty bucket is disabled, and the empty state is filter-aware. One predicate
map drives both the counts and the filter so they cannot drift.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-09 16:05:21 +05:30
47967c6318 console: use the full screen width on the lead page
The previous change over-corrected: left-anchoring a capped block pushed the
whole page to the left and left the right half of a wide monitor empty. Drop
the width cap so the content fills from the nav to the right edge, the way a
sidebar dashboard should.

- shell__main: no max-width, fills the width beside the nav.
- lead grid: story column takes the slack (1fr), facts rail fixed at its
  content width (360-420px).
- metric tiles: auto-fit, so three or four tiles always fill the row instead
  of leaving an empty hard-coded 4th cell on the right.

Reading comfort is still handled inside the feed (paragraphs cap their own
line length), so a wide panel does not sprawl the text.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-09 14:34:32 +05:30
6f50c0c30f console: kill the empty river — anchor the page to the nav
The content block was capped at 1460px and centred with margin:0 auto, so on a
wide or zoomed-out monitor a large empty river opened between the sidebar and
the content, with the whole page floating in the middle.

Fix is the standard sidebar-dashboard layout: one governing width, anchored to
the nav instead of centred. shell__main caps at 1320 and left-aligns, so the
header, metric tiles, stage strip and the two-column grid all share that width
and line up. The grid's own columns cap at their natural content widths (the
760px story column at its reading width, the 340-440px facts rail at what its
key/value pairs need) so neither card stretches into internal whitespace. Any
spare width on a very wide screen falls to the right as margin, the way a
sidebar layout should read.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-09 14:26:16 +05:30
125b3ffcf6 console: 60/40 split on the lead page — close the dead gap
The rail was a fixed 340px, so the main column ballooned much wider than the
feed text (capped for readability) and a dead gap opened between the narrative
and the facts. Proportional split now: the AI decision stream ~60%, the
customer / vehicle / document facts ~40%. The gap closes, the feed fills its
column (max line length nudged 76→82ch), and the facts breathe — the PDF names
stop wrapping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-09 14:17:02 +05:30
ee57184fa2 console: rebuild the look in the assured-inspired language (approved direction)
Full styling reset to the approved mockup — from scratch, no functional change.

- PALETTE: clean white ground, warm near-black ink, ONE confident magenta-pink
  accent (#e5227f) reserved for action and active state. The accent keeps the
  --zk-blue-* token names it always had; only the values changed. Teal is the
  customer, amber a person; pink is "act / here", nothing else. Neutrals warmed,
  shadows softened to a plum-ink low alpha.
- TYPE: Plus Jakarta Sans — geometric-humanist, bold on headings (page title
  800, panel titles and figures 700+), the modern-product register assured has.
- SHAPE: the flat single-canvas is gone; back to clean rounded cards that float
  a little on the off-white — panels, metric tiles (money in the accent tint),
  the stage rail, the facts rail. Status ribbon in teal, the primary action a
  pink pill with an accent glow.

Same components, same data, same behaviour — only the surface.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-09 12:24:44 +05:30
e21e3949f5 console: state before numbers; frame optional actions; flatten the overview band
Applying the lens from the status-strip critique across the surfaces.

LEAD PAGE
- "What is happening now" lifts above the metrics. An operator's first question
  is what is going on / what do I do, not what the premium is — so the status
  strip (an alert when blocked or stalled, the live state otherwise) sits
  directly under the header, before the four numbers, full-width.
- The optional "Add a document" action stopped floating: with no required step
  it read like the next move and sat under "nothing is waiting on you". A quiet
  "Optional" tag now frames it.

OVERVIEW
- The "right now" band was four heavy dark-navy shadowed cards — the
  cards-on-canvas language removed everywhere else, clashing on the flat white
  page. Flattened to light tiles read by a coloured left rule (blue AI, amber
  person, teal customer, grey risk), the figure taking that colour; a live
  person-queue or lapsed renewal warms its whole tile. Prominence from the big
  number and the accent, not a dark fill.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-09 12:01:46 +05:30
e4ca2b92c9 Revert "console: redesign the UI — one card language, icons, filters, live trail"
This reverts commit e90e4204ec.
2026-09-09 11:40:49 +05:30
e90e4204ec console: redesign the UI — one card language, icons, filters, live trail
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>
2026-09-09 11:37:42 +05:30
17561f3f73 console: the immediate action lives top-right, not below the fold
"What do I do now" was answered only by scrolling past the metrics and stage
rail to the Next step panel. When this user owns the next step it now sits in
the header top-right as a prominent amber button — where the eye goes for the
next move — and pressing it opens the form and scrolls to it. When nothing is
owed, the passive status stays there instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 01:42:36 +05:30
adef7838fc console: flat single-canvas design language, in the Zurich Kotak brand
The real problem was never the font or the shadows — it was the design LANGUAGE.
Floating white cards on a grey-blue ground is the generic admin-template look,
and no restyle escapes it. This ports the approved mockup: one canvas, sections
set off by hairlines and space, the activity feed as the hero.

- ONE CANVAS. The content ground is white; the sidebar is the only tinted
  surface. Panels drop their border, radius and shadow and become plain
  sections under their own headings.
- The metric strips (lead + overview) stop being caged tiles and become an
  inline stat row between two hairlines — money at display size, the rest
  quieter.
- The stage rail loses its card and becomes a slim inline stepper.
- The property rail flattens: Customer / Vehicle / Documents are hairline-
  divided groups aligned to the edge, headings at label size — it is
  reference, and should not shout over the story.
- "Who holds it" drops from a filled pill to a coloured dot and a line of text.
- Bricolage is gone. Display and body are both Zurich Sans, the brand face;
  headings and figures lean on weight and tracking, not a second family. The
  palette is the ZK blues throughout.

Same data, same functionality — only the surface. Revertible in one push.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 01:21:55 +05:30
83b5795b22 console: the surface pass — display face, depth, and whose move it is
The layout was right but the surface still read as a flat corporate form. Three
levers, all from the approved mockup:

- A DISPLAY FACE (Bricolage Grotesque) on the things the eye lands on — the
  lead name, panel headings, and every figure. Zurich Sans stays the body
  voice; this gives the numbers and titles the tighter, more current character
  the plain grotesque could not. Loaded from Google Fonts with Zurich Sans as
  the fallback, so a blocked CDN degrades to the brand face rather than breaking.

- DEPTH. Panels and the metric tiles lift from the barely-there --sh-sm to a
  real --sh-md with rounder corners, so cards read as objects on a surface
  rather than boxes drawn on paper.

- WHOSE MOVE IT IS. The header said "STAGE / Quote Presented" — a label. It now
  carries a coloured pill: "Right now · With the customer" (teal), "With the AI"
  (blue), "Waiting on a person" (amber). The stage name is in the rail below;
  the pill answers the question an operator actually opens a lead to ask.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 01:05:20 +05:30
a49ae3d86c console: the story is the page; the reference is the rail
The audit trail is the record of work nobody watched happen — it is what this
product is for — and it sat in a 384px gutter with its own inner scrollbar
beside a full-width panel of form fields. That said, in layout, that the AI's
work was a footnote to the data entry. Exactly backwards.

Swapped. The trail takes the wide column and scrolls with the page under a
heading that says what it is: "What happened". The reference data — customer,
vehicle, documents, the call, the stage's own facts — moves to a 340px sticky
rail, where short key/value pairs belong. The crew who worked the lead sits at
the top of that rail as its own small panel.

The overview now opens on who holds the work rather than on money: four large
numbers on navy — with the AI, waiting on a person, with the customer,
renewals at risk. A live person-queue or a lapsed renewal warms its cell; the
rest stay cool. Money is the outcome; this is the state, and it is the sentence
the whole console exists to say.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 00:04:12 +05:30
9c05e4b26b console: the shell, the trail and the lead header on the same rhythm
Continues 009153f into the surfaces it had not reached.

The top bar was 66px with a hard rule under it — admin chrome. It is 72 now,
on the softer line, with the product name at 16px instead of 15. The sidebar
gets wider gutters and taller rows, so the queue list stops reading as a dense
menu and starts reading as the pipeline it is drawn to represent.

The audit trail is where people spend their time on this screen, so it gets the
most air: entries at 24px apart rather than 18, quoted text with 12/16 padding
instead of 8/12, and the activity name at semibold so an entry has a heading
rather than a first line.

The stage badge was 15px under a 12px caption. It is the single most important
fact about a lead and now reads at 16px semibold, which is legible from across
a desk — which is how it will actually be read in a demo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 23:56:59 +05:30
009153fc0c console: a type scale, so size can carry meaning again
The app had FIFTY-FOUR distinct font sizes and forty of them sat between
0.58rem and 0.95rem — a three-pixel band. The most-used size was 0.78rem, so
body copy was 12.5px. That is not a hierarchy, it is a fog of small grey text,
and it is the main reason the screen read as a decade old: nothing could be
emphasised because everything was already the same size.

Nine steps now, with real jumps. 12px is the floor and is reserved for
uppercase eyebrows — every one of the 53 declarations sitting there that was
not an eyebrow has been lifted off it. The surfaces people actually read — the
trail's entries, WhatsApp bubbles, field values, table cells — are at body size
rather than meta size.

The hairline cages are gone with them. The lead metrics and the overview KPIs
were seven equal cells in a 1px grid, every value the same size as its label,
which left the reader to do the prioritising themselves on every lead. They are
separated by whitespace now, values at 22px against 12px labels, with premium
and commission larger again: size does the prioritising instead.

Panels breathe at 32px rather than 24, radii went 10/14 -> 12/16/20, borders
dropped to the softer line, and the stage rail, the queue table and the cards
were rebuilt on the same spacing rhythm.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 23:36:21 +05:30
b27141b797 console: mark the AI's work, and give the call its own card
Initials alone do not say a step was autonomous — "EN" reads as a colleague's
badge. On a screen whose whole claim is that the work ran without people, the
one fact every entry has to state is which entries were the machine's. Agent
discs now carry a sparkle, the settled convention for machine-generated work,
so it needs no legend; humans keep a plain disc, because a mark on every actor
would say nothing. Screen readers get "(AI)" said out loud, since they cannot
see it.

The call was the one step nobody could replay from the fields. Everything else
on a lead was typed or derived; this happened out loud, and its outcome sat in
the full file next to the source channel while the words themselves were only
reachable from a button halfway down the trail. It gets its own card, with the
transcript one click from the notes that summarise it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 19:30:33 +05:30
49d5443c60 console: the page carries what matters, the file holds everything
Fifty-seven fields over thirteen groups sat open on the lead page, which
pushed the audit trail — the part people actually read — a screen and a half
down. Most of it is reference material: nobody opens a lead to find its source
channel. They open it to see who the customer is, what is being insured, what
has been attached, and what this stage turns on.

So the page carries exactly that — four cards at most, the fourth chosen by the
stage the lead is in — and the complete record moves into a drawer behind one
click. Nothing is dropped: the drawer is handed the same computed groups the
page used to render, so the two cannot disagree about a value.

Hiding is only safe while it stays findable, so the trigger names the number it
is holding back rather than saying "more", and the drawer brings a filter over
labels AND values, because Ctrl-F was how people read the open version and a
click breaks that.

Long values are clamped in both places. uw_referral_reason and lost_reason run
to a paragraph, and a paragraph printed raw in a summary card would rebuild the
wall this exists to remove.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 19:21:56 +05:30
ea2b56e332 console: the file reads as facts, and filing a lead stays on the page
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>
2026-09-08 18:53:16 +05:30
3069bf4849 console: the lead file is a file again, and the journey is on the page
THE FILE WAS THIRTEEN TABS. One group of fields visible at a time, so
"what do we know about this lead?" meant thirteen clicks and a good
memory — on a page called the Lead FILE. A file you can read one page of
at a time is a filing cabinet.

Every group is now a card, laid out in columns that reflow to the width:
three on a wide screen, two normally, one on a laptop. The whole 360 is
one scroll and Ctrl-F finds anything. Groups avoid breaking across a
column boundary — a heading at the foot of one column with its fields at
the head of the next is worse than an uneven column — and inside a group
the label/value pairs stack, so a long value is not squeezed into half a
column against its label.

THE JOURNEY IS AT THE TOP. The page named the current stage in a corner
chip and said nothing about the path, so "how far along is this?" — the
first question anyone asks — needed the workflow memorised.

Passed steps are read from the AUDIT, not assumed from the order. A lead
can skip (documents uploaded from Qualified carried one straight past
Contacted) and can go backwards (a callback returns it to Awaiting
Contact); colouring everything left of the current stage would claim
steps that never happened. The side states — Parked, Referred, Lost,
Declined — are deliberately off the rail: they are departures from the
path, not points every lead passes, and a row implying otherwise would be
a lie about the process. When the lead is in one, the rail names it at
the end instead.

THE TRAIL STOPS READING AS A MARGIN NOTE. 384px beside a full-width file
made a column of two-line entries look like an aside; it is the half of
the page that says what happened. Widened to 440.

Layout only. The form, the actions, the audit fetch and every submission
path are untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 18:37:32 +05:30
de8b158ca9 console: the nav navigates, and the screens act
"New lead" sat in the sidebar. A sidebar is for moving between places;
filing a lead is an action, and an action in a list of destinations is
the one item that does not behave like its neighbours — everything above
and below it changes what you are looking at, and it did something.

It now sits on the overview and on every queue, beside the lists it adds
to, which is also where somebody is standing when they realise they have
a lead to file. Same role gate as before: shown only to a user who holds
an entry door.

The sidebar's own button CSS went with it rather than being left for
someone to wonder about.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 18:31:07 +05:30
33bf65c5f5 console: All leads means all leads
The page called "All leads" dropped every closed lead from its query, so
a lead that was lost, declined or onboarded vanished from the one place
somebody goes when they cannot find something. That is the page whose
entire job is to answer "where did it go?".

Closed leads are shown now, dimmed, with the stage they ended in. A
"Hide N closed" toggle is offered only when there is something to hide
and is OFF by default — hiding is a choice, not the behaviour.

The filter also moved off the query and onto the render. A view that
throws rows away when it fetches them cannot be un-filtered by a toggle,
and the header can now say how many are closed rather than silently
showing fewer than the count claims.

Dimmed rather than struck through or greyed to unreadability, and hover
restores it: the row is muted, not disabled — it still opens, and the
lead file behind a lost lead is often exactly what someone wants.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 17:38:46 +05:30
93baba3fa1 console: when an agent stops, say so and offer the way back
The lead page told an operator an automated step "typically completes
within two minutes; this view refreshes automatically" — and went on
saying it indefinitely. Today a lead sat forty minutes under that
sentence while the model provider returned 502s.

Agents stall, and for reasons this app does not control: the provider
slows from 6s a call to 90s or drops the request, a thinking budget runs
out mid-sentence, a late webhook wakes the wrong employee. From the
console every one of those looks identical — a lead that stops — so the
screen now reports the only thing it can honestly know (nothing has
happened for N minutes), says the work so far is safe, and offers the
way out.

THE WAY OUT IS NOT A RETRY BUTTON, because the platform has none and no
agent can be woken directly. But every agent is woken BY AN ACTIVITY, so
performing that activity again wakes it again. nudgeFor() holds that
mapping, and inside Document Pending it picks by how far the chain
actually got — three agents work that state in sequence and the one to
restart is the one that did not finish.

That matters most for the Advisor. It is the only AI step nobody may
perform by hand — permitted to ai_advisor alone — so re-performing the
activity that wakes it is the ONLY route back, and it is the step that
failed twice today. Without this table an operator's only option was to
wait or to call me.

Five minutes before it says anything: an employee wake plus a slow model
is legitimately three or four minutes, and a console that cries stall on
a working lead is one nobody reads.

The clock lives in state, ticking every 20s, rather than Date.now() in
the render body — reading the wall clock while rendering is impure, and
the counter would otherwise only move when something else happened to
re-render the page.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 16:37:25 +05:30
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
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
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
704afe170e console: the row is the control, and the agent says what it is doing
Two changes to the queue and overview tables.

The row is the click target. An "Open" button was a 90px destination inside
a 1000px row that already reads as one object, and it put a second tab stop
on every line. The row now carries the click, Enter/Space, a focus ring and
the hover tint; the button column is gone, along with its CSS.

The column it freed shows what is actually happening. This workflow is run
by agents and watched by a person, so the question a queue has to answer is
not "which stage" — inside a queue every row shares it — but "is it moving".
An automated stage now reports working (under 3 minutes), waiting (under 30),
or stalled with the elapsed time, with a pulse on the live one and a red left
edge on the stuck one. Thresholds are generous on purpose: an employee wake
takes a minute or two and a scheduled retry can be hours out, so stalled
means "longer than any normal step", not "longer than average".

Before this, a lead three hours into "Calling the customer…" looked identical
to one thirty seconds in — which is the failure this console exists to catch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 11:52:20 +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
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