Files
familysync/.planning/phases/05-web-push-notifications/05-01-SUMMARY.md
T

7.7 KiB

phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics
05-web-push-notifications 01 api/push-foundation
web-push
vapid
schema
migration
test-scaffolds
red-tests
requires provides affects
04-shared-lists-live-sync
push_subscriptions table
calendar_events.title column
Wave-0 RED test scaffolds
VAPID env wiring
apps/api/src/db/schema.ts
apps/api/src/db/migrations/
apps/api/test/setup.ts
docker-compose.yml
added patterns
web-push@3.6.7
@types/web-push@3.6.4
workbox-core@7.4.1
workbox-precaching@7.4.1
workbox-routing@7.4.1
drizzle-kit generate+migrate (never push)
mysqlTable FK+unique+index pattern
RED test scaffold pattern
created modified
apps/api/src/db/migrations/0003_same_xavin.sql
apps/api/src/db/migrations/meta/0003_snapshot.json
apps/api/tests/fixtures/vapid.ts
apps/api/tests/lib/pushDispatcher.test.ts
apps/api/tests/lib/pushCoalescer.test.ts
apps/api/tests/broker/reminderScheduler.test.ts
apps/api/tests/lib/eventChangeDispatcher.test.ts
apps/api/tests/routes/push.test.ts
.env.example
apps/api/src/db/schema.ts
apps/api/test/setup.ts
apps/api/package.json
apps/pwa/package.json
pnpm-lock.yaml
docker-compose.yml
VAPID config is env-injected at runtime (docker-compose.yml environment block); no key baked into image
pushSubscriptions endpoint column uses text (not varchar) — push endpoints can exceed 512 chars
calendarEvents.title is nullable varchar(500); pre-existing rows stay NULL until Phase 5 sync update
Test VAPID keypair inlined in tests/fixtures/vapid.ts for offline-safe unit tests
duration completed_date tasks_completed files_changed
20 2026-06-10 4 15

Phase 05 Plan 01: Wave-0 Foundation Summary

Web Push Wave-0 foundation: push dependencies installed, VAPID keypair env-injected, push_subscriptions table + calendar_events.title migrated, five RED test scaffolds committed.

Tasks Executed

Task 1: Package legitimacy gate + install push dependencies

Status: Done by orchestrator before this agent spawned.

Installed packages verified in package.json:

  • apps/api: web-push@^3.6.7 (prod), @types/web-push@^3.6.4 (dev)
  • apps/pwa: workbox-core@^7.4.1, workbox-precaching@^7.4.1, workbox-routing@^7.4.1 (dev)

Commit: 80bbdc1chore(05-01): install web-push and workbox push dependencies

Task 2: Generate VAPID keypair + record in env

Status: Done by orchestrator before this agent spawned.

VAPID keypair generated and stored in gitignored .env (VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECT). Real keys never committed.

(No dedicated commit — keys in .env only; .env.example documenting placeholders committed in Task 3.)

Task 3: Schema — push_subscriptions table + calendar_events.title, generate+migrate

Status: Completed.

Added pushSubscriptions mysqlTable to apps/api/src/db/schema.ts:

  • user_id INT NOT NULL FK → users.id ON DELETE CASCADE
  • endpoint TEXT NOT NULL (globally unique — uniq_push_endpoint)
  • p256dh TEXT NOT NULL
  • auth VARCHAR(256) NOT NULL
  • created_at, updated_at TIMESTAMP
  • Index idx_push_subscriptions_user_id on userId

Added title VARCHAR(500) (nullable) to calendarEvents after rawVevent. Populated from VEVENT SUMMARY by sync.ts in Plan 05-07; required for readable reminder/change copy (D-02/NOTIF-01).

Migration generated via db:generate and applied via db:migrate (NOT db:push — anti-pattern per drizzle-mariadb-push-unsafe memory). Migration file: 0003_same_xavin.sql.

VAPID container-transposability: added VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECT to docker-compose.yml api environment: block using ${VAR} syntax (no default — must be set). Created root .env.example documenting all environment variables including VAPID vars with placeholders and generation instructions.

Commit: 73fcdaffeat(05-01): add push_subscriptions table + calendar_events.title column; VAPID env wiring

Task 4: Wave-0 RED test scaffolds + VAPID fixture + setup truncation

Status: Completed.

Created tests/fixtures/vapid.ts — exports TEST_VAPID const with a statically inlined P-256 keypair (generated once; no runtime network call; offline-safe).

Created five RED test scaffolds (all fail on Cannot find module — correct RED state):

  1. tests/lib/pushDispatcher.test.ts — 4 tests: 410/404 prune DELETE, 201 no-delete, 5xx no-delete
  2. tests/lib/pushCoalescer.test.ts — 3 tests: burst collapses to 1 dispatch with count=N; excludeUserId passed; separate lists are independent
  3. tests/broker/reminderScheduler.test.ts — 3 tests: all-day excluded (D-07); non-shared excluded (D-05); (uid,minuteBucket) dedup
  4. tests/lib/eventChangeDispatcher.test.ts — 4 tests: create fires; title-change fires; description-only silent (D-04); actor excluded (D-03)
  5. tests/routes/push.test.ts — POST 201/401; DELETE removes rows; GET /api/push/vapid-public-key returns { publicKey }

Updated test/setup.ts:

  • Added pushSubscriptions to import from schema
  • Added await db.delete(pushSubscriptions) in afterEach (before lists delete; no FK to lists)

Commit: ef558b6test(05-01): add Wave-0 RED scaffolds + VAPID fixture + setup truncation

Deviations from Plan

Auto-added: VAPID container-transposability (orchestrator requirement)

The orchestrator folded in a requirement not in the original plan: VAPID env vars must be env-injected in docker-compose.yml, not baked into the image.

  • Fix: Added three ${VAPID_*} entries to docker-compose.yml api environment: block (no default fallback — unset = container won't start, which is correct: no VAPID = no push).
  • Also created: Root .env.example (the plan listed it in files_modified but it didn't exist yet) documenting all environment variables for the project including VAPID.
  • Files modified: docker-compose.yml, .env.example (created)

Package dependencies committed separately (Rule 3 — blocking issue)

Tasks 1/2 package installs were done by the orchestrator but not yet committed (uncommitted changes in apps/api/package.json, apps/pwa/package.json, pnpm-lock.yaml). These were staged and committed as a separate chore commit (80bbdc1) before the schema commit, to keep dependency changes isolated from schema changes.

Known Stubs

None. This plan lays only schema and test scaffolds — no UI rendering or data-flow stubs.

Threat Flags

No new threat surface introduced. VAPID private key is in gitignored .env only; .env.example contains placeholders only (T-05-01 mitigated). Migration used generate+migrate workflow (T-05-02 mitigated). Package installs were pre-approved by human checkpoint Task 1 (T-05-SC mitigated).

Self-Check

Files created/verified:

  • apps/api/src/db/migrations/0003_same_xavin.sql — exists
  • apps/api/tests/fixtures/vapid.ts — exists
  • apps/api/tests/lib/pushDispatcher.test.ts — exists
  • apps/api/tests/lib/pushCoalescer.test.ts — exists
  • apps/api/tests/broker/reminderScheduler.test.ts — exists
  • apps/api/tests/lib/eventChangeDispatcher.test.ts — exists
  • apps/api/tests/routes/push.test.ts — exists
  • .env.example — exists

Commits verified:

  • 80bbdc1: chore(05-01): install web-push and workbox push dependencies
  • 73fcdaf: feat(05-01): add push_subscriptions table + calendar_events.title column; VAPID env wiring
  • ef558b6: test(05-01): add Wave-0 RED scaffolds + VAPID fixture + setup truncation

Typecheck: passes (pnpm --filter @familysync/api typecheck — no errors) RED tests: all 5 scaffold files fail on Cannot find module (correct; implementations in Plans 05-02..05-06)

Self-Check: PASSED