sickbay 1.16.3 → 1.16.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"TuiApp-LHb149v8.js","names":["scoreColor","scoreBar","scoreBar","scoreColor"],"sources":["../src/components/tui/ActivityPanel.tsx","../src/components/tui/hooks/useGitStatus.ts","../src/components/tui/GitPanel.tsx","../src/components/tui/HealthPanel.tsx","../src/components/tui/HelpPanel.tsx","../src/components/tui/hooks/useFileWatcher.ts","../src/components/tui/hooks/useSickbayRunner.ts","../src/components/tui/hooks/useTerminalSize.ts","../src/components/tui/HotkeyBar.tsx","../src/components/tui/MonorepoPanel.tsx","../src/components/tui/PanelBorder.tsx","../src/components/tui/QuickWinsPanel.tsx","../src/components/tui/ScorePanel.tsx","../src/components/tui/TrendPanel.tsx","../src/components/tui/TuiApp.tsx"],"sourcesContent":["import React from 'react';\n\nimport { Box, Text } from 'ink';\n\nexport interface ActivityEntry {\n timestamp: Date;\n type: 'scan-start' | 'scan-complete' | 'file-change' | 'git-change' | 'regression' | 'info';\n message: string;\n}\n\ninterface ActivityPanelProps {\n entries: ActivityEntry[];\n availableHeight: number;\n}\n\nconst TYPE_COLOR: Record<ActivityEntry['type'], string> = {\n 'scan-start': 'cyan',\n 'scan-complete': 'green',\n 'file-change': 'yellow',\n 'git-change': 'magenta',\n regression: 'red',\n info: 'gray',\n};\n\nfunction formatTime(date: Date): string {\n return date.toLocaleTimeString('en-US', { hour12: false });\n}\n\nexport function ActivityPanel({ entries, availableHeight }: ActivityPanelProps) {\n if (entries.length === 0) {\n return (\n <Box>\n <Text dimColor>No activity yet.</Text>\n </Box>\n );\n }\n\n const visible = entries.slice(-Math.max(1, availableHeight));\n\n return (\n <Box flexDirection=\"column\">\n {visible.map((entry) => (\n <Box key={`${entry.timestamp.getTime()}-${entry.type}`}>\n <Text dimColor>\n {formatTime(entry.timestamp)}\n {' '}\n </Text>\n <Text color={TYPE_COLOR[entry.type] || 'white'}>{entry.message}</Text>\n </Box>\n ))}\n </Box>\n );\n}\n","import { execFile } from 'child_process';\nimport { promisify } from 'util';\n\nimport { useState, useEffect, useRef } from 'react';\n\nconst exec = promisify(execFile);\n\nexport interface GitStatus {\n branch: string;\n modified: number;\n staged: number;\n untracked: number;\n ahead: number;\n behind: number;\n stashes: number;\n lastCommit: string;\n lastCommitTime: string;\n}\n\nasync function git(args: string[], cwd: string): Promise<string> {\n try {\n const { stdout } = await exec('git', args, { cwd, timeout: 5000 });\n return stdout.trim();\n } catch {\n return '';\n }\n}\n\nasync function fetchGitStatus(projectPath: string): Promise<GitStatus> {\n const [branch, porcelain, revList, stashList, logLine] = await Promise.all([\n git(['branch', '--show-current'], projectPath),\n git(['status', '--porcelain'], projectPath),\n git(['rev-list', '--left-right', '--count', 'HEAD...@{upstream}'], projectPath),\n git(['stash', 'list'], projectPath),\n git(['log', '-1', '--format=%s|%cr'], projectPath),\n ]);\n\n const lines = porcelain ? porcelain.split('\\n') : [];\n const staged = lines.filter((l) => /^[MADRC]/.test(l)).length;\n const modified = lines.filter((l) => /^.[MD]/.test(l)).length;\n const untracked = lines.filter((l) => l.startsWith('??')).length;\n\n let ahead = 0;\n let behind = 0;\n if (revList) {\n const parts = revList.split(/\\s+/);\n ahead = parseInt(parts[0], 10) || 0;\n behind = parseInt(parts[1], 10) || 0;\n }\n\n const stashes = stashList ? stashList.split('\\n').filter(Boolean).length : 0;\n const [lastCommit, lastCommitTime] = logLine.split('|');\n\n return {\n branch: branch || 'unknown',\n modified,\n staged,\n untracked,\n ahead,\n behind,\n stashes,\n lastCommit: lastCommit || '',\n lastCommitTime: lastCommitTime || '',\n };\n}\n\nexport function useGitStatus(projectPath: string, pollInterval = 10000) {\n const [status, setStatus] = useState<GitStatus | null>(null);\n const intervalRef = useRef<ReturnType<typeof setInterval> | undefined>(undefined);\n\n useEffect(() => {\n let mounted = true;\n\n fetchGitStatus(projectPath).then((s) => {\n if (mounted) setStatus(s);\n });\n\n intervalRef.current = setInterval(() => {\n fetchGitStatus(projectPath).then((s) => {\n if (mounted) setStatus(s);\n });\n }, pollInterval);\n\n return () => {\n mounted = false;\n if (intervalRef.current) clearInterval(intervalRef.current);\n };\n }, [projectPath, pollInterval]);\n\n return status;\n}\n","import React from 'react';\n\nimport { Box, Text } from 'ink';\n\nimport { useGitStatus } from './hooks/useGitStatus.js';\n\ninterface GitPanelProps {\n projectPath: string;\n availableWidth?: number;\n}\n\nexport function GitPanel({ projectPath, availableWidth = 30 }: GitPanelProps) {\n const status = useGitStatus(projectPath);\n\n if (!status) {\n return (\n <Box>\n <Text dimColor>Loading git info...</Text>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\">\n <Box>\n <Text dimColor>Branch: </Text>\n <Text bold color=\"green\">\n {status.branch}\n </Text>\n </Box>\n {(status.ahead > 0 || status.behind > 0) && (\n <Box>\n {status.ahead > 0 && (\n <Text color=\"green\">\n {'\\u2191'}\n {status.ahead}{' '}\n </Text>\n )}\n {status.behind > 0 && (\n <Text color=\"red\">\n {'\\u2193'}\n {status.behind}\n </Text>\n )}\n </Box>\n )}\n <Box>\n <Text dimColor>Modified: </Text>\n <Text color={status.modified > 0 ? 'yellow' : 'green'}>{status.modified}</Text>\n </Box>\n <Box>\n <Text dimColor>Staged: </Text>\n <Text color={status.staged > 0 ? 'cyan' : 'green'}>{status.staged}</Text>\n </Box>\n <Box>\n <Text dimColor>Untracked: </Text>\n <Text>{status.untracked}</Text>\n </Box>\n {status.stashes > 0 && (\n <Box>\n <Text dimColor>Stashes: </Text>\n <Text>{status.stashes}</Text>\n </Box>\n )}\n {status.lastCommit && (\n <Box marginTop={1} flexDirection=\"column\">\n <Text dimColor>Last commit:</Text>\n <Text>\n {status.lastCommit.length > availableWidth\n ? status.lastCommit.slice(0, availableWidth - 3) + '...'\n : status.lastCommit}\n </Text>\n <Text dimColor>{status.lastCommitTime}</Text>\n </Box>\n )}\n </Box>\n );\n}\n","import React, { useState, useEffect } from 'react';\n\nimport { Box, Text } from 'ink';\n\nimport { LOADING_MESSAGES } from '../../lib/messages.js';\n\nimport type { CheckResult } from 'sickbay-core';\n\ninterface HealthPanelProps {\n checks: CheckResult[];\n isScanning: boolean;\n progress: Array<{ name: string; status: string }>;\n scrollOffset: number;\n availableHeight: number;\n}\n\nfunction scoreColor(score: number): string {\n if (score >= 80) return 'green';\n if (score >= 60) return 'yellow';\n return 'red';\n}\n\nfunction scoreBar(score: number, width = 10): string {\n const filled = Math.round((score / 100) * width);\n return '\\u2588'.repeat(filled) + '\\u2591'.repeat(width - filled);\n}\n\nfunction truncate(str: string, maxLen: number): string {\n return str.length > maxLen ? str.slice(0, maxLen - 1) + '\\u2026' : str;\n}\n\nfunction statusIcon(status: string): string {\n switch (status) {\n case 'pass':\n return '\\u2713';\n case 'fail':\n return '\\u2717';\n case 'warning':\n return '\\u26A0';\n case 'skipped':\n return '\\u25CB';\n default:\n return '?';\n }\n}\n\nexport function HealthPanel({\n checks,\n isScanning,\n progress,\n scrollOffset,\n availableHeight,\n}: HealthPanelProps) {\n const [msgIdx, setMsgIdx] = useState(0);\n\n useEffect(() => {\n if (!isScanning || progress.length > 0) return;\n const id = setInterval(() => {\n setMsgIdx((i) => (i + 1) % LOADING_MESSAGES.length);\n }, 4000);\n return () => clearInterval(id);\n }, [isScanning, progress.length]);\n\n if (isScanning && checks.length === 0) {\n if (progress.length === 0) {\n return (\n <Box>\n <Text dimColor>{LOADING_MESSAGES[msgIdx]}</Text>\n </Box>\n );\n }\n return (\n <Box flexDirection=\"column\">\n {progress.map((p) => (\n <Box key={p.name}>\n <Text\n color={p.status === 'done' ? 'green' : p.status === 'running' ? 'yellow' : 'gray'}\n >\n {p.status === 'done' ? '\\u2713' : p.status === 'running' ? '\\u25CC' : '\\u25CB'}\n </Text>\n <Text> {p.name}</Text>\n </Box>\n ))}\n </Box>\n );\n }\n\n if (checks.length === 0) {\n return (\n <Box>\n <Text dimColor>No results yet. Press [r] to scan.</Text>\n </Box>\n );\n }\n\n const maxVisible = Math.max(1, availableHeight);\n const visible = checks.slice(scrollOffset, scrollOffset + maxVisible);\n const hasMore = scrollOffset + maxVisible < checks.length;\n const hasLess = scrollOffset > 0;\n\n return (\n <Box flexDirection=\"column\">\n {hasLess && <Text dimColor> \\u25B2 {scrollOffset} more above</Text>}\n {visible.map((check) => (\n <Box key={check.id}>\n <Text\n color={check.status === 'pass' ? 'green' : check.status === 'fail' ? 'red' : 'yellow'}\n >\n {statusIcon(check.status)}\n </Text>\n <Text> </Text>\n <Box width={26}>\n <Text wrap=\"truncate\">{truncate(check.name, 26)}</Text>\n </Box>\n <Text> </Text>\n <Text color={scoreColor(check.score)}>{scoreBar(check.score)}</Text>\n <Text color={scoreColor(check.score)}> {String(check.score).padStart(3)}</Text>\n </Box>\n ))}\n {hasMore && (\n <Text dimColor>\n {' \\u25BC '}\n {checks.length - scrollOffset - maxVisible} more below\n </Text>\n )}\n {isScanning && <Text color=\"yellow\">{' \\u25CC Re-scanning...'}</Text>}\n </Box>\n );\n}\n","import React from 'react';\n\nimport { Box, Text } from 'ink';\n\ninterface HelpRow {\n keys: string[];\n description: string;\n}\n\ninterface HelpSection {\n title: string;\n rows: HelpRow[];\n}\n\nconst SECTIONS: HelpSection[] = [\n {\n title: 'PANELS',\n rows: [\n { keys: ['h'], description: 'Focus / unfocus Health Checks' },\n { keys: ['g'], description: 'Focus / unfocus Git Status' },\n { keys: ['t'], description: 'Focus / unfocus Trend' },\n { keys: ['q'], description: 'Focus / unfocus Quick Wins' },\n { keys: ['a'], description: 'Focus / unfocus Activity' },\n { keys: ['f'], description: 'Expand focused panel to full screen' },\n { keys: ['Esc'], description: 'Unfocus panel / exit full screen' },\n ],\n },\n {\n title: 'SCROLLING',\n rows: [{ keys: ['↑', '↓'], description: 'Scroll Health Checks (when focused)' }],\n },\n {\n title: 'ACTIONS',\n rows: [\n { keys: ['r'], description: 'Re-run scan' },\n { keys: ['w'], description: 'Open web dashboard in browser' },\n { keys: ['W'], description: 'Open web dashboard with AI insights' },\n { keys: ['?'], description: 'Toggle this help screen' },\n ],\n },\n];\n\nexport function HelpPanel() {\n return (\n <Box flexDirection=\"column\" paddingX={2} paddingY={1}>\n <Box marginBottom={1}>\n <Text bold color=\"cyan\">\n SICKBAY TUI — KEYBOARD SHORTCUTS\n </Text>\n </Box>\n\n {SECTIONS.map((section) => (\n <Box key={section.title} flexDirection=\"column\" marginBottom={1}>\n <Box marginBottom={0}>\n <Text bold color=\"white\">\n {section.title}\n </Text>\n </Box>\n {section.rows.map((row) => (\n <Box key={row.description} marginLeft={2}>\n <Box width={16}>\n {row.keys.map((k, i) => (\n <Text key={k}>\n <Text color=\"cyan\" bold>\n [{k}]\n </Text>\n {i < row.keys.length - 1 ? <Text dimColor> / </Text> : null}\n </Text>\n ))}\n </Box>\n <Text dimColor>{row.description}</Text>\n </Box>\n ))}\n </Box>\n ))}\n\n <Box marginTop={1} borderStyle=\"single\" borderColor=\"gray\" paddingX={1}>\n <Text dimColor>Pro tip: focus a panel with its hotkey, then press </Text>\n <Text color=\"cyan\" bold>\n [f]\n </Text>\n <Text dimColor> to expand it full screen for more detail.</Text>\n </Box>\n\n <Box marginTop={1}>\n <Text dimColor>Press </Text>\n <Text color=\"cyan\" bold>\n [?]\n </Text>\n <Text dimColor> or </Text>\n <Text color=\"cyan\" bold>\n [Esc]\n </Text>\n <Text dimColor> to close</Text>\n </Box>\n </Box>\n );\n}\n","import { useState, useEffect, useRef } from 'react';\n\nimport type { FSWatcher } from 'chokidar';\n\ninterface UseFileWatcherOptions {\n projectPath: string;\n enabled: boolean;\n debounceMs?: number;\n onFilesChanged?: (files: string[]) => void;\n}\n\nexport function useFileWatcher({\n projectPath,\n enabled,\n debounceMs = 2000,\n onFilesChanged,\n}: UseFileWatcherOptions) {\n const [changedFiles, setChangedFiles] = useState<string[]>([]);\n const debounceRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const pendingRef = useRef<string[]>([]);\n const watcherRef = useRef<FSWatcher | null>(null);\n // Keep a ref so the debounce callback always calls the latest version,\n // even if the parent re-renders and passes a new function reference.\n const onFilesChangedRef = useRef(onFilesChanged);\n onFilesChangedRef.current = onFilesChanged;\n\n useEffect(() => {\n if (!enabled) return;\n\n let mounted = true;\n\n (async () => {\n try {\n const { watch } = await import('chokidar');\n if (!mounted) return;\n\n const watcher = watch(['**/*.{ts,tsx,js,jsx,json}'], {\n cwd: projectPath,\n ignored: [\n '**/node_modules/**',\n '**/dist/**',\n '**/.git/**',\n '**/build/**',\n '**/.next/**',\n '**/.turbo/**',\n '**/coverage/**',\n ],\n ignoreInitial: true,\n persistent: true,\n });\n\n watcherRef.current = watcher;\n\n watcher.on('change', (path: string) => {\n pendingRef.current.push(path);\n setChangedFiles((prev) => [...prev, path]);\n\n if (debounceRef.current) clearTimeout(debounceRef.current);\n debounceRef.current = setTimeout(() => {\n const files = [...pendingRef.current];\n pendingRef.current = [];\n onFilesChangedRef.current?.(files);\n }, debounceMs);\n });\n } catch {\n // chokidar not available — silently skip\n }\n })();\n\n return () => {\n mounted = false;\n if (debounceRef.current) clearTimeout(debounceRef.current);\n if (watcherRef.current) watcherRef.current.close();\n };\n }, [projectPath, enabled, debounceMs]);\n\n return changedFiles;\n}\n","import { useState, useCallback, useRef } from 'react';\n\nimport { runSickbay, runSickbayMonorepo, detectMonorepo, buildSummary } from 'sickbay-core';\n\nimport type { SickbayReport, MonorepoReport, CheckResult } from 'sickbay-core';\n\ninterface ProgressItem {\n name: string;\n status: 'pending' | 'running' | 'done';\n}\n\ninterface UseSickbayRunnerOptions {\n projectPath: string;\n checks?: string[];\n quotes?: boolean;\n}\n\n/**\n * Synthesize a rolled-up SickbayReport from a MonorepoReport for use in\n * TUI panels that only understand single-project reports. Strategy: merge\n * all checks from all packages, keeping the worst score per check name.\n */\nfunction rollUpMonorepoReport(monorepo: MonorepoReport): SickbayReport {\n const byId = new Map<string, CheckResult>();\n for (const pkg of monorepo.packages) {\n for (const check of pkg.checks) {\n const existing = byId.get(check.id);\n if (!existing || check.score < existing.score) {\n byId.set(check.id, check);\n }\n }\n }\n const checks = Array.from(byId.values());\n return {\n timestamp: monorepo.timestamp,\n projectPath: monorepo.rootPath,\n projectInfo: {\n name: `monorepo (${monorepo.packages.length} packages)`,\n version: '0.0.0',\n hasTypeScript: false,\n hasESLint: false,\n hasPrettier: false,\n framework: 'node',\n packageManager: monorepo.packageManager,\n totalDependencies: 0,\n dependencies: {},\n devDependencies: {},\n },\n checks,\n overallScore: monorepo.overallScore,\n summary: buildSummary(checks),\n };\n}\n\nexport function useSickbayRunner({ projectPath, checks, quotes }: UseSickbayRunnerOptions) {\n const [report, setReport] = useState<SickbayReport | null>(null);\n const [monorepoReport, setMonorepoReport] = useState<MonorepoReport | null>(null);\n const [isScanning, setIsScanning] = useState(false);\n const [progress, setProgress] = useState<ProgressItem[]>([]);\n const [error, setError] = useState<string | null>(null);\n const scanningRef = useRef(false);\n\n const scan = useCallback(async () => {\n if (scanningRef.current) return null;\n scanningRef.current = true;\n setIsScanning(true);\n setError(null);\n\n try {\n const monorepoInfo = await detectMonorepo(projectPath);\n\n if (monorepoInfo.isMonorepo) {\n const result = await runSickbayMonorepo({\n projectPath,\n checks,\n quotes,\n onPackageStart: (name) => {\n setProgress((prev) => [...prev, { name, status: 'running' }]);\n },\n onPackageComplete: (pkg) => {\n setProgress((prev) =>\n prev.map((p) => (p.name === pkg.name ? { ...p, status: 'done' } : p)),\n );\n },\n });\n\n setMonorepoReport(result);\n const rolledUp = rollUpMonorepoReport(result);\n setReport(rolledUp);\n\n setIsScanning(false);\n scanningRef.current = false;\n return rolledUp;\n }\n\n const result = await runSickbay({\n projectPath,\n checks,\n quotes,\n onRunnersReady: (names) => {\n setProgress(names.map((name) => ({ name, status: 'pending' as const })));\n },\n onCheckStart: (name) => {\n setProgress((prev) =>\n prev.map((p) => (p.name === name ? { ...p, status: 'running' } : p)),\n );\n },\n onCheckComplete: (check) => {\n setProgress((prev) =>\n prev.map((p) => (p.name === check.id ? { ...p, status: 'done' } : p)),\n );\n },\n });\n\n setMonorepoReport(null);\n setReport(result);\n\n // Save to trend history\n try {\n const { saveEntry } = await import('../../../lib/history.js');\n saveEntry(result);\n } catch {\n // Non-critical\n }\n\n setIsScanning(false);\n scanningRef.current = false;\n return result;\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n setIsScanning(false);\n scanningRef.current = false;\n return null;\n }\n }, [projectPath, checks, quotes]);\n\n return { report, monorepoReport, isScanning, progress, error, scan };\n}\n","import { useState, useEffect } from 'react';\n\nexport function useTerminalSize() {\n const [size, setSize] = useState({\n columns: process.stdout.columns || 80,\n rows: process.stdout.rows || 24,\n });\n\n useEffect(() => {\n const handler = () => {\n setSize({\n columns: process.stdout.columns || 80,\n rows: process.stdout.rows || 24,\n });\n };\n process.stdout.on('resize', handler);\n return () => {\n process.stdout.off('resize', handler);\n };\n }, []);\n\n return size;\n}\n","import React from 'react';\n\nimport { Box, Text } from 'ink';\n\nexport type PanelId = 'health' | 'git' | 'trend' | 'quickwins' | 'activity';\n\ninterface HotkeyBarProps {\n activePanel: PanelId | null;\n}\n\nconst HOTKEYS: Array<{ key: string; label: string; panel?: PanelId }> = [\n { key: 'h', label: 'health', panel: 'health' },\n { key: 'g', label: 'git', panel: 'git' },\n { key: 't', label: 'trend', panel: 'trend' },\n { key: 'q', label: 'quick wins', panel: 'quickwins' },\n { key: 'a', label: 'activity', panel: 'activity' },\n { key: 'r', label: 're-run' },\n { key: 'w', label: 'web' },\n { key: 'W', label: 'web+AI' },\n { key: '?', label: 'help' },\n];\n\nexport function HotkeyBar({ activePanel }: HotkeyBarProps) {\n return (\n <Box>\n {HOTKEYS.map((hk) => {\n const isActive = hk.panel !== undefined && hk.panel === activePanel;\n return (\n <Box key={hk.key} marginRight={2}>\n <Text bold={isActive} color={isActive ? 'cyan' : undefined}>\n [{hk.key}]\n </Text>\n <Text bold={isActive} color={isActive ? 'cyan' : 'gray'}>\n {' '}\n {hk.label}\n </Text>\n </Box>\n );\n })}\n </Box>\n );\n}\n","import React from 'react';\n\nimport { Box, Text } from 'ink';\n\nimport type { MonorepoReport, PackageReport } from 'sickbay-core';\n\ninterface MonorepoPanelProps {\n report: MonorepoReport | null;\n availableWidth?: number;\n}\n\nfunction scoreBar(score: number, width = 8): string {\n const filled = Math.round((score / 100) * width);\n return '█'.repeat(filled) + '░'.repeat(width - filled);\n}\n\nfunction scoreColor(score: number): string {\n if (score >= 80) return 'green';\n if (score >= 60) return 'yellow';\n return 'red';\n}\n\nexport function MonorepoPanel({ report, availableWidth }: MonorepoPanelProps) {\n if (!report) {\n return (\n <Box>\n <Text dimColor>Waiting for scan...</Text>\n </Box>\n );\n }\n\n const nameWidth = Math.max(12, Math.min(20, (availableWidth ?? 36) - 22));\n\n return (\n <Box flexDirection=\"column\">\n <Box marginBottom={1}>\n <Text bold>{report.packages.length} packages</Text>\n <Text dimColor> · </Text>\n <Text dimColor>{report.monorepoType} workspaces</Text>\n <Text dimColor> · </Text>\n <Text color={scoreColor(report.overallScore)} bold>\n {report.overallScore}\n </Text>\n <Text dimColor> avg</Text>\n </Box>\n\n {report.packages.map((pkg: PackageReport) => {\n const name = pkg.name.includes('/') ? (pkg.name.split('/').pop() ?? pkg.name) : pkg.name;\n const truncatedName =\n name.length > nameWidth\n ? name.slice(0, nameWidth - 1) + '\\u2026'\n : name.padEnd(nameWidth);\n\n return (\n <Box key={pkg.path} gap={1}>\n <Text color={scoreColor(pkg.score)}>{scoreBar(pkg.score)}</Text>\n <Text color={scoreColor(pkg.score)} bold>\n {String(pkg.score).padStart(3)}\n </Text>\n <Text>{truncatedName}</Text>\n {pkg.summary.critical > 0 && <Text color=\"red\"> ⚠</Text>}\n </Box>\n );\n })}\n\n <Box marginTop={1}>\n <Text dimColor>Press </Text>\n <Text color=\"cyan\">[w]</Text>\n <Text dimColor> for per-package web view</Text>\n </Box>\n </Box>\n );\n}\n","import React from 'react';\n\nimport { Box, Text } from 'ink';\n\ninterface PanelBorderProps {\n title: string;\n color: string;\n focused?: boolean;\n visible?: boolean;\n flash?: 'green' | 'red';\n children: React.ReactNode;\n}\n\nexport function PanelBorder({\n title,\n color,\n focused,\n visible = true,\n flash,\n children,\n}: PanelBorderProps) {\n const borderColor = flash ?? (focused ? color : 'gray');\n const borderStyle = flash ? 'double' : focused ? 'double' : 'single';\n\n return (\n <Box\n flexDirection=\"column\"\n borderStyle={borderStyle}\n borderColor={borderColor}\n paddingX={1}\n flexGrow={1}\n >\n <Text bold color={flash ?? color}>\n {title}\n </Text>\n {visible ? children : <Text dimColor>···</Text>}\n </Box>\n );\n}\n","import React from 'react';\n\nimport { Box, Text } from 'ink';\n\nimport type { SickbayReport } from 'sickbay-core';\n\ninterface QuickWinsPanelProps {\n report: SickbayReport | null;\n availableWidth?: number;\n}\n\nfunction replacePackageManager(cmd: string, pm: string): string {\n if (pm === 'npm') return cmd;\n const install = pm === 'pnpm' ? 'pnpm add' : pm === 'yarn' ? 'yarn add' : 'bun add';\n const uninstall = pm === 'pnpm' ? 'pnpm remove' : pm === 'yarn' ? 'yarn remove' : 'bun remove';\n const update = pm === 'pnpm' ? 'pnpm update' : pm === 'yarn' ? 'yarn upgrade' : 'bun update';\n const auditFix =\n pm === 'pnpm' ? 'pnpm audit --fix' : pm === 'yarn' ? 'yarn npm audit --fix' : 'bun audit';\n return cmd\n .replace(/^npm install(?=\\s)/, install)\n .replace(/^npm uninstall(?=\\s)/, uninstall)\n .replace(/^npm update(?=\\s)/, update)\n .replace(/^npm audit fix/, auditFix);\n}\n\nfunction shortenPath(filepath: string): string {\n const parts = filepath.split('/');\n if (parts.length <= 2) return filepath;\n return '\\u2026/' + parts.slice(-2).join('/');\n}\n\nfunction smartTruncate(str: string, maxLen: number): string {\n if (str.length <= maxLen) return str;\n // If it contains a file path, shorten the path but keep the filename\n const pathMatch = str.match(/(\\S*\\/\\S+)/);\n if (pathMatch) {\n const shortened = str.replace(pathMatch[1], shortenPath(pathMatch[1]));\n if (shortened.length <= maxLen) return shortened;\n return shortened.slice(0, maxLen - 1) + '\\u2026';\n }\n return str.slice(0, maxLen - 1) + '\\u2026';\n}\n\nfunction shortenCommand(cmd: string, maxLen: number): string {\n if (cmd.length <= maxLen) return cmd;\n // Shorten any file paths in the command first\n const shortened = cmd.replace(/(\\S*\\/\\S+)/g, (match) => shortenPath(match));\n if (shortened.length <= maxLen) return shortened;\n return shortened.slice(0, maxLen - 1) + '\\u2026';\n}\n\nexport function QuickWinsPanel({ report, availableWidth }: QuickWinsPanelProps) {\n if (!report) {\n return (\n <Box>\n <Text dimColor>Waiting for scan...</Text>\n </Box>\n );\n }\n\n const fixes: Array<{\n description: string;\n command: string;\n severity: string;\n }> = [];\n\n for (const check of report.checks) {\n for (const issue of check.issues) {\n if (issue.fix?.command) {\n fixes.push({\n description: issue.fix.description,\n command: issue.fix.command,\n severity: issue.severity,\n });\n }\n }\n }\n\n const order: Record<string, number> = { critical: 0, warning: 1, info: 2 };\n fixes.sort((a, b) => (order[a.severity] ?? 3) - (order[b.severity] ?? 3));\n const top = fixes.slice(0, 5);\n\n if (top.length === 0) {\n return (\n <Box>\n <Text color=\"green\">Looking good!</Text>\n </Box>\n );\n }\n\n const pm = report.projectInfo?.packageManager ?? 'npm';\n const maxTextLen = Math.max(16, (availableWidth ?? 26) - 4);\n\n return (\n <Box flexDirection=\"column\">\n {top.map((fix, i) => (\n <Box key={i} flexDirection=\"column\" marginBottom={i < top.length - 1 ? 1 : 0}>\n <Text>\n <Text\n color={\n fix.severity === 'critical' ? 'red' : fix.severity === 'warning' ? 'yellow' : 'gray'\n }\n >\n {'\\u2192'}{' '}\n </Text>\n {smartTruncate(fix.description, maxTextLen)}\n </Text>\n <Text dimColor>\n {' '}\n {shortenCommand(replacePackageManager(fix.command, pm), maxTextLen)}\n </Text>\n </Box>\n ))}\n </Box>\n );\n}\n","import React, { useState, useEffect, useRef } from 'react';\n\nimport { Box, Text } from 'ink';\n\nimport { countUniqueIssues } from '../../lib/issue-grouping.js';\n\nimport type { SickbayReport } from 'sickbay-core';\n\ninterface ScorePanelProps {\n report: SickbayReport | null;\n previousScore: number | null;\n animate?: boolean;\n}\n\nfunction scoreColor(score: number): string {\n if (score >= 80) return 'green';\n if (score >= 60) return 'yellow';\n return 'red';\n}\n\nfunction scoreBar(score: number, width = 15): string {\n const filled = Math.round((score / 100) * width);\n return '\\u2588'.repeat(filled) + '\\u2591'.repeat(width - filled);\n}\n\nexport function ScorePanel({ report, previousScore, animate = true }: ScorePanelProps) {\n const [animatedScore, setAnimatedScore] = useState(0);\n const prevTargetRef = useRef(0);\n\n useEffect(() => {\n if (!animate) return;\n\n if (!report) {\n setAnimatedScore(0);\n prevTargetRef.current = 0;\n return;\n }\n\n const target = report.overallScore;\n const start = prevTargetRef.current;\n prevTargetRef.current = target;\n\n if (start === target) return;\n\n let current = start;\n const step = target > start ? 1 : -1;\n\n const id = setInterval(() => {\n current += step;\n setAnimatedScore(current);\n if (current === target) clearInterval(id);\n }, 20);\n\n return () => clearInterval(id);\n }, [report, animate]);\n\n // When animate=false, render score directly from prop (no state update needed)\n const displayScore = animate ? animatedScore : (report?.overallScore ?? 0);\n\n if (!report) {\n return (\n <Box>\n <Text dimColor>Waiting for scan...</Text>\n </Box>\n );\n }\n\n const delta = previousScore !== null ? report.overallScore - previousScore : null;\n const u = countUniqueIssues(report.checks);\n\n return (\n <Box flexDirection=\"column\">\n <Box>\n <Text color={scoreColor(displayScore)} bold>\n {displayScore}/100\n </Text>\n <Text> </Text>\n <Text color={scoreColor(displayScore)}>{scoreBar(displayScore)}</Text>\n </Box>\n {delta !== null && (\n <Text dimColor>\n {delta > 0 ? `+${delta}` : delta === 0 ? '\\u00B10' : `${delta}`} since last scan\n </Text>\n )}\n <Box marginTop={1} flexDirection=\"column\">\n <Box>\n <Text color=\"red\">\n {'\\u2717'} {u.critical} critical\n {u.totalCritical > u.critical ? ` (${u.totalCritical})` : ''}\n </Text>\n <Text>{' '}</Text>\n <Text color=\"yellow\">\n {'\\u26A0'} {u.warnings} warn\n {u.totalWarnings > u.warnings ? ` (${u.totalWarnings})` : ''}\n </Text>\n <Text>{' '}</Text>\n <Text dimColor>i {u.info} info</Text>\n </Box>\n </Box>\n {report.quote && (\n <Box marginTop={1}>\n <Text italic dimColor>\n \"{report.quote.text}\" — {report.quote.source}\n </Text>\n </Box>\n )}\n {report.config?.hasCustomConfig && (\n <Text dimColor italic>\n Custom config\n </Text>\n )}\n </Box>\n );\n}\n","import React, { useState, useEffect } from 'react';\n\nimport { Box, Text } from 'ink';\n\nimport { sparkline, trendArrow } from '../../commands/trend.js';\nimport { loadHistory } from '../../lib/history.js';\n\ninterface TrendPanelProps {\n projectPath: string;\n lastScanTime: Date | null;\n availableHeight?: number;\n}\n\nconst CATEGORY_COLORS: Record<string, string> = {\n overall: 'white',\n dependencies: 'blue',\n security: 'green',\n 'code-quality': 'yellow',\n performance: 'magenta',\n git: 'cyan',\n};\n\nconst SHORT_LABELS: Record<string, string> = {\n dependencies: 'Deps',\n security: 'Security',\n 'code-quality': 'Quality',\n performance: 'Perf',\n git: 'Git',\n};\n\nexport function TrendPanel({ projectPath, lastScanTime, availableHeight }: TrendPanelProps) {\n const [trends, setTrends] = useState<\n Array<{\n label: string;\n spark: string;\n latest: number;\n arrow: string;\n color: string;\n }>\n >([]);\n\n useEffect(() => {\n const history = loadHistory(projectPath);\n if (!history || history.entries.length === 0) {\n setTrends([]);\n return;\n }\n\n const entries = history.entries.slice(-10);\n const overallValues = entries.map((e) => e.overallScore);\n const overallArrow = trendArrow(overallValues);\n\n const result: typeof trends = [\n {\n label: 'Overall',\n spark: sparkline(overallValues),\n latest: overallValues[overallValues.length - 1],\n arrow: overallArrow.label,\n color: 'white',\n },\n ];\n\n const categories = Object.keys(entries[entries.length - 1].categoryScores);\n for (const cat of categories) {\n const values = entries\n .map((e) => e.categoryScores[cat])\n .filter((v): v is number => v !== undefined);\n\n if (values.length === 0) continue;\n\n const arrow = trendArrow(values);\n result.push({\n label: SHORT_LABELS[cat] || cat.charAt(0).toUpperCase() + cat.slice(1),\n spark: sparkline(values),\n latest: values[values.length - 1],\n arrow: arrow.label,\n color: CATEGORY_COLORS[cat] || 'white',\n });\n }\n\n setTrends(result);\n }, [projectPath, lastScanTime]);\n\n if (trends.length === 0) {\n return (\n <Box>\n <Text dimColor>No trend data yet.</Text>\n </Box>\n );\n }\n\n const maxRows = availableHeight ?? trends.length;\n const visible = trends.slice(0, maxRows);\n\n return (\n <Box flexDirection=\"column\">\n {visible.map((t) => (\n <Box key={t.label}>\n <Box width={10}>\n <Text color={t.color}>{t.label}</Text>\n </Box>\n <Text>{t.spark}</Text>\n <Text bold> {String(t.latest).padStart(3)}</Text>\n <Text> {t.arrow}</Text>\n </Box>\n ))}\n </Box>\n );\n}\n","import React, { useState, useEffect, useCallback, useRef } from 'react';\n\nimport { Box, Text, useInput } from 'ink';\n\nimport { checkForUpdate } from '../../lib/update-check.js';\nimport { UpdateNotice } from '../UpdateNotice.js';\nimport { ActivityPanel, type ActivityEntry } from './ActivityPanel.js';\nimport { GitPanel } from './GitPanel.js';\nimport { HealthPanel } from './HealthPanel.js';\nimport { HelpPanel } from './HelpPanel.js';\nimport { useFileWatcher } from './hooks/useFileWatcher.js';\nimport { useSickbayRunner } from './hooks/useSickbayRunner.js';\nimport { useTerminalSize } from './hooks/useTerminalSize.js';\nimport { HotkeyBar, type PanelId } from './HotkeyBar.js';\nimport { MonorepoPanel } from './MonorepoPanel.js';\nimport { PanelBorder } from './PanelBorder.js';\nimport { QuickWinsPanel } from './QuickWinsPanel.js';\nimport { ScorePanel } from './ScorePanel.js';\nimport { TrendPanel } from './TrendPanel.js';\n\nimport type { UpdateInfo } from '../../lib/update-check.js';\nimport type { SickbayReport, MonorepoReport } from 'sickbay-core';\n\ndeclare const __VERSION__: string;\n\ninterface TuiAppProps {\n projectPath: string;\n checks?: string[];\n watchEnabled: boolean;\n refreshInterval: number;\n quotes?: boolean;\n animateOnMount?: boolean;\n}\n\nexport function TuiApp({\n projectPath,\n checks,\n watchEnabled,\n refreshInterval,\n quotes,\n animateOnMount = true,\n}: TuiAppProps) {\n const { rows, columns } = useTerminalSize();\n const { report, monorepoReport, isScanning, progress, scan } = useSickbayRunner({\n projectPath,\n checks,\n quotes,\n });\n\n const [focusedPanel, setFocusedPanel] = useState<PanelId | null>(null);\n const [expandedPanel, setExpandedPanel] = useState<PanelId | null>(null);\n const [showHelp, setShowHelp] = useState(false);\n const [lastScanTime, setLastScanTime] = useState<Date | null>(null);\n const [previousScore, setPreviousScore] = useState<number | null>(null);\n const [activityLog, setActivityLog] = useState<ActivityEntry[]>([]);\n const [healthScrollOffset, setHealthScrollOffset] = useState(0);\n const [scoreFlash, setScoreFlash] = useState<'green' | 'red' | undefined>();\n const [updateInfo, setUpdateInfo] = useState<UpdateInfo | null>(null);\n const ALL_PANELS = new Set(['health', 'score', 'trend', 'git', 'quickwins', 'activity']);\n const [visiblePanels, setVisiblePanels] = useState<Set<string>>(\n animateOnMount ? new Set() : ALL_PANELS,\n );\n const refreshRef = useRef<ReturnType<typeof setInterval> | undefined>(undefined);\n\n // Panel entrance animation — stagger panels appearing on mount\n useEffect(() => {\n const schedule: Array<[string, number]> = [\n ['health', 100],\n ['score', 300],\n ['trend', 500],\n ['git', 700],\n ['quickwins', 900],\n ['activity', 1100],\n ];\n const timers = schedule.map(([panel, delay]) =>\n setTimeout(() => {\n setVisiblePanels((prev) => new Set([...prev, panel]));\n }, delay),\n );\n return () => timers.forEach(clearTimeout);\n }, []);\n\n useEffect(() => {\n checkForUpdate(__VERSION__).then((info) => {\n if (info) setUpdateInfo(info);\n });\n }, []);\n\n const reportRef = useRef<SickbayReport | null>(null);\n const monorepoReportRef = useRef<MonorepoReport | null>(null);\n\n // Keep refs in sync\n useEffect(() => {\n reportRef.current = report;\n }, [report]);\n\n useEffect(() => {\n monorepoReportRef.current = monorepoReport;\n }, [monorepoReport]);\n\n const addActivity = useCallback((type: ActivityEntry['type'], message: string) => {\n setActivityLog((prev) => [...prev, { timestamp: new Date(), type, message }]);\n }, []);\n\n const handleScanComplete = useCallback(\n async (result: SickbayReport) => {\n const prevScore = reportRef.current?.overallScore ?? null;\n if (prevScore !== null) setPreviousScore(prevScore);\n setLastScanTime(new Date());\n\n // Auto-save last report snapshot\n try {\n const { saveLastReport } = await import('../../lib/history.js');\n saveLastReport(result);\n } catch {\n // Non-critical\n }\n\n // Cache dependency tree for web dashboard\n try {\n const { getDependencyTree } = await import('sickbay-core');\n const { saveDepTree } = await import('../../lib/history.js');\n const tree = await getDependencyTree(projectPath, result.projectInfo.packageManager);\n saveDepTree(projectPath, tree);\n } catch {\n /* dep tree is optional */\n }\n\n const delta = prevScore !== null ? result.overallScore - prevScore : null;\n addActivity(\n 'scan-complete',\n `Scan complete: ${result.overallScore}/100${delta !== null ? ` (${delta >= 0 ? '+' : ''}${delta})` : ''}`,\n );\n\n // Flash score panel border on score change\n if (delta !== null && delta !== 0) {\n const flash = delta > 0 ? 'green' : 'red';\n setScoreFlash(flash);\n setTimeout(() => setScoreFlash(undefined), 600);\n }\n\n // Check for regressions\n try {\n const { loadHistory, detectRegressions } = await import('../../lib/history.js');\n const history = loadHistory(projectPath);\n if (history) {\n const regressions = detectRegressions(history.entries);\n for (const reg of regressions) {\n addActivity(\n 'regression',\n `\\u26A0 ${reg.category} regressed: ${reg.from} \\u2192 ${reg.to}`,\n );\n }\n }\n } catch {\n // Non-critical\n }\n },\n [addActivity, projectPath],\n );\n\n // Initial scan\n useEffect(() => {\n addActivity('info', 'TUI started');\n addActivity('scan-start', 'Starting initial health scan...');\n scan().then((result) => {\n if (result) handleScanComplete(result);\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Auto-refresh timer\n useEffect(() => {\n if (refreshInterval <= 0) return;\n refreshRef.current = setInterval(async () => {\n addActivity('scan-start', 'Auto-scan triggered');\n const result = await scan();\n if (result) handleScanComplete(result);\n }, refreshInterval * 1000);\n\n return () => {\n if (refreshRef.current) clearInterval(refreshRef.current);\n };\n }, [refreshInterval, scan, handleScanComplete, addActivity]);\n\n // File watcher\n useFileWatcher({\n projectPath,\n enabled: watchEnabled,\n onFilesChanged: async (files) => {\n for (const f of files.slice(0, 3)) {\n addActivity('file-change', `File changed: ${f}`);\n }\n if (files.length > 3) {\n addActivity('file-change', `...and ${files.length - 3} more files`);\n }\n addActivity('scan-start', 'Re-scan triggered (file change)');\n const result = await scan();\n if (result) handleScanComplete(result);\n },\n });\n\n // Manual re-scan\n const triggerRescan = useCallback(async () => {\n addActivity('scan-start', 'Manual re-scan triggered');\n const result = await scan();\n if (result) handleScanComplete(result);\n }, [addActivity, scan, handleScanComplete]);\n\n // Keyboard input — only active when stdin supports raw mode (interactive TTY)\n const isTTY = !!(process.stdin.isTTY && process.stdin.setRawMode);\n useInput(\n (input, key) => {\n // Help overlay: ? toggles, Escape closes\n if (showHelp) {\n if (input === '?' || key.escape) {\n setShowHelp(false);\n }\n return;\n }\n\n // Expanded panel: escape or f to exit\n if (expandedPanel) {\n if (key.escape || input === 'f') {\n setExpandedPanel(null);\n return;\n }\n }\n\n if (input === '?') {\n setShowHelp(true);\n return;\n }\n\n // Panel focus toggles\n if (input === 'h') setFocusedPanel((prev) => (prev === 'health' ? null : 'health'));\n else if (input === 'g') setFocusedPanel((prev) => (prev === 'git' ? null : 'git'));\n else if (input === 't') setFocusedPanel((prev) => (prev === 'trend' ? null : 'trend'));\n else if (input === 'q')\n setFocusedPanel((prev) => (prev === 'quickwins' ? null : 'quickwins'));\n else if (input === 'a') setFocusedPanel((prev) => (prev === 'activity' ? null : 'activity'));\n else if (input === 'r') triggerRescan();\n else if (input === 'f' && focusedPanel) setExpandedPanel(focusedPanel);\n else if (input === 'w' || input === 'W') {\n const withAI = input === 'W';\n (async () => {\n const webReport = monorepoReportRef.current ?? reportRef.current;\n if (!webReport) return;\n try {\n const { serveWeb } = await import('../../commands/web.js');\n const { default: openBrowser } = await import('open');\n\n let aiService;\n if (withAI && process.env.ANTHROPIC_API_KEY && !monorepoReportRef.current) {\n const { createAIService } = await import('../../services/ai.js');\n aiService = createAIService(process.env.ANTHROPIC_API_KEY);\n addActivity('info', 'Launching web dashboard with AI...');\n } else {\n addActivity('info', 'Launching web dashboard...');\n }\n\n const url = await serveWeb(webReport, 3030, aiService);\n await openBrowser(url);\n addActivity(\n 'info',\n `Web dashboard at ${url}${withAI && aiService ? ' (AI enabled)' : ''}`,\n );\n } catch (err) {\n addActivity('info', `Failed to open web: ${err instanceof Error ? err.message : err}`);\n }\n })();\n } else if (key.escape) {\n setFocusedPanel(null);\n }\n\n // Scrolling in focused health panel\n if (focusedPanel === 'health') {\n if (key.upArrow) setHealthScrollOffset((prev) => Math.max(0, prev - 1));\n if (key.downArrow) {\n const maxOffset = Math.max(0, visibleChecks.length - 5);\n setHealthScrollOffset((prev) => Math.min(maxOffset, prev + 1));\n }\n }\n },\n { isActive: isTTY },\n );\n\n const visibleChecks = report?.checks.filter((c) => c.status !== 'skipped') ?? [];\n\n // Layout calculations — reserve 1 row for project header + 1 for hotkey bar\n const topHeight = Math.floor((rows - 2) * 0.6);\n const bottomHeight = rows - 2 - topHeight;\n\n // Help overlay\n if (showHelp) {\n return (\n <Box flexDirection=\"column\" width={columns} height={rows}>\n <Box flexGrow={1}>\n <PanelBorder title=\"HELP\" color=\"cyan\" focused>\n <HelpPanel />\n </PanelBorder>\n </Box>\n <HotkeyBar activePanel={focusedPanel} />\n </Box>\n );\n }\n\n // Full-screen expand mode\n if (expandedPanel) {\n return (\n <Box flexDirection=\"column\" width={columns} height={rows}>\n <Box flexGrow={1}>\n {expandedPanel === 'health' && (\n <PanelBorder title=\"HEALTH CHECKS\" color=\"green\" focused>\n <HealthPanel\n checks={visibleChecks}\n isScanning={isScanning}\n progress={progress}\n scrollOffset={healthScrollOffset}\n availableHeight={rows - 4}\n />\n </PanelBorder>\n )}\n {expandedPanel === 'git' && (\n <PanelBorder title=\"GIT STATUS\" color=\"yellow\" focused>\n <GitPanel projectPath={projectPath} availableWidth={columns - 4} />\n </PanelBorder>\n )}\n {expandedPanel === 'trend' && (\n <PanelBorder title=\"TREND\" color=\"magenta\" focused>\n <TrendPanel projectPath={projectPath} lastScanTime={lastScanTime} />\n </PanelBorder>\n )}\n {expandedPanel === 'quickwins' && (\n <PanelBorder title={monorepoReport ? 'MONOREPO' : 'QUICK WINS'} color=\"red\" focused>\n {monorepoReport ? (\n <MonorepoPanel report={monorepoReport} />\n ) : (\n <QuickWinsPanel report={report} />\n )}\n </PanelBorder>\n )}\n {expandedPanel === 'activity' && (\n <PanelBorder title=\"ACTIVITY\" color=\"cyan\" focused>\n <ActivityPanel entries={activityLog} availableHeight={rows - 4} />\n </PanelBorder>\n )}\n </Box>\n <HotkeyBar activePanel={expandedPanel} />\n </Box>\n );\n }\n\n // Normal grid layout\n const projectName = monorepoReport\n ? `${monorepoReport.rootPath.split('/').pop()} (monorepo)`\n : (report?.projectInfo?.name ?? '—');\n const projectVersion = report?.projectInfo?.version;\n const scanLabel = lastScanTime\n ? `Last scan ${lastScanTime.toLocaleTimeString()}`\n : isScanning\n ? 'Scanning…'\n : 'Not yet scanned';\n\n return (\n <Box flexDirection=\"column\" width={columns} height={rows}>\n {updateInfo && (\n <UpdateNotice\n currentVersion={updateInfo.currentVersion}\n latestVersion={updateInfo.latestVersion}\n />\n )}\n {/* Project header */}\n <Box paddingX={1} justifyContent=\"space-between\">\n <Box gap={1}>\n <Text bold color=\"cyan\">\n SICKBAY\n </Text>\n <Text bold>{projectName}</Text>\n {projectVersion && <Text dimColor>v{projectVersion}</Text>}\n </Box>\n <Text dimColor>{scanLabel}</Text>\n </Box>\n\n {/* Top Row */}\n <Box height={topHeight}>\n <Box width=\"55%\">\n <PanelBorder\n title=\"HEALTH CHECKS\"\n color=\"green\"\n focused={focusedPanel === 'health'}\n visible={visiblePanels.has('health')}\n >\n <HealthPanel\n checks={visibleChecks}\n isScanning={isScanning}\n progress={progress}\n scrollOffset={healthScrollOffset}\n availableHeight={topHeight - 4}\n />\n </PanelBorder>\n </Box>\n <Box width=\"45%\" flexDirection=\"column\">\n <Box height=\"50%\">\n <PanelBorder\n title=\"SCORE\"\n color=\"blue\"\n visible={visiblePanels.has('score')}\n flash={scoreFlash}\n >\n <ScorePanel report={report} previousScore={previousScore} animate={animateOnMount} />\n </PanelBorder>\n </Box>\n <Box height=\"50%\">\n <PanelBorder\n title=\"TREND\"\n color=\"magenta\"\n focused={focusedPanel === 'trend'}\n visible={visiblePanels.has('trend')}\n >\n <TrendPanel\n projectPath={projectPath}\n lastScanTime={lastScanTime}\n availableHeight={Math.floor(topHeight / 2) - 3}\n />\n </PanelBorder>\n </Box>\n </Box>\n </Box>\n\n {/* Bottom Row */}\n <Box height={bottomHeight}>\n <Box width=\"25%\">\n <PanelBorder\n title=\"GIT STATUS\"\n color=\"yellow\"\n focused={focusedPanel === 'git'}\n visible={visiblePanels.has('git')}\n >\n <GitPanel projectPath={projectPath} availableWidth={Math.floor(columns * 0.25) - 4} />\n </PanelBorder>\n </Box>\n <Box width=\"30%\">\n <PanelBorder\n title={monorepoReport ? 'MONOREPO' : 'QUICK WINS'}\n color=\"red\"\n focused={focusedPanel === 'quickwins'}\n visible={visiblePanels.has('quickwins')}\n >\n {monorepoReport ? (\n <MonorepoPanel\n report={monorepoReport}\n availableWidth={Math.floor(columns * 0.3) - 4}\n />\n ) : (\n <QuickWinsPanel report={report} availableWidth={Math.floor(columns * 0.3) - 4} />\n )}\n </PanelBorder>\n </Box>\n <Box width=\"45%\">\n <PanelBorder\n title=\"ACTIVITY\"\n color=\"cyan\"\n focused={focusedPanel === 'activity'}\n visible={visiblePanels.has('activity')}\n >\n <ActivityPanel entries={activityLog} availableHeight={bottomHeight - 4} />\n </PanelBorder>\n </Box>\n </Box>\n\n {/* Hotkey Bar */}\n <HotkeyBar activePanel={focusedPanel} />\n </Box>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TuiApp-B3l1OC7M.js","names":["scoreColor","scoreBar","scoreBar","scoreColor"],"sources":["../src/components/tui/ActivityPanel.tsx","../src/components/tui/hooks/useGitStatus.ts","../src/components/tui/GitPanel.tsx","../src/components/tui/HealthPanel.tsx","../src/components/tui/HelpPanel.tsx","../src/components/tui/hooks/useFileWatcher.ts","../src/components/tui/hooks/useSickbayRunner.ts","../src/components/tui/hooks/useTerminalSize.ts","../src/components/tui/HotkeyBar.tsx","../src/components/tui/MonorepoPanel.tsx","../src/components/tui/PanelBorder.tsx","../src/components/tui/QuickWinsPanel.tsx","../src/components/tui/ScorePanel.tsx","../src/components/tui/TrendPanel.tsx","../src/components/tui/TuiApp.tsx"],"sourcesContent":["import React from 'react';\n\nimport { Box, Text } from 'ink';\n\nexport interface ActivityEntry {\n timestamp: Date;\n type: 'scan-start' | 'scan-complete' | 'file-change' | 'git-change' | 'regression' | 'info';\n message: string;\n}\n\ninterface ActivityPanelProps {\n entries: ActivityEntry[];\n availableHeight: number;\n}\n\nconst TYPE_COLOR: Record<ActivityEntry['type'], string> = {\n 'scan-start': 'cyan',\n 'scan-complete': 'green',\n 'file-change': 'yellow',\n 'git-change': 'magenta',\n regression: 'red',\n info: 'gray',\n};\n\nfunction formatTime(date: Date): string {\n return date.toLocaleTimeString('en-US', { hour12: false });\n}\n\nexport function ActivityPanel({ entries, availableHeight }: ActivityPanelProps) {\n if (entries.length === 0) {\n return (\n <Box>\n <Text dimColor>No activity yet.</Text>\n </Box>\n );\n }\n\n const visible = entries.slice(-Math.max(1, availableHeight));\n\n return (\n <Box flexDirection=\"column\">\n {visible.map((entry) => (\n <Box key={`${entry.timestamp.getTime()}-${entry.type}`}>\n <Text dimColor>\n {formatTime(entry.timestamp)}\n {' '}\n </Text>\n <Text color={TYPE_COLOR[entry.type] || 'white'}>{entry.message}</Text>\n </Box>\n ))}\n </Box>\n );\n}\n","import { execFile } from 'child_process';\nimport { promisify } from 'util';\n\nimport { useState, useEffect, useRef } from 'react';\n\nconst exec = promisify(execFile);\n\nexport interface GitStatus {\n branch: string;\n modified: number;\n staged: number;\n untracked: number;\n ahead: number;\n behind: number;\n stashes: number;\n lastCommit: string;\n lastCommitTime: string;\n}\n\nasync function git(args: string[], cwd: string): Promise<string> {\n try {\n const { stdout } = await exec('git', args, { cwd, timeout: 5000 });\n return stdout.trim();\n } catch {\n return '';\n }\n}\n\nasync function fetchGitStatus(projectPath: string): Promise<GitStatus> {\n const [branch, porcelain, revList, stashList, logLine] = await Promise.all([\n git(['branch', '--show-current'], projectPath),\n git(['status', '--porcelain'], projectPath),\n git(['rev-list', '--left-right', '--count', 'HEAD...@{upstream}'], projectPath),\n git(['stash', 'list'], projectPath),\n git(['log', '-1', '--format=%s|%cr'], projectPath),\n ]);\n\n const lines = porcelain ? porcelain.split('\\n') : [];\n const staged = lines.filter((l) => /^[MADRC]/.test(l)).length;\n const modified = lines.filter((l) => /^.[MD]/.test(l)).length;\n const untracked = lines.filter((l) => l.startsWith('??')).length;\n\n let ahead = 0;\n let behind = 0;\n if (revList) {\n const parts = revList.split(/\\s+/);\n ahead = parseInt(parts[0], 10) || 0;\n behind = parseInt(parts[1], 10) || 0;\n }\n\n const stashes = stashList ? stashList.split('\\n').filter(Boolean).length : 0;\n const [lastCommit, lastCommitTime] = logLine.split('|');\n\n return {\n branch: branch || 'unknown',\n modified,\n staged,\n untracked,\n ahead,\n behind,\n stashes,\n lastCommit: lastCommit || '',\n lastCommitTime: lastCommitTime || '',\n };\n}\n\nexport function useGitStatus(projectPath: string, pollInterval = 10000) {\n const [status, setStatus] = useState<GitStatus | null>(null);\n const intervalRef = useRef<ReturnType<typeof setInterval> | undefined>(undefined);\n\n useEffect(() => {\n let mounted = true;\n\n fetchGitStatus(projectPath).then((s) => {\n if (mounted) setStatus(s);\n });\n\n intervalRef.current = setInterval(() => {\n fetchGitStatus(projectPath).then((s) => {\n if (mounted) setStatus(s);\n });\n }, pollInterval);\n\n return () => {\n mounted = false;\n if (intervalRef.current) clearInterval(intervalRef.current);\n };\n }, [projectPath, pollInterval]);\n\n return status;\n}\n","import React from 'react';\n\nimport { Box, Text } from 'ink';\n\nimport { useGitStatus } from './hooks/useGitStatus.js';\n\ninterface GitPanelProps {\n projectPath: string;\n availableWidth?: number;\n}\n\nexport function GitPanel({ projectPath, availableWidth = 30 }: GitPanelProps) {\n const status = useGitStatus(projectPath);\n\n if (!status) {\n return (\n <Box>\n <Text dimColor>Loading git info...</Text>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\">\n <Box>\n <Text dimColor>Branch: </Text>\n <Text bold color=\"green\">\n {status.branch}\n </Text>\n </Box>\n {(status.ahead > 0 || status.behind > 0) && (\n <Box>\n {status.ahead > 0 && (\n <Text color=\"green\">\n {'\\u2191'}\n {status.ahead}{' '}\n </Text>\n )}\n {status.behind > 0 && (\n <Text color=\"red\">\n {'\\u2193'}\n {status.behind}\n </Text>\n )}\n </Box>\n )}\n <Box>\n <Text dimColor>Modified: </Text>\n <Text color={status.modified > 0 ? 'yellow' : 'green'}>{status.modified}</Text>\n </Box>\n <Box>\n <Text dimColor>Staged: </Text>\n <Text color={status.staged > 0 ? 'cyan' : 'green'}>{status.staged}</Text>\n </Box>\n <Box>\n <Text dimColor>Untracked: </Text>\n <Text>{status.untracked}</Text>\n </Box>\n {status.stashes > 0 && (\n <Box>\n <Text dimColor>Stashes: </Text>\n <Text>{status.stashes}</Text>\n </Box>\n )}\n {status.lastCommit && (\n <Box marginTop={1} flexDirection=\"column\">\n <Text dimColor>Last commit:</Text>\n <Text>\n {status.lastCommit.length > availableWidth\n ? status.lastCommit.slice(0, availableWidth - 3) + '...'\n : status.lastCommit}\n </Text>\n <Text dimColor>{status.lastCommitTime}</Text>\n </Box>\n )}\n </Box>\n );\n}\n","import React, { useState, useEffect } from 'react';\n\nimport { Box, Text } from 'ink';\n\nimport { LOADING_MESSAGES } from '../../lib/messages.js';\n\nimport type { CheckResult } from 'sickbay-core';\n\ninterface HealthPanelProps {\n checks: CheckResult[];\n isScanning: boolean;\n progress: Array<{ name: string; status: string }>;\n scrollOffset: number;\n availableHeight: number;\n}\n\nfunction scoreColor(score: number): string {\n if (score >= 80) return 'green';\n if (score >= 60) return 'yellow';\n return 'red';\n}\n\nfunction scoreBar(score: number, width = 10): string {\n const filled = Math.round((score / 100) * width);\n return '\\u2588'.repeat(filled) + '\\u2591'.repeat(width - filled);\n}\n\nfunction truncate(str: string, maxLen: number): string {\n return str.length > maxLen ? str.slice(0, maxLen - 1) + '\\u2026' : str;\n}\n\nfunction statusIcon(status: string): string {\n switch (status) {\n case 'pass':\n return '\\u2713';\n case 'fail':\n return '\\u2717';\n case 'warning':\n return '\\u26A0';\n case 'skipped':\n return '\\u25CB';\n default:\n return '?';\n }\n}\n\nexport function HealthPanel({\n checks,\n isScanning,\n progress,\n scrollOffset,\n availableHeight,\n}: HealthPanelProps) {\n const [msgIdx, setMsgIdx] = useState(0);\n\n useEffect(() => {\n if (!isScanning || progress.length > 0) return;\n const id = setInterval(() => {\n setMsgIdx((i) => (i + 1) % LOADING_MESSAGES.length);\n }, 4000);\n return () => clearInterval(id);\n }, [isScanning, progress.length]);\n\n if (isScanning && checks.length === 0) {\n if (progress.length === 0) {\n return (\n <Box>\n <Text dimColor>{LOADING_MESSAGES[msgIdx]}</Text>\n </Box>\n );\n }\n return (\n <Box flexDirection=\"column\">\n {progress.map((p) => (\n <Box key={p.name}>\n <Text\n color={p.status === 'done' ? 'green' : p.status === 'running' ? 'yellow' : 'gray'}\n >\n {p.status === 'done' ? '\\u2713' : p.status === 'running' ? '\\u25CC' : '\\u25CB'}\n </Text>\n <Text> {p.name}</Text>\n </Box>\n ))}\n </Box>\n );\n }\n\n if (checks.length === 0) {\n return (\n <Box>\n <Text dimColor>No results yet. Press [r] to scan.</Text>\n </Box>\n );\n }\n\n const maxVisible = Math.max(1, availableHeight);\n const visible = checks.slice(scrollOffset, scrollOffset + maxVisible);\n const hasMore = scrollOffset + maxVisible < checks.length;\n const hasLess = scrollOffset > 0;\n\n return (\n <Box flexDirection=\"column\">\n {hasLess && <Text dimColor> \\u25B2 {scrollOffset} more above</Text>}\n {visible.map((check) => (\n <Box key={check.id}>\n <Text\n color={check.status === 'pass' ? 'green' : check.status === 'fail' ? 'red' : 'yellow'}\n >\n {statusIcon(check.status)}\n </Text>\n <Text> </Text>\n <Box width={26}>\n <Text wrap=\"truncate\">{truncate(check.name, 26)}</Text>\n </Box>\n <Text> </Text>\n <Text color={scoreColor(check.score)}>{scoreBar(check.score)}</Text>\n <Text color={scoreColor(check.score)}> {String(check.score).padStart(3)}</Text>\n </Box>\n ))}\n {hasMore && (\n <Text dimColor>\n {' \\u25BC '}\n {checks.length - scrollOffset - maxVisible} more below\n </Text>\n )}\n {isScanning && <Text color=\"yellow\">{' \\u25CC Re-scanning...'}</Text>}\n </Box>\n );\n}\n","import React from 'react';\n\nimport { Box, Text } from 'ink';\n\ninterface HelpRow {\n keys: string[];\n description: string;\n}\n\ninterface HelpSection {\n title: string;\n rows: HelpRow[];\n}\n\nconst SECTIONS: HelpSection[] = [\n {\n title: 'PANELS',\n rows: [\n { keys: ['h'], description: 'Focus / unfocus Health Checks' },\n { keys: ['g'], description: 'Focus / unfocus Git Status' },\n { keys: ['t'], description: 'Focus / unfocus Trend' },\n { keys: ['q'], description: 'Focus / unfocus Quick Wins' },\n { keys: ['a'], description: 'Focus / unfocus Activity' },\n { keys: ['f'], description: 'Expand focused panel to full screen' },\n { keys: ['Esc'], description: 'Unfocus panel / exit full screen' },\n ],\n },\n {\n title: 'SCROLLING',\n rows: [{ keys: ['↑', '↓'], description: 'Scroll Health Checks (when focused)' }],\n },\n {\n title: 'ACTIONS',\n rows: [\n { keys: ['r'], description: 'Re-run scan' },\n { keys: ['w'], description: 'Open web dashboard in browser' },\n { keys: ['W'], description: 'Open web dashboard with AI insights' },\n { keys: ['?'], description: 'Toggle this help screen' },\n ],\n },\n];\n\nexport function HelpPanel() {\n return (\n <Box flexDirection=\"column\" paddingX={2} paddingY={1}>\n <Box marginBottom={1}>\n <Text bold color=\"cyan\">\n SICKBAY TUI — KEYBOARD SHORTCUTS\n </Text>\n </Box>\n\n {SECTIONS.map((section) => (\n <Box key={section.title} flexDirection=\"column\" marginBottom={1}>\n <Box marginBottom={0}>\n <Text bold color=\"white\">\n {section.title}\n </Text>\n </Box>\n {section.rows.map((row) => (\n <Box key={row.description} marginLeft={2}>\n <Box width={16}>\n {row.keys.map((k, i) => (\n <Text key={k}>\n <Text color=\"cyan\" bold>\n [{k}]\n </Text>\n {i < row.keys.length - 1 ? <Text dimColor> / </Text> : null}\n </Text>\n ))}\n </Box>\n <Text dimColor>{row.description}</Text>\n </Box>\n ))}\n </Box>\n ))}\n\n <Box marginTop={1} borderStyle=\"single\" borderColor=\"gray\" paddingX={1}>\n <Text dimColor>Pro tip: focus a panel with its hotkey, then press </Text>\n <Text color=\"cyan\" bold>\n [f]\n </Text>\n <Text dimColor> to expand it full screen for more detail.</Text>\n </Box>\n\n <Box marginTop={1}>\n <Text dimColor>Press </Text>\n <Text color=\"cyan\" bold>\n [?]\n </Text>\n <Text dimColor> or </Text>\n <Text color=\"cyan\" bold>\n [Esc]\n </Text>\n <Text dimColor> to close</Text>\n </Box>\n </Box>\n );\n}\n","import { useState, useEffect, useRef } from 'react';\n\nimport type { FSWatcher } from 'chokidar';\n\ninterface UseFileWatcherOptions {\n projectPath: string;\n enabled: boolean;\n debounceMs?: number;\n onFilesChanged?: (files: string[]) => void;\n}\n\nexport function useFileWatcher({\n projectPath,\n enabled,\n debounceMs = 2000,\n onFilesChanged,\n}: UseFileWatcherOptions) {\n const [changedFiles, setChangedFiles] = useState<string[]>([]);\n const debounceRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const pendingRef = useRef<string[]>([]);\n const watcherRef = useRef<FSWatcher | null>(null);\n // Keep a ref so the debounce callback always calls the latest version,\n // even if the parent re-renders and passes a new function reference.\n const onFilesChangedRef = useRef(onFilesChanged);\n onFilesChangedRef.current = onFilesChanged;\n\n useEffect(() => {\n if (!enabled) return;\n\n let mounted = true;\n\n (async () => {\n try {\n const { watch } = await import('chokidar');\n if (!mounted) return;\n\n const watcher = watch(['**/*.{ts,tsx,js,jsx,json}'], {\n cwd: projectPath,\n ignored: [\n '**/node_modules/**',\n '**/dist/**',\n '**/.git/**',\n '**/build/**',\n '**/.next/**',\n '**/.turbo/**',\n '**/coverage/**',\n ],\n ignoreInitial: true,\n persistent: true,\n });\n\n watcherRef.current = watcher;\n\n watcher.on('change', (path: string) => {\n pendingRef.current.push(path);\n setChangedFiles((prev) => [...prev, path]);\n\n if (debounceRef.current) clearTimeout(debounceRef.current);\n debounceRef.current = setTimeout(() => {\n const files = [...pendingRef.current];\n pendingRef.current = [];\n onFilesChangedRef.current?.(files);\n }, debounceMs);\n });\n } catch {\n // chokidar not available — silently skip\n }\n })();\n\n return () => {\n mounted = false;\n if (debounceRef.current) clearTimeout(debounceRef.current);\n if (watcherRef.current) watcherRef.current.close();\n };\n }, [projectPath, enabled, debounceMs]);\n\n return changedFiles;\n}\n","import { useState, useCallback, useRef } from 'react';\n\nimport { runSickbay, runSickbayMonorepo, detectMonorepo, buildSummary } from 'sickbay-core';\n\nimport type { SickbayReport, MonorepoReport, CheckResult } from 'sickbay-core';\n\ninterface ProgressItem {\n name: string;\n status: 'pending' | 'running' | 'done';\n}\n\ninterface UseSickbayRunnerOptions {\n projectPath: string;\n checks?: string[];\n quotes?: boolean;\n}\n\n/**\n * Synthesize a rolled-up SickbayReport from a MonorepoReport for use in\n * TUI panels that only understand single-project reports. Strategy: merge\n * all checks from all packages, keeping the worst score per check name.\n */\nfunction rollUpMonorepoReport(monorepo: MonorepoReport): SickbayReport {\n const byId = new Map<string, CheckResult>();\n for (const pkg of monorepo.packages) {\n for (const check of pkg.checks) {\n const existing = byId.get(check.id);\n if (!existing || check.score < existing.score) {\n byId.set(check.id, check);\n }\n }\n }\n const checks = Array.from(byId.values());\n return {\n timestamp: monorepo.timestamp,\n projectPath: monorepo.rootPath,\n projectInfo: {\n name: `monorepo (${monorepo.packages.length} packages)`,\n version: '0.0.0',\n hasTypeScript: false,\n hasESLint: false,\n hasPrettier: false,\n framework: 'node',\n packageManager: monorepo.packageManager,\n totalDependencies: 0,\n dependencies: {},\n devDependencies: {},\n },\n checks,\n overallScore: monorepo.overallScore,\n summary: buildSummary(checks),\n };\n}\n\nexport function useSickbayRunner({ projectPath, checks, quotes }: UseSickbayRunnerOptions) {\n const [report, setReport] = useState<SickbayReport | null>(null);\n const [monorepoReport, setMonorepoReport] = useState<MonorepoReport | null>(null);\n const [isScanning, setIsScanning] = useState(false);\n const [progress, setProgress] = useState<ProgressItem[]>([]);\n const [error, setError] = useState<string | null>(null);\n const scanningRef = useRef(false);\n\n const scan = useCallback(async () => {\n if (scanningRef.current) return null;\n scanningRef.current = true;\n setIsScanning(true);\n setError(null);\n\n try {\n const monorepoInfo = await detectMonorepo(projectPath);\n\n if (monorepoInfo.isMonorepo) {\n const result = await runSickbayMonorepo({\n projectPath,\n checks,\n quotes,\n onPackageStart: (name) => {\n setProgress((prev) => [...prev, { name, status: 'running' }]);\n },\n onPackageComplete: (pkg) => {\n setProgress((prev) =>\n prev.map((p) => (p.name === pkg.name ? { ...p, status: 'done' } : p)),\n );\n },\n });\n\n setMonorepoReport(result);\n const rolledUp = rollUpMonorepoReport(result);\n setReport(rolledUp);\n\n setIsScanning(false);\n scanningRef.current = false;\n return rolledUp;\n }\n\n const result = await runSickbay({\n projectPath,\n checks,\n quotes,\n onRunnersReady: (names) => {\n setProgress(names.map((name) => ({ name, status: 'pending' as const })));\n },\n onCheckStart: (name) => {\n setProgress((prev) =>\n prev.map((p) => (p.name === name ? { ...p, status: 'running' } : p)),\n );\n },\n onCheckComplete: (check) => {\n setProgress((prev) =>\n prev.map((p) => (p.name === check.id ? { ...p, status: 'done' } : p)),\n );\n },\n });\n\n setMonorepoReport(null);\n setReport(result);\n\n // Save to trend history\n try {\n const { saveEntry } = await import('../../../lib/history.js');\n saveEntry(result);\n } catch {\n // Non-critical\n }\n\n setIsScanning(false);\n scanningRef.current = false;\n return result;\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n setIsScanning(false);\n scanningRef.current = false;\n return null;\n }\n }, [projectPath, checks, quotes]);\n\n return { report, monorepoReport, isScanning, progress, error, scan };\n}\n","import { useState, useEffect } from 'react';\n\nexport function useTerminalSize() {\n const [size, setSize] = useState({\n columns: process.stdout.columns || 80,\n rows: process.stdout.rows || 24,\n });\n\n useEffect(() => {\n const handler = () => {\n setSize({\n columns: process.stdout.columns || 80,\n rows: process.stdout.rows || 24,\n });\n };\n process.stdout.on('resize', handler);\n return () => {\n process.stdout.off('resize', handler);\n };\n }, []);\n\n return size;\n}\n","import React from 'react';\n\nimport { Box, Text } from 'ink';\n\nexport type PanelId = 'health' | 'git' | 'trend' | 'quickwins' | 'activity';\n\ninterface HotkeyBarProps {\n activePanel: PanelId | null;\n}\n\nconst HOTKEYS: Array<{ key: string; label: string; panel?: PanelId }> = [\n { key: 'h', label: 'health', panel: 'health' },\n { key: 'g', label: 'git', panel: 'git' },\n { key: 't', label: 'trend', panel: 'trend' },\n { key: 'q', label: 'quick wins', panel: 'quickwins' },\n { key: 'a', label: 'activity', panel: 'activity' },\n { key: 'r', label: 're-run' },\n { key: 'w', label: 'web' },\n { key: 'W', label: 'web+AI' },\n { key: '?', label: 'help' },\n];\n\nexport function HotkeyBar({ activePanel }: HotkeyBarProps) {\n return (\n <Box>\n {HOTKEYS.map((hk) => {\n const isActive = hk.panel !== undefined && hk.panel === activePanel;\n return (\n <Box key={hk.key} marginRight={2}>\n <Text bold={isActive} color={isActive ? 'cyan' : undefined}>\n [{hk.key}]\n </Text>\n <Text bold={isActive} color={isActive ? 'cyan' : 'gray'}>\n {' '}\n {hk.label}\n </Text>\n </Box>\n );\n })}\n </Box>\n );\n}\n","import React from 'react';\n\nimport { Box, Text } from 'ink';\n\nimport type { MonorepoReport, PackageReport } from 'sickbay-core';\n\ninterface MonorepoPanelProps {\n report: MonorepoReport | null;\n availableWidth?: number;\n}\n\nfunction scoreBar(score: number, width = 8): string {\n const filled = Math.round((score / 100) * width);\n return '█'.repeat(filled) + '░'.repeat(width - filled);\n}\n\nfunction scoreColor(score: number): string {\n if (score >= 80) return 'green';\n if (score >= 60) return 'yellow';\n return 'red';\n}\n\nexport function MonorepoPanel({ report, availableWidth }: MonorepoPanelProps) {\n if (!report) {\n return (\n <Box>\n <Text dimColor>Waiting for scan...</Text>\n </Box>\n );\n }\n\n const nameWidth = Math.max(12, Math.min(20, (availableWidth ?? 36) - 22));\n\n return (\n <Box flexDirection=\"column\">\n <Box marginBottom={1}>\n <Text bold>{report.packages.length} packages</Text>\n <Text dimColor> · </Text>\n <Text dimColor>{report.monorepoType} workspaces</Text>\n <Text dimColor> · </Text>\n <Text color={scoreColor(report.overallScore)} bold>\n {report.overallScore}\n </Text>\n <Text dimColor> avg</Text>\n </Box>\n\n {report.packages.map((pkg: PackageReport) => {\n const name = pkg.name.includes('/') ? (pkg.name.split('/').pop() ?? pkg.name) : pkg.name;\n const truncatedName =\n name.length > nameWidth\n ? name.slice(0, nameWidth - 1) + '\\u2026'\n : name.padEnd(nameWidth);\n\n return (\n <Box key={pkg.path} gap={1}>\n <Text color={scoreColor(pkg.score)}>{scoreBar(pkg.score)}</Text>\n <Text color={scoreColor(pkg.score)} bold>\n {String(pkg.score).padStart(3)}\n </Text>\n <Text>{truncatedName}</Text>\n {pkg.summary.critical > 0 && <Text color=\"red\"> ⚠</Text>}\n </Box>\n );\n })}\n\n <Box marginTop={1}>\n <Text dimColor>Press </Text>\n <Text color=\"cyan\">[w]</Text>\n <Text dimColor> for per-package web view</Text>\n </Box>\n </Box>\n );\n}\n","import React from 'react';\n\nimport { Box, Text } from 'ink';\n\ninterface PanelBorderProps {\n title: string;\n color: string;\n focused?: boolean;\n visible?: boolean;\n flash?: 'green' | 'red';\n children: React.ReactNode;\n}\n\nexport function PanelBorder({\n title,\n color,\n focused,\n visible = true,\n flash,\n children,\n}: PanelBorderProps) {\n const borderColor = flash ?? (focused ? color : 'gray');\n const borderStyle = flash ? 'double' : focused ? 'double' : 'single';\n\n return (\n <Box\n flexDirection=\"column\"\n borderStyle={borderStyle}\n borderColor={borderColor}\n paddingX={1}\n flexGrow={1}\n >\n <Text bold color={flash ?? color}>\n {title}\n </Text>\n {visible ? children : <Text dimColor>···</Text>}\n </Box>\n );\n}\n","import React from 'react';\n\nimport { Box, Text } from 'ink';\n\nimport type { SickbayReport } from 'sickbay-core';\n\ninterface QuickWinsPanelProps {\n report: SickbayReport | null;\n availableWidth?: number;\n}\n\nfunction replacePackageManager(cmd: string, pm: string): string {\n if (pm === 'npm') return cmd;\n const install = pm === 'pnpm' ? 'pnpm add' : pm === 'yarn' ? 'yarn add' : 'bun add';\n const uninstall = pm === 'pnpm' ? 'pnpm remove' : pm === 'yarn' ? 'yarn remove' : 'bun remove';\n const update = pm === 'pnpm' ? 'pnpm update' : pm === 'yarn' ? 'yarn upgrade' : 'bun update';\n const auditFix =\n pm === 'pnpm' ? 'pnpm audit --fix' : pm === 'yarn' ? 'yarn npm audit --fix' : 'bun audit';\n return cmd\n .replace(/^npm install(?=\\s)/, install)\n .replace(/^npm uninstall(?=\\s)/, uninstall)\n .replace(/^npm update(?=\\s)/, update)\n .replace(/^npm audit fix/, auditFix);\n}\n\nfunction shortenPath(filepath: string): string {\n const parts = filepath.split('/');\n if (parts.length <= 2) return filepath;\n return '\\u2026/' + parts.slice(-2).join('/');\n}\n\nfunction smartTruncate(str: string, maxLen: number): string {\n if (str.length <= maxLen) return str;\n // If it contains a file path, shorten the path but keep the filename\n const pathMatch = str.match(/(\\S*\\/\\S+)/);\n if (pathMatch) {\n const shortened = str.replace(pathMatch[1], shortenPath(pathMatch[1]));\n if (shortened.length <= maxLen) return shortened;\n return shortened.slice(0, maxLen - 1) + '\\u2026';\n }\n return str.slice(0, maxLen - 1) + '\\u2026';\n}\n\nfunction shortenCommand(cmd: string, maxLen: number): string {\n if (cmd.length <= maxLen) return cmd;\n // Shorten any file paths in the command first\n const shortened = cmd.replace(/(\\S*\\/\\S+)/g, (match) => shortenPath(match));\n if (shortened.length <= maxLen) return shortened;\n return shortened.slice(0, maxLen - 1) + '\\u2026';\n}\n\nexport function QuickWinsPanel({ report, availableWidth }: QuickWinsPanelProps) {\n if (!report) {\n return (\n <Box>\n <Text dimColor>Waiting for scan...</Text>\n </Box>\n );\n }\n\n const fixes: Array<{\n description: string;\n command: string;\n severity: string;\n }> = [];\n\n for (const check of report.checks) {\n for (const issue of check.issues) {\n if (issue.fix?.command) {\n fixes.push({\n description: issue.fix.description,\n command: issue.fix.command,\n severity: issue.severity,\n });\n }\n }\n }\n\n const order: Record<string, number> = { critical: 0, warning: 1, info: 2 };\n fixes.sort((a, b) => (order[a.severity] ?? 3) - (order[b.severity] ?? 3));\n const top = fixes.slice(0, 5);\n\n if (top.length === 0) {\n return (\n <Box>\n <Text color=\"green\">Looking good!</Text>\n </Box>\n );\n }\n\n const pm = report.projectInfo?.packageManager ?? 'npm';\n const maxTextLen = Math.max(16, (availableWidth ?? 26) - 4);\n\n return (\n <Box flexDirection=\"column\">\n {top.map((fix, i) => (\n <Box key={i} flexDirection=\"column\" marginBottom={i < top.length - 1 ? 1 : 0}>\n <Text>\n <Text\n color={\n fix.severity === 'critical' ? 'red' : fix.severity === 'warning' ? 'yellow' : 'gray'\n }\n >\n {'\\u2192'}{' '}\n </Text>\n {smartTruncate(fix.description, maxTextLen)}\n </Text>\n <Text dimColor>\n {' '}\n {shortenCommand(replacePackageManager(fix.command, pm), maxTextLen)}\n </Text>\n </Box>\n ))}\n </Box>\n );\n}\n","import React, { useState, useEffect, useRef } from 'react';\n\nimport { Box, Text } from 'ink';\n\nimport { countUniqueIssues } from '../../lib/issue-grouping.js';\n\nimport type { SickbayReport } from 'sickbay-core';\n\ninterface ScorePanelProps {\n report: SickbayReport | null;\n previousScore: number | null;\n animate?: boolean;\n}\n\nfunction scoreColor(score: number): string {\n if (score >= 80) return 'green';\n if (score >= 60) return 'yellow';\n return 'red';\n}\n\nfunction scoreBar(score: number, width = 15): string {\n const filled = Math.round((score / 100) * width);\n return '\\u2588'.repeat(filled) + '\\u2591'.repeat(width - filled);\n}\n\nexport function ScorePanel({ report, previousScore, animate = true }: ScorePanelProps) {\n const [animatedScore, setAnimatedScore] = useState(0);\n const prevTargetRef = useRef(0);\n\n useEffect(() => {\n if (!animate) return;\n\n if (!report) {\n setAnimatedScore(0);\n prevTargetRef.current = 0;\n return;\n }\n\n const target = report.overallScore;\n const start = prevTargetRef.current;\n prevTargetRef.current = target;\n\n if (start === target) return;\n\n let current = start;\n const step = target > start ? 1 : -1;\n\n const id = setInterval(() => {\n current += step;\n setAnimatedScore(current);\n if (current === target) clearInterval(id);\n }, 20);\n\n return () => clearInterval(id);\n }, [report, animate]);\n\n // When animate=false, render score directly from prop (no state update needed)\n const displayScore = animate ? animatedScore : (report?.overallScore ?? 0);\n\n if (!report) {\n return (\n <Box>\n <Text dimColor>Waiting for scan...</Text>\n </Box>\n );\n }\n\n const delta = previousScore !== null ? report.overallScore - previousScore : null;\n const u = countUniqueIssues(report.checks);\n\n return (\n <Box flexDirection=\"column\">\n <Box>\n <Text color={scoreColor(displayScore)} bold>\n {displayScore}/100\n </Text>\n <Text> </Text>\n <Text color={scoreColor(displayScore)}>{scoreBar(displayScore)}</Text>\n </Box>\n {delta !== null && (\n <Text dimColor>\n {delta > 0 ? `+${delta}` : delta === 0 ? '\\u00B10' : `${delta}`} since last scan\n </Text>\n )}\n <Box marginTop={1} flexDirection=\"column\">\n <Box>\n <Text color=\"red\">\n {'\\u2717'} {u.critical} critical\n {u.totalCritical > u.critical ? ` (${u.totalCritical})` : ''}\n </Text>\n <Text>{' '}</Text>\n <Text color=\"yellow\">\n {'\\u26A0'} {u.warnings} warn\n {u.totalWarnings > u.warnings ? ` (${u.totalWarnings})` : ''}\n </Text>\n <Text>{' '}</Text>\n <Text dimColor>i {u.info} info</Text>\n </Box>\n </Box>\n {report.quote && (\n <Box marginTop={1}>\n <Text italic dimColor>\n \"{report.quote.text}\" — {report.quote.source}\n </Text>\n </Box>\n )}\n {report.config?.hasCustomConfig && (\n <Text dimColor italic>\n Custom config\n </Text>\n )}\n </Box>\n );\n}\n","import React, { useState, useEffect } from 'react';\n\nimport { Box, Text } from 'ink';\n\nimport { sparkline, trendArrow } from '../../commands/trend.js';\nimport { loadHistory } from '../../lib/history.js';\n\ninterface TrendPanelProps {\n projectPath: string;\n lastScanTime: Date | null;\n availableHeight?: number;\n}\n\nconst CATEGORY_COLORS: Record<string, string> = {\n overall: 'white',\n dependencies: 'blue',\n security: 'green',\n 'code-quality': 'yellow',\n performance: 'magenta',\n git: 'cyan',\n};\n\nconst SHORT_LABELS: Record<string, string> = {\n dependencies: 'Deps',\n security: 'Security',\n 'code-quality': 'Quality',\n performance: 'Perf',\n git: 'Git',\n};\n\nexport function TrendPanel({ projectPath, lastScanTime, availableHeight }: TrendPanelProps) {\n const [trends, setTrends] = useState<\n Array<{\n label: string;\n spark: string;\n latest: number;\n arrow: string;\n color: string;\n }>\n >([]);\n\n useEffect(() => {\n const history = loadHistory(projectPath);\n if (!history || history.entries.length === 0) {\n setTrends([]);\n return;\n }\n\n const entries = history.entries.slice(-10);\n const overallValues = entries.map((e) => e.overallScore);\n const overallArrow = trendArrow(overallValues);\n\n const result: typeof trends = [\n {\n label: 'Overall',\n spark: sparkline(overallValues),\n latest: overallValues[overallValues.length - 1],\n arrow: overallArrow.label,\n color: 'white',\n },\n ];\n\n const categories = Object.keys(entries[entries.length - 1].categoryScores);\n for (const cat of categories) {\n const values = entries\n .map((e) => e.categoryScores[cat])\n .filter((v): v is number => v !== undefined);\n\n if (values.length === 0) continue;\n\n const arrow = trendArrow(values);\n result.push({\n label: SHORT_LABELS[cat] || cat.charAt(0).toUpperCase() + cat.slice(1),\n spark: sparkline(values),\n latest: values[values.length - 1],\n arrow: arrow.label,\n color: CATEGORY_COLORS[cat] || 'white',\n });\n }\n\n setTrends(result);\n }, [projectPath, lastScanTime]);\n\n if (trends.length === 0) {\n return (\n <Box>\n <Text dimColor>No trend data yet.</Text>\n </Box>\n );\n }\n\n const maxRows = availableHeight ?? trends.length;\n const visible = trends.slice(0, maxRows);\n\n return (\n <Box flexDirection=\"column\">\n {visible.map((t) => (\n <Box key={t.label}>\n <Box width={10}>\n <Text color={t.color}>{t.label}</Text>\n </Box>\n <Text>{t.spark}</Text>\n <Text bold> {String(t.latest).padStart(3)}</Text>\n <Text> {t.arrow}</Text>\n </Box>\n ))}\n </Box>\n );\n}\n","import React, { useState, useEffect, useCallback, useRef } from 'react';\n\nimport { Box, Text, useInput } from 'ink';\n\nimport { checkForUpdate } from '../../lib/update-check.js';\nimport { UpdateNotice } from '../UpdateNotice.js';\nimport { ActivityPanel, type ActivityEntry } from './ActivityPanel.js';\nimport { GitPanel } from './GitPanel.js';\nimport { HealthPanel } from './HealthPanel.js';\nimport { HelpPanel } from './HelpPanel.js';\nimport { useFileWatcher } from './hooks/useFileWatcher.js';\nimport { useSickbayRunner } from './hooks/useSickbayRunner.js';\nimport { useTerminalSize } from './hooks/useTerminalSize.js';\nimport { HotkeyBar, type PanelId } from './HotkeyBar.js';\nimport { MonorepoPanel } from './MonorepoPanel.js';\nimport { PanelBorder } from './PanelBorder.js';\nimport { QuickWinsPanel } from './QuickWinsPanel.js';\nimport { ScorePanel } from './ScorePanel.js';\nimport { TrendPanel } from './TrendPanel.js';\n\nimport type { UpdateInfo } from '../../lib/update-check.js';\nimport type { SickbayReport, MonorepoReport } from 'sickbay-core';\n\ndeclare const __VERSION__: string;\n\ninterface TuiAppProps {\n projectPath: string;\n checks?: string[];\n watchEnabled: boolean;\n refreshInterval: number;\n quotes?: boolean;\n animateOnMount?: boolean;\n}\n\nexport function TuiApp({\n projectPath,\n checks,\n watchEnabled,\n refreshInterval,\n quotes,\n animateOnMount = true,\n}: TuiAppProps) {\n const { rows, columns } = useTerminalSize();\n const { report, monorepoReport, isScanning, progress, scan } = useSickbayRunner({\n projectPath,\n checks,\n quotes,\n });\n\n const [focusedPanel, setFocusedPanel] = useState<PanelId | null>(null);\n const [expandedPanel, setExpandedPanel] = useState<PanelId | null>(null);\n const [showHelp, setShowHelp] = useState(false);\n const [lastScanTime, setLastScanTime] = useState<Date | null>(null);\n const [previousScore, setPreviousScore] = useState<number | null>(null);\n const [activityLog, setActivityLog] = useState<ActivityEntry[]>([]);\n const [healthScrollOffset, setHealthScrollOffset] = useState(0);\n const [scoreFlash, setScoreFlash] = useState<'green' | 'red' | undefined>();\n const [updateInfo, setUpdateInfo] = useState<UpdateInfo | null>(null);\n const ALL_PANELS = new Set(['health', 'score', 'trend', 'git', 'quickwins', 'activity']);\n const [visiblePanels, setVisiblePanels] = useState<Set<string>>(\n animateOnMount ? new Set() : ALL_PANELS,\n );\n const refreshRef = useRef<ReturnType<typeof setInterval> | undefined>(undefined);\n\n // Panel entrance animation — stagger panels appearing on mount\n useEffect(() => {\n const schedule: Array<[string, number]> = [\n ['health', 100],\n ['score', 300],\n ['trend', 500],\n ['git', 700],\n ['quickwins', 900],\n ['activity', 1100],\n ];\n const timers = schedule.map(([panel, delay]) =>\n setTimeout(() => {\n setVisiblePanels((prev) => new Set([...prev, panel]));\n }, delay),\n );\n return () => timers.forEach(clearTimeout);\n }, []);\n\n useEffect(() => {\n checkForUpdate(__VERSION__).then((info) => {\n if (info) setUpdateInfo(info);\n });\n }, []);\n\n const reportRef = useRef<SickbayReport | null>(null);\n const monorepoReportRef = useRef<MonorepoReport | null>(null);\n\n // Keep refs in sync\n useEffect(() => {\n reportRef.current = report;\n }, [report]);\n\n useEffect(() => {\n monorepoReportRef.current = monorepoReport;\n }, [monorepoReport]);\n\n const addActivity = useCallback((type: ActivityEntry['type'], message: string) => {\n setActivityLog((prev) => [...prev, { timestamp: new Date(), type, message }]);\n }, []);\n\n const handleScanComplete = useCallback(\n async (result: SickbayReport) => {\n const prevScore = reportRef.current?.overallScore ?? null;\n if (prevScore !== null) setPreviousScore(prevScore);\n setLastScanTime(new Date());\n\n // Auto-save last report snapshot\n try {\n const { saveLastReport } = await import('../../lib/history.js');\n saveLastReport(result);\n } catch {\n // Non-critical\n }\n\n // Cache dependency tree for web dashboard\n try {\n const { getDependencyTree } = await import('sickbay-core');\n const { saveDepTree } = await import('../../lib/history.js');\n const tree = await getDependencyTree(projectPath, result.projectInfo.packageManager);\n saveDepTree(projectPath, tree);\n } catch {\n /* dep tree is optional */\n }\n\n const delta = prevScore !== null ? result.overallScore - prevScore : null;\n addActivity(\n 'scan-complete',\n `Scan complete: ${result.overallScore}/100${delta !== null ? ` (${delta >= 0 ? '+' : ''}${delta})` : ''}`,\n );\n\n // Flash score panel border on score change\n if (delta !== null && delta !== 0) {\n const flash = delta > 0 ? 'green' : 'red';\n setScoreFlash(flash);\n setTimeout(() => setScoreFlash(undefined), 600);\n }\n\n // Check for regressions\n try {\n const { loadHistory, detectRegressions } = await import('../../lib/history.js');\n const history = loadHistory(projectPath);\n if (history) {\n const regressions = detectRegressions(history.entries);\n for (const reg of regressions) {\n addActivity(\n 'regression',\n `\\u26A0 ${reg.category} regressed: ${reg.from} \\u2192 ${reg.to}`,\n );\n }\n }\n } catch {\n // Non-critical\n }\n },\n [addActivity, projectPath],\n );\n\n // Initial scan\n useEffect(() => {\n addActivity('info', 'TUI started');\n addActivity('scan-start', 'Starting initial health scan...');\n scan().then((result) => {\n if (result) handleScanComplete(result);\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Auto-refresh timer\n useEffect(() => {\n if (refreshInterval <= 0) return;\n refreshRef.current = setInterval(async () => {\n addActivity('scan-start', 'Auto-scan triggered');\n const result = await scan();\n if (result) handleScanComplete(result);\n }, refreshInterval * 1000);\n\n return () => {\n if (refreshRef.current) clearInterval(refreshRef.current);\n };\n }, [refreshInterval, scan, handleScanComplete, addActivity]);\n\n // File watcher\n useFileWatcher({\n projectPath,\n enabled: watchEnabled,\n onFilesChanged: async (files) => {\n for (const f of files.slice(0, 3)) {\n addActivity('file-change', `File changed: ${f}`);\n }\n if (files.length > 3) {\n addActivity('file-change', `...and ${files.length - 3} more files`);\n }\n addActivity('scan-start', 'Re-scan triggered (file change)');\n const result = await scan();\n if (result) handleScanComplete(result);\n },\n });\n\n // Manual re-scan\n const triggerRescan = useCallback(async () => {\n addActivity('scan-start', 'Manual re-scan triggered');\n const result = await scan();\n if (result) handleScanComplete(result);\n }, [addActivity, scan, handleScanComplete]);\n\n // Keyboard input — only active when stdin supports raw mode (interactive TTY)\n const isTTY = !!(process.stdin.isTTY && process.stdin.setRawMode);\n useInput(\n (input, key) => {\n // Help overlay: ? toggles, Escape closes\n if (showHelp) {\n if (input === '?' || key.escape) {\n setShowHelp(false);\n }\n return;\n }\n\n // Expanded panel: escape or f to exit\n if (expandedPanel) {\n if (key.escape || input === 'f') {\n setExpandedPanel(null);\n return;\n }\n }\n\n if (input === '?') {\n setShowHelp(true);\n return;\n }\n\n // Panel focus toggles\n if (input === 'h') setFocusedPanel((prev) => (prev === 'health' ? null : 'health'));\n else if (input === 'g') setFocusedPanel((prev) => (prev === 'git' ? null : 'git'));\n else if (input === 't') setFocusedPanel((prev) => (prev === 'trend' ? null : 'trend'));\n else if (input === 'q')\n setFocusedPanel((prev) => (prev === 'quickwins' ? null : 'quickwins'));\n else if (input === 'a') setFocusedPanel((prev) => (prev === 'activity' ? null : 'activity'));\n else if (input === 'r') triggerRescan();\n else if (input === 'f' && focusedPanel) setExpandedPanel(focusedPanel);\n else if (input === 'w' || input === 'W') {\n const withAI = input === 'W';\n (async () => {\n const webReport = monorepoReportRef.current ?? reportRef.current;\n if (!webReport) return;\n try {\n const { serveWeb } = await import('../../commands/web.js');\n const { default: openBrowser } = await import('open');\n\n let aiService;\n if (withAI && process.env.ANTHROPIC_API_KEY && !monorepoReportRef.current) {\n const { createAIService } = await import('../../services/ai.js');\n aiService = createAIService(process.env.ANTHROPIC_API_KEY);\n addActivity('info', 'Launching web dashboard with AI...');\n } else {\n addActivity('info', 'Launching web dashboard...');\n }\n\n const url = await serveWeb(webReport, 3030, aiService);\n await openBrowser(url);\n addActivity(\n 'info',\n `Web dashboard at ${url}${withAI && aiService ? ' (AI enabled)' : ''}`,\n );\n } catch (err) {\n addActivity('info', `Failed to open web: ${err instanceof Error ? err.message : err}`);\n }\n })();\n } else if (key.escape) {\n setFocusedPanel(null);\n }\n\n // Scrolling in focused health panel\n if (focusedPanel === 'health') {\n if (key.upArrow) setHealthScrollOffset((prev) => Math.max(0, prev - 1));\n if (key.downArrow) {\n const maxOffset = Math.max(0, visibleChecks.length - 5);\n setHealthScrollOffset((prev) => Math.min(maxOffset, prev + 1));\n }\n }\n },\n { isActive: isTTY },\n );\n\n const visibleChecks = report?.checks.filter((c) => c.status !== 'skipped') ?? [];\n\n // Layout calculations — reserve 1 row for project header + 1 for hotkey bar\n const topHeight = Math.floor((rows - 2) * 0.6);\n const bottomHeight = rows - 2 - topHeight;\n\n // Help overlay\n if (showHelp) {\n return (\n <Box flexDirection=\"column\" width={columns} height={rows}>\n <Box flexGrow={1}>\n <PanelBorder title=\"HELP\" color=\"cyan\" focused>\n <HelpPanel />\n </PanelBorder>\n </Box>\n <HotkeyBar activePanel={focusedPanel} />\n </Box>\n );\n }\n\n // Full-screen expand mode\n if (expandedPanel) {\n return (\n <Box flexDirection=\"column\" width={columns} height={rows}>\n <Box flexGrow={1}>\n {expandedPanel === 'health' && (\n <PanelBorder title=\"HEALTH CHECKS\" color=\"green\" focused>\n <HealthPanel\n checks={visibleChecks}\n isScanning={isScanning}\n progress={progress}\n scrollOffset={healthScrollOffset}\n availableHeight={rows - 4}\n />\n </PanelBorder>\n )}\n {expandedPanel === 'git' && (\n <PanelBorder title=\"GIT STATUS\" color=\"yellow\" focused>\n <GitPanel projectPath={projectPath} availableWidth={columns - 4} />\n </PanelBorder>\n )}\n {expandedPanel === 'trend' && (\n <PanelBorder title=\"TREND\" color=\"magenta\" focused>\n <TrendPanel projectPath={projectPath} lastScanTime={lastScanTime} />\n </PanelBorder>\n )}\n {expandedPanel === 'quickwins' && (\n <PanelBorder title={monorepoReport ? 'MONOREPO' : 'QUICK WINS'} color=\"red\" focused>\n {monorepoReport ? (\n <MonorepoPanel report={monorepoReport} />\n ) : (\n <QuickWinsPanel report={report} />\n )}\n </PanelBorder>\n )}\n {expandedPanel === 'activity' && (\n <PanelBorder title=\"ACTIVITY\" color=\"cyan\" focused>\n <ActivityPanel entries={activityLog} availableHeight={rows - 4} />\n </PanelBorder>\n )}\n </Box>\n <HotkeyBar activePanel={expandedPanel} />\n </Box>\n );\n }\n\n // Normal grid layout\n const projectName = monorepoReport\n ? `${monorepoReport.rootPath.split('/').pop()} (monorepo)`\n : (report?.projectInfo?.name ?? '—');\n const projectVersion = report?.projectInfo?.version;\n const scanLabel = lastScanTime\n ? `Last scan ${lastScanTime.toLocaleTimeString()}`\n : isScanning\n ? 'Scanning…'\n : 'Not yet scanned';\n\n return (\n <Box flexDirection=\"column\" width={columns} height={rows}>\n {updateInfo && (\n <UpdateNotice\n currentVersion={updateInfo.currentVersion}\n latestVersion={updateInfo.latestVersion}\n />\n )}\n {/* Project header */}\n <Box paddingX={1} justifyContent=\"space-between\">\n <Box gap={1}>\n <Text bold color=\"cyan\">\n SICKBAY\n </Text>\n <Text bold>{projectName}</Text>\n {projectVersion && <Text dimColor>v{projectVersion}</Text>}\n </Box>\n <Text dimColor>{scanLabel}</Text>\n </Box>\n\n {/* Top Row */}\n <Box height={topHeight}>\n <Box width=\"55%\">\n <PanelBorder\n title=\"HEALTH CHECKS\"\n color=\"green\"\n focused={focusedPanel === 'health'}\n visible={visiblePanels.has('health')}\n >\n <HealthPanel\n checks={visibleChecks}\n isScanning={isScanning}\n progress={progress}\n scrollOffset={healthScrollOffset}\n availableHeight={topHeight - 4}\n />\n </PanelBorder>\n </Box>\n <Box width=\"45%\" flexDirection=\"column\">\n <Box height=\"50%\">\n <PanelBorder\n title=\"SCORE\"\n color=\"blue\"\n visible={visiblePanels.has('score')}\n flash={scoreFlash}\n >\n <ScorePanel report={report} previousScore={previousScore} animate={animateOnMount} />\n </PanelBorder>\n </Box>\n <Box height=\"50%\">\n <PanelBorder\n title=\"TREND\"\n color=\"magenta\"\n focused={focusedPanel === 'trend'}\n visible={visiblePanels.has('trend')}\n >\n <TrendPanel\n projectPath={projectPath}\n lastScanTime={lastScanTime}\n availableHeight={Math.floor(topHeight / 2) - 3}\n />\n </PanelBorder>\n </Box>\n </Box>\n </Box>\n\n {/* Bottom Row */}\n <Box height={bottomHeight}>\n <Box width=\"25%\">\n <PanelBorder\n title=\"GIT STATUS\"\n color=\"yellow\"\n focused={focusedPanel === 'git'}\n visible={visiblePanels.has('git')}\n >\n <GitPanel projectPath={projectPath} availableWidth={Math.floor(columns * 0.25) - 4} />\n </PanelBorder>\n </Box>\n <Box width=\"30%\">\n <PanelBorder\n title={monorepoReport ? 'MONOREPO' : 'QUICK WINS'}\n color=\"red\"\n focused={focusedPanel === 'quickwins'}\n visible={visiblePanels.has('quickwins')}\n >\n {monorepoReport ? (\n <MonorepoPanel\n report={monorepoReport}\n availableWidth={Math.floor(columns * 0.3) - 4}\n />\n ) : (\n <QuickWinsPanel report={report} availableWidth={Math.floor(columns * 0.3) - 4} />\n )}\n </PanelBorder>\n </Box>\n <Box width=\"45%\">\n <PanelBorder\n title=\"ACTIVITY\"\n color=\"cyan\"\n focused={focusedPanel === 'activity'}\n visible={visiblePanels.has('activity')}\n >\n <ActivityPanel entries={activityLog} availableHeight={bottomHeight - 4} />\n </PanelBorder>\n </Box>\n </Box>\n\n {/* Hotkey Bar */}\n <HotkeyBar activePanel={focusedPanel} />\n </Box>\n );\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-CiIaOW0V.js";
2
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
3
3
  import { join } from "path";
4
4
  //#region src/lib/history.ts
5
5
  var history_exports = /* @__PURE__ */ __exportAll({
@@ -9,6 +9,23 @@ var history_exports = /* @__PURE__ */ __exportAll({
9
9
  saveEntry: () => saveEntry,
10
10
  saveLastReport: () => saveLastReport
11
11
  });
12
+ /**
13
+ * Crash-safe JSON write: write to a sibling `.tmp` path, then rename into
14
+ * place. If the process is killed mid-write (SIGINT, power loss, OOM, disk
15
+ * full), the target file is either fully written or unchanged — never
16
+ * truncated. Downstream readers (`diff`, `stats`, `trend`, `badge`, and the
17
+ * web server) previously had no recovery path for a partial `last-report.json`
18
+ * or `dep-tree.json` since `loadHistory` is the only reader with a `try/catch`.
19
+ *
20
+ * `renameSync` is atomic on POSIX and atomic within a single volume on
21
+ * Windows. `.tmp` and the destination are always siblings, so that's
22
+ * guaranteed to be true here.
23
+ */
24
+ function writeJsonAtomic(filePath, data) {
25
+ const tmpPath = `${filePath}.tmp`;
26
+ writeFileSync(tmpPath, JSON.stringify(data, null, 2));
27
+ renameSync(tmpPath, filePath);
28
+ }
12
29
  function historyFilePath(projectPath) {
13
30
  return join(projectPath, ".sickbay", "history.json");
14
31
  }
@@ -45,15 +62,15 @@ function saveEntry(report) {
45
62
  checksRun: report.checks.filter((c) => c.status !== "skipped").length
46
63
  });
47
64
  if (existing.entries.length > 100) existing.entries = existing.entries.slice(-100);
48
- writeFileSync(filePath, JSON.stringify(existing, null, 2));
65
+ writeJsonAtomic(filePath, existing);
49
66
  }
50
67
  function saveLastReport(report) {
51
68
  mkdirSync(join(report.projectPath, ".sickbay"), { recursive: true });
52
- writeFileSync(join(report.projectPath, ".sickbay", "last-report.json"), JSON.stringify(report, null, 2));
69
+ writeJsonAtomic(join(report.projectPath, ".sickbay", "last-report.json"), report);
53
70
  }
54
71
  function saveDepTree(projectPath, tree) {
55
72
  mkdirSync(join(projectPath, ".sickbay"), { recursive: true });
56
- writeFileSync(join(projectPath, ".sickbay", "dep-tree.json"), JSON.stringify(tree, null, 2));
73
+ writeJsonAtomic(join(projectPath, ".sickbay", "dep-tree.json"), tree);
57
74
  }
58
75
  function detectRegressions(entries) {
59
76
  if (entries.length < 2) return [];
@@ -80,4 +97,4 @@ function detectRegressions(entries) {
80
97
  //#endregion
81
98
  export { saveEntry as i, history_exports as n, loadHistory as r, detectRegressions as t };
82
99
 
83
- //# sourceMappingURL=history-DVegAwFp.js.map
100
+ //# sourceMappingURL=history-C9hu3f6s.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history-C9hu3f6s.js","names":[],"sources":["../src/lib/history.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from 'fs';\nimport { join } from 'path';\n\nimport type { SickbayReport } from 'sickbay-core';\n\n/**\n * Crash-safe JSON write: write to a sibling `.tmp` path, then rename into\n * place. If the process is killed mid-write (SIGINT, power loss, OOM, disk\n * full), the target file is either fully written or unchanged — never\n * truncated. Downstream readers (`diff`, `stats`, `trend`, `badge`, and the\n * web server) previously had no recovery path for a partial `last-report.json`\n * or `dep-tree.json` since `loadHistory` is the only reader with a `try/catch`.\n *\n * `renameSync` is atomic on POSIX and atomic within a single volume on\n * Windows. `.tmp` and the destination are always siblings, so that's\n * guaranteed to be true here.\n */\nfunction writeJsonAtomic(filePath: string, data: unknown): void {\n const tmpPath = `${filePath}.tmp`;\n writeFileSync(tmpPath, JSON.stringify(data, null, 2));\n renameSync(tmpPath, filePath);\n}\n\nexport interface TrendEntry {\n timestamp: string;\n overallScore: number;\n categoryScores: Record<string, number>;\n summary: { critical: number; warnings: number; info: number };\n checksRun: number;\n}\n\nexport interface TrendHistory {\n projectPath: string;\n projectName: string;\n entries: TrendEntry[];\n}\n\nfunction historyFilePath(projectPath: string): string {\n return join(projectPath, '.sickbay', 'history.json');\n}\n\nexport function loadHistory(projectPath: string): TrendHistory | null {\n const filePath = historyFilePath(projectPath);\n if (!existsSync(filePath)) return null;\n try {\n return JSON.parse(readFileSync(filePath, 'utf-8'));\n } catch {\n return null;\n }\n}\n\nexport function saveEntry(report: SickbayReport): void {\n mkdirSync(join(report.projectPath, '.sickbay'), { recursive: true });\n\n const filePath = historyFilePath(report.projectPath);\n const existing = loadHistory(report.projectPath) ?? {\n projectPath: report.projectPath,\n projectName: report.projectInfo.name,\n entries: [],\n };\n\n const categoryScores: Record<string, number> = {};\n const categoryChecks: Record<string, number[]> = {};\n for (const check of report.checks) {\n if (check.status === 'skipped') continue;\n if (!categoryChecks[check.category]) categoryChecks[check.category] = [];\n categoryChecks[check.category].push(check.score);\n }\n for (const [cat, scores] of Object.entries(categoryChecks)) {\n categoryScores[cat] = Math.round(scores.reduce((a, b) => a + b, 0) / scores.length);\n }\n\n existing.entries.push({\n timestamp: report.timestamp,\n overallScore: report.overallScore,\n categoryScores,\n summary: { ...report.summary },\n checksRun: report.checks.filter((c) => c.status !== 'skipped').length,\n });\n\n if (existing.entries.length > 100) {\n existing.entries = existing.entries.slice(-100);\n }\n\n writeJsonAtomic(filePath, existing);\n}\n\nexport function saveLastReport(report: SickbayReport): void {\n mkdirSync(join(report.projectPath, '.sickbay'), { recursive: true });\n writeJsonAtomic(join(report.projectPath, '.sickbay', 'last-report.json'), report);\n}\n\nexport function saveDepTree(projectPath: string, tree: unknown): void {\n mkdirSync(join(projectPath, '.sickbay'), { recursive: true });\n writeJsonAtomic(join(projectPath, '.sickbay', 'dep-tree.json'), tree);\n}\n\nexport function detectRegressions(\n entries: TrendEntry[],\n): Array<{ category: string; drop: number; from: number; to: number }> {\n if (entries.length < 2) return [];\n const latest = entries[entries.length - 1];\n const previous = entries[entries.length - 2];\n\n const regressions: Array<{\n category: string;\n drop: number;\n from: number;\n to: number;\n }> = [];\n\n if (latest.overallScore < previous.overallScore - 5) {\n regressions.push({\n category: 'overall',\n drop: previous.overallScore - latest.overallScore,\n from: previous.overallScore,\n to: latest.overallScore,\n });\n }\n\n for (const [cat, score] of Object.entries(latest.categoryScores)) {\n const prevScore = previous.categoryScores[cat];\n if (prevScore !== undefined && score < prevScore - 5) {\n regressions.push({\n category: cat,\n drop: prevScore - score,\n from: prevScore,\n to: score,\n });\n }\n }\n\n return regressions;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAiBA,SAAS,gBAAgB,UAAkB,MAAqB;CAC9D,MAAM,UAAU,GAAG,SAAS;AAC5B,eAAc,SAAS,KAAK,UAAU,MAAM,MAAM,EAAE,CAAC;AACrD,YAAW,SAAS,SAAS;;AAiB/B,SAAS,gBAAgB,aAA6B;AACpD,QAAO,KAAK,aAAa,YAAY,eAAe;;AAGtD,SAAgB,YAAY,aAA0C;CACpE,MAAM,WAAW,gBAAgB,YAAY;AAC7C,KAAI,CAAC,WAAW,SAAS,CAAE,QAAO;AAClC,KAAI;AACF,SAAO,KAAK,MAAM,aAAa,UAAU,QAAQ,CAAC;SAC5C;AACN,SAAO;;;AAIX,SAAgB,UAAU,QAA6B;AACrD,WAAU,KAAK,OAAO,aAAa,WAAW,EAAE,EAAE,WAAW,MAAM,CAAC;CAEpE,MAAM,WAAW,gBAAgB,OAAO,YAAY;CACpD,MAAM,WAAW,YAAY,OAAO,YAAY,IAAI;EAClD,aAAa,OAAO;EACpB,aAAa,OAAO,YAAY;EAChC,SAAS,EAAE;EACZ;CAED,MAAM,iBAAyC,EAAE;CACjD,MAAM,iBAA2C,EAAE;AACnD,MAAK,MAAM,SAAS,OAAO,QAAQ;AACjC,MAAI,MAAM,WAAW,UAAW;AAChC,MAAI,CAAC,eAAe,MAAM,UAAW,gBAAe,MAAM,YAAY,EAAE;AACxE,iBAAe,MAAM,UAAU,KAAK,MAAM,MAAM;;AAElD,MAAK,MAAM,CAAC,KAAK,WAAW,OAAO,QAAQ,eAAe,CACxD,gBAAe,OAAO,KAAK,MAAM,OAAO,QAAQ,GAAG,MAAM,IAAI,GAAG,EAAE,GAAG,OAAO,OAAO;AAGrF,UAAS,QAAQ,KAAK;EACpB,WAAW,OAAO;EAClB,cAAc,OAAO;EACrB;EACA,SAAS,EAAE,GAAG,OAAO,SAAS;EAC9B,WAAW,OAAO,OAAO,QAAQ,MAAM,EAAE,WAAW,UAAU,CAAC;EAChE,CAAC;AAEF,KAAI,SAAS,QAAQ,SAAS,IAC5B,UAAS,UAAU,SAAS,QAAQ,MAAM,KAAK;AAGjD,iBAAgB,UAAU,SAAS;;AAGrC,SAAgB,eAAe,QAA6B;AAC1D,WAAU,KAAK,OAAO,aAAa,WAAW,EAAE,EAAE,WAAW,MAAM,CAAC;AACpE,iBAAgB,KAAK,OAAO,aAAa,YAAY,mBAAmB,EAAE,OAAO;;AAGnF,SAAgB,YAAY,aAAqB,MAAqB;AACpE,WAAU,KAAK,aAAa,WAAW,EAAE,EAAE,WAAW,MAAM,CAAC;AAC7D,iBAAgB,KAAK,aAAa,YAAY,gBAAgB,EAAE,KAAK;;AAGvE,SAAgB,kBACd,SACqE;AACrE,KAAI,QAAQ,SAAS,EAAG,QAAO,EAAE;CACjC,MAAM,SAAS,QAAQ,QAAQ,SAAS;CACxC,MAAM,WAAW,QAAQ,QAAQ,SAAS;CAE1C,MAAM,cAKD,EAAE;AAEP,KAAI,OAAO,eAAe,SAAS,eAAe,EAChD,aAAY,KAAK;EACf,UAAU;EACV,MAAM,SAAS,eAAe,OAAO;EACrC,MAAM,SAAS;EACf,IAAI,OAAO;EACZ,CAAC;AAGJ,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,eAAe,EAAE;EAChE,MAAM,YAAY,SAAS,eAAe;AAC1C,MAAI,cAAc,KAAA,KAAa,QAAQ,YAAY,EACjD,aAAY,KAAK;GACf,UAAU;GACV,MAAM,YAAY;GAClB,MAAM;GACN,IAAI;GACL,CAAC;;AAIN,QAAO"}