docs: write down the second ground, and the rule it creates
PRD gets F15 and the fork that asked for it, DATA-MODEL the prefs field and the fact that a script outside the bundle now reads that key, and ARCHITECTURE the pre-paint script — the one piece of this that is neither CSS nor React. The AGENTS rule is the part worth reading twice: a component that names a colour instead of a token now looks right in one theme and wrong in the other, and nothing will tell you which. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
eca5ad7a0a
commit
c1cd1148d9
+20
-1
@@ -120,7 +120,8 @@ src/
|
||||
useMarkSet.ts ignores (and the superseded completions shape)
|
||||
useProgress.ts status, effort, note, daily override (F12)
|
||||
useDailyLog.ts which game-days are ticked off
|
||||
usePrefs.ts region, filters, focus, view, onboarding flags
|
||||
usePrefs.ts region, filters, focus, view, theme, onboarding flags
|
||||
theme.ts dark/light: resolving it, applying it, hues on paper
|
||||
useCustom.ts the reader's own games and events (F13)
|
||||
gameMeta.tsx lane id → name, label, hue; resolves custom lanes too
|
||||
sort.ts deadline order, or what you're partway through
|
||||
@@ -239,6 +240,24 @@ back to the last copy seen). Countdowns run off the device clock, so the app sta
|
||||
network. Offline state is surfaced in the header and above the footer — stale data must never be
|
||||
presented as current.
|
||||
|
||||
### The theme, before the bundle arrives
|
||||
|
||||
The page is drawn dark by default and light when the reader has asked for it (PRD F15). Which one
|
||||
is decided by one attribute on `<html>`: `styles.css` holds the dark tokens on `:root` and
|
||||
re-strikes them under `:root[data-theme="light"]`, so nothing in React knows a theme exists and no
|
||||
component holds a colour of its own.
|
||||
|
||||
Setting that attribute is the one part React cannot do in time. It mounts after the bundle has
|
||||
downloaded and parsed, which on a cold cache is long enough to show a reader who chose light a
|
||||
dark page, on every single load. So a small inline script in `index.html` reads the same
|
||||
`localStorage` prefs key the app does, sets the same attribute, and updates `<meta
|
||||
name="theme-color">` — before first paint, and with a `try`/`catch` so storage being unavailable
|
||||
costs the reader the default theme rather than the page.
|
||||
|
||||
That makes the theme's ground colour a fact written in three files that cannot import each other:
|
||||
the stylesheet, `state/theme.ts` (which needs it for the meta tag), and the shell. `test/theme.test.ts`
|
||||
pins the three together rather than trusting them to be edited at the same time.
|
||||
|
||||
### Shipping a new version to an open page
|
||||
|
||||
A cache-first shell is what makes the offline story work and what makes a deploy invisible: the
|
||||
|
||||
+5
-1
@@ -219,7 +219,7 @@ Namespaced, versioned, and small. Nothing here ever goes to the server.
|
||||
"gacha-tracker:v1:ignored" // { [eventId]: { at } } — "stop showing me this"
|
||||
"gacha-tracker:v1:prefs" // { region, hiddenGames[], knownGames[]?, focusGame, sort, view,
|
||||
// timelineDayWidth, timelineGroup, detectDaily, showCompleted,
|
||||
// showIgnored, regionConfirmed, onboarded }
|
||||
// showIgnored, theme, regionConfirmed, onboarded }
|
||||
// timelineDayWidth is px per day on the board, stored as the
|
||||
// measurement rather than a step number and read through
|
||||
// snapDayWidth — so a value from an older ladder still opens
|
||||
@@ -230,6 +230,10 @@ Namespaced, versioned, and small. Nothing here ever goes to the server.
|
||||
// knownGames is every lane the reader has been offered. Absent
|
||||
// means unrecorded, not "offered nothing" — see PRD F8; a lane
|
||||
// missing from it is new to them and arrives switched off.
|
||||
// theme is "dark" | "light" | "system", defaulting to dark — see
|
||||
// PRD F15. It is read by the app *and* by a pre-paint script in
|
||||
// index.html, which is the only thing outside the client bundle
|
||||
// that touches a key in this space.
|
||||
"gacha-tracker:v1:completions" // SUPERSEDED — read once to migrate, never written
|
||||
```
|
||||
|
||||
|
||||
+31
@@ -284,6 +284,37 @@ The footer shows when the feed was last updated, per game. If a game's data is m
|
||||
stale, its lane carries a warning badge. Never present stale data as current — the whole value
|
||||
proposition is trust in the dates.
|
||||
|
||||
**F15 — Light mode, with dark still the default.**
|
||||
The app is a lit instrument panel and that is what it should be on first sight, but it is also read
|
||||
on a train in daylight and by people who find a dark UI harder rather than moodier. A public fork
|
||||
worked a theme toggle out first and is credited for the idea in the colophon. So the ground is
|
||||
the reader's choice: **Dark**, **Light**, or **System** to follow the device, in settings next to the
|
||||
region — both are "how do I read this?" and neither changes what the page knows.
|
||||
|
||||
Dark is what it ships as, and deliberately not `System`. A reader whose laptop is in light mode has
|
||||
said something about their laptop, not about this page; defaulting to the device would also move
|
||||
every existing reader the first time they loaded a build that had this, which is the `knownGames`
|
||||
mistake in a different costume (F8). Choosing `System` is one tap, and from then on it *is* their
|
||||
answer.
|
||||
|
||||
Three things the light theme has to get right, because they are what a second palette usually gets
|
||||
wrong:
|
||||
|
||||
- **It is a re-strike, not an inversion.** Every colour in the UI is a token, and light redefines the
|
||||
tokens rather than adding a second set of rules to components. Nothing in the app asks which theme
|
||||
it is in, so a new component cannot forget to support one.
|
||||
- **Urgency still reads.** The heat ramp carries meaning, and the dark theme's amber is 1.9:1 on
|
||||
paper — an urgency the reader cannot read is not urgency. Every step is re-struck to clear 4.5:1
|
||||
against the light ground, keeping its order and its meaning.
|
||||
- **A game keeps its colour.** The hues are identity (F1) and were all picked against a near-black
|
||||
ground; on paper the bright ones vanish. They are darkened until they read, along the same hue, so
|
||||
Wuthering Waves is still the green one — including the hues a reader picked for a game they
|
||||
invented (F13).
|
||||
|
||||
Switching is instant, costs nothing and saves nothing: no reload, and nothing marked, typed or
|
||||
ticked is touched. And it survives the load it is chosen on — the shell sets the theme before first
|
||||
paint, so a reader on light is never shown a dark page while the bundle downloads.
|
||||
|
||||
## Out of scope for v1
|
||||
|
||||
Accounts and sync; push notifications; in-game resource or pull tracking; native mobile apps (the
|
||||
|
||||
Reference in New Issue
Block a user