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>
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>
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>
A contiguous run of WhatsApp turns collapsed into one timeline entry that kept
the FIRST turn's speaker and previewed the LAST turn's message. When a run held
the customer's question and Engage's answer, the entry carried Engage's chip
over a quote the customer had typed — the trail claimed the machine said
something a person did, which on an accountability demo is the worst thing it
could get wrong.
Runs now group per speaker. That still collapses the case grouping exists for
(the same sentence resent several times) and can no longer misattribute,
because every row in a run is one voice. Customer runs read "the customer /
THE CUSTOMER SAID"; Engage runs read as Engage / WE REPLIED.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two problems on the collect-documents form.
LAYOUT. Every field sat in one flat auto-fit grid, so on a wide screen the three
uploads took the first half of a row and three text inputs took the rest — and
an upload that has read a document is several times taller than a text box, so
the inputs stranded themselves up beside the buttons and the row went ragged.
Documents now render in their own section above the details, and each grid is
internally one height. The upload cells align to the top so a read document no
longer drags its neighbours down.
THE UPLOAD ITSELF. It was one pink slab that only changed its words, so a
finished upload shouted as loudly as an empty one. Now there are two states: an
empty field is a dashed target with an icon and the formats it takes; a filled
one is a quiet row with a document icon, the file name, and a small Replace.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Removing the node circles took the active-row marker with them — the tint alone
was too quiet to say which queue you were on. The active row (and Overview /
All leads) now carries a short accent bar on its left edge.
It appears only when active, so inactive rows stay clean and nothing reads as an
empty checkbox, which is what the circles did.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Five fixes to the confusing nav:
- The two per-row numbers had no key. The AI's share now reads "N with AI" in
quiet grey, so it stops competing with the pink "needs you" badge.
- Removed the outline node circles (and the rail line they hung on) — with one
queue per group they read as empty checkboxes, not a pipeline. Rows now align
flush and the active queue is shown by its tint alone.
- Dropped the section-header count that just repeated the single row beneath it.
- Lightened and tightened the group labels so the queues lead, not the headers.
- Closed now matches a queue row — same padding, hover and count badge — while
staying a foldable disclosure.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
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>
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>
Backend 106 keeps the WhatsApp reply loop open after the policy issues, so the
Engage employee answers post-issuance questions. Mirror that in the operator
console: the onboarded (closed) state now offers the same "Reply to the
customer" affordance the quote and payment stages do, folded away as a bounded
loop, so a person can step in on WhatsApp. customer-reply stays channel-only,
never an operator button, as at every other stage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The chain of thought reads the employee's "why" out of the workflow data
fields, matched by base id. Three real reasoning fields were written by the
employees but not in the map, so those steps showed as a bare heading even
though the reasoning was captured:
- uw_referral_reason — the underwriting screen and prepare steps write their
decision reasoning here; the map only knew uw_decision_notes (the
clear/decline note), so the substantive UW decision showed no chain.
- kyc_ref — a clean KYC leaves the mismatch note empty, so the step had no
rung; kyc_ref is the document package the employee matched identity against
and now stands in as the finding.
- nudge_notes — the payment-follow-up employee's rationale.
Traced against instance 57632: underwriting (2.3k-char reasoning), KYC and the
nudge all had their reasoning captured but unmapped. This is frontend-only, no
backend or workflow change.
Known residual: Accept, Payment, Realise and Onboard write only a reference,
no "why" field, so their reasoning lives in aiemployee.tbl_ai_decisions /
the ai_reasoning column, which the audit endpoint does not return and the
org-scoped monitor endpoint a partner_agent cannot call. Forcing a chain onto
those mechanical acks needs a workflow field + charter change (future
instances only).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
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>
TWO THINGS.
1. Fix: the header "Your move → Upload documents" kept showing after documents
were uploaded. The render already demotes Collect Documents from a step to a
"replace or add" recovery lever once the files are in (docsDone), but the
header CTA computed primaryAction straight off `actions` and never applied
that demotion — so it surfaced "Upload documents" over a lead whose docs had
just landed. The same docs-in check now guards primaryAction.
2. Refine: the chain of thought reads as a distinct AI reasoning artifact now,
not loose text under a link. The affordance is a tinted "thinking" chip; the
revealed reasoning sits in a soft accent-tinted panel with a stepped rail and
markers — the record of how the machine decided, given the weight it deserves.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When statusStrip was moved (fda1247), the regex that placed it after `const
stalled` matched only the FIRST line of stalled's multi-line ternary. The
`? { mins, nudge } : null` continuation was left dangling right after
`const statusStrip = (…)`, so statusStrip parsed as `<jsx> ? {mins,nudge} : null`
— a truthy JSX condition — and evaluated to the object {mins, nudge}. Rendering
that object threw React #31 and blanked the page for any stalled instance;
`stalled` itself was left a bare boolean.
Restored stalled to its object form and removed the orphaned ternary. statusStrip
is a clean JSX expression again.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
The lifted status strip (e21e394) was defined right after `holds`, but its JSX
references `blocked` and `stalled`, which are declared ~80 lines lower. A const
that reads a const declared below it throws "Cannot access before
initialization" at render — a temporal dead zone the bundler does not catch, so
the build passed and the lead page rendered blank.
Moved the statusStrip definition to just after `stalled`, so every value it
reads exists first. The lifted-above-the-metrics position is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
The role-icons-plus-corner-spark were busy and didn't read. Replaced with the
two symbols that are actually universal: a sparkle for the machines — the mark
Claude, Gemini and Copilot all use, so an AI step reads as AI with no legend —
in the agent's own colour, and a plain head-and-shoulders person glyph for the
people. The contrast is the whole message; which agent is said by the name
beside the disc and by the colour. Verified crisp at disc size.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An audit found 124 of ~200 sizings at 12-13px: the 13px "meta" tier had become
the de-facto body size (97 uses), so the app read tiny and uniform with nothing
for the eye to land on, and the upper scale was starved. Shifted the crowded
lower tiers up one notch — dominant text 13->14, field labels 14->15, values
15->16, emphasis 16->17. One change to the scale definitions, lifting every
surface at once; 12px stays reserved for uppercase eyebrows.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Zurich Sans (a soft humanist OTF) read poorly at the data-density sizes this
console runs, and did not carry the modern-enterprise register we are after.
The UI moves to Geist — Vercel's typeface, the register of Linear/Vercel-class
tools: neutral, technical, and sharp at small sizes. Refs and figures use Geist
Mono. Zurich Sans stays in the fallback stack, so a blocked CDN degrades to the
brand face rather than to a system default.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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 layout placed information by convenience, not importance, and repeated
itself. This is the consolidation pass, no new look.
- METRICS: seven at equal weight became four, money first — Premium, Commission,
AI confidence, Renewal-countdown. Dropped lead age and time-in-stage (they
overlapped each other, and the feed already carries timing), and product /
vehicle (the rail's "The vehicle" holds them). Nothing is lost; every removed
measure is still on the page once, where it belongs.
- STAGE stops appearing as a metric — the rail is where the lead's position
lives, and the feed shows each transition.
- NEXT STEP no longer repeats the status. Its heading and "nothing is waiting on
you" subtitle rendered even when nothing was owed, saying a third time what the
status strip and the header already said. The heading shows only for a real
step now; secondary actions (add a document, mark lost) still render, without
the framing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"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>
Text looked soft across the whole app. Cause: the entrance animation used
transform: translateY, and .shell__main (the entire content area) ran it with
fill-mode both. An element that animated transform stays on a GPU layer, and
text composited on a transformed layer renders blurry in Chrome on Linux — so
every word on the page was soft.
zk-rise is opacity-only now — it fades in without ever moving text onto a
fractional-pixel layer — and the page-wide container no longer animates at all
(a full-page fade on every navigation only softened text while it played, for
nothing). The remaining transforms are hover-only and transient.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The avatars were initials-plus-spark for the machines and bare initials for
people — thin, and it made five differently-skilled agents look like the same
badge in five colours. The convention that reads instantly is icons for
machines, initials for people: the contrast itself tells a human step from an
autonomous one before a name is read, and the symbol says WHICH machine.
Each employee now wears a glyph true to its job — a magnifier for Intake (checks
the lead), a speech bubble for Engage (talks to the customer), a shield for the
Advisor (recommends the cover), an ID badge for KYC (verifies identity), balance
scales for the Referral (prepares a file for a human to weigh). The AI spark
stays at the corner. People keep their initials, which is the right avatar for a
person and now the clear signal that a person, not a machine, did the step.
Verified legible at disc size (20-26px) across all five brand hues.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The New Lead form opened off-centre and its backdrop covered only part of the
screen. Cause: the dialog uses position:fixed, but .shell__main animates
transform on entrance, and an element that has animated transform keeps acting
as the containing block in Chrome even after the animation finishes. So "fixed"
resolved against the 1460px max-width, margin-auto content area — offset by the
sidebar — instead of the viewport.
Every scrim dialog shared this latent bug. Each now renders through
createPortal(…, document.body), so the fixed overlay is always relative to the
viewport regardless of any transformed ancestor. New Lead, the conversation and
call threads, the full lead file, the agent card, and the reasoning dialog all
now centre and dim the whole screen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
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>
Follows the previous commit but drops everything that would have needed a
backend change. The chain is built entirely from the audit trail the console
already fetches: the REASONED rung is the employee's own reasoning fields
(attribution_reason, ai_recommendation_rationale, and the rest — already in the
data), and the DECIDED rung is the activity it committed, the state it moved the
lead to, and its confidence (ai_recommendation_confidence, also a real field on
the step). The finding stays visible above the chain.
No dependency on any endpoint change: the tool-call rung and the tbl_ai_decisions
join are gone. What remains is real, self-contained, and ships with a push.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An agentic console has to answer one question before anyone will rely on it:
why did the AI do that. The reasoning was always recorded — the employees write
it, the platform stores the tools they called — but the trail showed only the
conclusion. An operator either trusted it blind or opened the raw record.
Each AI step now carries its chain of thought, assembled from real data, never
narrated after the fact: what it CHECKED (the tools it called, each marked for
whether it came back with anything), what it REASONED (the employee's own
words), and what it DECIDED (the activity it committed, the state it moved the
lead to, its confidence). The finding — the first sentence — stays visible; the
chain opens under it.
A tool that returned nothing is drawn hollow. That is the Manju Malik failure
made visible: a licence check that came back empty and was reasoned over as if
it were a finding is exactly what wrongly dropped a real lead, and an operator
scanning the chain should see the empty result the machine reasoned from.
The reasoning ships now — it is already in the audit data. The tool rung lights
up once view-service joins tbl_ai_decisions (next commit); until then the chain
shows two rungs and degrades cleanly.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
The conversation panel told the reader that two automatic sends were absent
from the thread. Only one is now — 97 removed the "I have your message"
receipt.
Co-Authored-By: Claude Opus 5 (1M context) <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>
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>
"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>
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>
Recording is now on for Meera (93) and the transcript is kept on the lead
(94), so the call stops being a thing only one AI ever saw.
The wake that ends a call already carried far more than Engage used — a
summary, a sentiment analysis, and the FULL TRANSCRIPT, turn by turn —
and all of it was dropped once the three-sentence write-up was done. So a
customer said "I think it's 12,000 rupees" and "lower premium", and the
file held a paraphrase.
That matters here more than it would elsewhere: consent_artefact =
verbal_call is written from what the agent HEARD, and it is the artefact
justifying every later contact. The evidence for it should be the
customer's own words.
"Hear the call" now sits on the Log Contact entry and opens the
conversation, laid out like the WhatsApp thread — agent right, caller left
— because it is the same kind of thing and should not need learning
twice. It also files under Contact in the lead record.
Turns are split on the speaker labels rather than on newlines: a single
turn wraps, and splitting per line shattered one sentence into four
bubbles. A transcript that will not parse is shown raw — it is still
evidence, and "nothing to display" would be a lie.
NOT INCLUDED: the audio. Recording is enabled and the WAV is uploaded,
but recording_url is delivered to the agent's post-call WEBHOOK
(voice_internal_handler.go:429), not in the wake payload, so nothing puts
it on the lead. Wiring that webhook to an external-API activity is a
separate change, worth doing when someone asks to listen rather than to
read.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The trail rendered twelve fields as prose and four as money. The other
hundred-odd were invisible — so Capture Motor Risk, which writes engine
capacity, fuel, year, NCB, previous insurer, policy number, IDV and the
add-ons, appeared as a heading and a timestamp. The only way to see what
an agent had decided was to open the Lead file and guess which values
came from which step.
Each entry now carries a folded "N fields written" list: every field that
step committed, by label, with the values already shown above it
(narrative, money, documents) excluded so nothing repeats. Folded because
most steps write a dozen and the trail has to stay readable as a story.
WHAT THIS IS NOT, and it matters: it is not the model's internal
reasoning, and it is not its tool calls. Both ARE recorded — ai_reasoning,
ai_confidence, ai_model and ai_tool_calls sit on the same audit row — but
GetAuditLogsForApp selects eight columns and none of them are those, so
an app JWT cannot read them at all. Surfacing them needs four columns
added to that query and to ActivityLogEntry, which is a view-service
change and an image build.
This is the next best thing and arguably the more useful half: not what
the agent thought, but what it committed, itemised against the step that
committed it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The page said "Waiting for the customer to reply on WhatsApp" directly
above a panel saying "Accepted by the customer, over WhatsApp. KYC and
underwriting are running." Both rendered from the same instant; only one
was true.
Quote Presented has the same shape as Document Pending — the state holds
still while the situation underneath it changes completely. Before the
customer answers, the lead is genuinely with them and nothing is running.
After they accept, KYC and the underwriting screen run inside the SAME
state, because the lead does not move until underwriting clears. phaseOf
splits it, so the strip now says "Verifying KYC" and then "Screening the
risk for underwriting", and the Next step line records only the fact of
the acceptance rather than repeating what is running.
That also puts this stretch under the stall detector for the first time,
which matters: the KYC agent talked itself out of the underwriting screen
earlier today and the lead sat in Quote Presented with a passed KYC and
nothing happening. With this it would have said so after five minutes and
offered "Run the underwriting screen" — both steps ops_admin can perform,
so the recovery needs nobody's help.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>