feat(marquee): Phase 4 realtime + files on both backends - #54
Merged
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 newmovieReviewsnamed query joinsmovieRatingas a reactive read; auseLiveQueryhook binds both to React. Convex is natively reactive; Supabase opts in through a per-queryrealtimewatch on thereviewstable (migration 0004 adds it to thesupabase_realtimepublication 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/removecalls work over Supabase Storage (abucket::pathhandle) and Convex storage (a storage id).MovieFormuploads on file select and persists the opaque handle asmovies.posterFile;MovieDetailresolves it to a URL. Thepostersbucket 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.