monomind 2.5.2 → 2.5.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/README.md +1 -1
- package/package.json +1 -1
- package/packages/@monomind/cli/.claude/agents/github/repo-architect.md +3 -3
- package/packages/@monomind/cli/.claude/agents/github/sync-coordinator.md +2 -2
- package/packages/@monomind/cli/.claude/agents/specialists/integration-architect.md +4 -4
- package/packages/@monomind/cli/.claude/commands/github/repo-architect.md +4 -4
- package/packages/@monomind/cli/.claude/commands/github/sync-coordinator.md +2 -2
- package/packages/@monomind/cli/.claude/helpers/event-logger.cjs +2 -2
- package/packages/@monomind/cli/.claude/helpers/extras-registry.json +1 -1
- package/packages/@monomind/cli/.claude/helpers/handlers/agent-start-handler.cjs +2 -2
- package/packages/@monomind/cli/.claude/helpers/handlers/edit-handler.cjs +2 -2
- package/packages/@monomind/cli/.claude/helpers/handlers/gates-handler.cjs +6 -1
- package/packages/@monomind/cli/.claude/helpers/handlers/route-handler.cjs +1 -1
- package/packages/@monomind/cli/.claude/helpers/handlers/session-handler.cjs +1 -1
- package/packages/@monomind/cli/.claude/helpers/handlers/session-restore-handler.cjs +11 -11
- package/packages/@monomind/cli/.claude/helpers/handlers/task-handler.cjs +2 -2
- package/packages/@monomind/cli/.claude/helpers/hook-handler.cjs +8 -8
- package/packages/@monomind/cli/.claude/skills/mastermind-techport/SKILL.md +1 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/SKILL.md +2 -2
- package/packages/@monomind/cli/.claude/skills/monodesign/reference/antipatterns-catalog.md +47 -2
- package/packages/@monomind/cli/.claude/skills/monodesign/reference/critique.md +1 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/reference/live.md +0 -8
- package/packages/@monomind/cli/.claude/skills/monodesign/reference/polish.md +2 -2
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/context-signals.mjs +39 -6
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/context.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/critique-storage.mjs +175 -2
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detect-csp.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detect.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/browser/injected/index.mjs +25 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/cli/main.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/design-system.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/detect-antipatterns-browser.js +356 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/detect-antipatterns.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/engines/browser/detect-url.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/engines/browser/drivers.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/engines/regex/detect-text.mjs +63 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/engines/static-html/css-cascade.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/engines/static-html/detect-html.mjs +19 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/engines/visual/screenshot-contrast.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/findings.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/fix/css-regions.mjs +181 -0
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/fix/fixers.mjs +373 -0
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/fix/index.mjs +426 -0
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/node/file-system.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/profile/profiler.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/registry/antipatterns.mjs +49 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/rules/checks.mjs +292 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/shared/color.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/shared/constants.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/shared/fonts.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/shared/inline-ignores.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/shared/page.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/hook-admin.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/hook-before-edit.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/hook-lib.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/hook.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/lib/design-parser.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/lib/is-generated.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/lib/monodesign-config.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/lib/monodesign-paths.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/lib/provider.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/lib/target-args.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live/browser-script-parts.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live/completion.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live/event-validation.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live/insert-ui.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live/manual-apply.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live/manual-edit-routes.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live/manual-edits-buffer.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live/session-store.mjs +5 -2
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live/svelte-component.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live/sveltekit-adapter.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live/ui-core.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live/vocabulary.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-accept.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-browser-dom.js +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-browser-session.js +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-browser.js +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-commit-manual-edits.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-complete.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-copy-edit-agent.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-discard-manual-edits.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-inject.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-insert.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-manual-edit-evidence.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-poll.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-resume.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-server.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-status.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-target.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-wrap.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/modern-screenshot.umd.js +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/palette.mjs +0 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/pin.mjs +0 -1
- package/packages/@monomind/cli/README.md +1 -1
- package/packages/@monomind/cli/dist/src/commands/design-detect.js +62 -1
- package/packages/@monomind/cli/dist/src/commands/design-palette.d.ts +0 -1
- package/packages/@monomind/cli/dist/src/commands/design-palette.js +0 -1
- package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.d.ts +1 -1
- package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.js +1 -1
- package/packages/@monomind/cli/dist/src/commands/hooks-workers.d.ts +1 -1
- package/packages/@monomind/cli/dist/src/commands/hooks-workers.js +8 -8
- package/packages/@monomind/cli/dist/src/commands/init.js +2 -2
- package/packages/@monomind/cli/dist/src/index.d.ts +1 -1
- package/packages/@monomind/cli/dist/src/index.js +3 -3
- package/packages/@monomind/cli/dist/src/init/claudemd-generator.js +5 -5
- package/packages/@monomind/cli/dist/src/init/executor.js +3 -3
- package/packages/@monomind/cli/dist/src/knowledge/document-pipeline.js +3 -1
- package/packages/@monomind/cli/dist/src/mcp-server.js +2 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/hooks-intelligence.d.ts +1 -1
- package/packages/@monomind/cli/dist/src/mcp-tools/hooks-intelligence.js +1 -1
- package/packages/@monomind/cli/dist/src/mcp-tools/hooks-routing.js +2 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/hooks-tools.js +1 -1
- package/packages/@monomind/cli/dist/src/memory/memory-kg.d.ts +5 -4
- package/packages/@monomind/cli/dist/src/memory/memory-kg.js +5 -4
- package/packages/@monomind/cli/dist/src/routing/embed-worker.js +1 -1
- package/packages/@monomind/cli/dist/src/routing/llm-caller.js +1 -1
- package/packages/@monomind/cli/dist/src/routing/route-layer-factory.js +1 -1
- package/packages/@monomind/cli/dist/src/ui/dashboard.html +22 -20
- package/packages/@monomind/cli/dist/src/update/checker.js +3 -3
- package/packages/@monomind/cli/package.json +5 -5
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
|
|
2
1
|
import {
|
|
3
2
|
BORDER_SAFE_TAGS,
|
|
4
3
|
GENERIC_FONTS,
|
|
@@ -2630,6 +2629,288 @@ function checkElementTextOverflowDOM(el) {
|
|
|
2630
2629
|
return [];
|
|
2631
2630
|
}
|
|
2632
2631
|
|
|
2632
|
+
// ─── Section 7: Stylesheet-level accessibility rules ─────────────────────────
|
|
2633
|
+
// These operate on raw CSS text (concatenated <style> blocks + linked sheets),
|
|
2634
|
+
// not per-element computed style — because :hover / :focus-visible / @media
|
|
2635
|
+
// conditions can't be read off a resting element. Shared by the regex, static
|
|
2636
|
+
// and browser engines (each hands over its own CSS text).
|
|
2637
|
+
|
|
2638
|
+
// Parse flat CSS into { selector, body, dark } tuples, tracking one level of
|
|
2639
|
+
// dark-scheme context (a `@media (prefers-color-scheme: dark)` ancestor).
|
|
2640
|
+
// @supports / @layer / @container / @scope are transparently descended.
|
|
2641
|
+
// @font-face / @keyframes and other at-rules are skipped.
|
|
2642
|
+
function collectCssRules(css, dark = false, depth = 0) {
|
|
2643
|
+
const out = [];
|
|
2644
|
+
if (!css || depth > 6) return out;
|
|
2645
|
+
const src = css.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
2646
|
+
let i = 0;
|
|
2647
|
+
while (i < src.length) {
|
|
2648
|
+
const braceStart = src.indexOf('{', i);
|
|
2649
|
+
if (braceStart === -1) break;
|
|
2650
|
+
const prelude = src.slice(i, braceStart).trim();
|
|
2651
|
+
let d = 1, j = braceStart + 1;
|
|
2652
|
+
for (; j < src.length; j++) {
|
|
2653
|
+
if (src[j] === '{') d++;
|
|
2654
|
+
else if (src[j] === '}') { d--; if (d === 0) break; }
|
|
2655
|
+
}
|
|
2656
|
+
const body = src.slice(braceStart + 1, j);
|
|
2657
|
+
i = j + 1;
|
|
2658
|
+
if (!prelude) continue;
|
|
2659
|
+
if (prelude.startsWith('@')) {
|
|
2660
|
+
const atName = prelude.slice(1).split(/[\s(]/)[0].toLowerCase();
|
|
2661
|
+
if (['media', 'supports', 'layer', 'container', 'scope'].includes(atName)) {
|
|
2662
|
+
const isDarkMedia = atName === 'media' && /prefers-color-scheme\s*:\s*dark/i.test(prelude);
|
|
2663
|
+
out.push(...collectCssRules(body, dark || isDarkMedia, depth + 1));
|
|
2664
|
+
}
|
|
2665
|
+
continue;
|
|
2666
|
+
}
|
|
2667
|
+
for (const sel of prelude.split(',').map(s => s.trim()).filter(Boolean)) {
|
|
2668
|
+
out.push({ selector: sel, body, dark });
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
return out;
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
// --- missing-focus-visible ---
|
|
2675
|
+
function selectorTargetsInteractive(sel) {
|
|
2676
|
+
return /(^|[\s>+~(,])(a|button|input|select|textarea|summary)(\b|[.:#[])/i.test(sel)
|
|
2677
|
+
|| /:focus\b/i.test(sel)
|
|
2678
|
+
|| /\[role\s*=\s*["']?button["']?\]/i.test(sel);
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
function checkFocusVisible(cssText) {
|
|
2682
|
+
if (!cssText) return [];
|
|
2683
|
+
const rules = collectCssRules(cssText);
|
|
2684
|
+
let suppressor = null;
|
|
2685
|
+
let hasFocusVisible = false;
|
|
2686
|
+
let hasFocusReplacement = false;
|
|
2687
|
+
for (const r of rules) {
|
|
2688
|
+
const sel = r.selector.toLowerCase();
|
|
2689
|
+
const body = r.body.toLowerCase();
|
|
2690
|
+
if (/:focus-visible/.test(sel)) {
|
|
2691
|
+
// Any :focus-visible styling counts as deliberate focus handling.
|
|
2692
|
+
hasFocusVisible = true;
|
|
2693
|
+
}
|
|
2694
|
+
const suppresses = /outline\s*:\s*(none|0(?:px)?)\b/.test(body)
|
|
2695
|
+
|| /outline-style\s*:\s*none/.test(body)
|
|
2696
|
+
|| /outline-width\s*:\s*0(?:px)?\b/.test(body);
|
|
2697
|
+
if (suppresses && selectorTargetsInteractive(sel) && !/:focus-visible/.test(sel)) {
|
|
2698
|
+
suppressor = suppressor || r;
|
|
2699
|
+
}
|
|
2700
|
+
// A plain :focus rule that draws a visible affordance is a valid fallback.
|
|
2701
|
+
if (/:focus\b/.test(sel) && !/:focus-visible/.test(sel)) {
|
|
2702
|
+
const olVal = /outline\s*:\s*([^;}]+)/.exec(body);
|
|
2703
|
+
const outlineReplaces = olVal && !/^\s*(none|0(?:px)?|inherit|initial|unset)\s*$/.test(olVal[1]);
|
|
2704
|
+
if (/box-shadow\s*:/.test(body)
|
|
2705
|
+
|| outlineReplaces
|
|
2706
|
+
|| /outline-(?:color|width|style)\s*:\s*(?!none|0(?:px)?\b)/.test(body)
|
|
2707
|
+
|| /border(?:-\w+)?(?:-color)?\s*:/.test(body)) {
|
|
2708
|
+
hasFocusReplacement = true;
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
if (suppressor && !hasFocusVisible && !hasFocusReplacement) {
|
|
2713
|
+
return [{ id: 'missing-focus-visible', snippet: `"${suppressor.selector}" removes outline with no :focus-visible replacement` }];
|
|
2714
|
+
}
|
|
2715
|
+
return [];
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2718
|
+
// --- hover-only-affordance ---
|
|
2719
|
+
function lastCompound(sel) {
|
|
2720
|
+
const parts = sel.split(/\s*[>+~]\s*|\s+/).filter(Boolean);
|
|
2721
|
+
const last = parts[parts.length - 1] || sel;
|
|
2722
|
+
return last.replace(/:{1,2}[a-z-]+(\([^)]*\))?/gi, '').trim().toLowerCase();
|
|
2723
|
+
}
|
|
2724
|
+
function bodyRevealsVisible(body) {
|
|
2725
|
+
const b = body.toLowerCase();
|
|
2726
|
+
if (/display\s*:\s*(block|flex|grid|inline(?:-\w+)?|table|list-item)/.test(b)) return true;
|
|
2727
|
+
if (/visibility\s*:\s*visible/.test(b)) return true;
|
|
2728
|
+
if (/opacity\s*:\s*(?:1(?:\.0+)?|0?\.[1-9]\d*|\.\d*[1-9])\b/.test(b)) return true;
|
|
2729
|
+
return false;
|
|
2730
|
+
}
|
|
2731
|
+
function bodyHidesByDefault(body) {
|
|
2732
|
+
const b = body.toLowerCase();
|
|
2733
|
+
return /display\s*:\s*none/.test(b)
|
|
2734
|
+
|| /visibility\s*:\s*hidden/.test(b)
|
|
2735
|
+
|| /opacity\s*:\s*0(?:\.0+)?\b/.test(b);
|
|
2736
|
+
}
|
|
2737
|
+
function checkHoverOnlyAffordance(cssText) {
|
|
2738
|
+
if (!cssText) return [];
|
|
2739
|
+
const rules = collectCssRules(cssText);
|
|
2740
|
+
const focusTargets = new Set();
|
|
2741
|
+
const hiddenTargets = new Set();
|
|
2742
|
+
for (const r of rules) {
|
|
2743
|
+
const sel = r.selector;
|
|
2744
|
+
if (/:(focus|focus-within|focus-visible|active)\b/i.test(sel) && bodyRevealsVisible(r.body)) {
|
|
2745
|
+
focusTargets.add(lastCompound(sel));
|
|
2746
|
+
}
|
|
2747
|
+
if (!/:hover\b/i.test(sel) && bodyHidesByDefault(r.body)) {
|
|
2748
|
+
hiddenTargets.add(lastCompound(sel));
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
const findings = [];
|
|
2752
|
+
const seen = new Set();
|
|
2753
|
+
for (const r of rules) {
|
|
2754
|
+
const sel = r.selector;
|
|
2755
|
+
if (!/:hover\b/i.test(sel)) continue;
|
|
2756
|
+
if (!bodyRevealsVisible(r.body)) continue;
|
|
2757
|
+
const parts = sel.split(/\s*[>+~]\s*|\s+/).filter(Boolean);
|
|
2758
|
+
if (parts.length < 2) continue; // hover on the subject itself — can't hide+reveal
|
|
2759
|
+
if (/:hover\b/i.test(parts[parts.length - 1])) continue; // :hover is on the revealed subject
|
|
2760
|
+
const target = lastCompound(sel);
|
|
2761
|
+
if (!target || !hiddenTargets.has(target)) continue;
|
|
2762
|
+
if (focusTargets.has(target)) continue;
|
|
2763
|
+
if (seen.has(target)) continue;
|
|
2764
|
+
seen.add(target);
|
|
2765
|
+
findings.push({ id: 'hover-only-affordance', snippet: `"${target}" is revealed on :hover with no focus/active equivalent` });
|
|
2766
|
+
}
|
|
2767
|
+
return findings;
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
// --- dark-scheme-contrast-blindspot ---
|
|
2771
|
+
const DARK_SCOPE_RE = /(?:^|[\s>+~(,])(?:html|:root|body)?(?:\.dark|\.theme-dark|\.dark-mode)\b|\[data-theme\s*[~|]?=\s*["']?dark["']?\]|\[data-mode\s*=\s*["']?dark["']?\]|\[data-color-scheme\s*=\s*["']?dark["']?\]/i;
|
|
2772
|
+
function stripDarkScope(sel) {
|
|
2773
|
+
return sel
|
|
2774
|
+
.replace(/(?:html|:root|body)?(?:\.dark|\.theme-dark|\.dark-mode)\b/gi, '')
|
|
2775
|
+
.replace(/\[data-(?:theme|mode|color-scheme)\s*[~|]?=\s*["']?dark["']?\]/gi, '')
|
|
2776
|
+
.replace(/\s*[>+~]\s*/g, ' ')
|
|
2777
|
+
.replace(/\s+/g, ' ')
|
|
2778
|
+
.trim();
|
|
2779
|
+
}
|
|
2780
|
+
function normalizeSelKey(sel) {
|
|
2781
|
+
return sel.replace(/\s*[>+~]\s*/g, ' ').replace(/\s+/g, ' ').trim().toLowerCase();
|
|
2782
|
+
}
|
|
2783
|
+
function declHasColor(body) { return /(?:^|[;{\s])color\s*:/i.test(body); }
|
|
2784
|
+
function declHasBg(body) { return /(?:^|[;{\s])background(?:-color)?\s*:/i.test(body); }
|
|
2785
|
+
function checkDarkSchemeContrast(cssText) {
|
|
2786
|
+
if (!cssText) return [];
|
|
2787
|
+
const rules = collectCssRules(cssText);
|
|
2788
|
+
const hasDarkMedia = /@media[^{]*prefers-color-scheme\s*:\s*dark/i.test(cssText);
|
|
2789
|
+
const hasDarkSelectors = rules.some(r => DARK_SCOPE_RE.test(r.selector));
|
|
2790
|
+
if (!hasDarkMedia && !hasDarkSelectors) return [];
|
|
2791
|
+
|
|
2792
|
+
const light = new Map(); // base selector → { color, bg }
|
|
2793
|
+
const darkAgg = new Map();
|
|
2794
|
+
for (const r of rules) {
|
|
2795
|
+
const scopedDark = DARK_SCOPE_RE.test(r.selector);
|
|
2796
|
+
const isDark = r.dark || scopedDark;
|
|
2797
|
+
if (isDark) {
|
|
2798
|
+
const base = normalizeSelKey(scopedDark ? stripDarkScope(r.selector) : r.selector);
|
|
2799
|
+
if (!base) continue;
|
|
2800
|
+
const e = darkAgg.get(base) || { color: false, bg: false };
|
|
2801
|
+
if (declHasColor(r.body)) e.color = true;
|
|
2802
|
+
if (declHasBg(r.body)) e.bg = true;
|
|
2803
|
+
darkAgg.set(base, e);
|
|
2804
|
+
} else {
|
|
2805
|
+
const base = normalizeSelKey(r.selector);
|
|
2806
|
+
if (!base) continue;
|
|
2807
|
+
const e = light.get(base) || { color: false, bg: false };
|
|
2808
|
+
if (declHasColor(r.body)) e.color = true;
|
|
2809
|
+
if (declHasBg(r.body)) e.bg = true;
|
|
2810
|
+
light.set(base, e);
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
const findings = [];
|
|
2815
|
+
for (const [base, d] of darkAgg) {
|
|
2816
|
+
if (d.color === d.bg) continue; // sets both or neither — not a split pair
|
|
2817
|
+
const lp = light.get(base);
|
|
2818
|
+
if (!lp || !(lp.color && lp.bg)) continue; // light scheme must pair both
|
|
2819
|
+
const changed = d.bg ? 'background' : 'text color';
|
|
2820
|
+
const missing = d.bg ? 'text color' : 'background';
|
|
2821
|
+
findings.push({ id: 'dark-scheme-contrast-blindspot', snippet: `"${base}" overrides ${changed} in dark scheme but not ${missing}` });
|
|
2822
|
+
}
|
|
2823
|
+
return findings;
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
// --- image-missing-dimensions ---
|
|
2827
|
+
function checkImageDimensions({ hasWidthAttr, hasHeightAttr, style }) {
|
|
2828
|
+
const s = String(style || '').toLowerCase();
|
|
2829
|
+
if (hasWidthAttr && hasHeightAttr) return [];
|
|
2830
|
+
if (/aspect-?ratio\s*:/.test(s)) return [];
|
|
2831
|
+
const heightM = /(?:^|[;{\s])height\s*:\s*([^;]+)/.exec(s);
|
|
2832
|
+
if (heightM && !/^\s*(auto|inherit|initial|unset|0(?:px)?)\s*$/.test(heightM[1])) return [];
|
|
2833
|
+
return [{ id: 'image-missing-dimensions', snippet: '<img> without width/height attributes or CSS aspect-ratio (layout shift risk)' }];
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2836
|
+
function checkElementImageDimensions(el, style) {
|
|
2837
|
+
const hasWidthAttr = (el.getAttribute?.('width') ?? el.attribs?.width) != null;
|
|
2838
|
+
const hasHeightAttr = (el.getAttribute?.('height') ?? el.attribs?.height) != null;
|
|
2839
|
+
const parts = [el.getAttribute?.('style') || el.attribs?.style || ''];
|
|
2840
|
+
const ar = style && (style.aspectRatio || style['aspect-ratio']);
|
|
2841
|
+
if (ar && ar !== 'auto') parts.push(`aspect-ratio:${ar}`);
|
|
2842
|
+
const h = style && style.height;
|
|
2843
|
+
if (h && h !== 'auto' && h !== '') parts.push(`height:${h}`);
|
|
2844
|
+
return checkImageDimensions({ hasWidthAttr, hasHeightAttr, style: parts.join(';') });
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
function checkElementImageDimensionsDOM(el) {
|
|
2848
|
+
const hasWidthAttr = el.getAttribute('width') != null;
|
|
2849
|
+
const hasHeightAttr = el.getAttribute('height') != null;
|
|
2850
|
+
// Browser computed `height` always resolves to a used px value after layout,
|
|
2851
|
+
// so it can't tell a reserved height from a rendered one — only use inline
|
|
2852
|
+
// style + the computed aspect-ratio (which stays 'auto' unless authored).
|
|
2853
|
+
const parts = [el.getAttribute('style') || ''];
|
|
2854
|
+
const style = getComputedStyle(el);
|
|
2855
|
+
const ar = style.aspectRatio || '';
|
|
2856
|
+
if (ar && ar !== 'auto') parts.push(`aspect-ratio:${ar}`);
|
|
2857
|
+
return checkImageDimensions({ hasWidthAttr, hasHeightAttr, style: parts.join(';') });
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2860
|
+
// --- small-touch-target ---
|
|
2861
|
+
const CLICKABLE_INPUT_TYPES = new Set(['button', 'submit', 'reset', 'checkbox', 'radio', 'image']);
|
|
2862
|
+
const CLICKABLE_ROLES = new Set(['button', 'link', 'menuitem', 'menuitemcheckbox', 'menuitemradio', 'tab', 'switch', 'checkbox', 'radio', 'option']);
|
|
2863
|
+
function isClickableControl(tag, role, href, inputType) {
|
|
2864
|
+
if (tag === 'button' || tag === 'select' || tag === 'summary') return true;
|
|
2865
|
+
if (tag === 'a') return href != null && href !== '';
|
|
2866
|
+
if (tag === 'input') return CLICKABLE_INPUT_TYPES.has(String(inputType || '').toLowerCase());
|
|
2867
|
+
if (role && CLICKABLE_ROLES.has(role)) return true;
|
|
2868
|
+
return false;
|
|
2869
|
+
}
|
|
2870
|
+
function checkSmallTouchTarget({ tag, role, href, inputType, widthPx, heightPx, isInlineLinkInProse }) {
|
|
2871
|
+
if (!isClickableControl(tag, role, href, inputType)) return [];
|
|
2872
|
+
if (isInlineLinkInProse) return [];
|
|
2873
|
+
if (!(widthPx > 0) || !(heightPx > 0)) return [];
|
|
2874
|
+
if (widthPx >= 44 && heightPx >= 44) return [];
|
|
2875
|
+
const roleLabel = role && !['button', 'a', 'input', 'select'].includes(tag) ? `[role=${role}]` : '';
|
|
2876
|
+
return [{ id: 'small-touch-target', snippet: `<${tag}>${roleLabel} renders ${Math.round(widthPx)}x${Math.round(heightPx)}px (min 44x44)` }];
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2879
|
+
const PROSE_ANCESTOR_SELECTOR = 'p,li,blockquote,figcaption,dd,dt';
|
|
2880
|
+
function checkElementSmallTouchTarget(el, style, tag) {
|
|
2881
|
+
// Static path: only provable when width AND height are explicit px lengths
|
|
2882
|
+
// (jsdom performs no layout; rem/%/auto can't be resolved to a real box).
|
|
2883
|
+
const wRaw = style.width || '';
|
|
2884
|
+
const hRaw = style.height || '';
|
|
2885
|
+
if (!/px\s*$/.test(wRaw) || !/px\s*$/.test(hRaw)) return [];
|
|
2886
|
+
const widthPx = parseFloat(wRaw);
|
|
2887
|
+
const heightPx = parseFloat(hRaw);
|
|
2888
|
+
const href = el.getAttribute?.('href');
|
|
2889
|
+
const role = (el.getAttribute?.('role') || '').toLowerCase();
|
|
2890
|
+
const inputType = el.getAttribute?.('type') || '';
|
|
2891
|
+
// An <a> is a prose text link unless it is explicitly laid out as a block
|
|
2892
|
+
// (button-like). jsdom returns '' for the default inline display, so key
|
|
2893
|
+
// off "not blockish" rather than "== inline".
|
|
2894
|
+
const isBlockish = /^(block|flex|grid|inline-block|inline-flex|table)/.test(style.display || '');
|
|
2895
|
+
const inProse = !!(el.closest && el.closest(PROSE_ANCESTOR_SELECTOR));
|
|
2896
|
+
const isInlineLinkInProse = tag === 'a' && !isBlockish && inProse;
|
|
2897
|
+
return checkSmallTouchTarget({ tag, role, href, inputType, widthPx, heightPx, isInlineLinkInProse });
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2900
|
+
function checkElementSmallTouchTargetDOM(el) {
|
|
2901
|
+
const tag = el.tagName.toLowerCase();
|
|
2902
|
+
const rect = el.getBoundingClientRect();
|
|
2903
|
+
if (!(rect.width > 0) || !(rect.height > 0)) return [];
|
|
2904
|
+
const href = el.getAttribute('href');
|
|
2905
|
+
const role = (el.getAttribute('role') || '').toLowerCase();
|
|
2906
|
+
const inputType = el.getAttribute('type') || '';
|
|
2907
|
+
const style = getComputedStyle(el);
|
|
2908
|
+
const isBlockish = /^(block|flex|grid|inline-block|inline-flex|table)/.test(style.display || '');
|
|
2909
|
+
const inProse = !!(el.closest && el.closest(PROSE_ANCESTOR_SELECTOR));
|
|
2910
|
+
const isInlineLinkInProse = tag === 'a' && !isBlockish && inProse;
|
|
2911
|
+
return checkSmallTouchTarget({ tag, role, href, inputType, widthPx: rect.width, heightPx: rect.height, isInlineLinkInProse });
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2633
2914
|
export {
|
|
2634
2915
|
checkBorders,
|
|
2635
2916
|
isEmojiOnlyText,
|
|
@@ -2701,4 +2982,14 @@ export {
|
|
|
2701
2982
|
checkElementClippedOverflowDOM,
|
|
2702
2983
|
isScreenReaderOnlyTextStyle,
|
|
2703
2984
|
checkElementTextOverflowDOM,
|
|
2985
|
+
collectCssRules,
|
|
2986
|
+
checkFocusVisible,
|
|
2987
|
+
checkHoverOnlyAffordance,
|
|
2988
|
+
checkDarkSchemeContrast,
|
|
2989
|
+
checkImageDimensions,
|
|
2990
|
+
checkElementImageDimensions,
|
|
2991
|
+
checkElementImageDimensionsDOM,
|
|
2992
|
+
checkSmallTouchTarget,
|
|
2993
|
+
checkElementSmallTouchTarget,
|
|
2994
|
+
checkElementSmallTouchTargetDOM,
|
|
2704
2995
|
};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
|
|
2
1
|
// Source scripts default to slash commands. The provider build replaces only
|
|
3
2
|
// this exact declaration, avoiding heuristic rewrites across executable code.
|
|
4
3
|
export const MONODESIGN_COMMAND_PREFIX = '/'; // @monodesign-provider-command-prefix
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
|
|
2
1
|
export function completionTypeForAcceptResult(eventType, acceptResult) {
|
|
3
2
|
if (eventType === 'discard') return acceptResult?.handled === true ? 'discarded' : 'error';
|
|
4
3
|
if (acceptResult?.handled === true && acceptResult?.carbonize === true) return 'agent_done';
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
// Pattern adapted from impeccable (Apache-2.0) — rebranded for monomind
|
|
2
1
|
import fs from 'node:fs';
|
|
3
2
|
import path from 'node:path';
|
|
4
3
|
import { getLegacyLiveSessionsDir, getLiveSessionsDir } from '../lib/monodesign-paths.mjs';
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
// steer_done is terminal too: a steer session has no pending work after the
|
|
6
|
+
// agent's reply, and leaving it "active" makes live-status suggest recovery
|
|
7
|
+
// for finished steers forever (the browser's trailing steer checkpoint would
|
|
8
|
+
// also re-stamp phase "steer"; terminal phases ignore late checkpoints).
|
|
9
|
+
const COMPLETED_PHASES = new Set(['completed', 'discarded', 'steer_done']);
|
|
7
10
|
|
|
8
11
|
export function createLiveSessionStore({ cwd = process.cwd(), sessionId } = {}) {
|
|
9
12
|
const rootDir = getLiveSessionsDir(cwd);
|