From e9e1adc0d658d2661cc9c981563a413b44bd21e2 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Sun, 27 Sep 2026 04:43:23 +0000 Subject: [PATCH] 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). --- internal/check-shadow-css-parses.mjs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/internal/check-shadow-css-parses.mjs b/internal/check-shadow-css-parses.mjs index 08c870b98..10c0f5043 100644 --- a/internal/check-shadow-css-parses.mjs +++ b/internal/check-shadow-css-parses.mjs @@ -73,6 +73,12 @@ function shadowCssOf(script, filename) { if (node.type === 'CallExpression' && node.callee && node.callee.name === 'useShadowStyles') { const acc = { css: '', complete: true } fold(node.arguments[0], acc) + // The style ID (second argument). A plain identifier is the only correct shape: a CSS rule moved + // past the first argument concatenates onto it ('@media(...){...}doxa-gospel-map'), which is valid + // JS, keeps the build green and silently loses the rule (gospel-plugin's trap, COMMON.md). + const lab = { text: '', literal: true, present: !!node.arguments[1] } + if (node.arguments[1]) { const la = { css: '', complete: true }; fold(node.arguments[1], la); lab.text = la.css; lab.literal = la.complete } + acc.label = lab found.push(acc) } for (const k of Object.keys(node)) { @@ -153,6 +159,14 @@ ok(r.open === 1 && !r.orphans.length, 'a data: URL with a ; inside url() does no ok(got.length === 1 && got[0].complete === false, 'an argument built partly from an imported constant is reported PARTIAL, not scanned as a whole sheet') } +{ + // gospel-plugin's trap: a rule moved past the CSS argument concatenates onto the style id + const trap = "useShadowStyles('.a{ color:red; }', '@media (max-width:1px){ .b{ color:red; } }' + 'doxa-gospel-map')" + const lab = shadowCssOf(trap, 'x.js')[0].label + ok(lab.literal && !/^[a-z0-9-]+$/.test(lab.text), 'the style-id arm reads a rule concatenated onto the id as NOT a plain identifier') + const good = shadowCssOf("useShadowStyles('.a{ color:red; }', 'doxa-gospel-map')", 'x.js')[0].label + ok(good.literal && /^[a-z0-9-]+$/.test(good.text), 'NEG CONTROL: a plain id passes the style-id arm') +} // ── the tree ──────────────────────────────────────────────────────────────────────────────────── const walk = (d) => readdirSync(d).flatMap((f) => { @@ -172,7 +186,13 @@ for (const f of files) { let blocks try { blocks = scriptsOf(f, src).flatMap((s) => shadowCssOf(s, f)) } catch (e) { console.log(` FAIL - ${rel(f)}: could not be parsed (${e.message.slice(0, 70)})`); fails++; continue } - for (const { css, complete } of blocks) { + for (const { css, complete, label } of blocks) { + // ID arm: a literal style ID must be a plain identifier. A computed one (a variable) is left alone. + if (label && label.present && label.literal && !/^[a-z0-9-]+$/.test(label.text)) { + console.log(` FAIL - ${rel(f)}: useShadowStyles id is not a plain identifier: ${JSON.stringify(label.text.slice(0, 80))}`) + console.log(' a CSS rule has probably been concatenated onto the id; move it back into the first argument') + fails++ + } if (!/[{}]/.test(css)) { partials.push(f); continue } // ★ A PARTIAL ARGUMENT IS STILL SCANNED, and skipping it was a false green waiting to happen. // region-dashboards.vue builds its sheet as DXLIFE_TOKENS + SHADOW_BOX_SIZING_RESET + literals,