Skip to content

feat(marquee): Phase 4 realtime + files on both backends - #54

Merged
2bTwist merged 3 commits into
mainfrom
app/marquee-phase4
Jun 20, 2026
Merged

2bTwist merged 3 commits into
mainfrom
app/marquee-phase4

Conversation

@2bTwist

@2bTwist 2bTwist commented Jun 20, 2026

Copy link
Copy Markdown
Owner

Phase 4 of the Marquee dogfood: realtime + files, proven on both live backends. Stacked on #53 (base app/marquee-phase3).

Realtime

The review feed and aggregate rating are live via the portable store.subscribe() seam. A new movieReviews named query joins movieRating as a reactive read; a useLiveQuery hook binds both to React. Convex is natively reactive; Supabase opts in through a per-query realtime watch on the reviews table (migration 0004 adds it to the supabase_realtime publication and sets replica identity full so deletes fire to anon subscribers under RLS). The detail page declares the capability with a live badge.

Files

Poster upload and render ride the portable file port. The same upload / getUrl / download / remove calls work over Supabase Storage (a bucket::path handle) and Convex storage (a storage id). MovieForm uploads on file select and persists the opaque handle as movies.posterFile; MovieDetail resolves it to a URL. The posters bucket is created by the seed; migration 0004 adds the column and Storage RLS (public read, authenticated write).

SDK fix found by the dogfood

The browser smoke surfaced a real portability bug: the Supabase adapter used crypto.randomUUID() for the Realtime channel name and the default upload path, but that API exists only in secure contexts (https or localhost). On a plain-http LAN-IP origin it is undefined, so opening a live subscription threw and unmounted the React tree. Fixed with a fallback id helper plus a red-first unit test. Ships with a changeset.

Gates (both live backends)

  • test/realtime.live.test.ts: a review written by one client is delivered live to a separate subscriber.
  • test/files.live.test.ts: upload roundtrips with identical bytes; a removed handle stops resolving.
  • Full marquee suite 48/48, plus a two-tab browser smoke (a delete in one tab updates the other with no reload) and poster render, on both backends.

2bTwist added 3 commits June 20, 2026 15:42
Realtime: the review feed + aggregate rating are now LIVE via the portable
store.subscribe() seam. A new movieReviews named query joins movieRating as a
reactive read; a useLiveQuery hook binds both to React. Convex is natively
reactive; Supabase opts in through a realtime watch on the reviews table
(migration 0004 adds it to the supabase_realtime publication + replica identity
full). The detail page declares the capability with a live badge.

Files: poster upload/render rides the portable file port. The same upload /
getUrl / download / remove calls work over Supabase Storage (bucket::path) and
Convex storage (storage id). MovieForm uploads on file-select and persists the
opaque handle as movies.posterFile; MovieDetail resolves it to a URL. The
posters bucket is created by the seed (idempotent); migration 0004 adds the
posterFile column and Storage policies (public read, authenticated write).

Gates (both live backends, serial): test/realtime.live.test.ts proves a review
written by one client is delivered live to a separate subscriber;
test/files.live.test.ts proves the upload roundtrip with identical bytes and
that a removed handle stops resolving. Full marquee suite 48/48.
…bsent

subscribe() (Realtime channel name) and files.upload() (default Storage path)
called crypto.randomUUID() directly. That API exists ONLY in secure contexts
(https, or http on localhost), so on a plain-http origin — a LAN-IP dev server
or any non-https deployment — it is undefined and the call threw
"TypeError: crypto.randomUUID is not a function", unmounting the React tree the
moment a live subscription opened. Both now route through a uniqueId() helper
that prefers crypto.randomUUID() and falls back to a time + counter +
Math.random id; neither use needs cryptographic strength. A unit test stubs the
API away and asserts subscribe() still opens a channel. Found by the Marquee
dogfood's realtime UI smoke over a LAN IP.
The reviews "● live" badge was hard-coded to the Convex accent, so it read
orange even on Supabase. Expose the active backend's accent as a --backend-accent
CSS var on the app wrapper and color the badge from it, so it is green on
Supabase, orange on Convex, blue on memory, and re-colors instantly on switch.
The var is now available app-wide for any other per-backend tell.
@2bTwist
2bTwist changed the base branch from app/marquee-phase3 to main June 20, 2026 21:36
@2bTwist
2bTwist merged commit c8f4668 into main Jun 20, 2026
11 checks passed
@2bTwist
2bTwist deleted the app/marquee-phase4 branch June 20, 2026 21:42
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