@monoes/monomindcli 2.14.1 → 2.15.0
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/.claude/helpers/control-start.cjs +10 -13
- package/.claude/skills/monodesign/scripts/context.mjs +4 -4
- package/.claude/skills/monodesign/scripts/critique-storage.mjs +1 -1
- package/.claude/skills/monodesign/scripts/detect-csp.mjs +1 -6
- package/.claude/skills/monodesign/scripts/detector/browser/injected/index.mjs +16 -18
- package/.claude/skills/monodesign/scripts/detector/cli/main.mjs +3 -3
- package/.claude/skills/monodesign/scripts/detector/detect-antipatterns-browser.js +67 -47
- package/.claude/skills/monodesign/scripts/detector/engines/regex/detect-text.mjs +6 -6
- package/.claude/skills/monodesign/scripts/detector/engines/static-html/css-cascade.mjs +6 -5
- package/.claude/skills/monodesign/scripts/detector/engines/static-html/detect-html.mjs +6 -6
- package/.claude/skills/monodesign/scripts/detector/fix/index.mjs +3 -3
- package/.claude/skills/monodesign/scripts/detector/node/file-system.mjs +1 -1
- package/.claude/skills/monodesign/scripts/detector/registry/antipatterns.mjs +1 -1
- package/.claude/skills/monodesign/scripts/detector/rules/checks.mjs +24 -25
- package/.claude/skills/monodesign/scripts/detector/shared/color.mjs +2 -2
- package/.claude/skills/monodesign/scripts/detector/shared/constants.mjs +1 -1
- package/.claude/skills/monodesign/scripts/detector/shared/inline-ignores.mjs +2 -2
- package/.claude/skills/monodesign/scripts/hook-admin.mjs +6 -6
- package/.claude/skills/monodesign/scripts/hook-before-edit.mjs +4 -4
- package/.claude/skills/monodesign/scripts/hook-lib.mjs +20 -11
- package/.claude/skills/monodesign/scripts/hook.mjs +1 -1
- package/.claude/skills/monodesign/scripts/lib/design-parser.mjs +9 -42
- package/.claude/skills/monodesign/scripts/lib/is-generated.mjs +1 -1
- package/.claude/skills/monodesign/scripts/lib/monodesign-config.mjs +5 -5
- package/.claude/skills/monodesign/scripts/lib/monodesign-paths.mjs +1 -1
- package/.claude/skills/monodesign/scripts/live/event-validation.mjs +13 -13
- package/.claude/skills/monodesign/scripts/live/manual-apply.mjs +4 -4
- package/.claude/skills/monodesign/scripts/live/manual-edits-buffer.mjs +1 -1
- package/.claude/skills/monodesign/scripts/live/session-store.mjs +5 -5
- package/.claude/skills/monodesign/scripts/live/svelte-component.mjs +13 -13
- package/.claude/skills/monodesign/scripts/live/sveltekit-adapter.mjs +4 -4
- package/.claude/skills/monodesign/scripts/live/ui-core.mjs +1 -1
- package/.claude/skills/monodesign/scripts/live-accept.mjs +24 -24
- package/.claude/skills/monodesign/scripts/live-browser-dom.js +8 -7
- package/.claude/skills/monodesign/scripts/live-browser-session.js +8 -7
- package/.claude/skills/monodesign/scripts/live-browser.js +405 -432
- package/.claude/skills/monodesign/scripts/live-commit-manual-edits.mjs +6 -6
- package/.claude/skills/monodesign/scripts/live-copy-edit-agent.mjs +2 -2
- package/.claude/skills/monodesign/scripts/live-discard-manual-edits.mjs +1 -1
- package/.claude/skills/monodesign/scripts/live-inject.mjs +7 -7
- package/.claude/skills/monodesign/scripts/live-insert.mjs +11 -11
- package/.claude/skills/monodesign/scripts/live-manual-edit-evidence.mjs +4 -4
- package/.claude/skills/monodesign/scripts/live-poll.mjs +5 -5
- package/.claude/skills/monodesign/scripts/live-server.mjs +10 -10
- package/.claude/skills/monodesign/scripts/live-wrap.mjs +31 -41
- package/.claude/skills/monodesign/scripts/live.mjs +5 -5
- package/.claude/skills/monodesign/scripts/palette.mjs +3 -1
- package/.claude/skills/monodesign/scripts/pin.mjs +1 -1
- package/dist/src/commands/doc-filters.d.ts.map +1 -1
- package/dist/src/commands/doc-filters.js +6 -0
- package/dist/src/commands/doc-filters.js.map +1 -1
- package/dist/src/commands/doc-library.d.ts +2 -1
- package/dist/src/commands/doc-library.d.ts.map +1 -1
- package/dist/src/commands/doc-library.js +66 -2
- package/dist/src/commands/doc-library.js.map +1 -1
- package/dist/src/commands/doc.d.ts.map +1 -1
- package/dist/src/commands/doc.js +2 -0
- package/dist/src/commands/doc.js.map +1 -1
- package/dist/src/commands/ui.d.ts.map +1 -1
- package/dist/src/commands/ui.js +60 -0
- package/dist/src/commands/ui.js.map +1 -1
- package/dist/src/knowledge/capture-envelope.d.ts +5 -0
- package/dist/src/knowledge/capture-envelope.d.ts.map +1 -1
- package/dist/src/knowledge/capture-envelope.js +2 -0
- package/dist/src/knowledge/capture-envelope.js.map +1 -1
- package/dist/src/knowledge/document-chunking.d.ts +42 -0
- package/dist/src/knowledge/document-chunking.d.ts.map +1 -0
- package/dist/src/knowledge/document-chunking.js +288 -0
- package/dist/src/knowledge/document-chunking.js.map +1 -0
- package/dist/src/knowledge/document-index.d.ts +124 -0
- package/dist/src/knowledge/document-index.d.ts.map +1 -0
- package/dist/src/knowledge/document-index.js +314 -0
- package/dist/src/knowledge/document-index.js.map +1 -0
- package/dist/src/knowledge/document-ingest.d.ts +23 -0
- package/dist/src/knowledge/document-ingest.d.ts.map +1 -0
- package/dist/src/knowledge/document-ingest.js +368 -0
- package/dist/src/knowledge/document-ingest.js.map +1 -0
- package/dist/src/knowledge/document-pipeline.d.ts +26 -208
- package/dist/src/knowledge/document-pipeline.d.ts.map +1 -1
- package/dist/src/knowledge/document-pipeline.js +25 -1104
- package/dist/src/knowledge/document-pipeline.js.map +1 -1
- package/dist/src/knowledge/document-search.d.ts +27 -0
- package/dist/src/knowledge/document-search.d.ts.map +1 -0
- package/dist/src/knowledge/document-search.js +138 -0
- package/dist/src/knowledge/document-search.js.map +1 -0
- package/dist/src/knowledge/document-store.d.ts +28 -0
- package/dist/src/knowledge/document-store.d.ts.map +1 -0
- package/dist/src/knowledge/document-store.js +61 -0
- package/dist/src/knowledge/document-store.js.map +1 -0
- package/dist/src/knowledge/document-types.d.ts +103 -0
- package/dist/src/knowledge/document-types.d.ts.map +1 -0
- package/dist/src/knowledge/document-types.js +12 -0
- package/dist/src/knowledge/document-types.js.map +1 -0
- package/dist/src/knowledge/highlights.d.ts +168 -0
- package/dist/src/knowledge/highlights.d.ts.map +1 -0
- package/dist/src/knowledge/highlights.js +311 -0
- package/dist/src/knowledge/highlights.js.map +1 -0
- package/dist/src/knowledge/lookup.d.ts +80 -0
- package/dist/src/knowledge/lookup.d.ts.map +1 -0
- package/dist/src/knowledge/lookup.js +156 -0
- package/dist/src/knowledge/lookup.js.map +1 -0
- package/dist/src/knowledge/okf-bundle.d.ts +19 -0
- package/dist/src/knowledge/okf-bundle.d.ts.map +1 -0
- package/dist/src/knowledge/okf-bundle.js +106 -0
- package/dist/src/knowledge/okf-bundle.js.map +1 -0
- package/dist/src/knowledge/profile-store.d.ts +134 -0
- package/dist/src/knowledge/profile-store.d.ts.map +1 -0
- package/dist/src/knowledge/profile-store.js +237 -0
- package/dist/src/knowledge/profile-store.js.map +1 -0
- package/dist/src/orgrt/agent-runner.d.ts +4 -0
- package/dist/src/orgrt/agent-runner.d.ts.map +1 -1
- package/dist/src/orgrt/agent-runner.js +22 -0
- package/dist/src/orgrt/agent-runner.js.map +1 -1
- package/dist/src/orgrt/antigravity-runner.d.ts.map +1 -1
- package/dist/src/orgrt/antigravity-runner.js +2 -1
- package/dist/src/orgrt/antigravity-runner.js.map +1 -1
- package/dist/src/orgrt/authority-mask.d.ts +32 -0
- package/dist/src/orgrt/authority-mask.d.ts.map +1 -0
- package/dist/src/orgrt/authority-mask.js +135 -0
- package/dist/src/orgrt/authority-mask.js.map +1 -0
- package/dist/src/orgrt/codex-runner.d.ts.map +1 -1
- package/dist/src/orgrt/codex-runner.js +2 -1
- package/dist/src/orgrt/codex-runner.js.map +1 -1
- package/dist/src/orgrt/copilot-runner.d.ts.map +1 -1
- package/dist/src/orgrt/copilot-runner.js +2 -1
- package/dist/src/orgrt/copilot-runner.js.map +1 -1
- package/dist/src/orgrt/crush-runner.d.ts.map +1 -1
- package/dist/src/orgrt/crush-runner.js +6 -1
- package/dist/src/orgrt/crush-runner.js.map +1 -1
- package/dist/src/orgrt/daemon.d.ts +4 -0
- package/dist/src/orgrt/daemon.d.ts.map +1 -1
- package/dist/src/orgrt/daemon.js +13 -2
- package/dist/src/orgrt/daemon.js.map +1 -1
- package/dist/src/orgrt/decisions.d.ts +9 -0
- package/dist/src/orgrt/decisions.d.ts.map +1 -1
- package/dist/src/orgrt/decisions.js +32 -5
- package/dist/src/orgrt/decisions.js.map +1 -1
- package/dist/src/orgrt/file-roots.d.ts +2 -1
- package/dist/src/orgrt/file-roots.d.ts.map +1 -1
- package/dist/src/orgrt/file-roots.js +7 -3
- package/dist/src/orgrt/file-roots.js.map +1 -1
- package/dist/src/orgrt/grok-runner.d.ts.map +1 -1
- package/dist/src/orgrt/grok-runner.js +2 -1
- package/dist/src/orgrt/grok-runner.js.map +1 -1
- package/dist/src/orgrt/hermes-runner.d.ts.map +1 -1
- package/dist/src/orgrt/hermes-runner.js +2 -1
- package/dist/src/orgrt/hermes-runner.js.map +1 -1
- package/dist/src/orgrt/inbox.d.ts.map +1 -1
- package/dist/src/orgrt/inbox.js +70 -4
- package/dist/src/orgrt/inbox.js.map +1 -1
- package/dist/src/orgrt/kimicode-runner.d.ts.map +1 -1
- package/dist/src/orgrt/kimicode-runner.js +2 -1
- package/dist/src/orgrt/kimicode-runner.js.map +1 -1
- package/dist/src/orgrt/opencode-runner.d.ts.map +1 -1
- package/dist/src/orgrt/opencode-runner.js +3 -2
- package/dist/src/orgrt/opencode-runner.js.map +1 -1
- package/dist/src/orgrt/pi-rpc-runner.d.ts.map +1 -1
- package/dist/src/orgrt/pi-rpc-runner.js +3 -2
- package/dist/src/orgrt/pi-rpc-runner.js.map +1 -1
- package/dist/src/orgrt/pi-runner.d.ts.map +1 -1
- package/dist/src/orgrt/pi-runner.js +2 -1
- package/dist/src/orgrt/pi-runner.js.map +1 -1
- package/dist/src/orgrt/policy.d.ts.map +1 -1
- package/dist/src/orgrt/policy.js +3 -0
- package/dist/src/orgrt/policy.js.map +1 -1
- package/dist/src/orgrt/qwen-rpc-runner.d.ts.map +1 -1
- package/dist/src/orgrt/qwen-rpc-runner.js +3 -2
- package/dist/src/orgrt/qwen-rpc-runner.js.map +1 -1
- package/dist/src/orgrt/qwen-runner.d.ts.map +1 -1
- package/dist/src/orgrt/qwen-runner.js +2 -1
- package/dist/src/orgrt/qwen-runner.js.map +1 -1
- package/dist/src/orgrt/role-sandbox.d.ts +21 -0
- package/dist/src/orgrt/role-sandbox.d.ts.map +1 -1
- package/dist/src/orgrt/role-sandbox.js +31 -6
- package/dist/src/orgrt/role-sandbox.js.map +1 -1
- package/dist/src/orgrt/session.d.ts.map +1 -1
- package/dist/src/orgrt/session.js +21 -2
- package/dist/src/orgrt/session.js.map +1 -1
- package/dist/src/orgrt/task-dag.d.ts +3 -0
- package/dist/src/orgrt/task-dag.d.ts.map +1 -1
- package/dist/src/orgrt/task-dag.js +1 -0
- package/dist/src/orgrt/task-dag.js.map +1 -1
- package/dist/src/orgrt/types.d.ts +5 -0
- package/dist/src/orgrt/types.d.ts.map +1 -1
- package/dist/src/orgrt/types.js +23 -1
- package/dist/src/orgrt/types.js.map +1 -1
- package/dist/src/ui/dashboard.html +28 -848
- package/dist/src/ui/human-auth.mjs +103 -0
- package/dist/src/ui/org-hil.mjs +86 -37
- package/dist/src/ui/org-runtime.mjs +21 -13
- package/dist/src/ui/routes-org.mjs +49 -445
- package/dist/src/ui/server.mjs +90 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
|
@@ -67,7 +67,7 @@ function isBrandFontOnOwnDomain(font) {
|
|
|
67
67
|
const allowed = BRAND_FONT_DOMAINS[font];
|
|
68
68
|
if (!allowed) return false;
|
|
69
69
|
const host = location.hostname.toLowerCase();
|
|
70
|
-
return allowed.some(suffix => host === suffix || host.endsWith(
|
|
70
|
+
return allowed.some(suffix => host === suffix || host.endsWith(`.${suffix}`));
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
const GENERIC_FONTS = new Set([
|
|
@@ -672,7 +672,7 @@ function contrastRatio(c1, c2) {
|
|
|
672
672
|
}
|
|
673
673
|
|
|
674
674
|
function parseGradientColors(bgImage) {
|
|
675
|
-
if (!bgImage
|
|
675
|
+
if (!bgImage?.includes('gradient')) return [];
|
|
676
676
|
const colors = [];
|
|
677
677
|
for (const m of bgImage.matchAll(/rgba?\([^)]+\)/g)) {
|
|
678
678
|
const c = parseRgb(m[0]);
|
|
@@ -709,7 +709,7 @@ function getHue(c) {
|
|
|
709
709
|
|
|
710
710
|
function colorToHex(c) {
|
|
711
711
|
if (!c) return '?';
|
|
712
|
-
return
|
|
712
|
+
return `#${[c.r, c.g, c.b].map(v => v.toString(16).padStart(2, '0')).join('')}`;
|
|
713
713
|
}
|
|
714
714
|
|
|
715
715
|
// --- cli/engine/shared/fonts.mjs ---
|
|
@@ -805,7 +805,7 @@ function checkColors(opts) {
|
|
|
805
805
|
if (hasDirectText && textColor && !isEmojiOnly) {
|
|
806
806
|
// Run background-dependent checks against either a solid bg or, if the
|
|
807
807
|
// ancestor is a gradient, against every gradient stop (use the worst case).
|
|
808
|
-
const bgs = effectiveBg ? [effectiveBg] : (effectiveBgStops
|
|
808
|
+
const bgs = effectiveBg ? [effectiveBg] : (effectiveBgStops?.length ? effectiveBgStops : null);
|
|
809
809
|
if (bgs) {
|
|
810
810
|
// Gray on colored background — flag if every stop is chromatic
|
|
811
811
|
const textLum = relativeLuminance(textColor);
|
|
@@ -986,7 +986,6 @@ function isAccentColor(cssColor) {
|
|
|
986
986
|
if (!cssColor) return false;
|
|
987
987
|
const s = String(cssColor).trim();
|
|
988
988
|
// rgb / rgba — direct channel-distance check.
|
|
989
|
-
const rgbM = /rgba?\(\s*(\d+)\s*,?\s+|\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/.exec(s.replace(/rgba?\(\s*/, 'rgb(').replace(/,/g, ', '));
|
|
990
989
|
const rgbStrict = /rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/.exec(s);
|
|
991
990
|
if (rgbStrict) {
|
|
992
991
|
const r = +rgbStrict[1], g = +rgbStrict[2], b = +rgbStrict[3];
|
|
@@ -1031,7 +1030,7 @@ function isAccentColor(cssColor) {
|
|
|
1031
1030
|
// uppercase eyebrow OR the modern accent-colored bold eyebrow.
|
|
1032
1031
|
function checkHeroEyebrow(opts) {
|
|
1033
1032
|
const {
|
|
1034
|
-
headingTag, headingText,
|
|
1033
|
+
headingTag, headingText,
|
|
1035
1034
|
siblingTag, siblingText, siblingTextTransform,
|
|
1036
1035
|
siblingFontSize, siblingLetterSpacing,
|
|
1037
1036
|
siblingFontWeight, siblingColor,
|
|
@@ -1502,7 +1501,7 @@ function parseRadiusToPx(value, widthPx) {
|
|
|
1502
1501
|
return num;
|
|
1503
1502
|
}
|
|
1504
1503
|
|
|
1505
|
-
function resolveBorderRadiusPx(
|
|
1504
|
+
function resolveBorderRadiusPx(_el, style, widthPx, _win) {
|
|
1506
1505
|
const fromComputed = parseRadiusToPx(style.borderRadius, widthPx);
|
|
1507
1506
|
if (fromComputed !== null) return fromComputed;
|
|
1508
1507
|
return 0;
|
|
@@ -1545,7 +1544,7 @@ function checkElementColorsDOM(el) {
|
|
|
1545
1544
|
effectiveBg,
|
|
1546
1545
|
effectiveBgStops: effectiveBg ? null : resolveGradientStops(el),
|
|
1547
1546
|
fontSize: parseFloat(style.fontSize) || 16,
|
|
1548
|
-
fontWeight: parseInt(style.fontWeight) || 400,
|
|
1547
|
+
fontWeight: parseInt(style.fontWeight, 10) || 400,
|
|
1549
1548
|
hasDirectText,
|
|
1550
1549
|
isEmojiOnly: isEmojiOnlyText(directText),
|
|
1551
1550
|
bgClip: style.webkitBackgroundClip || style.backgroundClip || '',
|
|
@@ -1653,7 +1652,7 @@ function buildCustomPropMap(document) {
|
|
|
1653
1652
|
if (!style) continue;
|
|
1654
1653
|
for (let i = 0; i < style.length; i++) {
|
|
1655
1654
|
const prop = style[i];
|
|
1656
|
-
if (!prop
|
|
1655
|
+
if (!prop?.startsWith('--')) continue;
|
|
1657
1656
|
const val = style.getPropertyValue(prop).trim();
|
|
1658
1657
|
if (val) map.set(prop, val);
|
|
1659
1658
|
}
|
|
@@ -1695,7 +1694,7 @@ function oklchToRgb(L, C, H) {
|
|
|
1695
1694
|
const bLin = -0.0041960863 * lc - 0.7034186147 * mc + 1.7076147010 * sc;
|
|
1696
1695
|
const enc = (x) => {
|
|
1697
1696
|
const c = Math.max(0, Math.min(1, x));
|
|
1698
|
-
return c <= 0.0031308 ? 12.92 * c : 1.055 *
|
|
1697
|
+
return c <= 0.0031308 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - 0.055;
|
|
1699
1698
|
};
|
|
1700
1699
|
return {
|
|
1701
1700
|
r: Math.round(enc(rLin) * 255),
|
|
@@ -1997,7 +1996,7 @@ function resolveFontSizePx(el, win) {
|
|
|
1997
1996
|
const v = chain[i];
|
|
1998
1997
|
if (!v || v === 'inherit') continue;
|
|
1999
1998
|
const num = parseFloat(v);
|
|
2000
|
-
if (isNaN(num)) continue;
|
|
1999
|
+
if (Number.isNaN(num)) continue;
|
|
2001
2000
|
if (v.endsWith('px')) px = num;
|
|
2002
2001
|
else if (v.endsWith('rem')) px = num * 16;
|
|
2003
2002
|
else if (v.endsWith('em')) px = num * px;
|
|
@@ -2012,7 +2011,7 @@ function resolveFontSizePx(el, win) {
|
|
|
2012
2011
|
function resolveLengthPx(value, fontSizePx) {
|
|
2013
2012
|
if (!value || value === 'normal' || value === 'auto' || value === 'inherit') return null;
|
|
2014
2013
|
const num = parseFloat(value);
|
|
2015
|
-
if (isNaN(num)) return null;
|
|
2014
|
+
if (Number.isNaN(num)) return null;
|
|
2016
2015
|
if (value.endsWith('px')) return num;
|
|
2017
2016
|
if (value.endsWith('rem')) return num * 16;
|
|
2018
2017
|
if (value.endsWith('em')) return num * fontSizePx;
|
|
@@ -2128,7 +2127,7 @@ function checkQuality(opts) {
|
|
|
2128
2127
|
// font-size — bigger text demands proportionally more padding.
|
|
2129
2128
|
// vertical: max(4px, fontSize × 0.3)
|
|
2130
2129
|
// horizontal: max(8px, fontSize × 0.5)
|
|
2131
|
-
const isInlineCode = tag === 'code' && !
|
|
2130
|
+
const isInlineCode = tag === 'code' && !el.closest?.('pre');
|
|
2132
2131
|
if (!isInlineCode && rect && hasDirectText && textLen > 20 && rect.width > 100 && rect.height > 30) {
|
|
2133
2132
|
const borders = {
|
|
2134
2133
|
top: parseFloat(style.borderTopWidth) || 0,
|
|
@@ -2244,7 +2243,7 @@ function checkQuality(opts) {
|
|
|
2244
2243
|
const CHILD_INSULATE_THRESHOLD = 4;
|
|
2245
2244
|
const childrenInsulate = { top: false, right: false, bottom: false, left: false };
|
|
2246
2245
|
for (const child of el.children) {
|
|
2247
|
-
|
|
2246
|
+
const childStyle = getComputedStyleFor(win, child);
|
|
2248
2247
|
if (!childStyle) continue;
|
|
2249
2248
|
const childPad = {
|
|
2250
2249
|
top: resolveLengthPx(childStyle.paddingTop, fontSize) ?? 0,
|
|
@@ -2376,7 +2375,7 @@ function checkQuality(opts) {
|
|
|
2376
2375
|
// Only flag actual body content, not UI labels (buttons, tabs, badges, captions, footer text, etc.)
|
|
2377
2376
|
if (hasDirectText && textLen > 20 && fontSize < 12) {
|
|
2378
2377
|
const skipTags = ['sub', 'sup', 'code', 'kbd', 'samp', 'var', 'caption', 'figcaption'];
|
|
2379
|
-
const inUIContext = el.closest
|
|
2378
|
+
const inUIContext = el.closest?.('button, a, label, summary, pre, [role="button"], [role="link"], [role="tab"], [role="menuitem"], [role="option"], nav, footer, [aria-hidden="true"], [class*="badge" i], [class*="caption" i], [class*="chip" i], [class*="code" i], [class*="console" i], [class*="diff" i], [class*="label" i], [class*="meta" i], [class*="mock" i], [class*="pill" i], [class*="preview" i], [class*="tag" i], [class*="terminal" i], [class*="writes" i]');
|
|
2380
2379
|
const isUppercase = style.textTransform === 'uppercase';
|
|
2381
2380
|
if (!skipTags.includes(tag) && !inUIContext && !isUppercase) {
|
|
2382
2381
|
findings.push({ id: 'tiny-text', snippet: `${fontSize}px body text` });
|
|
@@ -2441,7 +2440,7 @@ function checkPageQualityFromDoc(doc) {
|
|
|
2441
2440
|
let prevLevel = 0;
|
|
2442
2441
|
let prevText = '';
|
|
2443
2442
|
for (const h of headings) {
|
|
2444
|
-
const level = parseInt(h.tagName[1]);
|
|
2443
|
+
const level = parseInt(h.tagName[1], 10);
|
|
2445
2444
|
const text = (h.textContent || '').trim().replace(/\s+/g, ' ').slice(0, 60);
|
|
2446
2445
|
if (prevLevel > 0 && level > prevLevel + 1) {
|
|
2447
2446
|
findings.push({
|
|
@@ -2489,7 +2488,7 @@ function checkElementBorders(tag, style, overrides, resolvedRadius) {
|
|
|
2489
2488
|
if (widths[s] === 0 && overrides && overrides[s]) {
|
|
2490
2489
|
widths[s] = overrides[s].width;
|
|
2491
2490
|
colors[s] = overrides[s].color;
|
|
2492
|
-
} else if (colors[s]
|
|
2491
|
+
} else if (colors[s]?.startsWith('var(') && overrides?.[s]) {
|
|
2493
2492
|
// Longhand case: jsdom kept the width but left the color as the
|
|
2494
2493
|
// literal `var(...)` string. Substitute the resolved color.
|
|
2495
2494
|
colors[s] = overrides[s].color;
|
|
@@ -2550,7 +2549,7 @@ function checkElementColors(el, style, tag, window, customPropMap, hasAnchorInhe
|
|
|
2550
2549
|
effectiveBg,
|
|
2551
2550
|
effectiveBgStops: effectiveBg ? null : resolveGradientStops(el, window),
|
|
2552
2551
|
fontSize: parseFloat(style.fontSize) || 16,
|
|
2553
|
-
fontWeight: parseInt(style.fontWeight) || 400,
|
|
2552
|
+
fontWeight: parseInt(style.fontWeight, 10) || 400,
|
|
2554
2553
|
hasDirectText,
|
|
2555
2554
|
isEmojiOnly: isEmojiOnlyText(directText),
|
|
2556
2555
|
bgClip: style.webkitBackgroundClip || style.backgroundClip || '',
|
|
@@ -2677,7 +2676,7 @@ function checkTypography() {
|
|
|
2677
2676
|
let totalTextElements = 0;
|
|
2678
2677
|
for (const el of document.querySelectorAll('p, h1, h2, h3, h4, h5, h6, li, td, th, dd, blockquote, figcaption, a, button, label, span')) {
|
|
2679
2678
|
// Skip monodesign's own elements
|
|
2680
|
-
if (el.closest
|
|
2679
|
+
if (el.closest?.('.monodesign-overlay, .monodesign-label, .monodesign-banner, .monodesign-tooltip')) continue;
|
|
2681
2680
|
// Only count elements that actually have visible direct text
|
|
2682
2681
|
const hasText = [...el.childNodes].some(n => n.nodeType === 3 && n.textContent.trim().length > 0);
|
|
2683
2682
|
if (!hasText) continue;
|
|
@@ -2718,7 +2717,7 @@ function checkTypography() {
|
|
|
2718
2717
|
const sorted = [...sizes].sort((a, b) => a - b);
|
|
2719
2718
|
const ratio = sorted[sorted.length - 1] / sorted[0];
|
|
2720
2719
|
if (ratio < 2.0) {
|
|
2721
|
-
findings.push({ type: 'flat-type-hierarchy', detail: `Sizes: ${sorted.map(s => s
|
|
2720
|
+
findings.push({ type: 'flat-type-hierarchy', detail: `Sizes: ${sorted.map(s => `${s}px`).join(', ')} (ratio ${ratio.toFixed(1)}:1)` });
|
|
2722
2721
|
}
|
|
2723
2722
|
}
|
|
2724
2723
|
|
|
@@ -2837,7 +2836,7 @@ function checkPageTypography(doc, win) {
|
|
|
2837
2836
|
const sorted = [...sizes].sort((a, b) => a - b);
|
|
2838
2837
|
const ratio = sorted[sorted.length - 1] / sorted[0];
|
|
2839
2838
|
if (ratio < 2.0) {
|
|
2840
|
-
findings.push({ id: 'flat-type-hierarchy', snippet: `Sizes: ${sorted.map(s => s
|
|
2839
|
+
findings.push({ id: 'flat-type-hierarchy', snippet: `Sizes: ${sorted.map(s => `${s}px`).join(', ')} (ratio ${ratio.toFixed(1)}:1)` });
|
|
2841
2840
|
}
|
|
2842
2841
|
}
|
|
2843
2842
|
|
|
@@ -2969,7 +2968,7 @@ function checkCreamPalette(doc, win) {
|
|
|
2969
2968
|
// 2. Tailwind class fallback — for the static path, where utility classes
|
|
2970
2969
|
// never resolve to computed CSS.
|
|
2971
2970
|
for (const el of [body, html]) {
|
|
2972
|
-
const tok = creamFromClassList(el
|
|
2971
|
+
const tok = creamFromClassList(el?.getAttribute ? el.getAttribute('class') : '');
|
|
2973
2972
|
if (tok) {
|
|
2974
2973
|
findings.push({ id: 'cream-palette', snippet: `cream/beige page background (Tailwind ${tok})` });
|
|
2975
2974
|
break;
|
|
@@ -3005,7 +3004,7 @@ function checkOversizedH1({ tag, fontSize, headingText, rect = null, viewportWid
|
|
|
3005
3004
|
return [];
|
|
3006
3005
|
}
|
|
3007
3006
|
|
|
3008
|
-
function checkElementOversizedH1(el,
|
|
3007
|
+
function checkElementOversizedH1(el, _style, tag, window) {
|
|
3009
3008
|
if (tag !== 'h1') return [];
|
|
3010
3009
|
const fontSize = resolveFontSizePx(el, window);
|
|
3011
3010
|
const headingText = (el.textContent || '').trim().replace(/\s+/g, ' ');
|
|
@@ -3089,7 +3088,7 @@ function borderColorsFromStyle(style) {
|
|
|
3089
3088
|
];
|
|
3090
3089
|
}
|
|
3091
3090
|
|
|
3092
|
-
function checkElementGptBorderShadow(
|
|
3091
|
+
function checkElementGptBorderShadow(_el, style) {
|
|
3093
3092
|
return checkGptThinBorderWideShadow({ borderWidths: borderWidthsFromStyle(style), borderColors: borderColorsFromStyle(style), boxShadow: style.boxShadow || '' });
|
|
3094
3093
|
}
|
|
3095
3094
|
|
|
@@ -3239,7 +3238,7 @@ function checkClippedOverflow(el, style, getStyle) {
|
|
|
3239
3238
|
return [];
|
|
3240
3239
|
}
|
|
3241
3240
|
|
|
3242
|
-
function checkElementClippedOverflow(el, style,
|
|
3241
|
+
function checkElementClippedOverflow(el, style, _tag, window) {
|
|
3243
3242
|
return checkClippedOverflow(el, style, (n) => window.getComputedStyle(n));
|
|
3244
3243
|
}
|
|
3245
3244
|
|
|
@@ -3358,6 +3357,29 @@ function checkElementTextOverflowDOM(el) {
|
|
|
3358
3357
|
// conditions can't be read off a resting element. Shared by the regex, static
|
|
3359
3358
|
// and browser engines (each hands over its own CSS text).
|
|
3360
3359
|
|
|
3360
|
+
// Split a selector list on top-level commas only — i.e. commas inside a
|
|
3361
|
+
// functional pseudo-class's parens (`:is(a, button)`, `:where(a, b)`,
|
|
3362
|
+
// `:not(a, b)`, `:has(x, y)`) don't split. A naive `str.split(',')` breaks
|
|
3363
|
+
// `:is(a, button):focus { outline: none }` into `:is(a` and `button):focus`,
|
|
3364
|
+
// which both selectorTargetsInteractive() and the dark/light selector maps
|
|
3365
|
+
// below then treat as two unrelated (and malformed) selectors — producing
|
|
3366
|
+
// false-positive missing-focus-visible findings and double-counted rules.
|
|
3367
|
+
function splitTopLevelCommas(str) {
|
|
3368
|
+
const parts = [];
|
|
3369
|
+
let depth = 0, start = 0;
|
|
3370
|
+
for (let k = 0; k < str.length; k++) {
|
|
3371
|
+
const ch = str[k];
|
|
3372
|
+
if (ch === '(') depth++;
|
|
3373
|
+
else if (ch === ')') depth = Math.max(0, depth - 1);
|
|
3374
|
+
else if (ch === ',' && depth === 0) {
|
|
3375
|
+
parts.push(str.slice(start, k));
|
|
3376
|
+
start = k + 1;
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
parts.push(str.slice(start));
|
|
3380
|
+
return parts;
|
|
3381
|
+
}
|
|
3382
|
+
|
|
3361
3383
|
// Parse flat CSS into { selector, body, dark } tuples, tracking one level of
|
|
3362
3384
|
// dark-scheme context (a `@media (prefers-color-scheme: dark)` ancestor).
|
|
3363
3385
|
// @supports / @layer / @container / @scope are transparently descended.
|
|
@@ -3387,7 +3409,7 @@ function collectCssRules(css, dark = false, depth = 0) {
|
|
|
3387
3409
|
}
|
|
3388
3410
|
continue;
|
|
3389
3411
|
}
|
|
3390
|
-
for (const sel of prelude
|
|
3412
|
+
for (const sel of splitTopLevelCommas(prelude).map(s => s.trim()).filter(Boolean)) {
|
|
3391
3413
|
out.push({ selector: sel, body, dark });
|
|
3392
3414
|
}
|
|
3393
3415
|
}
|
|
@@ -3562,7 +3584,7 @@ function checkElementImageDimensions(el, style) {
|
|
|
3562
3584
|
const parts = [el.getAttribute?.('style') || el.attribs?.style || ''];
|
|
3563
3585
|
const ar = style && (style.aspectRatio || style['aspect-ratio']);
|
|
3564
3586
|
if (ar && ar !== 'auto') parts.push(`aspect-ratio:${ar}`);
|
|
3565
|
-
const h = style
|
|
3587
|
+
const h = style?.height;
|
|
3566
3588
|
if (h && h !== 'auto' && h !== '') parts.push(`height:${h}`);
|
|
3567
3589
|
return checkImageDimensions({ hasWidthAttr, hasHeightAttr, style: parts.join(';') });
|
|
3568
3590
|
}
|
|
@@ -3622,7 +3644,7 @@ function checkElementSmallTouchTarget(el, style, tag) {
|
|
|
3622
3644
|
// (button-like). jsdom returns '' for the default inline display, so key
|
|
3623
3645
|
// off "not blockish" rather than "== inline".
|
|
3624
3646
|
const isBlockish = /^(block|flex|grid|inline-block|inline-flex|table)/.test(style.display || '');
|
|
3625
|
-
const inProse = !!
|
|
3647
|
+
const inProse = !!el.closest?.(PROSE_ANCESTOR_SELECTOR);
|
|
3626
3648
|
const isInlineLinkInProse = tag === 'a' && !isBlockish && inProse;
|
|
3627
3649
|
return checkSmallTouchTarget({ tag, role, href, inputType, widthPx, heightPx, isInlineLinkInProse });
|
|
3628
3650
|
}
|
|
@@ -3636,7 +3658,7 @@ function checkElementSmallTouchTargetDOM(el) {
|
|
|
3636
3658
|
const inputType = el.getAttribute('type') || '';
|
|
3637
3659
|
const style = getComputedStyle(el);
|
|
3638
3660
|
const isBlockish = /^(block|flex|grid|inline-block|inline-flex|table)/.test(style.display || '');
|
|
3639
|
-
const inProse = !!
|
|
3661
|
+
const inProse = !!el.closest?.(PROSE_ANCESTOR_SELECTOR);
|
|
3640
3662
|
const isInlineLinkInProse = tag === 'a' && !isBlockish && inProse;
|
|
3641
3663
|
return checkSmallTouchTarget({ tag, role, href, inputType, widthPx: rect.width, heightPx: rect.height, isInlineLinkInProse });
|
|
3642
3664
|
}
|
|
@@ -3756,7 +3778,7 @@ if (IS_BROWSER) {
|
|
|
3756
3778
|
}
|
|
3757
3779
|
|
|
3758
3780
|
function showSpotlight(target) {
|
|
3759
|
-
if (!target
|
|
3781
|
+
if (!target?.getBoundingClientRect) return;
|
|
3760
3782
|
// Respect the spotlightBlur setting: if disabled, don't show the backdrop
|
|
3761
3783
|
if (window.__MONODESIGN_CONFIG__?.spotlightBlur === false) {
|
|
3762
3784
|
spotlightTarget = target;
|
|
@@ -3902,10 +3924,8 @@ if (IS_BROWSER) {
|
|
|
3902
3924
|
}
|
|
3903
3925
|
});
|
|
3904
3926
|
|
|
3905
|
-
const highlight =
|
|
3927
|
+
const highlight = (el, findings) => {
|
|
3906
3928
|
if (el._monodesignOverlay) detachOverlay(el._monodesignOverlay);
|
|
3907
|
-
const hasSlop = findings.some(f => RULE_CATEGORY[f.type || f.id] === 'slop');
|
|
3908
|
-
|
|
3909
3929
|
const fixed = isInFixedContext(el);
|
|
3910
3930
|
const rect = el.getBoundingClientRect();
|
|
3911
3931
|
const outline = document.createElement('div');
|
|
@@ -4049,7 +4069,7 @@ if (IS_BROWSER) {
|
|
|
4049
4069
|
overlays.push(outline);
|
|
4050
4070
|
};
|
|
4051
4071
|
|
|
4052
|
-
const showPageBanner =
|
|
4072
|
+
const showPageBanner = (findings) => {
|
|
4053
4073
|
if (!findings.length) return;
|
|
4054
4074
|
const banner = document.createElement('div');
|
|
4055
4075
|
banner.className = 'monodesign-overlay monodesign-banner';
|
|
@@ -4149,7 +4169,7 @@ if (IS_BROWSER) {
|
|
|
4149
4169
|
.filter(c => !c.startsWith('monodesign-') && !isLikelyHashedClass(c))
|
|
4150
4170
|
.slice(0, 2);
|
|
4151
4171
|
if (classes.length > 0) {
|
|
4152
|
-
sel +=
|
|
4172
|
+
sel += `.${classes.map(c => CSS.escape(c)).join('.')}`;
|
|
4153
4173
|
}
|
|
4154
4174
|
}
|
|
4155
4175
|
|
|
@@ -4157,7 +4177,7 @@ if (IS_BROWSER) {
|
|
|
4157
4177
|
const parent = el.parentElement;
|
|
4158
4178
|
if (parent) {
|
|
4159
4179
|
try {
|
|
4160
|
-
const matching = parent.querySelectorAll(
|
|
4180
|
+
const matching = parent.querySelectorAll(`:scope > ${sel}`);
|
|
4161
4181
|
if (matching.length > 1) {
|
|
4162
4182
|
const sameType = [...parent.children].filter(c => c.tagName === el.tagName);
|
|
4163
4183
|
const idx = sameType.indexOf(el) + 1;
|
|
@@ -4174,7 +4194,7 @@ if (IS_BROWSER) {
|
|
|
4174
4194
|
function generateSelector(el) {
|
|
4175
4195
|
if (el === document.body) return 'body';
|
|
4176
4196
|
if (el === document.documentElement) return 'html';
|
|
4177
|
-
if (el.id) return
|
|
4197
|
+
if (el.id) return `#${CSS.escape(el.id)}`;
|
|
4178
4198
|
|
|
4179
4199
|
const parts = [];
|
|
4180
4200
|
let current = el;
|
|
@@ -4186,7 +4206,7 @@ if (IS_BROWSER) {
|
|
|
4186
4206
|
|
|
4187
4207
|
// Anchor on an ancestor's ID and stop walking up
|
|
4188
4208
|
if (current.id) {
|
|
4189
|
-
parts[0] =
|
|
4209
|
+
parts[0] = `#${CSS.escape(current.id)}`;
|
|
4190
4210
|
break;
|
|
4191
4211
|
}
|
|
4192
4212
|
|
|
@@ -4326,7 +4346,7 @@ if (IS_BROWSER) {
|
|
|
4326
4346
|
|
|
4327
4347
|
const textColor = parseRgb(style.color);
|
|
4328
4348
|
const fontSize = parseFloat(style.fontSize) || 16;
|
|
4329
|
-
const fontWeight = parseInt(style.fontWeight) || 400;
|
|
4349
|
+
const fontWeight = parseInt(style.fontWeight, 10) || 400;
|
|
4330
4350
|
const isLargeText = fontSize >= WCAG_LARGE_TEXT_PX || (fontSize >= WCAG_LARGE_BOLD_TEXT_PX && fontWeight >= 700);
|
|
4331
4351
|
const threshold = isLargeText ? 3.0 : 4.5;
|
|
4332
4352
|
const clip = {
|
|
@@ -4544,7 +4564,7 @@ if (IS_BROWSER) {
|
|
|
4544
4564
|
function sampleDrawablePixel(drawable, sourcePoint) {
|
|
4545
4565
|
if (visualContrastRasterCache.has(drawable)) {
|
|
4546
4566
|
const cached = visualContrastRasterCache.get(drawable);
|
|
4547
|
-
if (!cached
|
|
4567
|
+
if (!cached?.ctx) return { status: 'unresolved', reason: cached?.reason || 'image sample failed' };
|
|
4548
4568
|
try {
|
|
4549
4569
|
const x = Math.max(0, Math.min(cached.width - 1, Math.floor(sourcePoint.x * cached.scaleX)));
|
|
4550
4570
|
const y = Math.max(0, Math.min(cached.height - 1, Math.floor(sourcePoint.y * cached.scaleY)));
|
|
@@ -4679,7 +4699,7 @@ if (IS_BROWSER) {
|
|
|
4679
4699
|
const unresolved = [];
|
|
4680
4700
|
|
|
4681
4701
|
for (const node of nodes) {
|
|
4682
|
-
if (
|
|
4702
|
+
if (node?.nodeType !== 1) continue;
|
|
4683
4703
|
if (node.closest?.('.monodesign-overlay, .monodesign-label, .monodesign-banner, .monodesign-tooltip')) continue;
|
|
4684
4704
|
const tag = node.tagName?.toLowerCase();
|
|
4685
4705
|
if (tag === 'img') {
|
|
@@ -4876,7 +4896,7 @@ if (IS_BROWSER) {
|
|
|
4876
4896
|
}));
|
|
4877
4897
|
}
|
|
4878
4898
|
|
|
4879
|
-
const printSummary =
|
|
4899
|
+
const printSummary = (allFindings) => {
|
|
4880
4900
|
if (allFindings.length === 0) {
|
|
4881
4901
|
console.log('%c[monodesign] No anti-patterns found.', 'color: #22c55e; font-weight: bold');
|
|
4882
4902
|
return;
|
|
@@ -5203,7 +5223,7 @@ if (IS_BROWSER) {
|
|
|
5203
5223
|
try { rules = sheet.cssRules; } catch { continue; }
|
|
5204
5224
|
if (!rules) continue;
|
|
5205
5225
|
for (const rule of Array.from(rules)) {
|
|
5206
|
-
try { stylesheetCssText += rule.cssText
|
|
5226
|
+
try { stylesheetCssText += `${rule.cssText}\n`; } catch { /* ignore */ }
|
|
5207
5227
|
}
|
|
5208
5228
|
}
|
|
5209
5229
|
const stylesheetFindings = [
|
|
@@ -5476,7 +5496,7 @@ if (IS_BROWSER) {
|
|
|
5476
5496
|
}
|
|
5477
5497
|
|
|
5478
5498
|
let firstScanDone = false;
|
|
5479
|
-
const scan =
|
|
5499
|
+
const scan = (options = {}) => {
|
|
5480
5500
|
clearOverlays();
|
|
5481
5501
|
const generation = scanGeneration;
|
|
5482
5502
|
const collected = collectBrowserFindings();
|
|
@@ -5493,7 +5513,7 @@ if (IS_BROWSER) {
|
|
|
5493
5513
|
return allFindings;
|
|
5494
5514
|
};
|
|
5495
5515
|
|
|
5496
|
-
const scanAsync = async
|
|
5516
|
+
const scanAsync = async (options = {}) => {
|
|
5497
5517
|
clearOverlays();
|
|
5498
5518
|
const generation = scanGeneration;
|
|
5499
5519
|
if (shouldRunVisualContrast(options)) {
|
|
@@ -5505,13 +5525,13 @@ if (IS_BROWSER) {
|
|
|
5505
5525
|
return renderBrowserFindings(collectBrowserFindings(), options);
|
|
5506
5526
|
};
|
|
5507
5527
|
|
|
5508
|
-
const detect =
|
|
5528
|
+
const detect = (options = {}) => {
|
|
5509
5529
|
lastVisualContrastAnalyses = [];
|
|
5510
5530
|
const { allFindings } = collectBrowserFindings();
|
|
5511
5531
|
return options.serialize === false ? allFindings : serializeFindings(allFindings);
|
|
5512
5532
|
};
|
|
5513
5533
|
|
|
5514
|
-
const detectAsync = async
|
|
5534
|
+
const detectAsync = async (options = {}) => {
|
|
5515
5535
|
if (shouldRunVisualContrast(options)) {
|
|
5516
5536
|
const { allFindings } = await collectBrowserFindingsAsync(options);
|
|
5517
5537
|
return options.serialize === false ? allFindings : serializeFindings(allFindings);
|
|
@@ -106,22 +106,22 @@ const REGEX_MATCHERS = [
|
|
|
106
106
|
fmt: (m) => `Google Fonts: ${m.overusedGoogleFont || firstOverusedGoogleFont(m[0])}` },
|
|
107
107
|
// --- Gradient text ---
|
|
108
108
|
{ id: 'gradient-text', regex: /background-clip\s*:\s*text|-webkit-background-clip\s*:\s*text/gi,
|
|
109
|
-
test: (
|
|
109
|
+
test: (_m, line) => /gradient/i.test(line),
|
|
110
110
|
fmt: () => 'background-clip: text + gradient' },
|
|
111
111
|
// --- Gradient text (Tailwind) ---
|
|
112
112
|
{ id: 'gradient-text', regex: /\bbg-clip-text\b/g,
|
|
113
|
-
test: (
|
|
113
|
+
test: (_m, line) => /\bbg-gradient-to-/i.test(line),
|
|
114
114
|
fmt: () => 'bg-clip-text + bg-gradient' },
|
|
115
115
|
// --- Tailwind gray on colored bg ---
|
|
116
116
|
{ id: 'gray-on-color', regex: /\btext-(?:gray|slate|zinc|neutral|stone)-(\d+)\b/g,
|
|
117
|
-
test: (
|
|
117
|
+
test: (_m, line) => /\bbg-(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d+\b/.test(line),
|
|
118
118
|
fmt: (m, line) => { const bg = line.match(/\bbg-(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d+\b/); return `${m[0]} on ${bg?.[0] || '?'}`; } },
|
|
119
119
|
// --- Tailwind AI palette ---
|
|
120
120
|
{ id: 'ai-color-palette', regex: /\btext-(?:purple|violet|indigo)-(\d+)\b/g,
|
|
121
|
-
test: (
|
|
121
|
+
test: (_m, line) => /\btext-(?:[2-9]xl|[3-9]xl)\b|<h[1-3]/i.test(line),
|
|
122
122
|
fmt: (m) => `${m[0]} on heading` },
|
|
123
123
|
{ id: 'ai-color-palette', regex: /\bfrom-(?:purple|violet|indigo)-(\d+)\b/g,
|
|
124
|
-
test: (
|
|
124
|
+
test: (_m, line) => /\bto-(?:purple|violet|indigo|blue|cyan|pink|fuchsia)-\d+\b/.test(line),
|
|
125
125
|
fmt: (m) => `${m[0]} gradient` },
|
|
126
126
|
// --- Bounce/elastic easing ---
|
|
127
127
|
{ id: 'bounce-easing', regex: /\banimate-bounce\b/g,
|
|
@@ -226,7 +226,7 @@ const REGEX_ANALYZERS = [
|
|
|
226
226
|
const lines = content.split('\n');
|
|
227
227
|
let line = 1;
|
|
228
228
|
for (let i = 0; i < lines.length; i++) { if (/font-size/i.test(lines[i]) || /\btext-(?:xs|sm|base|lg|xl|\d)/i.test(lines[i])) { line = i + 1; break; } }
|
|
229
|
-
return [finding('flat-type-hierarchy', filePath, `Sizes: ${sorted.map(s => s
|
|
229
|
+
return [finding('flat-type-hierarchy', filePath, `Sizes: ${sorted.map(s => `${s}px`).join(', ')} (ratio ${ratio.toFixed(1)}:1)`, line)];
|
|
230
230
|
},
|
|
231
231
|
// Monotonous spacing (regex)
|
|
232
232
|
(content, filePath) => {
|
|
@@ -118,17 +118,17 @@ function buildBorderOverrideMap(document, window) {
|
|
|
118
118
|
|
|
119
119
|
for (const [prop, side] of SIDE_PROPS) {
|
|
120
120
|
const val = rule.style[prop];
|
|
121
|
-
if (!val
|
|
121
|
+
if (!val?.includes('var(')) continue;
|
|
122
122
|
const parsed = parseShorthand(resolveVar(val));
|
|
123
|
-
if (parsed
|
|
123
|
+
if (parsed?.color) perSide[side] = parsed;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
// Uniform `border: <w> <style> var(...)` applies to every side the
|
|
127
127
|
// per-side map didn't already claim.
|
|
128
128
|
const borderAll = rule.style.border;
|
|
129
|
-
if (borderAll
|
|
129
|
+
if (borderAll?.includes('var(')) {
|
|
130
130
|
const parsed = parseShorthand(resolveVar(borderAll));
|
|
131
|
-
if (parsed
|
|
131
|
+
if (parsed?.color) {
|
|
132
132
|
for (const s of ['Top', 'Right', 'Bottom', 'Left']) {
|
|
133
133
|
if (!perSide[s]) perSide[s] = parsed;
|
|
134
134
|
}
|
|
@@ -144,7 +144,7 @@ function buildBorderOverrideMap(document, window) {
|
|
|
144
144
|
['borderBottomColor', 'Bottom'],
|
|
145
145
|
]) {
|
|
146
146
|
const val = rule.style[prop];
|
|
147
|
-
if (!val
|
|
147
|
+
if (!val?.includes('var(')) continue;
|
|
148
148
|
const resolved = resolveVar(val).trim();
|
|
149
149
|
if (!resolved) continue;
|
|
150
150
|
// Width may or may not come from this rule — that's fine; the
|
|
@@ -185,6 +185,7 @@ function buildBorderOverrideMap(document, window) {
|
|
|
185
185
|
// styles. We walk the source character-by-character, balancing braces
|
|
186
186
|
// so we correctly handle nested style rules inside the layer block.
|
|
187
187
|
function unwrapCssAtLayer(source) {
|
|
188
|
+
// biome-ignore lint/complexity/useOptionalChain: exported helper with no typed callers; `source?.includes` would throw for a falsy non-string (0/false) that this guard currently returns unchanged
|
|
188
189
|
if (!source || !source.includes('@layer')) return source;
|
|
189
190
|
// Find `@layer <name>? {` openers. The match starts at the @, and
|
|
190
191
|
// we then balance braces from the opening { onward.
|
|
@@ -74,14 +74,14 @@ function checkStaticPageTypography(document, window) {
|
|
|
74
74
|
const sorted = [...sizes].sort((a, b) => a - b);
|
|
75
75
|
const ratio = sorted[sorted.length - 1] / sorted[0];
|
|
76
76
|
if (ratio < 2.0) {
|
|
77
|
-
findings.push({ id: 'flat-type-hierarchy', snippet: `Sizes: ${sorted.map(s => s
|
|
77
|
+
findings.push({ id: 'flat-type-hierarchy', snippet: `Sizes: ${sorted.map(s => `${s}px`).join(', ')} (ratio ${ratio.toFixed(1)}:1)` });
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
return findings;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
function checkElementBrokenImage(el) {
|
|
84
|
-
const src =
|
|
84
|
+
const src = el.getAttribute?.('src') ?? el.attribs?.src;
|
|
85
85
|
// Missing src attribute entirely
|
|
86
86
|
if (src === undefined || src === null) {
|
|
87
87
|
return [{ id: 'broken-image', snippet: '<img> with no src attribute' }];
|
|
@@ -95,10 +95,10 @@ function checkElementBrokenImage(el) {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
const STATIC_ELEMENT_RULES = [
|
|
98
|
-
{ id: 'border-rules', selector: '*', run: (el, tag, style, window,
|
|
98
|
+
{ id: 'border-rules', selector: '*', run: (el, tag, style, window, _customPropMap) => checkElementBorders(tag, style, null, resolveBorderRadiusPx(el, style, parseFloat(style.width) || 0, window)) },
|
|
99
99
|
{ id: 'color-rules', selector: '*', run: (el, tag, style, window, customPropMap) => checkElementColors(el, style, tag, window, customPropMap, false) },
|
|
100
100
|
{ id: 'dark-glow', selector: '*', run: (el, tag, style, window, customPropMap) => checkElementGlow(tag, style, resolveBackground(el.parentElement || el, window, customPropMap)) },
|
|
101
|
-
{ id: 'motion-rules', selector: '*', run: (
|
|
101
|
+
{ id: 'motion-rules', selector: '*', run: (_el, tag, style) => checkElementMotion(tag, style) },
|
|
102
102
|
{ id: 'icon-tile-stack', selector: 'h1,h2,h3,h4,h5,h6', run: (el, tag, _style, window) => checkElementIconTile(el, tag, window) },
|
|
103
103
|
{ id: 'italic-serif-display', selector: 'h1,h2', run: (el, tag, style) => checkElementItalicSerif(el, style, tag) },
|
|
104
104
|
{ id: 'hero-eyebrow-chip', selector: 'h1', run: (el, tag, style, window, customPropMap) => checkElementHeroEyebrow(el, style, tag, window, customPropMap) },
|
|
@@ -106,8 +106,8 @@ const STATIC_ELEMENT_RULES = [
|
|
|
106
106
|
{ id: 'quality-rules', selector: '*', run: (el, tag, style, window) => checkElementQuality(el, style, tag, window) },
|
|
107
107
|
{ id: 'oversized-h1', selector: 'h1', run: (el, tag, style, window) => checkElementOversizedH1(el, style, tag, window) },
|
|
108
108
|
{ id: 'clipped-overflow-container', selector: '*', run: (el, tag, style, window) => checkElementClippedOverflow(el, style, tag, window) },
|
|
109
|
-
{ id: 'gpt-thin-border-wide-shadow', selector: '*', run: (el,
|
|
110
|
-
{ id: 'image-missing-dimensions', selector: 'img', run: (el,
|
|
109
|
+
{ id: 'gpt-thin-border-wide-shadow', selector: '*', run: (el, _tag, style) => checkElementGptBorderShadow(el, style) },
|
|
110
|
+
{ id: 'image-missing-dimensions', selector: 'img', run: (el, _tag, style) => checkElementImageDimensions(el, style) },
|
|
111
111
|
{ id: 'small-touch-target', selector: 'a,button,input,select,summary,[role=button],[role=link],[role=menuitem],[role=tab],[role=switch]', run: (el, tag, style) => checkElementSmallTouchTarget(el, style, tag) },
|
|
112
112
|
];
|
|
113
113
|
|
|
@@ -130,7 +130,7 @@ function groupByRule(findings) {
|
|
|
130
130
|
async function runFix(targets, options = {}) {
|
|
131
131
|
const cwd = options.cwd || process.cwd();
|
|
132
132
|
const dryRun = Boolean(options.dryRun);
|
|
133
|
-
const ruleFilter = options.rules
|
|
133
|
+
const ruleFilter = options.rules?.length ? new Set(options.rules) : null;
|
|
134
134
|
const config = options.noConfig
|
|
135
135
|
? { ignoreRules: [], ignoreFiles: [], ignoreValues: [] }
|
|
136
136
|
: readDetectionConfig(cwd);
|
|
@@ -409,8 +409,8 @@ async function runFixCli(args) {
|
|
|
409
409
|
const report = await runFix(targets, options);
|
|
410
410
|
for (const warning of report.warnings) process.stderr.write(`${warning}\n`);
|
|
411
411
|
const formatted = formatFixReport(report, jsonMode);
|
|
412
|
-
if (jsonMode || report.dryRun) process.stdout.write(formatted
|
|
413
|
-
else process.stderr.write(formatted
|
|
412
|
+
if (jsonMode || report.dryRun) process.stdout.write(`${formatted}\n`);
|
|
413
|
+
else process.stderr.write(`${formatted}\n`);
|
|
414
414
|
process.exit(!report.dryRun && report.remaining.length > 0 ? 2 : 0);
|
|
415
415
|
}
|
|
416
416
|
|
|
@@ -46,7 +46,7 @@ function resolveImport(specifier, fromDir, fileSet) {
|
|
|
46
46
|
}
|
|
47
47
|
// index file convention
|
|
48
48
|
for (const ext of SCANNABLE_EXTENSIONS) {
|
|
49
|
-
const indexFile = path.join(base,
|
|
49
|
+
const indexFile = path.join(base, `index${ext}`);
|
|
50
50
|
if (fileSet.has(indexFile)) return indexFile;
|
|
51
51
|
}
|
|
52
52
|
return null;
|
|
@@ -527,7 +527,7 @@ function filterByProviders(findings, providers = []) {
|
|
|
527
527
|
if (!GATED_PROVIDERS.size) return findings;
|
|
528
528
|
return findings.filter(f => {
|
|
529
529
|
const rule = getAntipattern(f.antipattern);
|
|
530
|
-
if (!rule
|
|
530
|
+
if (!rule?.gated) return true;
|
|
531
531
|
return enabled.has(rule.gated);
|
|
532
532
|
});
|
|
533
533
|
}
|