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>
This commit is contained in:
Yashas 2026-09-09 01:42:36 +05:30
parent 60f6c97201
commit 17561f3f73
2 changed files with 64 additions and 2 deletions

View File

@ -319,6 +319,25 @@ export default function Lead() {
// anyway; showing them a row of buttons that all 403 reads as a broken app
// rather than as a control.
const actions = actionsFor(roles, stage?.uid)
// THE ONE THING THIS USER MUST DO, promoted to the header. "What do I do
// now" is the first question an operator asks of a lead, and it was answered
// only by scrolling past the metrics and the stage rail to the Next step
// panel. When this user has an action, it belongs top-right, where the eye
// goes for the next move; when they do not, the passive status stays there
// instead. `role: 'do'` is the committing step never a re-run or a
// recovery lever, which must not be dressed as the thing to do.
const acceptedAlready = Boolean(row.acceptance_ref)
const primaryAction = actions.find(
(a) => a.role === 'do' && !(acceptedAlready && a.uid === 'zk-act-accept'),
) || null
const goToAction = () => {
if (!primaryAction) return
setOpen(primaryAction.uid)
// Let the form mount, then bring the working surface into view.
requestAnimationFrame(() =>
document.getElementById('lead-next')?.scrollIntoView({ behavior: 'smooth', block: 'center' }))
}
const fieldCount = groups.reduce((n, [, shown]) => n + shown.length, 0)
// Built from the SAME rows the full file renders, so the two can never
@ -406,7 +425,18 @@ export default function Lead() {
{/* WHOSE MOVE IT IS. The stage name is in the rail below; this says
who holds the lead right now, which is the question the header is
really being asked. The customer tone reuses the teal cust palette. */}
{holds ? (
{primaryAction ? (
/* YOUR MOVE. When this user owns the next step, the header carries it
as the prominent thing amber, because it is waiting on a person
and pressing it opens the form below and scrolls to it. */
<button type="button" className="cta" onClick={goToAction}>
<span className="cta__txt">
<small>Your move</small>
<b>{primaryAction.label}</b>
</span>
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M3 8h9M8.5 4.5 12 8l-3.5 3.5" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"/></svg>
</button>
) : holds ? (
<div className={'holds holds--' + (holds.pillTone === 'cust' ? 'cust' : holds.tone)}>
<span className="holds__puck" aria-hidden="true">
{holds.tone === 'ai' ? (
@ -622,7 +652,7 @@ export default function Lead() {
if (!step.length && !again.length && !extras.length) return null
return (
<div className="panel">
<div className="panel" id="lead-next">
<div className="panel__head">
<div>
<h2 className="panel__title">Next step</h2>

View File

@ -1798,3 +1798,35 @@
.holds--cust .holds__puck { background: var(--zk-teal); box-shadow: 0 0 0 4px var(--zk-teal-tint); }
.holds--cust .holds__txt small { color: var(--zk-teal-ink); }
/* Your move: the immediate action, in the header
The one thing this user must do, promoted top-right where the eye looks for
the next move. Amber because it is waiting on a person; pressing it opens
the form and scrolls to it. When nothing is owed, the passive holds status
sits here instead. */
.cta {
flex: none;
display: inline-flex;
align-items: center;
gap: var(--sp-3);
padding: var(--sp-3) var(--sp-4) var(--sp-3) var(--sp-5);
border: 0;
border-radius: var(--r-md);
background: var(--zk-amber);
color: #fff;
cursor: pointer;
box-shadow: var(--sh-sm);
transition: background var(--t-fast), box-shadow var(--t-fast), transform var(--t-fast);
}
.cta:hover { background: var(--zk-amber-ink); box-shadow: var(--sh-md); }
.cta:focus-visible { outline: 2px solid var(--zk-amber-ink); outline-offset: 2px; }
.cta__txt { display: flex; flex-direction: column; align-items: flex-start; line-height: 1.2; }
.cta__txt small {
font-size: var(--fs-3xs);
font-weight: var(--fw-semi);
letter-spacing: .08em;
text-transform: uppercase;
opacity: .82;
}
.cta__txt b { font-size: var(--fs-sm); font-weight: var(--fw-semi); }
.cta svg { width: 16px; height: 16px; flex: none; }