Skip to content

feat(frontend): optional Google sign-in + my-reels library + delete-all - #37

Merged
upgradedev merged 3 commits into
mainfrom
feat/multitenancy-frontend
Jul 28, 2026
Merged

feat(frontend): optional Google sign-in + my-reels library + delete-all#37
upgradedev merged 3 commits into
mainfrom
feat/multitenancy-frontend

Conversation

@upgradedev

Copy link
Copy Markdown
Owner

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.

  • Gated on build-time config: lib/auth.ts::isAuthEnabled() checks all four VITE_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.
  • Lazy Firebase: firebase/app + firebase/auth are reached only via dynamic import() inside lib/auth.ts, so a guest build never fetches that chunk. firebase is a new prod dependency (^12.16.0); npm audit --omit=dev --audit-level=high is clean.
  • Fresh token per request: getIdToken() never caches the raw JWT — lib/api.ts's withAuth() helper and the equivalent in lib/provenance-verify.ts call it on every submit/poll/get/verify call, adding Authorization only when a token exists and leaving the request byte-identical (down to the exact headers object) when it doesn't.
  • My reels (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 all my data (DELETE /me/data): behind a hand-rolled accessible ConfirmDialog (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.
  • Docs: the four VITE_FIREBASE_* vars documented in frontend/.env.example and frontend/README.md (public values, guest-only without them, backend also needs FIREBASE_PROJECT_ID).

Nothing outside frontend/ changed — no backend files, lib/progress.ts, or deploy scripts touched.

Test plan

  • CI green: typecheck, test:coverage (vitest, thresholds lines/statements/functions ≥90, branches ≥85), build, Playwright e2e, Lighthouse a11y ≥0.95, npm audit (frontend prod deps), CodeQL
  • Every pre-existing test file is untouched — all new coverage lives in new files (*.auth.test.ts(x), AuthMenu.test.tsx, ConfirmDialog.test.tsx, MyReels.test.tsx, App.library.test.tsx)
  • git diff --stat against main shows zero changes under src/cinemory/**, frontend/src/lib/progress.ts, deploy/**, or any existing *.test.* file
  • New tests cover: auth-enabled shows the sign-in control / auth-disabled renders nothing; signed-in requests carry Authorization, 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

Efthimios Fousekis 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.
@upgradedev
upgradedev merged commit e78b2e7 into main Jul 28, 2026
12 checks passed
@upgradedev
upgradedev deleted the feat/multitenancy-frontend branch July 28, 2026 15:45
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