From 8682c85c57bc71c0fc29ff47401dc9d975703f75 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Wed, 15 Jul 2026 16:23:49 -0700 Subject: [PATCH] Fix Live side-tab validation gaps Scan Astro style blocks for inset-shadow stripes, recognize semantically chromatic external tokens without flagging neutral unknowns, and make the polling generator run advisory detector checks before publication. Sync the affected detector bundles and add a paired regression fixture.\n\nAI-assisted: Codex analyzed the failed Live task, implemented the detector and generator changes, and ran the validation suites under maintainer direction. --- .../agents/impeccable_live_generator.toml | 5 ++- .../detector/detect-antipatterns-browser.js | 34 +++++++++++---- .../detector/engines/regex/detect-text.mjs | 30 ++++++++++++-- .../scripts/detector/rules/checks.mjs | 34 +++++++++++---- .../detector/detect-antipatterns-browser.js | 34 +++++++++++---- .../detector/engines/regex/detect-text.mjs | 30 ++++++++++++-- .../scripts/detector/rules/checks.mjs | 34 +++++++++++---- .../detector/detect-antipatterns-browser.js | 34 +++++++++++---- .../detector/engines/regex/detect-text.mjs | 30 ++++++++++++-- .../scripts/detector/rules/checks.mjs | 34 +++++++++++---- .../detector/detect-antipatterns-browser.js | 34 +++++++++++---- .../detector/engines/regex/detect-text.mjs | 30 ++++++++++++-- .../scripts/detector/rules/checks.mjs | 34 +++++++++++---- .../detector/detect-antipatterns-browser.js | 34 +++++++++++---- .../detector/engines/regex/detect-text.mjs | 30 ++++++++++++-- .../scripts/detector/rules/checks.mjs | 34 +++++++++++---- .../detector/detect-antipatterns-browser.js | 34 +++++++++++---- .../detector/engines/regex/detect-text.mjs | 30 ++++++++++++-- .../scripts/detector/rules/checks.mjs | 34 +++++++++++---- .../detector/detect-antipatterns-browser.js | 34 +++++++++++---- .../detector/engines/regex/detect-text.mjs | 30 ++++++++++++-- .../scripts/detector/rules/checks.mjs | 34 +++++++++++---- .../detector/detect-antipatterns-browser.js | 34 +++++++++++---- .../detector/engines/regex/detect-text.mjs | 30 ++++++++++++-- .../scripts/detector/rules/checks.mjs | 34 +++++++++++---- .../detector/detect-antipatterns-browser.js | 34 +++++++++++---- .../detector/engines/regex/detect-text.mjs | 30 ++++++++++++-- .../scripts/detector/rules/checks.mjs | 34 +++++++++++---- .../detector/detect-antipatterns-browser.js | 34 +++++++++++---- .../detector/engines/regex/detect-text.mjs | 30 ++++++++++++-- .../scripts/detector/rules/checks.mjs | 34 +++++++++++---- .../detector/detect-antipatterns-browser.js | 34 +++++++++++---- .../detector/engines/regex/detect-text.mjs | 30 ++++++++++++-- .../scripts/detector/rules/checks.mjs | 34 +++++++++++---- .../detector/detect-antipatterns-browser.js | 34 +++++++++++---- .../detector/engines/regex/detect-text.mjs | 30 ++++++++++++-- .../scripts/detector/rules/checks.mjs | 34 +++++++++++---- cli/engine/detect-antipatterns-browser.js | 34 +++++++++++---- cli/engine/engines/regex/detect-text.mjs | 30 ++++++++++++-- cli/engine/rules/checks.mjs | 34 +++++++++++---- .../detector/detect-antipatterns-browser.js | 34 +++++++++++---- .../detector/engines/regex/detect-text.mjs | 30 ++++++++++++-- .../scripts/detector/rules/checks.mjs | 34 +++++++++++---- skill/agents/impeccable-live-generator.md | 5 ++- tests/detect-antipatterns-fixtures.test.mjs | 33 +++++++++++++++ tests/detect-antipatterns.test.js | 15 +++++++ .../astro-inset-shadow-stripe.astro | 41 +++++++++++++++++++ 47 files changed, 1187 insertions(+), 284 deletions(-) create mode 100644 tests/fixtures/antipatterns/astro-inset-shadow-stripe.astro diff --git a/.agents/skills/impeccable/agents/impeccable_live_generator.toml b/.agents/skills/impeccable/agents/impeccable_live_generator.toml index 1c0333740..3a2ca539b 100644 --- a/.agents/skills/impeccable/agents/impeccable_live_generator.toml +++ b/.agents/skills/impeccable/agents/impeccable_live_generator.toml @@ -25,6 +25,7 @@ Do not request the full Live reference or repeat broad project discovery. Use th - Preserve the existing component contract, semantic tag, links, accessibility relationships, and functional descendants. - Reuse existing components, CSS custom properties, typography, spacing, radii, and color roles. Never invent raw colors or foreign fonts when tokens exist. - Do not add gradients, blur, glow, glass, neon, decorative shadows, emoji, or unrelated content unless the explicit user direction requires it. +- Never decorate a card, label, row, tab, or container with a colored stripe on only one edge. This includes borders, inset box-shadows, gradients, and pseudo-elements; selection and focus indicators are the only exception. - Produce the requested number of materially different directions through hierarchy, layout, density, or existing color-role allocation. CSS-only no-ops and source-identical variants are invalid. - Keep temporary Live markers and preview CSS out of accepted project truth; the publisher/Accept pipeline owns cleanup. @@ -34,10 +35,10 @@ Do not request the full Live reference or repeat broad project discovery. Use th 2. If annotations exist, read the screenshot before designing. Treat pins and strokes as semantic constraints. 3. Name all directions and their parameter axes before writing so the set stays coherent. Parameters are lazy: revision 1 carries no parameter manifest. 4. Prepare revision 1 with `live-publish.mjs --prepare --id EVENT_ID --file SOURCE_FILE`. Edit only the returned artifact (or isolated component directory), never live project source. -5. Write one complete, valid first variant plus only its CSS. Publish it immediately with the returned epoch, artifact path, expected source hash, `--arrived 1`, and the requested `--expected` count. +5. Write one complete, valid first variant plus only its CSS. Run `detect.mjs --json` on the staged artifact before publishing. Fix genuine findings; when inspection shows a contextual false positive, use judgment and continue without changing persistent detector configuration. The detector is a review signal, not an automatic publication veto. Publish immediately with the returned epoch, artifact path, expected source hash, `--arrived 1`, and the requested `--expected` count. 6. Prepare again from the published prefix, add the remaining validated directions, attach parameter manifests only with the complete set, and publish the largest ready prefix. Preserve every already-published variant byte-for-byte. 7. On `stale_generation_epoch`, `source_changed`, or another fence rejection, stop. Do not retry against stale source or leave direct edits behind. -8. Verify the final artifact/source parses. Reply exactly once with `live-poll.mjs --reply EVENT_ID done --file RELATIVE_PATH`. On a real failure, reply once with `error` and a short reason. +8. Verify the final artifact/source parses and run the detector again before the final publication. Apply the same genuine-finding versus contextual-false-positive judgment. Reply exactly once with `live-poll.mjs --reply EVENT_ID done --file RELATIVE_PATH`. On a real failure, reply once with `error` and a short reason. For Svelte or Vue component preview, write only `vN.svelte` / `vN.vue` in the isolated `componentDir` returned by prepare and update the isolated manifest. Never edit the live component directory. For JSX/TSX source previews, preserve JSX attribute syntax and wrap preview CSS as required by `scaffold.cssAuthoring`. diff --git a/.agents/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.agents/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index 033573e36..202ddd0a8 100644 --- a/.agents/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.agents/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -1264,6 +1264,11 @@ function findShadowColor(layer) { if (fn) return { color: parseAnyColor(fn[0]), start: fn.index, end: fn.index + fn[0].length }; const hex = layer.match(/#[0-9a-fA-F]{3,8}\b/); if (hex) return { color: parseAnyColor(hex[0]), start: hex.index, end: hex.index + hex[0].length }; + // Keep unresolved custom properties intact as one color token. Otherwise + // names such as `--signal-blue` are accidentally parsed as the named color + // `blue`, and digits in names such as `--accent-500` become shadow lengths. + const variable = layer.match(/var\([^)]*\)/i); + if (variable) return { color: null, start: variable.index, end: variable.index + variable[0].length }; const wordRe = /[a-zA-Z][a-zA-Z]*/g; let m; while ((m = wordRe.exec(layer)) !== null) { @@ -1273,6 +1278,13 @@ function findShadowColor(layer) { return null; } +const CHROMATIC_CUSTOM_PROPERTY_HINT_RE = /(?:^|-)(?:accent|kinpaku|patina|gold|red|orange|amber|yellow|lime|green|emerald|teal|cyan|blue|indigo|violet|purple|magenta|pink|rose|coral|aqua|mint|burgundy|crimson|scarlet)(?:-|$)/i; + +function unresolvedShadowTokenLooksChromatic(layer) { + const variable = layer.match(/var\(\s*(--[\w-]+)/i); + return variable ? CHROMATIC_CUSTOM_PROPERTY_HINT_RE.test(variable[1]) : false; +} + // Extract the length values of a shadow layer in declaration order, with the // color token removed so its components aren't misread as lengths. Handles // computed-style px values AND authored unitless zeros ("0 0 20px"); rem/em @@ -1674,16 +1686,22 @@ function scanCssTextForInsetStripe(content) { // menu items — are wider or leave width to layout. const declaredWidth = cssLengthToPx(resolveVarRefs(decls.get('width') || decls.get('inline-size') || '', customProps)); if (declaredWidth != null && declaredWidth <= 40) continue; - const value = resolveVarRefs(shadow, customProps); - for (const layer of value.split(/,(?![^(]*\))/)) { + for (const authoredLayer of shadow.split(/,(?![^(]*\))/)) { + const layer = resolveVarRefs(authoredLayer, customProps); if (!/\binset\b/i.test(layer)) continue; const colorInfo = findShadowColor(layer); - // Unresolvable colors (currentColor, external vars): don't guess. - if (!colorInfo || !colorInfo.color) continue; - const c = colorInfo.color; - if ((c.a ?? 1) < 0.1) continue; - const chroma = Math.max(c.r, c.g, c.b) - Math.min(c.r, c.g, c.b); - if (chroma < 30) continue; + if (!colorInfo) continue; + if (colorInfo.color) { + const c = colorInfo.color; + if ((c.a ?? 1) < 0.1) continue; + const chroma = Math.max(c.r, c.g, c.b) - Math.min(c.r, c.g, c.b); + if (chroma < 30) continue; + } else if (!unresolvedShadowTokenLooksChromatic(authoredLayer)) { + // External custom properties are unknowable in an isolated artifact. + // Only explicit accent or hue semantics justify treating one as + // chromatic; neutral shadow/divider tokens and currentColor stay legal. + continue; + } const vals = extractShadowLengths(layer, colorInfo.start, colorInfo.end); const x = vals[0] || 0, y = vals[1] || 0, blur = vals[2] || 0, sp = vals[3] || 0; if (blur !== 0 || sp !== 0) continue; diff --git a/.agents/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs b/.agents/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs index 81c30ab99..4e4e93d2f 100644 --- a/.agents/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +++ b/.agents/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs @@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS } from '../../shared/constants.mjs'; import { isNeutralColor } from '../../shared/color.mjs'; import { extractGoogleFontFamilies } from '../../shared/fonts.mjs'; import { checkSourceDesignSystem } from '../../design-system.mjs'; -import { scanCssTextForGlow, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; +import { scanCssTextForGlow, scanCssTextForInsetStripe, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; import { isFullPage } from '../../shared/page.mjs'; import { applyInlineIgnores } from '../../shared/inline-ignores.mjs'; import { finding } from '../../findings.mjs'; @@ -340,12 +340,12 @@ const REGEX_ANALYZERS = [ ]; // --------------------------------------------------------------------------- -// Style block extraction (Vue/Svelte