Files
cs249r_book/interviews/staffml/public/theme-bootstrap.js
Vijay Janapa Reddi e5ccbcc652 feat(staffml): flip default theme to light for ecosystem consistency
mlsysbook.ai book + labs + kits + slides + instructors sites all
default to light. StaffML was the outlier defaulting to dark, which
caused visual whiplash when users crossed from the book to interview
prep. Flipping the default fixes that while preserving per-user
preference: anyone who explicitly toggled dark keeps dark (localStorage
staffml_theme persists across sessions), and the cross-site mirror to
quarto-color-scheme keeps the choice in sync when they move between
subsites.

Changes:

public/theme-bootstrap.js — fallback return 'dark' -> 'light'. This is
  the render-blocking script that sets data-theme on the html element
  before first paint (avoids FOUC). Still checks localStorage
  staffml_theme first, then quarto-color-scheme as the cross-site
  secondary; only the fallback when neither is set changes.

src/components/ThemeProvider.tsx — three matching flips:
  - createContext default (cosmetic; used only before mount)
  - getInitialTheme fallback -> 'light'
  - useState initial -> 'light' (SSR + pre-hydration state)

What stays the same:
  - User's stored preference wins (both forward: if they toggled dark
    on the book and came to staffml, we honor it; and backward: if
    they toggled light on staffml and go to the book, book honors it)
  - The theme toggle still works in both directions
  - OS prefers-color-scheme is still NOT honored (deliberate; see
    getInitialTheme comment). Add that as a follow-up if requested.

Build verified green. Expect minor contrast tuning may surface once
real users see the light theme — dark was the first-class design
target originally, so some accent colors may look washed out on light
backgrounds. Follow-up PRs fix individual components as reported.
2026-04-22 12:08:49 -04:00

43 lines
2.0 KiB
JavaScript

// Theme bootstrap — runs synchronously before paint to avoid FOUC.
//
// This file MUST be loaded via a render-blocking <script src="..."> tag in
// the document <head> (no async, no defer). It reads the user's persisted
// theme preference and applies it to <html data-theme="..."> before the
// body parses, so CSS variables resolve to the right values on first paint.
//
// Lives in /public so it ships as a same-origin static asset, which lets us
// drop 'unsafe-inline' from the document CSP without losing the theme
// bootstrap. See src/app/layout.tsx for the matching <script> tag.
//
// Cross-site interop:
// The Quarto-built ecosystem subsites (book, labs, kits, slides, ...)
// persist the user's choice under the localStorage key
// `quarto-color-scheme`. We read that key as a secondary source when
// StaffML has no preference of its own, so a user who toggled dark mode
// on the book lands here in dark mode on first visit.
//
// The matching write-back lives in the StaffML theme toggle (see
// src/components/Providers.tsx / theme hook) — when the user toggles
// here we mirror the choice into `quarto-color-scheme`, so navigating
// onward to the book inherits the StaffML choice.
(function () {
function pick() {
try {
var t = localStorage.getItem("staffml_theme");
if (t === "light" || t === "dark") return t;
// Fall back to the ecosystem-shared key set by Quarto sites.
var q = localStorage.getItem("quarto-color-scheme");
if (q === "light" || q === "dark") return q;
} catch (_) {
/* localStorage unavailable (privacy mode, sandboxed iframe). */
}
// Ecosystem-wide default is light (book, labs, kits, slides, etc.).
// StaffML matches so users don't get visual whiplash crossing between
// the book and the interview prep. Users who want dark toggle once,
// the choice persists in localStorage AND mirrors to the Quarto sites
// via quarto-color-scheme.
return "light";
}
document.documentElement.dataset.theme = pick();
})();