docs: record the semantic palette landing, and one claim that was false
Build & publish Arch package / arch-package (push) Successful in 2m9s
CI / check (push) Successful in 2m31s
Search index maintenance / maintain-index (push) Successful in 6s
CI / e2e (push) Successful in 5m22s

The two findings recorded as too big for the contrast pass are fixed, so
the plan says so. Also corrects a claim I made and did not check: the
chrome does not stay dark under the light ramp -- that screenshot was
taken before the theme propagated, which is the third time in two passes
a picture read at the wrong moment produced a confident wrong claim.
This commit is contained in:
2026-08-13 01:08:17 -04:00
parent 0b7ffd5679
commit 7410109884
3 changed files with 145 additions and 19 deletions
+74
View File
@@ -1954,3 +1954,77 @@ Nine things worth keeping:
and every one was a *different* mechanism. A finding at 110 nodes
hides them; at 3 they are individually obvious. Cheap tail, only
reachable from the other side of the main fix.
## A role, not a value, decides whether a colour can be fixed
Plan 008 phase 2, third landing: the two findings the contrast pass had
recorded as too big to fix in it, fixed — plus the check for the trap
that has now cost four sessions.
The generalisation: **a token that is used for two different jobs will
be wrong at one of them, and no amount of choosing a better value
fixes it.** `--yj-error` was "the colour of error", which is two
questions. As the *background of a danger button* it wants to stay red
in every theme, and it does. As *the word `failed` on a surface* it
cannot be one value at all — a single colour cannot clear 4.5:1 against
both a near-black and a near-white background, which is why the fixed
set measured 2.314.28:1 on nearly every combination. Splitting the
token by the question it answers made both answerable; picking better
hexes never would have.
The same shape twice more in the same landing:
- **A fill's foreground cannot be written down**, because the accent is
a colour picker. `color: #000` is right for the current default
yellow and wrong for a navy one. `readableOn()` computes it — white
where white clears 4.5:1, black otherwise — which keeps a red danger
button conventional (4.51:1) and flips a green one (3.45:1).
- **`var(--yj-bg-base)` as a foreground is a token used for the wrong
meaning.** Two accent buttons did that. It reads as "the opposite of
the accent" and it is not: it inverts with the ramp, so the light
theme rendered white on yellow at 1.43:1. The bug is not the value,
it is that the *name* did not mean what the call site needed.
Six more things worth keeping:
- **The picture and the number disagreed, and the number was mine —
again.** I recorded "the header and player chrome stay dark while the
body goes light" as a finding, in the plan and in these notes. It is
false: `.top-bar` is `#e9ecef` and `.sidebar` `#f8f9fa` under the
light ramp, and a re-taken screenshot agrees with the DOM. The
original was captured before the theme had propagated. That is the
third time in two passes a screenshot read at the wrong *moment*
produced a confident wrong claim, after a spec reading the DOM before
a fetch and a sweep that had moved the app to another ramp. **A
screenshot has a timestamp and a state; check both before quoting
it.**
- **A `color:` regex matches `border-color:`.** Twice in one landing —
3 borders while rewriting semantic text, then 30 more while rewriting
accent text. Both caught by grepping the *result*, not by any test,
because a border in the wrong shade fails nothing and looks fine.
When a mechanical rewrite is the right tool, the review is a grep of
what it did, not a run of the suite.
- **A fix at the ubiquitous case makes the rare ones findable.** The
greyscale fix took the dark ramp to 0 and the light ramp from 50 to a
list short enough to read individually — at which point every
remaining item was a *different* mechanism, and two of them were the
findings above. A queue of 110 hides its own structure.
- **Knowledge that has been ignored three times is not a knowledge
problem.** The backtick-in-a-`css`-comment trap is documented in
`CLAUDE.md`, in the skill and here; I read it twice in the session it
then cost a cycle in. It is `make css-check` now — a pre-commit hook
and a CI step. The detection is exact rather than heuristic: if a
backtick in a comment closed the literal early, the text the parser
took as the literal contains an unterminated `/*`, and nothing else
produces that.
- **The value of that check is the sentence, not the failure.** `tsc`
already failed on it — with `Class static side 'typeof NowPlaying'
incorrectly extends base class static side` and `Property 'scroll'
does not exist on type 'CSSResult'`, pointing at a line of prose. The
check was verified by breaking a file on purpose and reading both
reports side by side, which is also the only way to know it fires.
- **A change can be invisible to the tier that looks the most like it
covers it.** `make ui-visual` passed unchanged through a whole
palette rewrite, twice, because the component tier has no `:root` and
renders the fallbacks. The tier that *did* catch things was a unit
test over the palette table and a probe against the running app.
+54 -19
View File
@@ -325,23 +325,60 @@ access to any value by not being able to. **Stays dropped.**
#### Two things the measurements found that are not in the audit
Both are bigger than what they were found under, and neither is fixed:
Both were bigger than what they were found under. Both are now fixed
see the third landing below.
- **The semantic colours are fixed across ramps, and a fixed colour
- **The semantic colours were fixed across ramps, and a fixed colour
cannot serve a near-black and a near-white background.** `--yj-error`
is 3.42:1 on dark's surface and 2.55:1 on its elevated; `--yj-info`
is 3.10:1 and 2.31:1; success and warning fail on dark and light
both. As *backgrounds* under white text, success (3.45) and warning
(3.58) fail too. The fix is a per-ramp semantic palette, which is a
decision about the app's colour identity rather than a value.
- **The light ramp is not a supported theme.** With the greyscale fixed
it still has **50 failing nodes**: the accent yellow under white text
(1.43:1), the autotag diff's pale greens and reds on white
(1.362.59:1), and the header and player chrome staying dark while
the body goes light. Read in a screenshot — the "Low confidence pick"
banner is invisible and the primary button is white-on-yellow. This
is a design job, and the honest question it raises is whether the
light theme should ship at all in its current state.
measured 3.42:1 on dark's surface and 2.55:1 on its elevated;
`--yj-info` 3.10:1 and 2.31:1; success and warning failed on dark and
light both. As *backgrounds* under white text, success (3.45) and
warning (3.58) failed too.
- **The light ramp was not a usable theme.** With the greyscale fixed
it still had **50 failing nodes**: the accent yellow under white text
(1.43:1) and the autotag diff's pale greens and reds on white
(1.362.59:1).
### Phase 2, third landing — the ramp reaches the semantic colours
**2237 nodes across three ramps and twelve views, 0 failing.**
The split is by the question a colour answers. A **fill** is "what
colour is a danger button" — red in every theme, unchanged. A **text**
colour is "what colour is the word *failed* on this background" — per
ramp, because one value cannot clear 4.5:1 against both a near-black and
a near-white surface. `bgOverlay` keeps the exception it already had on
the dark ramp.
And every fill now carries a **computed foreground**, because the accent
is a colour picker and no fixed answer survives one: white if white
clears 4.5:1, else black. That keeps a red danger button white and
flips a green or amber one to black. Accent-as-text goes through
`accentTextOn()`, which mixes along the hue until it clears the ramp's
surface and stops — returning the accent *unchanged* on both dark
ramps, so the dark themes are visually untouched by that half.
`make ui-test` 608 → **649**.
#### Where this pass was wrong
- **"The chrome stays dark while the body goes light" was mine, and it
was false.** I read it off a screenshot; the DOM says `.top-bar` is
`#e9ecef` and `.sidebar` `#f8f9fa` under the light ramp, and a
re-taken screenshot agrees. The first one was captured before the
theme had propagated. Third time in two passes that a screenshot read
at the wrong moment produced a confident wrong claim — and the second
time this pass that **the picture and the number disagreed and the
number was mine**.
- **A `color:` regex matches `border-color:`.** Twice: once rewriting
semantic text colours (3 borders) and once rewriting accent text (30
more). A border is a fill, not text. Caught by grepping the result
rather than by any test, because nothing renders differently enough
to fail.
- **Two accent buttons took their foreground from `--yj-bg-base`**,
which inverts with the ramp — white on yellow at 1.43:1. That is not
a colour that was chosen badly; it is a token used for the wrong
meaning, and it only shows up in the theme nobody looks at.
---
@@ -358,10 +395,8 @@ Two of them are not one-liners and should be treated as such:
minimum at 800×600. Reflow at high zoom is the same question one
variable over. It may want its own landing.
- **The unnamed `<select>`s** from Phase 1, with `26`.
- **The semantic palette** and **the light ramp**, from Phase 2. Both
are larger than the rest of this tail put together and may not belong
in it at all — the light ramp in particular is a question about
whether that theme ships, not a contrast fix.
- ~~**The semantic palette** and **the light ramp**~~ — both landed in
Phase 2's third pass rather than waiting for this tail.
- **`22`** asks for a non-colour marker on the playing row, which is a
visual change to the densest list in the app and moves a baseline.
+17
View File
@@ -505,6 +505,23 @@ first track arrives) and `job-indicator`, whose label swings between
"Scanning Music", "3 background jobs" and "Finished". The notification
surface already had one from Phase 3.
**A colour's role decides whether it can be fixed across themes.** A
*fill* — `--yj-error`, `--yj-success` — is "what colour is a danger
button", which is red in every theme and stays fixed. A *text* colour
— `--yj-error-text` — is "what colour is the word *failed* on this
background", which cannot be: one value cannot clear 4.5:1 against both
a near-black and a near-white surface, and the old fixed set measured
2.314.28:1 on nearly all of them. And every fill carries a **computed**
foreground (`--yj-accent-fg`, `--yj-*-fg`) rather than a written-down
one, because the accent is a colour picker: white on the default
`#ffd43b` is 1.43:1. `readableOn()` keeps white where white clears and
flips to black where it does not, and `accentTextOn()` mixes the accent
along its own hue until it clears the ramp's surface — returning it
unchanged on both dark ramps. Two accent buttons used to take their
foreground from `--yj-bg-base`, which *inverts with the ramp*; that is a
token used for the wrong meaning, and it only shows in the theme nobody
looks at.
**Contrast is a property of the ramp, and the ramps are data.**
`theme-store`'s `SHADE_PALETTES` — not `tokens.css.ts`, which holds only
the type scale and icon sizes — is where the colours live, applied to