@bastani/atomic 0.9.0-alpha.2 → 0.9.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/builtin/cursor/package.json +2 -2
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +24 -0
- package/dist/builtin/workflows/README.md +12 -12
- package/dist/builtin/workflows/builtin/goal-ledger.ts +2 -0
- package/dist/builtin/workflows/builtin/goal-prompts.ts +8 -0
- package/dist/builtin/workflows/builtin/goal-reports.ts +5 -0
- package/dist/builtin/workflows/builtin/goal-runner.ts +103 -4
- package/dist/builtin/workflows/builtin/goal-types.ts +4 -0
- package/dist/builtin/workflows/builtin/goal.d.ts +4 -0
- package/dist/builtin/workflows/builtin/goal.ts +14 -2
- package/dist/builtin/workflows/builtin/index.d.ts +8 -8
- package/dist/builtin/workflows/builtin/open-claude-design-feedback.ts +359 -0
- package/dist/builtin/workflows/builtin/open-claude-design-phases.ts +254 -352
- package/dist/builtin/workflows/builtin/open-claude-design-runner.ts +256 -414
- package/dist/builtin/workflows/builtin/open-claude-design-setup.ts +272 -0
- package/dist/builtin/workflows/builtin/open-claude-design-utils.ts +58 -68
- package/dist/builtin/workflows/builtin/open-claude-design.d.ts +5 -9
- package/dist/builtin/workflows/builtin/open-claude-design.ts +14 -26
- package/dist/builtin/workflows/builtin/prompt-refinement.ts +102 -0
- package/dist/builtin/workflows/builtin/ralph-core.ts +6 -4
- package/dist/builtin/workflows/builtin/ralph-runner.ts +22 -24
- package/dist/builtin/workflows/builtin/ralph.d.ts +2 -0
- package/dist/builtin/workflows/builtin/ralph.ts +3 -1
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/skills/impeccable/SKILL.md +14 -23
- package/dist/builtin/workflows/skills/impeccable/reference/brand.md +2 -2
- package/dist/builtin/workflows/skills/impeccable/reference/live.md +25 -4
- package/dist/builtin/workflows/skills/impeccable/scripts/context-signals.mjs +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/context.mjs +724 -29
- package/dist/builtin/workflows/skills/impeccable/scripts/critique-storage.mjs +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/browser/injected/index.mjs +219 -7
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/cli/main.mjs +57 -11
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/design-system.mjs +750 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +648 -53
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns.mjs +7 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +29 -4
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +44 -11
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +29 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +27 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/node/file-system.mjs +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/registry/antipatterns.mjs +29 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/rules/checks.mjs +401 -46
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/inline-ignores.mjs +148 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/page.mjs +6 -6
- package/dist/builtin/workflows/skills/impeccable/scripts/{design-parser.mjs → lib/design-parser.mjs} +8 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-config.mjs +638 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-paths.mjs +128 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/{is-generated.mjs → lib/is-generated.mjs} +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/target-args.mjs +42 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/browser-script-parts.mjs +49 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/{live-completion.mjs → live/completion.mjs} +1 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/{live-event-validation.mjs → live/event-validation.mjs} +6 -5
- package/dist/builtin/workflows/skills/impeccable/scripts/live/manual-apply.mjs +939 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/manual-edit-routes.mjs +357 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/{live-manual-edits-buffer.mjs → live/manual-edits-buffer.mjs} +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/{live-session-store.mjs → live/session-store.mjs} +21 -3
- package/dist/builtin/workflows/skills/impeccable/scripts/live/svelte-component.mjs +835 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/sveltekit-adapter.mjs +274 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/ui-core.mjs +180 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/vocabulary.mjs +36 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-accept.mjs +185 -60
- package/dist/builtin/workflows/skills/impeccable/scripts/live-browser-dom.js +146 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-browser.js +3369 -1026
- package/dist/builtin/workflows/skills/impeccable/scripts/live-commit-manual-edits.mjs +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/live-complete.mjs +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/live-discard-manual-edits.mjs +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/live-inject.mjs +133 -9
- package/dist/builtin/workflows/skills/impeccable/scripts/live-insert.mjs +42 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/live-manual-edit-evidence.mjs +4 -4
- package/dist/builtin/workflows/skills/impeccable/scripts/live-poll.mjs +21 -15
- package/dist/builtin/workflows/skills/impeccable/scripts/live-resume.mjs +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/live-server.mjs +205 -1269
- package/dist/builtin/workflows/skills/impeccable/scripts/live-status.mjs +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/live-target.mjs +30 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-wrap.mjs +69 -26
- package/dist/builtin/workflows/skills/impeccable/scripts/live.mjs +73 -22
- package/dist/builtin/workflows/src/extension/workflow-prompts.ts +3 -1
- package/dist/core/atomic-guide-command.d.ts.map +1 -1
- package/dist/core/atomic-guide-command.js +5 -5
- package/dist/core/atomic-guide-command.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +0 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/docs/index.md +2 -2
- package/docs/quickstart.md +9 -9
- package/docs/workflows.md +816 -47
- package/package.json +2 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/cleanup-deprecated.mjs +0 -284
- package/dist/builtin/workflows/skills/impeccable/scripts/impeccable-paths.mjs +0 -126
- /package/dist/builtin/workflows/skills/impeccable/scripts/{live-insert-ui.mjs → live/insert-ui.mjs} +0 -0
|
@@ -23,6 +23,13 @@ export {
|
|
|
23
23
|
checkHtmlPatterns,
|
|
24
24
|
} from './rules/checks.mjs';
|
|
25
25
|
export { createDetectorProfile, summarizeDetectorProfile } from './profile/profiler.mjs';
|
|
26
|
+
export {
|
|
27
|
+
parseFrontmatter as parseDesignFrontmatter,
|
|
28
|
+
normalizeDesignSystem,
|
|
29
|
+
loadDesignSystemForCwd,
|
|
30
|
+
checkSourceDesignSystem,
|
|
31
|
+
collectStaticDesignSystemFindings,
|
|
32
|
+
} from './design-system.mjs';
|
|
26
33
|
export { detectHtml } from './engines/static-html/detect-html.mjs';
|
|
27
34
|
export { detectUrl, createBrowserDetector } from './engines/browser/detect-url.mjs';
|
|
28
35
|
export { detectText, extractStyleBlocks, extractCSSinJS } from './engines/regex/detect-text.mjs';
|
package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs
CHANGED
|
@@ -7,6 +7,25 @@ import { filterByProviders } from '../../registry/antipatterns.mjs';
|
|
|
7
7
|
import { profileFindingsAsync, profileStep, profileStepAsync } from '../../profile/profiler.mjs';
|
|
8
8
|
import { captureVisualContrastCandidate } from '../visual/screenshot-contrast.mjs';
|
|
9
9
|
|
|
10
|
+
function serializeDesignSystemForBrowser(designSystem) {
|
|
11
|
+
if (!designSystem?.present) return null;
|
|
12
|
+
return {
|
|
13
|
+
present: true,
|
|
14
|
+
hasFonts: designSystem.hasFonts === true,
|
|
15
|
+
allowedFonts: Array.from(designSystem.allowedFonts || []),
|
|
16
|
+
hasColors: designSystem.hasColors === true,
|
|
17
|
+
allowedColors: Array.from(designSystem.allowedColorKeys?.values?.() || [])
|
|
18
|
+
.map(entry => entry?.color)
|
|
19
|
+
.filter(color => color && Number.isFinite(color.r) && Number.isFinite(color.g) && Number.isFinite(color.b))
|
|
20
|
+
.map(color => ({ r: color.r, g: color.g, b: color.b })),
|
|
21
|
+
hasRadii: designSystem.hasRadii === true,
|
|
22
|
+
allowedRadii: (designSystem.allowedRadii || [])
|
|
23
|
+
.map(entry => Number(entry?.px))
|
|
24
|
+
.filter(px => Number.isFinite(px)),
|
|
25
|
+
hasPillRadius: designSystem.hasPillRadius === true,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
10
29
|
async function runVisualContrastFallback(page, serializedGroups, options, profile, target) {
|
|
11
30
|
if (options?.visualContrast === false) return [];
|
|
12
31
|
const maxCandidates = Number.isFinite(options?.visualContrastMaxCandidates)
|
|
@@ -163,17 +182,19 @@ async function detectUrl(url, options = {}) {
|
|
|
163
182
|
}
|
|
164
183
|
|
|
165
184
|
// Inject the browser detection script and collect results
|
|
185
|
+
const browserDesignSystem = serializeDesignSystemForBrowser(options?.designSystem);
|
|
166
186
|
await profileStepAsync(profile, {
|
|
167
187
|
engine: 'browser',
|
|
168
188
|
phase: 'scan',
|
|
169
189
|
ruleId: 'configure-pure-detect',
|
|
170
190
|
target: url,
|
|
171
|
-
}, () => page.evaluate(() => {
|
|
191
|
+
}, () => page.evaluate((designSystem) => {
|
|
172
192
|
window.__IMPECCABLE_CONFIG__ = {
|
|
173
193
|
...(window.__IMPECCABLE_CONFIG__ || {}),
|
|
174
194
|
autoScan: false,
|
|
195
|
+
...(designSystem ? { designSystem } : {}),
|
|
175
196
|
};
|
|
176
|
-
}));
|
|
197
|
+
}, browserDesignSystem));
|
|
177
198
|
await profileStepAsync(profile, {
|
|
178
199
|
engine: 'browser',
|
|
179
200
|
phase: 'scan',
|
|
@@ -192,7 +213,7 @@ async function detectUrl(url, options = {}) {
|
|
|
192
213
|
return window.impeccableDetect({ decorate: false, serialize: true });
|
|
193
214
|
});
|
|
194
215
|
return serializedGroups.flatMap(({ findings }) =>
|
|
195
|
-
findings.map(f => ({ id: f.type, snippet: f.detail }))
|
|
216
|
+
findings.map(f => ({ id: f.type, snippet: f.detail, ignoreValue: f.ignoreValue || '' }))
|
|
196
217
|
);
|
|
197
218
|
});
|
|
198
219
|
const visualFindings = await runVisualContrastFallback(page, serializedGroups, options, profile, url);
|
|
@@ -213,7 +234,11 @@ async function detectUrl(url, options = {}) {
|
|
|
213
234
|
}, () => browser.close());
|
|
214
235
|
}
|
|
215
236
|
}
|
|
216
|
-
return filterByProviders(results.map(f =>
|
|
237
|
+
return filterByProviders(results.map(f => {
|
|
238
|
+
const item = finding(f.id, url, f.snippet);
|
|
239
|
+
if (f.ignoreValue) item.ignoreValue = f.ignoreValue;
|
|
240
|
+
return item;
|
|
241
|
+
}), options.providers);
|
|
217
242
|
}
|
|
218
243
|
|
|
219
244
|
async function createBrowserDetector(options = {}) {
|
package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { GENERIC_FONTS } from '../../shared/constants.mjs';
|
|
2
|
+
import { isNeutralColor } from '../../shared/color.mjs';
|
|
3
|
+
import { checkSourceDesignSystem } from '../../design-system.mjs';
|
|
2
4
|
import { isFullPage } from '../../shared/page.mjs';
|
|
5
|
+
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
|
|
3
6
|
import { finding } from '../../findings.mjs';
|
|
4
7
|
import { filterByProviders } from '../../registry/antipatterns.mjs';
|
|
5
8
|
import { profileFindings, profileStep } from '../../profile/profiler.mjs';
|
|
@@ -23,11 +26,24 @@ function stripHtmlToText(html) {
|
|
|
23
26
|
.replace(/\s+/g, ' ');
|
|
24
27
|
}
|
|
25
28
|
|
|
29
|
+
const PAGE_ANALYZER_EXTS = new Set(['.html', '.htm', '.astro', '.vue', '.svelte']);
|
|
30
|
+
|
|
31
|
+
function extFromFilePath(filePath) {
|
|
32
|
+
return filePath ? (filePath.match(/\.\w+$/)?.[0] || '').toLowerCase() : '';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function shouldRunPageAnalyzers(content, filePath) {
|
|
36
|
+
if (!isFullPage(content)) return false;
|
|
37
|
+
const ext = extFromFilePath(filePath);
|
|
38
|
+
return !ext || PAGE_ANALYZER_EXTS.has(ext);
|
|
39
|
+
}
|
|
40
|
+
|
|
26
41
|
function isNeutralBorderColor(str) {
|
|
27
|
-
const m = str.match(/solid\s+(
|
|
42
|
+
const m = str.match(/solid\s+((?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color)\([^)]*\)|#[0-9a-f]{3,8}\b|[a-z]+)/i);
|
|
28
43
|
if (!m) return false;
|
|
29
44
|
const c = m[1].toLowerCase();
|
|
30
45
|
if (['gray', 'grey', 'silver', 'white', 'black', 'transparent', 'currentcolor'].includes(c)) return true;
|
|
46
|
+
if (/^(?:rgba?|hsla?|oklch|oklab|lab|lch|hwb)\(/i.test(c)) return isNeutralColor(c);
|
|
31
47
|
const hex = c.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/);
|
|
32
48
|
if (hex) {
|
|
33
49
|
const [r, g, b] = [parseInt(hex[1], 16), parseInt(hex[2], 16), parseInt(hex[3], 16)];
|
|
@@ -44,10 +60,10 @@ function isNeutralBorderColor(str) {
|
|
|
44
60
|
const REGEX_MATCHERS = [
|
|
45
61
|
// --- Side-tab ---
|
|
46
62
|
{ id: 'side-tab', regex: /\bborder-[lrse]-(\d+)\b/g,
|
|
47
|
-
test: (m, line) => { const n = +m[1]; return hasRounded(line) ? n >=
|
|
63
|
+
test: (m, line) => { const n = +m[1]; return hasRounded(line) ? n >= 2 : n >= 4; },
|
|
48
64
|
fmt: (m) => m[0] },
|
|
49
65
|
{ id: 'side-tab', regex: /border-(?:left|right)\s*:\s*(\d+)px\s+solid[^;]*/gi,
|
|
50
|
-
test: (m, line) => { if (isSafeElement(line)) return false; if (isNeutralBorderColor(m[0])) return false; const n = +m[1]; return hasBorderRadius(line) ? n >=
|
|
66
|
+
test: (m, line) => { if (isSafeElement(line)) return false; if (isNeutralBorderColor(m[0])) return false; const n = +m[1]; return hasBorderRadius(line) ? n >= 2 : n >= 3; },
|
|
51
67
|
fmt: (m) => m[0].replace(/\s*;?\s*$/, '') },
|
|
52
68
|
{ id: 'side-tab', regex: /border-(?:left|right)-width\s*:\s*(\d+)px/gi,
|
|
53
69
|
test: (m, line) => !isSafeElement(line) && +m[1] >= 3,
|
|
@@ -98,9 +114,14 @@ const REGEX_MATCHERS = [
|
|
|
98
114
|
{ id: 'bounce-easing', regex: /\banimate-bounce\b/g,
|
|
99
115
|
test: () => true,
|
|
100
116
|
fmt: () => 'animate-bounce (Tailwind)' },
|
|
101
|
-
{ id: 'bounce-easing', regex: /animation(?:-name)?\s*:\s*[^;]
|
|
117
|
+
{ id: 'bounce-easing', regex: /animation(?:-name)?\s*:\s*([^;{}]*(?:bounce|elastic|wobble|jiggle|spring)[^;{}]*)/gi,
|
|
102
118
|
test: () => true,
|
|
103
|
-
fmt: (m) =>
|
|
119
|
+
fmt: (m) => {
|
|
120
|
+
const token = m[1]
|
|
121
|
+
.split(/[,\s]+/)
|
|
122
|
+
.find((part) => /bounce|elastic|wobble|jiggle|spring/i.test(part));
|
|
123
|
+
return `animation: ${token || m[1].trim()}`;
|
|
124
|
+
} },
|
|
104
125
|
{ id: 'bounce-easing', regex: /cubic-bezier\(\s*([\d.-]+)\s*,\s*([\d.-]+)\s*,\s*([\d.-]+)\s*,\s*([\d.-]+)\s*\)/g,
|
|
105
126
|
test: (m) => {
|
|
106
127
|
const y1 = parseFloat(m[2]), y2 = parseFloat(m[4]);
|
|
@@ -326,7 +347,7 @@ function extractStyleBlocks(content, ext) {
|
|
|
326
347
|
ext = ext.toLowerCase();
|
|
327
348
|
if (ext !== '.vue' && ext !== '.svelte') return [];
|
|
328
349
|
const blocks = [];
|
|
329
|
-
const re = /<style[^>]*>([\s\S]*?)<\/style
|
|
350
|
+
const re = /<style[^>]*>([\s\S]*?)<\/style[^>]*>/gi;
|
|
330
351
|
let m;
|
|
331
352
|
while ((m = re.exec(content)) !== null) {
|
|
332
353
|
const before = content.substring(0, m.index);
|
|
@@ -422,7 +443,7 @@ const TEXT_CONTENT_ANALYZER_IDS = [
|
|
|
422
443
|
|
|
423
444
|
function runTextContentAnalyzers(content, filePath, options = {}) {
|
|
424
445
|
const profile = options?.profile;
|
|
425
|
-
if (!
|
|
446
|
+
if (!shouldRunPageAnalyzers(content, filePath)) return [];
|
|
426
447
|
// The 4 text-content analyzers are at indices 3-6 in REGEX_ANALYZERS.
|
|
427
448
|
const findings = [];
|
|
428
449
|
for (let i = 0; i < TEXT_CONTENT_ANALYZER_IDS.length; i++) {
|
|
@@ -442,11 +463,11 @@ function detectText(content, filePath, options = {}) {
|
|
|
442
463
|
const profile = options?.profile;
|
|
443
464
|
const findings = [];
|
|
444
465
|
const lines = content.split('\n');
|
|
445
|
-
const ext =
|
|
466
|
+
const ext = extFromFilePath(filePath);
|
|
446
467
|
|
|
447
468
|
// Run regex matchers on the full file content (catches Tailwind classes, inline styles)
|
|
448
469
|
// Enable block context for CSS files where related properties span multiple lines
|
|
449
|
-
const cssLike = new Set(['.css', '.scss', '.less']);
|
|
470
|
+
const cssLike = new Set(['.css', '.scss', '.sass', '.less']);
|
|
450
471
|
findings.push(...runRegexMatchers(lines, filePath, 0, cssLike.has(ext) || null, {
|
|
451
472
|
profile,
|
|
452
473
|
phase: 'source',
|
|
@@ -486,6 +507,15 @@ function detectText(content, filePath, options = {}) {
|
|
|
486
507
|
}));
|
|
487
508
|
}
|
|
488
509
|
|
|
510
|
+
if (options?.designSystem) {
|
|
511
|
+
findings.push(...profileFindings(profile, {
|
|
512
|
+
engine: 'regex',
|
|
513
|
+
phase: 'source',
|
|
514
|
+
ruleId: 'design-system',
|
|
515
|
+
target: filePath,
|
|
516
|
+
}, () => checkSourceDesignSystem(content, filePath, { designSystem: options.designSystem })));
|
|
517
|
+
}
|
|
518
|
+
|
|
489
519
|
// Deduplicate findings (same antipattern + similar snippet, within 2 lines)
|
|
490
520
|
const deduped = [];
|
|
491
521
|
for (const f of findings) {
|
|
@@ -498,7 +528,7 @@ function detectText(content, filePath, options = {}) {
|
|
|
498
528
|
}
|
|
499
529
|
|
|
500
530
|
// Page-level analyzers only run on full pages
|
|
501
|
-
if (
|
|
531
|
+
if (shouldRunPageAnalyzers(content, filePath)) {
|
|
502
532
|
const analyzerIds = [
|
|
503
533
|
'single-font',
|
|
504
534
|
'flat-type-hierarchy',
|
|
@@ -520,7 +550,10 @@ function detectText(content, filePath, options = {}) {
|
|
|
520
550
|
}
|
|
521
551
|
}
|
|
522
552
|
|
|
523
|
-
|
|
553
|
+
const byProvider = filterByProviders(deduped, options?.providers);
|
|
554
|
+
// Inline `impeccable-disable*` waivers travel with the file; honor them unless
|
|
555
|
+
// explicitly bypassed (`--no-config` / `--no-inline-ignores`).
|
|
556
|
+
return options?.inlineIgnores === false ? byProvider : applyInlineIgnores(byProvider, content);
|
|
524
557
|
}
|
|
525
558
|
|
|
526
559
|
export {
|
|
@@ -267,7 +267,17 @@ const STATIC_DEFAULT_STYLE = {
|
|
|
267
267
|
paddingRight: '0px',
|
|
268
268
|
paddingBottom: '0px',
|
|
269
269
|
paddingLeft: '0px',
|
|
270
|
+
marginTop: '0px',
|
|
271
|
+
marginRight: '0px',
|
|
272
|
+
marginBottom: '0px',
|
|
273
|
+
marginLeft: '0px',
|
|
270
274
|
position: 'static',
|
|
275
|
+
visibility: 'visible',
|
|
276
|
+
top: 'auto',
|
|
277
|
+
right: 'auto',
|
|
278
|
+
bottom: 'auto',
|
|
279
|
+
left: 'auto',
|
|
280
|
+
inset: '',
|
|
271
281
|
display: '',
|
|
272
282
|
overflow: 'visible',
|
|
273
283
|
overflowX: 'visible',
|
|
@@ -312,7 +322,17 @@ const STATIC_PROP_MAP = {
|
|
|
312
322
|
'padding-right': 'paddingRight',
|
|
313
323
|
'padding-bottom': 'paddingBottom',
|
|
314
324
|
'padding-left': 'paddingLeft',
|
|
325
|
+
'margin-top': 'marginTop',
|
|
326
|
+
'margin-right': 'marginRight',
|
|
327
|
+
'margin-bottom': 'marginBottom',
|
|
328
|
+
'margin-left': 'marginLeft',
|
|
315
329
|
'position': 'position',
|
|
330
|
+
'visibility': 'visibility',
|
|
331
|
+
'top': 'top',
|
|
332
|
+
'right': 'right',
|
|
333
|
+
'bottom': 'bottom',
|
|
334
|
+
'left': 'left',
|
|
335
|
+
'inset': 'inset',
|
|
316
336
|
'display': 'display',
|
|
317
337
|
'overflow': 'overflow',
|
|
318
338
|
'overflow-x': 'overflowX',
|
|
@@ -579,6 +599,15 @@ function expandStaticDeclaration(prop, value) {
|
|
|
579
599
|
['paddingLeft', vals[3]],
|
|
580
600
|
];
|
|
581
601
|
}
|
|
602
|
+
if (p === 'margin') {
|
|
603
|
+
const vals = expandStaticBoxValues(splitCssTokens(v));
|
|
604
|
+
return [
|
|
605
|
+
['marginTop', vals[0]],
|
|
606
|
+
['marginRight', vals[1]],
|
|
607
|
+
['marginBottom', vals[2]],
|
|
608
|
+
['marginLeft', vals[3]],
|
|
609
|
+
];
|
|
610
|
+
}
|
|
582
611
|
if (p === 'font') return parseStaticFont(v);
|
|
583
612
|
if (p === 'transition') {
|
|
584
613
|
const parsed = parseStaticTransition(v);
|
|
@@ -2,7 +2,13 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
4
|
import { GENERIC_FONTS, OVERUSED_FONTS } from '../../shared/constants.mjs';
|
|
5
|
+
import {
|
|
6
|
+
checkSourceDesignSystem,
|
|
7
|
+
collectStaticDesignSystemFindings,
|
|
8
|
+
mergeDesignSystemFindings,
|
|
9
|
+
} from '../../design-system.mjs';
|
|
5
10
|
import { isFullPage } from '../../shared/page.mjs';
|
|
11
|
+
import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
|
|
6
12
|
import { finding } from '../../findings.mjs';
|
|
7
13
|
import { profileFindings, profileStep, profileStepAsync } from '../../profile/profiler.mjs';
|
|
8
14
|
import {
|
|
@@ -168,6 +174,22 @@ async function detectHtml(filePath, options = {}) {
|
|
|
168
174
|
}
|
|
169
175
|
}
|
|
170
176
|
|
|
177
|
+
if (options?.designSystem) {
|
|
178
|
+
const sourceDesignFindings = profileFindings(profile, {
|
|
179
|
+
engine: 'static-html',
|
|
180
|
+
phase: 'source',
|
|
181
|
+
ruleId: 'design-system',
|
|
182
|
+
target: filePath,
|
|
183
|
+
}, () => checkSourceDesignSystem(html, filePath, { designSystem: options.designSystem }));
|
|
184
|
+
const staticDesignFindings = profileFindings(profile, {
|
|
185
|
+
engine: 'static-html',
|
|
186
|
+
phase: 'page',
|
|
187
|
+
ruleId: 'design-system',
|
|
188
|
+
target: filePath,
|
|
189
|
+
}, () => collectStaticDesignSystemFindings(document, window, filePath, options.designSystem));
|
|
190
|
+
findings.push(...mergeDesignSystemFindings(staticDesignFindings, sourceDesignFindings));
|
|
191
|
+
}
|
|
192
|
+
|
|
171
193
|
if (isFullPage(html)) {
|
|
172
194
|
const runPageCheck = (ruleId, callback) => profile
|
|
173
195
|
? profileFindings(profile, { engine: 'static-html', phase: 'page', ruleId, target: filePath }, callback)
|
|
@@ -202,7 +224,11 @@ async function detectHtml(filePath, options = {}) {
|
|
|
202
224
|
}
|
|
203
225
|
}
|
|
204
226
|
|
|
205
|
-
|
|
227
|
+
const byProvider = filterByProviders(findings, options.providers);
|
|
228
|
+
// Static-HTML findings carry no line number, so only whole-file
|
|
229
|
+
// `impeccable-disable` directives apply here — exactly the standalone-document
|
|
230
|
+
// waiver this primitive targets. Bypassed by `--no-config` / `--no-inline-ignores`.
|
|
231
|
+
return options?.inlineIgnores === false ? byProvider : applyInlineIgnores(byProvider, html);
|
|
206
232
|
}
|
|
207
233
|
|
|
208
234
|
export { checkStaticPageTypography, STATIC_ELEMENT_RULES, detectHtml };
|
|
@@ -323,6 +323,35 @@ const ANTIPATTERNS = [
|
|
|
323
323
|
skillSection: 'Layout & Space',
|
|
324
324
|
skillGuideline: 'overflow container clipping positioned children',
|
|
325
325
|
},
|
|
326
|
+
{
|
|
327
|
+
id: 'design-system-font',
|
|
328
|
+
category: 'quality',
|
|
329
|
+
name: 'Font outside DESIGN.md',
|
|
330
|
+
description:
|
|
331
|
+
'A font is used that is not declared in DESIGN.md typography. Use the documented type system or update DESIGN.md if this is an intentional brand addition.',
|
|
332
|
+
skillSection: 'Typography',
|
|
333
|
+
skillGuideline: 'font family outside the project design system',
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
id: 'design-system-color',
|
|
337
|
+
category: 'quality',
|
|
338
|
+
severity: 'advisory',
|
|
339
|
+
name: 'Color outside DESIGN.md',
|
|
340
|
+
description:
|
|
341
|
+
'A literal color is outside the DESIGN.md palette and sidecar tonal ramps. This may be legitimate, but it should be an intentional design-system addition rather than drift.',
|
|
342
|
+
skillSection: 'Color & Contrast',
|
|
343
|
+
skillGuideline: 'literal color outside the project design system',
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
id: 'design-system-radius',
|
|
347
|
+
category: 'quality',
|
|
348
|
+
severity: 'advisory',
|
|
349
|
+
name: 'Radius outside DESIGN.md',
|
|
350
|
+
description:
|
|
351
|
+
'A border-radius value is outside the DESIGN.md rounded scale. Use a documented radius token or update the design system if the new shape is intentional.',
|
|
352
|
+
skillSection: 'Visual Details',
|
|
353
|
+
skillGuideline: 'border radius outside the project design system',
|
|
354
|
+
},
|
|
326
355
|
|
|
327
356
|
// ── Provider tells: opt-in via --gpt / --gemini (gated off by default) ──
|
|
328
357
|
{
|