-- Phase 4B: per-conversation whiteboards. -- -- conversation_whiteboards: one row per whiteboard. Created by a member; the -- bubble that appears in the chat message timeline is a normal `messages` -- row whose plaintext payload is `{v:1, type:'whiteboard', whiteboard_id:}`. -- -- whiteboard_strokes: append-only stream of drawing operations. Each row is -- one user gesture (pen-down → pen-up); stroke_json carries the tool/color/ -- width/points. Realtime subscribers replay rows in created_at order. create table if not exists public.conversation_whiteboards ( id uuid primary key default gen_random_uuid(), conversation_id uuid not null references public.conversations(id) on delete cascade, owner_user_id uuid not null references auth.users(id) on delete cascade, created_at timestamptz not null default now() ); create index if not exists conversation_whiteboards_conv_idx on public.conversation_whiteboards(conversation_id, created_at desc); create table if not exists public.whiteboard_strokes ( id uuid primary key default gen_random_uuid(), whiteboard_id uuid not null references public.conversation_whiteboards(id) on delete cascade, author_user_id uuid not null references auth.users(id) on delete cascade, stroke_json jsonb not null, created_at timestamptz not null default now() ); create index if not exists whiteboard_strokes_board_idx on public.whiteboard_strokes(whiteboard_id, created_at asc); alter table public.conversation_whiteboards enable row level security; alter table public.whiteboard_strokes enable row level security; drop policy if exists conversation_whiteboards_select on public.conversation_whiteboards; drop policy if exists conversation_whiteboards_insert on public.conversation_whiteboards; drop policy if exists conversation_whiteboards_delete on public.conversation_whiteboards; drop policy if exists whiteboard_strokes_select on public.whiteboard_strokes; drop policy if exists whiteboard_strokes_insert on public.whiteboard_strokes; drop policy if exists whiteboard_strokes_delete on public.whiteboard_strokes; create policy conversation_whiteboards_select on public.conversation_whiteboards for select using (public.is_conversation_member(conversation_id)); create policy conversation_whiteboards_insert on public.conversation_whiteboards for insert with check ( public.is_conversation_member(conversation_id) and owner_user_id = auth.uid() ); create policy conversation_whiteboards_delete on public.conversation_whiteboards for delete using (owner_user_id = auth.uid()); create policy whiteboard_strokes_select on public.whiteboard_strokes for select using ( exists ( select 1 from public.conversation_whiteboards w where w.id = whiteboard_strokes.whiteboard_id and public.is_conversation_member(w.conversation_id) ) ); create policy whiteboard_strokes_insert on public.whiteboard_strokes for insert with check ( author_user_id = auth.uid() and exists ( select 1 from public.conversation_whiteboards w where w.id = whiteboard_id and public.is_conversation_member(w.conversation_id) ) ); create policy whiteboard_strokes_delete on public.whiteboard_strokes for delete using ( exists ( select 1 from public.conversation_whiteboards w where w.id = whiteboard_strokes.whiteboard_id and public.is_conversation_member(w.conversation_id) ) ); alter table public.conversation_whiteboards replica identity full; alter table public.whiteboard_strokes replica identity full; do $$ begin if not exists ( select 1 from pg_publication_tables where pubname = 'supabase_realtime' and schemaname = 'public' and tablename = 'conversation_whiteboards' ) then execute 'alter publication supabase_realtime add table public.conversation_whiteboards'; end if; if not exists ( select 1 from pg_publication_tables where pubname = 'supabase_realtime' and schemaname = 'public' and tablename = 'whiteboard_strokes' ) then execute 'alter publication supabase_realtime add table public.whiteboard_strokes'; end if; end $$;