feat(fase-11): item_tags + shopping_item_tags schema (11.1)

Migration 022 adds two new tables for collective-scoped item tags plus the
many-to-many bridge to shopping_items. Tags carry a colour from an 8-preset
palette enforced by CHECK. RLS uses is_member for SELECT (guests can see)
and is_active_member for writes (guests are read-only). A new STABLE helper
item_collective_id(uuid) resolves the parent collective in a single function
call so the shopping_item_tags policies do not chain two STABLE lookups.

Both tables join the supabase_realtime publication with REPLICA IDENTITY
FULL, matching the shape of 007 for shopping_items / shopping_lists.

pgTAP 014 covers schema invariants (publication, replica identity, unique
+ CHECK constraints, cascade behaviour for both tag delete and collective
delete). Per-role RLS semantics will be covered by the Vitest integration
suite in the next commit — pgTAP runs as postgres which bypasses RLS.

Types: ItemTag, ItemWithTags + ItemTagColor in domain.ts; item_tags,
shopping_item_tags, item_collective_id added by hand to the curated
database.ts (the supabase CLI is not installed locally; just db-types is
gated and skips with a notice).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-18 03:01:52 +02:00
parent a868b5a314
commit 4c3552fb3c
4 changed files with 310 additions and 2 deletions

View File

@@ -10,6 +10,15 @@ 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 type ItemTagColor =
| 'slate'
| 'red'
| 'amber'
| 'green'
| 'sky'
| 'indigo'
| 'pink'
| 'stone';
export interface Database {
public: {
@@ -414,6 +423,45 @@ export interface Database {
};
Relationships: [];
};
item_tags: {
Row: {
id: string;
collective_id: string;
name: string;
color: ItemTagColor;
created_at: string;
};
Insert: {
id?: string;
collective_id: string;
name: string;
color?: ItemTagColor;
created_at?: string;
};
Update: {
id?: string;
collective_id?: string;
name?: string;
color?: ItemTagColor;
created_at?: string;
};
Relationships: [];
};
shopping_item_tags: {
Row: {
item_id: string;
tag_id: string;
};
Insert: {
item_id: string;
tag_id: string;
};
Update: {
item_id?: string;
tag_id?: string;
};
Relationships: [];
};
};
Views: Record<string, never>;
Functions: {
@@ -433,6 +481,10 @@ export interface Database {
Args: { p_collective_id: string };
Returns: boolean;
};
item_collective_id: {
Args: { p_item_id: string };
Returns: string;
};
};
Enums: {
language_code: LanguageCode;

View File

@@ -6,8 +6,8 @@ export type AvatarType = 'initials' | 'emoji' | 'upload';
export type MemberRole = 'admin' | 'member' | 'guest';
export type ListStatus = 'active' | 'completed' | 'archived';
// NoteColor is defined in database.ts (matches the Postgres enum) and re-exported via index.ts
import type { NoteColor } from './database.js';
export type { NoteColor };
import type { NoteColor, ItemTagColor } from './database.js';
export type { NoteColor, ItemTagColor };
export type Language = 'en' | 'es';
export interface User {
@@ -79,6 +79,19 @@ export interface ItemFrequency {
last_used_at: string;
}
export interface ItemTag {
id: string;
collective_id: string;
name: string;
color: ItemTagColor;
created_at: string;
}
/** Shopping item with the tag rows already joined in. */
export interface ItemWithTags extends ShoppingItem {
tags: ItemTag[];
}
export interface TaskList {
id: string;
collective_id: string;