fix(css): two cascade-flip bugs found by specificity audit (#433)

* fix(css): two cascade-flip bugs found by specificity audit

PR #431 stripped [data-design="v1"] from ~400 rules, dropping each by a
specificity tier; two cascade flips (#header outranking .appbar, #header h1
outranking .appbar-title) were caught visually during that PR's review and
fixed by renaming id="header" → id="ui-header" on the per-node UI page.
This is the audit follow-up; it found two more:

- textarea.skill-content-area (was .skill-content-area) — bumped to (0,1,1)
  so the rule ties with `.admin-modal textarea` (0,1,1) and wins on source
  order. Without the bump, min-height: 220px was clobbered to 40px by the
  modal default and the spec-content textarea rendered short. The three
  !important markers (font-family/size/line-height) are now redundant
  against the modal's font: inherit shorthand and are dropped.

- h3.skill-spec-heading — removed `font-size: inherit;`. The author wrote
  it to "reset UA defaults" but it locked font-size to the parent's
  (~14-16px) at (0,1,1), silently overriding `.skill-spec-heading`'s 10px
  at (0,1,0). The bare class already beats UA `h3` on specificity (class >
  tag), so no font-size reset was needed; the `margin-block: 0` line stays
  because the bare class's `margin: 14px 0 6px` shorthand may not reset
  the UA's logical margin-block-start/end on every engine.

Adds scripts/css_specificity_audit.py — the audit tool. It parses every
CSS file referenced from the project's three HTML entry points, computes
selector specificity (incl. :not/:is/:has math, attribute selectors, and
!important), and flags every place an unscoped legacy rule could outrank
a bare-class designed primitive. Honours per-page stylesheet manifests,
state-pseudo subset gating (a `:hover` rule overriding a resting-state
base rule is intentional, not a flip), and shorthand→longhand expansion
for font/padding/margin/border/background. Triage of remaining findings
(26 id-tier in default mode, 74 total at --all-tiers) confirmed all are
intentional designer overrides — id-scoped buttons, BEM modifier classes,
contextual ancestor selectors, last-child margin reset, [hidden] toggle.

* fix(css-audit): correct two cascade-resolution bugs flagged by Copilot

1. _parse_declarations dict insertion order didn't update on overwrite, so
   a sequence like `font-size: 13px; font: inherit; font-size: 12px;` would
   iterate as (font-size=12px, font=inherit) and the shorthand expansion
   then clobbered font-size back to `inherit` — wrong.  Delete-then-insert
   on overwrite so the last occurrence lands at the dict's tail and the
   shorthand expansion sees the real source order.

2. The cascade-winner tie-break used `rule.line_no` only, ignoring the
   stylesheet load order.  A rule at line 1000 of `base.css` looked
   "later" than a rule at line 50 of `style.css`, even though the page
   loads `base.css` BEFORE `style.css`.  Sort by `(file_index, line_no)`
   keyed off the element's per-page stylesheet manifest instead.
This commit is contained in:
Patrick Buckley
2026-04-27 20:05:25 -07:00
committed by GitHub
parent 83a97ba485
commit 4b5edce8c5
2 changed files with 1339 additions and 6 deletions
File diff suppressed because it is too large Load Diff
+13 -6
View File
@@ -2128,9 +2128,13 @@
margin-bottom: 0;
}
/* h3 used for screen-reader heading structure; reset UA defaults */
/* h3 used for screen-reader heading structure; reset UA defaults. We only
reset margin-block (the `margin` shorthand on .skill-spec-heading sets
physical margins but UA h3 uses logical margin-block-start/end); font-size
is left alone — .skill-spec-heading's font-size: 10px already beats the UA
h3 default on specificity, and an `inherit` here at (0,1,1) would silently
outrank the bare class at (0,1,0). */
h3.skill-spec-heading {
font-size: inherit;
margin-block: 0;
}
.skill-spec-heading {
@@ -2161,12 +2165,15 @@ h3.skill-spec-heading {
flex-direction: column;
}
.skill-content-area {
/* Chained `textarea.` so the rule beats `.admin-modal textarea` (0,1,1) on
source order — without that bump, min-height: 220px loses to the modal's
default min-height: 40px and the spec content textarea renders short. */
textarea.skill-content-area {
flex: 1;
min-height: 220px;
font-family: var(--font-mono) !important;
font-size: 11.5px !important;
line-height: 1.65 !important;
font-family: var(--font-mono);
font-size: 11.5px;
line-height: 1.65;
}
.skill-vars-row {