Rubric v1.4: two scoping rules — does this defect have a victim?
Added after the Indicator audit produced two findings that graded
correctly against the rule text and should never have been recorded.
Both failures were mine to catch and I did not; the TL did.
1. MIGRATION RULES BIND RELEASED SURFACE ONLY.
Q11, T33, P11, X20 and I7 all demand a [breaking] changeset and a
codemod. Every one exists to protect installed consumers, and not one
said so. Indicator merged 2026-08-11; core@0.3.0 shipped 2026-08-05 with
no ./Indicator subpath, so the whole layer was unreleased for its first
week in main -- and P11 was cited against it for omitting props from a
type no consumer could install. Both the auditor and I cited the rule
accurately and neither asked whether the export had ever shipped.
The rules now require establishing that first, with the command.
P11 also drops to FIX in modes N/O. Its subject is a CHANGESET, which is
consumed and deleted at release, so a BLOCK derived from one evaporates
on its own with nobody fixing anything and the next audit sees an
unexplained jump. That belongs on the PR, where it can be acted on before
merge -- which is what L14 already does, and is the model.
2. A FINDING NEEDS A REACHABLE VICTIM.
"Score the failure, not its likelihood" is right and stays. It is about
USERS in rare states. It does not license grading a defect nothing can
reach. A defect needing code the type rejects, that no call site contains
and no doc suggests, is a NIT at most.
The worked example is tabIndex on an indicator: axe really does report
aria-hidden-focus, measured 0 -> 1 violation, which reads like a solid
finding -- but nobody passes it, the type rejects the literal, and no doc
mentions it. Recorded as a FIX, it produced a PR carrying a new internal
module to strip a prop nobody sets. Corollary added: a finding closable
by a type change should say so, instead of leaving the fix shape to
whoever picks it up.
Minor, not patch: both narrow when a BLOCK may be recorded, so §2, §3,
§8 and §9 scores taken under <=1.3 are not comparable where a migration
rule was cited. Weights, floors and ceilings unchanged.
Rubric: unbreak the §2 and §5 tables
A blockquote in the middle of a markdown table ends the table: the rows
after it rendered as a paragraph of literal pipe text. The T1-exceptions
and T14-scoping notes split §2 into three pieces (T2-T14 and T15-T34 both
raw), and the D12 note split §5 (D13-D16 raw).
Each table is now contiguous, with the notes moved directly below it —
the shape §9 already used. Rows are unchanged apart from T2b's pointer,
which now says the T1 note is below rather than above.
Rubric v1.3: add X21 — a doc example may not teach a pattern we removed
The Indicator audit surfaced a finding with no rule to hold it. #4913
replaced `{children ?? mark}` in all three shipped indicators because it
deletes the state mark on a falsy child (#4893), and left the ONLY worked
example of writing a replacement indicator teaching exactly that shape.
Code fixed, documentation still instructing readers to rebuild the bug --
on a surface X5a's own rationale calls LLM training signal.
The auditor who found it filed it under X5. That citation is wrong: X5a is
a phantom prop, X5b is a missing prop entry, and this is neither. I
overruled the citation and recorded a FIX, which left a BLOCK-severity
defect scored as cosmetic. The real defect was in the rubric: no rule
covered it, so it had nowhere to go but a rule that did not fit.
X21 (§8, BLOCK) closes that gap, scoped so it cannot become a
style cudgel: the test is whether following the example re-creates a
defect the repo has already paid to remove -- not whether the example is
simplified, and not taste. Detection is a grep of the component's
.doc.mjs for the shape just removed, run as part of any behavioral fix.
Minor bump, not patch: a new check that can BLOCK changes what counts, so
§8 scores taken under <=1.2.1 are not comparable. No weight, floor or
ceiling moved, and no other section is affected.
Component-Audit-Rubric: correct T34 to the indicator convention we shipped
The host renders the indicator unconditionally and passes state; an
indicator may legitimately draw nothing in a state. The focus ring is
the owner's to guarantee -- it is painted onto the indicator's own
element, not omitted. Radius is tokenized (--radius-full, not 50%).
Rubric v1.2.1 + API Conventions: correct T28 (composition-layer theme targets) and P33 (html prefix)
Both from the AlertDialog audit review, PR #4887.
T28 read as a blanket ban on themeProps anywhere a composition wrapper
wraps themed components. That is not what #672 and #749 established:
#672 deliberately ADDED a target to MoreMenu over DropdownMenu so themes
could reach it, and #749 skipped Tokenizer, an outer node wrapping
several independently themed components. T28 now says that: a layer may
name its own concept on the painting element; the ban is on the
multi-component wrapper node.
P33 led with the html prefix. The prefix is for props that ARE the
native attribute (htmlName, htmlFor); a prop that merely reuses the name
with a different meaning keeps the semantic name. BaseProps already
omits title, so a component's own title prop is not a collision at all.
API Conventions §HTML Attribute Collisions rewritten to match, since it
owns the rule.
Ledger tooling and the scores page are on main; activate T34
State visuals: scale(0.98) is the button press treatment; match the archetype before the representation
Testing: compare visuals against Design Conventions; missing tooling is not a deduction
D12: link the view-transitions tracking issue
D12: view transitions are the resolution path, blocked on React stable
D12 applies on every render path; only T14's DOM-shape rule is scoped
D12: explain what the guard protects; the auto-height exception is permanent
T14: scope the style-only-wrapper rule to the main render path
D12: scope the layout-animation rule to the main render path; sanction the auto-height disclosure
Resolve the issue-filing contradiction: the nightly pass files none, a grading or promotion does
Repoint two more pointers at the sections that exist
Rubric: a row citing the rubric itself says nothing — name the real source or none
Fix every piped wiki link: GitHub treats the text after the pipe as the page name
Restore the Layer 3 design review and its form; the rubric still requires the form be archived
Delete the superseded hardening and auditor pages; repoint every link at the rubric
Ledger: the write path is a hand edit — no score-ledger tool or scores page exists yet
Component auditor: one component a night, audit-fix-reaudit, ledger as the only state, no issues
Rubric: cite the page that owns each rule, not the auditor's deleted section numbers
Wiki-wide: drop the mode-letter shorthand for plain language
Ledger: the JSON is the only stored form; delete the generated table
Component-Scores.md was a second copy of component-scores.json and went stale
the moment the JSON changed without a regeneration. The ledger now has exactly
one stored form (the JSON) and exactly one view (the sandbox page), which
fetches it at runtime and can therefore never be out of date.
Every page that linked [[Component Scores]] now links the sandbox page.
Rubric: lead with what you are doing; plain language; act as the hub for the rule pages
Ledger: nothing in CI reads it while the rubric is being calibrated
Document the ledger write path end to end
Mode R: set the bar by change type — bug fix, feature, new component (core vs lab)
Mode R: derive a small check set from the diff; depth follows the existing triage