From 485053c6398ccef86a490bd1fcdb3087fc7ff1c5 Mon Sep 17 00:00:00 2001 From: lorentz Date: Sun, 10 May 2026 07:24:23 -0400 Subject: [PATCH] docs(09-01): complete schema and types foundation plan - SUMMARY.md for plan 09-01: theme column, personal channels, event-key and subscription tables --- .../09-01-SUMMARY.md | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 .planning/phases/09-user-profile-preferences-new/09-01-SUMMARY.md diff --git a/.planning/phases/09-user-profile-preferences-new/09-01-SUMMARY.md b/.planning/phases/09-user-profile-preferences-new/09-01-SUMMARY.md new file mode 100644 index 0000000..cd802e6 --- /dev/null +++ b/.planning/phases/09-user-profile-preferences-new/09-01-SUMMARY.md @@ -0,0 +1,126 @@ +--- +phase: 09-user-profile-preferences-new +plan: "01" +subsystem: schema-and-types +tags: [migration, auth, types, schema, phase-9] +dependency_graph: + requires: [] + provides: + - "user.theme column with DEFAULT 'system' and backfill" + - "session.user.theme via Better Auth additionalFields" + - "notification_channels.owner_user_id column + cascade delete" + - "partial unique index notification_channels_owner_user_id_channel_type_uniq" + - "notify_event_keys table (admin-CRUD, humanization layer)" + - "user_event_subscriptions table (composite PK, opt-out model)" + - "NotifyEventKey TypeScript interface" + - "UserEventSubscription TypeScript interface" + - "NotificationChannel.owner_user_id: string | null TypeScript field" + affects: + - "lib/types/pipeline.ts — downstream code using NotificationChannel" + - "lib/auth.ts — session shape gains theme field" +tech_stack: + added: [] + patterns: + - "Mirror 083_add_user_timezone.sql pattern for theme column (ALTER + backfill + COMMENT)" + - "partial unique index WHERE owner_user_id IS NOT NULL for defense-in-depth UPSERT safety" + - "Better Auth additionalFields for server-canonical user preferences" +key_files: + created: + - migrations/084_add_user_theme.sql + - migrations/085_personal_notification_channels.sql + - migrations/086_notify_event_keys_and_subscriptions.sql + modified: + - lib/auth.ts + - lib/types/pipeline.ts +decisions: + - "Mirrored 083_add_user_timezone.sql structure exactly (ALTER + UPDATE backfill + COMMENT ON COLUMN)" + - "theme defaults to 'system' — zero behavior change for existing users; next-themes detects OS at render time" + - "partial unique index on notification_channels is defense-in-depth for Plan 02 API-layer UPSERT race window" + - "user_event_subscriptions uses row-absence = enabled (opt-out) model per D-15" + - "notify_event_keys is humanization layer only, not a gate — unknown keys are still routable" +metrics: + duration_minutes: 1 + completed_date: "2026-05-10" + tasks_completed: 2 + files_created: 3 + files_modified: 2 +--- + +# Phase 9 Plan 01: Schema and Types Foundation Summary + +One-liner: Three sequential migrations land theme column, personal-channel ownership, and event-subscription tables; TypeScript types extended to match; session.user.theme exposed via Better Auth additionalFields following Phase 7.1 precedent. + +## Tasks Completed + +| Task | Name | Commit | Files | +|------|------|--------|-------| +| 1 | Add theme column to user + Better Auth additionalField | 4fc4a3d | migrations/084_add_user_theme.sql, lib/auth.ts | +| 2 | Add owner_user_id + partial unique index to notification_channels + create event-keys / subscriptions tables | b4f8cca | migrations/085_personal_notification_channels.sql, migrations/086_notify_event_keys_and_subscriptions.sql, lib/types/pipeline.ts | + +## What Was Built + +### migrations/084_add_user_theme.sql +Adds `theme TEXT NOT NULL DEFAULT 'system'` to the Better Auth `"user"` table. + +- `ALTER TABLE "user" ADD COLUMN IF NOT EXISTS theme TEXT NOT NULL DEFAULT 'system'` +- Defensive backfill: `UPDATE "user" SET theme = 'system' WHERE theme IS NULL` +- `COMMENT ON COLUMN "user".theme` documents allowed values (`light | dark | system`) +- Mirrors 083_add_user_timezone.sql structure exactly + +### migrations/085_personal_notification_channels.sql +Extends `notification_channels` to support personal (per-user) channels alongside existing global rows. + +- `ALTER TABLE notification_channels ADD COLUMN IF NOT EXISTS owner_user_id TEXT REFERENCES "user"(id) ON DELETE CASCADE` +- Cascade delete: removing a Better Auth user removes their personal channels automatically +- Non-unique index `idx_notification_channels_owner` on `(owner_user_id, channel_type) WHERE owner_user_id IS NOT NULL` for query performance +- Partial unique index `notification_channels_owner_user_id_channel_type_uniq` on `(owner_user_id, channel_type) WHERE owner_user_id IS NOT NULL` — enforces one-personal-channel-per-type-per-user at DB layer; global rows (NULL owner) are exempt +- `COMMENT ON COLUMN notification_channels.owner_user_id` documents the global vs personal semantics + +### migrations/086_notify_event_keys_and_subscriptions.sql +Creates two new tables for the notification event taxonomy and per-user subscription matrix. + +- `notify_event_keys` table: `key TEXT PRIMARY KEY`, `display_label`, `description`, `sort_order`, `is_active`, audit columns. Index on `(is_active, sort_order, key)`. Seeded with `ticket_assigned_to_me` row via `INSERT ... ON CONFLICT (key) DO NOTHING`. +- `user_event_subscriptions` table: composite `PRIMARY KEY (user_id, event_key, channel_type)`, `enabled BOOLEAN NOT NULL DEFAULT true`, `updated_at`. `user_id` cascades on user delete. Index on `(user_id)`. +- Table comments document the opt-out model (row absence = enabled) and the humanization-layer-only role of event keys. + +### lib/auth.ts — theme additionalField +Added `theme` as the fourth `additionalFields` entry, immediately after `timezone`: + +```typescript +theme: { + type: "string", + defaultValue: "system", +}, +``` + +`session.user.theme` is now exposed via Better Auth in the same way `session.user.timezone` is (Phase 7.1 precedent). The server-side value becomes available after the next session refresh. + +### lib/types/pipeline.ts — type extensions +Three changes, no other types touched: + +1. `NotificationChannel` gains `owner_user_id: string | null` (inserted after `is_active`) +2. New exported interface `NotifyEventKey` with fields `key`, `display_label`, `description: string | null`, `sort_order`, `is_active`, `created_at`, `updated_at` +3. New exported interface `UserEventSubscription` with fields `user_id`, `event_key`, `channel_type: ChannelType`, `enabled`, `updated_at` + +## Deviations from Plan + +None — plan executed exactly as written. + +## Threat Flags + +No new network endpoints, auth paths, file access patterns, or schema changes at trust boundaries beyond what was declared in the plan's threat model. All six threats documented in plan T-09-01-01 through T-09-01-06 are addressed by the schema choices made here (cascade delete, composite PK, partial unique index, NOT NULL DEFAULT). + +## Self-Check: PASSED + +Files exist: +- migrations/084_add_user_theme.sql: FOUND +- migrations/085_personal_notification_channels.sql: FOUND +- migrations/086_notify_event_keys_and_subscriptions.sql: FOUND +- lib/auth.ts: FOUND (modified) +- lib/types/pipeline.ts: FOUND (modified) + +Commits exist: +- 4fc4a3d: FOUND (Task 1) +- b4f8cca: FOUND (Task 2) + +TypeScript: `npx tsc --noEmit --pretty` exit 0 — no errors.