@monoes/monomindcli 2.14.1 → 2.15.1

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.
Files changed (197) hide show
  1. package/.claude/helpers/control-start.cjs +10 -13
  2. package/.claude/skills/monodesign/scripts/context.mjs +4 -4
  3. package/.claude/skills/monodesign/scripts/critique-storage.mjs +1 -1
  4. package/.claude/skills/monodesign/scripts/detect-csp.mjs +1 -6
  5. package/.claude/skills/monodesign/scripts/detector/browser/injected/index.mjs +16 -18
  6. package/.claude/skills/monodesign/scripts/detector/cli/main.mjs +3 -3
  7. package/.claude/skills/monodesign/scripts/detector/detect-antipatterns-browser.js +67 -47
  8. package/.claude/skills/monodesign/scripts/detector/engines/regex/detect-text.mjs +6 -6
  9. package/.claude/skills/monodesign/scripts/detector/engines/static-html/css-cascade.mjs +6 -5
  10. package/.claude/skills/monodesign/scripts/detector/engines/static-html/detect-html.mjs +6 -6
  11. package/.claude/skills/monodesign/scripts/detector/fix/index.mjs +3 -3
  12. package/.claude/skills/monodesign/scripts/detector/node/file-system.mjs +1 -1
  13. package/.claude/skills/monodesign/scripts/detector/registry/antipatterns.mjs +1 -1
  14. package/.claude/skills/monodesign/scripts/detector/rules/checks.mjs +24 -25
  15. package/.claude/skills/monodesign/scripts/detector/shared/color.mjs +2 -2
  16. package/.claude/skills/monodesign/scripts/detector/shared/constants.mjs +1 -1
  17. package/.claude/skills/monodesign/scripts/detector/shared/inline-ignores.mjs +2 -2
  18. package/.claude/skills/monodesign/scripts/hook-admin.mjs +6 -6
  19. package/.claude/skills/monodesign/scripts/hook-before-edit.mjs +4 -4
  20. package/.claude/skills/monodesign/scripts/hook-lib.mjs +20 -11
  21. package/.claude/skills/monodesign/scripts/hook.mjs +1 -1
  22. package/.claude/skills/monodesign/scripts/lib/design-parser.mjs +9 -42
  23. package/.claude/skills/monodesign/scripts/lib/is-generated.mjs +1 -1
  24. package/.claude/skills/monodesign/scripts/lib/monodesign-config.mjs +5 -5
  25. package/.claude/skills/monodesign/scripts/lib/monodesign-paths.mjs +1 -1
  26. package/.claude/skills/monodesign/scripts/live/event-validation.mjs +13 -13
  27. package/.claude/skills/monodesign/scripts/live/manual-apply.mjs +4 -4
  28. package/.claude/skills/monodesign/scripts/live/manual-edits-buffer.mjs +1 -1
  29. package/.claude/skills/monodesign/scripts/live/session-store.mjs +5 -5
  30. package/.claude/skills/monodesign/scripts/live/svelte-component.mjs +13 -13
  31. package/.claude/skills/monodesign/scripts/live/sveltekit-adapter.mjs +4 -4
  32. package/.claude/skills/monodesign/scripts/live/ui-core.mjs +1 -1
  33. package/.claude/skills/monodesign/scripts/live-accept.mjs +24 -24
  34. package/.claude/skills/monodesign/scripts/live-browser-dom.js +8 -7
  35. package/.claude/skills/monodesign/scripts/live-browser-session.js +8 -7
  36. package/.claude/skills/monodesign/scripts/live-browser.js +405 -432
  37. package/.claude/skills/monodesign/scripts/live-commit-manual-edits.mjs +6 -6
  38. package/.claude/skills/monodesign/scripts/live-copy-edit-agent.mjs +2 -2
  39. package/.claude/skills/monodesign/scripts/live-discard-manual-edits.mjs +1 -1
  40. package/.claude/skills/monodesign/scripts/live-inject.mjs +7 -7
  41. package/.claude/skills/monodesign/scripts/live-insert.mjs +11 -11
  42. package/.claude/skills/monodesign/scripts/live-manual-edit-evidence.mjs +4 -4
  43. package/.claude/skills/monodesign/scripts/live-poll.mjs +5 -5
  44. package/.claude/skills/monodesign/scripts/live-server.mjs +10 -10
  45. package/.claude/skills/monodesign/scripts/live-wrap.mjs +31 -41
  46. package/.claude/skills/monodesign/scripts/live.mjs +5 -5
  47. package/.claude/skills/monodesign/scripts/palette.mjs +3 -1
  48. package/.claude/skills/monodesign/scripts/pin.mjs +1 -1
  49. package/dist/src/commands/doc-filters.d.ts.map +1 -1
  50. package/dist/src/commands/doc-filters.js +6 -0
  51. package/dist/src/commands/doc-filters.js.map +1 -1
  52. package/dist/src/commands/doc-library.d.ts +2 -1
  53. package/dist/src/commands/doc-library.d.ts.map +1 -1
  54. package/dist/src/commands/doc-library.js +66 -2
  55. package/dist/src/commands/doc-library.js.map +1 -1
  56. package/dist/src/commands/doc.d.ts.map +1 -1
  57. package/dist/src/commands/doc.js +2 -0
  58. package/dist/src/commands/doc.js.map +1 -1
  59. package/dist/src/commands/org.d.ts.map +1 -1
  60. package/dist/src/commands/org.js +42 -4
  61. package/dist/src/commands/org.js.map +1 -1
  62. package/dist/src/commands/ui.d.ts.map +1 -1
  63. package/dist/src/commands/ui.js +60 -0
  64. package/dist/src/commands/ui.js.map +1 -1
  65. package/dist/src/knowledge/capture-envelope.d.ts +5 -0
  66. package/dist/src/knowledge/capture-envelope.d.ts.map +1 -1
  67. package/dist/src/knowledge/capture-envelope.js +2 -0
  68. package/dist/src/knowledge/capture-envelope.js.map +1 -1
  69. package/dist/src/knowledge/document-chunking.d.ts +42 -0
  70. package/dist/src/knowledge/document-chunking.d.ts.map +1 -0
  71. package/dist/src/knowledge/document-chunking.js +288 -0
  72. package/dist/src/knowledge/document-chunking.js.map +1 -0
  73. package/dist/src/knowledge/document-index.d.ts +124 -0
  74. package/dist/src/knowledge/document-index.d.ts.map +1 -0
  75. package/dist/src/knowledge/document-index.js +314 -0
  76. package/dist/src/knowledge/document-index.js.map +1 -0
  77. package/dist/src/knowledge/document-ingest.d.ts +23 -0
  78. package/dist/src/knowledge/document-ingest.d.ts.map +1 -0
  79. package/dist/src/knowledge/document-ingest.js +368 -0
  80. package/dist/src/knowledge/document-ingest.js.map +1 -0
  81. package/dist/src/knowledge/document-pipeline.d.ts +26 -208
  82. package/dist/src/knowledge/document-pipeline.d.ts.map +1 -1
  83. package/dist/src/knowledge/document-pipeline.js +25 -1104
  84. package/dist/src/knowledge/document-pipeline.js.map +1 -1
  85. package/dist/src/knowledge/document-search.d.ts +27 -0
  86. package/dist/src/knowledge/document-search.d.ts.map +1 -0
  87. package/dist/src/knowledge/document-search.js +138 -0
  88. package/dist/src/knowledge/document-search.js.map +1 -0
  89. package/dist/src/knowledge/document-store.d.ts +28 -0
  90. package/dist/src/knowledge/document-store.d.ts.map +1 -0
  91. package/dist/src/knowledge/document-store.js +61 -0
  92. package/dist/src/knowledge/document-store.js.map +1 -0
  93. package/dist/src/knowledge/document-types.d.ts +103 -0
  94. package/dist/src/knowledge/document-types.d.ts.map +1 -0
  95. package/dist/src/knowledge/document-types.js +12 -0
  96. package/dist/src/knowledge/document-types.js.map +1 -0
  97. package/dist/src/knowledge/highlights.d.ts +168 -0
  98. package/dist/src/knowledge/highlights.d.ts.map +1 -0
  99. package/dist/src/knowledge/highlights.js +311 -0
  100. package/dist/src/knowledge/highlights.js.map +1 -0
  101. package/dist/src/knowledge/lookup.d.ts +80 -0
  102. package/dist/src/knowledge/lookup.d.ts.map +1 -0
  103. package/dist/src/knowledge/lookup.js +156 -0
  104. package/dist/src/knowledge/lookup.js.map +1 -0
  105. package/dist/src/knowledge/okf-bundle.d.ts +19 -0
  106. package/dist/src/knowledge/okf-bundle.d.ts.map +1 -0
  107. package/dist/src/knowledge/okf-bundle.js +106 -0
  108. package/dist/src/knowledge/okf-bundle.js.map +1 -0
  109. package/dist/src/knowledge/profile-store.d.ts +134 -0
  110. package/dist/src/knowledge/profile-store.d.ts.map +1 -0
  111. package/dist/src/knowledge/profile-store.js +237 -0
  112. package/dist/src/knowledge/profile-store.js.map +1 -0
  113. package/dist/src/orgrt/agent-runner.d.ts +4 -0
  114. package/dist/src/orgrt/agent-runner.d.ts.map +1 -1
  115. package/dist/src/orgrt/agent-runner.js +22 -0
  116. package/dist/src/orgrt/agent-runner.js.map +1 -1
  117. package/dist/src/orgrt/antigravity-runner.d.ts.map +1 -1
  118. package/dist/src/orgrt/antigravity-runner.js +2 -1
  119. package/dist/src/orgrt/antigravity-runner.js.map +1 -1
  120. package/dist/src/orgrt/authority-mask.d.ts +32 -0
  121. package/dist/src/orgrt/authority-mask.d.ts.map +1 -0
  122. package/dist/src/orgrt/authority-mask.js +135 -0
  123. package/dist/src/orgrt/authority-mask.js.map +1 -0
  124. package/dist/src/orgrt/codex-runner.d.ts.map +1 -1
  125. package/dist/src/orgrt/codex-runner.js +2 -1
  126. package/dist/src/orgrt/codex-runner.js.map +1 -1
  127. package/dist/src/orgrt/copilot-runner.d.ts.map +1 -1
  128. package/dist/src/orgrt/copilot-runner.js +2 -1
  129. package/dist/src/orgrt/copilot-runner.js.map +1 -1
  130. package/dist/src/orgrt/crush-runner.d.ts.map +1 -1
  131. package/dist/src/orgrt/crush-runner.js +6 -1
  132. package/dist/src/orgrt/crush-runner.js.map +1 -1
  133. package/dist/src/orgrt/daemon.d.ts +4 -0
  134. package/dist/src/orgrt/daemon.d.ts.map +1 -1
  135. package/dist/src/orgrt/daemon.js +13 -2
  136. package/dist/src/orgrt/daemon.js.map +1 -1
  137. package/dist/src/orgrt/decisions.d.ts +9 -0
  138. package/dist/src/orgrt/decisions.d.ts.map +1 -1
  139. package/dist/src/orgrt/decisions.js +32 -5
  140. package/dist/src/orgrt/decisions.js.map +1 -1
  141. package/dist/src/orgrt/file-roots.d.ts +2 -1
  142. package/dist/src/orgrt/file-roots.d.ts.map +1 -1
  143. package/dist/src/orgrt/file-roots.js +7 -3
  144. package/dist/src/orgrt/file-roots.js.map +1 -1
  145. package/dist/src/orgrt/grok-runner.d.ts.map +1 -1
  146. package/dist/src/orgrt/grok-runner.js +2 -1
  147. package/dist/src/orgrt/grok-runner.js.map +1 -1
  148. package/dist/src/orgrt/hermes-runner.d.ts.map +1 -1
  149. package/dist/src/orgrt/hermes-runner.js +2 -1
  150. package/dist/src/orgrt/hermes-runner.js.map +1 -1
  151. package/dist/src/orgrt/inbox.d.ts.map +1 -1
  152. package/dist/src/orgrt/inbox.js +70 -4
  153. package/dist/src/orgrt/inbox.js.map +1 -1
  154. package/dist/src/orgrt/kimicode-runner.d.ts.map +1 -1
  155. package/dist/src/orgrt/kimicode-runner.js +2 -1
  156. package/dist/src/orgrt/kimicode-runner.js.map +1 -1
  157. package/dist/src/orgrt/opencode-runner.d.ts.map +1 -1
  158. package/dist/src/orgrt/opencode-runner.js +3 -2
  159. package/dist/src/orgrt/opencode-runner.js.map +1 -1
  160. package/dist/src/orgrt/pi-rpc-runner.d.ts.map +1 -1
  161. package/dist/src/orgrt/pi-rpc-runner.js +3 -2
  162. package/dist/src/orgrt/pi-rpc-runner.js.map +1 -1
  163. package/dist/src/orgrt/pi-runner.d.ts.map +1 -1
  164. package/dist/src/orgrt/pi-runner.js +2 -1
  165. package/dist/src/orgrt/pi-runner.js.map +1 -1
  166. package/dist/src/orgrt/policy.d.ts.map +1 -1
  167. package/dist/src/orgrt/policy.js +3 -0
  168. package/dist/src/orgrt/policy.js.map +1 -1
  169. package/dist/src/orgrt/qwen-rpc-runner.d.ts.map +1 -1
  170. package/dist/src/orgrt/qwen-rpc-runner.js +3 -2
  171. package/dist/src/orgrt/qwen-rpc-runner.js.map +1 -1
  172. package/dist/src/orgrt/qwen-runner.d.ts.map +1 -1
  173. package/dist/src/orgrt/qwen-runner.js +2 -1
  174. package/dist/src/orgrt/qwen-runner.js.map +1 -1
  175. package/dist/src/orgrt/role-sandbox.d.ts +21 -0
  176. package/dist/src/orgrt/role-sandbox.d.ts.map +1 -1
  177. package/dist/src/orgrt/role-sandbox.js +31 -6
  178. package/dist/src/orgrt/role-sandbox.js.map +1 -1
  179. package/dist/src/orgrt/session.d.ts.map +1 -1
  180. package/dist/src/orgrt/session.js +21 -2
  181. package/dist/src/orgrt/session.js.map +1 -1
  182. package/dist/src/orgrt/task-dag.d.ts +3 -0
  183. package/dist/src/orgrt/task-dag.d.ts.map +1 -1
  184. package/dist/src/orgrt/task-dag.js +1 -0
  185. package/dist/src/orgrt/task-dag.js.map +1 -1
  186. package/dist/src/orgrt/types.d.ts +5 -0
  187. package/dist/src/orgrt/types.d.ts.map +1 -1
  188. package/dist/src/orgrt/types.js +23 -1
  189. package/dist/src/orgrt/types.js.map +1 -1
  190. package/dist/src/ui/dashboard.html +28 -848
  191. package/dist/src/ui/human-auth.mjs +103 -0
  192. package/dist/src/ui/org-hil.mjs +86 -37
  193. package/dist/src/ui/org-runtime.mjs +21 -13
  194. package/dist/src/ui/routes-org.mjs +49 -445
  195. package/dist/src/ui/server.mjs +103 -4
  196. package/dist/tsconfig.tsbuildinfo +1 -1
  197. 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('.' + suffix));
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 || !bgImage.includes('gradient')) return [];
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 '#' + [c.r, c.g, c.b].map(v => v.toString(16).padStart(2, '0')).join('');
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 && effectiveBgStops.length ? effectiveBgStops : null);
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, headingFontSize,
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(el, style, widthPx, win) {
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 || !prop.startsWith('--')) continue;
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 * Math.pow(c, 1 / 2.4) - 0.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' && !(el.closest && el.closest('pre'));
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
- let childStyle = getComputedStyleFor(win, child);
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 && 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]');
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] && colors[s].startsWith('var(') && overrides && overrides[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 && el.closest('.monodesign-overlay, .monodesign-label, .monodesign-banner, .monodesign-tooltip')) continue;
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 + 'px').join(', ')} (ratio ${ratio.toFixed(1)}:1)` });
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 + 'px').join(', ')} (ratio ${ratio.toFixed(1)}:1)` });
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 && el.getAttribute ? el.getAttribute('class') : '');
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, style, tag, window) {
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(el, style) {
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, tag, window) {
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.split(',').map(s => s.trim()).filter(Boolean)) {
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 && style.height;
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 = !!(el.closest && el.closest(PROSE_ANCESTOR_SELECTOR));
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 = !!(el.closest && el.closest(PROSE_ANCESTOR_SELECTOR));
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 || !target.getBoundingClientRect) return;
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 = function(el, findings) {
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 = function(findings) {
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 += '.' + classes.map(c => CSS.escape(c)).join('.');
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(':scope > ' + sel);
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 '#' + CSS.escape(el.id);
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] = '#' + CSS.escape(current.id);
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 || !cached.ctx) return { status: 'unresolved', reason: cached?.reason || 'image sample failed' };
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 (!node || node.nodeType !== 1) continue;
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 = function(allFindings) {
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 + '\n'; } catch { /* ignore */ }
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 = function(options = {}) {
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 function(options = {}) {
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 = function(options = {}) {
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 function(options = {}) {
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: (m, line) => /gradient/i.test(line),
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: (m, line) => /\bbg-gradient-to-/i.test(line),
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: (m, line) => /\bbg-(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d+\b/.test(line),
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: (m, line) => /\btext-(?:[2-9]xl|[3-9]xl)\b|<h[1-3]/i.test(line),
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: (m, line) => /\bto-(?:purple|violet|indigo|blue|cyan|pink|fuchsia)-\d+\b/.test(line),
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 + 'px').join(', ')} (ratio ${ratio.toFixed(1)}:1)`, line)];
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 || !val.includes('var(')) continue;
121
+ if (!val?.includes('var(')) continue;
122
122
  const parsed = parseShorthand(resolveVar(val));
123
- if (parsed && parsed.color) perSide[side] = 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 && borderAll.includes('var(')) {
129
+ if (borderAll?.includes('var(')) {
130
130
  const parsed = parseShorthand(resolveVar(borderAll));
131
- if (parsed && parsed.color) {
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 || !val.includes('var(')) continue;
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 + 'px').join(', ')} (ratio ${ratio.toFixed(1)}:1)` });
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 = (el.getAttribute && el.getAttribute('src')) ?? el.attribs?.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, customPropMap) => checkElementBorders(tag, style, null, resolveBorderRadiusPx(el, style, parseFloat(style.width) || 0, 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: (el, tag, style) => checkElementMotion(tag, style) },
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, tag, style) => checkElementGptBorderShadow(el, style) },
110
- { id: 'image-missing-dimensions', selector: 'img', run: (el, tag, style) => checkElementImageDimensions(el, style) },
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 && options.rules.length ? new Set(options.rules) : null;
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 + '\n');
413
- else process.stderr.write(formatted + '\n');
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, 'index' + ext);
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 || !rule.gated) return true;
530
+ if (!rule?.gated) return true;
531
531
  return enabled.has(rule.gated);
532
532
  });
533
533
  }