wip: phase 03 Gate 2 paused — live bring-up working; edit/delete join fix is the next blocker

This commit is contained in:
Lucas Berger
2026-06-06 22:42:06 -04:00
parent bdbb9b8324
commit 3ae7724df1
2 changed files with 70 additions and 69 deletions
+26 -22
View File
@@ -1,40 +1,44 @@
{
"version": "1.0",
"timestamp": "2026-06-06T01:47:16.059Z",
"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": 8,
"task": 1,
"total_tasks": 3,
"plan": "Gate 2 (Part D live verification)",
"task": "delete/edit join fix (diagnosed, not started)",
"total_tasks": null,
"status": "paused",
"completed_tasks": [
{"id": "03-09", "name": "Route-layer reachability: zod title/start/end contract (CR-01) + real OIDC iss/sub->users.id (CR-06)", "status": "done", "commit": "578315c"},
{"id": "03-12", "name": "EventForm edit/a11y: WR-03/05/07, IN-03/04", "status": "done", "commit": "1fc56f4"},
{"id": "03-10", "name": "Outbox real VEVENT dispatch: CR-02/03, WR-01/04/08, IN-01", "status": "done", "commit": "416e813"},
{"id": "03-11", "name": "Outbox durability + concurrency + etag re-read: CR-04/05, WR-02", "status": "done", "commit": "0e4a263"}
{"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": "03-08", "name": "Gate 2 live verification: real Authelia OIDC over Pangolin + iOS standalone install + end-to-end Fastmail write round-trips (human/device checkpoint, not a gap plan)", "status": "not_started"},
{"id": "deferred-WR-06", "name": "triggerTargetedResync N+1 fetchCalendars per row (perf warning)", "status": "not_started"},
{"id": "deferred-IN-02", "name": "resolveDefaultView ignores SSR guard return (info/nit)", "status": "not_started"},
{"id": "deferred-IN-05", "name": "Outbox terminal rows never pruned (info, low urgency)", "status": "not_started"}
{"id": "edit-delete-join", "name": "BLOCKING: edit (PATCH /:uid/edit ~line295) + delete (DELETE /:uid ~line392) handlers in apps/api/src/routes/events.ts select calendars.url/userId WITHOUT joining calendars → Drizzle 503 → delete dialog never closes. Add .innerJoin(calendars, eq(calendarEvents.calendarId, calendars.id)) to both + regression test that runs the REAL query builder (existing tests mock db.select() and miss it).", "status": "not_started"},
{"id": "displayName", "name": "me.ts passes (missing) email claim as displayName and doesn't read name/preferred_username; oidc_iss also blank → legend name blank (user id=2 display_name=''). May also need Authelia to put name/email in ID token or call userinfo.", "status": "not_started"},
{"id": "events-filter", "name": "GET /api/events has no userId/isShared filter (returns all users' events). Latent now (only 1 real user) but a real bug for 2nd member.", "status": "not_started"},
{"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": "Docker bring-up for newt/Pangolin exposure paused awaiting two decisions: (1) auth mode, (2) PWA serving", "type": "human_action", "workaround": "Dev-bypass + vite gives a working stack immediately; real OIDC needs creds"},
{"description": "OIDC_ISSUER and OIDC_CLIENT_SECRET are empty in .env — real-OIDC (production) auth path cannot run", "type": "external", "workaround": "Use DEV_AUTH_BYPASS=true with dev compose, or fill Authelia creds"},
{"description": "No PWA build in apps/api/public — API 404s the frontend at root", "type": "technical", "workaround": "Run `pnpm --filter @familysync/pwa build` into apps/api/public, or run vite dev separately on :5173"}
{"description": "Delete (and latently edit) events 503 — missing calendars join in events.ts. Diagnosed; fix not yet applied. User was choosing how to land it (batch vs quick-only vs inline) and paused.", "type": "technical", "workaround": "none — delete is broken until fixed"},
{"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": "Decide auth mode: dev-bypass (works now) vs real OIDC (fill OIDC_ISSUER/CLIENT_SECRET for Gate 2 over Pangolin)", "context": "Needed before api container is usable through newt", "blocking": true},
{"action": "Decide PWA serving: build into API (one port :3000) vs vite dev (:5173) vs API-only", "context": "Determines which ports newt maps", "blocking": true},
{"action": "Confirm which ports the Pangolin/newt config actually maps (3000 api, 5173 pwa, 3306 db, 6379 redis)", "context": "Unknown from repo", "blocking": false}
{"action": "Re-test in browser: create a NEW 9am event (verify time + your-user attribution); the OLD wrong-time test event (5am) should be deleted once delete is fixed.", "context": "BUG A/B fixes deployed; old test event was written pre-fix with bad time.", "blocking": false},
{"action": "Decide how to land the edit/delete join fix (batch with displayName+events-filter, quick-only, or inline).", "context": "User paused mid-AskUserQuestion on this.", "blocking": true}
],
"decisions": [
{"decision": "Executed Phase 03 with --gaps-only; 03-08 (Gate 2 human/iOS checkpoint) intentionally excluded", "rationale": "Gap plans (gap_closure: true) were 03-09/10/11/12 only; 03-08 needs human/device and cannot be driven by playwright-cli", "phase": "03"},
{"decision": "Left WR-06, IN-02, IN-05 open", "rationale": "Non-critical (1 perf warning + 2 info); not scoped into any gap plan. All 6 critical CR findings closed", "phase": "03"}
{"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": "Answer the two pending Docker decisions (auth mode + PWA serving), then bring up the stack: `docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d` (dev-bypass path) or production compose with OIDC creds filled.",
"context_notes": "Gap-closure execution is fully complete, merged to gsd/v1.0-milestone, 92 api + 126 pwa tests green, both tsc clean. Then user asked to spin up Docker for newt exposure. Investigation found: mariadb up/healthy; api+redis exited 255 (just WSL stop, NOT a crash — api boots fine on :3000, only warns about missing ./public). Architecture: api container serves BOTH api routes AND PWA static from ./public; production mounts OIDC unconditionally (creds empty -> broken), dev with DEV_AUTH_BYPASS=true skips OIDC. I asked the user the two decision questions via AskUserQuestion; they interrupted to pause instead of answering."
"next_action": "Fix the edit+delete missing-calendars-join bug in apps/api/src/routes/events.ts (add .innerJoin(calendars, eq(calendarEvents.calendarId, calendars.id)) to both the PATCH /:uid/edit (~line 295) and DELETE /:uid (~line 392) lookups), with a regression test that exercises the real query builder. Re-offer the user the landing choice they were deciding (batch with displayName + /api/events filter, quick-only, or inline). Then rebuild (docker compose up -d --build) and have the operator re-test delete in the browser.",
"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)."
}