feat(frontend): optional Google sign-in + my-reels library + delete-all - #37
Merged
Conversation
added 3 commits
July 28, 2026 18:33
Adds an optional multitenancy frontend on top of the merged backend (Authorization: Bearer <Firebase ID token> on every reel route, GET /me/library, DELETE /me/data). Gated entirely on the four VITE_FIREBASE_* build-time vars being present: with none set (every build today) the app renders no sign-in control and no library/delete UI, and lib/auth.ts's firebase/app + firebase/auth imports stay dynamic so guest builds never fetch that chunk. lib/api.ts and lib/provenance-verify.ts attach a fresh Authorization header to the reel/verify calls only when a token exists, preserving byte-identical guest requests.
tsc resolved ReturnType<typeof window.setTimeout> to Node's Timeout (@types/node is in scope) while the DOM window.setTimeout call returns a number, so the assignment failed typecheck (TS2322) and took the frontend, e2e and lighthouse jobs down with it. All three references are DOM window.* APIs, so number is the correct type.
Five new-in-this-PR tests asserted behaviour the implementation never had:
- Four api.auth cases inspected the fetch() call but expected no headers at
all. request() always merges its own { Accept } default before calling
fetch, so init.headers is never undefined there. What proves the guest path
is byte-identical is that the caller adds NO headers key, leaving exactly
that default and no Authorization -- which is what they now assert.
- The onAuthChange case demanded that unsubscribing before the lazy import
settles still dispose a subscription. The implementation is stronger: it
is cancelled first, so it never subscribes at all. The test now asserts
that invariant (nothing subscribed, nothing leaked, callback never fires).
No source behaviour changed.
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.
Summary
Adds the multitenancy frontend on top of the already-merged backend (#36): every reel API call optionally carries
Authorization: Bearer <Firebase ID token>, plus a "My reels" library and a "delete all my data" control.lib/auth.ts::isAuthEnabled()checks all fourVITE_FIREBASE_*vars. With none set (every build today, including CI) the header renders no sign-in control and no library/delete UI at all — guest is byte-identical to before this PR.firebase/app+firebase/authare reached only via dynamicimport()insidelib/auth.ts, so a guest build never fetches that chunk.firebaseis a new prod dependency (^12.16.0);npm audit --omit=dev --audit-level=highis clean.getIdToken()never caches the raw JWT —lib/api.ts'swithAuth()helper and the equivalent inlib/provenance-verify.tscall it on every submit/poll/get/verify call, addingAuthorizationonly when a token exists and leaving the request byte-identical (down to the exactheadersobject) when it doesn't.GET /me/library): list with name, created date, manifest-hash chip, and a per-row "View provenance" disclosure that fetches the manifest on demand. Empty state, and an honest degrade note on 401 ("Sign-in library is not available on this deployment") instead of an error screen.DELETE /me/data): behind a hand-rolled accessibleConfirmDialog(focus trap, Escape, focus-return,role="dialog"), disabled while in flight, shows an inline error on failure without closing, and refreshes the (now empty) library on success.VITE_FIREBASE_*vars documented infrontend/.env.exampleandfrontend/README.md(public values, guest-only without them, backend also needsFIREBASE_PROJECT_ID).Nothing outside
frontend/changed — no backend files,lib/progress.ts, or deploy scripts touched.Test plan
test:coverage(vitest, thresholds lines/statements/functions ≥90, branches ≥85), build, Playwright e2e, Lighthouse a11y ≥0.95, npm audit (frontend prod deps), CodeQL*.auth.test.ts(x),AuthMenu.test.tsx,ConfirmDialog.test.tsx,MyReels.test.tsx,App.library.test.tsx)git diff --statagainstmainshows zero changes undersrc/cinemory/**,frontend/src/lib/progress.ts,deploy/**, or any existing*.test.*fileAuthorization, guest requests don't (down to the literal headers object); My-reels list + empty state + 401 graceful-degrade + provenance expand/collapse; delete-all confirm →DELETE /me/data→ refetch