From 15135718fbb0ae567a58491cf273330771d95e05 Mon Sep 17 00:00:00 2001 From: dev_agent Date: Thu, 8 Oct 2026 09:27:28 -0400 Subject: [PATCH] Drop the Shopping list count when a Supply run refetch fails (Feature 17) The Plan page read run.data?.needed_count, which TanStack Query keeps after a later refetch fails, so the link showed a stale count beside the error banner. Show the count only on a successful read, and test a successful read followed by a failed refetch. Co-Authored-By: Claude Opus 5.5 --- web/src/plan/PlanPage.test.tsx | 27 +++++++++++++++++++++++++++ web/src/plan/PlanPage.tsx | 7 +++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/web/src/plan/PlanPage.test.tsx b/web/src/plan/PlanPage.test.tsx index bdfdd47..6f4ec8c 100644 --- a/web/src/plan/PlanPage.test.tsx +++ b/web/src/plan/PlanPage.test.tsx @@ -795,6 +795,33 @@ describe('PlanPage', () => { expect(await screen.findByText('Opened /plan/shopping?week=2026-10-05')).toBeInTheDocument() }) + it("drops the count when reading the week's shopping list again fails, showing the failure with Retry", async () => { + let failing = false + stubServer({ + [`GET ${THIS_WEEK}`]: () => jsonResponse(200, planBody('draft')), + 'GET /api/v1/shopping/2026-10-05': () => (failing ? errorResponse(500, 'internal') : jsonResponse(200, supplyRun(19))), + }) + await renderPage() + expect(await screen.findByRole('link', { name: 'Shopping list · 19' })).toBeInTheDocument() + + // Coming back to the page once the Supply run is stale reads it again, + // and that read fails. + failing = true + vi.setSystemTime(new Date('2026-10-07T16:01:00Z')) + act(() => { + window.dispatchEvent(new Event('visibilitychange')) + }) + const alert = await screen.findByRole('alert') + expect(alert).toHaveTextContent("Couldn't load this week's shopping list. internal message") + expect(screen.getByRole('link', { name: 'Shopping list' })).toHaveAttribute('href', '/plan/shopping?week=2026-10-05') + expect(screen.queryByRole('link', { name: 'Shopping list · 19' })).not.toBeInTheDocument() + // Retry reads it again, and the count is back. + failing = false + fireEvent.click(within(alert).getByRole('button', { name: 'Retry' })) + expect(await screen.findByRole('link', { name: 'Shopping list · 19' })).toBeInTheDocument() + expect(screen.queryByRole('alert')).not.toBeInTheDocument() + }) + it('links to the Supply run with no count while the week has no shopping list', async () => { const fetchMock = stubServer({ [`GET ${THIS_WEEK}`]: () => jsonResponse(200, planBody('draft')) }) await renderPage() diff --git a/web/src/plan/PlanPage.tsx b/web/src/plan/PlanPage.tsx index cdb4f32..8f97f99 100644 --- a/web/src/plan/PlanPage.tsx +++ b/web/src/plan/PlanPage.tsx @@ -378,8 +378,11 @@ function WeekBoard({ ) // A failed read of the week's Supply run is shown, with Retry, after the // Draft board's Shopping list link or under the plan manager's board; the - // Shopping list action still opens the Supply run. - const shoppingList = + // Shopping list action still opens the Supply run. The count is that of a + // successful read only: a failed refetch keeps the earlier Supply run in + // run.data, and its count is then no longer current. + const neededCount = run.isSuccess ? run.data?.needed_count : undefined + const shoppingList = const supplyRunFailure = run.isError && ( void run.refetch()}>Couldn't load this week's shopping list. {run.error.message} )