wulf-pulse/.planning/phases/09-user-profile-preferences-new/09-01-SUMMARY.md
lorentz 485053c639 docs(09-01): complete schema and types foundation plan
- SUMMARY.md for plan 09-01: theme column, personal channels, event-key and subscription tables
2026-05-10 07:24:23 -04:00

6.4 KiB

phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics
09-user-profile-preferences-new 01 schema-and-types
migration
auth
types
schema
phase-9
requires provides affects
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
lib/types/pipeline.ts — downstream code using NotificationChannel
lib/auth.ts — session shape gains theme field
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
created modified
migrations/084_add_user_theme.sql
migrations/085_personal_notification_channels.sql
migrations/086_notify_event_keys_and_subscriptions.sql
lib/auth.ts
lib/types/pipeline.ts
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
duration_minutes completed_date tasks_completed files_created files_modified
1 2026-05-10 2 3 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:

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:

TypeScript: npx tsc --noEmit --pretty exit 0 — no errors.