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}
>
)}
);
}