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>
This commit is contained in:
Yashas 2026-09-09 12:01:46 +05:30
parent d1ed66b7ae
commit e21e3949f5
2 changed files with 127 additions and 92 deletions

View File

@ -300,6 +300,82 @@ export default function Lead() {
// are still looked up on the state's own uid; only the presentation moves.
const stage = phaseOf(STAGES.find((s) => s.name === stateName), row)
const holds = holdsOf(stage)
/* WHAT IS HAPPENING NOW, lifted above the numbers. An operator's first
question about a lead is "what is going on / what do I do", not "what is
the premium" so the status strip (an alert when blocked or stalled, the
live state otherwise) sits directly under the header, before the metrics.
Rendered full-width there rather than inside the narrow work column. */
const statusStrip = (
blocked ? (
<div className="doing doing--blocked">
<span className="doing__stop" aria-hidden="true" />
<div>
<strong>Stopped {blocked.what}</strong>
<span>
{blocked.fix} The agents will pick the lead up again on their own once it
is there; nothing else is running in the meantime.
</span>
<button
type="button" className="doing__fix"
onClick={() => setOpen(blocked.via)}
>
Open Collect Documents
</button>
</div>
</div>
) : stalled ? (
<div className="doing doing--stalled">
<span className="doing__stop" aria-hidden="true" />
<div>
<strong>{stage.doing} nothing for {stalled.mins} minutes</strong>
<span>
{stage.by} has not come back. That is usually the model provider being
slow or dropping a request, not a problem with this lead the work so far
is safe and nothing has been lost.
{stalled.nudge
? ` Running ${stalled.nudge.label.toLowerCase()} wakes ${stalled.nudge.by} to try again.`
: ' There is no step to re-run from here; the actions below are the way on.'}
</span>
{stalled.nudge ? (
<button
type="button" className="doing__fix"
onClick={() => setOpen(stalled.nudge.uid)}
>
{stalled.nudge.label}
</button>
) : null}
</div>
</div>
) : stage?.kind === 'customer' && stage.doing ? (
/* Not an agent working, and not a task of yours either the lead
is with someone outside the business. Without this the screen
went blank at exactly the stage where an operator is most likely
to wonder whether something has broken. */
<div className="doing doing--waiting">
<span className="doing__wait" aria-hidden="true" />
<div>
<strong>{stage.doing}</strong>
<span>
Their answer comes back to this lead on its own Engage reads it and
records the acceptance. Nothing is waiting on you.
</span>
</div>
</div>
) : stage?.kind === 'auto' ? (
<div className="doing">
<span className="doing__pulse" aria-hidden="true" />
<div>
<strong>{stage.doing}</strong>
<span>
Handled by {stage.by}.
{stage.after ? ` The lead stays in ${stateName} until a quote exists — nothing is waiting on you.` : ''}
{' '}Typically completes within two minutes; this view refreshes automatically.
</span>
</div>
</div>
) : null
)
// Only what THIS user may run. The workflow refuses the rest server-side
// anyway; showing them a row of buttons that all 403 reads as a broken app
// rather than as a control.
@ -441,6 +517,8 @@ export default function Lead() {
) : null}
</header>
{statusStrip}
{/* Measures, not fields. Age and dwell time are computed neither is on
the record and they are the two that answer "is this lead moving?",
which no label/value pair on the page could. */}
@ -497,78 +575,6 @@ export default function Lead() {
</div>
) : null}
{/* An AI-carried stage has no empty action bar and no idle screen: it
says what is happening and who is doing it. The activities below it
stay available ops can run them by hand but they are not the
answer to "why is nothing moving?". */}
{blocked ? (
<div className="doing doing--blocked">
<span className="doing__stop" aria-hidden="true" />
<div>
<strong>Stopped {blocked.what}</strong>
<span>
{blocked.fix} The agents will pick the lead up again on their own once it
is there; nothing else is running in the meantime.
</span>
<button
type="button" className="doing__fix"
onClick={() => setOpen(blocked.via)}
>
Open Collect Documents
</button>
</div>
</div>
) : stalled ? (
<div className="doing doing--stalled">
<span className="doing__stop" aria-hidden="true" />
<div>
<strong>{stage.doing} nothing for {stalled.mins} minutes</strong>
<span>
{stage.by} has not come back. That is usually the model provider being
slow or dropping a request, not a problem with this lead the work so far
is safe and nothing has been lost.
{stalled.nudge
? ` Running ${stalled.nudge.label.toLowerCase()} wakes ${stalled.nudge.by} to try again.`
: ' There is no step to re-run from here; the actions below are the way on.'}
</span>
{stalled.nudge ? (
<button
type="button" className="doing__fix"
onClick={() => setOpen(stalled.nudge.uid)}
>
{stalled.nudge.label}
</button>
) : null}
</div>
</div>
) : stage?.kind === 'customer' && stage.doing ? (
/* Not an agent working, and not a task of yours either the lead
is with someone outside the business. Without this the screen
went blank at exactly the stage where an operator is most likely
to wonder whether something has broken. */
<div className="doing doing--waiting">
<span className="doing__wait" aria-hidden="true" />
<div>
<strong>{stage.doing}</strong>
<span>
Their answer comes back to this lead on its own Engage reads it and
records the acceptance. Nothing is waiting on you.
</span>
</div>
</div>
) : stage?.kind === 'auto' ? (
<div className="doing">
<span className="doing__pulse" aria-hidden="true" />
<div>
<strong>{stage.doing}</strong>
<span>
Handled by {stage.by}.
{stage.after ? ` The lead stays in ${stateName} until a quote exists — nothing is waiting on you.` : ''}
{' '}Typically completes within two minutes; this view refreshes automatically.
</span>
</div>
</div>
) : null}
{/* WHAT TO DO NEXT, not what is permitted.
Every action carries a role: `do` is the step this stage is
@ -661,6 +667,10 @@ export default function Lead() {
{again.length ? (
<div className="alt">
{/* With no required step, these optional actions floated at
the top of the column reading like the next step. A quiet
tag frames them as optional without repeating the status. */}
{!step.length ? <span className="alt__note">Optional</span> : null}
{again.map((a) => (
<button key={a.uid} type="button"
className={'alt__btn' + (open === a.uid ? ' is-open' : '')}

View File

@ -888,31 +888,32 @@
margin-bottom: var(--sp-5);
}
/* FLAT, TO MATCH THE REST. This was four heavy dark-navy cards with shadows
the cards-on-canvas language removed everywhere else, so on the flat white
page it clashed. Now light tiles, each read by a coloured left rule: blue
for the AI, amber for a person, teal for the customer, red for risk. The
number takes that colour, so the row is read as colour before text. Prominence
comes from a big figure and the accent, not from a dark fill. */
.now__cell {
position: relative;
padding: var(--sp-6) var(--sp-6) var(--sp-5);
border-radius: var(--r-lg);
background: var(--zk-navy);
color: var(--zk-white);
padding: var(--sp-5) var(--sp-5) var(--sp-5) var(--sp-6);
border-radius: var(--r-md);
background: var(--zk-white);
color: var(--zk-ink);
border: 1px solid var(--zk-line-soft);
overflow: hidden;
box-shadow: var(--sh-md);
}
.now__cell::before {
content: '';
position: absolute;
inset: 0 0 auto 0;
height: 4px;
background: var(--zk-blue-light);
inset: 0 auto 0 0;
width: 4px;
background: var(--zk-blue);
}
.now__cell--person::before { background: var(--zk-amber-line); }
.now__cell--cust::before { background: var(--zk-cyan); }
.now__cell--risk::before { background: var(--zk-danger-line); }
/* A live person-queue or a lapsed renewal is the one thing on this page that
is actually owed. It warms up; the rest stay navy. */
.now__cell--person.is-live { background: var(--zk-amber-ink); }
.now__cell--risk.is-live { background: var(--zk-danger-ink); }
.now__cell--person::before { background: var(--zk-amber); }
.now__cell--cust::before { background: var(--zk-teal); }
.now__cell--risk::before { background: var(--zk-grey); }
.now__l {
display: block;
@ -921,24 +922,35 @@
font-weight: var(--fw-semi);
letter-spacing: .09em;
text-transform: uppercase;
opacity: .78;
color: var(--zk-grey);
}
.now__v {
display: block;
margin-bottom: var(--sp-2);
font-size: var(--fs-3xl);
font-size: var(--fs-2xl);
font-weight: var(--fw-semi);
line-height: 1;
letter-spacing: -.03em;
letter-spacing: -.02em;
font-variant-numeric: tabular-nums;
color: var(--zk-blue-dark);
}
.now__cell--person .now__v { color: var(--zk-amber-ink); }
.now__cell--cust .now__v { color: var(--zk-teal-ink); }
.now__cell--risk .now__v { color: var(--zk-ink); }
/* A live person-queue or a lapsed renewal is the one thing actually owed. It
warms its whole tile; the rest stay plain white. */
.now__cell--person.is-live { background: var(--zk-amber-tint); border-color: var(--zk-amber-line); }
.now__cell--risk.is-live { background: var(--zk-danger-tint); border-color: var(--zk-danger-line); }
.now__cell--risk.is-live::before { background: var(--zk-danger); }
.now__cell--risk.is-live .now__v { color: var(--zk-danger-ink); }
.now__s {
display: block;
font-size: var(--fs-2xs);
line-height: var(--lh-snug);
opacity: .82;
color: var(--zk-muted);
}
/* KPI strip. Whitespace rather than rules see .lmetrics for why the cage
@ -1830,3 +1842,16 @@
}
.cta__txt b { font-size: var(--fs-sm); font-weight: var(--fw-semi); }
.cta svg { width: 16px; height: 16px; flex: none; }
/* Frames optional (again) actions when there is no required step, so a
secondary button stops reading as the next move. */
.alt__note {
display: inline-block;
align-self: center;
margin-right: var(--sp-2);
font-size: var(--fs-3xs);
font-weight: var(--fw-semi);
letter-spacing: .07em;
text-transform: uppercase;
color: var(--zk-grey);
}