import { Box, Text } from '@hermes/ink' import { memo, useState } from 'react' import { countPendingTodos } from '../lib/liveProgress.js' import { todoGlyph, todoTone } from '../lib/todo.js' import type { Theme } from '../theme.js' import type { TodoItem } from '../types.js' const rowColor = (t: Theme, status: TodoItem['status']) => { const tone = todoTone(status) return tone === 'active' ? t.color.cornsilk : tone === 'body' ? t.color.statusFg : t.color.dim } export const TodoPanel = memo(function TodoPanel({ collapsed, incomplete = false, onToggle, t, todos }: { collapsed?: boolean incomplete?: boolean onToggle?: () => void t: Theme todos: TodoItem[] }) { // Fallback local state for archived todos in transcript where there's no // external controller. Live TodoPanel passes collapsed+onToggle from the // turn store so clicks still work there. const [localCollapsed, setLocalCollapsed] = useState(false) const isControlled = typeof collapsed === 'boolean' const effectiveCollapsed = isControlled ? collapsed : localCollapsed const handleToggle = () => { if (onToggle) { onToggle() return } if (!isControlled) { setLocalCollapsed(v => !v) } } if (!todos.length) { return null } const done = todos.filter(todo => todo.status === 'completed').length const pending = countPendingTodos(todos) return ( {effectiveCollapsed ? '▸ ' : '▾ '} Todo {' '} ({done}/{todos.length}) {incomplete && pending > 0 && ( {' '} · incomplete · {pending} still {pending === 1 ? 'pending' : 'pending/in_progress'} )} {!effectiveCollapsed && ( {todos.map(todo => { const tone = todoTone(todo.status) const color = rowColor(t, todo.status) return ( {todoGlyph(todo.status)} {todo.content} ) })} )} ) })