Commit graph

3 commits

Author SHA1 Message Date
Doxa Maps
38e36e7de4 Fold the dangling-selector gate into the portedGlobals migration
One sha instead of two, per the shepherd: both branches fixed the SAME
regions-list rule, because a gate is useless on a tree where its poison case is
still live. Landing them separately would have applied that fix twice.

★ I HAD CLAIMED THE TWO COPIES WERE IDENTICAL. They were not — I checked before
folding rather than after, and the hashes differed. The CODE line is the same in
both (`cyrillicHeading('.rl-title')`, one occurrence each); only the explanatory
comment differed. I kept the css-dangling wording because it names the arm that
now catches the bug, which is what the next reader needs. The emitted CSS is
unchanged either way — comments are not CSS — and the byte-compare baseline still
passes, which is the assertion that would have caught me if I were wrong.

WHAT THIS SHA CARRIES, both halves verified after combining rather than assumed:

  THE MIGRATION (was 4a4bc96fc). The harness stub's `.value` is now the empty
  string, so an injected-config read and a locale interpolation both behave as
  they do in the browser. Measured components 2 -> 4, no NOT MEASURED left.
  region-dashboards is byte-identical (a pure de-duplication); regions-list
  changed because its old bytes were the bug, so its baseline was re-taken.
  strongMedium() deliberately NOT adopted — the helper emits bare `strong{...}`
  where region-dashboards scopes `.rd-root strong{...}`, and swapping would widen
  the rule.

  THE GATE (was 023ca2d08). check:shadow-css-parses now fails on a selector that
  never receives a block, gated on `endsLiteral` — the LAST fragment — rather than
  on `complete`, which is false for nearly every sheet here and therefore
  suppressed the very bug the arm was written for. Four self-tests live in the
  file: the exact regions-list string is caught, a last rule WITH a block is not,
  a trailing comment is not, whitespace is not.

Re-verified on the combined tree: ported-globals 4/4 byte-identical, shadow-css
80/80, the poison case still produces exactly 1 dangling hit when reintroduced,
gates:pre 56/56, and the broken form is absent from the shipped minified bundle.
2026-09-29 02:35:36 +00:00
Doxa Maps
e9e1adc0d6 check:shadow-css-parses reads the style id: it must be a plain identifier
gospel-plugin's trap (COMMON.md): a CSS rule moved past the first argument of useShadowStyles concatenates
onto the style id ('@media(...){...}doxa-gospel-map'). That is valid JS, so the build stays green and the rule
is silently lost; the brace-balance scan never sees it because the id is not CSS. The walker now folds the
second argument too, and a literal id must match /^[a-z0-9-]+$/ (a computed id is left alone). Measured on
the current tree first: 0 offenders, so this breaks no owner. Self-tests cover the trap and a plain id.
From dx-shepherd (END-DXS-CSSID).
2026-09-27 06:04:07 +00:00
Doxa Maps
f1b33af772 Validate the CSS we build as JS strings
check:sfc-parse proves a .vue script block parses. A broken stylesheet
inside a valid string literal is still a valid string literal, so it
passed a sheet with 9 declarations stranded outside any rule and braces
at 12 open / 21 close. Nothing else in the tree validated CSS syntax.

check:shadow-css-parses reads every useShadowStyles() argument and scans
the result as CSS: brace balance, declarations stranded at depth 0, empty
selectors. 72 stylesheets across library/ and plugin-designs/.

It reads the arguments off the @babel/parser AST the build already uses,
because three hand-rolled lexers each produced a confident FALSE RED on a
file that was fine: a /* CSS comment */ read as a stranded declaration; an
apostrophe in a .vue prose header opening a phantom string literal, so a
</div> from the template was reported as CSS; and a regex literal
containing a quote, which cannot be lexed without resolving regex-vs-
division. The CSS scan itself stays hand-written, because CSS is the thing
being checked -- and it consumes CSS comments and quoted values as their
own token types, so content:'a{b' opens no rule.

9 self-tests run inline on every invocation, including the exact damage
above, so the gate cannot quietly stop being able to fail. An argument
built partly from an imported constant is scanned for its literal
segments and labelled, rather than skipped -- skipping it would have
excluded region-dashboards.vue, which builds its sheet from the token
layer plus literals.
2026-09-27 04:14:32 +00:00