chore(03): close Phase 03 — transition to Phase 04
- 03-08-SUMMARY (Gate 2 record) written; phase marked complete (12/12) - ROADMAP: Phase 3 [x] complete 2026-06-07; REQUIREMENTS updated - STATE: advanced to Phase 4, focus + progress corrected (backlog 999.x were miscounted as milestone phases → reset total_phases to 5, 60%) - PROJECT.md evolved: event CRUD / OIDC login / iOS PWA install moved to Validated (Phase 3); D-17 logged; Android install + onboarding-provider-setup kept Active - removed stale .continue-here + one-shot HANDOFF.json Carried forward: B5 (Android install), Phase 4 SSE entry gate (D-14), backlog 999.3-999.9.
This commit is contained in:
@@ -1,46 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "1.0",
|
|
||||||
"timestamp": "2026-06-07T02:39:57.236Z",
|
|
||||||
"phase": "03",
|
|
||||||
"phase_name": "event-write-back-pwa-install",
|
|
||||||
"phase_dir": ".planning/phases/03-event-write-back-pwa-install",
|
|
||||||
"plan": "Gate 2 (Part D live verification)",
|
|
||||||
"task": "delete/edit join fix RESOLVED (quick 260607-l6l); Gate 2 human/device checkpoints remain",
|
|
||||||
"total_tasks": null,
|
|
||||||
"status": "paused",
|
|
||||||
"update_2026_06_07": "Quick task 260607-l6l cleared the three write-path CODE bugs (edit/delete join 503, displayName, GET events filter) on branch gsd/v1.0-milestone — build clean, 100/100 api tests pass, handler-coupled regression test added, deriveDisplayName extracted as shared helper. Remaining items are operator rebuild + human/device Gate 2 checkpoints, not code work.",
|
|
||||||
"completed_tasks": [
|
|
||||||
{"id": "tunnel", "name": "PWA loads through Pangolin/newt — newt MTU 1280→1200 (operator) fixed large-asset blackhole; API serves full ./public tree (431ab31)", "status": "done"},
|
|
||||||
{"id": "auth", "name": "Real Authelia OIDC login working — client_id=familysync-dev, scopes incl offline_access, /api/login route + redirect (quick 260606-tv8), fetchMe redirect:manual (1adb460)", "status": "done", "commit": "874f23d"},
|
|
||||||
{"id": "bringup", "name": "Stack bring-up: PWA built into API image, NODE_ENV=production, broker credential seeded (reused spike app password)", "status": "done", "commit": "b46b25b"},
|
|
||||||
{"id": "bug-A", "name": "BUG A timezone — events written 4h off; fixed via in-browser UTC serialization (eventDateTime.ts)", "status": "done", "commit": "a9d3de6"},
|
|
||||||
{"id": "bug-B", "name": "BUG B calendar identity — events attached to wrong user + duplicate calendar rows; unique(userId,url) + per-user predicates + migration applied to live DB", "status": "done", "commit": "a9d3de6"},
|
|
||||||
{"id": "spike-cleanup", "name": "Deleted obsolete spike user (id=1 Dev User) + calendar id=1 + cached events (DB op, operator-approved)", "status": "done"}
|
|
||||||
],
|
|
||||||
"remaining_tasks": [
|
|
||||||
{"id": "edit-delete-join", "name": "RESOLVED (quick 260607-l6l, commit 2870413): added .innerJoin(calendars,...) to PATCH /:uid/edit + DELETE /:uid lookups; handler-coupled regression test (asserts 202 + innerJoin spy, verified RED when join removed).", "status": "done", "commit": "2870413"},
|
|
||||||
{"id": "displayName", "name": "RESOLVED (quick 260607-l6l, commits 23c8bb3 + a99ef1d): shared deriveDisplayName helper (name→preferred_username→email→Member<sub>) used in me.ts + resolveUserId; user.ts now UPDATEs an existing blank display_name on re-upsert (so user id=2 self-corrects on next request). OPERATOR FOLLOW-UP: legend shows full name only if Authelia emits name/preferred_username — else it shows email.", "status": "done", "commit": "23c8bb3"},
|
|
||||||
{"id": "events-filter", "name": "RESOLVED (quick 260607-l6l, commit 00a0454): GET /api/events now filters WHERE (calendars.userId=currentUser OR calendars.isShared) AND <date-window>, correctly grouped.", "status": "done", "commit": "00a0454"},
|
|
||||||
{"id": "sync-toast", "name": "Syncing toast not animated / ~27s (outbox 15s drain + CalDAV) looks stalled — UI polish (backlog candidate).", "status": "not_started"},
|
|
||||||
{"id": "gate2-A2A3", "name": "Gate 2 Part A2 (session persists across browser restart) + A3 (2nd member distinct color) — operator verify.", "status": "not_started"},
|
|
||||||
{"id": "gate2-B", "name": "Gate 2 Part B — iOS standalone install + login (device-only; manifest/sw fixes now unblock it).", "status": "not_started"},
|
|
||||||
{"id": "gate2-C", "name": "Gate 2 Part C — 5-min SSE smoke (Phase 4 entry gate).", "status": "not_started"}
|
|
||||||
],
|
|
||||||
"blockers": [
|
|
||||||
{"description": "RESOLVED — edit/delete 503 (missing calendars join) fixed in quick 260607-l6l (commit 2870413). Code on gsd/v1.0-milestone; not yet rebuilt into the running container.", "type": "technical", "workaround": "n/a — fixed; operator must rebuild to deploy"},
|
|
||||||
{"description": "playwright-cli daemon wedges/crashes in this WSL2 env (hangs on never-settling pages; even open/run-code fail after). Cannot drive browser verification here.", "type": "technical", "workaround": "Verify via curl + ask the operator to test in their real browser/incognito."}
|
|
||||||
],
|
|
||||||
"human_actions_pending": [
|
|
||||||
{"action": "Rebuild + deploy: docker compose up -d --build (newt target drops ~30s then self-recovers — not a bug).", "context": "The 3 write-path code fixes (260607-l6l) are committed but not in the running container.", "blocking": true},
|
|
||||||
{"action": "Browser re-test after rebuild: delete an event (dialog should close, event disappears) and edit an event (no 503). Create a NEW 9am event; the OLD wrong-time 5am test event can now be deleted.", "context": "BUG 1 (join) + earlier BUG A/B (timezone/identity) fixes.", "blocking": false},
|
|
||||||
{"action": "Confirm calendar legend shows a name. If it shows email instead of full name, configure Authelia to emit the name/preferred_username OIDC claim (code reads them preferentially; existing blank row self-corrects on next request).", "context": "BUG 2 — displayName helper deployed; legend content depends on Authelia claim emission.", "blocking": false}
|
|
||||||
],
|
|
||||||
"decisions": [
|
|
||||||
{"decision": "newt -mtu 1200 (systemd drop-in)", "rationale": "newt default tunnel MTU 1280 == eth0 underlay; WireGuard overhead made encrypted packets exceed 1280 → large transfers (JS bundle) blackholed. THE root cause of the 'spinner'.", "phase": "03"},
|
|
||||||
{"decision": "Spike user (id=1) + its calendar/events deleted", "rationale": "Obsolete Phase 1 test identity polluting the unified view as 'Dev User'; events are re-syncable cache. Operator approved.", "phase": "03"},
|
|
||||||
{"decision": "fetchMe uses redirect:manual; serve full ./public; OIDC_SCOPES constrained", "rationale": "fetch followed cross-origin 302 and hung; static serving only did /assets/*; empty OIDC_SCOPES requested all scopes_supported (Authelia invalid_scope).", "phase": "03"}
|
|
||||||
],
|
|
||||||
"uncommitted_files": [],
|
|
||||||
"next_action": "The three write-path code bugs are FIXED (quick 260607-l6l, on gsd/v1.0-milestone, build clean + 100/100 api tests). NEXT: operator rebuilds (docker compose up -d --build) and re-tests delete/edit + legend name in a real browser. Then resume the remaining Gate 2 checkpoints: Part A2 (session persists across browser restart) + A3 (2nd member distinct color); Part B iOS standalone install+login (device-only); Part C 5-min SSE smoke (Phase 4 entry gate). sync-toast polish is a backlog candidate. New backlog todo captured: event-creation reminder/VALARM options (.planning/todos/pending/event-creation-reminder-options.md).",
|
|
||||||
"context_notes": "This session went from 'paused awaiting docker decisions' to a full Gate 2 live bring-up. The big unlock was the newt MTU fix — every earlier 'spinner' symptom was the JS bundle blackholing through the tunnel, not auth. Along the way fixed 6+ real bugs (auth redirect, static serving, OIDC scopes/client_id, timezone, calendar identity) and cleaned spike data. Stack is running (docker compose production target); /health 200 through tunnel; real OIDC login works. Write path (create) works end-to-end to Fastmail. Delete/edit are the current blocker (trivial join fix). Do NOT use playwright-cli (broken here). Do NOT read/write .env via tools (permission-locked; operator applies .env changes). Every docker compose recreate drops newt's target ~30s (503) then self-recovers — not a bug. DB now: 1 user (id=2, display_name blank), calendars id=2 Calendar(509ev) + id=3 USA Holidays(32)."
|
|
||||||
}
|
|
||||||
@@ -15,17 +15,18 @@ The household can see and co-edit one color-coded family calendar (shared + each
|
|||||||
<!-- Shipped and confirmed valuable. -->
|
<!-- Shipped and confirmed valuable. -->
|
||||||
|
|
||||||
- [x] Unified, color-coded calendar view aggregating all Fastmail-hosted calendars (shared family + each member's personal) — **Validated in Phase 2 (calendar-display)**: read-only day/week/month/agenda views, server-side recurrence expansion (DST-correct), all-day no-shift, color routing by member/shared. Operator UAT approved. (Shared/rose lane activates once a shared calendar is marked — deferred per D-16.)
|
- [x] Unified, color-coded calendar view aggregating all Fastmail-hosted calendars (shared family + each member's personal) — **Validated in Phase 2 (calendar-display)**: read-only day/week/month/agenda views, server-side recurrence expansion (DST-correct), all-day no-shift, color routing by member/shared. Operator UAT approved. (Shared/rose lane activates once a shared calendar is marked — deferred per D-16.)
|
||||||
|
- [x] Create / edit / delete events written back to the correct Fastmail calendar via the app's single broker token — **Validated in Phase 3 (Gate 2, live 2026-06-07)**: create (timed/all-day/weekly-recurring), edit, delete, and recurring-series delete all round-trip to caldav.fastmail.com; 412-conflict handled. Recurring repeat-bound + per-occurrence-duration UX are "create+display only in v1" gaps (backlog 999.7/999.8).
|
||||||
|
- [x] Authelia OIDC login for every member (true SSO) — **Validated in Phase 3 (Gate 2, live 2026-06-07)**: both members log in via real Authelia OIDC over Pangolin; distinct stable colors; session carried transparently by Authelia SSO. (Full-name legend needs an Authelia ID-token `claims_policy` — operator step.)
|
||||||
|
- [x] React PWA installable on iPhone via "Add to Home Screen" (no App Store) — **Validated in Phase 3 (Gate 2, live 2026-06-07)**: iOS install + full-screen standalone launch + standalone OIDC login (load-bearing) confirmed on the wife's iPhone. Android install walkthrough deferred (B5, not yet device-tested).
|
||||||
|
|
||||||
### Active
|
### Active
|
||||||
|
|
||||||
<!-- v1 scope. Hypotheses until shipped and validated. -->
|
<!-- v1 scope. Hypotheses until shipped and validated. -->
|
||||||
- [ ] Create / edit / delete events written back to the correct Fastmail calendar via the app's single broker token
|
|
||||||
- [ ] Shared collaborative lists (groceries, gift ideas) that both members co-edit, stored in MariaDB
|
- [ ] Shared collaborative lists (groceries, gift ideas) that both members co-edit, stored in MariaDB
|
||||||
- [ ] Live list sync so co-edits appear without manual refresh (Redis optional)
|
- [ ] Live list sync so co-edits appear without manual refresh (Redis optional)
|
||||||
- [ ] Authelia OIDC login for every member (true SSO)
|
|
||||||
- [ ] React PWA installable on iPhone and Android via "Add to Home Screen" (no App Store)
|
|
||||||
- [ ] Web Push notifications for event reminders and list changes
|
- [ ] Web Push notifications for event reminders and list changes
|
||||||
- [ ] Low-friction onboarding for the non-technical Apple member — visit one URL, sign in
|
- [ ] Android PWA install walkthrough verified on a real Android device (iOS validated Phase 3; Android = carried Gate 2 row B5)
|
||||||
|
- [ ] Low-friction onboarding for the non-technical Apple member — visit one URL, sign in. **Partially validated Phase 3** (wife logged in + installed unaided); the per-member Fastmail app-password provider-setup step is still missing (backlog 999.5)
|
||||||
|
|
||||||
### Out of Scope
|
### Out of Scope
|
||||||
|
|
||||||
@@ -77,6 +78,7 @@ The household can see and co-edit one color-coded family calendar (shared + each
|
|||||||
| **D-14:** Defer Phase 1 Gate 2 (live Authelia/Pangolin verification). SSE-over-Pangolin smoke = hard gate before Phase 4; live AUTH smoke incl. iOS standalone-PWA folded into Phase 3; full 2-member prod login verified there. Phases 2–3 develop behind a documented dev-auth bypass. | Gate 2 needs operator infra (Authelia config + tunnel) + docs that didn't exist; deferring unblocks Phase 2/3 code without rework risk, since the broker data path (CAL-01/CAL-08) is already proven live. SSE must still be verified before Phase 4 to avoid building live-sync on an unverified transport (#1034). | Tracked: `01-HUMAN-UAT.md`, `docs/deployment.md` |
|
| **D-14:** Defer Phase 1 Gate 2 (live Authelia/Pangolin verification). SSE-over-Pangolin smoke = hard gate before Phase 4; live AUTH smoke incl. iOS standalone-PWA folded into Phase 3; full 2-member prod login verified there. Phases 2–3 develop behind a documented dev-auth bypass. | Gate 2 needs operator infra (Authelia config + tunnel) + docs that didn't exist; deferring unblocks Phase 2/3 code without rework risk, since the broker data path (CAL-01/CAL-08) is already proven live. SSE must still be verified before Phase 4 to avoid building live-sync on an unverified transport (#1034). | Tracked: `01-HUMAN-UAT.md`, `docs/deployment.md` |
|
||||||
| **D-15:** Validate the real external topology via a **local Newt connector + test subdomain** through existing Pangolin (Mode A), not an Unraid deploy. Unraid (Mode B) reserved for go-live. | Authelia OIDC + SSE pass-through behaviour live in Authelia + Pangolin/Newt, not in where the origin runs — so a local Newt rig faithfully tests both, decoupling "does the topology work" from "is it in production." Newt dials outbound (no open ports). Only shared touch is an additive, reversible Authelia client. | — Pending (Gate 2) |
|
| **D-15:** Validate the real external topology via a **local Newt connector + test subdomain** through existing Pangolin (Mode A), not an Unraid deploy. Unraid (Mode B) reserved for go-live. | Authelia OIDC + SSE pass-through behaviour live in Authelia + Pangolin/Newt, not in where the origin runs — so a local Newt rig faithfully tests both, decoupling "does the topology work" from "is it in production." Newt dials outbound (no open ports). Only shared touch is an additive, reversible Authelia client. | — Pending (Gate 2) |
|
||||||
| **D-16 (2026-06-05, Phase 2):** No dedicated Fastmail "broker" account. The **shared-family calendar is a calendar collection created on the operator's primary Fastmail account** (`me@lucasberger.ca`) and shared out to the wife + others via Fastmail's own calendar sharing. The app's single app password enumerates it like any other collection; the `calendars.is_shared` flag (operator-set) marks which row is the shared one. | Clarified during the Wave 2 checkpoint: "broker account" was only ever the role the primary account's app password plays. id=1 ("Calendar") is the operator's **personal** calendar, not the shared one — so it must NOT be marked `is_shared`. Aggregating each *other* member's **personal** calendar still follows the D-09 per-member app-password model (open for Phase 3 onboarding: a member may get a personal color lane, or only the shared calendar). | — Pending (shared calendar not yet created) |
|
| **D-16 (2026-06-05, Phase 2):** No dedicated Fastmail "broker" account. The **shared-family calendar is a calendar collection created on the operator's primary Fastmail account** (`me@lucasberger.ca`) and shared out to the wife + others via Fastmail's own calendar sharing. The app's single app password enumerates it like any other collection; the `calendars.is_shared` flag (operator-set) marks which row is the shared one. | Clarified during the Wave 2 checkpoint: "broker account" was only ever the role the primary account's app password plays. id=1 ("Calendar") is the operator's **personal** calendar, not the shared one — so it must NOT be marked `is_shared`. Aggregating each *other* member's **personal** calendar still follows the D-09 per-member app-password model (open for Phase 3 onboarding: a member may get a personal color lane, or only the shared calendar). | — Pending (shared calendar not yet created) |
|
||||||
|
| **D-17 (2026-06-07, Phase 3):** Phase 1 Gate 2 (deferred per D-14) was executed live during Phase 3 against real Authelia OIDC over Pangolin/Newt (Mode A), clearing the load-bearing iOS-standalone-login risk. The full event write path (create/all-day/recurring/edit/delete/conflict) is verified end-to-end to Fastmail. | Live bring-up surfaced bugs the dev-bypass build could not (newt MTU blackhole, OIDC state-cookie race, write-path timezone/identity/join/cache bugs, all-day off-by-one, color collisions). All fixed; UX gaps captured as backlog 999.3–999.9. | — Validated (Gate 2, `03-GATE2-RESULTS.md`). Carried: Android install (B5), SSE smoke (Phase 4 entry gate, D-14). |
|
||||||
|
|
||||||
## Evolution
|
## Evolution
|
||||||
|
|
||||||
@@ -96,4 +98,4 @@ This document evolves at phase transitions and milestone boundaries.
|
|||||||
4. Update Context with current state
|
4. Update Context with current state
|
||||||
|
|
||||||
---
|
---
|
||||||
*Last updated: 2026-06-03 after initialization*
|
*Last updated: 2026-06-07 after Phase 3 (event-write-back-pwa-install)*
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ Decimal phases appear between their surrounding integers in numeric order.
|
|||||||
|
|
||||||
- [x] **Phase 1: Foundation + Broker Spike** - Auth, Docker scaffold, CalDAV broker read path, and personal-calendar ACL spike (go/no-go gate) (completed 2026-06-04)
|
- [x] **Phase 1: Foundation + Broker Spike** - Auth, Docker scaffold, CalDAV broker read path, and personal-calendar ACL spike (go/no-go gate) (completed 2026-06-04)
|
||||||
- [x] **Phase 2: Calendar Display** - Read-only unified color-coded calendar (week/month/day/agenda) built on the confirmed broker (completed 2026-06-05)
|
- [x] **Phase 2: Calendar Display** - Read-only unified color-coded calendar (week/month/day/agenda) built on the confirmed broker (completed 2026-06-05)
|
||||||
- [ ] **Phase 3: Event Write-Back + PWA Install** - Full event CRUD written back to Fastmail, PWA manifest + service worker, guided iOS install flow
|
- [x] **Phase 3: Event Write-Back + PWA Install** - Full event CRUD written back to Fastmail, PWA manifest + service worker, guided iOS install flow (completed 2026-06-07)
|
||||||
- [ ] **Phase 4: Shared Lists + Live Sync** - Named collaborative lists with item CRUD and real-time SSE co-edit sync
|
- [ ] **Phase 4: Shared Lists + Live Sync** - Named collaborative lists with item CRUD and real-time SSE co-edit sync
|
||||||
- [ ] **Phase 5: Web Push Notifications** - VAPID push for event reminders, event changes, and list-change alerts
|
- [ ] **Phase 5: Web Push Notifications** - VAPID push for event reminders, event changes, and list-change alerts
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ Plans:
|
|||||||
|
|
||||||
**Wave 5** *(blocked on Wave 4)*
|
**Wave 5** *(blocked on Wave 4)*
|
||||||
|
|
||||||
- [ ] 03-08-PLAN.md — Gate 2 live verification: real Authelia OIDC over Pangolin + iOS standalone login + end-to-end Fastmail write round-trips (success criterion 6, D-14/D-15)
|
- [x] 03-08-PLAN.md — Gate 2 live verification: real Authelia OIDC over Pangolin + iOS standalone login + end-to-end Fastmail write round-trips (success criterion 6, D-14/D-15)
|
||||||
|
|
||||||
**UI hint**: yes
|
**UI hint**: yes
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@ Note: Phase 4 depends only on Phase 1 and can begin as soon as Phase 1 is comple
|
|||||||
|-------|----------------|--------|-----------|
|
|-------|----------------|--------|-----------|
|
||||||
| 1. Foundation + Broker Spike | 4/4 | Complete | 2026-06-04 |
|
| 1. Foundation + Broker Spike | 4/4 | Complete | 2026-06-04 |
|
||||||
| 2. Calendar Display | 5/5 | Complete | 2026-06-05 |
|
| 2. Calendar Display | 5/5 | Complete | 2026-06-05 |
|
||||||
| 3. Event Write-Back + PWA Install | 11/12 | In Progress| |
|
| 3. Event Write-Back + PWA Install | 12/12 | Complete | 2026-06-07 |
|
||||||
| 4. Shared Lists + Live Sync | 0/? | Not started | - |
|
| 4. Shared Lists + Live Sync | 0/? | Not started | - |
|
||||||
| 5. Web Push Notifications | 0/? | Not started | - |
|
| 5. Web Push Notifications | 0/? | Not started | - |
|
||||||
|
|
||||||
@@ -178,7 +178,7 @@ Note: Phase 4 depends only on Phase 1 and can begin as soon as Phase 1 is comple
|
|||||||
|
|
||||||
**Goal:** [Captured for future planning] Abstract the calendar backend behind a provider interface so Fastmail/CalDAV is one implementation among potentially many. Shipping with a single provider is fine, but the broker, sync, and event-expansion layers should be structured so additional providers (e.g. other CalDAV hosts, Google Calendar, generic ICS feeds) can be added without rework. Captures the "provider" seam as an explicit architectural concern.
|
**Goal:** [Captured for future planning] Abstract the calendar backend behind a provider interface so Fastmail/CalDAV is one implementation among potentially many. Shipping with a single provider is fine, but the broker, sync, and event-expansion layers should be structured so additional providers (e.g. other CalDAV hosts, Google Calendar, generic ICS feeds) can be added without rework. Captures the "provider" seam as an explicit architectural concern.
|
||||||
**Requirements:** TBD
|
**Requirements:** TBD
|
||||||
**Plans:** 11/12 plans executed
|
**Plans:** 12/12 plans complete
|
||||||
|
|
||||||
Plans:
|
Plans:
|
||||||
|
|
||||||
@@ -235,6 +235,7 @@ Plans:
|
|||||||
**Context** (surfaced 2026-06-07, Gate 2 live testing): the wife logged in on her iPhone and added the PWA to her Home Screen, but there is no provider-setup step — so her personal calendar can't be connected. This is the onboarding half of the "each member's personal calendar" v1 requirement.
|
**Context** (surfaced 2026-06-07, Gate 2 live testing): the wife logged in on her iPhone and added the PWA to her Home Screen, but there is no provider-setup step — so her personal calendar can't be connected. This is the onboarding half of the "each member's personal calendar" v1 requirement.
|
||||||
|
|
||||||
**Scope to decide when promoted:**
|
**Scope to decide when promoted:**
|
||||||
|
|
||||||
- Detect "no credential yet" state server-side (`GET /api/me` exposes a `needsProviderSetup` flag, or a dedicated endpoint) and gate a setup screen in the PWA.
|
- Detect "no credential yet" state server-side (`GET /api/me` exposes a `needsProviderSetup` flag, or a dedicated endpoint) and gate a setup screen in the PWA.
|
||||||
- App-password entry UI + validation (test the credential with a CalDAV PROPFIND before saving), encrypted storage, and triggering the first sync.
|
- App-password entry UI + validation (test the credential with a CalDAV PROPFIND before saving), encrypted storage, and triggering the first sync.
|
||||||
- Non-technical-friendly instructions (the hard UX constraint) — ideally with a direct link to Fastmail's app-password page and a screenshot/walkthrough.
|
- Non-technical-friendly instructions (the hard UX constraint) — ideally with a direct link to Fastmail's app-password page and a screenshot/walkthrough.
|
||||||
|
|||||||
+19
-18
@@ -3,40 +3,40 @@ gsd_state_version: 1.0
|
|||||||
milestone: v1.0
|
milestone: v1.0
|
||||||
milestone_name: milestone
|
milestone_name: milestone
|
||||||
status: executing
|
status: executing
|
||||||
stopped_at: Completed 03-11-PLAN.md (gap-closure waves done; 03-08 Gate 2 human checkpoint remains)
|
stopped_at: "Phase 03 (event-write-back-pwa-install) COMPLETE — Gate 2 verified live (auth, iOS standalone login, full write round-trips). Ready to plan Phase 04 (Shared Lists + Live Sync), whose entry gate is the 5-min SSE-over-Pangolin smoke test (D-14)."
|
||||||
last_updated: "2026-06-06T00:34:09.142Z"
|
last_updated: "2026-06-07T23:30:00.000Z"
|
||||||
last_activity: 2026-06-06 -- Phase 03 execution started
|
last_activity: 2026-06-07
|
||||||
progress:
|
progress:
|
||||||
total_phases: 6
|
total_phases: 5
|
||||||
completed_phases: 2
|
completed_phases: 3
|
||||||
total_plans: 21
|
total_plans: 21
|
||||||
completed_plans: 16
|
completed_plans: 21
|
||||||
percent: 33
|
percent: 60
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project State
|
# Project State
|
||||||
|
|
||||||
## Project Reference
|
## Project Reference
|
||||||
|
|
||||||
See: .planning/PROJECT.md (updated 2026-06-03)
|
See: .planning/PROJECT.md (updated 2026-06-07)
|
||||||
|
|
||||||
**Core value:** One color-coded family calendar (shared + personal) and shared lists from a single low-friction PWA — cross-ecosystem, no app store
|
**Core value:** One color-coded family calendar (shared + personal) and shared lists from a single low-friction PWA — cross-ecosystem, no app store
|
||||||
**Current focus:** Phase 03 — event-write-back-pwa-install
|
**Current focus:** Phase 04 — Shared Lists + Live Sync (entry gate: SSE-over-Pangolin smoke test, D-14)
|
||||||
|
|
||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Phase: 03 (event-write-back-pwa-install) — EXECUTING
|
Phase: 4
|
||||||
Plan: 11 of 12 (gap-closure 03-09/10/11/12 complete; 03-08 Gate 2 live/iOS human checkpoint remains)
|
Plan: Not started
|
||||||
Status: Gap-closure waves complete — write path now reachable end-to-end
|
Status: Phase 03 complete — ready to plan Phase 04
|
||||||
Last activity: 2026-06-06 -- Phase 03 gap-closure (03-09..03-12) executed and merged
|
Last activity: 2026-06-07
|
||||||
|
|
||||||
Progress: [███████░░░] 65%
|
Progress: [██████░░░░] 60% (3 of 5 phases)
|
||||||
|
|
||||||
## Performance Metrics
|
## Performance Metrics
|
||||||
|
|
||||||
**Velocity:**
|
**Velocity:**
|
||||||
|
|
||||||
- Total plans completed: 5
|
- Total plans completed: 17
|
||||||
- Average duration: -
|
- Average duration: -
|
||||||
- Total execution time: 0 hours
|
- Total execution time: 0 hours
|
||||||
|
|
||||||
@@ -45,6 +45,7 @@ Progress: [███████░░░] 65%
|
|||||||
| Phase | Plans | Total | Avg/Plan |
|
| Phase | Plans | Total | Avg/Plan |
|
||||||
|-------|-------|-------|----------|
|
|-------|-------|-------|----------|
|
||||||
| 02 | 5 | - | - |
|
| 02 | 5 | - | - |
|
||||||
|
| 03 | 12 | - | - |
|
||||||
|
|
||||||
**Recent Trend:**
|
**Recent Trend:**
|
||||||
|
|
||||||
@@ -120,6 +121,6 @@ Recent decisions affecting current work:
|
|||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-06-07 — Completed quick task 260607-l6l (write-path bug batch)
|
Last session: 2026-06-07 — Phase 03 closed (Gate 2 live-verified end to end)
|
||||||
Stopped at: Edit/delete 503 blocker RESOLVED (missing calendars join), displayName + GET-events-filter fixed. Code on gsd/v1.0-milestone; build clean, 100/100 api tests pass. NEXT: operator must rebuild (docker compose up -d --build) and browser re-test delete/edit + legend name. Remaining Gate 2 items (A2/A3 session+2nd-member-color, B iOS install, C SSE smoke) are human/device checkpoints — see HANDOFF.json.
|
Stopped at: Phase 03 complete and transitioned. Next: plan Phase 04 (Shared Lists + Live Sync). ENTRY GATE — run the 5-min SSE-over-Pangolin smoke test (docs/deployment.md) before building live sync (D-14, issue #1034). Carried forward: B5 (Android install), backlog 999.3–999.9.
|
||||||
Resume file: .planning/HANDOFF.json (updated — 3 code bugs cleared, gate-2 human checkpoints remain)
|
Resume file: None
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
---
|
|
||||||
context: phase
|
|
||||||
phase: 03-event-write-back-pwa-install
|
|
||||||
task: "Gate 2 Part D — edit/delete join fix (diagnosed, not started)"
|
|
||||||
total_tasks: null
|
|
||||||
status: in_progress
|
|
||||||
last_updated: 2026-06-07T02:39:57.236Z
|
|
||||||
---
|
|
||||||
|
|
||||||
# Critical Anti-Patterns
|
|
||||||
|
|
||||||
| Pattern | Description | Severity | Prevention Mechanism |
|
|
||||||
|---------|-------------|----------|---------------------|
|
|
||||||
| playwright-cli wedges in this WSL2 env | After loading a never-settling page (e.g. the infinite-spinner state), the playwright-cli daemon hangs and even `open`/`run-code` fail afterward. Burned a lot of effort on it. | blocking | Do NOT use playwright-cli for verification here. Verify via `curl` against the tunnel + ask the operator to test in their real browser/incognito. |
|
|
||||||
| Drizzle: referencing joined-table columns without the join | `events.ts` edit + delete handlers select `calendars.url`/`calendars.userId` from `.from(calendarEvents)` with no `.innerJoin(calendars,...)` → runtime 503 "table calendars is not part of the query". Unit tests mock `db.select()` so they DON'T catch it. | blocking | Any handler selecting another table's columns MUST `.innerJoin` it. Regression tests for write endpoints must exercise the REAL query builder (test DB), not a mocked `db.select()`. |
|
|
||||||
| `.env` is permission-locked | The Read/Edit/Bash tools are denied on `.env` (and `.env.spike`). | advisory | Hand the operator exact `.env` lines to apply via the `!` prefix; never assume you can read/write it. |
|
|
||||||
| docker compose recreate drops the newt target ~30s | Every `docker compose up -d` recreates the api container, resetting newt's held TCP connection → tunnel returns 503 "no available server" for ~30s, then self-recovers. | advisory | After any recreate, poll `/health` through the tunnel until 200 before testing. Not a bug — do not chase it. |
|
|
||||||
|
|
||||||
<current_state>
|
|
||||||
Phase 03 **Gate 2 live verification is largely working.** The PWA now loads through Pangolin/newt, real Authelia OIDC login works, and create-event round-trips to Fastmail correctly (right time, right user) after this session's fixes. Working tree is clean (all committed).
|
|
||||||
|
|
||||||
**Immediate blocker:** deleting (and latently editing) an event 503s — the `events.ts` edit/delete handlers are missing a `calendars` join. Diagnosed, fix NOT yet applied. The operator paused right as I asked how to land the fix.
|
|
||||||
</current_state>
|
|
||||||
|
|
||||||
<completed_work>
|
|
||||||
This session (commits b46b25b → bdbb9b8):
|
|
||||||
- **Tunnel works** — operator set `newt -mtu 1200` (was 1280 == eth0 underlay; WireGuard overhead blackholed large packets). THIS was the real cause of every "spinner" — the 510KB JS bundle never downloaded. Plus API now serves the full `./public` tree (431ab31), so manifest/sw/icons stop returning HTML.
|
|
||||||
- **Auth works** — `/api/login` route + redirect (quick task 260606-tv8), `fetchMe` uses `redirect:'manual'` (1adb460), `OIDC_CLIENT_ID=familysync-dev`, `OIDC_SCOPES=openid profile email offline_access`, redirect URI corrected. Operator added `offline_access` to the Authelia client.
|
|
||||||
- **Bring-up** (b46b25b) — PWA built into the API image (single port :3000), `NODE_ENV=production`, broker credential seeded (reused the spike app password).
|
|
||||||
- **BUG A (timezone)** + **BUG B (calendar identity)** fixed via /gsd-debug (a9d3de6, session `.planning/debug/write-path-event-bugs.md`). Migration `0001_calendars_user_url_unique.sql` applied to the live DB.
|
|
||||||
- **Spike cleanup** — deleted obsolete user id=1 ("Dev User") + calendar id=1 + cached events (operator-approved DB op).
|
|
||||||
- **Backlog 999.2** added — slick unauthenticated-entry (no login flash).
|
|
||||||
</completed_work>
|
|
||||||
|
|
||||||
<remaining_work>
|
|
||||||
1. **BLOCKING — edit/delete join fix.** `apps/api/src/routes/events.ts`: the `PATCH /:uid/edit` lookup (~line 295) and `DELETE /:uid` lookup (~line 392) select `calendarUrl: calendars.url` + `userId: calendars.userId` from `.from(calendarEvents).where(eq(calendarEvents.uid, uid))` with NO join. Add `.innerJoin(calendars, eq(calendarEvents.calendarId, calendars.id))` to both (mirror the working `GET /api/events` query at ~line 153). Add a regression test that runs the real query builder.
|
|
||||||
2. `me.ts` blank displayName — passes the (missing) `email` claim as displayName, never reads `name`/`preferred_username`; `oidc_iss` also blank. Legend name is blank (user id=2 `display_name=''`). May also need Authelia to include name/email in the ID token, or call userinfo.
|
|
||||||
3. `GET /api/events` has no `userId`/`isShared` filter (returns all users' events) — latent now (1 real user), real bug for a 2nd member.
|
|
||||||
4. "Syncing" toast not animated / ~27s — UI polish (backlog candidate).
|
|
||||||
5. Gate 2 remaining: A2 (session persistence), A3 (2nd-member color), B (iOS standalone install+login — device-only), C (5-min SSE smoke — Phase 4 entry gate).
|
|
||||||
|
|
||||||
The operator was choosing how to land #1: **(a)** batch #1+#2+#3 in one /gsd-quick, **(b)** /gsd-quick just #1, or **(c)** fix #1 inline. Re-offer that.
|
|
||||||
</remaining_work>
|
|
||||||
|
|
||||||
<decisions_made>
|
|
||||||
- `newt -mtu 1200` — fixes large-asset blackhole through the WireGuard tunnel.
|
|
||||||
- Deleted the spike user (id=1) + its calendar/events — obsolete test identity, re-syncable cache.
|
|
||||||
- `fetchMe` `redirect:'manual'`, serve full `./public`, constrained `OIDC_SCOPES` — see HANDOFF.json.
|
|
||||||
</decisions_made>
|
|
||||||
|
|
||||||
<blockers>
|
|
||||||
- Delete/edit events 503 (missing calendars join) — trivial fix, not yet applied.
|
|
||||||
- playwright-cli broken in this env — verify via curl + operator's browser.
|
|
||||||
</blockers>
|
|
||||||
|
|
||||||
## Required Reading (in order)
|
|
||||||
1. `.planning/HANDOFF.json` — machine-readable mirror of this state.
|
|
||||||
2. `apps/api/src/routes/events.ts` — the edit (~line 295) + delete (~line 392) handlers missing the `calendars` join; compare to the working GET query (~line 153).
|
|
||||||
3. `.planning/debug/write-path-event-bugs.md` — the resolved timezone + calendar-identity debug session (context for the write path).
|
|
||||||
4. `.planning/phases/03-event-write-back-pwa-install/03-GATE2-RESULTS.md` — the Gate 2 checklist (still needs updating with what now passes).
|
|
||||||
|
|
||||||
## Infrastructure State
|
|
||||||
- Stack: `docker compose` (production target) — api + mariadb (healthy) + redis up. `/health` 200 locally AND through `https://familysync-dev.bergerhouse.net`.
|
|
||||||
- newt: systemd service, `-mtu 1200` drop-in applied by operator; WireGuard tunnel carries large transfers now.
|
|
||||||
- DB: 1 user (id=2, real OIDC, `display_name=''`, color #E8734A); calendars id=2 "Calendar" (509 ev) + id=3 "USA Holidays" (32). `uniq_calendar_user_url(user_id,url)` present.
|
|
||||||
- OIDC: `client_id=familysync-dev` registered in Authelia with `offline_access`; redirect `https://familysync-dev.bergerhouse.net/callback`.
|
|
||||||
- An old test event (uid `92dd5a80…`) exists in Fastmail at the WRONG time (written pre-BUG-A-fix) — operator should delete it via the app once delete works.
|
|
||||||
|
|
||||||
<context>
|
|
||||||
The whole session was a Gate 2 bring-up that turned into a bug hunt. The keystone was the newt MTU fix — until then the JS bundle couldn't traverse the tunnel, so the app showed a perpetual spinner that looked like (and got misdiagnosed as) auth problems. After that unlocked, live testing surfaced a cascade of real write-path bugs, most now fixed. Remaining work is small and well-understood; the edit/delete join is a 2-line fix gated only on the operator's choice of how to land it.
|
|
||||||
</context>
|
|
||||||
|
|
||||||
<next_action>
|
|
||||||
Start with: re-offer the operator the landing choice for the edit/delete join fix (batch #1+#2+#3 / quick-only #1 / inline). Then apply `.innerJoin(calendars, eq(calendarEvents.calendarId, calendars.id))` to the edit (~line 295) and delete (~line 392) handlers in `apps/api/src/routes/events.ts`, add a real-query regression test, `docker compose up -d --build`, wait for tunnel `/health` 200, and have the operator re-test delete in the browser.
|
|
||||||
</next_action>
|
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
---
|
||||||
|
phase: 03-event-write-back-pwa-install
|
||||||
|
plan: 08
|
||||||
|
subsystem: gate, live-verification, auth, broker, pwa
|
||||||
|
tags: [gate-2, live-verification, authelia, oidc, pangolin, ios-pwa, caldav, write-back]
|
||||||
|
|
||||||
|
# Dependency graph
|
||||||
|
requires:
|
||||||
|
- phase: 03-event-write-back-pwa-install
|
||||||
|
plan: 04
|
||||||
|
provides: write endpoints (create/edit/delete) + outbox
|
||||||
|
- phase: 03-event-write-back-pwa-install
|
||||||
|
plan: 06
|
||||||
|
provides: EventDetailPopover + DeleteConfirmationDialog + SyncStateToast
|
||||||
|
- phase: 03-event-write-back-pwa-install
|
||||||
|
plan: 07
|
||||||
|
provides: PWA manifest + service worker + InstallPrompt
|
||||||
|
|
||||||
|
provides:
|
||||||
|
- Gate 2 live-verification results against the real Authelia + Pangolin deploy
|
||||||
|
- Confirmed end-to-end write path (create/all-day/recurring/edit/delete/conflict) to Fastmail
|
||||||
|
- Confirmed iOS standalone install + OIDC login (load-bearing)
|
||||||
|
|
||||||
|
affects: [phase-04]
|
||||||
|
|
||||||
|
# Tech tracking
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns:
|
||||||
|
- "Live Mode-A topology: local origin + Newt connector + Authelia OIDC through Pangolin"
|
||||||
|
- "Operator-driven verification (playwright-cli unavailable in WSL2); evidence via DB/outbox + browser"
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created:
|
||||||
|
- .planning/phases/03-event-write-back-pwa-install/03-08-SUMMARY.md
|
||||||
|
modified:
|
||||||
|
- .planning/phases/03-event-write-back-pwa-install/03-GATE2-RESULTS.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 03 Plan 08: Gate 2 Live Verification — Summary
|
||||||
|
|
||||||
|
**One-liner:** Took FamilySync live (real Authelia OIDC over Pangolin/Newt) and verified the full event write-back path end-to-end to Fastmail on desktop and iOS, fixing a long string of blocker bugs found only under live conditions.
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
|
||||||
|
Gate 2 is **complete for Phase 03 scope**. See `03-GATE2-RESULTS.md` for the per-row record. Summary:
|
||||||
|
|
||||||
|
- **A — Auth/session/colors:** A1 (OIDC login → app) ✅, A2 (session — transparent via Authelia SSO) ✅, A3 (distinct member colors) ✅ after fixing a color-collision bug.
|
||||||
|
- **B — iOS standalone (load-bearing):** B1–B4 ✅ — install to Home Screen, full-screen standalone launch, and **OIDC login completed from standalone without dropping to Safari**. B5 (Android install) deferred.
|
||||||
|
- **C — SSE smoke:** deferred by design — this is the Phase 4 *entry* gate (D-14), verified at the start of Phase 4.
|
||||||
|
- **D — write round-trips:** D1–D6 ✅ — create (timed), all-day, weekly recurring, edit, delete, 412-conflict, plus recurring-series delete, all round-tripping to caldav.fastmail.com.
|
||||||
|
|
||||||
|
## Blocker bugs found + fixed live (all committed + deployed)
|
||||||
|
|
||||||
|
Live bring-up surfaced bugs the dev-bypass build could not:
|
||||||
|
|
||||||
|
- **Tunnel:** newt MTU 1280→1200 (operator) — encrypted WireGuard packets exceeded the underlay MTU, blackholing the JS bundle (the original "spinner"). API now serves the full `./public` tree.
|
||||||
|
- **Auth:** `/api/login` route + `fetchMe` `redirect:'manual'`; OIDC scopes/client_id; and the OIDC **state-cookie churn** (events query racing the login flow → `OAUTH_INVALID_RESPONSE`) — fixed by gating the events query on auth.
|
||||||
|
- **Write path:** event timezone (UTC serialization), per-user calendar identity (unique(userId,url) + per-user predicates), missing `calendars` join in edit/delete (503), delete **cache reconciliation** (deletes lingered as ghosts), and the post-write **refetch race** (resync now precedes marking the outbox row done).
|
||||||
|
- **UI:** calendar **remount flash** (nested component rendered as `<CalendarContent/>`), all-day **display off-by-one** (exclusive DTEND vs Schedule-X inclusive), member **color collision** and member-vs-shared **color clash**.
|
||||||
|
- **Identity:** displayName now derived from OIDC claims with self-heal (Authelia ID-token `claims_policy` documented as the operator step for full names).
|
||||||
|
|
||||||
|
## Deferred / carried forward
|
||||||
|
|
||||||
|
- **B5** — Android install walkthrough (device check).
|
||||||
|
- **C** — SSE 5-min smoke (Phase 4 entry gate, D-14).
|
||||||
|
- **Backlog 999.3–999.9** — session-timeout sign-in redirect; event reminder/VALARM options; first-login Fastmail app-password provider setup; all-day visual distinction; event-form end-tracking + all-day edit off-by-one; recurrence repeat-until/count bound; edit recurring series.
|
||||||
|
|
||||||
|
## Verification method
|
||||||
|
|
||||||
|
Operator-driven browser testing (desktop + the wife's iPhone) + backend evidence (`calendar_outbox` rows reaching `done`, `calendar_events` cache, stored VEVENTs). `playwright-cli` is unavailable in this WSL2 env, so desktop rows were operator-driven rather than automated.
|
||||||
|
|
||||||
|
## Self-Check
|
||||||
|
|
||||||
|
- [x] Gate 2 results recorded in `03-GATE2-RESULTS.md`
|
||||||
|
- [x] Write path (create/all-day/recurring/edit/delete/conflict) verified live to Fastmail
|
||||||
|
- [x] iOS standalone install + login (load-bearing) verified
|
||||||
|
- [x] All live blocker bugs fixed, committed, and deployed
|
||||||
|
- [x] UX gaps captured as backlog (999.3–999.9); B5/C deferred by design
|
||||||
Reference in New Issue
Block a user