Skip to content

realtime: Supabase Realtime for KDS + order tracking - #12

Merged
snackman merged 1 commit into
mainfrom
realtime
Jun 6, 2026
Merged

realtime: Supabase Realtime for KDS + order tracking#12
snackman merged 1 commit into
mainfrom
realtime

Conversation

@snackman

@snackman snackman commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

@

Summary

Upgrades the realtime layer from interval polling to Supabase Realtime, behind the existing getRealtimeProvider() seam. The KDS board and the customer order tracker now receive instant websocket pushes in production, with zero component changes and the zero-env build/preview/test invariant fully intact.

  • src/lib/realtime/supabase.ts — replaces the documented stub with a real createSupabaseRealtimeProvider(). Subscribes to postgres_changes (event: "*") on public.orders, scoped by a server-side filter parsed from the topic the hooks already pass: kds:<tenant>:<location>location_id=eq.<location>, track:<orderId>id=eq.<orderId>. Uses the @supabase/ssr browser client (getBrowserSupabase()), so the socket runs as the signed-in user and Realtime enforces the same RLS SELECT policies as PostgREST.
    • Initial load via the same fetcher the poller used; live deltas re-run it (debounced 150ms, concurrent runs de-duped) so the server-computed payload (KDS elapsed/age/station, tracker live delivery state) stays the single source of truth.
    • Reconnect: re-fetches on each SUBSCRIBED transition to reconcile anything missed offline.
    • Cleanup: idempotent unsubscribe — cancels the pending debounce, removes the channel, ignores late results.
    • Graceful fallback: delegates to the poller for any subscription it can't serve (no browser client on SSR/RSC, or an unrecognised topic).
  • src/lib/realtime/index.tsgetRealtimeProvider() selects Supabase Realtime when NEXT_PUBLIC_SUPABASE_URL + NEXT_PUBLIC_SUPABASE_ANON_KEY are present, else the poller. Read lazily at call time (memoized); nothing touches env at module load — mirrors the getPosDriver() / readDbConfig() guard.
  • src/lib/realtime/realtime.test.ts — new env-free Vitest covering selection (poller with no env, Supabase with env, memoized) and the provider's per-subscription initial-fetch fallback for both topic shapes.
  • supabase/README.md — new "Enabling Supabase Realtime" section with the exact publication SQL + verification queries.
  • plans/focaccia-51842-realtime.md — design + plan.

Scope

Only the realtime layer + its doc/test. No new deps (@supabase/supabase-js and @supabase/ssr already present). No changes to auth/middleware, payments, next.config, or package.json. Components, money, and RLS correctness untouched.

Verified locally (no env vars)

npm install && npm run build && npm run typecheck && npm run lint && npm run test:run — all green (127 tests).

Orchestrator: enable Realtime on the LIVE DB

Run once on the live Supabase project (RLS is unchanged — Realtime reuses the existing orders SELECT policies):

-- Stream the full row on UPDATE/DELETE so the row filter matches the OLD row.
alter table public.orders replica identity full;

-- Add orders to the realtime publication (create it if the project lacks it).
do $$
begin
  if not exists (select 1 from pg_publication where pubname = 'supabase_realtime') then
    create publication supabase_realtime;
  end if;
end$$;

alter publication supabase_realtime add table public.orders;

Verify:

select schemaname, tablename from pg_publication_tables where pubname = 'supabase_realtime';
select relreplident from pg_class where oid = 'public.orders'::regclass;  -- expect 'f' (FULL)

Not required for the build/deploy — it only makes updates instant once env vars are set.

🤖 Generated with Claude Code
@

Replace the documented stub in src/lib/realtime/supabase.ts with a real
createSupabaseRealtimeProvider(): subscribes to postgres_changes on
public.orders (scoped by location_id for KDS, id for the customer tracker)
via the @supabase/ssr browser client, so the websocket runs as the signed-in
user and Realtime enforces the same RLS SELECT policies as PostgREST.

Same RealtimeProvider.subscribe() contract as the poller, so use-kitchen-board
and use-order-tracking are unchanged. Initial load uses the same fetcher; live
deltas re-run it (debounced, de-duped) to keep the server-computed payload as
the single source of truth; reconnect re-fetches; teardown is idempotent.

getRealtimeProvider() selects Supabase Realtime when the public Supabase env
is present and falls back to polling otherwise (zero-env build/preview/tests
stay green), mirroring the getPosDriver() guard. No new deps; components,
auth/middleware, payments, and next.config untouched.

Docs: supabase/README.md gains the exact publication SQL (REPLICA IDENTITY
FULL + add orders to supabase_realtime) for the orchestrator. Plan saved at
plans/focaccia-51842-realtime.md. New env-free Vitest covers selection +
fallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pos Ready Ready Preview, Comment Jun 6, 2026 6:48am

Request Review

@snackman
snackman marked this pull request as ready for review June 6, 2026 06:51
@snackman
snackman merged commit 76c8af2 into main Jun 6, 2026
5 checks passed
@snackman
snackman deleted the realtime branch June 6, 2026 06:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant