feat(fase-18): migration 027 — list title catalog + per-collective default
Adds `collectives.default_list_title` (nullable text) plus a new `collective_list_titles (collective_id, title)` table — admins curate via two SECURITY DEFINER RPCs (`add_list_title` / `remove_list_title`, P0002 on non-admin, idempotent on conflict). Members read; direct writes are all-deny RLS, matching the item_frequency pattern. Table is added to the `supabase_realtime` publication with REPLICA IDENTITY FULL so the manage UI sees admin edits live across browsers. Numbering rules (#N suffix) stay client-side intentionally — the DB is agnostic about the format so future "YYYY-MM" or other prefixes don't require a schema migration. The two-clients-race-on-#6 case is accepted (documented in migration header); a unique constraint would block deliberate duplicates. Wires `default_list_title` through every Collective construction site: loadUserCollectives in root layout, onboarding, CreateCollectiveModal, features.ts realtime subscriber, and the manage-collective patcher. The hand-curated `database.ts` gets the new column, table, and two RPC signatures. pgTAP `019_list_title_catalog.sql` (13 assertions): schema invariants on the column + table, PK shape, RLS posture, two-policies count, RPC existence + SECURITY DEFINER flag, realtime publication membership, and the default-NULL behaviour. Role-gate denial is covered by the upcoming list-title-flow integration suite (postgres bypasses auth.uid inside pgTAP, same caveat as Fase 15's 018_item_frequency_weight.sql). Tests: 191 pgTAP (was 178; +13). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -72,6 +72,7 @@ export interface Database {
|
||||
emoji: string;
|
||||
created_by: string;
|
||||
feature_flags: Json;
|
||||
default_list_title: string | null;
|
||||
created_at: string;
|
||||
deleted_at: string | null;
|
||||
};
|
||||
@@ -81,6 +82,7 @@ export interface Database {
|
||||
emoji?: string;
|
||||
created_by: string;
|
||||
feature_flags?: Json;
|
||||
default_list_title?: string | null;
|
||||
created_at?: string;
|
||||
deleted_at?: string | null;
|
||||
};
|
||||
@@ -90,6 +92,7 @@ export interface Database {
|
||||
emoji?: string;
|
||||
created_by?: string;
|
||||
feature_flags?: Json;
|
||||
default_list_title?: string | null;
|
||||
created_at?: string;
|
||||
deleted_at?: string | null;
|
||||
};
|
||||
@@ -136,6 +139,31 @@ export interface Database {
|
||||
}
|
||||
];
|
||||
};
|
||||
collective_list_titles: {
|
||||
Row: {
|
||||
collective_id: string;
|
||||
title: string;
|
||||
created_at: string;
|
||||
};
|
||||
Insert: {
|
||||
collective_id: string;
|
||||
title: string;
|
||||
created_at?: string;
|
||||
};
|
||||
Update: {
|
||||
collective_id?: string;
|
||||
title?: string;
|
||||
created_at?: string;
|
||||
};
|
||||
Relationships: [
|
||||
{
|
||||
foreignKeyName: 'collective_list_titles_collective_id_fkey';
|
||||
columns: ['collective_id'];
|
||||
referencedRelation: 'collectives';
|
||||
referencedColumns: ['id'];
|
||||
}
|
||||
];
|
||||
};
|
||||
collective_invitations: {
|
||||
Row: {
|
||||
id: string;
|
||||
@@ -629,6 +657,14 @@ export interface Database {
|
||||
Args: { p_collective_id: string; p_name: string };
|
||||
Returns: void;
|
||||
};
|
||||
add_list_title: {
|
||||
Args: { p_collective_id: string; p_title: string };
|
||||
Returns: void;
|
||||
};
|
||||
remove_list_title: {
|
||||
Args: { p_collective_id: string; p_title: string };
|
||||
Returns: void;
|
||||
};
|
||||
};
|
||||
Enums: {
|
||||
language_code: LanguageCode;
|
||||
|
||||
Reference in New Issue
Block a user