Files
familysync/.planning/STATE.md
T

147 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
gsd_state_version: 1.0
milestone: v1.0
milestone_name: milestone
status: executing
stopped_at: Phase 5 UI-SPEC approved
last_updated: "2026-06-10T00:34:28.043Z"
last_activity: 2026-06-10 -- Phase 05 execution started
progress:
total_phases: 15
completed_phases: 4
total_plans: 36
completed_plans: 28
percent: 27
---
# Project State
## Project Reference
See: .planning/PROJECT.md (updated 2026-06-07)
**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 05 — web-push-notifications
## Current Position
Phase: 05 (web-push-notifications) — EXECUTING
Plan: 1 of 8
Status: Executing Phase 05
Last activity: 2026-06-10 -- Phase 05 execution started
Progress: [██████████] 100%
## Performance Metrics
**Velocity:**
- Total plans completed: 17
- Average duration: -
- Total execution time: 0 hours
**By Phase:**
| Phase | Plans | Total | Avg/Plan |
|-------|-------|-------|----------|
| 02 | 5 | - | - |
| 03 | 12 | - | - |
**Recent Trend:**
- Last 5 plans: -
- Trend: -
*Updated after each plan completion*
| Phase 03 P01 | 45 | 5 tasks | 8 files |
| Phase 03 P03-02 | 4 | 2 tasks | 2 files |
| Phase 03 P03-03 | 7 | 3 tasks | 2 files |
| Phase 03 P03-07 | 5 | 2 tasks | 7 files |
| Phase 03 P03-04 | 15 | 2 tasks | 3 files |
| Phase 03 P03-05 | 6 | 3 tasks | 6 files |
| Phase 04 P01 | 65 | 4 tasks | 17 files |
| Phase 04 P03 | 12 | 2 tasks | 9 files |
| Phase 04 P04 | 11 | 2 tasks | 10 files |
| Phase 04 P05 | 10 | 2 tasks | 4 files |
| Phase 04 P06 | 11 | 2 tasks | 7 files |
| Phase 04 P07 | 6 | 2 tasks | 4 files |
## Accumulated Context
### Decisions
Decisions are logged in PROJECT.md Key Decisions table.
Recent decisions affecting current work:
- CAL-08 RESOLVED → GO (Phase 1): per-member Fastmail app password reaches all of that account's calendars; no cross-account ACL needed. Unified view stands; no shared-only fallback. See CAL-08-DECISION.md.
- D-14 (2026-06-04): Phase 1 Gate 2 (live Authelia/Pangolin) deferred. SSE-over-Pangolin smoke = hard gate before Phase 4; live AUTH smoke incl. iOS standalone-PWA folded into Phase 3. Phases 23 build behind a dev-auth bypass. Tracked in 01-HUMAN-UAT.md + docs/deployment.md.
- D-15 (2026-06-04): Validate real topology via local Newt connector + test subdomain through Pangolin (Mode A), not an Unraid deploy; Unraid reserved for go-live.
- CalDAV locked: Fastmail does not expose calendars over JMAP. CalDAV via tsdav is the only protocol. No reconsideration.
- Identity: Use oidc_iss + oidc_sub as stable composite key. Never email.
- Real-time transport: Prefer SSE over WebSocket (proxy-resilient through Pangolin). SSE pass-through STILL UNVERIFIED — gate before Phase 4 (D-14).
- Recurring events: Create + display only in v1 (CALDAV:expand on server side). Single-occurrence edit deferred to v1.x.
- [Phase ?]: D-Task5-DDL: drizzle-kit push unsafe on MariaDB 11 (mysql dialect misreads metadata, schedules truncate). Additive DDL hand-applied; adopt drizzle-kit generate+migrate before next schema change.
- [Phase ?]: D-02-RRULE: use ICAL.Recur.fromString + ICAL.Property for RRULE serialization — addPropertyWithValue produces char-split output for RECUR types
- [Phase ?]: resolveUserId helper uses any type to avoid Hono context generic complexity — internal helper only
- [Phase ?]: Write endpoints enqueue-only (calendarOutbox) with 202 optimistic-accept; no Fastmail call in routes (D-12/D-05)
- [Phase ?]: GET /writable-calendars is authoritative D-03 writable-set endpoint: WHERE userId=currentUser.id OR isShared=1
- [Phase ?]: D-03-07-icons: PWA icons generated via pure Node.js (zlib/Buffer) when ImageMagick/sharp unavailable; placeholder art; operator can replace in public/ without code changes
- [Phase ?]: D-03-07-layout: InstallPrompt desktop layout wraps CalendarContent in flex-column div so banner appears above calendar grid
- [Phase ?]: D-03-04-hoisting: vi.hoisted() required for vi.mock() factory variables when test file has static import of module under test (TDZ issue)
- [Phase ?]: D-03-04-where: Drizzle types disallow chained .where().where(); use and() in single .where() call
- [Phase ?]: D-01 calendar default: last-used URL from localStorage (eventForm.lastCalendarUrl), first writable calendar as fallback
- [Phase ?]: D-02 calendar picker: hidden when writableCalendars.length === 1, shown when >1 — authoritative from GET /api/events/writable-calendars
- [Phase ?]: T-03-15 XSS: EventForm renders all values as plain-text JSX children; no dangerouslySetInnerHTML in code
- [Phase ?]: Phase 4 Plan 1
- [Phase ?]: D-04 GET scoped: two-select + Set union (owner + list_shares); ListDeleteDialog props-driven to preserve calendarStore dialog; zValidator returns 400 not 422 per existing convention
- [Phase 04-04]: listItemsRouter separate from listsRouter, mounted at /api/list-items for PATCH/DELETE item routes per RESEARCH architecture diagram
- [Phase 04-04]: Uncheck rank recomputed to active-bottom (generateKeyBetween(lastActiveRank, null)) in same DB write (Open Question 2 resolved)
- [Phase 04-04]: Delete-wins no-rollback: deleteMutation has no onError handler; item removal from cache is final (D-09)
- [Phase ?]: LIST-04: SSE connection lives in ListDetail (not hoisted to Lists route); Phase 5 push will own session lifecycle
- [Phase 04-07]: D-04-07-collation: Drizzle 0.45.x has no first-class collation option on varchar; used customType to emit varchar(255) COLLATE utf8mb4_bin for list_items.rank — keeps schema-as-code + generate+migrate workflow
- [Phase 04-07]: D-04-07-guard: isShared owner-only guard placed after access check, before updateValues construction; mirrors DELETE handler idiom (if !access.isOwner → 403)
### Roadmap Evolution
- Phase 6 added (2026-06-07): UX Polish — all-day visual distinction, event-form date/recurrence behavior, recurring-series edit, auth-flow smoothing. Candidate scope pulls from backlog 999.2/999.3/999.6/999.7/999.8/999.9.
### Pending Todos
- **Fix `docs/deployment.md` local-dev command** — the documented dev run is wrong: the API dev script (`node --watch dist/index.js`) does NOT load `.env`, and `DB_HOST` defaults to `localhost` with an empty password. Correct local-dev command is: `pnpm --filter @familysync/api build && set -a; source .env; set +a && DEV_AUTH_BYPASS=true DB_HOST=localhost pnpm --filter @familysync/api dev` (+ `pnpm --filter @familysync/pwa dev`). Consider adding `--env-file=.env` to the dev script so this is automatic. (Surfaced during Phase 2 UAT.)
- **REQUIREMENTS.md traceability gap** — phase.complete flagged 6 REQ-IDs in the body missing from the Traceability table: CAL-09, CAL-10, CAL-11, CAL-12, DISP-01, DISP-02. Add them to keep traceability in sync (likely Phase 4/5/display requirements).
- **DST spring-forward spot-check (Phase 2)** — recurring/DST is implemented and code-verified (VTIMEZONE before expansion + local display TZ), and operator approved general times; navigating to March 2026 to eyeball the spring-forward transition is a recommended future spot-check.
- **Gitea CI — regression on PR to main + Docker build/publish** — self-hosted Gitea runner exists but no CI yet. Add `.gitea/workflows/`: full regression (lint/typecheck/unit + API integration tests against a MariaDB service container + PWA build) gating PRs to `main`, plus build/publish the Docker image to the Gitea registry. Decide image matrix (API-only vs API+PWA — only `apps/api/Dockerfile` exists today) and tag policy. See pending todo `2026-06-10-gitea-ci-regression-and-docker-publish.md`.
### Blockers/Concerns
- ~~Phase 1: Personal-calendar CalDAV ACL~~ RESOLVED → CAL-08 GO (per-member app password; no cross-account ACL).
- ~~Phase 4 ENTRY GATE: Pangolin SSE pass-through (issue #1034) unverified~~ CLEARED 2026-06-08 — SSE smoke PASS over familysync-dev.bergerhouse.net (~6 min, 35 heartbeats, buffering off, no cut). Live sync unblocked. Caveat: untested for a max total connection-duration cap; residual risk covered by Phase 4 design (D-10/D-11/D-12). See quick 260607-u8o + 03-GATE2-RESULTS.md Part C.
- Phase 3: iOS standalone-PWA + Authelia login is load-bearing for the wife and is the first real external auth test (carried Gate 2 item, D-14). Also: iOS install guide is load-bearing — she gets no push notifications if she does not install the PWA.
- Phase 2/3 dev: build behind a documented dev-auth bypass until Gate 2 deploy (D-14).
- Phase 5: iOS push subscriptions silently revoked after 3 silent pushes. Subscription health-check and event.waitUntil() are mandatory from day one.
### Quick Tasks Completed
| # | Description | Date | Commit | Directory |
|---|-------------|------|--------|-----------|
| 260606-tv8 | Fix missing sign-in redirect in the PWA (Phase 03 auth-entry gap from Gate 2): guarded /api/login → / + full-page redirect on unauthenticated fetchMe | 2026-06-07 | 7c6531f | [260606-tv8-fix-missing-sign-in-redirect-in-the-pwa-](./quick/260606-tv8-fix-missing-sign-in-redirect-in-the-pwa-/) |
| 260607-l6l | Batch-fix Phase 03 write-path bugs: events.ts edit/delete missing calendars innerJoin (503, BLOCKING) + handler-coupled regression test; shared deriveDisplayName helper (me.ts + resolveUserId, corrects blank rows); GET /api/events userId/isShared ownership filter | 2026-06-07 | 2870413 | [260607-l6l-fix-phase-03-write-path-correctness-bugs](./quick/260607-l6l-fix-phase-03-write-path-correctness-bugs/) |
| 260607-u8o | Record SSE-over-Pangolin smoke test PASS (Phase 4 entry gate, D-14 / issue #1034) — updated 01-HUMAN-UAT item 4 + 03-GATE2-RESULTS Part C to PASS with live evidence | 2026-06-08 | 26655cf | [260607-u8o-record-sse-over-pangolin-smoke-test-pass](./quick/260607-u8o-record-sse-over-pangolin-smoke-test-pass/) |
## Deferred Items
| Category | Item | Status | Deferred At |
|----------|------|--------|-------------|
| Calendar | Single-occurrence recurring edit (RECURRENCE-ID) | v1.x | Roadmap |
| Calendar | "This and following" recurring edit | v1.x | Roadmap |
| Calendar | Apple Calendar native subscribe URL docs | v1.x | Roadmap |
| Calendar | Secondary timezone display toggle | v1.x | Roadmap |
| Display | Wall-display / kiosk dashboard | v2 | PROJECT.md |
| Calendar | Mark shared-family calendar `is_shared=1` — operator must first create a "Family" calendar on the primary Fastmail account + share it, let the poller sync it, then run `UPDATE calendars SET is_shared=1 WHERE id=<new row>`. Until then the shared color lane is empty (correct). | Phase 2 (deferred, D-16) | 2026-06-05 |
## Session Continuity
Last session: 2026-06-09T22:04:34.038Z
Stopped at: Phase 5 UI-SPEC approved
Resume file: .planning/phases/05-web-push-notifications/05-UI-SPEC.md