Maestro task 677261bc-1a7c-4d96-b8f9-7601caf2186d — "Calendar settings screen and plan sync status" (Feature 16, task 5 of 5, the last one).
Done-condition
/settings/calendar shows "Connect Google Calendar" when disconnected (navigates to the connect endpoint), and when connected shows the selected calendar with a picker of writable calendars, "Connected · {calendar}" in teal-800, and Disconnect (with confirmation); reconnect_required shows a grape-toned banner with Reconnect. The plan screens show "Synced to Google Calendar {relative time}" (teal-800 with check), "Changes not pushed" when changed, and the "Looks good — push to Calendar" / "Push to Calendar" buttons call the push endpoint with progress and error states (google_reconnect_required → link to settings). Vitest tests cover each state.
The real production push and its screenshot were split out of this task into a separate owner-dependent production-verification task (Maestro 9a4d2601 in Feature 22), because it needs a real Google OAuth client and the deployed service. This task's scope is the code and the Vitest coverage of every state, and the production step is explicitly out of scope here.
Scope
15 files, all in web/src; no Go or backend change.
Points for review
Each state of the settings screen: disconnected (a link to the connect endpoint so it opens as a full page, no picker, no calendars request), connected ("Connected · {calendar}" in teal-800, a picker that saves on choice, Disconnect behind a confirmation), and reconnect_required (a grape banner carrying the server's last_sync_error, with Reconnect, no picker, and Google not called).
The plan screens: the push buttons now call the endpoint with "Pushing…" progress and the server's returned synced plan replacing the shown one; google_reconnect_required, no_calendar, not_connected and calendar_unavailable each show a message linking to /settings/calendar; plan_changed re-reads the plan and offers Retry; any other failure shows its message with Retry, and no push failure is swallowed.
The status line reuses Feature 14's existing status rendering and relative-time formatter rather than adding a second one (mobile now shows the line under its push button too) — confirm there is genuinely no duplicate formatter or second status path.
web/src/settings/api.ts lost 27 lines and web/src/settings/calendarApi.ts (new, 165 lines) holds the calendar API: confirm this is a move, not a fork, and that nothing else that read the old location broke (including settings/SettingsPage.tsx and its tests).
web/src/test/ApiProviders.tsx changed — a test helper; confirm it did not weaken any existing test.
a11y of the new screen (labels, the confirmation, focus, the banner), colour-token compliance (no hex literals — the lint passes), and anything that would only show up in a browser. The developer loaded the built app in headless Chrome for the connected and reconnect-required settings states and /plan at 390 and 1440 px, but did not click the push through in a browser; the click flows are covered by Vitest only, so say if that leaves a material gap.
## Task
Maestro task `677261bc-1a7c-4d96-b8f9-7601caf2186d` — "Calendar settings screen and plan sync status" (Feature 16, task 5 of 5, the last one).
## Done-condition
`/settings/calendar` shows "Connect Google Calendar" when disconnected (navigates to the connect endpoint), and when connected shows the selected calendar with a picker of writable calendars, "Connected · {calendar}" in teal-800, and Disconnect (with confirmation); `reconnect_required` shows a grape-toned banner with Reconnect. The plan screens show "Synced to Google Calendar {relative time}" (teal-800 with check), "Changes not pushed" when changed, and the "Looks good — push to Calendar" / "Push to Calendar" buttons call the push endpoint with progress and error states (`google_reconnect_required` → link to settings). Vitest tests cover each state.
**The real production push and its screenshot were split out of this task** into a separate owner-dependent production-verification task (Maestro `9a4d2601` in Feature 22), because it needs a real Google OAuth client and the deployed service. This task's scope is the code and the Vitest coverage of every state, and the production step is explicitly out of scope here.
## Scope
15 files, all in `web/src`; no Go or backend change.
## Points for review
- Each state of the settings screen: disconnected (a **link** to the connect endpoint so it opens as a full page, no picker, no calendars request), connected ("Connected · {calendar}" in teal-800, a picker that saves on choice, Disconnect behind a confirmation), and `reconnect_required` (a grape banner carrying the server's `last_sync_error`, with Reconnect, no picker, and Google not called).
- The plan screens: the push buttons now call the endpoint with "Pushing…" progress and the server's returned synced plan replacing the shown one; `google_reconnect_required`, `no_calendar`, `not_connected` and `calendar_unavailable` each show a message linking to /settings/calendar; `plan_changed` re-reads the plan and offers Retry; any other failure shows its message with Retry, and **no push failure is swallowed**.
- The status line reuses Feature 14's existing status rendering and relative-time formatter rather than adding a second one (mobile now shows the line under its push button too) — confirm there is genuinely no duplicate formatter or second status path.
- `web/src/settings/api.ts` lost 27 lines and `web/src/settings/calendarApi.ts` (new, 165 lines) holds the calendar API: confirm this is a move, not a fork, and that nothing else that read the old location broke (including `settings/SettingsPage.tsx` and its tests).
- `web/src/test/ApiProviders.tsx` changed — a test helper; confirm it did not weaken any existing test.
- a11y of the new screen (labels, the confirmation, focus, the banner), colour-token compliance (no hex literals — the lint passes), and anything that would only show up in a browser. The developer loaded the built app in headless Chrome for the connected and reconnect-required settings states and /plan at 390 and 1440 px, but did **not** click the push through in a browser; the click flows are covered by Vitest only, so say if that leaves a material gap.
## Evidence (orchestrator-run gate at 2b5350bd62808b6e4f9370d0311b73c821fbd083)
- `cd web && npm run typecheck` — PASS
- `cd web && npm test` — PASS, 46 files / 797 tests (was 45 / 764)
- `cd web && npm run build` — PASS (colour lint and dist-font check included)
- `npm run lint:colors` — no hex colour literals outside tokens.css
- Go tree untouched (`gofmt -l .` empty, `CGO_ENABLED=0 go build ./...` ok); working tree clean
Implementation commit: `2b5350bd62808b6e4f9370d0311b73c821fbd083`
/settings/calendar now shows the Google Calendar connection: "Connect
Google Calendar" (a full-page navigation to the connect endpoint) when not
connected; "Connected · {calendar}" in teal-800 with a picker of the
writable calendars, saved on choice, and Disconnect behind a confirmation
when connected; and a grape banner with Reconnect, no picker, when Google
has refused the grant. The settings list's Google Calendar row reads the
same connection instead of the placeholder endpoint.
The plan screens' "Looks good — push to Calendar" and "Push to Calendar"
buttons now POST /api/v1/plans/{weekStart}/push-calendar, showing progress
and storing the synced plan the server answers with. Failures only the
settings can fix (google_reconnect_required, no_calendar, not_connected,
calendar_unavailable) link to /settings/calendar; plan_changed reads the
plan again; any other failure offers Retry. The mobile Draft board shows
the existing sync status line under its push button.
Maestro task 677261bc-1a7c-4d96-b8f9-7601caf2186d.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A push refused with plan_changed now stays pending until the plan has been
read again, so Push and Retry stay disabled until the changed plan is on
screen and nothing unreviewed can be pushed. A test holds that read back and
checks that no second push is sent meanwhile, and that Retry pushes once it
lands.
The reconnect banner's reason and required action are now an alert, as
ErrorBanner's message is, so a calendar request that turns the screen to
reconnect required is announced.
Task: 677261bc-1a7c-4d96-b8f9-7601caf2186d
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A push to Calendar now lives in the query client's mutation cache, keyed by
week, as a swap does, instead of in the Draft board's own useMutation. A
board remounted by leaving the week and coming back therefore still sees a
push pending, including one held open by the plan_changed reread, and keeps
Push and Retry disabled until that read lands. The reread now refetches the
week's plan even while it is not shown, so a return finds the changed plan
in the cache rather than the stale one.
Tests leave the week and come back with the reread held open (no second
POST; the changed plan, then Retry, push once), and let the reread land
while the week is left (the board returns on the changed plan).
Task: 677261bc-1a7c-4d96-b8f9-7601caf2186d
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A plan_changed reread answered from the service worker's cache left the
old plan shown with Push and Retry open. Push now needs a plan read from
the server (Plan.cached, set from cacheFallbackHeader), and the push
failure's Retry reads the plan again while the plan shown is cached.
Settled swaps and pushes were kept for every week ever touched. When a
week is shown, the settled ones of every other week with none pending are
now removed; pending ones still survive leaving the week and coming back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shared cleanup of swap and push mutations also runs whenever one of
its kind settles, so an entry that settles while another week is shown is
no longer kept indefinitely under gcTime: Infinity. Pending mutations, and
the settled ones of a week that still has one pending, are never removed;
a left week's latest mutation, when it failed, stays until another week is
shown so coming straight back still shows the failure.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cleanup of settled swaps and pushes was subscribed only while a Draft
board was mounted, so one that settled after the household left Plan stayed
in the mutation cache with gcTime Infinity. keepWeekMutations is now
installed once on the query client's own mutation cache and never removed;
the hooks only mark which week is shown. Pending ones, and those an earlier
pending one needs, are still kept.
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
677261bc-1a7c-4d96-b8f9-7601caf2186d— "Calendar settings screen and plan sync status" (Feature 16, task 5 of 5, the last one).Done-condition
/settings/calendarshows "Connect Google Calendar" when disconnected (navigates to the connect endpoint), and when connected shows the selected calendar with a picker of writable calendars, "Connected · {calendar}" in teal-800, and Disconnect (with confirmation);reconnect_requiredshows a grape-toned banner with Reconnect. The plan screens show "Synced to Google Calendar {relative time}" (teal-800 with check), "Changes not pushed" when changed, and the "Looks good — push to Calendar" / "Push to Calendar" buttons call the push endpoint with progress and error states (google_reconnect_required→ link to settings). Vitest tests cover each state.The real production push and its screenshot were split out of this task into a separate owner-dependent production-verification task (Maestro
9a4d2601in Feature 22), because it needs a real Google OAuth client and the deployed service. This task's scope is the code and the Vitest coverage of every state, and the production step is explicitly out of scope here.Scope
15 files, all in
web/src; no Go or backend change.Points for review
reconnect_required(a grape banner carrying the server'slast_sync_error, with Reconnect, no picker, and Google not called).google_reconnect_required,no_calendar,not_connectedandcalendar_unavailableeach show a message linking to /settings/calendar;plan_changedre-reads the plan and offers Retry; any other failure shows its message with Retry, and no push failure is swallowed.web/src/settings/api.tslost 27 lines andweb/src/settings/calendarApi.ts(new, 165 lines) holds the calendar API: confirm this is a move, not a fork, and that nothing else that read the old location broke (includingsettings/SettingsPage.tsxand its tests).web/src/test/ApiProviders.tsxchanged — a test helper; confirm it did not weaken any existing test.Evidence (orchestrator-run gate at
2b5350bd62)cd web && npm run typecheck— PASScd web && npm test— PASS, 46 files / 797 tests (was 45 / 764)cd web && npm run build— PASS (colour lint and dist-font check included)npm run lint:colors— no hex colour literals outside tokens.cssgofmt -l .empty,CGO_ENABLED=0 go build ./...ok); working tree cleanImplementation commit:
2b5350bd62808b6e4f9370d0311b73c821fbd083/settings/calendar now shows the Google Calendar connection: "Connect Google Calendar" (a full-page navigation to the connect endpoint) when not connected; "Connected · {calendar}" in teal-800 with a picker of the writable calendars, saved on choice, and Disconnect behind a confirmation when connected; and a grape banner with Reconnect, no picker, when Google has refused the grant. The settings list's Google Calendar row reads the same connection instead of the placeholder endpoint. The plan screens' "Looks good — push to Calendar" and "Push to Calendar" buttons now POST /api/v1/plans/{weekStart}/push-calendar, showing progress and storing the synced plan the server answers with. Failures only the settings can fix (google_reconnect_required, no_calendar, not_connected, calendar_unavailable) link to /settings/calendar; plan_changed reads the plan again; any other failure offers Retry. The mobile Draft board shows the existing sync status line under its push button. Maestro task 677261bc-1a7c-4d96-b8f9-7601caf2186d. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>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: 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.