Skip to content

fix(ux): restore auth CTA, minimal loading, and move theme control to Settings - #116

Merged
cameroncuster merged 3 commits into
mainfrom
bot/auth-loading-theme-ux-cleanup
Jul 26, 2026
Merged

fix(ux): restore auth CTA, minimal loading, and move theme control to Settings#116
cameroncuster merged 3 commits into
mainfrom
bot/auth-loading-theme-ux-cleanup

Conversation

@cameroncuster

@cameroncuster cameroncuster commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Problem

Recent changes introduced signed-out/loading/theme UX regressions: the auth CTA was renamed to Sign up, the OAuth callback became a page-like card, the Settings loading state lost its spinner (bare Loading settings… text), and the theme control lived in the header as a prominent picker.

Solution

Restore the prior behavior from git history and consolidate theme selection in Settings:

  • Auth CTA restored to Continue with GitHub (desktop, mobile, e2e) — reverts fix(auth): simplify sign-up label #111.
  • Auth-callback loading restored to a minimal spinner + Signing you in… instead of the page-like card, keeping feat(settings): add system theme and Kattis solve import #108's hardened redirect logic.
  • Settings loading spinner restored to the exact canonical animate-spin markup still used by the contests, leaderboard, and problem loading surfaces (with matching Loading settings... copy) — reverts the feat(settings): add system theme and Kattis solve import #108 plain-text regression. Audited all loading surfaces touched by the recent header/settings/theme/auth work; Settings was the only regressed one (ProblemDisplay/contests/leaderboard spinners were untouched).
  • Header theme cycle removed on desktop and mobile.
  • Compact single-target theme cycle added to Settings (system → light → dark → system), ≥44×44px, keyboard/SR-accessible via the existing ThemeCycleButton; default stays system. Persistence unchanged: signed-in → Supabase, anonymous → localStorage. No radio panels reintroduced.
  • Compact Settings gear icon preserved.

No new dependencies. Historical spinner/copy/style used as the source of truth — no new loading design invented.

Testing

  • pnpm run lint, pnpm run lint:es, pnpm run check — clean.
  • pnpm run test — 400/400 pass (updated settings-a11y + theme-service; new settings-spinner regression test).
  • pnpm exec playwright test e2e/smoke.spec.ts e2e/auth-sanity.spec.ts — 78 passed / 6 skipped (desktop + mobile), covering exact auth copy, minimal callback markup, absence of the header theme control, Settings theme cycling/default/persistence/a11y, and the Settings loading spinner (asserts the spinner renders and the regressed Loading settings… page copy does not).
  • pnpm run build passes when the public Supabase build-time vars are supplied (as CI/Playwright do). See caveat.

Misc

  • pnpm run build fails on this branch and on clean main without PUBLIC_SUPABASE_URL / PUBLIC_SUPABASE_PUBLISHABLE_KEY (inlined from $env/static/public at build time). Pre-existing environment/secrets requirement, not a regression from this PR; the Playwright config injects these itself, so the e2e build/run is green.

… settings

- Restore signed-out CTA to 'Continue with GitHub' (desktop, mobile, tests)
- Restore minimal auth-callback loading treatment (spinner + 'Signing you in…')
  in place of the page-like card, keeping the hardened redirect logic
- Remove the header theme cycle control on desktop and mobile
- Add a compact single-target theme cycle to the Settings page
  (system → light → dark → system), preserving Supabase/localStorage
  persistence; no radio panels reintroduced
- Preserve the compact Settings gear icon
- Update focused unit and Playwright coverage
@vercel

vercel Bot commented Jul 26, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
gitgud Ready Ready Preview, Comment Jul 26, 2026 5:30pm

@cameroncuster

Copy link
Copy Markdown
Owner Author

PR Author Agent

👋 I've got this PR.

I'll handle review feedback, CI failures, and merge conflicts, and ping you the moment it's ready for review. Drop a comment anytime.

Picking reviewers is your call — I won't request anyone myself.

@cameroncuster

cameroncuster commented Jul 26, 2026

Copy link
Copy Markdown
Owner Author

PR Risk Analyzer Agent🛡️

👀 Human Input Needed
A pair-review briefing is ready for you.

→ Pair Review Briefing

@cameroncuster cameroncuster left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Deep Code Review Agent🐛

Review completed with 1 suggestions.

Comment thread src/routes/settings/+page.svelte
Recent work (#108) replaced the Settings loading state's shared animate-spin
spinner with a bare 'Loading settings…' line, leaving a janky page-like
interim state. Restore the exact canonical spinner markup still used by the
contests, leaderboard, and problem loading surfaces, with the matching
'Loading settings...' copy. Add unit + Playwright regression coverage that the
spinner renders and the regressed plain page copy does not.
@cameroncuster

Copy link
Copy Markdown
Owner Author

Verifier 🛰️

🛰️ Verifier Report

TL;DR: Built the PR head and drove it in a real browser (desktop + mobile) — checked the restored auth CTA, the minimal sign-in callback, the removal of the header theme control, and the new Settings theme cycle — against the repo's hermetic mock-Supabase preview.

🔍 Walkthrough

  • Anonymous header now shows the restored Continue with GitHub CTA (desktop + mobile) with no theme control — the two viewport specs asserting the CTA copy pass, where mainline rendered Sign up.
    desktop header: Continue with GitHub, no theme control mobile menu: Continue with GitHub, no theme control

    server-rendered header markup + the "no theme control" checks

    The Theme:-prefixed cycle button is gone from both the desktop bar and the mobile menu — the specs assert getByRole('button', { name: /^Theme:/ }) has count 0 on each viewport. On mainline the header imported ThemeCycleButton and rendered it in both places.

  • The OAuth callback renders a minimal spinner + Signing you in…, not the page-like card — read straight from the server response on the head:

    the decisive callback HTML (read live from the running preview)
    <title>Signing in</title>
    <div class="flex min-h-[40vh] items-center justify-center" role="status" aria-live="polite">
      <span class="callback-spinner ..." aria-hidden="true"></span>
      <p ...>Signing you in…</p>
    </div>

    On mainline this was <title>Finishing sign-in</title>, min-h-[55vh], and an <h1>Finishing secure sign-in</h1> card. The spec asserts the head HTML contains('Signing you in…') and not.toContain('Finishing secure sign-in') — both pass, and the role="status"/min-h-[40vh]/callback-spinner markup above is what I pulled from the live server.

  • The theme control now lives in Settings › Appearance as one compact ≥44×44 cycle button; clicking it advances System → Light → Dark and applies live (light shot, then dark after one click):
    Settings Appearance theme cycle, light Settings Appearance theme cycle, dark after click

    full cycle + persistence + "not radio panels" + touch-target size, from a seeded signed-in session

    Driving the button through System → Light → Dark → System flips html[data-theme] accordingly, ends back on Theme: System. Switch to Light, persists gitgud-theme=system to localStorage, and getByRole('radio', …) is count 0 (a single target, not radio panels). The spec also asserts the button's bounding box is ≥44×44px. My own run seeded a member session and clicked Light→Dark; the panel flipped to dark and the icon changed ☀️→🌙 (shots above).

📋 Scope

Under test: Restores the Continue with GitHub auth CTA and the minimal Signing you in… callback from git history, removes the header theme control, and adds a compact single-target theme cycle to Settings (System→Light→Dark, persisted to Supabase when signed-in / localStorage when anonymous).

Not tested:

  • Signed-in Supabase theme persistence and the "Theme could not sync." retry banner (needs authenticated live behavior; the opt-in SUPABASE_SMOKE=1 path is read-only and out of scope unless requested). localStorage persistence for anonymous users is covered by the suite.
  • Real GitHub OAuth round-trip (the flow is exercised against the hermetic mock, which is by design — no live provider is contacted).
  • Unit-level theme-service / settings-a11y cases — covered by CI's pnpm run test, not re-run here.
  • One-time runner fix: the browser was missing system libs (libnspr4.so); I installed them (playwright install-deps) — not a change-request issue.

📦 Artifacts · targeted e2e (smoke.spec.ts + auth-sanity.spec.ts): 76 passed / 6 skipped on desktop + mobile (the 6 skips are viewport-conditional guards). Screenshots linked inline above.


Don't like what you see? Add a verification skill so the next run tests this kind of change.

Want another run? Comment cosmos verify on this PR.

👍 / 👎 · View session

Deep Code Review flagged (PR #116, r3653003232) that with the header theme
control removed and /settings redirecting signed-out users, anonymous visitors
had no reachable UI for the localStorage theme preference.

- Stop redirecting anonymous visitors from /settings; render the Appearance
  theme cycle for everyone.
- Gate the account-only Privacy and Import sections behind {#if user}, and only
  call fetchUserPreferences() when a session exists, so no account query fires
  for anonymous visitors.
- Add the compact Settings gear to the signed-out header (desktop + mobile) so
  anonymous users can reach Settings; the theme picker stays out of the header.
- Preserve system default and localStorage persistence; keep responsive footer
  spacing by moving the bottom margin onto the last visible card.
- Update/extend unit + Playwright coverage: anonymous can reach Settings, cycle
  system->light->dark->system, persist across reload, sees no account-only
  sections and triggers no account read; signed-in Supabase persistence retained.
@cameroncuster

Copy link
Copy Markdown
Owner Author

🛰️ Verifier — kickoff

Starting an end-to-end runtime verification of this PR at exact head SHA 5e2af49 (5e2af49315b76c5b2cdd46dcc9065e8ad1040609). Fetched pull/116/head and checked it out detached; will abort and report stale evidence if the head moves during the run. No code edits, no verdict, no merge.

Scenario plan (desktop + mobile where applicable):

  1. Signed-out visitor sees a compact Settings gear, reaches /settings via visible UI, no auth redirect.
  2. Anonymous Settings shows Appearance only (no Privacy/Import); zero user_preferences/account requests.
  3. Real Settings theme button system→light→dark→system: default system in a clean context, ≥44×44 target, icons/accessible names, keyboard activation, visible theme change.
  4. Anonymous theme set → reload → localStorage + rendered-theme persistence (through the UI, not exported functions).
  5. Signed-in Settings shows Appearance + Privacy + Import; signed-in Supabase persistence proven at runtime if sanctioned creds allow, else code/test inspection distinguished from runtime evidence.
  6. Header has no theme control (desktop/mobile); Settings stays a compact gear with no visible text for both auth states.
  7. Auth CTA is exactly Continue with GitHub; safe initiation check.
  8. Initial session resolution has no janky full-page flash; auth callback uses minimal spinner + concise copy and preserves redirects.
  9. Settings loading uses the canonical animate-spin spinner (not the regressed plain-text Loading settings…); audit other recently touched loading surfaces.
  10. Layout/a11y across desktop/mobile, account-section gating, footer spacing, console/network errors.

Also explicitly checking that blocking review issue r3653003232 (anonymous users had no reachable theme UI) is resolved in runtime behavior.

Milestone updates and a final per-scenario findings comment (evidence for human judgment, not a verdict) will follow.

🤖 Verifier · View session

@cameroncuster

Copy link
Copy Markdown
Owner Author

🛰️ Verifier — milestone 1/2 (anonymous desktop path, SHA 5e2af49)

Driving the live dev server (Vite dev, placeholder Supabase env exactly as CI uses) through the actual UI with a real browser. Evidence so far:

  • Header (desktop): compact Settings gear (icon-only, accessible name Settings, no visible text), auth CTA is exactly Continue with GitHub, and no theme control anywhere in the header. Snapshot shows only Home/Problems/Contests/Leaderboard/About + gear + CTA.
  • Reachability (fixes r3653003232): clicking the header gear as a signed-out visitor navigates to /settings and stays there — no auth redirect. curl /settings is 200, not a redirect.
  • Anonymous Settings content: renders Appearance only; Privacy and Import are absent. Non-static network requests: none — zero user_preferences/rest/v1/auth/v1 account calls (the only supabase-matching hits are Vite dev serving JS module source).
  • Theme button (the real one in Settings): default System in a clean context, target box measured 44×44px. Cycled System → Light → Dark → System and it wraps correctly. Icons: monitor / sun / moon; accessible names Theme: System. Switch to Light… Light. Switch to Dark… Dark. Switch to System. Keyboard activation (focus + Enter) advanced the cycle and visibly repainted the page (body bg → dark oklch(0.18 0 0)).
  • Anonymous persistence: set theme to Dark via clicks, reloaded, and both localStorage['gitgud-theme']==='dark' and the rendered theme (data-theme=dark, dark body) persisted — driven only through the UI, not exported theme functions.
  • Console: 0 errors, 0 warnings across these navigations.

Next: mobile viewport, auth callback + initial-flash, Settings loading spinner, other loading surfaces, and the signed-in path (via the app's own mocked e2e layer since production Supabase is out of bounds). Screenshots and a per-scenario findings comment to follow.

🤖 Verifier · View session

@cameroncuster

Copy link
Copy Markdown
Owner Author

🛰️ Verifier — findings (evidence, not a verdict)

TL;DR: Drove this PR's UX changes end-to-end against a live browser — the anonymous path on a real Vite dev server through the actual UI, and the signed-in / transient-loading / callback states through the app's own headless-Chromium e2e suite — all at exact head SHA 5e2af49, which never moved during the run. Below is what I observed, scenario by scenario, for a human to judge.

SHA: 5e2af49315b76c5b2cdd46dcc9065e8ad1040609 (re-checked at start and end; unchanged). No code edits, no approve/reject, no merge.

🔍 Per-scenario evidence

  1. Signed-out reaches /settings via visible UI, no redirect — clicked the header Settings gear (desktop) and the menu gear (mobile) as an anonymous visitor; both navigated to /settings and stayed there (URL /settings, title Settings). curl /settings returns 200, not a redirect.
  2. Anonymous Settings = Appearance only; zero account requests — rendered section headings were exactly ['Appearance']; Privacy and Import absent (desktop + mobile). Non-static network requests during the visit: none — zero rest/v1/auth/v1/user_preferences calls (the app's own e2e also asserts accountRequests == []).
  3. Real Settings theme button — default System in a clean context (fresh localStorage), target box measured 44×44px on both viewports. Cycled System → Light → Dark → System (wraps). Icons monitor/sun/moon; accessible names Theme: System. Switch to Light… Light. Switch to Dark… Dark. Switch to System. Keyboard activation (focus + Enter) advanced the cycle and visibly repainted the page (body bg → dark oklch(0.18 0 0)).
  4. Anonymous persistence via the real UI — set Dark by clicking the button, reloaded, and both localStorage['gitgud-theme']==='dark' and the rendered theme (html[data-theme=dark], dark body) persisted. Driven only through clicks — no exported theme functions called.
  5. Signed-in Settings + Supabase persistence — the app's e2e (seeded member session, mock Supabase standing in for the real backend, which is out of bounds by guardrail) passes drives the compact theme cycle from Settings, persists it, which awaits a real user_preferences write (response.ok() with body containing "theme":"dark") — runtime evidence the signed-in save path reaches Supabase. Signed-in Appearance renders at runtime there. Presence of Privacy + Import for a signed-in user is proven by code/test inspection (they sit inside the {#if user} gate; settings-a11y asserts this), not by a direct runtime screenshot in this session — flagged as a caveat below.
  6. No header theme control; compact gear — header snapshot (desktop) and mobile menu show no Theme: control; the Settings link is icon-only with empty text, 44×44, for both auth states. e2e asserts header Theme: count == 0 on both layouts.
  7. Auth CTA exactly Continue with GitHub — confirmed verbatim in the live header (desktop + mobile) and in the e2e. Clicking it initiates the flow (waitForURL(/\/auth\/callback/)); safe (no real OAuth completed).
  8. No janky flash; minimal callbackapp.html resolves the stored theme synchronously before first paint; initial shell renders a stable Checking session… status (no empty auth slot). Callback SSR HTML contains Signing you in… and not Finishing secure sign-in / Verification in progress (minimal spinner, no page-like card); a failed callback returns home with ?auth_error=true and a retryable error — redirect logic preserved.
  9. Settings loading uses the canonical animate-spin spinner — e2e asserts [role=status] svg.animate-spin is visible on Settings load and the regressed plain Loading settings… page copy has count 0. Audited other loading surfaces: animate-spin is shared by contests, leaderboard, ProblemDisplay, submit, and header — none regressed; the only bare Loading… is the Import Preview button label (a control spinner, not a page loader).
  10. Layout / a11y / errors — across desktop + mobile: account sections correctly gated, footer spacing intact (e2e asserts ≥60px below the last card), and 0 console errors / 0 warnings across all navigations I drove.

✅ Blocking review issue r3653003232

The issue reported that, with the header control removed, signed-out users had no reachable UI to change the gitgud-theme preference (the route redirected them away). At this head SHA (commit make Settings reachable for anonymous visitors) that is resolved in runtime behavior: the anonymous visitor reaches /settings through the visible gear, sees the Appearance theme control, changes the theme, and it persists across reload — all demonstrated above (scenarios 1, 3, 4) with no account query fired.

⚠️ Caveats (what this run did not runtime-prove)

  • Signed-in Privacy + Import rendering was confirmed by code/test inspection (the {#if user} gate + settings-a11y assertions), not by a live signed-in screenshot in this session. The signed-in e2e project seeds a member session and its Appearance/theme/persistence assertions pass; the section-presence guarantee rests on the gating tests.
  • Real production Supabase was never contacted (guardrail). Signed-in persistence is proven against the sanctioned mock Supabase the e2e harness uses; PUBLIC_SUPABASE_PUBLISHABLE_KEY was set to the CI placeholder for the anonymous dev-server run, so anonymous flows are fully real and never touch a backend.
  • Actual GitHub OAuth completion was not performed (only initiation), by design.

CI at this SHA: 13/13 checks green (inspected, not re-run). App e2e I ran locally (headless Chromium, mock Supabase): 80 passed / 0 failed / 4 skipped (skips are the opt-in live-smoke tests).

📷 Screenshots (anonymous, live dev server)

Desktop Settings, System (light-resolved) — monitor icon, header gear, no theme control, Continue with GitHub:

desktop settings system/light desktop settings dark

Mobile Settings (Appearance only):

mobile anonymous settings

These are findings for human judgment — not a pass/fail verdict and not an approval.


Want another run? Comment cosmos verify on this PR.

👍 / 👎 · View session

@cameroncuster
cameroncuster merged commit f6c1504 into main Jul 26, 2026
14 checks passed
@cameroncuster
cameroncuster deleted the bot/auth-loading-theme-ux-cleanup branch July 26, 2026 18:14
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