Skip to content

Commit fcc81fe

Browse files
committed
fix(lcars): choose the glyph against the disc it sits on
LCARS fills an active part with --liebe-part-color and declared a fixed dark glyph on it. For the two data-driven paths - a bulb's own RGB, a person's identity colour - that disc is a value no theme has seen, so the glyph was whatever the data made it: 2.44:1 for a pure-blue bulb against the 3:1 floor, 3.90:1 for the worst identity hue against the 4.5:1 an avatar's initials answer to as text. The task carried a question whose answer decides where it lives, and it is the first branch: --liebe-part-color is published, already carries the live value, and is already what this theme reads for the disc, so a theme can DERIVE the foreground even though no token holds one. My earlier read that all three published tokens hold author-chosen colours was right about the tokens and wrong about the conclusion - the question was whether one carries enough to compute from. 0036 is not reopened. contrast-color() returns black or white, whichever contrasts more, so it can only equal or beat the fixed black. Measured: bulb 2.44 -> 5.25, worst identity hue 3.90 -> 4.75, all 126 live-hue composites clear. The ten triplet glyphs are byte-identical (worst 5.05) because the function picks the same black for every colour the theme calibrated. Beneath it, a relative-colour rung for browsers with that syntax but not the function. Swept at five thresholds; the best still leaves one identity hue at 4.42:1, because no single Oklch lightness cut reproduces contrast-color() for chromatic colours - L does not track WCAG luminance across hues. It takes those browsers from six failing composites to one: a narrowing, recorded as such rather than presented as a closure. 0035 stays draft. PR 6 is ticked and a new PR 7 records what this could not settle - the icon-only tile composite, where a reviewer's ~2.33:1 and a synthetic harness's 6.81-10.80:1 are BOTH unverified against how GridCard delivers hueStyle to that tile, and the rung's residual.
1 parent 2af4cb4 commit fcc81fe

4 files changed

Lines changed: 102 additions & 3 deletions

File tree

docs/changes/0035-light-appearance-contrast.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,22 @@ PR 6 is PR 4's own residue and depends on nothing: it was measured in the same r
7979
- [x] **PR 3 — Residual accessible names**: name `InputSelectCard`'s select trigger and the switch `InputBooleanCard` renders in its own tile at `controlStyle: switch` (**not** the detail-controls switch, which is already named); re-run the `button-name` audit across every story in both appearances and record the resulting count; drop #191 from the outstanding list
8080
- [x] **PR 4 — The live hue on its own tint**: decide and implement what the glyph takes when a data-driven hue overrides the triplet — the light card's bulb colour under `useLightColor` and the person avatar's identity colour, one mechanism in `hueStyle` (`src/components/anatomy/anatomyPart.ts`) rather than two card defects; measure both against their floors (the avatar's initials are text at 4.5:1, not a glyph at 3:1) and move [options/light](../specs/entity-cards/options/light.md) and [options/person](../specs/entity-cards/options/person.md) with whatever is decided
8181
- [x] **PR 5 — The dark-appearance glyph on `media`**: `indigo-9` on its own 20% tint over the dark card measures **2.81:1**, under the 3:1 glyph floor. **It was not the only one, and that is the correction this PR carries**: the figure was Default's, and re-measured across every built-in theme in dark, Liquid Glass misses on three — `media` 2.17:1, `alert` 2.47:1, `ok` 2.89:1 — while Default misses on `media` alone (2.80:1 re-measured, next worst `alert` 3.63:1). LCARS clears everywhere, worst 5.05:1, because it owns its icon treatment. Found while implementing [0033](./0033-icon-only-cards.md)'s icon-only tile tint, which reproduces this pattern's exact composite at tile scale and so inherits the figure rather than introducing it. It contradicts this change's Out-of-Scope claim that "all four defects pass in dark", which was made about the light-appearance repin and never measured per hue in dark; resolving this task MUST correct that line as well. Resolved by extending PR 2's own answer rather than inventing a second one: the glyph takes the `-text` token in **both** appearances, which retires the per-appearance switch and the `--part-<name>-glyph` indirection with it, so `anatomy.css` now keys nothing off the appearance at all. All 50 hue×theme×appearance composites clear, worst 3.68:1 and that one in light; light and LCARS are unchanged to the decoded pixel.
82-
- [ ] **PR 6 — LCARS puts a fixed dark glyph on a live hue**: the theme declares `color` on the active circle itself and reads no role token, so PR 4's split does not reach it; its treatment is a solid disc of the hue rather than a 20% tint, and the fixed dark glyph on it measures **2.44:1** for a pure-blue bulb against the 3:1 glyph floor and **3.90:1** for the worst of the eight identity hues against the 4.5:1 the avatar's initials answer to (violet 3.90, iris 3.91, purple 4.06, plum 4.42; the other four clear). Found while measuring PR 4, on the same rig and in the same run, and unchanged by it — before and after are byte-identical for every LCARS cell. It is here rather than in [0036](./0036-theming-contract-gaps.md) because it is a contrast floor missed, not a theming hook missing; what it may need from 0036 is a hook, which is the decision this task carries. Note the shape of the problem before reaching for a repin: the failing ground is **data**, so there is no value a theme can pin that answers for every bulb colour a user's lamp can report. LCARS is dark-only, so this is a dark-appearance figure throughout.
82+
- [x] **PR 6 — LCARS puts a fixed dark glyph on a live hue**: the theme declares `color` on the active circle itself and reads no role token, so PR 4's split does not reach it; its treatment is a solid disc of the hue rather than a 20% tint, and the fixed dark glyph on it measures **2.44:1** for a pure-blue bulb against the 3:1 glyph floor and **3.90:1** for the worst of the eight identity hues against the 4.5:1 the avatar's initials answer to (violet 3.90, iris 3.91, purple 4.06, plum 4.42; the other four clear). Found while measuring PR 4, on the same rig and in the same run, and unchanged by it — before and after are byte-identical for every LCARS cell. It is here rather than in [0036](./0036-theming-contract-gaps.md) because it is a contrast floor missed, not a theming hook missing; what it may need from 0036 is a hook, which is the decision this task carries. Note the shape of the problem before reaching for a repin: the failing ground is **data**, so there is no value a theme can pin that answers for every bulb colour a user's lamp can report. LCARS is dark-only, so this is a dark-appearance figure throughout.
8383

8484
**PR 5's sweep narrows this usefully: LCARS's ten _triplet_ glyphs all clear in dark, worst 5.05:1** (`vacuum`; next `alert` 5.48:1), measured on the same rig in the same run. So the theme's solid-disc treatment is sound for every colour it was calibrated against, and the defect is confined to the **live-hue** path — a hue the theme never saw. That is evidence for the reading that no repin can fix it, since the failing input is data rather than a palette entry, and it means PR 6 need not revisit the triplet treatment at all.
8585

8686
**Start from [0036](./0036-theming-contract-gaps.md) PR 3's published tokens, not from the assumption that a hook is missing.** That PR landed `--liebe-part-color`, `--liebe-part-label` and `--liebe-part-label-active` precisely so a theme filling a part with a saturated hue can name the foreground on it — which is this theme, this composite, and the reason LCARS's own restatement of the domain-colour mapping was retired. The likely fix is therefore LCARS **consuming** finished work rather than 0036 owing more, and this task belongs here because it is a measured floor missed and 0035 owns whether a composite clears its floor. Read those three tokens before concluding otherwise. **The condition under which this moves:** if no published token can express "a solid disc of a **live** hue needs a foreground chosen against _that hue_" — the value being data, not a colour a theme can pick in advance — then the gap is in the contract rather than in LCARS, and that finding reopens [0036](./0036-theming-contract-gaps.md) and takes this task with it. Record which of the two it turned out to be either way.
8787

88+
**It turned out to be the first: the contract needed nothing added.** `--liebe-part-color` is published, already carries the live value, and is already what this theme reads for the disc — so a theme can _derive_ the foreground from it even though no token _holds_ one. `contrast-color(var(--liebe-part-color))` returns black or white, whichever contrasts more, so it can only equal or beat the fixed black it replaced. My earlier read — that all three published tokens hold colours a theme author chooses, none a function of a runtime value — was **right about the tokens and wrong about the conclusion**: the question was never whether a token _holds_ the answer, but whether one carries enough for a theme to compute it. **0036 is not reopened.** Measured: a pure-blue bulb 2.44:1 → **5.25:1**, the worst identity hue 3.90:1 → **4.75:1**, all 126 live-hue composites clear, and LCARS's ten triplet glyphs byte-identical (worst 5.05:1) because the function picks the same black for every colour the theme calibrated. Behind `@supports`, so a browser without the function keeps today's treatment.
89+
90+
- [ ] **PR 7 — The LCARS icon-only tile, and the fallback rung's residual**: two things PR 6 could not settle, both recorded as uncertainty rather than as findings.
91+
92+
**The tile composite is unverified in both directions.** `codex` review of PR 6 read `.liebe-card[data-icon-tile]:not([data-domain='person']) .liebe-icon[data-active] { color: var(--liebe-part-color) }` as leaving a live-hue glyph at ~**2.33:1** on its own tint. A synthetic harness built for that composite measured **6.81–10.80:1** instead — but for a reason that invalidates the harness rather than the claim: the glyph painted `#edb378`, because `--liebe-part-color` **resolved to LCARS's own colour rather than the hue set inline**. This theme re-declares that token _on the part_, which beats a value inherited from the card — and the card is where `GridCard` puts `hueStyle` for an icon-only tile. So the harness may have measured a composite that does not occur, and **neither figure is trustworthy**. This is the artifact-identity trap one level up: the rig was faithful to the stylesheets and not to how the DOM is actually assembled. Settling it needs a **real-story or e2e** measurement of a live-hue icon-only tile under LCARS, not another synthetic pass — do not rebuild the harness and trust it.
93+
94+
**The fallback rung does not fully close the floor.** PR 6's middle rung (relative-colour syntax, for browsers with it but without `contrast-color()`) was swept at five thresholds; the best leaves one of the eight identity hues at **4.42:1** against the 4.5:1 text floor, because no single Oklch lightness cut reproduces `contrast-color()` for chromatic colours — L does not track WCAG relative luminance across hues. It takes that browser set from six failing composites to one, which is a narrowing and not a closure. Options for closing it: a per-hue exception, a computed foreground supplied by the card rather than the theme, or accepting the residual and recording it.
95+
96+
**`docs/PRD.md` states no browser floor** — "Modern browsers only (Chrome, Firefox, Safari, Edge)", with no version, no `browserslist` and no Vite target — so how much either rung matters cannot be evaluated. Whoever takes this should settle that first; a progressive-enhancement decision has no meaning against an unstated baseline.
97+
8898
## Out of Scope
8999

90100
- **Slider thumb names** — already fixed ([#192](https://github.com/fx/liebe/issues/192), closed).

0 commit comments

Comments
 (0)