@vibecodeqa/cli 0.35.6 → 0.35.7
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 +132 -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,27 @@ function loadCachedScan(cwd) {
|
|
|
274
266
|
return null;
|
|
275
267
|
}
|
|
276
268
|
}
|
|
269
|
+
// ── Check Detail View ──
|
|
270
|
+
function CheckDetail({ check, height, cursor, copied }) {
|
|
271
|
+
const visibleIssues = Math.max(1, height - 6);
|
|
272
|
+
// Scroll window: keep cursor visible
|
|
273
|
+
const scrollStart = Math.max(0, Math.min(cursor - Math.floor(visibleIssues / 2), check.issues.length - visibleIssues));
|
|
274
|
+
const visibleSlice = check.issues.slice(scrollStart, scrollStart + visibleIssues);
|
|
275
|
+
return (_jsxs(Box, { flexDirection: "column", height: height, paddingX: 1, 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: [visibleSlice.map((iss, i) => {
|
|
276
|
+
const idx = scrollStart + i;
|
|
277
|
+
const sel = idx === cursor;
|
|
278
|
+
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(), " "] }), iss.file && _jsxs(Text, { color: "cyan", children: [String(iss.file).slice(0, 28).padEnd(28), " "] }), iss.line && _jsx(Text, { dimColor: true, children: String(iss.line).padEnd(5) }), _jsx(Text, { children: iss.message.slice(0, 55) }), iss.rule && _jsxs(Text, { dimColor: true, children: [" (", iss.rule, ")"] })] }, idx));
|
|
279
|
+
}), check.issues.length > scrollStart + visibleIssues && _jsxs(Text, { dimColor: true, children: [" +", check.issues.length - scrollStart - visibleIssues, " more"] })] }))] }));
|
|
280
|
+
}
|
|
277
281
|
function MonitorApp({ cwd }) {
|
|
278
282
|
const { exit } = useApp();
|
|
279
283
|
const { stdout } = useStdout();
|
|
280
284
|
const rows = stdout?.rows ?? 30;
|
|
281
285
|
const cached = loadCachedScan(cwd);
|
|
282
286
|
const [monCfg, setMonCfg] = useState(() => loadMonitorConfig(cwd));
|
|
283
|
-
const [mode, setMode] = useState("
|
|
287
|
+
const [mode, setMode] = useState({ view: "dashboard" });
|
|
288
|
+
const [panel, setPanel] = useState("checks");
|
|
289
|
+
const [cursor, setCursor] = useState(0);
|
|
284
290
|
const [state, setState] = useState(cached ?? {
|
|
285
291
|
checks: [], score: 0, grade: "?", duration: 0,
|
|
286
292
|
totalIssues: 0, scanning: true, scanCount: 0, scores: [],
|
|
@@ -288,6 +294,7 @@ function MonitorApp({ cwd }) {
|
|
|
288
294
|
const [log, setLog] = useState([
|
|
289
295
|
{ time: ts(), text: cached ? `Loaded cached scan: ${cached.grade} ${cached.score}/100` : `Monitoring ${basename(cwd)}...`, type: cached ? "scan" : "info" },
|
|
290
296
|
]);
|
|
297
|
+
const [copied, setCopied] = useState(false);
|
|
291
298
|
const scanningRef = useRef(false);
|
|
292
299
|
const prevScoreRef = useRef(cached ? cached.score : null);
|
|
293
300
|
const addLog = useCallback((text, type = "info") => {
|
|
@@ -295,6 +302,15 @@ function MonitorApp({ cwd }) {
|
|
|
295
302
|
}, []);
|
|
296
303
|
const workspace = detectWorkspace(cwd);
|
|
297
304
|
const stack = detectStack(cwd, workspace);
|
|
305
|
+
// Derived lists for navigation
|
|
306
|
+
const activeChecks = state.checks.filter((c) => !c.details.skipped && !c.details.comingSoon);
|
|
307
|
+
const allIssues = state.checks
|
|
308
|
+
.flatMap((c) => c.issues.map((i) => ({ check: c.name, ...i })))
|
|
309
|
+
.sort((a, b) => {
|
|
310
|
+
const o = { error: 0, warning: 1, info: 2 };
|
|
311
|
+
return (o[a.severity] ?? 2) - (o[b.severity] ?? 2);
|
|
312
|
+
});
|
|
313
|
+
const currentList = panel === "checks" ? activeChecks : allIssues;
|
|
298
314
|
const doScan = useCallback(async () => {
|
|
299
315
|
if (scanningRef.current)
|
|
300
316
|
return;
|
|
@@ -332,7 +348,6 @@ function MonitorApp({ cwd }) {
|
|
|
332
348
|
prevScoreRef.current = result.score;
|
|
333
349
|
scanningRef.current = false;
|
|
334
350
|
}, [cwd, monCfg, addLog]);
|
|
335
|
-
// Initial scan
|
|
336
351
|
useEffect(() => { doScan(); }, [doScan]);
|
|
337
352
|
// File watcher
|
|
338
353
|
useEffect(() => {
|
|
@@ -355,38 +370,104 @@ function MonitorApp({ cwd }) {
|
|
|
355
370
|
return () => { for (const w of watchers)
|
|
356
371
|
w.close(); };
|
|
357
372
|
}, [cwd, workspace, doScan, addLog, monCfg.debounceMs]);
|
|
358
|
-
// Keyboard —
|
|
373
|
+
// ── Keyboard — unified navigation ──
|
|
359
374
|
useInput((input, key) => {
|
|
375
|
+
// Quit from anywhere
|
|
376
|
+
if (input === "q" || (key.ctrl && input === "c")) {
|
|
377
|
+
exit();
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
// Esc: drill up or quit
|
|
360
381
|
if (key.escape) {
|
|
361
|
-
if (mode !== "
|
|
362
|
-
setMode("
|
|
382
|
+
if (mode.view !== "dashboard") {
|
|
383
|
+
setMode({ view: "dashboard" });
|
|
384
|
+
setCursor(0);
|
|
363
385
|
return;
|
|
364
386
|
}
|
|
365
387
|
exit();
|
|
366
388
|
return;
|
|
367
389
|
}
|
|
368
|
-
|
|
369
|
-
|
|
390
|
+
// Global shortcuts
|
|
391
|
+
if (input === "r" && mode.view === "dashboard") {
|
|
392
|
+
doScan();
|
|
370
393
|
return;
|
|
371
394
|
}
|
|
372
|
-
|
|
395
|
+
// View switching
|
|
396
|
+
if (input === "t") {
|
|
397
|
+
setMode({ view: "trends" });
|
|
398
|
+
setCursor(0);
|
|
373
399
|
return;
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
400
|
+
}
|
|
401
|
+
if (input === "c" && mode.view !== "config") {
|
|
402
|
+
setMode({ view: "config" });
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
// Dashboard navigation
|
|
406
|
+
if (mode.view === "dashboard") {
|
|
407
|
+
// Tab switches focused panel
|
|
408
|
+
if (key.tab) {
|
|
409
|
+
setPanel((p) => p === "checks" ? "issues" : "checks");
|
|
410
|
+
setCursor(0);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
// ↑↓ navigate within panel
|
|
414
|
+
if (key.upArrow)
|
|
415
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
416
|
+
if (key.downArrow)
|
|
417
|
+
setCursor((c) => Math.min(currentList.length - 1, c + 1));
|
|
418
|
+
// Enter: drill into selected check
|
|
419
|
+
if (key.return && panel === "checks" && activeChecks[cursor]) {
|
|
420
|
+
setMode({ view: "check-detail", checkName: activeChecks[cursor].name });
|
|
421
|
+
setCursor(0);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
// Enter on issue: drill into that check
|
|
425
|
+
if (key.return && panel === "issues" && allIssues[cursor]) {
|
|
426
|
+
setMode({ view: "check-detail", checkName: allIssues[cursor].check });
|
|
427
|
+
setCursor(0);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
// Check detail: ↑↓ scroll, Enter copies fix prompt
|
|
432
|
+
if (mode.view === "check-detail") {
|
|
433
|
+
const check = state.checks.find((c) => c.name === mode.checkName);
|
|
434
|
+
if (check) {
|
|
435
|
+
if (key.upArrow)
|
|
436
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
437
|
+
if (key.downArrow)
|
|
438
|
+
setCursor((c) => Math.min(check.issues.length - 1, c + 1));
|
|
439
|
+
if (key.return && check.issues[cursor]) {
|
|
440
|
+
const prompt = buildFixPrompt(check.name, check.issues[cursor]);
|
|
441
|
+
if (copyToClipboard(prompt)) {
|
|
442
|
+
setCopied(true);
|
|
443
|
+
addLog(`Copied fix prompt for ${check.name}:${check.issues[cursor].file || ""}`, "info");
|
|
444
|
+
setTimeout(() => setCopied(false), 2000);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
// Trends: ↑↓ scroll
|
|
450
|
+
if (mode.view === "trends") {
|
|
451
|
+
if (key.upArrow)
|
|
452
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
453
|
+
if (key.downArrow)
|
|
454
|
+
setCursor((c) => c + 1);
|
|
455
|
+
}
|
|
380
456
|
});
|
|
381
457
|
const proj = basename(cwd);
|
|
382
458
|
const p = monCfg.panels;
|
|
383
|
-
|
|
384
|
-
|
|
459
|
+
// ── Render views ──
|
|
460
|
+
if (mode.view === "check-detail") {
|
|
461
|
+
const check = state.checks.find((c) => c.name === mode.checkName);
|
|
462
|
+
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" }) })] }));
|
|
385
463
|
}
|
|
386
|
-
if (mode === "
|
|
387
|
-
return (_jsx(Box, { flexDirection: "column", height: rows,
|
|
464
|
+
if (mode.view === "trends") {
|
|
465
|
+
return (_jsx(Box, { flexDirection: "column", height: rows, children: _jsx(TrendsScreen, { cwd: cwd, height: rows, onClose: () => setMode({ view: "dashboard" }) }) }));
|
|
388
466
|
}
|
|
389
|
-
|
|
467
|
+
if (mode.view === "config") {
|
|
468
|
+
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" }) }) }));
|
|
469
|
+
}
|
|
470
|
+
// ── Dashboard ──
|
|
390
471
|
const sidebarVisible = p.score || p.checks;
|
|
391
472
|
const mainVisible = p.activity || p.issues;
|
|
392
473
|
const bodyRows = rows - 4;
|
|
@@ -396,8 +477,16 @@ function MonitorApp({ cwd }) {
|
|
|
396
477
|
const issuesH = p.issues ? bodyRows - activityH : 0;
|
|
397
478
|
const errorCount = state.checks.reduce((s, c) => s + c.issues.filter((i) => i.severity === "error").length, 0);
|
|
398
479
|
const warnCount = state.checks.reduce((s, c) => s + c.issues.filter((i) => i.severity === "warning").length, 0);
|
|
399
|
-
|
|
400
|
-
|
|
480
|
+
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) => {
|
|
481
|
+
const sel = panel === "checks" && i === cursor;
|
|
482
|
+
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));
|
|
483
|
+
})] }))] })), 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) => {
|
|
484
|
+
const sel = panel === "issues" && i === cursor;
|
|
485
|
+
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));
|
|
486
|
+
}), 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" }) })] }));
|
|
487
|
+
}
|
|
488
|
+
function Header({ proj, stack, workspace, state }) {
|
|
489
|
+
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
490
|
}
|
|
402
491
|
// ── Entry ──
|
|
403
492
|
export async function startMonitor(cwd) {
|