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}
)