feat(fase-9): migration 015 — users.theme column for dark mode
Adds `theme text not null default 'system' check (theme in
('light','dark','system'))` to public.users. New users get 'system' so
the UI follows the OS preference until they opt in; existing rows
backfill via the default.
Includes pgTAP test 009 (8 assertions covering column existence/type,
default value, valid transitions, and the check constraint rejecting
unknown values). Wires the theme field into the hand-curated
database.ts type so PostgREST queries stay typed.
Also hardens `just db-types` to bail out (instead of truncating
database.ts) when the supabase CLI is missing — the recipe was silently
wiping the file on this dev machine.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
15
Justfile
15
Justfile
@@ -47,9 +47,20 @@ db-migrate:
|
|||||||
db-seed:
|
db-seed:
|
||||||
docker exec -i colectivo-dev-db-1 psql -U postgres < supabase/seed.sql
|
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:
|
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"
|
echo "Types written to packages/types/src/database.ts"
|
||||||
|
|
||||||
# Open Supabase Studio
|
# Open Supabase Studio
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export type AvatarTypeEnum = 'initials' | 'emoji' | 'upload';
|
|||||||
export type MemberRoleEnum = 'admin' | 'member' | 'guest';
|
export type MemberRoleEnum = 'admin' | 'member' | 'guest';
|
||||||
export type ListStatusEnum = 'active' | 'completed' | 'archived';
|
export type ListStatusEnum = 'active' | 'completed' | 'archived';
|
||||||
export type NoteColor = 'yellow' | 'green' | 'blue' | 'pink' | 'purple' | 'orange' | 'gray' | 'red';
|
export type NoteColor = 'yellow' | 'green' | 'blue' | 'pink' | 'purple' | 'orange' | 'gray' | 'red';
|
||||||
|
export type ThemeMode = 'light' | 'dark' | 'system';
|
||||||
|
|
||||||
export interface Database {
|
export interface Database {
|
||||||
public: {
|
public: {
|
||||||
@@ -22,6 +23,7 @@ export interface Database {
|
|||||||
avatar_type: AvatarTypeEnum;
|
avatar_type: AvatarTypeEnum;
|
||||||
avatar_emoji: string | null;
|
avatar_emoji: string | null;
|
||||||
avatar_url: string | null;
|
avatar_url: string | null;
|
||||||
|
theme: ThemeMode;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
};
|
};
|
||||||
@@ -33,6 +35,7 @@ export interface Database {
|
|||||||
avatar_type?: AvatarTypeEnum;
|
avatar_type?: AvatarTypeEnum;
|
||||||
avatar_emoji?: string | null;
|
avatar_emoji?: string | null;
|
||||||
avatar_url?: string | null;
|
avatar_url?: string | null;
|
||||||
|
theme?: ThemeMode;
|
||||||
created_at?: string;
|
created_at?: string;
|
||||||
updated_at?: string;
|
updated_at?: string;
|
||||||
};
|
};
|
||||||
@@ -44,6 +47,7 @@ export interface Database {
|
|||||||
avatar_type?: AvatarTypeEnum;
|
avatar_type?: AvatarTypeEnum;
|
||||||
avatar_emoji?: string | null;
|
avatar_emoji?: string | null;
|
||||||
avatar_url?: string | null;
|
avatar_url?: string | null;
|
||||||
|
theme?: ThemeMode;
|
||||||
created_at?: string;
|
created_at?: string;
|
||||||
updated_at?: string;
|
updated_at?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
16
supabase/migrations/015_user_theme.sql
Normal file
16
supabase/migrations/015_user_theme.sql
Normal file
@@ -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.';
|
||||||
69
supabase/tests/009_user_theme.sql
Normal file
69
supabase/tests/009_user_theme.sql
Normal file
@@ -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;
|
||||||
Reference in New Issue
Block a user