{ "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) 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 , 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)." }