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>
This commit is contained in:
Yashas 2026-09-08 19:30:33 +05:30
parent 49d5443c60
commit b27141b797
4 changed files with 91 additions and 1 deletions

View File

@ -1,6 +1,7 @@
.who { display: inline-flex; align-items: center; gap: 6px; min-width: 0; } .who { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.who__disc { .who__disc {
position: relative;
display: grid; display: grid;
place-items: center; place-items: center;
flex: none; flex: none;
@ -56,3 +57,35 @@
opacity: 0; opacity: 0;
transition: opacity .12s; transition: opacity .12s;
} }
/* The AI mark, on the disc rather than beside the name: it has to survive
showName={false}, and it belongs to the worker, not to the label. Sits
proud of the disc so it stays legible against every one of the five hues,
with a white ring to separate it from the colour underneath. */
.who__ai {
position: absolute;
right: -3px;
bottom: -3px;
width: 11px;
height: 11px;
fill: var(--zk-amber, #b5761f);
stroke: var(--zk-white, #fff);
stroke-width: 0.9;
stroke-linejoin: round;
paint-order: stroke fill;
}
.who--md .who__ai { width: 13px; height: 13px; right: -3px; bottom: -3px; }
/* Said out loud for a screen reader, which cannot see a sparkle. */
.who__sr {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
border: 0;
}

View File

@ -20,8 +20,26 @@ export default function AgentChip({ agentKey, name, size = 'sm', showName = true
const body = ( const body = (
<> <>
<span className={`who__disc who__disc--${tone}`} aria-hidden="true">{initials}</span> {/* THE DISC SAYS WHICH WORKER; THE SPARKLE SAYS IT IS NOT A PERSON.
Initials alone do not carry that "EN" reads as a colleague's
badge, and on a screen whose entire claim is that the work was done
autonomously, the one fact every entry must state is which entries
were. The sparkle is the settled convention for machine-generated
work across current interfaces, so it needs no legend.
Only agents get it. A trail where a human's disc also carried one
would say nothing at all. */}
<span className={`who__disc who__disc--${tone}`} aria-hidden="true">
{initials}
{a ? (
<svg className="who__ai" viewBox="0 0 12 12" aria-hidden="true">
<path d="M6 .9 7.2 4.3 10.6 5.5 7.2 6.7 6 10.1 4.8 6.7 1.4 5.5 4.8 4.3Z" />
<path d="M10 8.2 10.5 9.6 11.9 10.1 10.5 10.6 10 12 9.5 10.6 8.1 10.1 9.5 9.6Z" />
</svg>
) : null}
</span>
{showName ? <span className="who__name">{label}</span> : null} {showName ? <span className="who__name">{label}</span> : null}
{a ? <span className="who__sr"> (AI)</span> : null}
</> </>
) )

View File

@ -316,6 +316,12 @@ export default function Lead() {
['Customer', pick(['customer_name', 'entity_name', 'mobile', 'email'])], ['Customer', pick(['customer_name', 'entity_name', 'mobile', 'email'])],
[line === 'sme' ? 'The risk' : 'The vehicle', pick(GLANCE_RISK[line])], [line === 'sme' ? 'The risk' : 'The vehicle', pick(GLANCE_RISK[line])],
['Documents', pick(docKeys)], ['Documents', pick(docKeys)],
// THE CALL IS THE ONE STEP NOBODY CAN REPLAY FROM THE FIELDS. Everything
// else on this lead was typed or derived; this happened out loud, and
// until now its outcome sat in the full file with the source channel while
// the words themselves were reachable only from a button halfway down the
// trail. It gets its own card whenever a call has actually happened.
['The call', pick(['contact_outcome', 'outreach_window', 'contact_notes'])],
['This stage', pick(GLANCE_STAGE[stage?.uid] || [])], ['This stage', pick(GLANCE_STAGE[stage?.uid] || [])],
].filter(([, rows]) => rows.length) ].filter(([, rows]) => rows.length)
@ -727,6 +733,15 @@ export default function Lead() {
</div> </div>
))} ))}
</dl> </dl>
{title === 'The call' && row.call_transcript ? (
<button type="button" className="glance__act" onClick={() => setShowCall(true)}>
<svg viewBox="0 0 16 16" aria-hidden="true">
<path d="M3.4 2.8h2.4l1.2 3-1.5 1a7.5 7.5 0 0 0 3.7 3.7l1-1.5 3 1.2v2.4a1 1 0 0 1-1.1 1A10.6 10.6 0 0 1 2.4 3.9a1 1 0 0 1 1-1.1Z"
fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinejoin="round" />
</svg>
Read what was actually said
</button>
) : null}
</section> </section>
))} ))}
</div> </div>

View File

@ -1534,6 +1534,30 @@
.glance .clamp__more { margin-top: 3px; font-size: 0.7rem; } .glance .clamp__more { margin-top: 3px; font-size: 0.7rem; }
/* An action that belongs to one card, not to the panel. Quiet until hovered:
the notes above it are the answer most of the time, and the transcript is
the follow-up question. */
.glance__act {
display: inline-flex;
align-items: center;
gap: 6px;
margin-top: 10px;
padding: 5px 10px 5px 8px;
border: 1px solid var(--zk-line);
border-radius: var(--r-pill);
background: var(--zk-white);
cursor: pointer;
font: inherit;
font-size: 0.74rem;
font-weight: 500;
color: var(--zk-blue-dark);
transition: background var(--t-fast), border-color var(--t-fast);
}
.glance__act:hover { background: var(--zk-tint-blue); border-color: var(--zk-blue-light); }
.glance__act:focus-visible { outline: 2px solid var(--zk-blue); outline-offset: 1px; }
.glance__act svg { width: 13px; height: 13px; flex: none; }
/* HIDING IS ONLY SAFE WHILE IT STAYS FINDABLE, so this names the number it is /* HIDING IS ONLY SAFE WHILE IT STAYS FINDABLE, so this names the number it is
holding rather than saying "more". Full width and on the panel's own floor: holding rather than saying "more". Full width and on the panel's own floor:
a link tucked into the header would be the discoverability problem that a link tucked into the header would be the discoverability problem that