Every theme set so far validated, stored, was served in the profile response,
and did nothing at all to the page.
applyTheme wrote its rule as `[data-profile="alice"]`, specificity (0,1,0). Pico
sets its palette on `:root:not([data-theme=dark])`, which is (0,2,0) — `:not()`
contributes the weight of its argument. Source order never came into it; the
theme lost on specificity and would have lost from anywhere in the document. The
comment in index.html asserted the opposite in as many words:
landing last is all it needs to win: no user rule ever has to
out-specify anything
The rule is now `:root[data-profile="alice"]`, which matches Pico's (0,2,0) and
wins on being last, and both the comment and docs/profile-template.md now explain
the arithmetic rather than assert the conclusion.
Worth naming how this survived: it failed silently in every direction. The
whitelist accepted the declarations, the canonical text round-tripped through
SQLite, the profile response carried them, applyTheme built the block and put it
in the document — and the page rendered exactly as if none of it existed. Every
test passed because every test asked whether the theme was *stored* correctly.
Nothing read a computed style out of a real browser, which is the only place the
question "did this change anything?" can actually be answered.
Also: .state now takes --pico-muted-color. Where a link goes and how a service is
shared are both secondary to the name, and were being drawn at full strength.- commit
- 8f35b413c930888152a3973a8d2c6372d289a1d9
- parent
- 1a02e9fa0aa7f5ad9fa84984c3bfeaa56de0f761
- author
- Dami <dami.etoile@gmail.com>
| M | docs/ | 22 | +++++++++++++++++++--- |
| M | web/ | 14 | ++++++++++---- |
| M | web/ | 6 | ++++-- |
| M | web/ | 5 | +++-- |
@@ -15,9 +15,25 @@ can answer mechanically, in `crates/devsite-server/src/theme.rs`.
1515 | Site | `web/site.css` | The typeface, and the few layout rules Pico has no opinion about. No colours. |1616 | Theme | `<style id="profile-theme">` | One rule, written at render time, holding only `--pico-*` assignments. |1717 18-They load in that order, so a theme wins ties on document order alone. No user19-declaration ever has to out-specify anything, and none of them can, because a20-theme contains no selectors.18+They load in that order, and the theme is written as one rule:19+20+```css21+:root[data-profile="alice"] { --pico-primary: #bc6c25; … }22+```23+24+The `:root` is not decoration. Pico sets its palette on25+`:root:not([data-theme=dark])`, which is specificity **(0,2,0)** — `:not()`26+contributes the weight of its argument. A bare `[data-profile="alice"]` is27+(0,1,0) and loses to that outright, however late it appears in the document.28+Matching (0,2,0) and coming last is what makes a theme win.29+30+This was wrong in the first implementation, and it failed silently: themes31+validated, stored, served in the profile response, and applied to a page that32+went on looking exactly the same. Nothing short of reading a computed style in a33+real browser would have caught it, which is now how it is checked.34+35+A theme still writes no selectors of its own — that one is supplied here, and the36+user supplies only the declarations inside it.2137 2238 **Type is fixed.** Open Sans, vendored at `web/vendor/fonts/`, at 400 and 700 and2339 no other weight. The file is a variable font, so it is declared twice at those two@@ -123,9 +123,15 @@ async function completeSignIn() {
123123 ///124124 /// The server has already checked every property against its whitelist and every125125 /// value against a grammar, so this is only assembly: one rule, scoped to the126-/// profile, holding nothing but custom-property assignments. It is the last127-/// stylesheet in the document, so equal specificity is enough — no user rule128-/// ever has to out-rank Pico.126+/// profile, holding nothing but custom-property assignments.127+///128+/// The `:root` in the selector is load-bearing. Pico sets its palette on129+/// `:root:not([data-theme=dark])`, which is specificity (0,2,0) because `:not()`130+/// contributes its argument's weight. A bare `[data-profile="…"]` is (0,1,0) and131+/// loses to it outright — no matter how late in the document it appears. With132+/// `:root` in front we match that (0,2,0) and win on source order, being the last133+/// stylesheet in the page. This was wrong until it was tested in a browser, and134+/// the failure was silent: themes stored, served, and ignored.129135 function applyTheme(handle, declarations = []) {130136 const root = document.documentElement;131137 // Belt and braces: handles are already restricted to this alphabet server-side.@@ -146,7 +152,7 @@ function applyTheme(handle, declarations = []) {
146152 .map((d) => ` ${d.property}: ${d.value};`)147153 .join('\n');148154 $('profile-theme').textContent = body149- ? `[data-profile="${scope}"] {\n${body}\n}\n`155+ ? `:root[data-profile="${scope}"] {\n${body}\n}\n`150156 : '';151157 }152158 @@ -5,8 +5,10 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">66 <title>dev.site</title>77 <!-- Pico first, this site's few additions second, and the profile's own theme8- last. The theme block only ever assigns --pico-* variables, so landing last9- is all it needs to win: no user rule ever has to out-specify anything. -->8+ last. Last is necessary but not sufficient: Pico's palette sits on9+ :root:not([data-theme=dark]), which is specificity (0,2,0), so the theme10+ block matches it with :root[data-profile="…"] and wins on order. See11+ applyTheme in app.js. -->1012 <link rel="stylesheet" href="/vendor/pico.min.css">1113 <link rel="stylesheet" href="/site.css">1214 <style id="profile-theme"></style>@@ -117,10 +117,11 @@ body > main {
117117 .entries > li > button:is(:hover, :focus-visible) {118118 color: var(--pico-primary-hover);119119 }120-/* Links carry their host here. Services carry nothing: whether one is reachable121- * is not known until you ask it. */120+/* The right-hand column: where a link goes, or how a service is shared. Both are121+ * secondary to the name, so both take the muted colour rather than the body one. */122122 .entries .state {123123 white-space: nowrap;124+ color: var(--pico-muted-color);124125 }125126 126127 /* -- the viewer ------------------------------------------------------------ */