Files
Lucas Berger 982438dc10 style(13-03): apply Prettier formatting across repo
Mechanical reformat — no logic changes. 398 files changed, 19125
insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc
(singleQuote:true, semi:true, tabWidth:2, trailingComma:all,
printWidth:100). Isolated per D-13-08 for reviewability.
2026-06-11 20:35:18 -04:00

295 lines
27 KiB
Markdown
Raw Permalink 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.
# Codebase Concerns
**Analysis Date:** 2026-06-09
## Tech Debt
**Drizzle-kit push unsafe on MariaDB 11:**
- Issue: `drizzle-kit push` emits false destructive DDL on MariaDB 11 (mysql dialect) — misreads table metadata and schedules column truncation in the migration diff. This destroys production data if applied blindly.
- Files: `apps/api/src/db/schema.ts`, `apps/api/drizzle.config.ts`, `.planning/STATE.md` (D-Task5-DDL)
- Impact: Any schema change requires manual validation. Automated push pipelines are unsafe.
- Current mitigation: All additive DDL hand-applied. Database migrations live in `apps/api/src/db/migrations/` (SQL files). Documented in STATE.md.
- Fix approach: Adopt `drizzle-kit generate+migrate` workflow for all future schema changes — generate the diff, manually review the SQL, then apply via migration file. Never use `push` on MariaDB without field-by-field validation. If multi-replica deployment is needed, consider PostgreSQL migration at that point.
**Dev-auth bypass lacks production guard redundancy:**
- Issue: The `DEV_AUTH_BYPASS` environment variable is guarded by a `NODE_ENV !== 'production'` check in `index.ts` (line 19), but relies on correct deployment configuration. If `NODE_ENV` is accidentally omitted from the production Docker Compose, the bypass could activate.
- Files: `apps/api/src/index.ts` (lines 1926), `apps/api/src/auth/devBypass.ts`
- Impact: Unauthenticated access to the API in production if misconfigured.
- Current mitigation: The `docker-compose.yml` should explicitly set `NODE_ENV=production`; `.env.example` has `DEV_AUTH_BYPASS` commented out. Documented in `docs/deployment.md` (line 266268).
- Fix approach: Add a startup assertion that logs an error and exits if `NODE_ENV !== 'production'` and `DEV_AUTH_BYPASS=true` are both detected. Consider a secondary check in the oidcAuthMiddleware instantiation.
**Event datetime serialization was timezone-naive (FIXED in Phase 3):**
- Issue: The PWA's `EventForm` previously sent naive local wall-clock strings (no UTC offset) to the API; the outbox worker's `new Date(string)` parsed them in the container's UTC timezone, resulting in events written 4 hours early/late. Fixed in Phase 3 quick 260607-l6l.
- Files: `apps/pwa/src/lib/eventDateTime.ts` (new), `apps/pwa/src/components/EventForm.tsx` (updated)
- Impact: FIXED. Regression test added (`apps/pwa/src/lib/eventDateTime.test.ts`).
- Fix status: Closed via commit 2870413 (2026-06-07). Serialization now uses `localWallClockToUtcIso()` to convert to UTC `Z` instant in the browser before sending to the API.
**Calendar row deduplication cross-user bug (FIXED in Phase 3):**
- Issue: The poller and sync used `url`-only predicates to lookup calendar rows, but the two household members share one Fastmail account — the same collection URL exists for both. This caused events to be cached under the wrong member's calendar and duplicate rows accumulated on every poll. Fixed in Phase 3 via commit 2870413 and migration `0001_calendars_user_url_unique.sql`.
- Files: `apps/api/src/broker/poller.ts` (line 5256), `apps/api/src/broker/sync.ts` (line 6266), `apps/api/src/db/schema.ts` (line 84), `apps/api/src/db/migrations/0001_calendars_user_url_unique.sql`
- Impact: FIXED. Unique constraint `uniq_calendar_user_url` enforces (userId, url) identity; all predicates scoped correctly.
- Fix status: Closed. Migration applied to live DB; regression tests added to `poller.test.ts` and `sync.test.ts`.
---
## Known Bugs
**GET /api/events missing userId/isShared filter (IDENTIFIED, RESOLVED via 260607-l6l):**
- Symptoms: GET /api/events returned events from all users (including stale spike data), not just owned + shared calendars.
- Files: `apps/api/src/routes/events.ts` (line 127129 now filters correctly via resolveUserId)
- Trigger: Any `/api/events` call without the ownership/isShared predicate in the JOIN.
- Status: FIXED in commit 2870413. The route now filters: `WHERE currentUserId = userId OR isShared=1`.
**Stale spike user + calendar data in production DB:**
- Symptoms: User id=1 ("Dev User", obsolete spike identity `oidc_iss='spike://cal-08'`) remains in the DB with 508 cached events under the now-deduplicated calendar row id=1. This is stale data, not a code bug.
- Files: Live MariaDB (data only, not source code)
- Impact: Low — new events written by the real users go to the correct rows (id=2, id=3 calendars). The spike data is not served to the app because the route filters by currentUserId. Safe to clean via a manual DB DELETE, but non-blocking.
- Fix approach: Post-deployment cleanup task: `DELETE FROM users WHERE oidc_iss='spike://cal-08'; DELETE FROM calendar_events WHERE calendar_id=1;` if confident no real events are under id=1. Safer: check `calendars.url` to confirm id=1 is the spike duplicate before deletion.
---
## Security Considerations
**Fastmail app password exposure risk:**
- Risk: The API loads and decrypts Fastmail app passwords from `member_credentials.encrypted_password`. If the encryption key is leaked or the decryption is implemented incorrectly, all calendar access is compromised.
- Files: `apps/api/src/broker/crypto.ts`, `apps/api/src/broker/poller.ts` (line 41), `deployment.md` (Step 2 — key generation)
- Current mitigation: AES-256-GCM encryption, key stored in `.env` (gitignored). Decrypted password never logged (T-03-04). Decryption happens only in `poller.ts` and `outboxWorker.ts`, not in HTTP routes.
- Recommendations: (1) Ensure `.env` is marked .gitignore in CI/CD (already done). (2) Rotate encryption key monthly + re-encrypt all passwords — design a rotation mechanism before multi-replica deployment. (3) Monitor access logs for repeated failed calendar syncs (sign of credential tampering). (4) Consider a secrets manager (e.g., Docker Compose secrets) for the encryption key in production.
**OIDC claim extraction fragility (Authelia defaults):**
- Risk: Authelia v4.39+ omits `name`, `email`, `preferred_username` from the ID token by default — requires a `claims_policy` config. The app's `deriveDisplayName()` (auth/user.ts) falls back through `name``preferred_username``email``sub`, but if Authelia is not configured with claims, all users appear as "Member" in the legend (observed in Phase 2). This is a configuration issue, not a code bug, but fragile.
- Files: `apps/api/src/auth/user.ts` (lines 819), `docs/deployment.md` (Authelia client config, line 9192 does NOT show claims_policy)
- Current mitigation: The identity is keyed on `iss+sub` (never email), so display name is cosmetic. The legend displays correctly after identity is established.
- Recommendations: (1) Add a `claims_policy` block to the example Authelia configuration in `docs/deployment.md` (or a separate `authelia-familysync-claims.yml` example). (2) Document that without claims, all users show as "Member" and that's non-blocking for v1 (they still get distinct colors via their `sub`). (3) Test Authelia claim extraction before Phase 5 push notifications are built (notification titles will need displayName).
**SSE heartbeat endpoint carries no secrets but could be abuse vector:**
- Risk: `/api/sse/heartbeat` is authenticated (behind oidcAuthMiddleware) but emits only timestamps — no sensitive data. However, a malicious actor with a valid session could hold open many concurrent heartbeat streams, consuming server resources (DoS).
- Files: `apps/api/src/routes/sse.ts`
- Current mitigation: The endpoint is single-purpose (testing transport viability); Phase 4 will add real list-change SSE with per-user subscriptions. Resource limits are absent.
- Recommendations: (1) For Phase 4, implement per-user connection limits (max 3 concurrent SSE streams per user). (2) Add heartbeat-timeout tracking: if a client doesn't read for 120s, close the stream. (3) Monitor stream creation rate in logs (spike = potential abuse).
---
## Performance Bottlenecks
**Calendar windowed query without pagination (acceptable for v1, scales to ~5000 events):**
- Problem: GET `/api/events?start=X&end=Y` returns all occurrences in the window with no pagination. The query is efficient (indexes on `dtstart_utc`, `dtstart_date`, `hasRrule`), but response size grows with window span and recurrence expansion.
- Files: `apps/api/src/routes/events.ts` (line 126170)
- Cause: No pagination implemented. For a 2-person household with ~500 events/person and heavy recurring series, a month-view response is ~25 KB (acceptable).
- Improvement path: (1) Monitor response time in Phase 4 (live sync will add per-user subscriptions). (2) If response >100 KB, add cursor-based pagination to the events endpoint. (3) Consider server-side caching of expansion results per (userId, window) for frequently-accessed ranges (e.g., current month).
**Broker poller is full-scan every 5 minutes (acceptable for <10 members, mitigated by ctag):**
- Problem: `poller.ts` loops all member_credentials and calls `fetchCalendars()` on each, then compares ctag. For a 2-person household with 2 Fastmail accounts (shared calendars + personal), this is ~24 PROPFIND/REPORT calls per cycle. Scales poorly to >10 members.
- Files: `apps/api/src/broker/poller.ts` (line 3577)
- Cause: No selective polling per calendar; all calendars checked every 5 minutes.
- Improvement path: (1) For v1 (24 members), current approach is fine — ~10 req/min to Fastmail. (2) For Phase 1.x (N-member expansion, per STATE.md note): track last-known ctag per calendar and skip polling if unchanged; implement WebDAV-Sync (sync-token) for delta-only fetches (RFC 6578). (3) Monitor Fastmail API rate-limit headers (`X-RateLimit-*`) in logs.
**Outbox worker retries backoff reaches 30 min max (acceptable, prevents spam):**
- Problem: The outbox retry window for a failed write is capped at ~30 min (BACKOFF_SECONDS: 15+60+300+600+1800). A transient Fastmail outage lasting >30 min will abandon the write as "dead" without user notification.
- Files: `apps/api/src/broker/outboxWorker.ts` (line 46, MAX_ATTEMPTS=5)
- Cause: Exponential backoff with a fixed cap to prevent infinite queuing.
- Improvement path: (1) For v1, 30 min is acceptable (household is US-based, Fastmail SLA is high). (2) For Phase 4, add a `dead-letter-queue` processor that logs unsent writes and optionally re-queues them manually. (3) Consider extending MAX_ATTEMPTS to 78 for a longer retry window (23 hours) if outages are observed.
---
## Fragile Areas
**CalDAV event write-back lacks conflict resolution (D-08 mitigation exists, risk remains):**
- Files: `apps/api/src/broker/write.ts`, `apps/api/src/broker/outboxWorker.ts` (line 180190), `docs/deployment.md` (Pitfall 14)
- Why fragile: When a user edits an event in the app and another user edits it concurrently in the native Fastmail app, the outbox worker receives a 412 (If-Match conflict). The current behavior is to mark the outbox row as "failed" and trigger a re-sync. This is correct but provides no UI feedback to the user — they don't know their edit was rejected. If this happens repeatedly, the user will see the calendar diverge unpredictably.
- Safe modification: (1) Add a `syncStatus` subscription in the PWA (already designed in Phase 3 Plan 03-06). The UI shows "sync conflict — your edit was rejected, event reloaded from server" in a toast. (2) If the outbox row is marked "failed", the next re-sync will pull the current server state. (3) For Phase 4+, consider implementing a "merge/overwrite" UI where the user can choose to force their edit if they're confident it's the right state. For v1, reject-and-reload is acceptable.
**Recurring event expansion via rrule + EXDATE is CPU-sensitive (mitigated by window cap):**
- Files: `apps/api/src/broker/expand.ts`, `apps/api/src/routes/events.ts` (line 45, MAX_WINDOW_DAYS=90)
- Why fragile: Expanding a 5-year-old weekly recurring event to a 90-day window generates ~50 occurrences. Expanding to a 1-year window generates ~250. If a user requests a 365-day window (not capped), the expansion becomes CPU-bound.
- Safe modification: The MAX_WINDOW_DAYS=90 guard is in place (T-02b-02, DoS protection). No change needed. If Phase 6 adds a "year view", re-evaluate the expansion window and consider caching expanded results per (event.uid, window).
**OIDC session middleware dependency on @hono/oidc-auth (tied to Authelia version):**
- Files: `apps/api/src/auth/middleware.ts`, package.json (@hono/oidc-auth: 1.8.3)
- Why fragile: @hono/oidc-auth v1.8.3 assumes a specific OIDC metadata contract. If Authelia makes a breaking change in its .well-known/openid-configuration response, the middleware could fail silently (e.g., missing `token_endpoint`, `userinfo_endpoint`).
- Safe modification: (1) Add a startup health check that fetches Authelia's OIDC metadata and logs an error if critical fields are missing. (2) Monitor Authelia release notes for OIDC spec changes. (3) Pin @hono/oidc-auth to 1.8.x in package.json (already done). (4) Test Authelia upgrades in a staging environment before deploying to production.
---
## Scaling Limits
**Single-process deployment concurrency guard in outbox worker:**
- Current capacity: The outbox worker's drain-concurrency guard (CR-05, line 87100) uses a module-level boolean flag. This is safe for a single-process Docker container but breaks if scaled to multiple API replicas.
- Limit: If the API is deployed as N replicas behind a load balancer, the drain cycles can overlap and double-dispatch the same outbox row to Fastmail, causing duplicate writes.
- Scaling path: (1) For v1 (single Unraid container), no change needed. (2) For multi-replica or Kubernetes: replace the module-level guard with a durable DB row claim (`UPDATE calendar_outbox SET status='processing' WHERE id=? AND status='pending'`). The first replica to claim wins; others skip that row. (3) Add a "processing" timeout (5 min) to prevent dead-replica claims from blocking the queue indefinitely.
**In-memory SSE fan-out via EventEmitter (Phase 4 dependency, acceptable for single process):**
- Current capacity: Phase 4 will add live list-change SSE that broadcasts to connected clients. If implemented as a simple Node EventEmitter, each replica process maintains its own in-memory subscriptions. A member on replica A updates a list; the SSE fires on replica A but replica B's connections don't see it (if the member's browser is routed to replica B after the update).
- Limit: Limited to single-process deployment or requires Redis Pub/Sub for fan-out across replicas.
- Scaling path: (1) For v1 (single container), EventEmitter is fine. (2) For Phase 4+, if multi-replica is needed: design the SSE layer to use Redis Pub/Sub for cross-process broadcasts. Add ioredis to package.json (it's already recommended in CLAUDE.md). See PITFALLS.md §Pitfall 15 for sequence-number replay strategy.
**Redis not yet installed (Phase 4 dependency, scheduled for list sync):**
- Current status: The app has no Redis dependency. Phase 4 will require Redis for pub/sub (list-change broadcasts across processes/replicas).
- Impact: v1 is single-process; live sync works fine without Redis. Phase 4+ requires it.
- Remediation: Add Redis to docker-compose.yml in Phase 4. ioredis client already in package.json recommendations (CLAUDE.md, Table 1). Configure connection pooling (ioredis default: 8 connections).
---
## Dependencies at Risk
**@hono/oidc-auth peer dependency on Authelia RFC compliance:**
- Risk: @hono/oidc-auth relies on Authelia conforming to OIDC RFC 6749/6234. If Authelia introduces a non-standard endpoint or claim format, the middleware may fail.
- Impact: OIDC login would break; users cannot access the app.
- Migration plan: If Authelia breaks OIDC compatibility, replace @hono/oidc-auth with `openid-client` (a lower-level OIDC library). Estimated effort: 23 days to wire custom middleware. openid-client is already in CLAUDE.md as an escape hatch (Table 1, row 3).
**tsdav maintained by single contributor (NateLinDev/tsdav):**
- Risk: The CalDAV client library `tsdav@2.2.2` has low maintenance activity. If a Fastmail CalDAV protocol change occurs or a critical bug is found, the library may not be updated promptly.
- Impact: Calendar sync could break (PROPFIND, REPORT, PUT all depend on tsdav).
- Migration plan: (1) For v1, tsdav is stable and proven in this codebase. (2) If maintenance becomes a blocker, the next option is to implement CalDAV PROPFIND/REPORT directly via fetch + xml2js (Pitfall 1 explicitly warns against this, but it's doable). Estimated effort: 1 week to implement a minimal CalDAV client. (3) Monitor tsdav GitHub issues and PRs.
**ical.js reference implementation (kewisch/ical.js):**
- Risk: ical.js is the Mozilla-maintained RRULE/iCalendar reference implementation, but Mozilla does not actively develop calendar software. If a new RFC 5545 edge case is discovered (e.g., an RRULE rule that breaks ical.js), it may not be fixed quickly.
- Impact: Recurring events could expand incorrectly (rare, but affects display).
- Migration plan: (1) For v1, ical.js is the most reliable available. (2) If a bug is found, open an issue on GitHub; Mozilla is responsive to reference-implementation bugs. (3) Fallback: use `rrule` library only (lighter weight) if ical.js is abandoned, but rrule is less comprehensive for EXDATE/RECURRENCE-ID handling.
---
## Missing Critical Features
**Single-occurrence recurring event override (deferred to v1.x):**
- Problem: A user cannot edit or delete a single occurrence of a recurring event (e.g., "skip next Tuesday's meeting"). The edit-as-move write path (D-04) supports full-series edits only.
- Blocks: Users frustrated when they want to reschedule one instance.
- Deferred reason: Requires RECURRENCE-ID write-back (RFC 5545) and complex VCALENDAR patching. Estimated effort: 23 days of implementation + testing. For v1, edit-all is acceptable for a 2-person household.
- Resolution approach: Phase 6 or v1.x — implement a "Edit this and all following" option that re-dates the RRULE UNTIL and creates a new series from the edit date onward.
**Notification subscription health-check (CRITICAL for Phase 5, deferred to Phase 5 implementation):**
- Problem: iOS silently revokes Web Push subscriptions after 3 silent push events (Pitfall 9). The app must detect this and re-subscribe automatically.
- Blocks: Phase 5 (push notifications) cannot be considered production-ready without this.
- Missing implementation: No subscription health-check exists in the PWA yet. The service worker needs to call `pushManager.getSubscription()` on every page open and compare the endpoint to the server's stored endpoint; if they differ, re-subscribe.
- Resolution approach: Phase 5 must include health-check implementation as a prerequisite, not a polish task.
---
## Test Coverage Gaps
**Events API route (GET /api/events, POST /create, PATCH /edit, DELETE /delete) has integration-level testing but lacks edge cases:**
- What's not tested: (1) Window boundary conditions (start=end, off-by-one day shifts). (2) Recurring all-day events with complex EXDATE. (3) Concurrent edit conflict (412 handling). (4) Ownership assertions with mixed owned + shared calendars.
- Files: `apps/api/tests/routes/events.test.ts` (126 lines, covers happy paths + 400/403 error cases)
- Risk: Edge cases in expansion or ownership filtering could silently pass tests and break in production.
- Priority: MEDIUM — add 1015 test cases before Phase 4 (live sync will depend on ownership filtering being bulletproof).
**Outbox worker state machine (retry backoff, edit-as-move ordering, dead-letter) has unit tests but lacks end-to-end CalDAV integration:**
- What's not tested: (1) Outbox row with a real Fastmail endpoint (mocked in tests). (2) 412 conflict response from Fastmail + re-sync flow. (3) Concurrent outbox rows from the same list (edit+delete pair ordering under network failures). (4) Recovery after a multi-hour Fastmail outage.
- Files: `apps/api/tests/broker/outboxWorker.test.ts` (state-machine tests only)
- Risk: Silent data loss if outbox row ordering is wrong under failures; list sync will depend on correct write ordering.
- Priority: HIGH — add integration tests before Phase 4. Mock Fastmail CalDAV responses (conflict, transient, success) and verify state transitions.
**PWA EventForm timezone serialization (fixed in Phase 3, regression test exists but limited scope):**
- What's not tested: (1) Daylight Saving Time transitions (create event on March 12, spring-forward boundary). (2) Cross-timezone consistency (create event in Toronto, verify UTC serialization, reload in UTC, confirm display is Toronto wall-clock). (3) All-day event edge cases (midnight boundary serialization).
- Files: `apps/pwa/src/lib/eventDateTime.test.ts` (5 cases: timed → UTC, all-day → DATE, round-trip)
- Risk: Similar timezone bug could reappear if eventDateTime.ts is refactored without comprehensive DST testing.
- Priority: MEDIUM — add 510 DST/all-day edge cases to the test suite before Phase 6 (UX polish will touch date/time handling).
**PWA service worker and offline behavior untested:**
- What's not tested: (1) Service worker install, activation, and update lifecycle. (2) Offline calendar view (reads from cache). (3) Offline list mutation (queues for sync). (4) Cache expiration strategy.
- Files: Service worker is auto-generated by vite-plugin-pwa; offline behavior is unimplemented in Phase 13.
- Risk: Phase 4's offline queue and Phase 5's background sync depend on correct SW lifecycle. Silent failures in SW updates could leave the wife on a stale version.
- Priority: MEDIUM — Phase 4 should include SW unit tests (simulate offline, verify cache reads, verify mutation queue behavior).
**Mobile-specific behavior (iOS push, PWA standalone mode, permissions) untested by vitest:**
- What's not tested: (1) iOS 16.4+ push subscription (requires real device). (2) Standalone PWA launch (requires Add-to-Home-Screen). (3) Permission request flow (requires user gesture). (4) Camera/location permissions (out of scope for v1, but worth listing).
- Files: Not applicable (device-only testing).
- Risk: High impact if broken (wife can't install, can't receive notifications). Mitigated by human UAT (Phase 3 Gate 2 item 4).
- Priority: MEDIUM — document a manual iOS test checklist in Phase 5 (must run before ship). Playwright can test browser-side behavior; device-side requires manual verification.
---
## Architectural Constraints & Anti-Patterns
**Single-process assumption in outbox drain guard (CR-05, documented but constrains scaling):**
- Constraint: The module-level boolean flag `let isProcessing = false` in outboxWorker.ts assumes a single Node.js process. This is correct for the Unraid single-container deployment but breaks if scaled horizontally.
- Consequence: Multi-replica deployments MUST implement a durable DB claim (UPDATE … WHERE status='processing') before the API is horizontally scaled.
- Workaround: Documented in code comment (line 91100). Clear and easy to address when scaling is needed.
**No pagination on calendar events endpoint (acceptable for v1, design assumption):**
- Constraint: GET /api/events returns all occurrences in the window with no pagination. Designed for a 90-day max window and <1000 occurrences per window (acceptable for 2-person household).
- Consequence: Very large windows or households with hundreds of recurring events could generate multi-MB responses.
- Workaround: MAX_WINDOW_DAYS=90 guard prevents DoS. For Phase 4+, if response size exceeds 500 KB, add cursor pagination.
**Dev-auth bypass is development-only but deployment-critical (configuration risk):**
- Constraint: The bypass is designed for local development (NODE_ENV !== 'production' + DEV_AUTH_BYPASS=true). If the bypass is accidentally enabled in production, the OIDC guard is completely bypassed.
- Consequence: Unauthenticated API access if misconfigured.
- Workaround: (1) .env.example has DEV_AUTH_BYPASS commented out. (2) docker-compose.yml MUST NOT include DEV_AUTH_BYPASS in env. (3) Documented in docs/deployment.md. Recommended: add a startup assertion to double-check.
---
## Infrastructure & Deployment Concerns
**Drizzle migrations require manual SQL review (no auto-apply in Docker):**
- Issue: The app does not auto-migrate on startup. The `drizzle-kit push` command is unsafe on MariaDB. Manual `drizzle-kit migrate` must be run once per DB version before the app starts.
- Files: `apps/api/src/db/migrations/`, `docs/deployment.md` (Step 3: `drizzle-kit push` is the documented command, but should be `migrate` or `generate+migrate` for production safety)
- Impact: If the operator forgets to migrate after pulling a new schema, the app will crash on startup (missing tables). The error message should be clear.
- Fix approach: (1) Update `docs/deployment.md` Step 3 to use `migrate` instead of `push`. (2) Add a startup health check in `src/db/client.ts` that verifies all expected tables exist; fail with a clear message if any are missing. (3) Document the migration process in a DEPLOYMENT.md subsection.
**Pangolin SSE idle timeout dependency (D-14, issue #1034) verified but residual risk remains:**
- Issue: SSE streams can be cut by proxy idle-timeout. The Phase 4 entry gate smoke test PASSED (6 min without cut), but only tested on the test domain `familysync-dev.bergerhouse.net`.
- Files: `docs/deployment.md` (line 165170), `.planning/phases/04-shared-lists-live-sync/04-CONTEXT.md`
- Impact: If the production Pangolin idle-timeout is lower than the test rig, SSE will be cut during live list sync. Users will experience brief disconnects (mitigated by reconnect logic in Phase 4).
- Current mitigation: Documented in deployment.md. The operator must set Pangolin's idle-timeout to ≥120s (recommended 300s) when deploying to production.
- Residual risk: If Pangolin is misconfigured and SSE is cut, the fallback (D-12 polling every 5s) will maintain sync but with degraded latency (5s vs real-time). Phase 4 must implement the polling fallback.
---
## Known Limitations (Documented as Design Decisions)
**Personal calendar sharing requires manual Fastmail setup (D-16 CAL-08 spike result):**
- Limitation: The two household members' personal Fastmail calendars are accessed via per-member app passwords (not a shared broker token). This requires each member to generate an app password and register it in the app.
- Impact: Acceptable. The unified view works correctly and scales to shared + personal calendars.
- Status: GO decision (CAL-08-DECISION.md, Phase 1).
**Recurring event edit supports edit-all only (single-occurrence override deferred to v1.x):**
- Limitation: The write path does not support RECURRENCE-ID overrides. Editing a recurring event changes all future occurrences.
- Impact: Users cannot reschedule a single meeting. For a 2-person household, edit-all is acceptable.
- Status: Documented in STATE.md (deferred items), Phase 6 planning.
**EU DMA compliance risk for EU-based households (Pitfall 11):**
- Limitation: iOS 17.4+ in EU countries removes standalone PWA mode and push support due to Digital Markets Act. FamilySync's push notifications would not work for an EU user.
- Impact: If the household moves to EU or uses EU Apple IDs, notifications are unavailable.
- Status: This is a Canadian household (me@lucasberger.ca, .ca domain, Unraid self-hosted). Documented as not applicable but worth flagging for future.
- Fix approach: Monitor for EU regulatory changes; if the household moves, switch to email or in-app notification fallback for v1.x.
---
_Concerns audit: 2026-06-09_