fix(ui): show provider notice summaries with informational severity

Keep existing normalized notice text visible in completed activity summaries and use an info icon for informational notices while preserving warning and error presentation.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-02 05:28:04 -05:00
1 parent 8559cdf370
commit a32ce172a4
5 files changed
+60 -4

No files matched your search

@@ -231,4 +231,21 @@ describe("TaskChatProtocolActivityRow", () => {
expect(row?.querySelector("details")).toBeNull();
expect(row?.querySelector('[aria-expanded]')).toBeNull();
});
it.each([
["info", "informational", "Provider update", "lucide-info"],
["warning", "informational", "Warning", "lucide-triangle-alert"],
["error", "failed", "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.";
render({
id: "notice", kind: "protocol", surface: "provider_activity", family: "provider_notice",
eventType: "provider.notice.recorded", status, title: "Provider notice", summary,
details: [{ label: "Severity", value: severity }], steps: [], links: [], children: [],
});
expect(container.textContent).toContain(label);
expect(container.querySelector("p")?.textContent).toBe(summary);
expect(container.querySelector('[data-testid="task-chat-protocol-activity-icon"]')?.classList.contains(icon)).toBe(true);
});
});
@@ -1,6 +1,5 @@
import { useId, useState, type ReactNode } from "react";
import {
AlertTriangle,
Check,
ChevronRight,
Circle,
@@ -281,13 +280,14 @@ export function TaskChatProtocolActivityRow({ item }: { item: TaskChatProtocolIt
const presentation = protocolActivityPresentation(item);
if (!presentation) return null;
if (item.surface === "provider_activity" && item.family === "provider_notice") {
const NoticeIcon = presentation.icon;
const summary = item.summary
?? item.details.find((entry) => entry.label === "Summary")?.value
?? "The provider reported a notice without a message.";
return (
<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">
<AlertTriangle className="h-3.5 w-3.5 shrink-0" aria-hidden data-testid="task-chat-protocol-activity-icon" />
<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>
</div>
<p className="min-w-0 whitespace-pre-wrap break-words text-foreground">{summary}</p>
@@ -1,3 +1,4 @@
import { AlertTriangle, Info } from "lucide-react";
import { describe, expect, it } from "vitest";
import { completedActivitySummary } from "./completed-activity-summary";
import type {
@@ -141,4 +142,35 @@ describe("completedActivitySummary", () => {
completedActivitySummary([tool("mcp__github__get_pull_request")]).label,
).toBe("Used connected tools");
});
it("shows the preserved pricing estimate as informational, not a billing receipt", () => {
const summary = "Pi estimates this turn at $0.000617 from its model prices. Billing cost is unverified.";
expect(completedActivitySummary([{
...provider("provider_notice", "informational"), summary,
details: [{ label: "Severity", value: "info" }],
}])).toEqual({ label: summary, fullLabel: summary, icon: Info });
});
it.each(["warning", "error", undefined])("retains warning/error icons and safe notice text (%s)", (severity) => {
const summary = "Provider request needs attention.";
const result = completedActivitySummary([{
...provider("provider_notice", severity === "error" ? "failed" : "informational"),
details: [
{ label: "Summary", value: summary },
...(severity ? [{ label: "Severity", value: severity }] : []),
],
}]);
expect(result).toEqual({ label: summary, fullLabel: summary, icon: AlertTriangle });
});
it("uses a generic fallback without promoting arbitrary detail fields", () => {
expect(completedActivitySummary([{
...provider("provider_notice", "informational"), summary: " ",
details: [{ label: "Raw input", value: "private-command-or-path" }],
}]).label).toBe("Received a provider update");
});
it("does not hide a failed notice behind informational metadata", () => {
expect(completedActivitySummary([{
...provider("provider_notice", "failed"), summary: "Provider failed.",
details: [{ label: "Severity", value: "info" }],
}]).icon).toBe(AlertTriangle);
});
});
@@ -67,6 +67,13 @@ export function completedActivitySummary(items: Activity[]) {
} else {
const p = protocolActivityPresentation(item);
if (!p) continue;
if (item.surface === "provider_activity" && item.family === "provider_notice") {
// 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);
continue;
}
const family =
item.surface === "provider_activity" ? item.family : item.surface;
const label =
@@ -84,7 +91,6 @@ export function completedActivitySummary(items: Activity[]) {
safety: "Reviewed safety",
terminal: "Ran commands",
wait: "Waited",
provider_notice: "Received a provider update",
workspace_change: "Worked on files",
workspace_file: "Referenced files",
resource: "Added resources",
@@ -9,6 +9,7 @@ import {
FilePenLine,
FileText,
GitBranch,
Info,
ListChecks,
PackageCheck,
Search,
@@ -135,7 +136,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: AlertTriangle, runningLabel: "Provider notice", completedLabel: "Provider notice", failedLabel: "Provider error", interruptedLabel: "Provider notice", detail };
return { icon: item.status !== "failed" && providerDetail(item, "Severity") === "info" ? Info : AlertTriangle, runningLabel: "Provider notice", completedLabel: "Provider notice", failedLabel: "Provider error", interruptedLabel: "Provider notice", detail };
}
}