wip: phase 03 Gate 2 paused — live bring-up working; edit/delete join fix is the next blocker
This commit is contained in:
@@ -1,78 +1,75 @@
|
||||
---
|
||||
context: phase
|
||||
phase: 03-event-write-back-pwa-install
|
||||
task: 1
|
||||
total_tasks: 3
|
||||
task: "Gate 2 Part D — edit/delete join fix (diagnosed, not started)"
|
||||
total_tasks: null
|
||||
status: in_progress
|
||||
last_updated: 2026-06-06T01:47:16.059Z
|
||||
last_updated: 2026-06-07T02:39:57.236Z
|
||||
---
|
||||
|
||||
<current_state>
|
||||
Phase 03 **gap-closure execution is complete** — all four gap plans (03-09, 03-12, 03-10, 03-11) executed in dependency order, merged to `gsd/v1.0-milestone`, with 92 API + 126 PWA tests green and both `tsc --noEmit` clean. Working tree is clean.
|
||||
# Critical Anti-Patterns
|
||||
|
||||
Work then shifted to an **operational task: spin up the Docker stack for newt/Pangolin external exposure**. This is paused mid-investigation, awaiting two decisions from the user (see Human Actions). The user installed newt into WSL2 and wants to map the stack's ports.
|
||||
| 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>
|
||||
|
||||
Gap-closure plans (all merged, all SUMMARY.md committed):
|
||||
- 03-09: Route-layer reachability — zod `title/start/end` contract (CR-01) + real OIDC `iss/sub`→`users.id` via upsertUser (CR-06). merge `578315c`.
|
||||
- 03-12: EventForm edit/a11y — WR-03 (blank/recurrence edit), WR-05 (zone-consistent dates), WR-07 (focus trap), IN-03/04. merge `1fc56f4`.
|
||||
- 03-10: Outbox real VEVENT dispatch — CR-02 (`buildVeventString` wired), CR-03 (fail-closed creds), WR-01 (backoff index), WR-04 (all-day DTEND+1), WR-08, IN-01. merge `416e813`.
|
||||
- 03-11: Outbox durability — CR-04 (create-before-delete gating), CR-05 (drain concurrency guard), WR-02 (etag re-read). merge `0e4a263`.
|
||||
|
||||
Review-finding coverage: **16/19 closed — all 6 critical (CR) resolved.** STATE.md + ROADMAP.md updated and committed (`b3eff3c`).
|
||||
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).
|
||||
|
||||
Phase 03:
|
||||
- **03-08** — 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, cannot be driven by playwright-cli. This is the only incomplete plan; phase stays "In Progress" until it passes.
|
||||
- Deferred (non-blocking, not scoped into any gap plan): WR-06 (N+1 fetchCalendars perf), IN-02 (resolveDefaultView SSR-guard nit), IN-05 (terminal outbox rows never pruned).
|
||||
|
||||
Operational (the paused task):
|
||||
- Bring up the Docker stack for newt exposure once the two decisions below are made.
|
||||
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>
|
||||
|
||||
- Executed Phase 03 with `--gaps-only`; 03-08 intentionally excluded (human/device Gate 2).
|
||||
- Left WR-06 / IN-02 / IN-05 open — non-critical; all critical findings closed.
|
||||
- `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>
|
||||
- Docker bring-up paused pending two user decisions (auth mode + PWA serving).
|
||||
- `OIDC_ISSUER` and `OIDC_CLIENT_SECRET` are **empty** in `.env` → the production/real-OIDC path cannot run yet.
|
||||
- No PWA build in `apps/api/public` → API serves nothing at `/` (root 404s); `/health` and `/api/*` work.
|
||||
- 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. `docker-compose.yml` + `docker-compose.dev.yml` — stack definition. Base = production target (OIDC unconditional); dev override = dev target, `NODE_ENV=development`, exposes mariadb:3306 + redis:6379.
|
||||
2. `apps/api/src/index.ts` (lines ~15-61) — `devBypassActive` logic (`NODE_ENV!='production' && DEV_AUTH_BYPASS==='true'`) and the `serveStatic('./public')` PWA-serving + catch-all.
|
||||
3. `apps/api/Dockerfile` — note: production stage does NOT build the PWA into `./public`; comment says PWA "built and served separately".
|
||||
4. `.planning/HANDOFF.json` — machine-readable mirror of this state.
|
||||
|
||||
## Critical Anti-Patterns
|
||||
_None discovered through failure this session. The "Exited (255)" on api/redis was a WSL/daemon stop 12h ago, NOT a crash — the api boots cleanly on :3000 (logs show "FamilySync API running on http://localhost:3000", only a harmless `serveStatic: root './public' not found` warning). Do not chase it as a bug._
|
||||
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
|
||||
- `familysync-mariadb-1`: **Up (healthy)**, `0.0.0.0:3306->3306`.
|
||||
- `familysync-api-1`: Exited (255) — stale from WSL stop, not a crash. Boots fine when restarted.
|
||||
- `familysync-redis-1`: Exited (255) — same; present-but-unused until Phase 4.
|
||||
- Docker 29.3.1, Compose v5.1.1.
|
||||
- newt installed in WSL2 (per user) for Pangolin tunnel; the ports it maps are unconfirmed from the repo side.
|
||||
|
||||
## Human Actions Pending (blocking the Docker bring-up)
|
||||
1. **Auth mode** — dev-bypass (`DEV_AUTH_BYPASS=true` + dev compose, works immediately, OIDC skipped) **vs** real OIDC (fill `OIDC_ISSUER`/`OIDC_CLIENT_SECRET` for the actual 03-08 Gate 2 login over Pangolin).
|
||||
2. **PWA serving** — build PWA into `apps/api/public` (single port :3000 for newt) **vs** vite dev on :5173 (two ports) **vs** API-only for now.
|
||||
3. (non-blocking) Confirm which ports the Pangolin/newt config actually maps.
|
||||
- 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>
|
||||
Mid-flow when paused: I had just run `AskUserQuestion` with the two decisions above and the user interrupted to `/gsd-pause-work` instead of answering. Nothing was started/changed for the Docker task — purely investigation. The gap-closure work is done and safe.
|
||||
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>
|
||||
Ask the user the two pending decisions (auth mode + PWA serving). Then bring up the stack:
|
||||
- Dev-bypass path: ensure `DEV_AUTH_BYPASS=true` in `.env`, then `docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build`. If serving the PWA via API, first `pnpm --filter @familysync/pwa build` and place output in `apps/api/public`; otherwise run `pnpm --filter @familysync/pwa dev` on :5173.
|
||||
- Real-OIDC path: wait for the user to fill `OIDC_ISSUER`/`OIDC_CLIENT_SECRET`, then `docker compose up -d --build` (production), and build the PWA into `apps/api/public` so the root URL serves.
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user