Paying down #2962 measured `flag="unused-css"` against a hand audit and
found it had a permanent false-positive floor: roughly sixty classes it
called unused are alive and always would be, because two ordinary authoring
forms produce names no reader of `class=` attributes can see. A flag whose
list you cannot act on line by line is worse than no flag — act on it and
you delete live UI.
- A transition `name=` IS a class reference. `<Transition name="toast">`
makes Vue apply `.toast-enter-active` and its siblings at runtime, and
React's `<CSSTransition classNames="fade">` does the same with a different
suffix set. Every spelling of the tag is read (`Transition`,
`TransitionGroup`, `transition-group`), and the emitted suffix set is the
union of Vue 3, Vue 2 and React: naming a class no rule defines costs
nothing, since it resolves to no row. A bound `:name` stays unknowable.
- A concatenated name contributes its static head as a PREFIX reference.
`` `status-${s}` ``, `'pri-' + p` and `class="card-{{ v }}"` all leave a
head behind once the hole is blanked — and `_CLASS_TOKEN_RE` accepts a
trailing hyphen, so until now the extractor emitted a junk token
`"status-"` that matched nothing. It is now `status-*`, and
resolve_consumers credits every row whose symbol starts with that head,
each under the same own-file-else-fan-out rule as an exact token. `*`
cannot occur in a class token, so the marker rides the existing
dict[str, int] with no schema change. A head shorter than two characters
says nothing and is dropped.
Crediting every candidate row is the honest reading: the template genuinely
does not say which one it built, and the alternative is reporting live rules
as dead. What the map still cannot see is a name assembled in a script —
`classList.add` — which stays deliberately out of scope; the skill, the
`flag=` docstring and the refresh payload docs all say so now.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>