import { Box, Text } from 'ink'; import type { AuditTuiState } from '../audit-state'; import { findingId, findingsForCategory } from '../audit-state'; import { InboxZeroCelebration } from '../celebration'; interface Props { state: AuditTuiState; focused: boolean; /** * Total height the pane will occupy. Used to compute the scroll * window so the selected finding stays visible without the pane * resizing. */ height: number; } /** * Compute a scroll window that keeps `selectedIndex` visible inside a * window of `windowSize` rows. Anchors selection at the top when it * would otherwise scroll off above; at the bottom when it would * scroll off below. Exported so the mouse-click hit-tester can apply * the same math when translating clicks into list indices. */ export function computeScrollWindow( selectedIndex: number, totalCount: number, windowSize: number, ): { start: number; end: number } { if (totalCount <= windowSize) return { start: 0, end: totalCount }; let start = Math.max(0, selectedIndex - Math.floor(windowSize / 2)); if (start + windowSize > totalCount) { start = totalCount - windowSize; } return { start, end: start + windowSize }; } /** Chrome (border+title+spacer) above the first finding row. */ export const FINDINGS_CHROME = 4; export function FindingsPane({ state, focused, height }: Props) { const findings = findingsForCategory(state.groups, state.selectedCategory); // Pane height accounts for: 2 border rows + 1 title row + 1 spacer // = 4 chrome rows. Reserve one bottom row for the "(N more)" hint // when the list overflows, so the visible window is height - 5. const chrome = 4; const overflowRow = findings.length > Math.max(height - chrome, 0) ? 1 : 0; const visible = Math.max(height - chrome - overflowRow, 0); const selectedIndex = Math.max( findings.findIndex((f) => findingId(f) === state.selectedFindingId), 0, ); const { start, end } = computeScrollWindow(selectedIndex, findings.length, visible); const slice = findings.slice(start, end); const hidden = findings.length - end; return ( 3 Findings · {state.selectedCategory ?? '—'} {findings.length > 0 ? ` (${selectedIndex + 1}/${findings.length})` : ''} {findings.length === 0 ? ( state.auditing ? ( (checking…) ) : state.report.findings.length === 0 ? ( ) : ( (none) ) ) : ( slice.map((f) => { const id = findingId(f); const isSelected = focused && id === state.selectedFindingId; const marker = isSelected ? '▸ ' : ' '; return ( {marker} {f.message} ); }) )} {hidden > 0 && ↓ {hidden} more} ); }