@vibecodeqa/cli 0.36.1 → 0.37.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/dist/monitor.js CHANGED
@@ -78,9 +78,31 @@ function copyToClipboard(text) {
78
78
  return false;
79
79
  }
80
80
  }
81
- function buildFixPrompt(checkName, issue) {
81
+ function buildFixPrompt(checkName, issue, cwd) {
82
82
  const loc = issue.file ? `${issue.file}${issue.line ? `:${issue.line}` : ""}` : "";
83
- return `Fix this ${issue.severity} in ${loc || "the project"}:\n${issue.message}${issue.rule ? ` (${issue.rule})` : ""}\nCheck: ${checkName}\n\nAnalyze the code, explain the issue, and provide the fix.`;
83
+ let prompt = `Fix this ${issue.severity} in ${loc || "the project"}:\n${issue.message}${issue.rule ? ` (${issue.rule})` : ""}\nCheck: ${checkName}`;
84
+ // Include source context if available
85
+ if (cwd && issue.file && issue.line) {
86
+ try {
87
+ const fullPath = join(cwd, issue.file);
88
+ if (existsSync(fullPath)) {
89
+ const content = readFileSync(fullPath, "utf-8");
90
+ const lines = content.split("\n");
91
+ const target = issue.line - 1;
92
+ const start = Math.max(0, target - 3);
93
+ const end = Math.min(lines.length, target + 4);
94
+ const snippet = lines.slice(start, end).map((l, i) => {
95
+ const num = start + i + 1;
96
+ const marker = num === issue.line ? ">>>" : " ";
97
+ return `${marker} ${num}: ${l}`;
98
+ }).join("\n");
99
+ prompt += `\n\nSource:\n${snippet}`;
100
+ }
101
+ }
102
+ catch { /* ignore */ }
103
+ }
104
+ prompt += "\n\nAnalyze the code, explain the issue, and provide the fix.";
105
+ return prompt;
84
106
  }
85
107
  // ── Scan via child process — UI never freezes ──
86
108
  function runScanProcess(cwd, skipTests) {
@@ -304,8 +326,12 @@ function readSourceContext(cwd, file, line, rule) {
304
326
  }
305
327
  function IssueDetail({ issue, checkName, cwd, height, copied }) {
306
328
  const ctx = readSourceContext(cwd, issue.file, issue.line, issue.rule);
307
- const bodyHeight = height - 8;
308
- return (_jsxs(Box, { flexDirection: "column", height: height, paddingX: 1, overflowY: "hidden", children: [_jsx(Text, { bold: true, color: "magenta", children: " \u25C8 Issue Detail" }), _jsxs(Text, { children: [_jsxs(Text, { color: sc(issue.severity), bold: true, children: [" ", issue.severity.toUpperCase(), " "] }), _jsx(Text, { dimColor: true, children: checkName }), issue.rule && _jsxs(Text, { dimColor: true, children: [" \u00B7 ", issue.rule] }), copied && _jsx(Text, { color: "green", bold: true, children: " \u2713 Copied!" })] }), _jsxs(Text, { wrap: "wrap", children: [" ", issue.message] }), issue.file && (_jsxs(Text, { color: "cyan", children: [" ", issue.file, issue.line ? `:${issue.line}` : ""] })), _jsx(Text, { children: " " }), ctx ? (_jsxs(Box, { flexDirection: "column", height: bodyHeight, overflowY: "hidden", children: [_jsxs(Text, { dimColor: true, children: [" \u2500\u2500\u2500 ", ctx.filePath, " \u2500\u2500\u2500"] }), ctx.lines.slice(0, bodyHeight - 2).map((l) => (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: l.highlight ? "yellow" : "gray", children: l.highlight ? "▸" : " " }), _jsxs(Text, { dimColor: true, children: [String(l.num).padStart(4), "\u2502"] }), _jsx(Text, { color: l.highlight ? "white" : undefined, children: l.text })] }, l.num))), _jsx(Text, { dimColor: true, children: " \u2500\u2500\u2500" })] })) : (_jsxs(Text, { dimColor: true, children: [" Source not available", issue.file ? ` (${issue.file})` : ""] }))] }));
329
+ const prompt = buildFixPrompt(checkName, issue, cwd);
330
+ // Split height: source gets top half, prompt gets bottom
331
+ const srcHeight = ctx ? Math.min(ctx.lines.length + 2, Math.floor((height - 8) * 0.6)) : 0;
332
+ const promptHeight = height - 8 - srcHeight;
333
+ const promptLines = prompt.split("\n");
334
+ return (_jsxs(Box, { flexDirection: "column", height: height, paddingX: 1, overflowY: "hidden", children: [_jsx(Text, { bold: true, color: "magenta", children: " \u25C8 Issue Detail" }), _jsxs(Text, { children: [_jsxs(Text, { color: sc(issue.severity), bold: true, children: [" ", issue.severity.toUpperCase(), " "] }), _jsx(Text, { dimColor: true, children: checkName }), issue.rule && _jsxs(Text, { dimColor: true, children: [" \u00B7 ", issue.rule] }), copied && _jsx(Text, { color: "green", bold: true, children: " \u2713 Copied!" })] }), _jsxs(Text, { wrap: "wrap", children: [" ", issue.message] }), issue.file && (_jsxs(Text, { color: "cyan", children: [" ", issue.file, issue.line ? `:${issue.line}` : ""] })), ctx && (_jsxs(Box, { flexDirection: "column", height: srcHeight, overflowY: "hidden", children: [_jsxs(Text, { dimColor: true, children: [" \u2500\u2500\u2500 ", ctx.filePath, " \u2500\u2500\u2500"] }), ctx.lines.slice(0, srcHeight - 2).map((l) => (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: l.highlight ? "yellow" : "gray", children: l.highlight ? "▸" : " " }), _jsxs(Text, { dimColor: true, children: [String(l.num).padStart(4), "\u2502"] }), _jsx(Text, { color: l.highlight ? "white" : undefined, children: l.text })] }, l.num))), _jsx(Text, { dimColor: true, children: " \u2500\u2500\u2500" })] })), _jsxs(Box, { flexDirection: "column", height: Math.max(3, promptHeight), overflowY: "hidden", marginTop: ctx ? 0 : 1, children: [_jsxs(Text, { bold: true, color: "green", children: [" Fix prompt ", _jsx(Text, { dimColor: true, children: "(y to copy)" })] }), promptLines.slice(0, Math.max(1, promptHeight - 1)).map((line, i) => (_jsxs(Text, { dimColor: true, wrap: "truncate", children: [" ", line] }, i)))] })] }));
309
335
  }
310
336
  function MonitorApp({ cwd }) {
311
337
  const { exit } = useApp();
@@ -544,7 +570,7 @@ function MonitorApp({ cwd }) {
544
570
  return;
545
571
  }
546
572
  if (input === "y" && check.issues[cursor]) {
547
- const prompt = buildFixPrompt(check.name, check.issues[cursor]);
573
+ const prompt = buildFixPrompt(check.name, check.issues[cursor], cwd);
548
574
  if (copyToClipboard(prompt)) {
549
575
  setCopied(true);
550
576
  addLog(`Copied fix prompt for ${check.name}:${check.issues[cursor].file || ""}`, "info");
@@ -557,7 +583,7 @@ function MonitorApp({ cwd }) {
557
583
  if (mode.view === "issue-detail") {
558
584
  const check = state.checks.find((c) => c.name === mode.checkName);
559
585
  if (check && check.issues[mode.issueIdx] && input === "y") {
560
- const prompt = buildFixPrompt(check.name, check.issues[mode.issueIdx]);
586
+ const prompt = buildFixPrompt(check.name, check.issues[mode.issueIdx], cwd);
561
587
  if (copyToClipboard(prompt)) {
562
588
  setCopied(true);
563
589
  addLog(`Copied fix prompt`, "info");
@@ -179,7 +179,8 @@ export function categoryPage(cs, fl, allChecks, cwd) {
179
179
  if (cwd && iss.line && typeof iss.file === "string") {
180
180
  const src = readSourceSnippet(cwd, iss.file, iss.line);
181
181
  if (src) {
182
- srcBlock = `<div class="src-block">${src}</div>`;
182
+ const fixPrompt = `Fix this ${iss.severity} in ${iss.file}:${iss.line}\n${iss.message}${iss.rule ? ` (${iss.rule})` : ""}\nCheck: ${c.name}\n\nAnalyze the code, explain the issue, and provide the fix.`;
183
+ srcBlock = `<div class="src-block">${src}<div class="src-prompt"><button class="cp-btn src-fix-btn" data-prompt="${e(fixPrompt)}" title="Copy fix prompt with source context">Copy fix prompt</button></div></div>`;
183
184
  }
184
185
  }
185
186
  issuesHtml += `<div class="ir ${iss.severity}"><span class="is">${iss.severity[0].toUpperCase()}</span>${iss.line ? `<span class="il">${iss.line}</span>` : ""}<span class="im">${e(iss.message)}</span>${iss.rule ? `<span class="iru">${e(iss.rule)}</span>` : ""}${snippetBtn}<button class="cp-btn" data-prompt="${e(prompt)}" title="Copy fix prompt">\ud83d\udccb</button></div>${srcBlock}`;
@@ -1,2 +1,2 @@
1
1
  /** All CSS for the HTML report, extracted for maintainability. */
2
- export declare const CSS = "\n:root{--bg:#09090b;--card:#111115;--border:#1e1e24;--text:#e5e5e5;--muted:#6b7280;--pass:#22c55e;--fail:#ef4444;--warn:#eab308;--info:#6366f1;--accent:#818cf8;--side-w:200px;--top-h:42px;--nav-bg:#0c0c0fdd;--side-bg:#0c0c0f;--hover:#14141a;--dim:#555;--card-alt:#0d0d12}\n[data-theme=\"light\"]{--bg:#f5f5f7;--card:#ffffff;--border:#e2e4e9;--text:#1a1a2e;--muted:#64748b;--pass:#16a34a;--fail:#dc2626;--warn:#ca8a04;--info:#4f46e5;--accent:#4f46e5;--nav-bg:#ffffffee;--side-bg:#fafafa;--hover:#eef0f5;--dim:#94a3b8;--card-alt:#f0f0f5}\nhtml{font-size:17px}\n*{margin:0;padding:0;box-sizing:border-box}\nbody{font-family:\"Inter\",system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.5}\ncode{font-family:\"SF Mono\",Menlo,monospace;font-size:0.85em}\n\n/* \u2500\u2500 Top nav \u2500\u2500 */\n.top{position:sticky;top:0;z-index:30;background:var(--nav-bg);backdrop-filter:blur(12px);border-bottom:1px solid var(--border);padding:0 1.5rem;display:flex;align-items:center;height:var(--top-h)}\n.logo{font-weight:800;font-size:1rem;margin-right:0.5rem;flex-shrink:0;text-decoration:none;color:var(--text)}\n.logo span{color:var(--accent)}\n.nav-project{font-size:0.72rem;color:var(--muted);font-weight:600;margin-right:1rem;padding:0.2rem 0.5rem;background:var(--card);border:1px solid var(--border);border-radius:4px;flex-shrink:0}\n.nav-scroll{display:flex;align-items:center;gap:0;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;flex:1}\n.nav-scroll::-webkit-scrollbar{display:none}\n.tn{padding:0 0.7rem;font-size:0.78rem;color:var(--muted);text-decoration:none;border-bottom:2px solid transparent;transition:all 0.15s;white-space:nowrap;line-height:var(--top-h)}\n.tn:hover{color:var(--text)}\n.tn.active{color:var(--text);border-bottom-color:var(--accent)}\n.hamburger{display:none;background:none;border:none;color:var(--muted);font-size:1.3rem;cursor:pointer;padding:0 0.4rem;line-height:var(--top-h)}\n\n/* \u2500\u2500 Sidebar \u2500\u2500 */\n.side{position:fixed;top:var(--top-h);left:0;bottom:0;width:var(--side-w);background:var(--side-bg);border-right:1px solid var(--border);overflow-y:auto;padding:0.6rem 0;font-size:0.7rem;z-index:20}\n.side-section{padding:0.3rem 0;border-bottom:1px solid var(--border)}\n.side-section:last-child{border-bottom:none}\n.side-label{padding:0.2rem 0.8rem;font-size:0.6rem;text-transform:uppercase;letter-spacing:0.05em;color:var(--dim);font-weight:600}\n.side-score{font-size:1.4rem;font-weight:900;padding:0.2rem 0.8rem}\n.side-cat{display:block;padding:0.3rem 0.8rem;color:var(--muted);font-weight:600;cursor:pointer;text-decoration:none;font-size:0.72rem}\n.side-cat:hover{background:var(--hover);color:var(--text)}\n.side-cat-active{color:var(--text);font-weight:700;border-left:2px solid var(--accent);padding-left:calc(0.8rem - 2px)}\n.side-cat-title{padding:0.3rem 0.8rem;font-size:0.65rem;text-transform:uppercase;letter-spacing:0.04em;color:var(--accent);font-weight:700}\n.side-check{display:block;padding:0.15rem 0.8rem 0.15rem 0.8rem;color:var(--muted);cursor:pointer;text-decoration:none;font-size:0.65rem}\n.side-check:hover{color:var(--text);background:#14141a}\n.side-check span{display:inline-block;min-width:2.5rem;font-weight:700;font-size:0.6rem}\n.side-stat{padding:0.15rem 0.8rem;font-size:0.7rem;color:var(--muted)}\n.side-stat span{font-weight:800;font-size:0.8rem}\n.side-views{padding-top:0.3rem}\n.side-views .side-check{padding-left:0.8rem}\n\n/* \u2500\u2500 Content \u2500\u2500 */\n.content{margin-left:var(--side-w);padding:1.5rem 2rem;max-width:960px}\n\n/* \u2500\u2500 Overview \u2500\u2500 */\n.dash{display:flex;gap:2rem;margin-bottom:2rem;align-items:center;flex-wrap:wrap}\n.hero{display:flex;align-items:center;gap:1rem}\n.hero svg{width:100px;height:100px}\n.hc{display:flex;flex-direction:column}\n.hg{font-size:2.5rem;font-weight:900;line-height:1}\n.hs{font-size:1rem;font-weight:600}\n.hd{font-size:0.68rem;color:var(--muted)}\n.radar{flex:1;display:flex;justify-content:center}\n.radar svg{max-width:240px;width:100%}\n.cats{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:0.6rem;margin-bottom:2rem}\n.cc{background:var(--card);border:1px solid var(--border);border-radius:0.6rem;padding:0.8rem;transition:border-color 0.15s;text-decoration:none;color:var(--text);display:block}\n.cc:hover{border-color:var(--accent)}\n.cc-s{font-size:1.8rem;font-weight:900}\n.cc-l{font-size:0.75rem;color:var(--muted)}\n.cc-m{margin-top:0.3rem;display:flex;gap:0.25rem}\n.mc{font-size:0.65rem;font-weight:800}\nh3{font-size:0.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:0.04em;margin-bottom:0.5rem}\n\n/* \u2500\u2500 Overview sections \u2500\u2500 */\n.ov-section{margin-bottom:1.5rem}\n.ov-issue{font-size:0.68rem;font-family:\"SF Mono\",monospace;padding:0.2rem 0;display:flex;gap:0.4rem;align-items:baseline;border-bottom:1px solid var(--border)}\n.ov-issue .is{flex-shrink:0}\n.ov-issue.error .is{color:var(--fail)}\n.ov-issue.warning .is{color:var(--warn)}\n.ov-check{color:var(--muted);width:70px;flex-shrink:0;font-size:0.62rem}\n.ov-loc{color:var(--accent);flex-shrink:0;font-size:0.62rem;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.ov-msg{flex:1;word-break:break-word}\n.ov-link{display:block;margin-top:0.5rem;font-size:0.72rem;color:var(--accent);text-decoration:none}\n.ov-link:hover{text-decoration:underline}\n\n/* \u2500\u2500 Timeline \u2500\u2500 */\n.timeline{margin:0.5rem 0;overflow-x:auto}\n.timeline svg{max-width:100%}\n\n/* \u2500\u2500 Bar chart \u2500\u2500 */\n.bars{margin-bottom:1.5rem}\n.brow{display:flex;align-items:center;gap:0.4rem;margin-bottom:0.25rem;font-size:0.72rem}\n.bl{width:90px;text-align:right;color:var(--muted);flex-shrink:0}\n.bb{flex:1;height:14px;background:var(--card);border-radius:3px;overflow:hidden;border:1px solid var(--border)}\n.bf{height:100%;border-radius:2px}\n.bv{width:36px;font-weight:700;font-size:0.68rem}\n.stack{display:flex;gap:0.35rem;flex-wrap:wrap;margin-top:1rem}\n.stack span{background:var(--card);border:1px solid var(--border);padding:0.1rem 0.45rem;border-radius:9999px;font-size:0.62rem;color:var(--muted)}\n\n/* \u2500\u2500 Workspace / Repo structure \u2500\u2500 */\n.ws-info{display:flex;gap:0.6rem;align-items:center;flex-wrap:wrap;margin-bottom:0.5rem;font-size:0.72rem;color:var(--muted)}\n.ws-badge{background:var(--accent);color:#fff;padding:0.15rem 0.5rem;border-radius:4px;font-size:0.65rem;font-weight:700}\n.ws-pkgs{display:flex;flex-direction:column;gap:0.15rem}\n.ws-pkg{display:flex;gap:0.6rem;align-items:center;font-size:0.68rem;padding:0.15rem 0.4rem;background:var(--card);border-radius:4px}\n.ws-path{font-family:monospace;color:var(--text);min-width:140px}\n.ws-name{color:var(--muted);flex:1}\n.ws-flags{color:var(--muted);font-size:0.6rem}\n.ws-more{font-size:0.62rem;color:var(--muted);padding:0.2rem 0.4rem}\n\n/* \u2500\u2500 Category pages \u2500\u2500 */\n.cat-head{margin-bottom:0.3rem}\n.bar2{height:4px;background:var(--card);border-radius:2px;margin-bottom:1.5rem;overflow:hidden}\n.bf2{height:100%;border-radius:2px}\n.check-section{margin-bottom:2.5rem;padding-top:0.5rem;border-top:1px solid var(--border)}\n.check-section:first-of-type{border-top:none}\n\n/* \u2500\u2500 Check detail \u2500\u2500 */\n.ch-head{display:flex;align-items:center;gap:0.7rem;margin-bottom:0.8rem}\n.ch-g{font-size:2rem;font-weight:900}\n.ch-s{display:block;font-size:0.7rem;color:var(--muted)}\n.pri{font-size:0.62rem;font-weight:700;text-transform:uppercase;letter-spacing:0.04em;padding:0.15rem 0.5rem;border-radius:9999px;border:1px solid currentColor;flex-shrink:0}\n.info-panel{background:var(--card-alt);border:1px solid var(--border);border-radius:0.5rem;padding:0.7rem 0.9rem;margin-bottom:1rem;font-size:0.72rem;line-height:1.6}\n.ip-row{margin-bottom:0.4rem;display:flex;gap:0.5rem}\n.ip-row:last-child{margin-bottom:0}\n.ip-label{color:var(--accent);font-weight:700;min-width:2.5rem;flex-shrink:0}\n.skip-r{color:var(--muted);font-style:italic;font-size:0.78rem}\n.kvs{display:flex;gap:0.6rem;flex-wrap:wrap;margin-bottom:1rem}\n.kv{background:var(--card);border:1px solid var(--border);border-radius:0.4rem;padding:0.3rem 0.6rem;font-size:0.7rem}\n.k{color:var(--muted);margin-right:0.3rem}\n.v{font-weight:600}\n\n/* \u2500\u2500 Issue list grouped by file \u2500\u2500 */\n.iss-list{margin-top:1rem}\n.fg{margin-bottom:0.8rem}\n.fn{font-size:0.72rem;font-weight:600;font-family:\"SF Mono\",monospace;padding:0.3rem 0;border-bottom:1px solid var(--border);margin-bottom:0.2rem;display:flex;align-items:center;gap:0.5rem}\n.fc{background:var(--border);border-radius:9999px;padding:0 0.4rem;font-size:0.6rem;color:var(--muted)}\n.ir{font-size:0.65rem;font-family:\"SF Mono\",monospace;padding:0.12rem 0 0.12rem 0.5rem;display:flex;gap:0.4rem;align-items:baseline}\n.is{font-weight:800;font-size:0.55rem;width:0.9rem;text-align:center;border-radius:2px;flex-shrink:0}\n.ir.error .is{color:var(--fail);background:#ef444418}\n.ir.warning .is{color:var(--warn);background:#eab30818}\n.ir.info .is{color:var(--info);background:#6366f118}\n.il{color:var(--accent);min-width:2rem;flex-shrink:0}\n.im{flex:1;word-break:break-word}\n.iru{color:var(--dim);font-size:0.55rem}\n\n/* \u2500\u2500 Source code snippets \u2500\u2500 */\n.src-block{background:var(--card-alt);border:1px solid var(--border);border-radius:6px;margin:0.3rem 0 0.5rem 0.5rem;padding:0.3rem 0;font-family:\"SF Mono\",Menlo,monospace;font-size:0.62rem;line-height:1.6;overflow-x:auto}\n.src-ln{padding:0 0.5rem;white-space:pre}\n.src-hl{padding:0 0.5rem;white-space:pre;background:#eab30815;border-left:2px solid var(--warn)}\n.src-num{color:var(--dim);margin-right:0.5rem;user-select:none;display:inline-block;min-width:2.5rem;text-align:right}\n\n/* \u2500\u2500 All issues table \u2500\u2500 */\n.isf{color:var(--muted);font-size:0.75rem;margin-bottom:0.8rem}\n.it{width:100%;border-collapse:collapse;font-size:0.68rem}\n.it th{text-align:left;padding:0.35rem 0.4rem;color:var(--muted);font-size:0.62rem;text-transform:uppercase;border-bottom:1px solid var(--border)}\n.it td{padding:0.25rem 0.4rem;border-bottom:1px solid var(--border);font-family:\"SF Mono\",monospace;font-size:0.62rem}\n.it tr.error .is2{color:var(--fail)}\n.it tr.warning .is2{color:var(--warn)}\n.is2{font-weight:800;width:1rem}\n.ic2{color:var(--muted);width:70px}\n.il2{color:var(--muted)}\n.iru2{color:var(--dim);font-size:0.58rem}\n\n/* \u2500\u2500 File health \u2500\u2500 */\n.fr{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.3rem;font-size:0.7rem}\n.ff{width:200px;font-family:\"SF Mono\",monospace;font-size:0.65rem;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.fb{flex:1;height:12px;background:var(--card);border-radius:3px;overflow:hidden;border:1px solid var(--border)}\n.fbf{height:100%;border-radius:2px}\n.fv{width:50px;font-size:0.65rem;color:var(--muted);flex-shrink:0}\n.hm-row{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.2rem;font-size:0.7rem}\n.hm-name{width:200px;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:\"SF Mono\",monospace;font-size:0.65rem}\n.hm-bar{height:14px;border-radius:3px;min-width:4px}\n.hm-count{color:var(--muted);font-size:0.65rem;flex-shrink:0;min-width:50px}\n.hm-checks{font-size:0.58rem;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n\n/* \u2500\u2500 Premium cards \u2500\u2500 */\n.pro-card{background:linear-gradient(135deg,#0f0f1a 0%,#13131f 100%);border:1px solid #2a2a3d;border-radius:0.75rem;padding:1.5rem;position:relative;overflow:hidden}\n.pro-card::before{content:\"\";position:absolute;top:-50%;right:-50%;width:200%;height:200%;background:radial-gradient(circle,#6366f108 0%,transparent 70%);pointer-events:none}\n.pro-badge{display:inline-block;background:linear-gradient(135deg,#6366f1,#818cf8);color:#fff;font-size:0.6rem;font-weight:800;padding:0.15rem 0.5rem;border-radius:9999px;letter-spacing:0.06em;margin-bottom:0.6rem}\n.pro-desc{color:var(--muted);font-size:0.78rem;line-height:1.6;margin-bottom:0.8rem}\n.pro-cta{color:#6366f1;font-size:0.72rem;font-weight:600;margin-top:1rem}\n.sn-pro{opacity:0.7}\n\n/* \u2500\u2500 Trends page \u2500\u2500 */\n.trend-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:1rem;margin-top:0.5rem}\n.trend-card{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:0.8rem}\n.trend-header{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.3rem}\n.trend-name{font-size:0.78rem;font-weight:700;flex:1}\n.trend-score{font-size:1.1rem;font-weight:900}\n.trend-chart{overflow:hidden}\n.trend-chart svg{width:100%;height:60px}\n.trend-table{margin-bottom:1.5rem}\n.trend-row{display:flex;align-items:center;gap:0.5rem;padding:0.25rem 0;border-bottom:1px solid var(--border);font-size:0.75rem}\n.trend-row-name{flex:1;font-weight:600}\n.trend-row-val{width:2rem;text-align:center;color:var(--muted)}\n.trend-row-arrow{color:var(--muted);font-size:0.6rem}\n.trend-row-delta{width:2.5rem;text-align:right;font-weight:700}\n\n.footer{text-align:center;color:var(--muted);font-size:0.58rem;margin-top:2rem;padding:0.8rem 0;border-top:1px solid var(--border)}\n.footer a{color:var(--muted)}\n.muted{color:var(--muted)}\n.deeper-tools code{background:var(--border);padding:0.1rem 0.4rem;border-radius:4px;font-size:0.62rem;color:var(--accent);margin-right:0.3rem}\n.flink{color:var(--accent);text-decoration:none;font-family:\"SF Mono\",monospace}.flink:hover{text-decoration:underline}\n.arch-svg{margin:1rem 0;overflow-x:auto;-webkit-overflow-scrolling:touch}\n.arch-svg svg{border-radius:8px}\n.cp-btn{background:none;border:none;cursor:pointer;font-size:0.6rem;opacity:0.3;padding:0 0.2rem;flex-shrink:0}.cp-btn:hover{opacity:1}\n.ir:hover .cp-btn{opacity:0.6}\n\n/* \u2500\u2500 Mobile: hamburger collapses both navs \u2500\u2500 */\n@media(max-width:768px){\n.hamburger{display:block}\n.nav-scroll{display:none}\n.nav-scroll.open{display:flex;position:absolute;top:var(--top-h);left:0;right:0;background:var(--bg);border-bottom:1px solid var(--border);flex-wrap:wrap;padding:0.3rem 0.5rem;z-index:25}\n.side{display:none}\n.side.open{display:block;z-index:25}\n.top{padding:0 0.8rem}\n.logo{font-size:0.85rem;margin-right:0.5rem}\n.content{margin-left:0;padding:0.8rem}\n.cats{grid-template-columns:1fr 1fr}\n.dash{flex-direction:column;gap:1rem}\n.hero svg{width:80px;height:80px}\n.hg{font-size:2rem}\n.radar svg{max-width:180px}\n.bl{width:60px;font-size:0.62rem}\n.bv{width:30px;font-size:0.6rem}\n.it{display:block;overflow-x:auto;-webkit-overflow-scrolling:touch}\n.ff{width:120px;font-size:0.58rem}\n.hm-name{width:120px;font-size:0.58rem}\n.hm-checks{display:none}\n.ov-check{width:50px}\n.ov-loc{max-width:120px}\n.ir{font-size:0.6rem}\n.ch-head{flex-wrap:wrap}\n.ch-g{font-size:1.5rem}\n.info-panel{font-size:0.68rem;padding:0.5rem 0.6rem}\n.ip-row{flex-direction:column;gap:0.1rem}\n.kvs{gap:0.4rem}\n.kv{font-size:0.62rem;padding:0.2rem 0.4rem}\n.arch-svg svg{min-width:400px}\n}\n@media(max-width:480px){\n.cats{grid-template-columns:1fr}\n.tn{padding:0 0.4rem;font-size:0.65rem}\n.ff{width:90px}\n.hm-name{width:90px}\n.ov-check{display:none}\n}\n\n/* \u2500\u2500 Feature Map (Pro) \u2500\u2500 */\n.fm-header{margin-bottom:1.5rem}\n.fm-header h2{display:flex;align-items:center;gap:0.6rem}\n.fm-stats{display:flex;gap:1.5rem;margin-bottom:2rem;padding:1rem 1.2rem;background:var(--card);border:1px solid var(--border);border-radius:12px}\n.fm-stat{display:flex;flex-direction:column;align-items:center}\n.fm-stat-n{font-size:1.6rem;font-weight:900;line-height:1.2}\n.fm-stat-l{font-size:0.65rem;color:var(--muted);text-transform:uppercase;letter-spacing:0.04em;font-weight:600}\n.fm-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1rem}\n.fm-card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:1.2rem;transition:border-color 0.15s}\n.fm-card:hover{border-color:#333}\n.fm-card-issue{border-color:#eab30830;background:linear-gradient(135deg,var(--card) 0%,#1a1a0f 100%)}\n.fm-card-top{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:0.4rem}\n.fm-card-label{font-weight:800;font-size:0.95rem}\n.fm-card-desc{font-size:0.72rem;color:var(--muted);margin-bottom:0.5rem;line-height:1.4}\n.fm-card-dir{font-size:0.65rem;color:var(--dim);font-family:\"SF Mono\",monospace;margin-bottom:0.6rem}\n.fm-card-badge{font-size:0.6rem;font-weight:700;padding:0.15rem 0.5rem;border-radius:9999px;white-space:nowrap}\n.fm-ok{background:#22c55e18;color:var(--pass)}\n.fm-warn{background:#eab30818;color:var(--warn)}\n.fm-info{background:#6366f118;color:var(--info)}\n.fm-card-files{display:flex;flex-direction:column;gap:0.15rem;margin-bottom:0.6rem}\n.fm-file{font-size:0.68rem;color:var(--muted);font-family:\"SF Mono\",monospace}\n.fm-file a{color:var(--accent);text-decoration:none}\n.fm-file a:hover{text-decoration:underline}\n.fm-more{color:var(--dim);font-style:italic}\n.fm-findings{margin-top:0.6rem;padding-top:0.6rem;border-top:1px solid var(--border);display:flex;flex-direction:column;gap:0.3rem}\n.fm-finding{display:flex;align-items:baseline;gap:0.4rem;font-size:0.68rem;line-height:1.4}\n.fm-f-sev{font-weight:800;font-size:0.6rem;width:1rem;flex-shrink:0}\n.fm-f-warn .fm-f-sev{color:var(--warn)}\n.fm-f-info .fm-f-sev{color:var(--info)}\n.fm-f-loc{color:var(--dim);font-family:\"SF Mono\",monospace;flex-shrink:0}\n.fm-f-loc a{color:var(--accent);text-decoration:none}\n.fm-f-msg{color:var(--text)}\n.fm-f-rule{color:var(--dim);font-size:0.6rem;font-family:\"SF Mono\",monospace}\n\n/* Teaser (no Pro key) */\n.fm-teaser{margin-top:1.5rem}\n.fm-teaser-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:1rem;margin-bottom:2rem}\n.fm-card-blur{filter:blur(3px);opacity:0.5;pointer-events:none;user-select:none}\n.fm-cta{text-align:center;padding:2rem;background:linear-gradient(135deg,#0f0f1a,#13131f);border:1px solid #2a2a3d;border-radius:12px}\n.fm-cta code{background:#1a1a2e;padding:0.2rem 0.5rem;border-radius:4px;font-size:0.8rem}\n@media(max-width:640px){\n.fm-grid,.fm-teaser-grid{grid-template-columns:1fr}\n.fm-stats{flex-wrap:wrap;gap:1rem}\n}\n\n/* \u2500\u2500 Preferences panel \u2500\u2500 */\n.prefs-btn{background:none;border:1px solid var(--border);color:var(--muted);font-size:0.72rem;cursor:pointer;padding:0.2rem 0.5rem;border-radius:6px;margin-left:auto;flex-shrink:0;font-family:inherit;line-height:1.4}\n.prefs-btn:hover{color:var(--text);border-color:var(--dim)}\n.prefs-panel{display:none;position:absolute;top:var(--top-h);right:1rem;background:var(--card);border:1px solid var(--border);border-radius:10px;padding:0.8rem 1rem;z-index:40;min-width:200px;box-shadow:0 8px 30px #0008}\n.prefs-panel.open{display:block}\n.prefs-label{font-size:0.6rem;text-transform:uppercase;letter-spacing:0.05em;color:var(--dim);font-weight:600;margin-bottom:0.3rem}\n.prefs-label:not(:first-child){margin-top:0.7rem}\n.prefs-row{display:flex;gap:0.3rem}\n.prefs-opt{background:var(--card-alt);border:1px solid var(--border);color:var(--muted);font-size:0.68rem;padding:0.25rem 0.6rem;border-radius:6px;cursor:pointer;font-family:inherit;transition:all 0.1s}\n.prefs-opt:hover{color:var(--text);border-color:var(--dim)}\n.prefs-opt.active{background:var(--accent);color:#fff;border-color:var(--accent)}\n[data-theme=\"light\"] .prefs-panel{box-shadow:0 8px 30px #0002}\n";
2
+ export declare const CSS = "\n:root{--bg:#09090b;--card:#111115;--border:#1e1e24;--text:#e5e5e5;--muted:#6b7280;--pass:#22c55e;--fail:#ef4444;--warn:#eab308;--info:#6366f1;--accent:#818cf8;--side-w:200px;--top-h:42px;--nav-bg:#0c0c0fdd;--side-bg:#0c0c0f;--hover:#14141a;--dim:#555;--card-alt:#0d0d12}\n[data-theme=\"light\"]{--bg:#f5f5f7;--card:#ffffff;--border:#e2e4e9;--text:#1a1a2e;--muted:#64748b;--pass:#16a34a;--fail:#dc2626;--warn:#ca8a04;--info:#4f46e5;--accent:#4f46e5;--nav-bg:#ffffffee;--side-bg:#fafafa;--hover:#eef0f5;--dim:#94a3b8;--card-alt:#f0f0f5}\nhtml{font-size:17px}\n*{margin:0;padding:0;box-sizing:border-box}\nbody{font-family:\"Inter\",system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.5}\ncode{font-family:\"SF Mono\",Menlo,monospace;font-size:0.85em}\n\n/* \u2500\u2500 Top nav \u2500\u2500 */\n.top{position:sticky;top:0;z-index:30;background:var(--nav-bg);backdrop-filter:blur(12px);border-bottom:1px solid var(--border);padding:0 1.5rem;display:flex;align-items:center;height:var(--top-h)}\n.logo{font-weight:800;font-size:1rem;margin-right:0.5rem;flex-shrink:0;text-decoration:none;color:var(--text)}\n.logo span{color:var(--accent)}\n.nav-project{font-size:0.72rem;color:var(--muted);font-weight:600;margin-right:1rem;padding:0.2rem 0.5rem;background:var(--card);border:1px solid var(--border);border-radius:4px;flex-shrink:0}\n.nav-scroll{display:flex;align-items:center;gap:0;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;flex:1}\n.nav-scroll::-webkit-scrollbar{display:none}\n.tn{padding:0 0.7rem;font-size:0.78rem;color:var(--muted);text-decoration:none;border-bottom:2px solid transparent;transition:all 0.15s;white-space:nowrap;line-height:var(--top-h)}\n.tn:hover{color:var(--text)}\n.tn.active{color:var(--text);border-bottom-color:var(--accent)}\n.hamburger{display:none;background:none;border:none;color:var(--muted);font-size:1.3rem;cursor:pointer;padding:0 0.4rem;line-height:var(--top-h)}\n\n/* \u2500\u2500 Sidebar \u2500\u2500 */\n.side{position:fixed;top:var(--top-h);left:0;bottom:0;width:var(--side-w);background:var(--side-bg);border-right:1px solid var(--border);overflow-y:auto;padding:0.6rem 0;font-size:0.7rem;z-index:20}\n.side-section{padding:0.3rem 0;border-bottom:1px solid var(--border)}\n.side-section:last-child{border-bottom:none}\n.side-label{padding:0.2rem 0.8rem;font-size:0.6rem;text-transform:uppercase;letter-spacing:0.05em;color:var(--dim);font-weight:600}\n.side-score{font-size:1.4rem;font-weight:900;padding:0.2rem 0.8rem}\n.side-cat{display:block;padding:0.3rem 0.8rem;color:var(--muted);font-weight:600;cursor:pointer;text-decoration:none;font-size:0.72rem}\n.side-cat:hover{background:var(--hover);color:var(--text)}\n.side-cat-active{color:var(--text);font-weight:700;border-left:2px solid var(--accent);padding-left:calc(0.8rem - 2px)}\n.side-cat-title{padding:0.3rem 0.8rem;font-size:0.65rem;text-transform:uppercase;letter-spacing:0.04em;color:var(--accent);font-weight:700}\n.side-check{display:block;padding:0.15rem 0.8rem 0.15rem 0.8rem;color:var(--muted);cursor:pointer;text-decoration:none;font-size:0.65rem}\n.side-check:hover{color:var(--text);background:#14141a}\n.side-check span{display:inline-block;min-width:2.5rem;font-weight:700;font-size:0.6rem}\n.side-stat{padding:0.15rem 0.8rem;font-size:0.7rem;color:var(--muted)}\n.side-stat span{font-weight:800;font-size:0.8rem}\n.side-views{padding-top:0.3rem}\n.side-views .side-check{padding-left:0.8rem}\n\n/* \u2500\u2500 Content \u2500\u2500 */\n.content{margin-left:var(--side-w);padding:1.5rem 2rem;max-width:960px}\n\n/* \u2500\u2500 Overview \u2500\u2500 */\n.dash{display:flex;gap:2rem;margin-bottom:2rem;align-items:center;flex-wrap:wrap}\n.hero{display:flex;align-items:center;gap:1rem}\n.hero svg{width:100px;height:100px}\n.hc{display:flex;flex-direction:column}\n.hg{font-size:2.5rem;font-weight:900;line-height:1}\n.hs{font-size:1rem;font-weight:600}\n.hd{font-size:0.68rem;color:var(--muted)}\n.radar{flex:1;display:flex;justify-content:center}\n.radar svg{max-width:240px;width:100%}\n.cats{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:0.6rem;margin-bottom:2rem}\n.cc{background:var(--card);border:1px solid var(--border);border-radius:0.6rem;padding:0.8rem;transition:border-color 0.15s;text-decoration:none;color:var(--text);display:block}\n.cc:hover{border-color:var(--accent)}\n.cc-s{font-size:1.8rem;font-weight:900}\n.cc-l{font-size:0.75rem;color:var(--muted)}\n.cc-m{margin-top:0.3rem;display:flex;gap:0.25rem}\n.mc{font-size:0.65rem;font-weight:800}\nh3{font-size:0.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:0.04em;margin-bottom:0.5rem}\n\n/* \u2500\u2500 Overview sections \u2500\u2500 */\n.ov-section{margin-bottom:1.5rem}\n.ov-issue{font-size:0.68rem;font-family:\"SF Mono\",monospace;padding:0.2rem 0;display:flex;gap:0.4rem;align-items:baseline;border-bottom:1px solid var(--border)}\n.ov-issue .is{flex-shrink:0}\n.ov-issue.error .is{color:var(--fail)}\n.ov-issue.warning .is{color:var(--warn)}\n.ov-check{color:var(--muted);width:70px;flex-shrink:0;font-size:0.62rem}\n.ov-loc{color:var(--accent);flex-shrink:0;font-size:0.62rem;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.ov-msg{flex:1;word-break:break-word}\n.ov-link{display:block;margin-top:0.5rem;font-size:0.72rem;color:var(--accent);text-decoration:none}\n.ov-link:hover{text-decoration:underline}\n\n/* \u2500\u2500 Timeline \u2500\u2500 */\n.timeline{margin:0.5rem 0;overflow-x:auto}\n.timeline svg{max-width:100%}\n\n/* \u2500\u2500 Bar chart \u2500\u2500 */\n.bars{margin-bottom:1.5rem}\n.brow{display:flex;align-items:center;gap:0.4rem;margin-bottom:0.25rem;font-size:0.72rem}\n.bl{width:90px;text-align:right;color:var(--muted);flex-shrink:0}\n.bb{flex:1;height:14px;background:var(--card);border-radius:3px;overflow:hidden;border:1px solid var(--border)}\n.bf{height:100%;border-radius:2px}\n.bv{width:36px;font-weight:700;font-size:0.68rem}\n.stack{display:flex;gap:0.35rem;flex-wrap:wrap;margin-top:1rem}\n.stack span{background:var(--card);border:1px solid var(--border);padding:0.1rem 0.45rem;border-radius:9999px;font-size:0.62rem;color:var(--muted)}\n\n/* \u2500\u2500 Workspace / Repo structure \u2500\u2500 */\n.ws-info{display:flex;gap:0.6rem;align-items:center;flex-wrap:wrap;margin-bottom:0.5rem;font-size:0.72rem;color:var(--muted)}\n.ws-badge{background:var(--accent);color:#fff;padding:0.15rem 0.5rem;border-radius:4px;font-size:0.65rem;font-weight:700}\n.ws-pkgs{display:flex;flex-direction:column;gap:0.15rem}\n.ws-pkg{display:flex;gap:0.6rem;align-items:center;font-size:0.68rem;padding:0.15rem 0.4rem;background:var(--card);border-radius:4px}\n.ws-path{font-family:monospace;color:var(--text);min-width:140px}\n.ws-name{color:var(--muted);flex:1}\n.ws-flags{color:var(--muted);font-size:0.6rem}\n.ws-more{font-size:0.62rem;color:var(--muted);padding:0.2rem 0.4rem}\n\n/* \u2500\u2500 Category pages \u2500\u2500 */\n.cat-head{margin-bottom:0.3rem}\n.bar2{height:4px;background:var(--card);border-radius:2px;margin-bottom:1.5rem;overflow:hidden}\n.bf2{height:100%;border-radius:2px}\n.check-section{margin-bottom:2.5rem;padding-top:0.5rem;border-top:1px solid var(--border)}\n.check-section:first-of-type{border-top:none}\n\n/* \u2500\u2500 Check detail \u2500\u2500 */\n.ch-head{display:flex;align-items:center;gap:0.7rem;margin-bottom:0.8rem}\n.ch-g{font-size:2rem;font-weight:900}\n.ch-s{display:block;font-size:0.7rem;color:var(--muted)}\n.pri{font-size:0.62rem;font-weight:700;text-transform:uppercase;letter-spacing:0.04em;padding:0.15rem 0.5rem;border-radius:9999px;border:1px solid currentColor;flex-shrink:0}\n.info-panel{background:var(--card-alt);border:1px solid var(--border);border-radius:0.5rem;padding:0.7rem 0.9rem;margin-bottom:1rem;font-size:0.72rem;line-height:1.6}\n.ip-row{margin-bottom:0.4rem;display:flex;gap:0.5rem}\n.ip-row:last-child{margin-bottom:0}\n.ip-label{color:var(--accent);font-weight:700;min-width:2.5rem;flex-shrink:0}\n.skip-r{color:var(--muted);font-style:italic;font-size:0.78rem}\n.kvs{display:flex;gap:0.6rem;flex-wrap:wrap;margin-bottom:1rem}\n.kv{background:var(--card);border:1px solid var(--border);border-radius:0.4rem;padding:0.3rem 0.6rem;font-size:0.7rem}\n.k{color:var(--muted);margin-right:0.3rem}\n.v{font-weight:600}\n\n/* \u2500\u2500 Issue list grouped by file \u2500\u2500 */\n.iss-list{margin-top:1rem}\n.fg{margin-bottom:0.8rem}\n.fn{font-size:0.72rem;font-weight:600;font-family:\"SF Mono\",monospace;padding:0.3rem 0;border-bottom:1px solid var(--border);margin-bottom:0.2rem;display:flex;align-items:center;gap:0.5rem}\n.fc{background:var(--border);border-radius:9999px;padding:0 0.4rem;font-size:0.6rem;color:var(--muted)}\n.ir{font-size:0.65rem;font-family:\"SF Mono\",monospace;padding:0.12rem 0 0.12rem 0.5rem;display:flex;gap:0.4rem;align-items:baseline}\n.is{font-weight:800;font-size:0.55rem;width:0.9rem;text-align:center;border-radius:2px;flex-shrink:0}\n.ir.error .is{color:var(--fail);background:#ef444418}\n.ir.warning .is{color:var(--warn);background:#eab30818}\n.ir.info .is{color:var(--info);background:#6366f118}\n.il{color:var(--accent);min-width:2rem;flex-shrink:0}\n.im{flex:1;word-break:break-word}\n.iru{color:var(--dim);font-size:0.55rem}\n\n/* \u2500\u2500 Source code snippets \u2500\u2500 */\n.src-block{background:var(--card-alt);border:1px solid var(--border);border-radius:6px;margin:0.3rem 0 0.5rem 0.5rem;padding:0.3rem 0;font-family:\"SF Mono\",Menlo,monospace;font-size:0.62rem;line-height:1.6;overflow-x:auto}\n.src-ln{padding:0 0.5rem;white-space:pre}\n.src-hl{padding:0 0.5rem;white-space:pre;background:#eab30815;border-left:2px solid var(--warn)}\n.src-num{color:var(--dim);margin-right:0.5rem;user-select:none;display:inline-block;min-width:2.5rem;text-align:right}\n.src-prompt{padding:0.3rem 0.5rem;border-top:1px solid var(--border);display:flex;justify-content:flex-end}\n.src-fix-btn{background:var(--card);border:1px solid var(--border);color:var(--accent);font-size:0.62rem;padding:0.2rem 0.6rem;border-radius:4px;cursor:pointer;font-family:inherit}\n.src-fix-btn:hover{background:var(--accent);color:#fff;border-color:var(--accent)}\n\n/* \u2500\u2500 All issues table \u2500\u2500 */\n.isf{color:var(--muted);font-size:0.75rem;margin-bottom:0.8rem}\n.it{width:100%;border-collapse:collapse;font-size:0.68rem}\n.it th{text-align:left;padding:0.35rem 0.4rem;color:var(--muted);font-size:0.62rem;text-transform:uppercase;border-bottom:1px solid var(--border)}\n.it td{padding:0.25rem 0.4rem;border-bottom:1px solid var(--border);font-family:\"SF Mono\",monospace;font-size:0.62rem}\n.it tr.error .is2{color:var(--fail)}\n.it tr.warning .is2{color:var(--warn)}\n.is2{font-weight:800;width:1rem}\n.ic2{color:var(--muted);width:70px}\n.il2{color:var(--muted)}\n.iru2{color:var(--dim);font-size:0.58rem}\n\n/* \u2500\u2500 File health \u2500\u2500 */\n.fr{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.3rem;font-size:0.7rem}\n.ff{width:200px;font-family:\"SF Mono\",monospace;font-size:0.65rem;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.fb{flex:1;height:12px;background:var(--card);border-radius:3px;overflow:hidden;border:1px solid var(--border)}\n.fbf{height:100%;border-radius:2px}\n.fv{width:50px;font-size:0.65rem;color:var(--muted);flex-shrink:0}\n.hm-row{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.2rem;font-size:0.7rem}\n.hm-name{width:200px;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:\"SF Mono\",monospace;font-size:0.65rem}\n.hm-bar{height:14px;border-radius:3px;min-width:4px}\n.hm-count{color:var(--muted);font-size:0.65rem;flex-shrink:0;min-width:50px}\n.hm-checks{font-size:0.58rem;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n\n/* \u2500\u2500 Premium cards \u2500\u2500 */\n.pro-card{background:linear-gradient(135deg,#0f0f1a 0%,#13131f 100%);border:1px solid #2a2a3d;border-radius:0.75rem;padding:1.5rem;position:relative;overflow:hidden}\n.pro-card::before{content:\"\";position:absolute;top:-50%;right:-50%;width:200%;height:200%;background:radial-gradient(circle,#6366f108 0%,transparent 70%);pointer-events:none}\n.pro-badge{display:inline-block;background:linear-gradient(135deg,#6366f1,#818cf8);color:#fff;font-size:0.6rem;font-weight:800;padding:0.15rem 0.5rem;border-radius:9999px;letter-spacing:0.06em;margin-bottom:0.6rem}\n.pro-desc{color:var(--muted);font-size:0.78rem;line-height:1.6;margin-bottom:0.8rem}\n.pro-cta{color:#6366f1;font-size:0.72rem;font-weight:600;margin-top:1rem}\n.sn-pro{opacity:0.7}\n\n/* \u2500\u2500 Trends page \u2500\u2500 */\n.trend-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:1rem;margin-top:0.5rem}\n.trend-card{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:0.8rem}\n.trend-header{display:flex;align-items:center;gap:0.5rem;margin-bottom:0.3rem}\n.trend-name{font-size:0.78rem;font-weight:700;flex:1}\n.trend-score{font-size:1.1rem;font-weight:900}\n.trend-chart{overflow:hidden}\n.trend-chart svg{width:100%;height:60px}\n.trend-table{margin-bottom:1.5rem}\n.trend-row{display:flex;align-items:center;gap:0.5rem;padding:0.25rem 0;border-bottom:1px solid var(--border);font-size:0.75rem}\n.trend-row-name{flex:1;font-weight:600}\n.trend-row-val{width:2rem;text-align:center;color:var(--muted)}\n.trend-row-arrow{color:var(--muted);font-size:0.6rem}\n.trend-row-delta{width:2.5rem;text-align:right;font-weight:700}\n\n.footer{text-align:center;color:var(--muted);font-size:0.58rem;margin-top:2rem;padding:0.8rem 0;border-top:1px solid var(--border)}\n.footer a{color:var(--muted)}\n.muted{color:var(--muted)}\n.deeper-tools code{background:var(--border);padding:0.1rem 0.4rem;border-radius:4px;font-size:0.62rem;color:var(--accent);margin-right:0.3rem}\n.flink{color:var(--accent);text-decoration:none;font-family:\"SF Mono\",monospace}.flink:hover{text-decoration:underline}\n.arch-svg{margin:1rem 0;overflow-x:auto;-webkit-overflow-scrolling:touch}\n.arch-svg svg{border-radius:8px}\n.cp-btn{background:none;border:none;cursor:pointer;font-size:0.6rem;opacity:0.3;padding:0 0.2rem;flex-shrink:0}.cp-btn:hover{opacity:1}\n.ir:hover .cp-btn{opacity:0.6}\n\n/* \u2500\u2500 Mobile: hamburger collapses both navs \u2500\u2500 */\n@media(max-width:768px){\n.hamburger{display:block}\n.nav-scroll{display:none}\n.nav-scroll.open{display:flex;position:absolute;top:var(--top-h);left:0;right:0;background:var(--bg);border-bottom:1px solid var(--border);flex-wrap:wrap;padding:0.3rem 0.5rem;z-index:25}\n.side{display:none}\n.side.open{display:block;z-index:25}\n.top{padding:0 0.8rem}\n.logo{font-size:0.85rem;margin-right:0.5rem}\n.content{margin-left:0;padding:0.8rem}\n.cats{grid-template-columns:1fr 1fr}\n.dash{flex-direction:column;gap:1rem}\n.hero svg{width:80px;height:80px}\n.hg{font-size:2rem}\n.radar svg{max-width:180px}\n.bl{width:60px;font-size:0.62rem}\n.bv{width:30px;font-size:0.6rem}\n.it{display:block;overflow-x:auto;-webkit-overflow-scrolling:touch}\n.ff{width:120px;font-size:0.58rem}\n.hm-name{width:120px;font-size:0.58rem}\n.hm-checks{display:none}\n.ov-check{width:50px}\n.ov-loc{max-width:120px}\n.ir{font-size:0.6rem}\n.ch-head{flex-wrap:wrap}\n.ch-g{font-size:1.5rem}\n.info-panel{font-size:0.68rem;padding:0.5rem 0.6rem}\n.ip-row{flex-direction:column;gap:0.1rem}\n.kvs{gap:0.4rem}\n.kv{font-size:0.62rem;padding:0.2rem 0.4rem}\n.arch-svg svg{min-width:400px}\n}\n@media(max-width:480px){\n.cats{grid-template-columns:1fr}\n.tn{padding:0 0.4rem;font-size:0.65rem}\n.ff{width:90px}\n.hm-name{width:90px}\n.ov-check{display:none}\n}\n\n/* \u2500\u2500 Feature Map (Pro) \u2500\u2500 */\n.fm-header{margin-bottom:1.5rem}\n.fm-header h2{display:flex;align-items:center;gap:0.6rem}\n.fm-stats{display:flex;gap:1.5rem;margin-bottom:2rem;padding:1rem 1.2rem;background:var(--card);border:1px solid var(--border);border-radius:12px}\n.fm-stat{display:flex;flex-direction:column;align-items:center}\n.fm-stat-n{font-size:1.6rem;font-weight:900;line-height:1.2}\n.fm-stat-l{font-size:0.65rem;color:var(--muted);text-transform:uppercase;letter-spacing:0.04em;font-weight:600}\n.fm-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1rem}\n.fm-card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:1.2rem;transition:border-color 0.15s}\n.fm-card:hover{border-color:#333}\n.fm-card-issue{border-color:#eab30830;background:linear-gradient(135deg,var(--card) 0%,#1a1a0f 100%)}\n.fm-card-top{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:0.4rem}\n.fm-card-label{font-weight:800;font-size:0.95rem}\n.fm-card-desc{font-size:0.72rem;color:var(--muted);margin-bottom:0.5rem;line-height:1.4}\n.fm-card-dir{font-size:0.65rem;color:var(--dim);font-family:\"SF Mono\",monospace;margin-bottom:0.6rem}\n.fm-card-badge{font-size:0.6rem;font-weight:700;padding:0.15rem 0.5rem;border-radius:9999px;white-space:nowrap}\n.fm-ok{background:#22c55e18;color:var(--pass)}\n.fm-warn{background:#eab30818;color:var(--warn)}\n.fm-info{background:#6366f118;color:var(--info)}\n.fm-card-files{display:flex;flex-direction:column;gap:0.15rem;margin-bottom:0.6rem}\n.fm-file{font-size:0.68rem;color:var(--muted);font-family:\"SF Mono\",monospace}\n.fm-file a{color:var(--accent);text-decoration:none}\n.fm-file a:hover{text-decoration:underline}\n.fm-more{color:var(--dim);font-style:italic}\n.fm-findings{margin-top:0.6rem;padding-top:0.6rem;border-top:1px solid var(--border);display:flex;flex-direction:column;gap:0.3rem}\n.fm-finding{display:flex;align-items:baseline;gap:0.4rem;font-size:0.68rem;line-height:1.4}\n.fm-f-sev{font-weight:800;font-size:0.6rem;width:1rem;flex-shrink:0}\n.fm-f-warn .fm-f-sev{color:var(--warn)}\n.fm-f-info .fm-f-sev{color:var(--info)}\n.fm-f-loc{color:var(--dim);font-family:\"SF Mono\",monospace;flex-shrink:0}\n.fm-f-loc a{color:var(--accent);text-decoration:none}\n.fm-f-msg{color:var(--text)}\n.fm-f-rule{color:var(--dim);font-size:0.6rem;font-family:\"SF Mono\",monospace}\n\n/* Teaser (no Pro key) */\n.fm-teaser{margin-top:1.5rem}\n.fm-teaser-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:1rem;margin-bottom:2rem}\n.fm-card-blur{filter:blur(3px);opacity:0.5;pointer-events:none;user-select:none}\n.fm-cta{text-align:center;padding:2rem;background:linear-gradient(135deg,#0f0f1a,#13131f);border:1px solid #2a2a3d;border-radius:12px}\n.fm-cta code{background:#1a1a2e;padding:0.2rem 0.5rem;border-radius:4px;font-size:0.8rem}\n@media(max-width:640px){\n.fm-grid,.fm-teaser-grid{grid-template-columns:1fr}\n.fm-stats{flex-wrap:wrap;gap:1rem}\n}\n\n/* \u2500\u2500 Preferences panel \u2500\u2500 */\n.prefs-btn{background:none;border:1px solid var(--border);color:var(--muted);font-size:0.72rem;cursor:pointer;padding:0.2rem 0.5rem;border-radius:6px;margin-left:auto;flex-shrink:0;font-family:inherit;line-height:1.4}\n.prefs-btn:hover{color:var(--text);border-color:var(--dim)}\n.prefs-panel{display:none;position:absolute;top:var(--top-h);right:1rem;background:var(--card);border:1px solid var(--border);border-radius:10px;padding:0.8rem 1rem;z-index:40;min-width:200px;box-shadow:0 8px 30px #0008}\n.prefs-panel.open{display:block}\n.prefs-label{font-size:0.6rem;text-transform:uppercase;letter-spacing:0.05em;color:var(--dim);font-weight:600;margin-bottom:0.3rem}\n.prefs-label:not(:first-child){margin-top:0.7rem}\n.prefs-row{display:flex;gap:0.3rem}\n.prefs-opt{background:var(--card-alt);border:1px solid var(--border);color:var(--muted);font-size:0.68rem;padding:0.25rem 0.6rem;border-radius:6px;cursor:pointer;font-family:inherit;transition:all 0.1s}\n.prefs-opt:hover{color:var(--text);border-color:var(--dim)}\n.prefs-opt.active{background:var(--accent);color:#fff;border-color:var(--accent)}\n[data-theme=\"light\"] .prefs-panel{box-shadow:0 8px 30px #0002}\n";
@@ -136,6 +136,9 @@ h3{font-size:0.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:
136
136
  .src-ln{padding:0 0.5rem;white-space:pre}
137
137
  .src-hl{padding:0 0.5rem;white-space:pre;background:#eab30815;border-left:2px solid var(--warn)}
138
138
  .src-num{color:var(--dim);margin-right:0.5rem;user-select:none;display:inline-block;min-width:2.5rem;text-align:right}
139
+ .src-prompt{padding:0.3rem 0.5rem;border-top:1px solid var(--border);display:flex;justify-content:flex-end}
140
+ .src-fix-btn{background:var(--card);border:1px solid var(--border);color:var(--accent);font-size:0.62rem;padding:0.2rem 0.6rem;border-radius:4px;cursor:pointer;font-family:inherit}
141
+ .src-fix-btn:hover{background:var(--accent);color:#fff;border-color:var(--accent)}
139
142
 
140
143
  /* ── All issues table ── */
141
144
  .isf{color:var(--muted);font-size:0.75rem;margin-bottom:0.8rem}
@@ -125,8 +125,8 @@ export function runAccessibility(cwd) {
125
125
  }
126
126
  }
127
127
  }
128
- // 7. Check for html lang attribute in index.html
129
- const htmlPaths = ["index.html", "web/index.html", "public/index.html"];
128
+ // 7. Check for html lang attribute + viewport + mobile meta in index.html
129
+ const htmlPaths = ["index.html", "web/index.html", "public/index.html", "src/index.html"];
130
130
  for (const h of htmlPaths) {
131
131
  const full = join(cwd, h);
132
132
  if (!existsSync(full))
@@ -136,6 +136,42 @@ export function runAccessibility(cwd) {
136
136
  missingLang++;
137
137
  issues.push({ severity: "warning", message: "<html> missing lang attribute", file: h, rule: "html-lang" });
138
138
  }
139
+ // Mobile viewport
140
+ if (!/<meta[^>]*name=["']viewport["']/.test(content)) {
141
+ issues.push({ severity: "error", message: "Missing <meta name=\"viewport\"> — page won't scale on mobile", file: h, rule: "missing-viewport" });
142
+ }
143
+ // charset
144
+ if (!/<meta[^>]*charset=/i.test(content)) {
145
+ issues.push({ severity: "warning", message: "Missing <meta charset> — may cause encoding issues", file: h, rule: "missing-charset" });
146
+ }
147
+ // Touch icon for mobile bookmarks
148
+ if (!/<link[^>]*apple-touch-icon/.test(content) && !/<link[^>]*icon/.test(content)) {
149
+ issues.push({ severity: "info", message: "No favicon or apple-touch-icon — poor mobile bookmark experience", file: h, rule: "missing-icon" });
150
+ }
151
+ }
152
+ // 8. Mobile-unfriendly patterns in components
153
+ for (const f of files) {
154
+ const source = f.rawContent || f.content;
155
+ const lines = source.split("\n");
156
+ for (let i = 0; i < lines.length; i++) {
157
+ const line = lines[i];
158
+ // Fixed pixel widths that break on mobile
159
+ if (/style=.*width:\s*\d{4,}px/.test(line)) {
160
+ issues.push({ severity: "info", message: "Fixed width ≥1000px — likely breaks on mobile", file: f.path, line: i + 1, rule: "fixed-width" });
161
+ }
162
+ // Horizontal scroll containers without overflow handling
163
+ if (/overflow-x:\s*(?:scroll|auto)/.test(line) && !/\btouch\b/.test(line) && !/-webkit-overflow-scrolling/.test(line)) {
164
+ issues.push({ severity: "info", message: "Horizontal scroll without touch-action — poor mobile scroll UX", file: f.path, line: i + 1, rule: "touch-scroll" });
165
+ }
166
+ // Hover-only interactions (no touch fallback)
167
+ if (/onMouseEnter=|@mouseenter|on:mouseenter/.test(line) && !/onClick=|@click|on:click|onTouchStart|@touchstart/.test(line)) {
168
+ issues.push({ severity: "info", message: "Hover-only interaction — unreachable on touch devices", file: f.path, line: i + 1, rule: "hover-only" });
169
+ }
170
+ // Tiny touch targets
171
+ if (/(?:width|height):\s*(?:1[0-9]|[1-9])px/.test(line) && /(?:onClick|@click|on:click|button|<a )/.test(line)) {
172
+ issues.push({ severity: "info", message: "Touch target likely <44px — hard to tap on mobile (WCAG 2.5.8)", file: f.path, line: i + 1, rule: "small-touch-target" });
173
+ }
174
+ }
139
175
  }
140
176
  const errors = issues.filter((i) => i.severity === "error").length;
141
177
  const warnings = issues.filter((i) => i.severity === "warning").length;
@@ -82,8 +82,10 @@ export async function runDeadPatterns(cwd) {
82
82
  let started = false;
83
83
  for (let j = i; j < Math.min(i + 20, lines.length); j++) {
84
84
  const l = lines[j];
85
- braceDepth += (l.match(/\{/g) || []).length;
86
- braceDepth -= (l.match(/\}/g) || []).length;
85
+ // For "} catch (e) {" on one line, only count braces after "catch"
86
+ const braceText = j === i && l.includes("catch") ? l.slice(l.indexOf("catch")) : l;
87
+ braceDepth += (braceText.match(/\{/g) || []).length;
88
+ braceDepth -= (braceText.match(/\}/g) || []).length;
87
89
  if (braceDepth > 0)
88
90
  started = true;
89
91
  if (started && j > i) {
@@ -178,6 +178,36 @@ export function runPerformance(cwd) {
178
178
  });
179
179
  }
180
180
  }
181
+ // ── 7. PWA readiness (web projects only) ──
182
+ const isWebProject = !!(deps.react || deps.vue || deps.svelte || deps["@sveltejs/kit"] || deps.next || deps.nuxt);
183
+ if (isWebProject) {
184
+ const manifestPaths = ["public/manifest.json", "public/manifest.webmanifest", "manifest.json", "web/manifest.json"];
185
+ const hasManifest = manifestPaths.some((p) => existsSync(join(cwd, p)));
186
+ if (!hasManifest) {
187
+ issues.push({ severity: "info", message: "No web app manifest — can't install as PWA or add to home screen", rule: "no-manifest" });
188
+ }
189
+ const swPaths = ["public/sw.js", "public/service-worker.js", "src/service-worker.ts", "src/sw.ts"];
190
+ const hasSW = swPaths.some((p) => existsSync(join(cwd, p))) || !!(deps["workbox-webpack-plugin"] || deps["vite-plugin-pwa"] || deps["next-pwa"]);
191
+ if (!hasSW) {
192
+ issues.push({ severity: "info", message: "No service worker — app won't work offline", rule: "no-service-worker" });
193
+ }
194
+ }
195
+ // ── 8. CSS best practices ──
196
+ const cssFiles = getProductionFiles(cwd).filter((f) => f.ext === ".css");
197
+ for (const f of cssFiles) {
198
+ const lines = f.content.split("\n");
199
+ for (let i = 0; i < lines.length; i++) {
200
+ const line = lines[i];
201
+ // !important overuse
202
+ if (/!important/.test(line)) {
203
+ issues.push({ severity: "info", message: "!important — specificity escape hatch, usually a sign of CSS architecture issues", file: f.path, line: i + 1, rule: "css-important" });
204
+ }
205
+ }
206
+ // No media queries in CSS with fixed layouts
207
+ if (f.content.length > 500 && !/@media/.test(f.content) && /width:\s*\d{3,}px/.test(f.content)) {
208
+ issues.push({ severity: "info", message: "CSS with fixed widths but no @media queries — likely not responsive", file: f.path, rule: "no-media-queries" });
209
+ }
210
+ }
181
211
  // Score — proportional to codebase, capped per category
182
212
  const totalFiles = sourceFiles.length || 1;
183
213
  const barrelPenalty = Math.min(15, (barrelImports / totalFiles) * 200);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibecodeqa/cli",
3
- "version": "0.36.1",
3
+ "version": "0.37.0",
4
4
  "description": "Code health scanner for the AI coding era. 25 checks, zero config, full report.",
5
5
  "type": "module",
6
6
  "bin": {