From ad8f869278aa983a4b20644a3c2f6924efb72894 Mon Sep 17 00:00:00 2001 From: Dotta Date: Thu, 20 Aug 2026 15:57:02 +0000 Subject: [PATCH] fix(apps): tolerate stale activity API Use the omitted-window contract for all-time activity so mixed-version restarts do not reject the page, and remove the obsolete audit footer. Co-Authored-By: Paperclip --- ui/src/api/tools.test.ts | 34 ++++++++++++++++++++++++++++ ui/src/api/tools.ts | 5 +++- ui/src/pages/tools/AuditTab.test.tsx | 4 ++-- ui/src/pages/tools/AuditTab.tsx | 4 ---- 4 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 ui/src/api/tools.test.ts diff --git a/ui/src/api/tools.test.ts b/ui/src/api/tools.test.ts new file mode 100644 index 00000000000..4d0a3fb0d31 --- /dev/null +++ b/ui/src/api/tools.test.ts @@ -0,0 +1,34 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const mockApi = vi.hoisted(() => ({ + get: vi.fn(), +})); + +vi.mock("./client", () => ({ + api: mockApi, +})); + +import { toolsApi } from "./tools"; + +describe("toolsApi.listActivity", () => { + beforeEach(() => { + mockApi.get.mockReset(); + mockApi.get.mockResolvedValue({ events: [], nextCursor: null }); + }); + + it("uses the omitted-window contract for all-time activity", async () => { + await toolsApi.listActivity("company-1", { window: "all", limit: 50 }); + + expect(mockApi.get).toHaveBeenCalledWith( + "/tool-gateway/audit?companyId=company-1&limit=50", + ); + }); + + it("sends bounded activity windows explicitly", async () => { + await toolsApi.listActivity("company-1", { window: "30d", limit: 50 }); + + expect(mockApi.get).toHaveBeenCalledWith( + "/tool-gateway/audit?companyId=company-1&window=30d&limit=50", + ); + }); +}); diff --git a/ui/src/api/tools.ts b/ui/src/api/tools.ts index 9aa96583ff7..a6bc260e4c5 100644 --- a/ui/src/api/tools.ts +++ b/ui/src/api/tools.ts @@ -470,7 +470,10 @@ export const toolsApi = { if (params.app) search.set("app", params.app); if (params.agent) search.set("agent", params.agent); if (params.outcome) search.set("outcome", params.outcome); - if (params.window) search.set("window", params.window); + // Omitting the window is the API's canonical all-time request. This also + // keeps the page usable during a rolling restart against an older server + // that does not recognize the newer explicit `all` value. + if (params.window && params.window !== "all") search.set("window", params.window); if (params.search) search.set("search", params.search); if (params.cursor) search.set("cursor", params.cursor); search.set("limit", String(params.limit ?? 50)); diff --git a/ui/src/pages/tools/AuditTab.test.tsx b/ui/src/pages/tools/AuditTab.test.tsx index 72f14c9c77b..d9684e49621 100644 --- a/ui/src/pages/tools/AuditTab.test.tsx +++ b/ui/src/pages/tools/AuditTab.test.tsx @@ -125,14 +125,14 @@ describe("AuditTab", () => { }); } - it("renders humanized sentences, the outcome chip, and the footer note", async () => { + it("renders humanized sentences and the outcome chip without the old footer note", async () => { await render(); expect(container.textContent).toContain("Fable"); expect(container.textContent).toContain("Send Email"); expect(container.textContent).toContain("Gmail"); expect(container.textContent).toContain("Blocked"); - expect(container.textContent).toContain("Recorded by Paperclip — entries can't be edited."); + expect(container.textContent).not.toContain("Recorded by Paperclip — entries can't be edited."); expect(listActivityMock).toHaveBeenCalledWith("company-1", expect.objectContaining({ window: "all" })); // Vocabulary gate: no raw tool ID or ops terms in the sentence list. expect(container.textContent).not.toContain("mail:send_email"); diff --git a/ui/src/pages/tools/AuditTab.tsx b/ui/src/pages/tools/AuditTab.tsx index aef757194cf..805c2cf4062 100644 --- a/ui/src/pages/tools/AuditTab.tsx +++ b/ui/src/pages/tools/AuditTab.tsx @@ -514,10 +514,6 @@ export function AuditTab({ companyId }: { companyId: string }) { ) : null} - -

- Recorded by Paperclip — entries can't be edited. Sensitive values are never stored. -

); }