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. // 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 // @babel/preset-env etc. would over-transform — the compiler is our only
// transform here. // transform here. babelrc:false stops @babel/cli from walking up the
babelrc: false, // filesystem looking for other configs (the parent repo might add one).
configFile: false babelrc: false
} }

View File

@ -3,6 +3,7 @@ import typescriptEslint from '@typescript-eslint/eslint-plugin'
import typescriptParser from '@typescript-eslint/parser' import typescriptParser from '@typescript-eslint/parser'
import perfectionist from 'eslint-plugin-perfectionist' import perfectionist from 'eslint-plugin-perfectionist'
import reactPlugin from 'eslint-plugin-react' import reactPlugin from 'eslint-plugin-react'
import reactCompiler from 'eslint-plugin-react-compiler'
import hooksPlugin from 'eslint-plugin-react-hooks' import hooksPlugin from 'eslint-plugin-react-hooks'
import unusedImports from 'eslint-plugin-unused-imports' import unusedImports from 'eslint-plugin-unused-imports'
import globals from 'globals' import globals from 'globals'
@ -43,6 +44,7 @@ export default [
'custom-rules': customRules, 'custom-rules': customRules,
perfectionist, perfectionist,
react: reactPlugin, react: reactPlugin,
'react-compiler': reactCompiler,
'react-hooks': hooksPlugin, 'react-hooks': hooksPlugin,
'unused-imports': unusedImports 'unused-imports': unusedImports
}, },
@ -53,6 +55,12 @@ export default [
'@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'off',
'no-undef': 'off', 'no-undef': 'off',
'no-unused-vars': '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': [ 'padding-line-between-statements': [
1, 1,
{ blankLine: 'always', next: ['block-like', 'block', 'return', 'if', 'class', 'continue', 'debugger', 'break', 'multiline-const', 'multiline-let'], prev: '*' }, { 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-constant-condition': 'off',
'no-empty': 'off', 'no-empty': 'off',
'no-redeclare': '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' 'react-hooks/exhaustive-deps': 'off'
} }
}, },

View File

@ -6,7 +6,8 @@
"scripts": { "scripts": {
"dev": "npm run build --prefix packages/hermes-ink && tsx --watch src/entry.tsx", "dev": "npm run build --prefix packages/hermes-ink && tsx --watch src/entry.tsx",
"start": "tsx 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", "type-check": "tsc --noEmit -p tsconfig.json",
"lint": "eslint src/ packages/", "lint": "eslint src/ packages/",
"lint:fix": "eslint src/ packages/ --fix", "lint:fix": "eslint src/ packages/ --fix",

View File

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