Improve chat reliability and Supabase migration hardening#62
Improve chat reliability and Supabase migration hardening#620x3EF8 wants to merge 1 commit intohallofcodes:masterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR syncs the fork snapshot onto an upstream-compatible branch while tightening the Supabase baseline migration flow and improving chat realtime UX (dedupe/optimistic messaging, unread count accuracy, and safer conversation creation).
Changes:
- Introduces a squashed Supabase baseline migration for fresh installs and archives historical migrations.
- Improves chat realtime behavior (broadcast-based optimistic delivery, duplicate-send prevention, unread count refresh, user picker sourcing).
- Updates project docs and regenerated Supabase TypeScript types; includes a small dependency lockfile bump.
Reviewed changes
Copilot reviewed 32 out of 43 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| supabase/migrations/20260407120000_baseline_fresh_setup.sql | New consolidated baseline migration including chat tables/RLS, triggers, and archived migration content. |
| supabase/migrations/*.sql (removed) | Removes individual historical migrations now represented in the baseline. |
| supabase/migrations_archive/*.sql | Adds archived copies of historical migrations for reference/inspection. |
| README.md | Documents the new baseline + archive migration workflow and setup steps. |
| .gitignore | Un-ignores supabase/migrations_archive so archived migrations are tracked. |
| .env.example | Updates example Supabase project ID value. |
| app/supabase-types.ts | Regenerates types to match updated schema. |
| app/components/Chat.tsx | Integrates new composer/stream behavior and memoizes message filtering. |
| app/components/chat/hooks/useChatUserPicker.ts | Switches user picker source to global conversation participants. |
| app/components/chat/hooks/useChatMessageComposer.ts | Adds send dedupe, sending state, and broadcast optimistic messages + retract. |
| app/components/chat/hooks/useChatInputBehavior.ts | Blocks Enter-to-send while a send is in-flight. |
| app/components/chat/hooks/useChatConversationsRealtime.ts | Refreshes unread counts via server-derived counts instead of local increments. |
| app/components/chat/hooks/useChatConversationActions.ts | Hardens conversation creation (explicit IDs, better rollback + error surfacing). |
| app/components/chat/hooks/useActiveConversationStream.ts | Adds broadcast message handling and reconciles ephemeral vs persisted messages. |
| app/components/dashboard/Navbar.tsx | Changes unread refresh strategy and sidebar collapsed defaults. |
| package-lock.json | Updates a few transitive dependency versions/integrities. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| set email = excluded.email; | ||
| return new; | ||
| end; | ||
| $$ language plpgsql security definer; |
There was a problem hiding this comment.
public.handle_new_user() is declared SECURITY DEFINER but does not set an explicit search_path. This is a known Postgres footgun because a caller-controlled search_path can change which objects are resolved inside the function. Set SET search_path = public (optionally including pg_temp) on the function definition to harden it.
| $$ language plpgsql security definer; | |
| $$ language plpgsql security definer | |
| set search_path = public; |
| CREATE OR REPLACE VIEW top_user_stats AS | ||
| SELECT | ||
| us.user_id, | ||
| us.total_seconds, | ||
| a.email | ||
| FROM user_stats us |
There was a problem hiding this comment.
The top_user_stats view is created without a schema qualifier (CREATE OR REPLACE VIEW top_user_stats). In migrations this can end up in an unexpected schema if search_path differs, and it also makes dependencies less explicit. Prefer CREATE OR REPLACE VIEW public.top_user_stats ... (and schema-qualify referenced tables consistently).
| CREATE OR REPLACE VIEW top_user_stats AS | |
| SELECT | |
| us.user_id, | |
| us.total_seconds, | |
| a.email | |
| FROM user_stats us | |
| CREATE OR REPLACE VIEW public.top_user_stats AS | |
| SELECT | |
| us.user_id, | |
| us.total_seconds, | |
| a.email | |
| FROM public.user_stats us |
| @@ -1,4 +1,4 @@ | |||
| export type Json = | |||
| export type Json = | |||
There was a problem hiding this comment.
The generated types file appears to start with a UTF-8 BOM (there is an invisible character before export type Json). This can cause linting/formatting or tooling issues in some environments. Re-save/regenerate the file without the BOM so the first character is e in export.
| export type Json = | |
| export type Json = |
|
|
||
| SUPABASE_ACCESS_TOKEN= | ||
| SUPABASE_PROJECT_ID= | ||
| SUPABASE_PROJECT_ID=vswabkwgipyweqsabzwv |
There was a problem hiding this comment.
.env.example should not contain a real Supabase project ref; it’s meant to be a template. Replace the concrete value with an empty value or obvious placeholder (e.g. <project-id>), so users don’t accidentally link/push migrations to the wrong project.
| SUPABASE_PROJECT_ID=vswabkwgipyweqsabzwv | |
| SUPABASE_PROJECT_ID=<project-id> |
| setMobileHidden(true); | ||
| } else { | ||
| setCollapsed(false); | ||
| setCollapsed(true); |
There was a problem hiding this comment.
On desktop resize, the else branch now forces collapsed to true, which makes the sidebar start collapsed and also re-collapse whenever the window resizes. If the intended behavior is to show the expanded sidebar on desktop (as before), this should set collapsed to false in the non-mobile branch.
| setCollapsed(true); | |
| setCollapsed(false); |
| CREATE OR REPLACE VIEW top_user_stats AS | ||
| SELECT | ||
| us.user_id, | ||
| us.total_seconds, | ||
| a.email, | ||
| us.categories -- new column | ||
| FROM user_stats us | ||
| JOIN auth.users a ON us.user_id = a.id; |
There was a problem hiding this comment.
This second CREATE OR REPLACE VIEW top_user_stats statement is also missing an explicit schema. Qualify it as public.top_user_stats here as well so the baseline migration is deterministic regardless of search_path.
| values (new.id, new.email) | ||
| on conflict (id) do update | ||
| set email = excluded.email; |
There was a problem hiding this comment.
public.profiles.email is defined NOT NULL, but handle_new_user() inserts NEW.email directly. Supabase auth.users.email can be NULL (e.g., phone-based signups), which would make this trigger fail and block user creation. Consider using a fallback (like the @user.local pattern used elsewhere) or relaxing the profiles.email constraint if email-less accounts are supported.
| values (new.id, new.email) | |
| on conflict (id) do update | |
| set email = excluded.email; | |
| values (new.id, coalesce(new.email, new.id::text || '@user.local')) | |
| on conflict (id) do update | |
| set email = coalesce(excluded.email, public.profiles.id::text || '@user.local'); |
Summary
Validation