Shopping item toggles made while offline (or when the PATCH fails with a network error) are applied locally, persisted in IndexedDB/localStorage with their order, shown with a subtle "will sync" marker, and replayed in order when connectivity returns; a 409 (list archived) on replay discards the queued change and shows a notice. The shopping list query is also persisted so the list opens offline. Vitest tests with mocked online/offline events and fetch cover: offline toggle → persisted → online replay in order; replay surviving a page reload; 409 on replay. Typecheck, test and build pass.
Scope
Web only. No Go change, no migration, no new dependency. The Supply archive screen (3cf3a22a) is untouched.
Points for review
The queue extends the existing toggle path (useToggles' serial mutation scope, shared as toggleOptions) rather than adding a second toggle mechanism; ordering is preserved and replayed in order, including after a page reload.
Which failures queue versus surface: a network failure queues; 400/404/409 and other error responses are not queued as though offline. A 409 on replay discards that change, puts the count back and shows a notice, without retrying forever.
The persisted store (web/src/shopping/offline.ts, localStorage mealquest.supply-run) holds the schedule, the saved Supply runs and the ordered queue, and reports storage failures rather than losing a change quietly (no silent fallback).
The "will sync" marker is distinct from Archived and from a failed toggle; the counts stay consistent while offline and when a queued change is discarded.
useSupplyRunSync/SupplyRunSync mounted in App.tsx drives the wait-for-connectivity replay: confirm it cannot double-replay, cannot replay out of order, and does not run while the user is mid-toggle.
The list opens offline from the saved run; the page keeps showing the saved list on a network error and uses the saved schedule when settings cannot be read offline.
Test adequacy: the three named scenarios plus ordering of multiple queued toggles and the non-network case; 48 files / 841 tests (836 before).
cd web && npm run typecheck PASS; npm test PASS 48 files / 841 tests; npm run build PASS
npm run lint:colors no hex colour literals outside tokens.css
Only web/ files changed; working tree clean; Go tree untouched
## Task
Maestro task `a9689163-95e2-4ec7-8da5-3ef180f14898` — "Offline toggle queue" (Feature 17, task 6 of 7).
## Done-condition
Shopping item toggles made while offline (or when the PATCH fails with a network error) are applied locally, persisted in IndexedDB/localStorage with their order, shown with a subtle "will sync" marker, and replayed in order when connectivity returns; a 409 (list archived) on replay discards the queued change and shows a notice. The shopping list query is also persisted so the list opens offline. Vitest tests with mocked online/offline events and fetch cover: offline toggle → persisted → online replay in order; replay surviving a page reload; 409 on replay. Typecheck, test and build pass.
## Scope
Web only. No Go change, no migration, no new dependency. The Supply archive screen (`3cf3a22a`) is untouched.
## Points for review
- The queue extends the existing toggle path (`useToggles`' serial mutation scope, shared as `toggleOptions`) rather than adding a second toggle mechanism; ordering is preserved and replayed in order, including after a page reload.
- Which failures queue versus surface: a network failure queues; 400/404/409 and other error responses are **not** queued as though offline. A 409 on replay discards that change, puts the count back and shows a notice, without retrying forever.
- The persisted store (`web/src/shopping/offline.ts`, localStorage `mealquest.supply-run`) holds the schedule, the saved Supply runs and the ordered queue, and reports storage failures rather than losing a change quietly (no silent fallback).
- The "will sync" marker is distinct from Archived and from a failed toggle; the counts stay consistent while offline and when a queued change is discarded.
- `useSupplyRunSync`/`SupplyRunSync` mounted in `App.tsx` drives the wait-for-connectivity replay: confirm it cannot double-replay, cannot replay out of order, and does not run while the user is mid-toggle.
- The list opens offline from the saved run; the page keeps showing the saved list on a network error and uses the saved schedule when settings cannot be read offline.
- Test adequacy: the three named scenarios plus ordering of multiple queued toggles and the non-network case; 48 files / 841 tests (836 before).
## Evidence (orchestrator-run gate at b7608d0)
- `cd web && npm run typecheck` PASS; `npm test` PASS 48 files / 841 tests; `npm run build` PASS
- `npm run lint:colors` no hex colour literals outside tokens.css
- Only `web/` files changed; working tree clean; Go tree untouched
A toggle made while the server cannot be reached (the browser is offline,
or its PATCH fails with a network error) now stays pending in the existing
serial toggle mutation, laid over the list with a "will sync" marker, and
is sent once the network status says the server is reachable again. Every
toggle is saved in order in localStorage until it settles, and
SupplyRunSync replays the saved ones after a reload. Any answer from the
server settles a toggle: a 409 archived discards it, puts the count back
and shows the refusal; other refusals surface as before and are never
queued.
The Supply run and the household schedule are saved on the device too, so
the Supply run screen opens offline from the saved list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every Supply run toggle, made here or replayed from the saved queue, now
runs in one mutation scope whatever its week, so a replay spanning weeks
is sent strictly in the stored order and never overlaps a live toggle.
The "will sync" marker latches on a toggle that was pending while the
server could not be reached and stays until that toggle settles, rather
than vanishing as soon as the network returns while its replay is still
in flight.
The Supply run screen reads the saved schedule once per mount instead of
on every render, so a corrupt saved store offline reports its problem
once instead of looping.
Replay is documented as at-least-once: a toggle sets needed to a fixed
value, so a duplicate sent after a reload leaves the item and the counts
unchanged. Tests cover a two-week queue replayed in order one PATCH at a
time, a duplicate replay, corrupt storage offline and a delayed replay
answer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Each try of a toggle (GET /api/v1/me and the PATCH) has a 10 s deadline:
past it the PATCH is aborted, the network marked failed, and the toggle
stays pending at the head of the queue to be tried again.
- A toggle waiting while the server cannot be reached is tried again when
the network status says it can be, or after a wait doubling from 1 s up
to 30 s, so recovery never depends on a browser online event.
- The "will sync" keys live in a page-lifetime store in offline.ts, not in
a component ref, so leaving and reopening the Supply run screen keeps
the marker until the toggle settles.
- ApiRequest takes an optional abort signal; the client sets no deadline.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- loadMe takes an optional abort signal; AuthBootstrap calls it with none.
- Each toggle try loads GET /api/v1/me itself under its deadline when the
session is not loaded, instead of joining a /me query already in flight
that may never answer.
- The /me refresh a csrf 403 makes is bounded by the request's signal: a
request whose signal aborts stops waiting at once and the refresh is no
longer shared, so the next csrf 403 starts a fresh /me. Requests without
a signal still share it, and it is aborted only once no request waits.
- Tests: a hung /me and a hung csrf refresh during a toggle exchange, both
recovered on the next try in order; the client's refresh sharing and abort.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Task
Maestro task
a9689163-95e2-4ec7-8da5-3ef180f14898— "Offline toggle queue" (Feature 17, task 6 of 7).Done-condition
Shopping item toggles made while offline (or when the PATCH fails with a network error) are applied locally, persisted in IndexedDB/localStorage with their order, shown with a subtle "will sync" marker, and replayed in order when connectivity returns; a 409 (list archived) on replay discards the queued change and shows a notice. The shopping list query is also persisted so the list opens offline. Vitest tests with mocked online/offline events and fetch cover: offline toggle → persisted → online replay in order; replay surviving a page reload; 409 on replay. Typecheck, test and build pass.
Scope
Web only. No Go change, no migration, no new dependency. The Supply archive screen (
3cf3a22a) is untouched.Points for review
useToggles' serial mutation scope, shared astoggleOptions) rather than adding a second toggle mechanism; ordering is preserved and replayed in order, including after a page reload.web/src/shopping/offline.ts, localStoragemealquest.supply-run) holds the schedule, the saved Supply runs and the ordered queue, and reports storage failures rather than losing a change quietly (no silent fallback).useSupplyRunSync/SupplyRunSyncmounted inApp.tsxdrives the wait-for-connectivity replay: confirm it cannot double-replay, cannot replay out of order, and does not run while the user is mid-toggle.Evidence (orchestrator-run gate at
b7608d0)cd web && npm run typecheckPASS;npm testPASS 48 files / 841 tests;npm run buildPASSnpm run lint:colorsno hex colour literals outside tokens.cssweb/files changed; working tree clean; Go tree untouchedIndependent Codex gpt-6-sol review: CHANGES_REQUIRED for this exact commit.
Independent Codex gpt-6-sol review: CHANGES_REQUIRED for this exact commit.
Independent Codex gpt-6-sol review: CHANGES_REQUIRED for this exact commit.
Independent Codex gpt-6-sol review: APPROVED for this exact commit.