Skip to content

Supabase functionality #729

Open
Open

Description

@ngoiyaeric

Thoroughly analyze supabase clerk configuration for complete backend intergration such that document attachments and retrieval augmented generation are possible. Multi user collaboration through real time and that the update delete functions for save chat are all functional. Perform a deep analysis for this and forsee future issues.

Activity

  1. ngoiyaeric commented on Jul 10, 2026

    @ngoiyaeric
    CollaboratorAuthor
  2. ngoiyaeric commented on Jul 10, 2026

    @ngoiyaeric
    CollaboratorAuthor
  3. coderabbitai commented on Jul 10, 2026

    @coderabbitai
    Contributor

    Warning

    A plan generation is already in progress. Please wait for it to complete before requesting a new plan.

  4. coderabbitai commented on Jul 10, 2026

    @coderabbitai
    Contributor

    Coding Plan

    Summary

    • Convert the mock Supabase clients into real Clerk-native-third-party-auth clients so end-user requests can drive Supabase RLS, Storage, and Realtime — the missing foundation the whole ticket depends on.
    • Finish chat CRUD by exposing the existing delete primitive and adding rename/visibility via new server actions, a [id] API route, and per-item history UI controls.
    • Build a real document-attachment + RAG pipeline (Storage upload → parse/chunk/embed into new documents/document_chunks pgvector tables → new agent retrieval tool) reusing existing tool and schema patterns.
    • Activate multi-user collaboration by implementing chat_participants access control, re-enabling sharing, and adding RLS-gated postgres_changes Realtime subscriptions, with explicit hardening for dual-write identity consistency and concurrent-write safety.
    Design Choices
    Design Choice 1: How should end-user requests authenticate to Supabase for Storage/Realtime/RLS?

    Options Considered:

    1. Adopt Clerk's native third-party auth integration (Clerk session token accepted directly by Supabase), configure Supabase clients with an accessToken callback, and enforce access via the existing clerk_id()/is_clerk_user() RLS functions.
    2. Keep the deprecated Clerk "Supabase JWT template" approach (mint a Supabase-signed token per request).
    3. Continue service-role-only access and proxy all Storage/Realtime through custom server endpoints.

    Chosen Option: 1

    Rationale: Web search confirms Clerk deprecated JWT templates (Mar 2025) in favor of native third-party auth, which works across Data API, Storage, and Realtime with RLS and avoids sharing JWT secrets — aligning with the existing clerk_id()/is_clerk_user() RLS bridge already in migrations.

    Design Choice 2: Where should uploaded-document embeddings live for RAG?

    Options Considered:

    1. Reuse messages.embedding and store document chunks as pseudo-messages.
    2. Add dedicated documents + document_chunks tables (chunk text + vector(1536) embedding), with a retrieval RPC.

    Chosen Option: 2

    Rationale: A dedicated documents/chunks model keeps attachment lifecycle, chunking, and ownership distinct from chat messages and mirrors the existing pgvector customType pattern without overloading messages.

    Design Choice 3: Which Realtime mechanism should power collaboration?

    Options Considered:

    1. postgres_changes subscriptions on messages/chat_participants (RLS-gated, simplest).
    2. Broadcast/private channels with realtime.messages RLS policies.

    Chosen Option: 1

    Rationale: Per web search, postgres_changes adheres to existing table RLS with no extra authorization tables, matching the already-defined RLS policies; Broadcast/Presence can be layered later for cursors/typing if needed.

    💡 User Tips

    Regenerate the plan with different choices with @coderabbitai <feedback>.

    Implementation Steps

    Phase 1: Establish a Real End-User Clerk↔Supabase Auth Bridge

    This foundational phase replaces the inert mock Supabase clients with real clients that carry the signed-in Clerk user's identity, so that Supabase RLS, Storage, and Realtime can authorize interactive requests (not just the service-role webhook). This unblocks every subsequent phase, since Storage uploads, RAG retrieval, and Realtime collaboration all depend on per-user Supabase authorization.

    Task 1: Configure Clerk native third-party auth with Supabase

    Enable Supabase to trust Clerk session tokens and document the required configuration.

    • Document (in a setup note alongside CLERK_SUPABASE_FIX.md) the Clerk dashboard + Supabase "Third-Party Auth (Clerk)" configuration steps, replacing the deprecated JWT-template approach.
    • Add any needed env vars to .env.local.example (Supabase URL/anon key already present); confirm no shared JWT secret is required under native integration.
    • Verify the existing clerk_id()/is_clerk_user() RLS helpers resolve correctly against the identity now carried by Clerk-issued tokens; add a migration only if the claim lookup needs adjustment for the native token's sub.
    Task 2: Implement functional browser and server Supabase clients bound to Clerk

    Replace the mock stubs with real, identity-aware clients.

    • Rewrite lib/supabase/browser-client.ts to create a @supabase/supabase-js client whose accessToken callback returns the current Clerk session token (via Clerk's client-side session), so browser Storage/Realtime calls authorize as the user.
    • Rewrite lib/supabase/client.ts (server) to create a client whose accessToken callback returns the Clerk token from @clerk/nextjs/server's auth(), for server-side RLS-gated reads/writes and signed-URL generation.
    • Preserve the existing service-role client usage in app/api/clerk/webhook/route.ts for the sync RPC (privileged path unchanged).
    Task 3: Reconcile identity and connection configuration

    Remove the documented env/connection inconsistencies that will otherwise cause subtle production failures.

    • Align .env.local.example so the Drizzle connection variable actually read by code (POSTGRES_URL) is documented consistently with DATABASE_URL.
    • Confirm users.clerk_user_id linkage remains authoritative so Drizzle-based ownership checks and Supabase RLS agree on the same user mapping.
    🤖 Prompt for AI agents
    Implement the Clerk↔Supabase end-user auth bridge that replaces the currently
    inert mock Supabase clients with real, identity-aware clients, using Clerk's
    native third-party auth integration (not the deprecated JWT-template approach).
    
    - Configure Clerk dashboard + Supabase "Third-Party Auth (Clerk)" settings;
    write a setup note alongside `CLERK_SUPABASE_FIX.md` documenting these steps.
    - Update `.env.local.example` with any needed env vars (Supabase URL/anon key
    already present); confirm no shared JWT secret is needed under native
    integration.
    - Verify `clerk_id()`/`is_clerk_user()` RLS helper functions correctly resolve
    identity from Clerk-issued tokens; only add a new migration if the claim lookup
    needs adjustment for the native token's `sub` claim.
    - Rewrite `lib/supabase/browser-client.ts`: create a `@supabase/supabase-js`
    client with an `accessToken` callback that returns the current Clerk client-side
    session token, so browser Storage/Realtime calls authorize as the signed-in
    user.
    - Rewrite `lib/supabase/client.ts` (server): create a client with an
    `accessToken` callback that returns the Clerk token via `@clerk/nextjs/server`'s
    `auth()`, for server-side RLS-gated reads/writes and signed-URL generation.
    - Keep `app/api/clerk/webhook/route.ts`'s existing service-role client and
    `sync_clerk_user` RPC usage unchanged (this privileged path is separate from the
    new user-bound clients).
    - Align `.env.local.example` so the Drizzle connection variable actually read by
    code (`POSTGRES_URL`) is documented consistently alongside `DATABASE_URL`,
    removing the inconsistency.
    - Confirm `users.clerk_user_id` remains the authoritative linkage so
    Drizzle-based ownership checks and Supabase RLS checks agree on the same user
    mapping.
    

    Phase 2: Complete Chat CRUD (Update & Delete)

    This phase makes the save-chat lifecycle fully functional by exposing the already-existing delete primitive and adding rename/visibility update, then surfacing both in the history UI. It builds directly on existing patterns (chat-db.ts, chat.ts, app/api/chats/*).

    Task 1: Add server-action and DB update/delete capabilities

    Expose per-chat mutations with ownership enforcement.

    • Add updateChat (title/visibility) to lib/actions/chat-db.ts, and add server-action wrappers deleteChat and updateChat in lib/actions/chat.ts that resolve the current user and enforce ownership, then revalidatePath('/').
    • Reuse the existing (currently unwired) deleteChat(id, userId) in lib/actions/chat-db.ts.
    Task 2: Add per-chat API routes

    Provide REST endpoints paralleling the existing bulk routes.

    • Create app/api/chats/[id]/route.ts supporting DELETE (single chat) and PATCH (rename/visibility), calling the new server actions and enforcing Clerk auth like app/api/chats/all/route.ts.
    Task 3: Wire delete/rename affordances into the history UI

    Give users per-chat controls.

    • Extend components/history-item.tsx (and its container components/sidebar/chat-history-client.tsx) with a per-item menu offering Delete (confirm dialog, mirroring the existing clear-history AlertDialog) and Rename (inline edit) that call the new API routes and refresh the list.
    • Add data-testid hooks consistent with existing test conventions in tests/sidebar.spec.ts.
    🤖 Prompt for AI agents
    Complete the chat CRUD surface by exposing the existing delete primitive and
    adding rename/visibility update, then surfacing both in the history UI, building
    directly on existing patterns in `chat-db.ts`, `chat.ts`, and `app/api/chats/*`.
    
    - In `lib/actions/chat-db.ts`, add an `updateChat` function (title/visibility)
    alongside the existing `deleteChat(id, userId)` DB primitive.
    - In `lib/actions/chat.ts`, add server-action wrappers `deleteChat` and
    `updateChat` that resolve the current user, enforce ownership, and call
    `revalidatePath('/')` after mutation.
    - Reuse the existing (currently unwired) `deleteChat(id, userId)` DB function
    rather than duplicating logic.
    - Create `app/api/chats/[id]/route.ts` supporting `DELETE` (single chat) and
    `PATCH` (rename/visibility) HTTP methods, calling the new server actions and
    enforcing Clerk auth the same way as `app/api/chats/all/route.ts`.
    - Extend `components/history-item.tsx` and its container
    `components/sidebar/chat-history-client.tsx` with a per-item menu offering:
    Delete (with a confirmation dialog mirroring the existing clear-history
    `AlertDialog` pattern) and Rename (inline edit). Both actions should call the
    new API routes and refresh the chat list afterward.
    - Add `data-testid` attributes consistent with the existing test conventions
    found in `tests/sidebar.spec.ts`.
    

    Phase 3: Document Attachments & RAG Pipeline

    This phase turns the client-only attachment stub into a real ingestion + retrieval flow: documents are uploaded to Supabase Storage, parsed and chunked, embedded, stored in pgvector, and retrieved at query time via a new agent tool. It relies on the Phase 1 Supabase clients for Storage/RLS.

    Task 1: Provision Supabase Storage and document schema

    Create the persistence layer for attachments and their embeddings.

    • Add a private Supabase Storage bucket for chat attachments with RLS policies keyed to is_clerk_user()/owner.
    • Add documents (metadata: owner, chatId, storage path, mime, status) and document_chunks (chunkText, embedding vector(1536), FK to documents) tables to lib/db/schema.ts, reusing the existing pgvector customType; generate a new Drizzle migration.
    • Add a document-scoped vector retrieval RPC (mirroring the existing hybrid_search() pattern) or a Drizzle cosine-distance query helper.
    Task 2: Implement upload + ingestion flow

    Persist and process uploaded documents.

    • In components/chat-panel.tsx, when a non-image document is selected, upload it to Storage via the Phase 1 browser client and append a storage reference to FormData (instead of sending raw bytes for documents; keep the existing inline-image path).
    • In app/actions.tsx submit(), branch on MIME type in the existing file-handling block: for documents, create a documents row, extract text, chunk it, generate embeddings via @ai-sdk/openai, and persist chunks — keeping the image path unchanged.
    • Optionally offload ingestion to a new app/api/documents/route.ts for larger files, following the existing route conventions.
    Task 3: Add a RAG retrieval tool to the agent

    Feed retrieved document context into responses.

    • Add lib/schema/document-retrieve.tsx (Zod params: query + optional documentId) following the retrieve/search schema pattern.
    • Add lib/agents/tools/document-retrieve.tsx that embeds the query, runs the vector search, streams a UI card, and returns matched chunks; register it in lib/agents/tools/index.tsx (env-gated like search).
    • Add a rendering branch in app/actions.tsx getUIStateFromAIState and a directive in the researcher system prompt (lib/agents/researcher.tsx) for when to invoke document retrieval.
    🤖 Prompt for AI agents
    Implement the document attachment and RAG pipeline, turning the current
    client-only attachment stub into a real ingestion + retrieval flow: upload to
    Supabase Storage, parse/chunk/embed into pgvector, and retrieve via a new agent
    tool. This depends on the Phase 1 Supabase clients for Storage/RLS.
    
    - Provision a private Supabase Storage bucket for chat attachments with RLS
    policies keyed to `is_clerk_user()`/owner.
    - Add `documents` (metadata: owner, chatId, storage path, mime, status) and
    `document_chunks` (`chunkText`, `embedding vector(1536)`, FK to `documents`)
    tables in `lib/db/schema.ts`, reusing the existing pgvector `customType`.
    Generate a new Drizzle migration for these tables.
    - Add a document-scoped vector retrieval RPC mirroring the existing
    `hybrid_search()` pattern, or alternatively a Drizzle cosine-distance query
    helper.
    - In `components/chat-panel.tsx`, when a non-image document is selected, upload
    it to Storage via the Phase 1 browser client and append a storage reference to
    `FormData` instead of sending raw bytes; keep the existing inline-image path
    unchanged.
    - In `app/actions.tsx`'s `submit()`, branch on MIME type within the existing
    file-handling block: for documents, create a `documents` row, extract text,
    chunk it, generate embeddings via `@ai-sdk/openai`, and persist the chunks;
    leave the image handling path unchanged.
    - Optionally, offload ingestion to a new `app/api/documents/route.ts` for larger
    files, following existing route conventions.
    - Add `lib/schema/document-retrieve.tsx` with Zod params (query + optional
    documentId), following the existing `retrieve`/`search` schema pattern.
    - Add `lib/agents/tools/document-retrieve.tsx` that embeds the query, runs the
    vector search, streams a UI card, and returns matched chunks. Register this tool
    in `lib/agents/tools/index.tsx`, env-gated similarly to the `search` tool.
    - Add a rendering branch in `app/actions.tsx`'s `getUIStateFromAIState` for the
    new tool's output, and add a directive in the researcher system prompt
    (`lib/agents/researcher.tsx`) describing when to invoke document retrieval.
    

    Phase 4: Multi-User Real-Time Collaboration

    This phase activates the dormant chat_participants table and layers Supabase Realtime so multiple authorized users see chat updates live. It depends on Phase 1 (Realtime authorizes via the Clerk-bound client) and Phase 2 (shared chat access control).

    Task 1: Implement collaborator access control

    Make chats shareable to specific users.

    • Add participant CRUD (add/remove/list collaborators) in lib/actions/chat.ts + a app/api/chats/[id]/participants/route.ts, writing to the existing chat_participants table.
    • Update chat access checks (getChat/getChatsPage in lib/actions/chat-db.ts) and the corresponding RLS policies so participants — not only owners — can read/append to shared chats.
    • Re-enable and reimplement components/chat-share.tsx (currently early-returns null) as an "invite collaborator / share" dialog driven by the new participant actions.
    Task 2: Add Realtime synchronization to the chat view

    Live-sync messages and participants across users.

    • In components/chat.tsx (and/or the message list), subscribe via the Phase 1 browser client to postgres_changes on messages (and chat_participants) filtered by chatId, appending/refreshing state on insert/update; unsubscribe on unmount.
    • Rely on postgres_changes following existing table RLS so users only receive events for chats they can access (per Supabase Realtime + RLS behavior).
    Task 3: Foreseeable-issue hardening

    Address the risks surfaced by the analysis.

    • Ensure saveChat's upsert does not clobber concurrent collaborators' messages (dedup by message id already exists via onConflictDoUpdate; verify ownership/userId stamping per message rather than per chat).
    • Document/guard the dual-write consistency between the Drizzle app DB and the service-role sync so a user existing in one store but not the other cannot break RLS-authorized Realtime/Storage access.
    🤖 Prompt for AI agents
    Implement multi-user real-time chat collaboration by activating the dormant
    `chat_participants` table and layering Supabase Realtime, dependent on Phase 1's
    Clerk-bound Supabase clients and Phase 2's chat access control.
    
    - Add participant CRUD (add/remove/list collaborators) in `lib/actions/chat.ts`
    and a new `app/api/chats/[id]/participants/route.ts`, writing to the existing
    `chat_participants` table.
    - Update chat access checks in `getChat`/`getChatsPage`
    (`lib/actions/chat-db.ts`) and the corresponding RLS policies so that
    participants, not just owners, can read/append to shared chats.
    - Re-enable and reimplement `components/chat-share.tsx` (which currently
    early-returns `null`) as an "invite collaborator / share" dialog driven by the
    new participant actions.
    - In `components/chat.tsx` (and/or the message list), subscribe via the Phase 1
    browser client to `postgres_changes` events on `messages` (and
    `chat_participants`) filtered by `chatId`; append/refresh local state on
    insert/update events; ensure unsubscription on component unmount.
    - Rely on `postgres_changes` following the existing table RLS policies so that
    users only receive Realtime events for chats they are authorized to access.
    - Harden against foreseeable issues: verify `saveChat`'s upsert does not clobber
    concurrent collaborators' messages (message-id dedup already exists via
    `onConflictDoUpdate`; verify ownership/`userId` stamping is done per message
    rather than per chat).
    - Document and guard against dual-write consistency issues between the Drizzle
    app DB and the service-role sync, ensuring a user existing in one store but not
    the other cannot break RLS-authorized Realtime/Storage access.
    
    Research

    This is a Next.js App Router app using Clerk for auth and a Supabase-hosted Postgres accessed two ways: (1) the app DB via Drizzle + postgres-js (lib/db/index.ts, POSTGRES_URL), where RLS is disabled and access is gated in application code via getClerkUserIdOnServer/resolveClerkUserToDbUser; and (2) a separate Supabase project reached only from app/api/clerk/webhook/route.ts using the service-role key to call a sync_clerk_user RPC.

    The Supabase browser/server clients in lib/supabase/ are inert mock stubs, so no end-user request currently presents a Clerk identity to Supabase (RLS/clerk_id()/is_clerk_user() from migrations 0003/0004 therefore never fire for interactive traffic).

    Chat persistence flows through the RSC submit() action in app/actions.tsx, which invokes researcher() (tool-calling via the ai SDK, tools registered in lib/agents/tools/index.tsx) and saves via onSetAIState → saveChat.

    The schema (lib/db/schema.ts) already defines a pgvector messages.embedding column, a hybrid_search() RPC, and a chat_participants table — all currently unused.

    Chat delete exists in the DB layer (deleteChat) but is unwired; rename/visibility update and per-chat UI affordances do not exist; document attachments are client-only (bundled into FormData, never persisted); and no Realtime code exists.


    🚀 Next Steps

    🤖 All AI agent prompts combined
    Task: 1
    
    Implement the Clerk↔Supabase end-user auth bridge that replaces the currently
    inert mock Supabase clients with real, identity-aware clients, using Clerk's
    native third-party auth integration (not the deprecated JWT-template approach).
    
    - Configure Clerk dashboard + Supabase "Third-Party Auth (Clerk)" settings;
    write a setup note alongside `CLERK_SUPABASE_FIX.md` documenting these steps.
    - Update `.env.local.example` with any needed env vars (Supabase URL/anon key
    already present); confirm no shared JWT secret is needed under native
    integration.
    - Verify `clerk_id()`/`is_clerk_user()` RLS helper functions correctly resolve
    identity from Clerk-issued tokens; only add a new migration if the claim lookup
    needs adjustment for the native token's `sub` claim.
    - Rewrite `lib/supabase/browser-client.ts`: create a `@supabase/supabase-js`
    client with an `accessToken` callback that returns the current Clerk client-side
    session token, so browser Storage/Realtime calls authorize as the signed-in
    user.
    - Rewrite `lib/supabase/client.ts` (server): create a client with an
    `accessToken` callback that returns the Clerk token via `@clerk/nextjs/server`'s
    `auth()`, for server-side RLS-gated reads/writes and signed-URL generation.
    - Keep `app/api/clerk/webhook/route.ts`'s existing service-role client and
    `sync_clerk_user` RPC usage unchanged (this privileged path is separate from the
    new user-bound clients).
    - Align `.env.local.example` so the Drizzle connection variable actually read by
    code (`POSTGRES_URL`) is documented consistently alongside `DATABASE_URL`,
    removing the inconsistency.
    - Confirm `users.clerk_user_id` remains the authoritative linkage so
    Drizzle-based ownership checks and Supabase RLS checks agree on the same user
    mapping.
    ===============================================================================
    
    Task: 2
    
    Complete the chat CRUD surface by exposing the existing delete primitive and
    adding rename/visibility update, then surfacing both in the history UI, building
    directly on existing patterns in `chat-db.ts`, `chat.ts`, and `app/api/chats/*`.
    
    - In `lib/actions/chat-db.ts`, add an `updateChat` function (title/visibility)
    alongside the existing `deleteChat(id, userId)` DB primitive.
    - In `lib/actions/chat.ts`, add server-action wrappers `deleteChat` and
    `updateChat` that resolve the current user, enforce ownership, and call
    `revalidatePath('/')` after mutation.
    - Reuse the existing (currently unwired) `deleteChat(id, userId)` DB function
    rather than duplicating logic.
    - Create `app/api/chats/[id]/route.ts` supporting `DELETE` (single chat) and
    `PATCH` (rename/visibility) HTTP methods, calling the new server actions and
    enforcing Clerk auth the same way as `app/api/chats/all/route.ts`.
    - Extend `components/history-item.tsx` and its container
    `components/sidebar/chat-history-client.tsx` with a per-item menu offering:
    Delete (with a confirmation dialog mirroring the existing clear-history
    `AlertDialog` pattern) and Rename (inline edit). Both actions should call the
    new API routes and refresh the chat list afterward.
    - Add `data-testid` attributes consistent with the existing test conventions
    found in `tests/sidebar.spec.ts`.
    ===============================================================================
    
    Task: 3
    
    Implement the document attachment and RAG pipeline, turning the current
    client-only attachment stub into a real ingestion + retrieval flow: upload to
    Supabase Storage, parse/chunk/embed into pgvector, and retrieve via a new agent
    tool. This depends on the Phase 1 Supabase clients for Storage/RLS.
    
    - Provision a private Supabase Storage bucket for chat attachments with RLS
    policies keyed to `is_clerk_user()`/owner.
    - Add `documents` (metadata: owner, chatId, storage path, mime, status) and
    `document_chunks` (`chunkText`, `embedding vector(1536)`, FK to `documents`)
    tables in `lib/db/schema.ts`, reusing the existing pgvector `customType`.
    Generate a new Drizzle migration for these tables.
    - Add a document-scoped vector retrieval RPC mirroring the existing
    `hybrid_search()` pattern, or alternatively a Drizzle cosine-distance query
    helper.
    - In `components/chat-panel.tsx`, when a non-image document is selected, upload
    it to Storage via the Phase 1 browser client and append a storage reference to
    `FormData` instead of sending raw bytes; keep the existing inline-image path
    unchanged.
    - In `app/actions.tsx`'s `submit()`, branch on MIME type within the existing
    file-handling block: for documents, create a `documents` row, extract text,
    chunk it, generate embeddings via `@ai-sdk/openai`, and persist the chunks;
    leave the image handling path unchanged.
    - Optionally, offload ingestion to a new `app/api/documents/route.ts` for larger
    files, following existing route conventions.
    - Add `lib/schema/document-retrieve.tsx` with Zod params (query + optional
    documentId), following the existing `retrieve`/`search` schema pattern.
    - Add `lib/agents/tools/document-retrieve.tsx` that embeds the query, runs the
    vector search, streams a UI card, and returns matched chunks. Register this tool
    in `lib/agents/tools/index.tsx`, env-gated similarly to the `search` tool.
    - Add a rendering branch in `app/actions.tsx`'s `getUIStateFromAIState` for the
    new tool's output, and add a directive in the researcher system prompt
    (`lib/agents/researcher.tsx`) describing when to invoke document retrieval.
    ===============================================================================
    
    Task: 4
    
    Implement multi-user real-time chat collaboration by activating the dormant
    `chat_participants` table and layering Supabase Realtime, dependent on Phase 1's
    Clerk-bound Supabase clients and Phase 2's chat access control.
    
    - Add participant CRUD (add/remove/list collaborators) in `lib/actions/chat.ts`
    and a new `app/api/chats/[id]/participants/route.ts`, writing to the existing
    `chat_participants` table.
    - Update chat access checks in `getChat`/`getChatsPage`
    (`lib/actions/chat-db.ts`) and the corresponding RLS policies so that
    participants, not just owners, can read/append to shared chats.
    - Re-enable and reimplement `components/chat-share.tsx` (which currently
    early-returns `null`) as an "invite collaborator / share" dialog driven by the
    new participant actions.
    - In `components/chat.tsx` (and/or the message list), subscribe via the Phase 1
    browser client to `postgres_changes` events on `messages` (and
    `chat_participants`) filtered by `chatId`; append/refresh local state on
    insert/update events; ensure unsubscription on component unmount.
    - Rely on `postgres_changes` following the existing table RLS policies so that
    users only receive Realtime events for chats they are authorized to access.
    - Harden against foreseeable issues: verify `saveChat`'s upsert does not clobber
    concurrent collaborators' messages (message-id dedup already exists via
    `onConflictDoUpdate`; verify ownership/`userId` stamping is done per message
    rather than per chat).
    - Document and guard against dual-write consistency issues between the Drizzle
    app DB and the service-role sync, ensuring a user existing in one store but not
    the other cannot break RLS-authorized Realtime/Storage access.
    
    💡 Iterate on the plan with: `@coderabbitai` <feedback>
    Example Feedback
    - `@coderabbitai` You can skip phase 3. Add a simple unit test case for phase 2.
    - `@coderabbitai` For design choice 1 go ahead with option 3 and replan.
    

    💬 Have feedback or questions? Drop into our discord!

  5. ngoiyaeric commented on Jul 10, 2026

    @ngoiyaeric
    CollaboratorAuthor

    @coderabbitai what's the point of the first task

  6. coderabbitai commented on Jul 10, 2026

    @coderabbitai
    Contributor

    @ngoiyaeric Task 1 is the configuration/setup prerequisite that Task 2 depends on — it's about wiring Supabase and Clerk together at the platform level, not writing code.

    Concretely, it covers:

    1. Supabase dashboard config: Enabling Clerk as a recognized "Third-Party Auth" provider in Supabase, so Supabase's Data API, Storage, and Realtime layers know how to verify a Clerk-issued session token (validate its signature/issuer) instead of a Supabase-minted JWT.
    2. Clerk dashboard config: Setting up Clerk's native Supabase integration so it issues tokens in the shape Supabase expects.
    3. Env vars: Confirming .env.local.example has whatever's needed (Supabase URL/anon key) and that no shared JWT secret is required — that's a leftover artifact of the older, now-deprecated "JWT template" approach.
    4. Verifying the RLS bridge: Checking that the existing clerk_id()/is_clerk_user() Postgres functions (from migrations 0003/0004) correctly extract the user identity from the sub claim of the native Clerk token format — this could differ subtly from the old JWT-template claims, which is why a migration might be needed if the claim shape changed.

    Without this configuration step done first, Task 2's code change (adding an accessToken callback to the Supabase clients that returns a Clerk session token) would be pointless — Supabase would reject or ignore tokens from a provider it hasn't been told to trust, and RLS policies relying on clerk_id() would never resolve correctly. So Task 1 is the platform-level handshake, Task 2 is the client-side code that leverages it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions