feat(tui): allow collapsing archived todo panels

This commit is contained in:
Brooklyn Nicholson 2026-04-26 16:15:59 -05:00
parent c78b528125
commit b36007b246
4 changed files with 40 additions and 9 deletions

View File

@ -26,7 +26,7 @@ module.exports = {
],
// We feed already-compiled JS into babel; don't re-parse as TS/JSX.
// @babel/preset-env etc. would over-transform — the compiler is our only
// transform here.
babelrc: false,
configFile: false
// transform here. babelrc:false stops @babel/cli from walking up the
// filesystem looking for other configs (the parent repo might add one).
babelrc: false
}

View File

@ -3,6 +3,7 @@ import typescriptEslint from '@typescript-eslint/eslint-plugin'
import typescriptParser from '@typescript-eslint/parser'
import perfectionist from 'eslint-plugin-perfectionist'
import reactPlugin from 'eslint-plugin-react'
import reactCompiler from 'eslint-plugin-react-compiler'
import hooksPlugin from 'eslint-plugin-react-hooks'
import unusedImports from 'eslint-plugin-unused-imports'
import globals from 'globals'
@ -43,6 +44,7 @@ export default [
'custom-rules': customRules,
perfectionist,
react: reactPlugin,
'react-compiler': reactCompiler,
'react-hooks': hooksPlugin,
'unused-imports': unusedImports
},
@ -53,6 +55,12 @@ export default [
'@typescript-eslint/no-unused-vars': 'off',
'no-undef': 'off',
'no-unused-vars': 'off',
// React Compiler: warn (not error) so the gate doesn't block merges
// while we migrate. Flags patterns that would break the compiler at
// runtime (mutating refs during render, non-PascalCase components,
// etc.). See audit §5 — we run the compiler in `npm run build` as a
// post-pass over tsc's `dist/` output.
'react-compiler/react-compiler': 'warn',
'padding-line-between-statements': [
1,
{ blankLine: 'always', next: ['block-like', 'block', 'return', 'if', 'class', 'continue', 'debugger', 'break', 'multiline-const', 'multiline-let'], prev: '*' },
@ -89,6 +97,9 @@ export default [
'no-constant-condition': 'off',
'no-empty': 'off',
'no-redeclare': 'off',
// Ink internals: reconciler, style pool, DOM node impl — full of
// intentional side effects the compiler rules reject.
'react-compiler/react-compiler': 'off',
'react-hooks/exhaustive-deps': 'off'
}
},

View File

@ -6,7 +6,8 @@
"scripts": {
"dev": "npm run build --prefix packages/hermes-ink && tsx --watch src/entry.tsx",
"start": "tsx src/entry.tsx",
"build": "npm run build --prefix packages/hermes-ink && tsc -p tsconfig.build.json && chmod +x dist/entry.js",
"build": "npm run build --prefix packages/hermes-ink && tsc -p tsconfig.build.json && npm run build:compile && chmod +x dist/entry.js",
"build:compile": "babel dist --out-dir dist --config-file ./babel.compiler.config.cjs --extensions .js --keep-file-extension",
"type-check": "tsc --noEmit -p tsconfig.json",
"lint": "eslint src/ packages/",
"lint:fix": "eslint src/ packages/ --fix",

View File

@ -1,5 +1,5 @@
import { Box, Text } from '@hermes/ink'
import { memo } from 'react'
import { memo, useState } from 'react'
import { countPendingTodos } from '../lib/liveProgress.js'
import { todoGlyph, todoTone } from '../lib/todo.js'
@ -13,7 +13,7 @@ const rowColor = (t: Theme, status: TodoItem['status']) => {
}
export const TodoPanel = memo(function TodoPanel({
collapsed = false,
collapsed,
incomplete = false,
onToggle,
t,
@ -25,6 +25,25 @@ export const TodoPanel = memo(function TodoPanel({
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
}
@ -34,9 +53,9 @@ export const TodoPanel = memo(function TodoPanel({
return (
<Box flexDirection="column" marginBottom={1}>
<Box onClick={onToggle}>
<Box onClick={handleToggle}>
<Text color={t.color.dim}>
<Text color={t.color.amber}>{collapsed ? '▸ ' : '▾ '}</Text>
<Text color={t.color.amber}>{effectiveCollapsed ? '▸ ' : '▾ '}</Text>
<Text bold color={t.color.cornsilk}>
Todo
</Text>{' '}
@ -52,7 +71,7 @@ export const TodoPanel = memo(function TodoPanel({
</Text>
</Box>
{!collapsed && (
{!effectiveCollapsed && (
<Box flexDirection="column" marginLeft={2}>
{todos.map(todo => {
const tone = todoTone(todo.status)