Files
familysync/.planning/phases/03-event-write-back-pwa-install/.continue-here.md
T

7.6 KiB

context, phase, task, total_tasks, status, last_updated
context phase task total_tasks status last_updated
phase 03-event-write-back-pwa-install Gate 2 Part D — edit/delete join fix (diagnosed, not started) null in_progress 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 b46b25bbdbb9b8):

  • 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>
- Delete/edit events 503 (missing calendars join) — trivial fix, not yet applied. - playwright-cli broken in this env — verify via curl + operator's browser.

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.
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.

<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>