Close the frontend class inventory #28

Merged
pecheny merged 5 commits from class-inventory into main 2026-07-24 23:12:12 +00:00
Owner

Why

The Kit's Vocabulary is closed where Go emits markup, but 72% of the class names are written in TypeScript and core.css is shared by both apps — so no single module could check either half, and the two drifted. kind="primary" rendered as a plain .btn because the expander had no branch for it; 21 names were emitted with no rule behind them.

just class-check now walks all three stylesheets against every emitter and fails on a rule nothing emits or a name nothing styles. Everything else here is what it found, plus the two primitives the duplication motivated: isClipped (one definition of "this text does not fit") and bindScrollEdges (one listener, replacing seven copies).

Also fixes ОД Подробно, where the М header scrolled away while Команда and Σ stayed frozen.

🤖 Posted by Claude

# Why The Kit's Vocabulary is closed where Go emits markup, but 72% of the class names are written in TypeScript and `core.css` is shared by both apps — so no single module could check either half, and the two drifted. `kind="primary"` rendered as a plain `.btn` because the expander had no branch for it; 21 names were emitted with no rule behind them. `just class-check` now walks all three stylesheets against every emitter and fails on a rule nothing emits or a name nothing styles. Everything else here is what it found, plus the two primitives the duplication motivated: `isClipped` (one definition of "this text does not fit") and `bindScrollEdges` (one listener, replacing seven copies). Also fixes ОД Подробно, where the М header scrolled away while Команда and Σ stayed frozen. 🤖 *Posted by Claude*
The Kit's Vocabulary is closed where Go emits markup, but 72% of the class
names are written in TypeScript and core.css is shared by both apps, so no
single module can check either half — xy's dead-CSS test covers only the xy
layer. This walks all three stylesheets against every emitter and fails on a
rule nothing emits or a name nothing styles.

The two directions use different precision on purpose: an orphan-rule false
positive would have you delete live styling, so "emitted" is read generously
there, and strictly for dead names — token sequences in TypeScript, go/ast
nodes in Go. The composition rule is borrowed from xy's test, which is what
keeps the enum-built u-* utilities out of the report.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three justfiles each carried a copy of generate-check, all comparing the
regenerated file against HEAD. That conflates "you forgot to regenerate" with
"you regenerated but haven't committed yet", which makes pre-commit unusable
mid-change on any vocab edit. Compare against a snapshot of the file instead.

The root recipe also only checked the kit, but both app overlays generate from
the kit's vocab as well as their own — a kit edit leaves them stale too, which
is how a removed enum value reached a build failure rather than this check.

scripts/ holds two Go modules no module recipe reaches, so webbuild and
classcheck had no fmt or vet at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
button-kind declared four values; the expander handled three and the stylesheet
styled a fourth nothing emitted. So kind="primary" rendered as a plain .btn —
xy's «Подключить Trello» has never been blue — while .btn-secondary was emitted
with no rule anywhere. primary gets its branch; secondary leaves the enum,
since .btn alone is already what those buttons have always looked like.

gametopbar's `viewer` flag did nothing but add .viewer-header-main, which
nothing styles. Both go.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The class-inventory check found 21 names emitted with no rule and no selector
behind them, plus .divider styled with no emitter. Deleting a name that nothing
styles and nothing selects cannot change rendering, so they go.

Five of them were behaviour hooks wearing class syntax, which is why the dead
ones were unfindable: player-select, roster-team-cell, ksi-detailed-team-cell
and dope-unnumbered-banner move to data-* — a class is for styling, a data
attribute is for behaviour. entry-selection-anchor was neither; only the code
clearing it ever selected it, and the anchor already lives in entrySelection.

Two primitives absorb what five pages had been copying:

  isClipped        the one definition of "this text does not fit its box",
                   epsilon included. fest-grid reimplemented markNameOverflow
                   verbatim; host.ts inlined the same comparison.
  bindScrollEdges  one listener, one epsilon, one teardown. Seven copies of
                   classList.toggle(name, el.scrollLeft > 1), two of them
                   byte-identical, and host.ts bound the same class twice on
                   the same .sheet-frame with no way to unbind the second.

CSS follows: one .popover-inline replaces five positioning blocks that differed
only in a left offset, and the reveal/suppress triplet is written once instead
of five times. .fight-frame zeroes --sheet-corner-col rather than restating
four frozen-column offsets, and the mobile query stops re-stating rules that
already compute to the same values.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The М header scrolled away while Команда and Σ stayed put, because the rule
that un-froze the place column did it with `position: static`, which also
outranked `.match-table thead th` and took the header's stickiness with it.

Costs two visible question columns on a phone (249px frozen of 402).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
pecheny/dopesuite!28
No description provided.