import { Box, Text } from 'ink'; import type { AuditTuiState } from '../audit-state'; import { selectedFinding } from '../audit-state'; import { InboxZeroCelebration } from '../celebration'; import { SEVERITY_VISUALS } from '../icons'; import { wrapText } from '../wrap'; interface Props { state: AuditTuiState; focused: boolean; /** * The pane's full width in columns. Used to pre-wrap multi-line * content (details, non-actionable guidance) so each rendered line * fits within the pane — Ink's column layout assumes one Text = * one row, so a wrapping Text would overlap the next sibling. */ width: number; } /** * ASCII-art "celilo" wordmark in the figlet "Standard" font. 4 rows * tall, ~30 cols wide. Rendered cyan to pop against the pane chrome. * * Hard-coded as a string array rather than a runtime figlet * dependency — the wordmark is stable and the dep would be overkill. */ const CELILO_ASCII = [ ' ___ ___ _ ___ _ ___ ', ' / __| __| | |_ _| | / _ \\ ', ' | (__| _|| |__ | || |_| (_) |', ' \\___|___|____|___|____\\___/ ', ]; /** * Banner shown in this pane when the user is focused on Summary * (pane 1). Lazygit-style orientation: ASCII wordmark, what celilo * is, where to learn more, where to find help. The full keymap * lives behind `?` and is suppressed here to keep the banner clean. */ function CeliloBanner({ contentWidth }: { contentWidth: number }) { const description = wrapText( 'Home-lab orchestration: Terraform + Ansible + a module registry, wrapped in a CLI that knows your topology.', contentWidth, ); const screenDesc = wrapText( 'Reports drift across CLI version, schema, capability ABI, terraform plan, module versions, configs, health, backups, undeployed modules, and unconfigured modules.', contentWidth, ); // Show ASCII art only when the pane is wide enough to fit it // without ugly wrap. Falls back to a plain "celilo" title on // narrow terminals. const fitsAscii = contentWidth >= CELILO_ASCII[0].length; return ( {fitsAscii ? ( {CELILO_ASCII.map((line, i) => ( // biome-ignore lint/suspicious/noArrayIndexKey: static ASCII art {line} ))} ) : ( celilo )} {description.map((line, i) => ( // biome-ignore lint/suspicious/noArrayIndexKey: pre-wrapped static lines {line} ))} https://celilo.computer This screen — `system audit` {screenDesc.map((line, i) => ( // biome-ignore lint/suspicious/noArrayIndexKey: pre-wrapped static lines {line} ))} Press `?` for the full keymap. Copyright 2026 Peter Banka ); } export function DetailPane({ state, focused, width }: Props) { const finding = selectedFinding(state); const showBanner = state.focusedPane === 'summary'; const inboxZero = !state.auditing && state.report.findings.length === 0; // Content width = pane width - 2 border cols - 2 padding cols (paddingX={1}). const contentWidth = Math.max(width - 4, 10); return ( 4 Detail {showBanner ? ( ) : inboxZero ? ( ) : finding === null ? ( (no finding selected) ) : ( <> {wrapText(finding.message, contentWidth).map((line, i) => ( {line} ))} {finding.details && ( {wrapText(finding.details, contentWidth).map((line, i) => ( // biome-ignore lint/suspicious/noArrayIndexKey: pre-wrapped static lines {line} ))} )} {finding.remediation && finding.actionable && ( {wrapText(`→ ${finding.remediation}`, contentWidth).map((line, i) => ( // biome-ignore lint/suspicious/noArrayIndexKey: pre-wrapped static lines {line} ))} )} {finding.remediation && !finding.actionable && ( {/* * Non-runnable remediation — render as plain guidance * (no `→` arrow, no command-style cyan) so the user * doesn't expect to hit `r` and get a one-shot fix. */} {wrapText(finding.remediation, contentWidth).map((line, i) => ( // biome-ignore lint/suspicious/noArrayIndexKey: pre-wrapped static lines {line} ))} )} {finding.subject} · {finding.code} )} ); }