fix(ui): preserve Pi notice severity and compact summaries

Label error severity consistently and keep one strongest notice category in the collapsed activity summary while preserving the individual expanded messages.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-05 13:09:08 -05:00
1 parent 203343c9ea
commit b8a59b259c
5 files changed
+47 -4

No files matched your search

@@ -235,6 +235,7 @@ describe("TaskChatProtocolActivityRow", () => {
["info", "informational", "Provider update", "lucide-info"],
["warning", "informational", "Warning", "lucide-triangle-alert"],
["error", "failed", "Error", "lucide-triangle-alert"],
["error", "informational", "Error", "lucide-triangle-alert"],
["info", "failed", "Error", "lucide-triangle-alert"],
] as const)("renders %s/%s notice severity without hiding its summary", (severity, status, label, icon) => {
const summary = "Pi estimates this turn at $0.000617. Billing cost is unverified.";
@@ -22,6 +22,7 @@ import {
protocolActivityIsRunning,
protocolActivityLabel,
protocolActivityPresentation,
providerNoticeSeverity,
} from "./task-chat-activity-presentation";
const COMPACT_RESEARCH_RESULT_LIMIT = 5;
@@ -281,6 +282,7 @@ export function TaskChatProtocolActivityRow({ item }: { item: TaskChatProtocolIt
if (!presentation) return null;
if (item.surface === "provider_activity" && item.family === "provider_notice") {
const NoticeIcon = presentation.icon;
const severity = providerNoticeSeverity(item);
const summary = item.summary
?? item.details.find((entry) => entry.label === "Summary")?.value
?? "The provider reported a notice without a message.";
@@ -288,7 +290,7 @@ export function TaskChatProtocolActivityRow({ item }: { item: TaskChatProtocolIt
<div className="flex min-w-0 flex-col gap-1.5 py-1 text-xs" data-testid="task-chat-protocol-activity-row" data-activity-family="provider_notice">
<div className="flex items-center gap-2 text-muted-foreground">
<NoticeIcon className="h-3.5 w-3.5 shrink-0" aria-hidden data-testid="task-chat-protocol-activity-icon" />
<span className="font-medium">{item.status === "failed" ? "Error" : item.details.find(detail => detail.label === "Severity")?.value === "info" ? "Provider update" : "Warning"}</span>
<span className="font-medium">{severity === "error" ? "Error" : severity === "info" ? "Provider update" : "Warning"}</span>
</div>
<p className="min-w-0 whitespace-pre-wrap break-words text-foreground">{summary}</p>
{item.details.some(detail => detail.label !== "Summary") ? (
@@ -173,4 +173,17 @@ describe("completedActivitySummary", () => {
}]).icon).toBe(AlertTriangle);
});
it("groups distinct notices and keeps a later error visible beside actual work", () => {
const notices = ["info", "info", "warning", "error"].map((severity, index) => ({
...provider("provider_notice", "informational"),
id: `notice-${index}`,
summary: `Distinct provider message ${index}`,
details: [{ label: "Severity", value: severity }],
}));
const result = completedActivitySummary([tool("bash"), ...notices]);
expect(result.label).toBe("Provider error reported, ran commands");
expect(result.fullLabel).toBe(result.label);
expect(result.label).not.toContain("Distinct provider message");
});
});
@@ -5,7 +5,7 @@ import {
type ToolFamily,
type ToolIcon,
} from "./tool-taxonomy";
import { protocolActivityPresentation } from "./task-chat-activity-presentation";
import { protocolActivityPresentation, providerNoticeSeverity } from "./task-chat-activity-presentation";
type Activity = TaskChatActivityPhaseItem["items"][number];
const labels: Record<ToolFamily, string> = {
@@ -42,6 +42,12 @@ export function completedActivitySummary(items: Activity[]) {
completed: boolean;
order: number;
}> = [];
const notices: Array<{
summary: string | undefined;
severity: "info" | "warning" | "error";
icon: ToolIcon;
order: number;
}> = [];
for (const [order, item] of items.entries()) {
if (item.kind === "tool") {
const p = toolActivityPresentation({
@@ -71,7 +77,7 @@ export function completedActivitySummary(items: Activity[]) {
// Reuse only the normalized notice text already available in the row.
const summary = item.summary?.trim()
|| item.details.find((entry) => entry.label === "Summary")?.value.trim();
add(summary || "Received a provider update", p.icon, order);
notices.push({ summary, severity: providerNoticeSeverity(item), icon: p.icon, order });
continue;
}
const family =
@@ -100,6 +106,21 @@ export function completedActivitySummary(items: Activity[]) {
}
}
}
if (notices.length) {
const strongest = notices.find((notice) => notice.severity === "error")
?? notices.find((notice) => notice.severity === "warning")
?? notices[0]!;
const label = notices.length === 1
? strongest.summary || "Received a provider update"
: strongest.severity === "error"
? "Provider error reported"
: strongest.severity === "warning"
? "Provider warning reported"
: "Received provider updates";
// One notice category leaves room for actual work. Keep warnings and errors
// visible when the compact label truncates other activity categories.
add(label, strongest.icon, strongest.severity === "info" ? notices[0]!.order : -1);
}
const completedFamilies = new Set(
tools.filter((tool) => tool.completed).map((tool) => tool.family),
);
@@ -41,6 +41,12 @@ export interface TaskChatActivityPresentation {
detail?: string;
}
export function providerNoticeSeverity(item: TaskChatProviderActivityItem): "info" | "warning" | "error" {
const severity = item.details.find((entry) => entry.label === "Severity")?.value;
if (item.status === "failed" || severity === "error") return "error";
return severity === "info" ? "info" : "warning";
}
function providerDetail(item: TaskChatProviderActivityItem, ...labels: string[]): string | undefined {
return item.details.find((entry) => labels.includes(entry.label))?.value;
}
@@ -136,7 +142,7 @@ export function providerActivityPresentation(item: TaskChatProviderActivityItem)
case "wait":
return { icon: Clock3, runningLabel: "Waiting", completedLabel: "Finished waiting", failedLabel: "Wait failed", interruptedLabel: "Wait stopped", detail };
case "provider_notice":
return { icon: item.status !== "failed" && providerDetail(item, "Severity") === "info" ? Info : AlertTriangle, runningLabel: "Provider notice", completedLabel: "Provider notice", failedLabel: "Provider error", interruptedLabel: "Provider notice", detail };
return { icon: providerNoticeSeverity(item) === "info" ? Info : AlertTriangle, runningLabel: "Provider notice", completedLabel: "Provider notice", failedLabel: "Provider error", interruptedLabel: "Provider notice", detail };
}
}