Milestone v1.0: FamilySync MVP #1

Merged
luckberg merged 376 commits from gsd/v1.0-milestone into main 2026-06-10 17:39:19 -04:00
3 changed files with 108 additions and 14 deletions
Showing only changes of commit 78a8cb5ccd - Show all commits
+6 -3
View File
@@ -235,7 +235,7 @@ Plans:
Plans:
**Wave 1** *(parallel — exclusive file ownership)*
- [ ] 06-01-PLAN.md — TDD: duration-preserving end-tracking math (computeNewTimedEnd/computeNewAllDayEnd) in eventDateTime.ts (D-04)
- [x] 06-01-PLAN.md — TDD: duration-preserving end-tracking math (computeNewTimedEnd/computeNewAllDayEnd) in eventDateTime.ts (D-04)
- [ ] 06-02-PLAN.md — TDD: RRULE UNTIL/COUNT serialization + Zod acceptance + FREQ-persistence regression (vevent/outboxWorker/events route) (D-06/D-07)
- [ ] 06-03-PLAN.md — TDD: hasRrule on CalendarOccurrence + bounded-expansion lock (expand.ts) (D-06/D-08)
- [ ] 06-04-PLAN.md — Spinner/pulse: global @keyframes pulse + remove redundant spin redefinition (D-13)
@@ -260,7 +260,7 @@ Note: Phase 4 depends only on Phase 1 and can begin as soon as Phase 1 is comple
| 3. Event Write-Back + PWA Install | 12/12 | Complete | 2026-06-07 |
| 4. Shared Lists + Live Sync | 6/6 | Complete | 2026-06-09 |
| 5. Web Push Notifications | 8/8 | Complete | 2026-06-10 |
| 6. UX Polish | 0/6 | Planned | - |
| 6. UX Polish | 1/6 | In Progress| |
## Backlog
@@ -268,7 +268,7 @@ Note: Phase 4 depends only on Phase 1 and can begin as soon as Phase 1 is comple
**Goal:** [Captured for future planning] Abstract the calendar backend behind a provider interface so Fastmail/CalDAV is one implementation among potentially many. Shipping with a single provider is fine, but the broker, sync, and event-expansion layers should be structured so additional providers (e.g. other CalDAV hosts, Google Calendar, generic ICS feeds) can be added without rework. Captures the "provider" seam as an explicit architectural concern.
**Requirements:** TBD
**Plans:** 8/8 plans complete
**Plans:** 1/6 plans executed
Plans:
@@ -393,6 +393,7 @@ Plans:
### Phase 999.10: Admin Settings / Administration section — manage app passwords + designate the shared calendar via UI (BACKLOG)
**Goal:** [Captured for future planning] Add an in-app **Settings/Administration** section, gated to an administrator role, for configuration that today requires manual backend/DB steps:
- **View/update per-member Fastmail app passwords** (stored encrypted via `APP_PASSWORD_ENCRYPTION_KEY`, existing crypto path) — rotate or re-enter a member's credential and re-trigger sync.
- **Designate which synced calendar is the "shared" calendar** by toggling `calendars.is_shared` from the UI. Today this is a manual DB write: e.g. `UPDATE calendars SET is_shared=1 WHERE id=<row>` — done by hand on 2026-06-10 to mark the "FamilySync" calendar (id 10) shared after the poller synced it (D-16). The admin should pick the shared calendar from a list of synced collections instead of relying on a backend process. (The poller's upsert already leaves `is_shared` untouched, so a UI-set flag persists.)
@@ -407,9 +408,11 @@ Plans:
### Phase 999.11: Initial setup wizard — first-run config of env vars, app passwords, DB connection (BACKLOG)
**Goal:** [Captured for future planning] Add a first-run **setup wizard** that walks the administrator through defining all bootstrap configuration instead of hand-editing `.env` / `docker-compose.yml`:
- **App environment variables:** OIDC client id/secret/issuer/redirect URI + external URL, session signing secret (`OIDC_AUTH_SECRET`), `APP_PASSWORD_ENCRYPTION_KEY`, and the **VAPID keypair** (subject + public + private).
- **MariaDB connection:** host/port/user/password/db, with a connectivity test.
- **First Fastmail app password** for the initial member, encrypted on save.
Wizard should **validate inputs before completing** — e.g. VAPID private key decodes to 32 bytes AND pairs with the public key, OIDC discovery resolves, DB connects, app-password reaches CalDAV.
**Context:** Motivated by setup friction observed 2026-06-10 — a VAPID private key truncated on paste into `.env` silently broke push (`setVapidDetails failed — 32 bytes`), and `DB_HOST` / dev overrides must currently be set by hand. A guided + validated wizard would have caught these. **Related:** 999.10 (ongoing admin Settings) and 999.5 (member onboarding). Tags: onboarding, setup, install, env, vapid, mariadb, oidc.
+12 -11
View File
@@ -4,14 +4,14 @@ milestone: v1.0
milestone_name: milestone
status: executing
stopped_at: Phase 06 UI-SPEC approved
last_updated: "2026-06-10T14:37:02.111Z"
last_activity: 2026-06-10 -- Phase 06 planning complete
last_updated: "2026-06-10T14:51:02.287Z"
last_activity: 2026-06-10 -- Phase 06 execution started
progress:
total_phases: 15
total_phases: 17
completed_phases: 5
total_plans: 36
completed_plans: 36
percent: 33
total_plans: 42
completed_plans: 37
percent: 29
---
# Project State
@@ -21,14 +21,14 @@ progress:
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 05web-push-notifications
**Current focus:** Phase 06ux-polish
## Current Position
Phase: 05 (web-push-notifications) — EXECUTING
Plan: 8 of 8
Phase: 06 (ux-polish) — EXECUTING
Plan: 2 of 6
Status: Ready to execute
Last activity: 2026-06-10 -- Phase 06 planning complete
Last activity: 2026-06-10 -- Phase 06 execution started
Progress: [█████████░] 89%
@@ -73,6 +73,7 @@ Progress: [█████████░] 89%
| Phase 05 P06 | 6 | 1 tasks | 2 files |
| Phase 05 P08 | 9 | 3 tasks | 7 files |
| Phase 05 P07 | 8 | 1 tasks | 4 files |
| Phase 06-ux-polish P01 | 2 | 2 tasks | 2 files |
## Accumulated Context
@@ -157,6 +158,6 @@ Recent decisions affecting current work:
## Session Continuity
Last session: 2026-06-10T13:46:18.209Z
Last session: 2026-06-10T14:51:02.281Z
Stopped at: Phase 06 UI-SPEC approved
Resume file: .planning/phases/06-ux-polish/06-UI-SPEC.md
@@ -0,0 +1,90 @@
---
phase: 06-ux-polish
plan: "01"
subsystem: pwa/lib
tags: [tdd, date-math, event-form, d-04]
dependency_graph:
requires: []
provides:
- computeNewTimedEnd (apps/pwa/src/lib/eventDateTime.ts)
- computeNewAllDayEnd (apps/pwa/src/lib/eventDateTime.ts)
affects:
- Plan 06-06 (EventForm wires these helpers into onChange handlers)
tech_stack:
added: []
patterns:
- Local Date accessors (WR-05): getFullYear/getMonth/getDate/getHours/getMinutes — never toISOString().slice
- TDD RED→GREEN with Vitest in apps/pwa
key_files:
created: []
modified:
- apps/pwa/src/lib/eventDateTime.ts
- apps/pwa/src/lib/eventDateTime.test.ts
decisions:
- WR-05 enforced: all new date formatting uses local accessors; toISOString().slice banned for date strings
- Private helpers (localDateISO, localTimeHHMM, dateDiffDays, addDaysISO) added to eventDateTime.ts to support the two exports
metrics:
duration_minutes: 2
completed_date: "2026-06-10"
tasks_completed: 2
files_changed: 2
---
# Phase 06 Plan 01: End-Tracking Duration Math (D-04) Summary
**One-liner:** Pure duration-preservation helpers (`computeNewTimedEnd` + `computeNewAllDayEnd`) with 1h/same-day floor rules, TDD RED→GREEN in eventDateTime.ts.
## Tasks Completed
| Task | Name | Commit | Files |
|------|------|--------|-------|
| 1 (RED) | Failing tests for computeNewTimedEnd / computeNewAllDayEnd | `16cdbf3` | eventDateTime.test.ts |
| 2 (GREEN) | Implement the two end-tracking helpers | `605f543` | eventDateTime.ts |
## What Was Built
Two exported pure functions added to `apps/pwa/src/lib/eventDateTime.ts`:
- **`computeNewTimedEnd(newStartDate, newStartTime, oldStartDate, oldStartTime, oldEndDate, oldEndTime)`** — preserves a timed event's duration when the start moves. Returns `{ endDate: 'YYYY-MM-DD', endTime: 'HH:MM' }`. If the old end was already behind the old start (stale state), floors to newStart + 1 hour.
- **`computeNewAllDayEnd(newStartDate, oldStartDate, oldEndDate)`** — preserves an all-day event's inclusive day-span when the start moves. Returns `'YYYY-MM-DD'`. If the old span was negative (stale state), floors to 0 days (same day as newStart).
Four private helpers added to the same file: `localDateISO`, `localTimeHHMM`, `dateDiffDays`, `addDaysISO`. All use local Date accessors (WR-05 compliance).
## Test Coverage
Six new unit tests in `apps/pwa/src/lib/eventDateTime.test.ts`:
| Test | Behavior |
|------|----------|
| preserves a 1-hour timed delta | old 09:00→10:00; new start 11:00 → new end 12:00 |
| preserves a multi-day timed delta (26h) | old 08:00→+26h; new start same offset → correct |
| floors to 1h when old end was behind start | stale end → snaps to newStart+1h |
| preserves a 0-day span (single day all-day) | oldStart=oldEnd → newEnd=newStart |
| preserves a 3-day span | newEnd = newStart + 3 days |
| floors to same day when old end behind start | negative span → 0 → same day |
Full suite: **166/166 tests pass**. Pre-existing `serializeEventDateTime` / `localWallClockToUtcIso` tests unaffected.
## TDD Gate Compliance
- RED commit (`test(06-01): ...`): `16cdbf3` — 6 tests failing with `TypeError: computeNewTimedEnd is not a function`
- GREEN commit (`feat(06-01): ...`): `605f543` — all 166 tests pass
- RED→GREEN order confirmed via `git log`
## Deviations from Plan
None — plan executed exactly as written.
## Threat Flags
None. Pure client-side date arithmetic on already-trusted local form state. No network boundary, no new attack surface. T-06-01 accepted per threat model.
## Self-Check: PASSED
- `apps/pwa/src/lib/eventDateTime.ts` — FOUND (modified)
- `apps/pwa/src/lib/eventDateTime.test.ts` — FOUND (modified)
- Commit `16cdbf3` — FOUND (RED: test(06-01))
- Commit `605f543` — FOUND (GREEN: feat(06-01))
- `computeNewTimedEnd` export — FOUND in eventDateTime.ts
- `computeNewAllDayEnd` export — FOUND in eventDateTime.ts
- No `toISOString().slice` in helper code — CONFIRMED