Skip to content

feat(staging): admin staging switcher and staging sync procedure - #81

Merged
Programcode merged 10 commits into
mainfrom
t3code/make-staging-usable
Oct 1, 2026
Merged

Programcode merged 10 commits into
mainfrom
t3code/make-staging-usable

Conversation

@Programcode

@Programcode Programcode commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Plan item 8 from plans/2026-10-01-next-15.md: make staging usable.

What changes

  • Admin switcher in Settings. Admins get a Developer card that asks for confirmation, then switches projects and reloads. It reuses the settings.developer* and settings.envSwitch* strings that were already translated in en, hu and es.
  • Staging strip on every page. While staging is active, a sticky strip at the top says "Staging backend" and has a Switch to Production button. A new staging session starts signed out, Settings needs sign-in, and an admin on production is not necessarily an admin on staging. Without the strip, switching could leave someone stuck on staging. For the same reason, the Settings card shows to everyone while staging is active. The strip publishes its height as --staging-banner-height, and the landing navbar and app header sit below it.
  • Clean switches. The app's persisted stores are shared by both projects, so AuthProvider.leaveEnvironment waits at most 4 seconds for sign-out. Then it always drops this project's stored session and Google token and resets every user-specific store, including form drafts. An unresponsive project can't block the switch or leak data into the other project.
  • Other tabs follow. Every tab watches the debate-supabase-env key. When another tab switches, it clears only its own sessionStorage (Google token, sparring state) and reloads, so no tab keeps talking to the old project while sharing stores with the new one.
  • Staging sync procedure in docs/operations.md. It has five steps: confirm the ref, list what is pending, apply migrations, deploy stale functions, then verify. Steps 3 and 4 need Adam's authorization each time. The status step runs npm run backend:status -- --project-ref <staging-ref> --check from feat(ops): add a read-only backend drift check #79. Until feat(ops): add a read-only backend drift check #79 lands, the step also lists the two read-only CLI queries the script wraps.

The switch is a convenience. Staging RLS and its separate accounts are the access boundary.

What I found about "enable the staging workflow"

The workflow is already enabled. STAGING_E2E_ENABLED has been true since 2026-09-18, and the staging environment has all three secrets. The job passed 10 times, most recently on 2026-09-23. Every run since then was skipped because the Quality run it waits for fails in 4 to 5 seconds with zero steps, which is the Actions-minutes pattern. Once Actions minutes are back, the staging job will run again. The workflow file doesn't need to change. docs/operations.md now records this.

Staging's backend, checked read-only today against brifeyypjauyusmkptkx:

  • Migrations: all 38 repo versions are recorded as applied. migration list compares version numbers only. fix(db): define the legacy purge without running it #78 rewrote 20260926090100_legacy_debate_cleanup.sql after staging had recorded it, so staging most likely ran the old body, which purged legacy rows. That only affects staging test data, but staging's function definitions match the new file while its data reflects the old one.
  • Functions: 11 of 12 deployed functions were redeployed on 2026-09-30 at 13:59 UTC, after the last change to them (2026-09-28). stripe-webhook on staging is stale. It was last deployed 2026-05-20, before f7c9fd7 (2026-07-13, payment hardening) and 65cc821 (2026-08-21, the entitlement upsert fix). Production's copy is current (deployed 2026-08-21 11:04 UTC). Redeploying it to staging is step 4 of the new procedure and needs Adam's authorization. workspace-invites isn't deployed because Workspace is hidden.

I changed nothing on staging.

What actually unblocks the staging job

  • A successful Quality push run on main. Both triggers need one, including manual workflow_dispatch. Actions was out of minutes from 2026-09-24 until today and is running again, so the next merge to main should start the staging job.
  • Or run the suite locally. That needs STAGING_DEMO_CODE, which exists only as a GitHub environment secret, so Adam would have to supply it out of band. It also writes and deletes one test source on staging, which needs Adam's go-ahead.

Note for plan item 4 (CSP)

The switcher only works while vercel.json connect-src allows the staging project origin. Today the *.supabase.co wildcard covers it. If item 4 narrows that wildcard, it has to list both project origins. docs/operations.md now says so. From the deployed frontend, only demo-code sign-in is known to work on staging. Google and email sign-in depend on staging's Auth redirect URLs, which I didn't check.

Verification

All at 60c3f65, run locally:

  • Unit tests: 110 files, 1119 tests pass. New coverage is in EnvironmentSwitcher.test.tsx (switcher, strip, cross-tab reload), AuthProvider.behavior.test.tsx (leaveEnvironment with a hung sign-out, discardTabState leaving shared stores alone) and SettingsAdminNavigation.test.tsx (who sees the card). Each failed before its fix.
  • e2e/staging-switch.spec.ts, desktop and mobile Chromium: the strip returns to production from / and /login, the landing navbar sits below the strip, and switching in one tab reloads another. The landing test and the two-tab test each fail without their fix.
  • Full desktop and mobile Chromium E2E: 232 passed, 6 skipped, none flaky.
  • Lint, typecheck, docs:check and build pass.
  • Not run: the protected staging suite, which writes test data to staging and needs Adam's authorization, and the database gate, since no SQL changed.

Review

Codex (gpt-6.1-sol, high) reviewed every push. Six rounds of findings, all checked against the code and fixed, each with a test:

  1. A stalled sign-out could leave the switch pending forever.
  2. A timed-out sign-out let one project's cached results reload into the other.
  3. Cached Google tokens and form drafts survived the switch, and the landing navbar covered the strip.
  4. Other open tabs kept the old project while merging the new project's stores.
  5. Those tabs kept their own Google token, and on mobile the strip covered the menu button.
  6. A late follower tab could wipe the new project's shared state.

The seventh pass, at 60c3f65, found nothing actionable.

CodeRabbit was rate-limited on every request because the open PRs share its hourly allowance. One reply said "Review finished" but posted no review and no comments. Copilot is out of quota.

CI

GitHub Actions is running again. live-e2e fails here exactly as it does on #79, #80 and main (5 of 8 live journeys), but not because of billing or this PR. From 2026-09-27 to 2026-09-30 every live-e2e run died in 2 to 5 seconds without a runner, so the live suite never ran against Current Debate (#72). Today's runs are the first real ones since. The live specs don't set up a current debate:

  • Crossfire, Attack/Brainstorm and case generation stop at the "Which debate is this for?" dialog, so the request the test waits for is never sent.
  • The Library journey can't click "Add Source", which stays disabled without a current debate.
  • The sparring journey never reaches its draft box.

The Edge "wall clock" terminations in the log come from the browser closing when the tests time out, not from slow AI calls. Fixing the live specs, for example with the seedCurrentDebate helper the other E2E suites use, belongs in its own PR. Earlier, browser-tests failed once on 3 timeouts and passed on rerun with no change.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Admins can switch between production and staging from Settings. The switch requires confirmation and clears environment-specific session and user data before reloading.
    • A visible staging banner identifies when staging is active and provides a way to return to production. Open tabs update when the environment changes.
  • Documentation
    • Added guidance for switching frontend environments and synchronizing staging, including authorization and verification steps.
    • Clarified that staging end-to-end tests have been skipped due to a failing prerequisite check.

Programcode and others added 2 commits October 1, 2026 08:51
Admins get a Developer card in Settings that signs out, sets the
debate-supabase-env key that src/lib/supabase/env.ts already reads, and
reloads. While staging is active, every page shows a strip with Switch to
Production, and the Settings card shows to everyone. A new staging session
starts signed out and need not have the admin role, so the way back cannot
depend on either. If sign-out fails the switch still happens; each project
keeps its own auth storage key.

docs/operations.md gains the staging sync procedure, built on
npm run backend:status --project-ref from plan item 6. It also records
why the staging job is skipped: STAGING_E2E_ENABLED is already true, and
the Quality run it waits for fails before any runner starts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ging

The switcher needs connect-src to keep allowing the staging project, which
today rides on the *.supabase.co wildcard that plan item 4 wants to narrow.
Only demo-code sign-in is known to work on staging from the deployed
frontend; Google and email depend on staging's Auth redirect URLs.

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

vercel Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
debate-navigator Ready Ready Preview Oct 1, 2026 5:32pm UTC

Copilot AI balanced review requested due to automatic review settings October 1, 2026 09:08
@Programcode

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Warning

Review limit reached

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

Next included review available in 39 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Free

Run ID: 985cafcc-f94d-472c-8c80-66011662220e

📥 Commits

Reviewing files that changed from the base of the PR and between d6c4357 and e5aa404.

📒 Files selected for processing (1)
  • docs/operations.md
ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Free

Run ID: 39fafd29-b9e3-4563-8cb5-2c3b2e8c67d6

📥 Commits

Reviewing files that changed from the base of the PR and between f1a33c6 and d6c4357.

📒 Files selected for processing (5)
  • src/components/StagingBanner.tsx
  • src/lib/supabase/AuthProvider.tsx
  • src/lib/supabase/switch-env.ts
  • src/test/auth/AuthProvider.behavior.test.tsx
  • src/test/components/EnvironmentSwitcher.test.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/supabase/switch-env.ts

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The frontend now supports switching between staging and production. Switching clears local state and reloads the page; other tabs respond to environment changes. The changes also add staging-operation documentation and tests for switching behavior.

Changes

Frontend environment switching

Layer / File(s) Summary
Environment storage and local cleanup
src/lib/supabase/env.ts, src/lib/supabase/client.ts, src/lib/supabase/AuthProvider.tsx, src/test/auth/AuthProvider.behavior.test.tsx
Environment storage keys are exported. Auth cleanup removes the current project’s credentials and resets local user state. Tests cover cleanup when sign-out times out.
Switch orchestration and Settings control
src/lib/supabase/switch-env.ts, src/components/features/settings/EnvironmentSwitcher.tsx, src/pages/SettingsPage.tsx, src/test/components/EnvironmentSwitcher.test.tsx, src/test/pages/SettingsAdminNavigation.test.tsx
The Settings control confirms environment switches and invokes cleanup, environment update, and reload. Tests cover its visibility, confirmation, cancellation, and sign-out failure behavior.
Staging banner and cross-tab integration
src/components/StagingBanner.tsx, src/App.tsx, src/components/landing/Navbar.tsx, src/components/layout/AppLayout.tsx, src/locales/*.json, src/test/components/EnvironmentSwitcher.test.tsx, e2e/staging-switch.spec.ts, docs/architecture.md, docs/operations.md
The app displays a staging banner and offsets navigation while it is active. Tests cover banner visibility, switching, and cross-tab updates. Documentation describes frontend environment selection and switching.

Staging operations documentation

Layer / File(s) Summary
Staging status and synchronization procedure
docs/operations.md
The guide reports the staging E2E job status and describes how to compare and apply staging migrations and functions, then verify the changes before running the browser suite.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SettingsPage
  participant EnvironmentSwitcher
  participant AuthProvider
  participant switchEnv
  participant EnvironmentStorage
  participant OtherTab
  SettingsPage->>EnvironmentSwitcher: Render switch control
  EnvironmentSwitcher->>switchEnv: Request environment switch
  switchEnv->>AuthProvider: Leave current environment
  switchEnv->>EnvironmentStorage: Store target environment
  switchEnv->>EnvironmentSwitcher: Reload current page
  EnvironmentStorage->>OtherTab: Emit storage change
  OtherTab->>AuthProvider: Discard tab state
  OtherTab->>OtherTab: Reload page
Loading

Architecture Summary

Architecture risk: 🔵 Low · up to d6c43

The change affects 3 systems.

Changed systems: src, docs, e2e

Architecture concerns
No architecture-level concerns identified.

Review details

Systems and components

  • observed — src (service) was modified; 16 changed files map to changed impact.
  • observed — docs (service) was modified; 2 changed files map to changed impact.
  • observed — e2e (service) was modified; 1 changed file maps to changed impact.

Before / after behavior

  • observed — Modified behavior in docs/architecture.md: The environment-configuration description now links to the operations guide for switching projects from Settings.
  • observed — Modified behavior in docs/operations.md: Adds the reported staging-job status and states that the job resumes when Actions minutes are restored, without workflow changes.
  • observed — Modified behavior in docs/operations.md: Adds the staging sync procedure: verify the staging project, identify pending migrations and stale functions against the intended commit, apply only those changes, and verify before running the staging browser suite. Steps 1–2 are read-only; steps 3–4 require Adam’s authorization. Production changes then follow the separate Supabase procedure.
  • observed — Modified behavior in docs/operations.md: Adds staging frontend-switch behavior: staging in the startup localStorage key selects staging, otherwise production is selected. The Settings control clears local user data and session credentials during switching; other tabs clear their session storage and reload when the key changes. It also documents banner display, control availability, and the staging-origin connect-src requirement, while stating that the switch is not access control.
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Essentials by visiting https://app.coderabbit.ai/settings/billing.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 43 minutes.

@Programcode

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 59 minutes.

@Programcode

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 58 minutes.

…witch

AuthProvider.signOut waits on the Supabase logout request with no timeout,
and both switch controls disable themselves on click. An unresponsive
staging project could therefore leave the switch pending forever, which is
the trap the staging strip exists to avoid. Wait at most 4 s, the same as
AuthProvider's auth operation timeout, then switch anyway.

Found by Codex review.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ut hangs

The persisted app stores are shared by production and staging. AuthProvider
resets them only once sign-out completes and SIGNED_OUT fires, so a timed-out
sign-out let one project's cached results, such as a generated case, reload
into the other.

AuthProvider now offers leaveEnvironment. It waits at most 4 s for sign-out,
then always drops this project's stored session and resets the session,
settings (keeping the interface language), sparring, debate and debate
context stores. The switcher and the staging strip use it. client.ts exports
AUTH_STORAGE_KEY so the key is defined once.

Found by Codex review.

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

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…he strip clickable

- leaveEnvironment now clears the cached Google provider token, which export
  falls back to. A hung sign-out never fires SIGNED_OUT, so the token could
  otherwise reach the other project's account. Both paths share one helper.
- It also calls the session store's clearSession, which empties the persisted
  form drafts and active results that resetLocalData leaves behind.
- On the landing page the fixed navbar covered the staging strip and caught
  its clicks. The strip now stacks above it. A new Playwright test clicks
  Switch to Production on / and /login once the navbar has slid in.

Found by Codex review.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each tab picks its Supabase project once at startup, but all tabs share the
persisted stores and merge each other's writes. After one tab switched, the
others kept the old project while receiving the new project's data, so a
production draft could be submitted to staging. Every tab now watches the
debate-supabase-env key and reloads as soon as it points elsewhere, before the
switching tab writes anything for the new project. A two-tab Playwright test
covers it and fails without the listener.

Found by Codex review.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… the strip

- A tab that reloads because another tab switched projects now clears its
  own session storage first: the Google token, which export falls back to,
  and the sparring state. AuthProvider exposes discardLocalState for this,
  and leaveEnvironment uses the same function after its bounded sign-out.
- The staging strip is now sticky with a fixed height, published as
  --staging-banner-height. The landing navigation and the app header offset
  by it, so on mobile the strip no longer covers the menu button.

Found by Codex review.

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

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Programcode

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 57 minutes.

A tab that handled the storage event late reset the shared persisted stores,
which by then could hold the new project's data, such as the Current Debate
pointer and the theme. Follower tabs now call discardTabState, which clears
only sessionStorage state (Google token, sparring). The full cleanup stays
inside leaveEnvironment for the tab that switches.

Found by Codex review.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
leaveEnvironment called signOut() with Supabase's default global scope,
which revokes every session of the account. Switching between production
and staging signed the admin out on all their other devices too. Use the
local scope, which ends only this browser's session.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	docs/operations.md
@Programcode

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Programcode
Programcode merged commit 363533e into main Oct 1, 2026
8 checks passed

This branch was successfully deployed

1 active deployment
Preview — e5aa4041 Deployed Oct 1, 2026 by vercel[bot]
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.

2 participants