diff --git a/Justfile b/Justfile index fd4fd48..8049bd3 100644 --- a/Justfile +++ b/Justfile @@ -47,9 +47,20 @@ db-migrate: db-seed: docker exec -i colectivo-dev-db-1 psql -U postgres < supabase/seed.sql -# Regenerate TypeScript types from Supabase schema +# Regenerate TypeScript types from Supabase schema. +# Writes to a tempfile first so a missing/failing `supabase` CLI does not +# truncate the existing hand-curated database.ts (which would silently +# break every TS consumer in the monorepo). db-types: - supabase gen types typescript --local > packages/types/src/database.ts + #!/usr/bin/env bash + set -euo pipefail + if ! command -v supabase >/dev/null 2>&1; then + echo "supabase CLI not installed — database.ts is hand-curated, edit it manually" >&2 + exit 1 + fi + tmp=$(mktemp) + supabase gen types typescript --local > "$tmp" + mv "$tmp" packages/types/src/database.ts echo "Types written to packages/types/src/database.ts" # Open Supabase Studio diff --git a/packages/types/src/database.ts b/packages/types/src/database.ts index 5bc1cfc..b158f93 100644 --- a/packages/types/src/database.ts +++ b/packages/types/src/database.ts @@ -9,6 +9,7 @@ export type AvatarTypeEnum = 'initials' | 'emoji' | 'upload'; export type MemberRoleEnum = 'admin' | 'member' | 'guest'; export type ListStatusEnum = 'active' | 'completed' | 'archived'; export type NoteColor = 'yellow' | 'green' | 'blue' | 'pink' | 'purple' | 'orange' | 'gray' | 'red'; +export type ThemeMode = 'light' | 'dark' | 'system'; export interface Database { public: { @@ -22,6 +23,7 @@ export interface Database { avatar_type: AvatarTypeEnum; avatar_emoji: string | null; avatar_url: string | null; + theme: ThemeMode; created_at: string; updated_at: string; }; @@ -33,6 +35,7 @@ export interface Database { avatar_type?: AvatarTypeEnum; avatar_emoji?: string | null; avatar_url?: string | null; + theme?: ThemeMode; created_at?: string; updated_at?: string; }; @@ -44,6 +47,7 @@ export interface Database { avatar_type?: AvatarTypeEnum; avatar_emoji?: string | null; avatar_url?: string | null; + theme?: ThemeMode; created_at?: string; updated_at?: string; }; diff --git a/supabase/migrations/015_user_theme.sql b/supabase/migrations/015_user_theme.sql new file mode 100644 index 0000000..50eebd9 --- /dev/null +++ b/supabase/migrations/015_user_theme.sql @@ -0,0 +1,16 @@ +-- Migration 015: theme preference on public.users (Fase 9.1) +-- +-- Stores the user's chosen UI theme: 'light' | 'dark' | 'system'. +-- New users (and existing rows on upgrade) default to 'system' so the UI +-- inherits the OS preference until they explicitly opt in. +-- +-- The JWT minted by GoTrue caches `auth.users` (not `public.users`), so +-- adding this column does NOT invalidate existing sessions — clients read +-- the value via PostgREST `SELECT theme FROM users WHERE id = auth.uid()`. + +ALTER TABLE public.users + ADD COLUMN theme text NOT NULL DEFAULT 'system' + CHECK (theme IN ('light', 'dark', 'system')); + +COMMENT ON COLUMN public.users.theme IS + 'UI theme preference. ''system'' follows prefers-color-scheme.'; diff --git a/supabase/tests/009_user_theme.sql b/supabase/tests/009_user_theme.sql new file mode 100644 index 0000000..b50869f --- /dev/null +++ b/supabase/tests/009_user_theme.sql @@ -0,0 +1,69 @@ +-- pgTAP: public.users.theme column (migration 015) +-- Run with: psql -U postgres -d postgres -f supabase/tests/009_user_theme.sql +-- +-- Fase 9.1 — dark mode feature flagship. Adds `theme` to public.users with +-- three valid values: 'light' | 'dark' | 'system'. New users default to +-- 'system' so the UI matches the OS preference until they opt in. + +CREATE EXTENSION IF NOT EXISTS pgtap; + +BEGIN; +SELECT plan(8); + +-- ── Column existence + type ───────────────────────────────────────────── +SELECT has_column( + 'public', 'users', 'theme', + 'public.users has a theme column' +); + +SELECT col_type_is( + 'public', 'users', 'theme', 'text', + 'public.users.theme is of type text' +); + +SELECT col_not_null( + 'public', 'users', 'theme', + 'public.users.theme is NOT NULL' +); + +SELECT col_default_is( + 'public', 'users', 'theme', 'system', + 'public.users.theme defaults to system' +); + +-- ── Check constraint behaviour ────────────────────────────────────────── +-- Use Ana (seed admin) so the row exists. +UPDATE public.users SET theme = 'light' +WHERE id = '11111111-1111-1111-1111-111111111111'; +SELECT is( + (SELECT theme FROM public.users WHERE id = '11111111-1111-1111-1111-111111111111'), + 'light', + 'theme can be set to light' +); + +UPDATE public.users SET theme = 'dark' +WHERE id = '11111111-1111-1111-1111-111111111111'; +SELECT is( + (SELECT theme FROM public.users WHERE id = '11111111-1111-1111-1111-111111111111'), + 'dark', + 'theme can be set to dark' +); + +UPDATE public.users SET theme = 'system' +WHERE id = '11111111-1111-1111-1111-111111111111'; +SELECT is( + (SELECT theme FROM public.users WHERE id = '11111111-1111-1111-1111-111111111111'), + 'system', + 'theme can be set to system' +); + +-- Invalid value must be rejected by the check constraint. +SELECT throws_ok( + $$UPDATE public.users SET theme = 'sepia' WHERE id = '11111111-1111-1111-1111-111111111111'$$, + '23514', + NULL, + 'theme rejects values other than light|dark|system' +); + +SELECT * FROM finish(); +ROLLBACK;