--- phase: 03-event-write-back-pwa-install plan: 01 subsystem: database, testing, infra tags: [drizzle, mariadb, vitest, vite-plugin-pwa, caldav, outbox] # Dependency graph requires: - phase: 02-calendar-read-display provides: calendarEvents table, sync.ts upsert loop, existing test infrastructure provides: - calendarOutbox table live in MariaDB (calendar_outbox, 3 indexes) - calendarEvents.objectUrl column live in MariaDB (object_url varchar 1024) - vite-plugin-pwa installed in apps/pwa - Five Wave 0 RED test files covering all Phase 3 behaviors (vevent, write, outboxWorker, events routes, InstallPrompt) affects: [03-02, 03-03, 03-04, 03-05, 03-06, 03-07, 03-08] # Tech tracking tech-stack: added: [vite-plugin-pwa@1.3.0] patterns: - mysqlEnum for outbox status/operation columns in Drizzle schema - objectUrl stored on calendarEvents from obj.url during sync upsert - Wave 0 RED scaffold: import not-yet-existing modules so test suite fails before implementation key-files: created: - apps/api/tests/broker/vevent.test.ts - apps/api/tests/broker/write.test.ts - apps/api/tests/broker/outboxWorker.test.ts - apps/pwa/src/components/InstallPrompt.test.tsx modified: - apps/api/src/db/schema.ts - apps/api/src/broker/sync.ts - apps/api/tests/routes/events.test.ts - apps/pwa/package.json key-decisions: - "D-Task5-DDL: drizzle-kit push is unsafe on MariaDB 11 with mysql dialect — misreads metadata and schedules truncate on populated tables. Additive DDL (calendar_outbox CREATE + object_url ALTER) was hand-applied and verified. Adopt drizzle-kit generate+migrate workflow before next schema change (tracked in todos/pending/adopt-drizzle-migrations-workflow.md)." patterns-established: - "Outbox pattern: calendar_outbox table with status enum (pending/done/failed/dead), groupId for edit-as-move pairing, nextAttemptAt for exponential backoff" - "objectUrl stored from tsdav obj.url on every sync upsert — enables If-Match header on CalDAV update/delete" - "Wave 0 RED scaffold: all phase test files created before any implementation so GREEN gate is explicit" requirements-completed: [CAL-04, CAL-05, CAL-06, CAL-07, PWA-01, PWA-02] # Metrics duration: ~45min completed: 2026-06-05 --- # Phase 03 Plan 01: Foundation Scaffold Summary **calendarOutbox table + calendarEvents.objectUrl pushed live to MariaDB, vite-plugin-pwa installed, and five Wave 0 RED test files covering all Phase 3 write-back and PWA behaviors** ## Performance - **Duration:** ~45 min - **Started:** 2026-06-05T21:18Z - **Completed:** 2026-06-05T22:10Z - **Tasks:** 5 (Tasks 1-5; Task 1 was human-verify gate, Task 5 was human-action gate) - **Files modified:** 8 ## Accomplishments - Extended Drizzle schema with `calendarOutbox` table (12 columns, 3 indexes: idx_outbox_user_status, idx_outbox_next_attempt, idx_outbox_uid) and `calendarEvents.objectUrl` column; both live in MariaDB - Populated `objectUrl: obj.url ?? null` in both `.values()` and `.onDuplicateKeyUpdate()` blocks of the calendarEvents upsert in sync.ts — enables If-Match writes (D-08) - Installed `vite-plugin-pwa` (legitimacy-gated via Task 1 supply-chain checkpoint T-03-SC) - Created five Wave 0 RED test files covering every Phase 3 behavior: VEVENT builder, CalDAV write layer, outbox state machine, events API routes, and PWA InstallPrompt ## Task Commits 1. **Task 1: Supply-chain gate T-03-SC** — no commit (verification-only checkpoint) 2. **Task 2: Extend Drizzle schema + install vite-plugin-pwa** — `78f0dee` (feat) 3. **Task 3: Populate calendarEvents.objectUrl in sync.ts** — `0c0bcef` (feat) 4. **Task 4: Wave 0 RED test scaffold** — `bbfccda` (test) 5. **Task 5: Push schema to MariaDB** — hand-applied DDL by orchestrator (no code commit; DB verified) ## Files Created/Modified - `apps/api/src/db/schema.ts` — added mysqlEnum import, calendarOutbox table definition, groupId column, 3 indexes; added objectUrl column to calendarEvents - `apps/api/src/broker/sync.ts` — set `objectUrl: obj.url ?? null` in values and onDuplicateKeyUpdate blocks - `apps/api/tests/broker/vevent.test.ts` — RED: VCALENDAR/VEVENT builder tests (timed, all-day D-13, RRULE) - `apps/api/tests/broker/write.test.ts` — RED: createCalendarEvent, updateCalendarEvent (If-Match), deleteCalendarEvent - `apps/api/tests/broker/outboxWorker.test.ts` — RED: outbox state machine (pending→done/failed/backoff/dead), edit-as-move ordering (D-04) - `apps/api/tests/routes/events.test.ts` — extended with POST create, PATCH edit, DELETE, sync-status, writable-calendars, D-03 access control - `apps/pwa/src/components/InstallPrompt.test.tsx` — RED: isIOSSafariNonStandalone(), useAndroidInstallPrompt - `apps/pwa/package.json` — added vite-plugin-pwa dependency ## Decisions Made - **D-Task5-DDL:** `drizzle-kit push` with the `mysql` dialect against a live MariaDB 11 instance produces a FALSE destructive diff — it misreads MariaDB-11 metadata and schedules `truncate table` on `calendars`, `calendar_events`, and `users` (503 events at risk). The two genuinely additive statements were hand-applied by the orchestrator and verified. A follow-up todo (`.planning/todos/pending/adopt-drizzle-migrations-workflow.md`) tracks migrating to `drizzle-kit generate` + `drizzle-kit migrate` before any future schema change. No `drizzle-kit push` should be run against this instance again. ## Deviations from Plan ### Task 5: drizzle-kit push replaced by hand-applied additive DDL **Category:** Orchestrator-resolved deviation (not a Rule 1–4 auto-fix; resolved by human operator per gate instructions) - **Found during:** Task 5 (blocking human-action gate) - **Issue:** `drizzle-kit push` with the Drizzle `mysql` dialect against MariaDB 11 misread database metadata and reported a destructive plan including `truncate table` on `calendars`, `calendar_events`, and `users`. This is a known incompatibility — drizzle-kit 0.31.10 has no `mariadb` dialect; the `mysql` dialect misinterprets MariaDB-11 server metadata. - **Fix:** Orchestrator manually ran only the two additive statements: `CREATE TABLE calendar_outbox (...)` matching schema.ts exactly, and `ALTER TABLE calendar_events ADD COLUMN object_url varchar(1024)`. Data verified intact (calendars=1, calendar_events=503). - **Files modified:** None (DB DDL only; schema.ts was already correct) - **Verification:** `SHOW TABLES LIKE 'calendar_outbox'` → 1 row; `SHOW COLUMNS FROM calendar_events LIKE 'object_url'` → 1 row - **Follow-up:** `.planning/todos/pending/adopt-drizzle-migrations-workflow.md` created to track migrating to generate+migrate workflow --- **Total deviations:** 1 (Task 5 DDL approach replaced; resolved by operator at the blocking gate) **Impact on plan:** No scope creep. Schema is correct. Must-haves fully satisfied. Follow-up todo prevents recurrence. ## Issues Encountered None beyond the Task 5 drizzle-kit deviation documented above. ## User Setup Required None — no external service configuration required for this plan. The schema push was a one-time operation handled by the orchestrator at the Task 5 gate. ## Next Phase Readiness - Wave 0 RED scaffold is in place — plans 03-02 through 03-08 can proceed to GREEN implementation - `calendarOutbox` and `calendarEvents.objectUrl` are live; outbox worker and write routes can reference them immediately - `vite-plugin-pwa` is installed; PWA manifest configuration (Plan 03-06) can proceed - **Action before next schema change:** Adopt `drizzle-kit generate` + `drizzle-kit migrate` (see pending todo) — do NOT run `drizzle-kit push` again --- *Phase: 03-event-write-back-pwa-install* *Completed: 2026-06-05*