Skip to content

Commit 3106ce7

Browse files
committed
fix(webapp): one spinner at a time — the investigation card's progress pill wins
The card's progress line is now the same pill the in-flight tools use, and while an in_progress investigation card is on screen the concurrent tool pill is suppressed.
1 parent 7abce81 commit 3106ce7

3 files changed

Lines changed: 35 additions & 8 deletions

File tree

apps/webapp/app/components/dashboard-agent/AgentChart.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import type { OutputColumnMetadata } from "@internal/clickhouse";
22
import type { ChartBlock } from "@internal/dashboard-agent";
3-
import { isTriggerUri, type AgentIntent, type ChartAction } from "@internal/dashboard-agent-contracts";
3+
import {
4+
isTriggerUri,
5+
type AgentIntent,
6+
type ChartAction,
7+
} from "@internal/dashboard-agent-contracts";
48
import { useEffect, useState } from "react";
59
import { QueryResultsChart } from "~/components/code/QueryResultsChart";
610
import type { ChartConfiguration } from "~/components/metrics/QueryWidget";

apps/webapp/app/components/dashboard-agent/DashboardAgentMessages.tsx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,11 @@ function withoutSupersededInvestigations(
163163
* Citations are handled a level up, where a run of them can be grouped into one
164164
* row.
165165
*/
166-
function renderDashboardPart(part: UIMessage["parts"][number], i: number) {
166+
function renderDashboardPart(
167+
part: UIMessage["parts"][number],
168+
i: number,
169+
options?: { suppressPendingPill?: boolean }
170+
) {
167171
const p = part as {
168172
type: string;
169173
text?: string;
@@ -179,6 +183,9 @@ function renderDashboardPart(part: UIMessage["parts"][number], i: number) {
179183

180184
if (type.startsWith("tool-")) {
181185
if (IN_FLIGHT_TOOL_STATES.has(p.state ?? "")) {
186+
// One spinner at a time: an in_progress investigation card in this turn
187+
// already shows its own progress pill.
188+
if (options?.suppressPendingPill) return null;
182189
return <ChatPendingTool key={i} label={`${toolPendingLabel(type.slice(5))}…`} />;
183190
}
184191
if (p.state === "output-error") return renderPart(part, i);
@@ -267,6 +274,22 @@ const DashboardAgentTurn = memo(function DashboardAgentTurn({
267274
const parts = message.parts ?? [];
268275
if (parts.length === 0) return null;
269276

277+
// An in_progress investigation card carries its own live pill (its
278+
// `progress` line), so a concurrent tool pill would put two spinners on
279+
// screen — the card's, being the more specific, wins.
280+
const hasLiveInvestigationCard = parts.some((part, i) =>
281+
withoutSupersededInvestigations(
282+
blocksFor(part) ?? [],
283+
`${message.id}:${i}`,
284+
investigationWinners
285+
).some(
286+
(block) =>
287+
(block as { type?: string; outcome?: unknown; investigation?: { outcome?: string } })
288+
.type === "investigation" &&
289+
(block as { investigation?: { outcome?: string } }).investigation?.outcome === "in_progress"
290+
)
291+
);
292+
270293
const body: React.ReactNode[] = [];
271294
for (let i = 0; i < parts.length; i++) {
272295
const part = parts[i]!;
@@ -306,7 +329,7 @@ const DashboardAgentTurn = memo(function DashboardAgentTurn({
306329
continue;
307330
}
308331

309-
body.push(renderDashboardPart(part, i));
332+
body.push(renderDashboardPart(part, i, { suppressPendingPill: hasLiveInvestigationCard }));
310333
}
311334

312335
// A wake narration is identified by the message id the agent wrote it under,

apps/webapp/app/components/dashboard-agent/InvestigationCard.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import {
3737
SeverityBadge,
3838
VerdictBadge,
3939
} from "./agent-badges";
40-
import { ChatProgress } from "./chat-layout";
40+
import { ChatPendingTool } from "./chat-layout";
4141
import type { ResolvedUri } from "./ReportView";
4242

4343
const SEVERITY_LABELS: Record<InvestigationSeverity, string> = {
@@ -245,10 +245,10 @@ export function InvestigationCard({
245245
</div>
246246
</div>
247247
{/* Progress lives outside the card, on the left — the same line the chat
248-
uses for "Working…" and in-flight tools. `ChatProgress` carries the
249-
transcript's alignment itself, so it lines up with the card above it
250-
instead of hugging the card's border. */}
251-
{inProgress ? <ChatProgress>{investigation.progress ?? "Working…"}</ChatProgress> : null}
248+
uses for in-flight tools — the same pill, so the transcript never
249+
shows two spinner styles at once. It carries the transcript's
250+
alignment itself, lining up with the card above it. */}
251+
{inProgress ? <ChatPendingTool label={investigation.progress ?? "Working…"} /> : null}
252252
</div>
253253
);
254254
}

0 commit comments

Comments
 (0)