@vibecodeqa/cli 0.35.6 → 0.35.8
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 +162 -43
- package/package.json +1 -1
package/dist/monitor.js
CHANGED
|
@@ -10,7 +10,7 @@ import { useState, useEffect, useCallback, useRef } from "react";
|
|
|
10
10
|
import { render, Box, Text, useApp, useInput, useStdout } from "ink";
|
|
11
11
|
import { resolve, join, basename } from "node:path";
|
|
12
12
|
import { watch, existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
|
|
13
|
-
import { execFile } from "node:child_process";
|
|
13
|
+
import { execFile, execSync } from "node:child_process";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
import { detectStack, detectWorkspace } from "./detect.js";
|
|
16
16
|
import { loadHistory } from "./history.js";
|
|
@@ -68,9 +68,19 @@ function spark(values) {
|
|
|
68
68
|
function ts() {
|
|
69
69
|
return new Date().toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
|
70
70
|
}
|
|
71
|
-
function
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
function copyToClipboard(text) {
|
|
72
|
+
try {
|
|
73
|
+
const cmd = process.platform === "darwin" ? "pbcopy" : process.platform === "win32" ? "clip" : "xclip -selection clipboard";
|
|
74
|
+
execSync(cmd, { input: text, stdio: ["pipe", "pipe", "pipe"] });
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function buildFixPrompt(checkName, issue) {
|
|
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.`;
|
|
74
84
|
}
|
|
75
85
|
// ── Scan via child process — UI never freezes ──
|
|
76
86
|
function runScanProcess(cwd, skipTests) {
|
|
@@ -111,12 +121,6 @@ function ScorePanel({ state, height }) {
|
|
|
111
121
|
const active = state.checks.filter((c) => !c.details.skipped && !c.details.comingSoon);
|
|
112
122
|
return (_jsxs(Box, { flexDirection: "column", width: 24, height: height, borderStyle: "round", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { bold: true, color: "magenta", children: " \u25C8 Score" }), _jsx(Box, { justifyContent: "center", marginY: 1, children: _jsx(Text, { color: gc(state.grade), bold: true, children: state.scanning ? " scanning..." : ` ${state.grade} ${state.score}/100` }) }), _jsxs(Text, { dimColor: true, children: [" ", active.length, " checks \u00B7 ", state.totalIssues, " issues"] }), _jsxs(Text, { dimColor: true, children: [" ", state.duration, "ms \u00B7 scan #", state.scanCount] }), s && _jsxs(Text, { color: "cyan", children: [" ", s] })] }));
|
|
113
123
|
}
|
|
114
|
-
function ChecksPanel({ checks, height }) {
|
|
115
|
-
const active = checks.filter((c) => !c.details.skipped && !c.details.comingSoon);
|
|
116
|
-
const pro = checks.filter((c) => c.details.comingSoon);
|
|
117
|
-
const visibleLines = Math.max(1, height - 3);
|
|
118
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, width: 24, height: height, overflowY: "hidden", children: [_jsx(Text, { bold: true, color: "magenta", children: " \u25C8 Checks" }), active.slice(0, visibleLines).map((c) => (_jsxs(Text, { children: [_jsxs(Text, { color: gc(c.grade), children: [" ", c.grade === "A" ? "●" : c.grade === "B" ? "◐" : "○", " "] }), _jsx(Text, { children: c.name.slice(0, 13).padEnd(13) }), _jsxs(Text, { dimColor: true, children: [" ", bar(c.score, 4), " "] }), _jsx(Text, { color: gc(c.grade), children: String(c.score).padStart(3) })] }, c.name))), active.length > visibleLines && _jsxs(Text, { dimColor: true, children: [" +", active.length - visibleLines, " more"] }), pro.length > 0 && _jsxs(Text, { color: "magenta", children: [" \u25C6 ", pro.length, " Pro"] })] }));
|
|
119
|
-
}
|
|
120
124
|
function ActivityPanel({ log, height }) {
|
|
121
125
|
const colors = {
|
|
122
126
|
info: "gray", scan: "cyan", change: "yellow",
|
|
@@ -125,18 +129,6 @@ function ActivityPanel({ log, height }) {
|
|
|
125
129
|
const visibleLines = Math.max(1, height - 3);
|
|
126
130
|
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, height: height, overflowY: "hidden", children: [_jsx(Text, { bold: true, color: "magenta", children: " \u25C8 Activity" }), log.slice(-visibleLines).map((entry, i) => (_jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { dimColor: true, children: [entry.time, " "] }), _jsx(Text, { color: colors[entry.type], children: entry.text })] }, i)))] }));
|
|
127
131
|
}
|
|
128
|
-
function IssuesPanel({ checks, height }) {
|
|
129
|
-
const issues = checks
|
|
130
|
-
.flatMap((c) => c.issues.map((i) => ({ check: c.name, ...i })))
|
|
131
|
-
.sort((a, b) => {
|
|
132
|
-
const o = { error: 0, warning: 1, info: 2 };
|
|
133
|
-
return (o[a.severity] ?? 2) - (o[b.severity] ?? 2);
|
|
134
|
-
});
|
|
135
|
-
const errors = issues.filter((i) => i.severity === "error").length;
|
|
136
|
-
const warnings = issues.filter((i) => i.severity === "warning").length;
|
|
137
|
-
const visibleLines = Math.max(1, height - 3);
|
|
138
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, height: height, overflowY: "hidden", children: [_jsxs(Text, { bold: true, color: "magenta", children: [" ", "\u25C8 Issues (", issues.length, ")", errors > 0 && _jsxs(Text, { color: "red", children: [" ", errors, "E"] }), warnings > 0 && _jsxs(Text, { color: "yellow", children: [" ", warnings, "W"] })] }), issues.slice(0, visibleLines).map((iss, i) => (_jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { color: sc(iss.severity), bold: true, children: [iss.severity[0].toUpperCase(), " "] }), _jsxs(Text, { dimColor: true, children: [(iss.check || "").slice(0, 11).padEnd(11), " "] }), iss.file && _jsxs(Text, { color: "cyan", children: [basename(String(iss.file)).slice(0, 18).padEnd(18), " "] }), _jsx(Text, { children: iss.message.slice(0, 45) })] }, i))), issues.length > visibleLines && _jsxs(Text, { dimColor: true, children: [" +", issues.length - visibleLines, " more"] })] }));
|
|
139
|
-
}
|
|
140
132
|
function ConfigScreen({ monCfg, onSave, onClose, }) {
|
|
141
133
|
const [cursor, setCursor] = useState(0);
|
|
142
134
|
const [cfg, setCfg] = useState(JSON.parse(JSON.stringify(monCfg)));
|
|
@@ -274,13 +266,57 @@ function loadCachedScan(cwd) {
|
|
|
274
266
|
return null;
|
|
275
267
|
}
|
|
276
268
|
}
|
|
269
|
+
// ── Check Detail View ──
|
|
270
|
+
function CheckDetail({ check, height, cursor, copied }) {
|
|
271
|
+
const bodyHeight = height - 5; // header + score + blank + footer margin
|
|
272
|
+
// Each issue takes 2-3 lines: header line + message (wraps if long)
|
|
273
|
+
// Estimate lines per issue for scroll calculation
|
|
274
|
+
const issueHeights = check.issues.map((iss) => {
|
|
275
|
+
const msgLen = iss.message.length;
|
|
276
|
+
return msgLen > 80 ? 3 : 2; // 2 lines base, 3 if message wraps
|
|
277
|
+
});
|
|
278
|
+
// Find scroll window that keeps cursor visible
|
|
279
|
+
let scrollStart = 0;
|
|
280
|
+
let linesUsed = 0;
|
|
281
|
+
// First, find how many items fit
|
|
282
|
+
const fits = [];
|
|
283
|
+
for (let i = 0; i < check.issues.length; i++) {
|
|
284
|
+
if (linesUsed + issueHeights[i] > bodyHeight)
|
|
285
|
+
break;
|
|
286
|
+
fits.push(i);
|
|
287
|
+
linesUsed += issueHeights[i];
|
|
288
|
+
}
|
|
289
|
+
const maxVisible = fits.length || 1;
|
|
290
|
+
// Adjust scroll so cursor is visible
|
|
291
|
+
if (cursor >= scrollStart + maxVisible)
|
|
292
|
+
scrollStart = cursor - maxVisible + 1;
|
|
293
|
+
if (cursor < scrollStart)
|
|
294
|
+
scrollStart = cursor;
|
|
295
|
+
scrollStart = Math.max(0, Math.min(scrollStart, check.issues.length - maxVisible));
|
|
296
|
+
// Collect visible items within height budget
|
|
297
|
+
const visible = [];
|
|
298
|
+
let usedLines = 0;
|
|
299
|
+
for (let i = scrollStart; i < check.issues.length; i++) {
|
|
300
|
+
if (usedLines + issueHeights[i] > bodyHeight)
|
|
301
|
+
break;
|
|
302
|
+
visible.push({ issue: check.issues[i], idx: i });
|
|
303
|
+
usedLines += issueHeights[i];
|
|
304
|
+
}
|
|
305
|
+
const remaining = check.issues.length - (scrollStart + visible.length);
|
|
306
|
+
return (_jsxs(Box, { flexDirection: "column", height: height, paddingX: 1, overflowY: "hidden", children: [_jsxs(Text, { bold: true, color: "magenta", children: [" \u25C8 ", check.name] }), _jsxs(Text, { children: [_jsxs(Text, { color: gc(check.grade), bold: true, children: [" ", check.grade, " ", check.score, "/100"] }), _jsxs(Text, { dimColor: true, children: [" \u00B7 ", check.issues.length, " issues \u00B7 ", check.duration, "ms"] }), copied && _jsx(Text, { color: "green", bold: true, children: " \u2713 Copied!" })] }), _jsx(Text, { children: " " }), check.issues.length === 0 ? (_jsx(Text, { color: "green", children: " No issues found." })) : (_jsxs(_Fragment, { children: [visible.map(({ issue: iss, idx }) => {
|
|
307
|
+
const sel = idx === cursor;
|
|
308
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 0, children: [_jsxs(Text, { children: [_jsx(Text, { color: sel ? "white" : "gray", children: sel ? "▸" : " " }), _jsxs(Text, { color: sc(iss.severity), bold: true, children: [iss.severity[0].toUpperCase(), " "] }), iss.file && _jsxs(Text, { color: "cyan", children: [String(iss.file), iss.line ? `:${iss.line}` : "", " "] }), iss.rule && _jsxs(Text, { dimColor: true, children: ["(", iss.rule, ")"] })] }), _jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: sel ? "white" : "gray", children: " " }), _jsx(Text, { color: sel ? "white" : undefined, children: iss.message })] })] }, idx));
|
|
309
|
+
}), remaining > 0 && _jsxs(Text, { dimColor: true, children: [" +", remaining, " more (\u2193 to scroll)"] })] }))] }));
|
|
310
|
+
}
|
|
277
311
|
function MonitorApp({ cwd }) {
|
|
278
312
|
const { exit } = useApp();
|
|
279
313
|
const { stdout } = useStdout();
|
|
280
314
|
const rows = stdout?.rows ?? 30;
|
|
281
315
|
const cached = loadCachedScan(cwd);
|
|
282
316
|
const [monCfg, setMonCfg] = useState(() => loadMonitorConfig(cwd));
|
|
283
|
-
const [mode, setMode] = useState("
|
|
317
|
+
const [mode, setMode] = useState({ view: "dashboard" });
|
|
318
|
+
const [panel, setPanel] = useState("checks");
|
|
319
|
+
const [cursor, setCursor] = useState(0);
|
|
284
320
|
const [state, setState] = useState(cached ?? {
|
|
285
321
|
checks: [], score: 0, grade: "?", duration: 0,
|
|
286
322
|
totalIssues: 0, scanning: true, scanCount: 0, scores: [],
|
|
@@ -288,6 +324,7 @@ function MonitorApp({ cwd }) {
|
|
|
288
324
|
const [log, setLog] = useState([
|
|
289
325
|
{ time: ts(), text: cached ? `Loaded cached scan: ${cached.grade} ${cached.score}/100` : `Monitoring ${basename(cwd)}...`, type: cached ? "scan" : "info" },
|
|
290
326
|
]);
|
|
327
|
+
const [copied, setCopied] = useState(false);
|
|
291
328
|
const scanningRef = useRef(false);
|
|
292
329
|
const prevScoreRef = useRef(cached ? cached.score : null);
|
|
293
330
|
const addLog = useCallback((text, type = "info") => {
|
|
@@ -295,6 +332,15 @@ function MonitorApp({ cwd }) {
|
|
|
295
332
|
}, []);
|
|
296
333
|
const workspace = detectWorkspace(cwd);
|
|
297
334
|
const stack = detectStack(cwd, workspace);
|
|
335
|
+
// Derived lists for navigation
|
|
336
|
+
const activeChecks = state.checks.filter((c) => !c.details.skipped && !c.details.comingSoon);
|
|
337
|
+
const allIssues = state.checks
|
|
338
|
+
.flatMap((c) => c.issues.map((i) => ({ check: c.name, ...i })))
|
|
339
|
+
.sort((a, b) => {
|
|
340
|
+
const o = { error: 0, warning: 1, info: 2 };
|
|
341
|
+
return (o[a.severity] ?? 2) - (o[b.severity] ?? 2);
|
|
342
|
+
});
|
|
343
|
+
const currentList = panel === "checks" ? activeChecks : allIssues;
|
|
298
344
|
const doScan = useCallback(async () => {
|
|
299
345
|
if (scanningRef.current)
|
|
300
346
|
return;
|
|
@@ -332,7 +378,6 @@ function MonitorApp({ cwd }) {
|
|
|
332
378
|
prevScoreRef.current = result.score;
|
|
333
379
|
scanningRef.current = false;
|
|
334
380
|
}, [cwd, monCfg, addLog]);
|
|
335
|
-
// Initial scan
|
|
336
381
|
useEffect(() => { doScan(); }, [doScan]);
|
|
337
382
|
// File watcher
|
|
338
383
|
useEffect(() => {
|
|
@@ -355,38 +400,104 @@ function MonitorApp({ cwd }) {
|
|
|
355
400
|
return () => { for (const w of watchers)
|
|
356
401
|
w.close(); };
|
|
357
402
|
}, [cwd, workspace, doScan, addLog, monCfg.debounceMs]);
|
|
358
|
-
// Keyboard —
|
|
403
|
+
// ── Keyboard — unified navigation ──
|
|
359
404
|
useInput((input, key) => {
|
|
405
|
+
// Quit from anywhere
|
|
406
|
+
if (input === "q" || (key.ctrl && input === "c")) {
|
|
407
|
+
exit();
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
// Esc: drill up or quit
|
|
360
411
|
if (key.escape) {
|
|
361
|
-
if (mode !== "
|
|
362
|
-
setMode("
|
|
412
|
+
if (mode.view !== "dashboard") {
|
|
413
|
+
setMode({ view: "dashboard" });
|
|
414
|
+
setCursor(0);
|
|
363
415
|
return;
|
|
364
416
|
}
|
|
365
417
|
exit();
|
|
366
418
|
return;
|
|
367
419
|
}
|
|
368
|
-
|
|
369
|
-
|
|
420
|
+
// Global shortcuts
|
|
421
|
+
if (input === "r" && mode.view === "dashboard") {
|
|
422
|
+
doScan();
|
|
370
423
|
return;
|
|
371
424
|
}
|
|
372
|
-
|
|
425
|
+
// View switching
|
|
426
|
+
if (input === "t") {
|
|
427
|
+
setMode({ view: "trends" });
|
|
428
|
+
setCursor(0);
|
|
373
429
|
return;
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
430
|
+
}
|
|
431
|
+
if (input === "c" && mode.view !== "config") {
|
|
432
|
+
setMode({ view: "config" });
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
// Dashboard navigation
|
|
436
|
+
if (mode.view === "dashboard") {
|
|
437
|
+
// Tab switches focused panel
|
|
438
|
+
if (key.tab) {
|
|
439
|
+
setPanel((p) => p === "checks" ? "issues" : "checks");
|
|
440
|
+
setCursor(0);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
// ↑↓ navigate within panel
|
|
444
|
+
if (key.upArrow)
|
|
445
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
446
|
+
if (key.downArrow)
|
|
447
|
+
setCursor((c) => Math.min(currentList.length - 1, c + 1));
|
|
448
|
+
// Enter: drill into selected check
|
|
449
|
+
if (key.return && panel === "checks" && activeChecks[cursor]) {
|
|
450
|
+
setMode({ view: "check-detail", checkName: activeChecks[cursor].name });
|
|
451
|
+
setCursor(0);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
// Enter on issue: drill into that check
|
|
455
|
+
if (key.return && panel === "issues" && allIssues[cursor]) {
|
|
456
|
+
setMode({ view: "check-detail", checkName: allIssues[cursor].check });
|
|
457
|
+
setCursor(0);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
// Check detail: ↑↓ scroll, Enter copies fix prompt
|
|
462
|
+
if (mode.view === "check-detail") {
|
|
463
|
+
const check = state.checks.find((c) => c.name === mode.checkName);
|
|
464
|
+
if (check) {
|
|
465
|
+
if (key.upArrow)
|
|
466
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
467
|
+
if (key.downArrow)
|
|
468
|
+
setCursor((c) => Math.min(check.issues.length - 1, c + 1));
|
|
469
|
+
if (key.return && check.issues[cursor]) {
|
|
470
|
+
const prompt = buildFixPrompt(check.name, check.issues[cursor]);
|
|
471
|
+
if (copyToClipboard(prompt)) {
|
|
472
|
+
setCopied(true);
|
|
473
|
+
addLog(`Copied fix prompt for ${check.name}:${check.issues[cursor].file || ""}`, "info");
|
|
474
|
+
setTimeout(() => setCopied(false), 2000);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
// Trends: ↑↓ scroll
|
|
480
|
+
if (mode.view === "trends") {
|
|
481
|
+
if (key.upArrow)
|
|
482
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
483
|
+
if (key.downArrow)
|
|
484
|
+
setCursor((c) => c + 1);
|
|
485
|
+
}
|
|
380
486
|
});
|
|
381
487
|
const proj = basename(cwd);
|
|
382
488
|
const p = monCfg.panels;
|
|
383
|
-
|
|
384
|
-
|
|
489
|
+
// ── Render views ──
|
|
490
|
+
if (mode.view === "check-detail") {
|
|
491
|
+
const check = state.checks.find((c) => c.name === mode.checkName);
|
|
492
|
+
return (_jsxs(Box, { flexDirection: "column", height: rows, children: [_jsx(Header, { proj: proj, stack: stack, workspace: workspace, state: state }), check ? _jsx(CheckDetail, { check: check, height: rows - 3, cursor: cursor, copied: copied }) : _jsx(Text, { dimColor: true, children: " Check not found" }), _jsx(Box, { paddingX: 1, children: _jsx(Text, { dimColor: true, children: "Esc back \u00B7 \u2191\u2193 select \u00B7 Enter copy fix prompt \u00B7 q quit" }) })] }));
|
|
493
|
+
}
|
|
494
|
+
if (mode.view === "trends") {
|
|
495
|
+
return (_jsx(Box, { flexDirection: "column", height: rows, children: _jsx(TrendsScreen, { cwd: cwd, height: rows, onClose: () => setMode({ view: "dashboard" }) }) }));
|
|
385
496
|
}
|
|
386
|
-
if (mode === "config") {
|
|
387
|
-
return (_jsx(Box, { flexDirection: "column", height: rows, justifyContent: "center", alignItems: "center", children: _jsx(ConfigScreen, { monCfg: monCfg, onSave: (cfg) => { setMonCfg(cfg); saveMonitorConfig(cwd, cfg); addLog("Settings saved", "info"); }, onClose: () => setMode("
|
|
497
|
+
if (mode.view === "config") {
|
|
498
|
+
return (_jsx(Box, { flexDirection: "column", height: rows, justifyContent: "center", alignItems: "center", children: _jsx(ConfigScreen, { monCfg: monCfg, onSave: (cfg) => { setMonCfg(cfg); saveMonitorConfig(cwd, cfg); addLog("Settings saved", "info"); }, onClose: () => setMode({ view: "dashboard" }) }) }));
|
|
388
499
|
}
|
|
389
|
-
//
|
|
500
|
+
// ── Dashboard ──
|
|
390
501
|
const sidebarVisible = p.score || p.checks;
|
|
391
502
|
const mainVisible = p.activity || p.issues;
|
|
392
503
|
const bodyRows = rows - 4;
|
|
@@ -396,8 +507,16 @@ function MonitorApp({ cwd }) {
|
|
|
396
507
|
const issuesH = p.issues ? bodyRows - activityH : 0;
|
|
397
508
|
const errorCount = state.checks.reduce((s, c) => s + c.issues.filter((i) => i.severity === "error").length, 0);
|
|
398
509
|
const warnCount = state.checks.reduce((s, c) => s + c.issues.filter((i) => i.severity === "warning").length, 0);
|
|
399
|
-
|
|
400
|
-
|
|
510
|
+
return (_jsxs(Box, { flexDirection: "column", height: rows, children: [_jsx(Header, { proj: proj, stack: stack, workspace: workspace, state: state }), _jsx(Box, { paddingX: 1, gap: 2, height: 1, children: state.scanCount > 0 && (_jsxs(_Fragment, { children: [_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "E " }), _jsx(Text, { color: "red", bold: true, children: errorCount })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "W " }), _jsx(Text, { color: "yellow", bold: true, children: warnCount })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "checks " }), _jsx(Text, { bold: true, children: activeChecks.length })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "scan " }), _jsxs(Text, { children: [state.duration, "ms"] })] }), state.scores.length >= 2 && _jsx(Text, { color: "cyan", children: spark(state.scores) })] })) }), _jsxs(Box, { height: bodyRows, children: [sidebarVisible && (_jsxs(Box, { flexDirection: "column", width: 26, children: [p.score && _jsx(ScorePanel, { state: state, height: scoreH }), p.checks && (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: panel === "checks" ? "magenta" : "gray", paddingX: 1, width: 24, height: checksH, overflowY: "hidden", children: [_jsxs(Text, { bold: true, color: "magenta", children: [" \u25C8 Checks ", panel === "checks" && _jsx(Text, { dimColor: true, children: "\u25C4" })] }), activeChecks.slice(0, checksH - 3).map((c, i) => {
|
|
511
|
+
const sel = panel === "checks" && i === cursor;
|
|
512
|
+
return (_jsxs(Text, { children: [_jsxs(Text, { color: sel ? "white" : gc(c.grade), children: [sel ? "▸" : " ", c.grade === "A" ? "●" : c.grade === "B" ? "◐" : "○", " "] }), _jsx(Text, { bold: sel, children: c.name.slice(0, 13).padEnd(13) }), _jsx(Text, { color: gc(c.grade), children: String(c.score).padStart(3) })] }, c.name));
|
|
513
|
+
})] }))] })), mainVisible && (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [p.activity && _jsx(ActivityPanel, { log: log, height: activityH }), p.issues && (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: panel === "issues" ? "magenta" : "gray", paddingX: 1, height: issuesH, overflowY: "hidden", children: [_jsxs(Text, { bold: true, color: "magenta", children: [" ", "\u25C8 Issues (", allIssues.length, ") ", panel === "issues" && _jsx(Text, { dimColor: true, children: "\u25C4" })] }), allIssues.slice(0, issuesH - 3).map((iss, i) => {
|
|
514
|
+
const sel = panel === "issues" && i === cursor;
|
|
515
|
+
return (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: sel ? "white" : "gray", children: sel ? "▸" : " " }), _jsxs(Text, { color: sc(iss.severity), bold: true, children: [iss.severity[0].toUpperCase(), " "] }), _jsxs(Text, { dimColor: true, children: [(iss.check || "").slice(0, 11).padEnd(11), " "] }), iss.file && _jsxs(Text, { color: "cyan", children: [basename(String(iss.file)).slice(0, 18).padEnd(18), " "] }), _jsx(Text, { children: iss.message.slice(0, 40) })] }, i));
|
|
516
|
+
}), allIssues.length > issuesH - 3 && _jsxs(Text, { dimColor: true, children: [" +", allIssues.length - (issuesH - 3), " more"] })] }))] })), !sidebarVisible && !mainVisible && (_jsx(Box, { height: bodyRows, justifyContent: "center", alignItems: "center", children: _jsx(Text, { dimColor: true, children: "All panels hidden. Press c to configure." }) }))] }), _jsx(Box, { paddingX: 1, justifyContent: "space-between", children: _jsx(Text, { dimColor: true, children: "Tab panel \u00B7 \u2191\u2193 select \u00B7 Enter drill \u00B7 Esc back \u00B7 r scan \u00B7 t trends \u00B7 c config \u00B7 q quit" }) })] }));
|
|
517
|
+
}
|
|
518
|
+
function Header({ proj, stack, workspace, state }) {
|
|
519
|
+
return (_jsxs(Box, { paddingX: 1, justifyContent: "space-between", children: [_jsxs(Text, { children: [_jsx(Text, { color: "magenta", bold: true, children: "vcqa monitor" }), _jsxs(Text, { dimColor: true, children: [" v", VERSION] })] }), _jsxs(Text, { children: [_jsx(Text, { bold: true, children: proj }), _jsxs(Text, { dimColor: true, children: [" ", stack.language, "/", stack.framework, workspace.isMonorepo ? ` · ${workspace.tool}` : ""] })] }), _jsxs(Text, { children: [state.score > 0 && _jsxs(Text, { color: gc(state.grade), bold: true, children: [state.grade, " ", state.score, " "] }), _jsx(Text, { dimColor: true, children: state.scanning ? "⟳ scanning" : "● watching" })] })] }));
|
|
401
520
|
}
|
|
402
521
|
// ── Entry ──
|
|
403
522
|
export async function startMonitor(cwd) {
|