From c998a57b425fdcbcbf8a14b95c8eca17b0cc367f Mon Sep 17 00:00:00 2001 From: Dotta Date: Fri, 2 Oct 2026 16:12:33 -0500 Subject: [PATCH] fix(ui): explain connection pool quota waits Co-Authored-By: Paperclip --- ui/src/components/IssueMonitorBanner.test.tsx | 15 +++++++++++++++ ui/src/components/IssueMonitorBanner.tsx | 10 ++++++++++ 2 files changed, 25 insertions(+) diff --git a/ui/src/components/IssueMonitorBanner.test.tsx b/ui/src/components/IssueMonitorBanner.test.tsx index 412581a6b7e..bd4430501c3 100644 --- a/ui/src/components/IssueMonitorBanner.test.tsx +++ b/ui/src/components/IssueMonitorBanner.test.tsx @@ -158,6 +158,21 @@ describe("IssueMonitorBanner / IssueMonitorComposerStrip rendering", () => { flushSync(() => root.unmount()); }); + it("explains pool exhaustion and retained account affinity on both waiting surfaces", () => { + const issue = { + status: "in_progress", scheduledRetry: { status: "scheduled_retry", scheduledRetryReason: "ai_connection_pool_wait", scheduledRetryAt: new Date(NOW.getTime() + 60_000).toISOString(), scheduledRetryAttempt: 1 }, + } as Issue; + const root = createRoot(container); + flushSync(() => root.render(<>)); + expect(container.textContent).toContain("Pool exhausted"); + expect(container.textContent).toContain("Usage recheck in 1m"); + expect(container.textContent).toContain("This task keeps its account"); + expect(container.textContent).toContain("The selected account stays pinned."); + expect(container.textContent).not.toContain("Attempt 1"); + expect(container.querySelector("button")).not.toBeNull(); + flushSync(() => root.unmount()); + }); + it("renders the banner with a working Check now button while waiting", () => { const onCheckNow = vi.fn(); expect(hasVisibleMonitorSurface(issueWithMonitor(new Date(NOW.getTime() + 2 * 60 * 60_000).toISOString()))).toBe(true); diff --git a/ui/src/components/IssueMonitorBanner.tsx b/ui/src/components/IssueMonitorBanner.tsx index de0e83ff470..ac7fdc4a3c1 100644 --- a/ui/src/components/IssueMonitorBanner.tsx +++ b/ui/src/components/IssueMonitorBanner.tsx @@ -81,6 +81,16 @@ export function buildMonitorSurfaceCopy( const eta = formatMonitorEta(derived.nextCheckAt, now); // "in 2h 12m" | "due now" | "overdue by 18m" const absolute = formatMonitorAbsolute(derived.nextCheckAt, {}, now); // local time, e.g. "Today, 4:08 PM" + if (derived.source === "scheduled-retry" && scheduledRetryReason === "ai_connection_pool_wait") { + return { + bannerTitle: "Pool exhausted", + stripTitle: "Pool exhausted", + bannerMeta: [`Usage recheck ${eta} · ${absolute} (your time)`, "This task keeps its account and resumes when usage permits."], + stripMeta: [`Usage recheck ${eta} · ${absolute}`, "The selected account stays pinned."], + tone: "info", + }; + } + const isScheduledRetryOnly = derived.source === "scheduled-retry"; let bannerTitle: string;