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:
parent
49d5443c60
commit
b27141b797
@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@ -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}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user