mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 22:26:38 +03:00
The live overlay's detect scan ran unfiltered: requestDetectScan() posted
only { scanId }, so detector.ignoreRules and detector.ignoreValues in
.impeccable/config.json reached impeccable detect and the edit hook but
never the surface a designer actually watches.
The server now serializes the project's detector waivers into the /live.js
prelude (window.__IMPECCABLE_PROJECT_IGNORES__), read per request through
hook-lib's readConfig so config.local.json wins and edits land on the next
tab reload. A new script part, live-browser-ignores.js, resolves that
config against the page URL when a scan starts: ignoreRules suppress
outright, wildcard ignoreValues suppress their rule in the files their
globs name, and the remaining entries ride along as disabledValues for the
detector to match on each finding's own value. The detector bundle applies
those where the findings are assembled, since the overlay draws its own
markers from the collected findings.
Scope resolution mirrors cli/lib/impeccable-config.mjs deliberately: the
same glob dialect (globToRegex, including {a,b} alternation), the same
path-suffix matching as findingMatchesScopedIgnoreFile, and the same
refusal to apply an unscoped wildcard entry. The served-root prefixes that
bridge project-relative globs and site-relative URLs come from the inject
config's own files globs, never from the ignore globs; deriving them from
the ignore globs lets one entry scoped to prototype/library/** lend its
prefix to every page and suppress site-wide, which looks like success
because the numbers go down.
Known gaps, recorded in the detector comment: the motion value extractor
is not mirrored, so a value-scoped bounce-easing waiver only matches when
the finding carries ignoreValue directly, and design-system-color matches
on the normalized string without the CLI's color-equality fallback.
Tests: unit tests for the resolver part (stale globals, string ignoreRules,
malformed entries, directory URLs, percent-escapes, glob metacharacters,
the roots trap), an extension-mode puppeteer test that disabledValues
suppress exactly the waived findings, and the live-browser regression pin
now asserts the new scan config shape instead of { scanId }.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
85 lines
3.6 KiB
JavaScript
85 lines
3.6 KiB
JavaScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
|
|
import { LIVE_CHROME_MOUNT_CONTRACT, LIVE_UI_SURFACES } from './ui-surfaces.mjs';
|
|
|
|
export const LIVE_BROWSER_SCRIPT_PARTS = Object.freeze([
|
|
Object.freeze({ name: 'session-state', file: 'live-browser-session.js' }),
|
|
Object.freeze({ name: 'dom-helpers', file: 'live-browser-dom.js' }),
|
|
Object.freeze({ name: 'project-ignores', file: 'live-browser-ignores.js' }),
|
|
Object.freeze({ name: 'browser-ui', file: 'live-browser.js' }),
|
|
]);
|
|
|
|
export function resolveLiveBrowserScriptParts(scriptsDir, parts = LIVE_BROWSER_SCRIPT_PARTS) {
|
|
if (!scriptsDir) throw new Error('scriptsDir is required');
|
|
return parts.map((part, index) => ({
|
|
...part,
|
|
index,
|
|
path: path.join(scriptsDir, part.file),
|
|
}));
|
|
}
|
|
|
|
export function assertLiveBrowserScriptParts(parts, exists = fs.existsSync) {
|
|
for (const part of parts) {
|
|
if (!exists(part.path)) {
|
|
throw new Error(`Live browser script part missing: ${part.name} (${part.path})`);
|
|
}
|
|
}
|
|
return parts;
|
|
}
|
|
|
|
export function readLiveBrowserScriptParts(parts, readFile = (filePath) => fs.readFileSync(filePath, 'utf-8')) {
|
|
return parts.map((part) => ({
|
|
...part,
|
|
source: readFile(part.path),
|
|
}));
|
|
}
|
|
|
|
export function assembleLiveBrowserScript({
|
|
token,
|
|
port,
|
|
vocabulary,
|
|
commandPrefix = '/',
|
|
appRoot = null,
|
|
parts,
|
|
// Defaulted rather than threaded through live-server.mjs: the browser bundle
|
|
// must always carry the canonical inventory, and a default makes that true by
|
|
// construction instead of by every caller remembering to pass it. Overridable
|
|
// so tests can assemble with a stand-in.
|
|
uiSurfaces = LIVE_UI_SURFACES,
|
|
mountContract = LIVE_CHROME_MOUNT_CONTRACT,
|
|
// Project detector waivers ({ ignoreRules, ignoreValues, roots }), read from
|
|
// .impeccable config by live-server.mjs. live-browser-ignores.js resolves
|
|
// them against the page when a detect scan starts, so the overlay filters
|
|
// the same findings the CLI and the edit hook do (issue #639).
|
|
projectIgnores = null,
|
|
}) {
|
|
const prelude =
|
|
`window.__IMPECCABLE_TOKEN__ = '${token}';\n` +
|
|
`window.__IMPECCABLE_PORT__ = ${port};\n` +
|
|
// Project identity for browser-side session storage. localStorage is
|
|
// keyed by ORIGIN, and two projects routinely share a localhost port
|
|
// across time; saved sessions carry this value so a resume can tell a
|
|
// foreign project's leftovers from its own.
|
|
`window.__IMPECCABLE_APP_ROOT__ = ${JSON.stringify(appRoot)};\n` +
|
|
`window.__IMPECCABLE_COMMAND_PREFIX__ = ${JSON.stringify(commandPrefix)};\n` +
|
|
// Canonical command vocabulary (values + labels + icons). live-browser.js
|
|
// builds its action picker from this instead of an inline copy.
|
|
`window.__IMPECCABLE_VOCAB__ = ${JSON.stringify(vocabulary)};\n` +
|
|
// Canonical Live chrome inventory from live/ui-surfaces.mjs. live-browser.js
|
|
// is a classic script and cannot import an ES module at runtime, so the list
|
|
// is serialized here and read off the global there. Node consumers (this
|
|
// repo's tests, the impeccable-site Live UI lab) import the module directly,
|
|
// which is what keeps the two from drifting.
|
|
`window.__IMPECCABLE_LIVE_UI_SURFACES__ = ${JSON.stringify(uiSurfaces)};\n` +
|
|
`window.__IMPECCABLE_LIVE_MOUNT_CONTRACT__ = ${JSON.stringify(mountContract)};\n` +
|
|
`window.__IMPECCABLE_PROJECT_IGNORES__ = ${JSON.stringify(projectIgnores)};\n`;
|
|
|
|
const body = parts.map((part) => {
|
|
const file = part.file || path.basename(part.path || '');
|
|
return `// --- impeccable live script part: ${part.name} (${file}) ---\n${part.source}`;
|
|
}).join('\n');
|
|
|
|
return prelude + body;
|
|
}
|