diff --git a/plans/badges-codes/2026-08-21-badges-web-checkout.md b/plans/badges-codes/2026-08-21-badges-web-checkout.md index 38890ec133..8c5918323e 100644 --- a/plans/badges-codes/2026-08-21-badges-web-checkout.md +++ b/plans/badges-codes/2026-08-21-badges-web-checkout.md @@ -1510,6 +1510,8 @@ Append here when a step contradicts this plan: the step id, what was wrong, and - **D2 fix round 2 — "which rule comes last" is not the same question as "what does the element get", and only the second one is safe to assert.** The first logo test asserted that `.logo` is `display: block` and that one unconditional `.logo--dark { display: none }` exists. Both halves were true of a stylesheet that also carried an unconditional `.logo--light { display: none }` — which shows **zero** logos in light mode — and the whole suite passed with that rule added. The fix was the *outcome*: `test/css.mjs` now resolves the cascade (`effectiveValue`) — applicable rules filtered by their media conditions, ordered by specificity and then document position — and the test asserts that in each scheme exactly one of the two `` elements resolves to a `display` other than `none`. Three mutations now fail it, one per way of getting it wrong: zero logos, the wrong logo, both logos. **A check phrased over the rules can only catch the mistakes you thought of; a check phrased over the result catches the ones you did not.** This is the third time in this step that the shape of a verification claim, rather than its content, was the defect — see also the browser-list ruling above. - **D2 fix round 3 — the cascade resolver now refuses input it cannot model, instead of documenting a precondition and hoping.** `effectiveValue` ordered candidates by specificity and document position and had no concept of `!important`, while its docstring named "no `!important`, no inline styles, no layers" as a *precondition on the caller*. An early unconditional `.logo--dark { display: block !important }` — which in a browser shows two logos in light mode, since an important normal declaration outranks every plain one whatever the order — was resolved to `none`, and the whole suite passed. A partial resolver that silently drops what it cannot read is worse than the narrow check it replaced: the narrow one was incomplete, this one was confidently wrong. It now **throws** on `!important` in any matched declaration, on `@layer` anywhere in the sheet (layer order is sheet-wide, so that check is global rather than per-candidate), on any at-rule or media condition outside the four it models, and on a selector it cannot parse that could nonetheless match the element — a combinator or pseudo-class naming one of the element's classes, its tag, or `*`. The "inline styles" clause was dropped as vacuous: a stylesheet has none. **The rule this leaves for anyone extending `test/css.mjs`: a helper's guarantee belongs in its code, not in a comment asking callers to be careful.** The same reasoning as the `!important` case applies to any future cascade feature — refusing to answer is always available, and is never the wrong answer. + **Two silent-wrong paths remain in `declaration()`, both unreachable in today's `styles.css` and both older than this round.** The round-3 re-review probed for what the four refusals still miss and found exactly two. First, `declaration()`'s regex carries no `/g` flag, so a property declared **twice in one rule block** returns the *first* match where the cascade takes the last. Second, a `display: var(--x)` indirection is returned as the literal string `var(--x)`, and because the logo test only asks whether the value differs from `none`, an unresolved custom property would count as visible whatever it resolves to. Neither is reachable now — no rule block in `styles.css` declares a property twice, and no `display` anywhere uses `var()` — so neither was fixed, on the same "unreachable is not a defect" ground the plan applies elsewhere. They are recorded because the guard against them is a property of the *stylesheet*, not of the helper: whoever adds a duplicated declaration or a `var()`-valued `display` re-opens them, and will get a confidently wrong answer with a green suite, which is precisely what rounds 2 and 3 were spent eliminating. + ## 10. End-to-end verification After F5: