ADR-0159: The colour scheme follows the system
- Status: accepted
- Date: 2026-10-01
- Deciders: Mahdi Amirabdollahi
Context
ADR-0005 set the console "dark-mode-first". index.html forced the dark scheme, a first visit always opened dark, and the header control toggled between dark and light. An operator whose operating system is light, or switches with the time of day, got a dark console regardless and had to override it on every browser.
A remembered choice has to be applied before the first paint, or a reload shows one frame in the wrong scheme. Mantine's answer, ColorSchemeScript, is an inline script, and Studio's Content-Security-Policy allows scripts only from its own origin (script-src 'self', ADR-0122). The table density chosen by the viewer (ADR-0162) has the same first-paint problem.
Decision
We will follow the operating system by default, with three states the viewer chooses from.
- Three states: system, light and dark.
MantineProviderusesdefaultColorScheme="auto", andindex.htmlno longer forces a scheme. A first visit follows the system. - One control, one action. The header control cycles system, light, dark. Its accessible name states the scheme it switches to and, while it follows the system, the scheme the system is using ("Use light theme (system is dark)"). The command palette offers the same action through the same hook.
web/public/boot-prefs.jsapplies the choice before React runs. It is a static, same-origin script loaded synchronously fromindex.html, so the CSP allows it withoutunsafe-inlineor a nonce. It:- reads Mantine's
mantine-color-scheme-value, accepts onlylight,darkorauto, resolvesautothroughmatchMedia, and setsdata-mantine-color-scheme; - reads
as:density, accepts only its known values, and setsdata-density; - ignores anything else in storage.
- reads Mantine's
- A backend test asserts that
/boot-prefs.jsis served as JavaScript. Without it, the SPA fallback would quietly serveindex.htmlat that path and the script would never run. - Graph canvases take their
colorModefrom the computed scheme.
This amends ADR-0005's "dark-mode-first". Both schemes are first-class and meet the same contrast test (ADR-0158).
Consequences
- The console opens in the scheme the operator's system uses, and a chosen scheme opens with no frame of the other one.
- The CSP is unchanged.
- One more static file must stay in the build, and its whitelist must grow with any new preference it applies.
boot-prefs.jscopies Mantine's storage key and value set. A Mantine change to either breaks it silently, so the colour-scheme tests read the value through the script and through Mantine.- Every screen is now designed and checked in both schemes, which doubles the screenshot sweep.
Alternatives considered
- Mantine's
ColorSchemeScript. An inline script the CSP blocks. Allowing it needs a hash that changes with every Mantine release, orunsafe-inlinefor scripts. - Apply the scheme in React after mount. Simple, but every reload shows a frame in the wrong scheme.
- A cookie read by the server, which renders the attribute. The server would have to template
index.html, which it serves as a static file today, for a preference that is the browser's own. - Keep dark-first. It ignores a setting the operator has already made in their system.