Describe the bug
The collaboration rooms feature uses Supabase Realtime to display new messages without polling, but the subscription silently receives zero messages for all users. The room loads initial messages correctly (server-side rendered), but any new message sent by any participant never appears in real-time.
Root cause
`MessageFeed.tsx` creates a Supabase client using only the anon key, with no authentication session attached:
```ts
// src/components/rooms/MessageFeed.tsx
const realtimeClient = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! // no auth session set
);
```
The RLS policy on `room_messages` checks membership via a JWT claim:
```sql
-- supabase/schema.sql
CREATE POLICY "message_select" ON room_messages
FOR SELECT USING (
EXISTS (
SELECT 1 FROM room_members
WHERE room_id = room_messages.room_id
AND github_username = current_setting('request.jwt.claims', true)::json->>'login'
)
);
```
Because DevTrack uses NextAuth (not Supabase Auth), users have a Next.js session cookie but no Supabase JWT. An anon Supabase connection has no `request.jwt.claims`, so `login` resolves to `null`, the membership check fails, and Supabase broadcasts zero messages to the client.
Second compounding issue
`RoomClient.tsx` maintains a `messages` state and updates it in `handleSent` when the current user sends a message. However, `MessageFeed` is passed `initialMessages` (the static SSR prop), not `RoomClient.messages`. The two states are completely disconnected, so the optimistic update in `handleSent` has no visible effect.
```tsx
// RoomClient.tsx — messages state is updated but never passed to MessageFeed
function handleSent(msg: RoomMessage) {
setMessages((prev) => [...prev, msg]); // updates state that MessageFeed doesn't read
}
// ...
// ^^^^^^^^^^^^^^
// static prop, not the state above
```
Files affected
- `src/components/rooms/MessageFeed.tsx` — unauthenticated Realtime client
- `src/app/rooms/[roomId]/RoomClient.tsx` — disconnected messages state
- `supabase/schema.sql` (lines 278–293) — RLS policies requiring JWT claims
Steps to reproduce
- Create a collaboration room and invite a second GitHub account.
- Open the room in two separate browser sessions (one per account).
- Send a message from one session.
- Observe: the message never appears in the other session's feed without a page refresh.
Expected behaviour
New messages sent by any room member should appear in all other members' feeds in real-time without requiring a page refresh.
Suggested fix
Option A (recommended) — Generate a short-lived Supabase custom JWT server-side (signed with `SUPABASE_JWT_SECRET`) containing the user's GitHub login, pass it to `MessageFeed` as a prop, and call `realtimeClient.realtime.setAuth(jwt)` before subscribing.
Option B — Replace the Realtime subscription with a polling call to `/api/rooms/[roomId]/messages?after=` on a short interval (e.g. 5 seconds) as a stopgap until a proper auth solution is in place.
Additionally, wire `RoomClient.messages` state into `MessageFeed` so that the current user's optimistic send is immediately reflected without waiting for a Realtime event.
Describe the bug
The collaboration rooms feature uses Supabase Realtime to display new messages without polling, but the subscription silently receives zero messages for all users. The room loads initial messages correctly (server-side rendered), but any new message sent by any participant never appears in real-time.
Root cause
`MessageFeed.tsx` creates a Supabase client using only the anon key, with no authentication session attached:
```ts
// src/components/rooms/MessageFeed.tsx
const realtimeClient = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! // no auth session set
);
```
The RLS policy on `room_messages` checks membership via a JWT claim:
```sql
-- supabase/schema.sql
CREATE POLICY "message_select" ON room_messages
FOR SELECT USING (
EXISTS (
SELECT 1 FROM room_members
WHERE room_id = room_messages.room_id
AND github_username = current_setting('request.jwt.claims', true)::json->>'login'
)
);
```
Because DevTrack uses NextAuth (not Supabase Auth), users have a Next.js session cookie but no Supabase JWT. An anon Supabase connection has no `request.jwt.claims`, so `login` resolves to `null`, the membership check fails, and Supabase broadcasts zero messages to the client.
Second compounding issue
`RoomClient.tsx` maintains a `messages` state and updates it in `handleSent` when the current user sends a message. However, `MessageFeed` is passed `initialMessages` (the static SSR prop), not `RoomClient.messages`. The two states are completely disconnected, so the optimistic update in `handleSent` has no visible effect.
```tsx
// RoomClient.tsx — messages state is updated but never passed to MessageFeed
function handleSent(msg: RoomMessage) {
setMessages((prev) => [...prev, msg]); // updates state that MessageFeed doesn't read
}
// ...
// ^^^^^^^^^^^^^^
// static prop, not the state above
```
Files affected
Steps to reproduce
Expected behaviour
New messages sent by any room member should appear in all other members' feeds in real-time without requiring a page refresh.
Suggested fix
Option A (recommended) — Generate a short-lived Supabase custom JWT server-side (signed with `SUPABASE_JWT_SECRET`) containing the user's GitHub login, pass it to `MessageFeed` as a prop, and call `realtimeClient.realtime.setAuth(jwt)` before subscribing.
Option B — Replace the Realtime subscription with a polling call to `/api/rooms/[roomId]/messages?after=` on a short interval (e.g. 5 seconds) as a stopgap until a proper auth solution is in place.
Additionally, wire `RoomClient.messages` state into `MessageFeed` so that the current user's optimistic send is immediately reflected without waiting for a Realtime event.