fix(views): transcript identity row reads as one sentence, not three peers

The row put the agent, the trigger word, and the person at near-equal
weight with two same-size avatars, so it read as a jumble of names with
"comment-triggered" floating between them.

- The agent is the sole primary identity (avatar + medium weight).
- Trigger + person collapse into one muted secondary unit set apart from
  the agent, ordered "<person> · <how>".
- Drop the person's avatar here (new AttributionBadge `hideAvatar`) so
  two same-size faces don't read as two agents; the name alone, being
  muted, can't be confused with the agent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Naiyuan Qing
2026-07-24 15:27:48 +08:00
parent 56b2e196a0
commit 868c8aadf0
2 changed files with 36 additions and 25 deletions

View File

@@ -516,34 +516,39 @@ export function AgentTranscriptDialog({
(status anchors the left), who ran it, why it exists (trigger),
and who's accountable. All diagnostics move to the ⓘ popover. */}
<div className="border-b px-4 py-3 shrink-0">
<div className="flex min-w-0 items-center gap-2.5">
<div className="flex min-w-0 items-center gap-3">
{statusBadge}
<div className="flex min-w-0 flex-1 items-center gap-x-3 overflow-hidden">
<div className="flex min-w-0 items-center gap-2">
{task.agent_id ? (
<ActorAvatar actorType="agent" actorId={task.agent_id} size="sm" enableHoverCard />
) : (
<div className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-info/10 text-info">
<Bot className="h-3 w-3" />
</div>
)}
<span className="truncate font-medium text-sm">
{agentName || agentInfo?.name || ""}
</span>
</div>
{/* Why this run exists: the trigger mechanism plus the
accountable human who's answerable for what the agent did. */}
<span className="shrink-0 text-xs text-muted-foreground">{triggerLabel}</span>
{/* Primary identity: the agent that ran this. It is the one
foreground entity — avatar + medium weight. */}
<div className="flex min-w-0 items-center gap-2">
{task.agent_id ? (
<ActorAvatar actorType="agent" actorId={task.agent_id} size="sm" enableHoverCard />
) : (
<div className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-info/10 text-info">
<Bot className="h-3 w-3" />
</div>
)}
<span className="truncate font-medium text-sm">
{agentName || agentInfo?.name || ""}
</span>
</div>
{/* Provenance, one muted secondary unit set apart from the agent:
who triggered the run and how — reads as "<person> · <how>",
not three peer entities. The person's avatar is dropped here so
two same-size faces don't read as two agents. */}
<div className="flex min-w-0 flex-1 items-center gap-x-1.5 overflow-hidden text-xs text-muted-foreground">
{hasTriggeredBy && (
<>
<FactDot />
<AttributionBadge
attribution={task.attribution}
variant="inline"
hideAvatar
className="min-w-0"
/>
<FactDot />
</>
)}
<span className="shrink-0">{triggerLabel}</span>
</div>
<div className="flex shrink-0 items-center gap-0.5">

View File

@@ -49,10 +49,14 @@ export function AttributionBadge({
attribution,
className,
variant = "badge",
hideAvatar = false,
}: {
attribution?: TaskAttribution;
className?: string;
variant?: "badge" | "avatar" | "inline";
/** Inline variant only: render the name without the avatar, so it does not
* compete with a nearby primary avatar (the transcript identity row). */
hideAvatar?: boolean;
}) {
const { t } = useT("issues");
if (!attribution) return null;
@@ -124,13 +128,15 @@ export function AttributionBadge({
className
)}
>
<ActorAvatar
name={name}
initials={initialsOf(name)}
avatarUrl={initiatorInline.avatar_url}
size="xs"
className="shrink-0"
/>
{!hideAvatar && (
<ActorAvatar
name={name}
initials={initialsOf(name)}
avatarUrl={initiatorInline.avatar_url}
size="xs"
className="shrink-0"
/>
)}
<span className="min-w-0 truncate">{name}</span>
</span>
}