Now I remember the theme
This commit is contained in:
62
openspec/changes/archive/2026-02-11-remember-theme/design.md
Normal file
62
openspec/changes/archive/2026-02-11-remember-theme/design.md
Normal file
@@ -0,0 +1,62 @@
|
||||
## Context
|
||||
|
||||
The site already supports theme selection via a floating notch and persists the user's choice using `localStorage` (key: `site.theme`). Theme selection affects the root document via `html[data-theme]` and is initialized before first paint via an inline head script.
|
||||
|
||||
Two gaps remain:
|
||||
- Returning users should reliably see their last-selected theme even in environments where `localStorage` is unavailable.
|
||||
- We need analytics to measure theme switcher usage and preferred themes.
|
||||
|
||||
The site uses Umami for analytics. Most interactions are tracked via `data-umami-event*` attributes, and runtime-only events use `window.umami.track(...)`.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Persist theme selection across visits with a robust fallback: `localStorage` primary, client-side cookie fallback.
|
||||
- Apply the stored theme before first paint when possible.
|
||||
- Emit a deterministic Umami event when a user changes theme via the theme notch.
|
||||
|
||||
**Non-Goals:**
|
||||
- Server-side rendering of theme choice (the site is statically built; no request-time HTML variation).
|
||||
- Tracking theme selection on initial page load (only user-initiated changes).
|
||||
- Adding new UI beyond the existing theme notch.
|
||||
|
||||
## Decisions
|
||||
|
||||
1) Persistence mechanism and precedence
|
||||
|
||||
- **Decision**: Read theme preference in this order:
|
||||
1. `localStorage` (`site.theme`)
|
||||
2. Cookie (`site_theme`)
|
||||
3. Environment signals (forced-colors, prefers-color-scheme)
|
||||
- **Rationale**: `localStorage` is already in use and provides a stable primary store. Cookies provide a resilient fallback when storage access is blocked or throws.
|
||||
- **Alternatives considered**:
|
||||
- Cookie-only: simpler but unnecessary regression from existing behavior.
|
||||
- URL param: not persistent and adds user-visible noise.
|
||||
|
||||
2) Cookie format and attributes
|
||||
|
||||
- **Decision**: Store `site_theme=<theme>` with `Max-Age=31536000`, `Path=/`, `SameSite=Lax`. Set `Secure` when running under HTTPS.
|
||||
- **Rationale**: First-party cookie with long TTL provides continuity across visits. The cookie is readable from the inline head script for pre-paint initialization.
|
||||
|
||||
3) Analytics event shape
|
||||
|
||||
- **Decision**: Emit a custom Umami event via `window.umami.track("theme_switch", data)` only on user-initiated changes.
|
||||
- **Event properties**:
|
||||
- `target_id`: `theme.switch.<theme>`
|
||||
- `placement`: `theme_notch`
|
||||
- `theme`: new theme value (`dark` | `light` | `high-contrast`)
|
||||
- `prev_theme`: previous theme value (same enum) when known
|
||||
- **Rationale**: A dedicated event name makes reporting straightforward (no need to filter general `click`). Using `target_id`/`placement` keeps it compatible with the site's interaction taxonomy.
|
||||
- **Alternatives considered**:
|
||||
- Reuse `click` event: consistent, but mixes preference changes into general click reporting.
|
||||
|
||||
4) Avoid tracking initial theme restoration
|
||||
|
||||
- **Decision**: Do not emit `theme_switch` from the head theme-init script.
|
||||
- **Rationale**: We want to measure explicit user interaction with the notch, not implicit restoration.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- Cookie and storage may both be blocked in restrictive environments → fallback to environment signals; no persistence.
|
||||
- Umami may be disabled/unconfigured or not loaded at event time → guard with `typeof window.umami !== "undefined"` and keep behavior non-fatal.
|
||||
- Using cookies introduces another persistence layer → must keep `localStorage` and cookie consistent on successful theme changes.
|
||||
Reference in New Issue
Block a user