From ee4c71fa4688ddd8842499c63ed9e4498fb88f5c Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Thu, 4 Jun 2026 10:17:21 -0400 Subject: [PATCH] docs(01-01): clear Task 3 checkpoint, mark plan complete Stack verified live (/health green, 4 tables pushed). Record Docker deviation in SUMMARY, mark 01-01 complete in ROADMAP, clean up paused-setup handoff files. --- .planning/.continue-here.md | 86 ------------------- .planning/HANDOFF.json | 42 --------- .planning/ROADMAP.md | 21 ++++- .planning/STATE.md | 18 ++-- .planning/config.json | 3 +- .../01-01-SUMMARY.md | 23 +++-- 6 files changed, 47 insertions(+), 146 deletions(-) delete mode 100644 .planning/.continue-here.md delete mode 100644 .planning/HANDOFF.json diff --git a/.planning/.continue-here.md b/.planning/.continue-here.md deleted file mode 100644 index 00ca2e4..0000000 --- a/.planning/.continue-here.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -context: default -phase: none (pre-Phase-1 — project initialized, planning not started) -task: 0 -total_tasks: 5 -status: paused -last_updated: 2026-06-03T20:34:20.534Z ---- - - -`/gsd-new-project` ran to completion. FamilySync is fully initialized: PROJECT.md, config.json, -research (STACK/FEATURES/ARCHITECTURE/PITFALLS/SUMMARY), REQUIREMENTS.md (20 v1 reqs), and an -approved 5-phase ROADMAP.md (MVP mode) are all written and committed. Working tree is clean. -No phase/plan/task work has started yet. The immediate next step is `/gsd-discuss-phase 1`. - - - - -- PROJECT.md written + committed (ad2aad8) -- config.json: interactive mode, standard granularity, parallel, balanced (Sonnet) models, - research + plan-check + verifier + drift-guard all on (1ce0348) -- Domain research: 4 parallel researchers + synthesis (0f79277) -- REQUIREMENTS.md: 20 v1 requirements — AUTH-01..03, CAL-01..08, LIST-01..04, NOTIF-01..03, PWA-01..02 (a20afd4) -- ROADMAP.md + STATE.md: 5 phases, 20/20 coverage; CLAUDE.md generated (ac82d32) - - - - -5 phases, none started: -1. Foundation + Broker Spike — AUTH-01/02/03, CAL-01, CAL-08 -2. Calendar Display — CAL-02, CAL-03 -3. Event Write-Back + PWA Install — CAL-04/05/06/07, PWA-01/02 -4. Shared Lists + Live Sync — LIST-01/02/03/04 -5. Web Push Notifications — NOTIF-01/02/03 - - - - -- **Fastmail calendars are CalDAV-only** — JMAP-for-calendars is not available (pending RFC 8984). - Broker authenticates with a Fastmail **app password**, NOT an API token (tokens are JMAP-only). - This resolves the long-open JMAP-vs-CalDAV question. -- **Stack:** Hono + Drizzle (mysql2/MariaDB) + tsdav + ical.js + rrule + vite-plugin-pwa + - web-push + @hono/oidc-auth; SSE (+ optional Redis) for live list sync. -- **CAL-08 personal-calendar overlay is spike-gated in Phase 1** with a shared-family-only fallback. -- v1 scope: all 4 calendar views; recurring create+display only (single-instance edit -> v1.x); - all 3 push types (event reminder, list-change, event-change). -- **Vertical MVP** structure (each phase = end-to-end user capability). -- Authelia already deployed + both members have accounts -> Phase 1 auth is OIDC-client - integration only. - - - -- None blocking. One important open question (see Pre-Execution / human actions): where the wife's - personal calendar actually lives (Fastmail vs iCloud) — determines whether CAL-08 is achievable. - - -## Required Reading (in order) -1. `.planning/ROADMAP.md` — the 5-phase plan and per-phase success criteria -2. `.planning/research/SUMMARY.md` — cross-cutting findings; read before Phase 1 planning -3. `.planning/research/PITFALLS.md` — phase-mapped risks (CalDAV RRULE/ETag, iOS Web Push, Authelia, Pangolin) -4. `.planning/REQUIREMENTS.md` — 20 v1 requirements + traceability -5. `.planning/PROJECT.md` — project context, constraints, open questions -6. `.planning/notes/familysync-architecture.md` — reasoning behind dropped options (Baikal/Vikunja) - -## Critical Anti-Patterns (do NOT repeat these) -- ANTI-PATTERN: Assuming JMAP for calendar read/write → it does not exist on Fastmail. Use CalDAV (tsdav) only. -- ANTI-PATTERN: Building calendar UI before proving the personal-calendar share works → Phase 1 spike gates CAL-08 first. -- ANTI-PATTERN: Treating iOS Web Push as working from a Safari tab → it requires an installed (Add-to-Home-Screen) PWA; the guided install UX is load-bearing, not optional. - -## Infrastructure State -- Unraid + Docker Compose host; Authelia deployed (both members have accounts); Pangolin/Newt tunnel for public access (no open ports); MariaDB available, Redis available, no PostgreSQL. -- Nothing for FamilySync deployed yet — greenfield repo, only `.planning/` + generated CLAUDE.md exist. - -## Pre-Execution Critique Required -- Before/at Phase 1: confirm where the wife's PERSONAL calendar lives (Fastmail vs iCloud). If iCloud, CAL-08 (personal overlay) is unreachable via the Fastmail broker and degrades to shared-only (v1.x). This is the Phase 1 go/no-go spike. - - -This is a clean handoff right after project setup — no mid-task state to untangle. The roadmap is -approved as-is. The one piece of judgment to carry forward is the CAL-08 risk: front-loaded into -Phase 1 deliberately. Knowing the wife's calendar location before planning Phase 1 would sharpen it. - - - -Start with: `/gsd-discuss-phase 1` — and open by confirming where the wife's personal calendar -lives, since it drives the CAL-08 go/no-go spike. - diff --git a/.planning/HANDOFF.json b/.planning/HANDOFF.json deleted file mode 100644 index 623bf4b..0000000 --- a/.planning/HANDOFF.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "version": "1.0", - "timestamp": "2026-06-03T20:34:20.534Z", - "phase": null, - "phase_name": "Pre-Phase-1 (project initialized, planning not started)", - "phase_dir": null, - "plan": null, - "task": null, - "total_tasks": null, - "status": "paused", - "completed_tasks": [ - {"id": 1, "name": "PROJECT.md written + committed", "status": "done", "commit": "ad2aad8"}, - {"id": 2, "name": "config.json (interactive, standard, parallel, balanced, research/plan-check/verifier/drift-guard on)", "status": "done", "commit": "1ce0348"}, - {"id": 3, "name": "Domain research — 4 parallel researchers + synthesis (STACK/FEATURES/ARCHITECTURE/PITFALLS/SUMMARY)", "status": "done", "commit": "0f79277"}, - {"id": 4, "name": "REQUIREMENTS.md — 20 v1 requirements across AUTH/CAL/LIST/NOTIF/PWA", "status": "done", "commit": "a20afd4"}, - {"id": 5, "name": "ROADMAP.md + STATE.md — 5 phases, MVP mode, 20/20 coverage; CLAUDE.md generated", "status": "done", "commit": "ac82d32"} - ], - "remaining_tasks": [ - {"id": 6, "name": "Phase 1 — Foundation + Broker Spike: discuss → plan → execute", "status": "not_started"}, - {"id": 7, "name": "Phase 2 — Calendar Display", "status": "not_started"}, - {"id": 8, "name": "Phase 3 — Event Write-Back + PWA Install", "status": "not_started"}, - {"id": 9, "name": "Phase 4 — Shared Lists + Live Sync", "status": "not_started"}, - {"id": 10, "name": "Phase 5 — Web Push Notifications", "status": "not_started"} - ], - "blockers": [], - "human_actions_pending": [ - {"action": "Confirm where the wife's PERSONAL calendar actually lives (Fastmail vs iCloud)", "context": "CAL-08 personal-calendar overlay depends on cross-account Fastmail CalDAV sharing, which is unconfirmed. If her calendar is on iCloud, the broker cannot reach it and CAL-08 degrades to shared-family-only (moves to v1.x). This is the Phase 1 go/no-go spike — knowing the answer up front de-risks planning.", "blocking": false}, - {"action": "Have a Fastmail app password ready for the broker account (CalDAV)", "context": "Fastmail calendar access is CalDAV-only (no JMAP for calendars). The broker authenticates with an app password, NOT an API token (API tokens are JMAP-only). Needed for the Phase 1 broker read spike.", "blocking": false}, - {"action": "Be ready to register FamilySync as an OIDC confidential client in the existing Authelia", "context": "Authelia is already deployed and both members already have accounts. Phase 1 auth = OIDC client registration + integration only (no Authelia deploy, no account provisioning).", "blocking": false} - ], - "decisions": [ - {"decision": "Calendar access is CalDAV-only; JMAP-for-calendars is not available on Fastmail", "rationale": "Fastmail docs: JMAP calendar support pending RFC 8984 finalization. Resolves the long-standing JMAP-vs-CalDAV open question.", "phase": "pre-1"}, - {"decision": "Recommended stack: Hono + Drizzle (mysql2/MariaDB) + tsdav + ical.js + rrule + vite-plugin-pwa + web-push + @hono/oidc-auth; SSE (+ optional Redis) for list sync", "rationale": "Full convergence across all 4 research threads; current/maintained libraries.", "phase": "pre-1"}, - {"decision": "Personal-calendar overlay (CAL-08) is spike-gated in Phase 1 with shared-only fallback", "rationale": "Cross-account CalDAV sharing unconfirmed and wife's cal may be on iCloud — biggest v1 risk; prove before building UI.", "phase": "1"}, - {"decision": "v1 includes all 4 calendar views, recurring create+display (single-instance edit -> v1.x), and all 3 push types", "rationale": "User scoping decisions during requirements.", "phase": "pre-1"}, - {"decision": "Vertical MVP structure (each phase = end-to-end user capability)", "rationale": "Fits broker-spike-then-build order; validates risk on a working slice early.", "phase": "pre-1"}, - {"decision": "Wall display deferred to v2; lists in MariaDB (no Vikunja); calendar on Fastmail (no Baikal)", "rationale": "From PROJECT.md + architecture note; v1 core is calendar + lists.", "phase": "pre-1"} - ], - "uncommitted_files": [], - "next_action": "Run /gsd-discuss-phase 1 (Foundation + Broker Spike). Open with confirming where the wife's personal calendar lives, since it drives the CAL-08 go/no-go.", - "context_notes": "Project initialization via /gsd-new-project is fully complete and committed; working tree is clean. No phase/plan/task work has started. The roadmap is approved (5 phases, MVP mode). The single most important thing carried forward: the personal-calendar overlay risk (CAL-08) — Fastmail is CalDAV-only and cross-account sharing is unproven. Phase 1 front-loads this as a go/no-go spike." -} diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index cee1eb3..0004dec 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -7,6 +7,7 @@ FamilySync is built in five phases, each delivering an end-to-end user-observabl ## Phases **Phase Numbering:** + - Integer phases (1, 2, 3): Planned milestone work - Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED) @@ -21,72 +22,88 @@ Decimal phases appear between their surrounding integers in numeric order. ## Phase Details ### Phase 1: Foundation + Broker Spike + **Goal**: The app stack is running, both members can authenticate, and the CalDAV broker can read Fastmail calendars — with a confirmed go/no-go decision on personal-calendar cross-account sharing **Mode:** mvp **Depends on**: Nothing (first phase) **Requirements**: AUTH-01, AUTH-02, AUTH-03, CAL-01, CAL-08 **Success Criteria** (what must be TRUE): + 1. Both members can reach the app URL, authenticate through Authelia OIDC, and land on the app home page without entering any Fastmail credentials 2. Sessions persist across browser restarts — neither member is asked to log in again on the next visit 3. Each member is assigned a stable, distinct display color that does not change between sessions 4. The broker successfully fetches and caches at least one event from the shared Fastmail calendar via CalDAV PROPFIND/REPORT 5. The personal-calendar ACL spike produces a documented go/no-go decision: either the broker token sees the wife's personal calendar after Fastmail share+accept, or the fallback strategy (shared-family-only or per-member app password) is chosen and recorded + **Plans**: 4 plans Plans: -- [ ] 01-01-PLAN.md — Walking skeleton: monorepo scaffold + Docker/MariaDB + Drizzle schema (push) + /health end-to-end slice + Vitest Wave 0 harness + +- [x] 01-01-PLAN.md — Walking skeleton: monorepo scaffold + Docker/MariaDB + Drizzle schema (push) + /health end-to-end slice + Vitest Wave 0 harness - [ ] 01-02-PLAN.md — Authelia OIDC slice: stable identity (iss+sub) + auto-assigned member color + /api/me + authenticated PWA shell (AUTH-01/02/03) - [ ] 01-03-PLAN.md — CalDAV broker slice: AES-256-GCM credential encryption + tsdav broker + ical.js sync (all-day DATE) + ctag poller + /api/events (CAL-01) - [ ] 01-04-PLAN.md — Integration + gate: wire poller/routes, event-proof landing page, CAL-08 spike + go/no-go doc, live Pangolin deploy + SSE smoke test ### Phase 2: Calendar Display + **Goal**: Both members can see a unified, color-coded calendar aggregating all accessible Fastmail calendars across day, week, month, and agenda views — read-only, no write-back yet **Mode:** mvp **Depends on**: Phase 1 **Requirements**: CAL-02, CAL-03, CAL-07 **Success Criteria** (what must be TRUE): + 1. Opening the app shows a color-coded calendar where each member's events appear in their assigned color, with shared events distinguishable from personal events 2. The user can switch between day, week, month, and agenda views and all events render correctly in each view 3. A recurring event (e.g., weekly meeting) displays all its occurrences correctly in the current view window, including correct behavior across DST boundaries 4. All-day events (birthdays, holidays) appear as full-day banners on the correct date with no timezone shift + **Plans**: TBD **UI hint**: yes ### Phase 3: Event Write-Back + PWA Install + **Goal**: Both members can create, edit, and delete events that are written back to the correct Fastmail calendar, and the app is installable to the iPhone and Android home screens with a guided onboarding flow **Mode:** mvp **Depends on**: Phase 2 **Requirements**: CAL-04, CAL-05, CAL-06, CAL-07, PWA-01, PWA-02 **Success Criteria** (what must be TRUE): + 1. A member can create a timed or all-day event (including recurring events) in the app and see it appear in the native Fastmail app within the next sync cycle 2. A member can edit an existing event's title, time, or description and the change persists correctly in Fastmail 3. A member can delete an event and it disappears from all views on the next sync 4. On Android, the app shows a browser install prompt and installs to the home screen; on iOS, the app shows a guided "Add to Home Screen" walkthrough with annotated screenshots that a non-technical user can follow independently 5. The installed PWA opens full-screen without browser chrome on both iOS and Android + **Plans**: TBD **UI hint**: yes ### Phase 4: Shared Lists + Live Sync + **Goal**: Both members can create and manage shared named lists with real-time co-edit sync — edits by one member appear for the other without any manual refresh **Mode:** mvp **Depends on**: Phase 1 **Requirements**: LIST-01, LIST-02, LIST-03, LIST-04 **Success Criteria** (what must be TRUE): + 1. Either member can create a named list (e.g., "Groceries") and delete a list they no longer need 2. Either member can add items to a list, check items off, reorder them by drag-and-drop, and delete individual items 3. When one member adds or checks off an item, the other member sees the change appear in the list within a few seconds without refreshing — even if they reconnect after a brief network gap + **Plans**: TBD **UI hint**: yes ### Phase 5: Web Push Notifications + **Goal**: Both members receive timely Web Push alerts for upcoming events, event changes made by the other member, and list changes — reliably on both iOS and Android **Mode:** mvp **Depends on**: Phase 3, Phase 4 **Requirements**: NOTIF-01, NOTIF-02, NOTIF-03 **Success Criteria** (what must be TRUE): + 1. A member receives a push notification on their phone approximately 15 minutes before a calendar event starts — delivered to the installed PWA, including on iOS 2. When the other member adds or changes a calendar event, the first member receives a push notification with the event title and action described in the payload 3. When the other member modifies a shared list (adds, checks off, or deletes an item), the first member receives a push notification identifying the list and the change 4. After an extended period of app inactivity, push notifications are still delivered (subscription health-check prevents silent revocation on iOS) + **Plans**: TBD ## Progress @@ -97,7 +114,7 @@ Note: Phase 4 depends only on Phase 1 and can begin as soon as Phase 1 is comple | Phase | Plans Complete | Status | Completed | |-------|----------------|--------|-----------| -| 1. Foundation + Broker Spike | 0/? | Not started | - | +| 1. Foundation + Broker Spike | 1/4 | In Progress| | | 2. Calendar Display | 0/? | Not started | - | | 3. Event Write-Back + PWA Install | 0/? | Not started | - | | 4. Shared Lists + Live Sync | 0/? | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index 7811d5c..db77d2c 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -2,14 +2,14 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: milestone -status: planning +status: executing stopped_at: Phase 1 context gathered -last_updated: "2026-06-04T11:55:07.999Z" -last_activity: 2026-06-03 — Roadmap created +last_updated: "2026-06-04T13:45:46.863Z" +last_activity: 2026-06-04 -- Phase 01 execution started progress: total_phases: 5 completed_phases: 0 - total_plans: 0 + total_plans: 4 completed_plans: 0 percent: 0 --- @@ -21,14 +21,14 @@ progress: See: .planning/PROJECT.md (updated 2026-06-03) **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 1 — Foundation + Broker Spike +**Current focus:** Phase 01 — foundation-broker-spike ## Current Position -Phase: 1 of 5 (Foundation + Broker Spike) -Plan: 0 of ? in current phase -Status: Ready to plan -Last activity: 2026-06-03 — Roadmap created +Phase: 01 (foundation-broker-spike) — EXECUTING +Plan: 1 of 4 +Status: Executing Phase 01 +Last activity: 2026-06-04 -- Phase 01 execution started Progress: [░░░░░░░░░░] 0% diff --git a/.planning/config.json b/.planning/config.json index 4f12bfb..3159ec8 100644 --- a/.planning/config.json +++ b/.planning/config.json @@ -41,7 +41,8 @@ "post_planning_gaps": true, "security_enforcement": true, "security_asvs_level": 1, - "security_block_on": "high" + "security_block_on": "high", + "_auto_chain_active": false }, "ship": { "pr_body_sections": [ diff --git a/.planning/phases/01-foundation-broker-spike/01-01-SUMMARY.md b/.planning/phases/01-foundation-broker-spike/01-01-SUMMARY.md index c61f6a2..056872b 100644 --- a/.planning/phases/01-foundation-broker-spike/01-01-SUMMARY.md +++ b/.planning/phases/01-foundation-broker-spike/01-01-SUMMARY.md @@ -96,7 +96,7 @@ completed: "2026-06-04" # Phase 01 Plan 01: Walking Skeleton — Summary -**pnpm monorepo with Hono API, Drizzle/MariaDB schema (4 tables), Docker Compose stack, and /health route with real DB round-trip — Tasks 1-2 complete; stopped at checkpoint Task 3 (drizzle-kit push requires running Docker stack)** +**pnpm monorepo with Hono API, Drizzle/MariaDB schema (4 tables), Docker Compose stack, and /health route with real DB round-trip — ALL 3 tasks complete. Task 3 checkpoint cleared by orchestrator: stack brought up, `drizzle-kit push` applied the 4 tables to live MariaDB, and `/health` returned `{"ok":true,"db":"up"}` end-to-end. Required fixing 3 Docker build defects (see Deviations).** ## Performance @@ -163,10 +163,21 @@ None — plan executed exactly as specified. One minor pnpm API difference (allo - **Verification:** Both health tests pass; no hoisting warnings - **Committed in:** `96cda58` (Task 2 feat commit) +**3. [Checkpoint clearing - Blocking] Docker image build broken for pnpm workspace** +- **Found during:** Task 3 (orchestrator bringing up the stack to clear the checkpoint) +- **Issue:** The original `apps/api/Dockerfile` built from a `./apps/api` context and could not work in a pnpm workspace: + 1. `COPY package.json pnpm-lock.yaml* ./` + `pnpm install --frozen-lockfile` failed (`ERR_PNPM_NO_LOCKFILE`) — the lockfile lives at the repo root, not in `apps/api/`. + 2. pnpm 11 refused to run `esbuild`'s build script (`ERR_PNPM_IGNORED_BUILDS`) because the root `pnpm-workspace.yaml` (which carries `allowBuilds.esbuild`) was outside the build context. Neither package.json `pnpm.onlyBuiltDependencies` nor `.npmrc dangerously-allow-all-builds` resolved it in the isolated context. + 3. `dev` stage ran `node --watch dist/index.js` but never compiled `src`→`dist`; production stage had invalid Dockerfile syntax (`COPY apps/pwa/dist/ ./public/ 2>/dev/null || true`) referencing a path outside its context. +- **Fix:** Switched to the correct monorepo pattern — build from the **repo-root context** (`docker-compose.yml` `build.context: .`, `dockerfile: apps/api/Dockerfile`), copy the root `pnpm-workspace.yaml` + `pnpm-lock.yaml` + both workspace `package.json`s, and `pnpm install --frozen-lockfile --filter @familysync/api...`. Reordered stages so `production` is the default; `dev` now reuses the builder output; dropped the invalid PWA COPY. Updated `docker-compose.dev.yml` volume mount to `./apps/api/src:/app/apps/api/src`. +- **Files modified:** apps/api/Dockerfile, docker-compose.yml, docker-compose.dev.yml +- **Verification:** `docker compose up -d --build` succeeds; `drizzle-kit push` applied 4 tables; `curl /health` → `{"ok":true,"db":"up"}`; `pnpm test` → 2 passed / 21 todo. +- **Committed in:** `fix(01-01): build Docker image from repo-root pnpm workspace context` + --- -**Total deviations:** 2 auto-fixed (1 blocking, 1 bug) -**Impact on plan:** Both fixes necessary for install/tests to work. No scope creep. +**Total deviations:** 3 (2 auto-fixed during execution, 1 Docker-build fix while clearing the Task 3 checkpoint) +**Impact on plan:** All fixes necessary for install/tests/stack to work. No scope creep — same walking-skeleton capability, corrected build topology. ## Issues Encountered @@ -195,9 +206,9 @@ No new threat surface beyond what was planned in the threat model: ## Next Phase Readiness -- Task 3 checkpoint must be cleared first (drizzle-kit push + Docker stack verification) -- After Task 3: Plan 02 (OIDC auth) can proceed — imports `db`, `users` schema -- Plan 03 (broker) depends on Plan 02 identity layer + this schema +- Task 3 checkpoint CLEARED — schema pushed, stack verified, `/health` green +- Plan 02 (OIDC auth) can proceed — imports `db`, `users` schema +- Plan 03 (broker) depends on this schema + crypto pattern ## Self-Check: PASSED