Skip to content

feat(settings): add system theme and Kattis solve import - #108

Merged
cameroncuster merged 1 commit into
mainfrom
cameroncuster/theme-auth-kattis
Jul 26, 2026
Merged

feat(settings): add system theme and Kattis solve import#108
cameroncuster merged 1 commit into
mainfrom
cameroncuster/theme-auth-kattis

Conversation

@cameroncuster

Copy link
Copy Markdown
Owner

Summary

  • add three-way System, Light, and Dark appearance preferences with synchronous pre-paint resolution and live OS-theme updates
  • persist signed-in appearance choices across devices while keeping anonymous choices local
  • add immediate GitHub sign-in feedback, a secure callback progress state, and recoverable auth/logout errors
  • add safe Kattis solve import from pasted IDs/URLs or local text/HTML files without scraping Kattis or requesting credentials
  • re-derive tracked problem UUIDs at confirmation time under the current actor and existing RLS
  • harden preference writes against concurrent theme/privacy changes, stale actor sessions, and out-of-order file reads

Behavior and safety

  • existing explicit Light/Dark preferences stay explicit
  • new and anonymous users default to System
  • no initial light-theme flash when the OS or stored preference is dark
  • Kattis preview is read-only; confirmation writes only database-derived tracked IDs
  • no new dependencies

Validation

  • pnpm run check
  • pnpm run lint:es
  • pnpm run lint
  • pnpm run test:coverage — 395 passed; 98/95/98 thresholds satisfied
  • pnpm run test:performance — 21,367 B raw / 1,610 B gzip
  • pnpm run test:e2e — 205 passed, 7 intentional viewport/live skips

Risk analysis

  • preference writes are column-specific and serialized where ordering matters
  • every asynchronous save is bound to the actor that initiated it
  • stale account fetches and stale local-file reads cannot overwrite newer UI state
  • OAuth failure paths always return to a visible, retryable state
  • theme startup remains synchronous and dependency-free

Pull Request opened by Augment Code | View session

@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 3:57am

@cameroncuster
cameroncuster marked this pull request as ready for review July 26, 2026 05:13
@cameroncuster
cameroncuster merged commit e385d6e into main Jul 26, 2026
14 checks passed
@cameroncuster
cameroncuster deleted the cameroncuster/theme-auth-kattis branch July 26, 2026 05:13
@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

Copy link
Copy Markdown
Owner Author

Verifier🛰️

🛰️ Verifier Report

TL;DR: Drove the new three-way appearance preference, the Kattis solve import, and the OAuth callback/error recovery end-to-end in a real browser against a production build of the PR head served on a local mock Supabase.

Heads-up: this PR was already merged by the time the run started; findings are on the head commit 078e1b2 for the record.

🔍 Walkthrough

  • Appearance is genuinely three-way and follows the OS — with the OS in light, System resolves light; flip a fresh visitor's OS to dark and System resolves dark (no light flash), exactly what mainline's light-only default couldn't do:
    System resolves to light under OS light System resolves to dark under OS dark, no flash

  • Selecting Dark repaints the whole document to Dark Ink and persists (live "Saved"), driving data-theme from lightdark:
    Whole settings page repainted to Dark Ink with Saved toast

    the runtime signal behind the repaint (read from the live page after selecting Dark)
    THEME_AFTER_DARK=dark        # document.documentElement.dataset.theme after selecting "Dark"
    THEME_SYSTEM_OS_DARK=dark    # System preference under OS prefers-color-scheme: dark
    

    Both radios also carry the live "Currently resolved to …" hint (light vs dark) visible in the shots above.

  • Kattis import parses pasted IDs/URLs in-browser and never contacts Kattisgamma missing gamma <gamma-url> previews 1 matched, 1 unmatched, 2 duplicates removed:
    Kattis preview: 1 matched, 1 unmatched, 2 duplicates removed

    the read-only + no-scrape guarantees, and the file paths, all confirmed by the suite

    From e2e/import-solves.spec.ts (all green): paste preview asserts zero *.kattis.com requests and getSolvedWriteAttempts() === 0; a local .html file is parsed in-browser and only the DB-re-derived tracked UUID is imported (the evil.test/problems/… link is ignored); an oversized replacement clears the prior payload; the latest file wins on out-of-order reads. Confirm imports the server-derived match and is idempotent (0 newly solved on a second pass).

  • OAuth callback with no session lands on a visible, retryable error rather than a dead end — home shows "Continue with GitHub" + "Couldn't open GitHub. Try again."; the suite confirms retry re-enters /auth/callback and clears the alert:
    Home with retryable Couldn't open GitHub auth error

📋 Scope

Under test: three-way System/Light/Dark appearance with synchronous pre-paint resolution + live OS-theme follow and cross-device persistence; safe Kattis solve import from pasted IDs/URLs or local .txt/.html files (no scraping, no credentials); and clearer GitHub sign-in / callback progress + recoverable auth/logout errors.

Also covered: full hermetic suite pnpm test:e2e205 passed, 7 skipped (intentional viewport/live splits) on desktop + mobile, including the anonymous /settings guard, the empty-handle and provider-failure (notfound/ratelimited/fail/malformed) inline-error paths (all write nothing), and the settings a11y/keyboard/44px-target checks.

Not tested:

  • SQL DDL in sql/auth/user_preferences.sql (theme default lightsystem, the NOT VALID theme IN ('system','light','dark') CHECK, and the new-user trigger insert) — no runtime DB surface reachable from this runner (server initdb isn't installed; the shared Supabase MCP isn't attached and any DDL would require an isolated branch). Static consistency confirmed: the DB allowlist matches the app's THEME_PREFERENCES = ['system','light','dark'] and both defaults are system.
  • Live GitHub OAuth round-trip — the suite (and this run) seed a deterministic mock session by design; the opt-in SUPABASE_SMOKE=1 path is off by default.
  • Pure-logic units (parser tokenizing, normalizeThemePreference, resolution) — covered by the diff's own tests/*.test.ts under CI, not re-run here.

📦 Artifacts · System/light · System/OS-dark · Dark full page · Kattis preview · Auth error


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 change request.

👍 / 👎 · View session

@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.

ALTER TABLE user_preferences DROP CONSTRAINT IF EXISTS user_preferences_theme_check;
ALTER TABLE user_preferences
ADD CONSTRAINT user_preferences_theme_check
CHECK (theme IN ('system', 'light', 'dark')) NOT VALID;

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🐛

This adds the theme check as NOT VALID, so any existing user_preferences row with an out-of-set theme remains in place but will start failing subsequent unrelated UPDATEs (for example changing hide_from_leaderboard) because PostgreSQL enforces not-valid checks on new row versions. Those legacy rows need an explicit rollout path; otherwise affected users cannot save unrelated preferences.

Severity: medium


🤖 Was this useful? React with 👍 or 👎

cameroncuster added a commit that referenced this pull request Jul 26, 2026
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.
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