From 0d321fca819df422985d2883bd8adedb4ff21634 Mon Sep 17 00:00:00 2001 From: Jason Mulligan Date: Mon, 15 Jun 2026 23:07:14 -0400 Subject: [PATCH] feat: propose TUI redesign change via OpenSpec Create OpenSpec change 'tui-redesign' with complete artifacts: - proposal.md: Motivation, capabilities, impact analysis - design.md: Technical decisions (useReducer, useStreaming hook, file restructuring, panel removal, command registry) - specs/tui-redesign/spec.md: 10 requirements with scenarios covering state management, streaming, file structure, panel removal, command registry, runtime toggles, status bar, message display, scrolling, commands, input lifecycle, edge cases - tasks.md: 15 task groups, 38 implementation steps --- openspec/changes/tui-redesign/.openspec.yaml | 2 + openspec/changes/tui-redesign/design.md | 135 ++++++++++++ openspec/changes/tui-redesign/proposal.md | 29 +++ .../tui-redesign/specs/tui-redesign/spec.md | 197 ++++++++++++++++++ openspec/changes/tui-redesign/tasks.md | 97 +++++++++ 5 files changed, 460 insertions(+) create mode 100644 openspec/changes/tui-redesign/.openspec.yaml create mode 100644 openspec/changes/tui-redesign/design.md create mode 100644 openspec/changes/tui-redesign/proposal.md create mode 100644 openspec/changes/tui-redesign/specs/tui-redesign/spec.md create mode 100644 openspec/changes/tui-redesign/tasks.md diff --git a/openspec/changes/tui-redesign/.openspec.yaml b/openspec/changes/tui-redesign/.openspec.yaml new file mode 100644 index 0000000..a903f7f --- /dev/null +++ b/openspec/changes/tui-redesign/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-06-16 diff --git a/openspec/changes/tui-redesign/design.md b/openspec/changes/tui-redesign/design.md new file mode 100644 index 0000000..2977580 --- /dev/null +++ b/openspec/changes/tui-redesign/design.md @@ -0,0 +1,135 @@ +## Context + +The TUI (`src/tui/`) is the primary user-facing interface for the madz agent. It's built on Ink + ink-scroll-view with a structured pino logger. The current implementation works but has accumulated structural debt: + +- **State sprawl**: Eight independent `useState` calls in `app.js` with no coordination. A single message arrival triggers four separate state updates across four renders. +- **Inline streaming**: The streaming callback is set up inline in `handleChat()` / `handleCommand()` and passed through multiple layers, mixing streaming concerns with command handling. +- **Flat file layout**: 17 files at the root of `src/tui/` — no grouping by concern, making navigation and onboarding difficult. +- **Panel contradiction**: `panels.js`, `skillsPanel.js`, `memoryPanel.js`, `settingsPanel.js` exist despite the blueprint's core tenet: "No panels, no tabs, no switching." +- **Switch-driven commands**: `commandParser.js` uses a dispatch table with switch-case logic — adding commands requires editing the switch, not registering a new handler. + +The TUI uses no external state management library — just React's built-in `useState` and `useRef`. This is sufficient for the current scale but doesn't scale well. + +## Goals / Non-Goals + +**Goals:** +- Consolidate all TUI state into a single `useReducer` with a `TUIState` interface and typed action types +- Extract streaming logic into a dedicated `useStreaming()` hook with clean state transitions +- Restructure `src/tui/` into a concern-based hierarchy (`state/`, `hooks/`, `components/`, `panels/`, `utils/`) +- Remove the panel system entirely — replace with commands that produce output in the conversation stream +- Refactor command parser to an event-driven registry pattern +- Implement bitchx-inspired `/toggle` runtime configuration system +- Add toggle/filter indicators to the status bar + +**Non-Goals:** +- Changing the core philosophy (input is primary, silence is default, etc.) +- Adding new external dependencies +- Changing the streaming pipeline (LangGraph → dispatchProvider → streamingCallback) +- Modifying the session management layer (compaction, trimming, persistence) +- Changing the message bubble rendering (markdown, role colors, tool call display) +- Adding new commands beyond the existing set + `/toggle` + +## Decisions + +### 1. `useReducer` over `useState` + +**Decision**: Consolidate all TUI state into a single `useReducer` with a `TUIState` interface. + +**Rationale**: Eight independent `useState` calls create uncoordinated renders. When a message arrives, `messages`, `statusMessage`, `contextSize`, and `chatHistory` all update separately. A single reducer ensures one render cycle per meaningful state change and provides a clear action taxonomy. + +**Alternatives considered**: +- `zustand` or `jotai` — adds external dependency for what is essentially local component state +- `useReducer` with separate reducers per domain — over-engineered for a single component tree + +### 2. `useStreaming()` hook + +**Decision**: Extract all streaming logic into a dedicated hook. + +**Rationale**: The streaming callback currently lives inline in `handleChat()` / `handleCommand()`, mixing streaming concerns with command dispatch. A hook encapsulates: +- AbortController lifecycle management +- Stream event → state transition transformation +- Auto-continue circuit breaker logic +- A clean `streamingState` object exposed to the UI + +**Alternatives considered**: +- Service class — overkill for what is fundamentally React state management +- Context provider — adds unnecessary indirection for a single-component concern + +### 3. File restructuring by concern + +**Decision**: Group files into `state/`, `hooks/`, `components/`, `panels/`, `utils/`. + +**Rationale**: Predictability. When looking for streaming logic, you know exactly where to look. The current flat layout works for 17 files but doesn't scale. + +**Structure**: +``` +src/tui/ +├── app.js # Root component, providers, reducer +├── state/ # TUIState, actions, reducer, selectors +├── hooks/ # useStreaming, useScroll, useInput, useCommand +├── components/ # ConversationPanel, InputPanel, StatusBar, MessageBubble, Banner +├── panels/ # OnboardingPanel (only panel that remains) +├── utils/ # commandParser, contextTokens, markdownText, format +└── index.js # Entry point +``` + +### 4. Panel removal + +**Decision**: Remove `panels.js`, `skillsPanel.js`, `memoryPanel.js`, `settingsPanel.js`. Replace with commands. + +**Rationale**: The blueprint's philosophy is explicit: "No panels, no tabs, no switching." The panel system contradicts this. Skills and memory are inspected via `/skills` and `/memory` commands that produce output in the conversation stream — the natural TUI paradigm. + +**OnboardingPanel exception**: This is not a "panel" in the navigation sense — it's a one-time first-run flow. It stays in `panels/` as the sole remaining panel. + +### 5. Command registry pattern + +**Decision**: Refactor `commandParser.js` to an event-driven registry. + +**Rationale**: A switch-driven dispatch table requires editing the switch for every new command. A registry where commands are objects with `validate`, `execute`, and `help` properties makes adding commands a registration, not a code edit. + +**Schema**: +```typescript +interface Command { + name: string; + description: string; + usage: string; + validate: (args: string[]) => boolean | string; + execute: (args, state, dispatch, helpers) => Promise | void; +} +``` + +### 6. Runtime toggles in-memory only + +**Decision**: Toggle overrides stored in memory. `config.yaml` `tui` section is the source of truth on restart. + +**Rationale**: Simpler than persisting to a separate file. The blueprint explicitly states: "No separate `tui-config.json` file is needed." + +## Risks / Trade-offs + +| Risk | Mitigation | +|------|-----------| +| Breaking existing tests | All 1129 existing tests must pass. New tests cover all refactored files. | +| Regression in streaming behavior | `useStreaming()` hook is fully unit-testable in isolation. | +| Panel removal breaks user workflows | Panels were unused per the blueprint philosophy. Commands provide equivalent functionality. | +| `useReducer` adds complexity for simple state | The reducer is generated from the existing `useState` calls — the migration is mechanical, not conceptual. | +| File restructuring is a large diff | Each file is moved/created independently. The diff is large but each change is focused. | + +## Migration Plan + +1. **Phase 1**: Create new file structure (`state/`, `hooks/`, `components/`, `utils/`) +2. **Phase 2**: Migrate state from `useState` to `useReducer` in a new `app.js` +3. **Phase 3**: Extract streaming into `useStreaming()` hook +4. **Phase 4**: Move components to `components/` directory +5. **Phase 5**: Refactor command parser to registry pattern +6. **Phase 6**: Implement `/toggle` command and runtime toggle system +7. **Phase 7**: Remove panel files, add `/skills` and `/memory` commands +8. **Phase 8**: Update status bar with toggle indicators +9. **Phase 9**: Run full test suite, fix any regressions + +**Rollback**: The change is entirely within `src/tui/`. If regressions occur, revert the TUI directory and the rest of the system is unaffected. + +## Open Questions + +1. Should the `/toggle` command support `/toggle ` (set to specific value) in addition to `/toggle ` (toggle)? +2. Should format specifiers (`/format system "[%T] %BSystem%n: %I%t%n"`) be implemented, or deferred per the YAGNI note in the blueprint? +3. Should message-level filtering (`/level debug`) be implemented, or deferred per the YAGNI note? diff --git a/openspec/changes/tui-redesign/proposal.md b/openspec/changes/tui-redesign/proposal.md new file mode 100644 index 0000000..8b3b2a7 --- /dev/null +++ b/openspec/changes/tui-redesign/proposal.md @@ -0,0 +1,29 @@ +## Why + +The current TUI works but suffers from structural debt: eight independent `useState` calls with no coordination, inline streaming logic scattered across handlers, a flat 17-file layout that doesn't scale, and panel files that contradict the core philosophy of "no panels, no tabs, no switching." The interface needs a clean architectural reorganization — consolidating state, extracting streaming into its own hook, grouping files by concern, and removing the panel system entirely — while preserving the four core principles that make the TUI feel right: input is primary, silence is the default, batteries included, and the terminal is the window. + +## What Changes + +- **State consolidation**: Replace eight independent `useState` calls with a single `useReducer` backed by a `TUIState` interface and typed action types +- **Streaming extraction**: Extract streaming logic (AbortController lifecycle, event transformation, auto-continue circuit breaker) into a dedicated `useStreaming()` hook +- **File reorganization**: Restructure `src/tui/` from a flat layout into a concern-based hierarchy (`state/`, `hooks/`, `components/`, `panels/`, `utils/`) +- **Panel removal**: Remove `panels.js`, `skillsPanel.js`, `memoryPanel.js`, `settingsPanel.js` — replace with commands (`/skills`, `/memory`) that produce output in the conversation stream +- **Command registry**: Refactor `commandParser.js` from a switch-driven dispatch table to an event-driven command registry with `validate`, `execute`, and `help` properties +- **Runtime toggles**: Implement bitchx-inspired `/toggle` commands for runtime config overrides (autoScroll, timestamps, commandEcho, cursorBreathe, debugOutput) +- **Status bar indicators**: Add toggle/filter status indicators to the status bar for quick glance visibility + +## Capabilities + +### New Capabilities + +- `tui-redesign`: Complete TUI architectural reorganization — state consolidation via `useReducer`, streaming extraction via `useStreaming` hook, file restructuring by concern, panel removal, command registry refactor, and runtime toggle system + +### Modified Capabilities + + + +## Impact + +- **Affected code**: `src/tui/app.js` (state management, streaming logic), `src/tui/commandParser.js` (command dispatch), `src/tui/panels.js` (removal), `src/tui/skillsPanel.js` (removal), `src/tui/memoryPanel.js` (removal), `src/tui/settingsPanel.js` (removal) +- **New files**: `src/tui/state/reducer.js`, `src/tui/state/types.js`, `src/tui/state/selectors.js`, `src/tui/hooks/useStreaming.js`, `src/tui/hooks/useScroll.js`, `src/tui/hooks/useInput.js`, `src/tui/hooks/useCommand.js`, `src/tui/components/ConversationPanel.js`, `src/tui/components/InputPanel.js`, `src/tui/components/StatusBar.js`, `src/tui/components/MessageBubble.js`, `src/tui/components/Banner.js`, `src/tui/utils/format.js` +- **Dependencies**: No new external dependencies — uses existing `ink`, `ink-scroll-view`, `pino`, `marked`, `marked-terminal`, `tiktoken` diff --git a/openspec/changes/tui-redesign/specs/tui-redesign/spec.md b/openspec/changes/tui-redesign/specs/tui-redesign/spec.md new file mode 100644 index 0000000..1c9be4d --- /dev/null +++ b/openspec/changes/tui-redesign/specs/tui-redesign/spec.md @@ -0,0 +1,197 @@ +## ADDED Requirements + +### Requirement: TUI State Management via useReducer +The TUI SHALL consolidate all state into a single `useReducer` with a `TUIState` interface and typed action types. All state updates SHALL flow through the reducer, ensuring one render cycle per meaningful state change. + +#### Scenario: State consolidation +- **WHEN** the TUI initializes +- **THEN** all state (messages, chatHistory, historyIndex, inputText, inputFocused, contextSize, isCompacting, isStreaming, isAutoContinuing, autoContinueCount, scrollOffset, viewportHeight, toggles) is managed by a single `useReducer` + +#### Scenario: Single render per state change +- **WHEN** a message arrives during streaming +- **THEN** `messages`, `statusMessage`, `contextSize`, and `chatHistory` update in a single render cycle via one reducer dispatch + +#### Scenario: Typed action types +- **WHEN** any state transition occurs +- **THEN** the action type is one of the defined `TUIAction` union types (ADD_MESSAGE, UPDATE_MESSAGE, CLEAR_MESSAGES, SET_INPUT_TEXT, SUBMIT_INPUT, SET_STATUS, SET_STREAMING, TOGGLE_CONFIG, etc.) + +### Requirement: Streaming Logic Extracted to useStreaming Hook +The TUI SHALL extract all streaming logic into a dedicated `useStreaming()` hook that manages the AbortController lifecycle, translates stream events into state transitions, handles the auto-continue circuit breaker, and exposes a clean `streamingState` object to the UI. + +#### Scenario: AbortController lifecycle +- **WHEN** a user message is submitted +- **THEN** `useStreaming()` creates an `AbortController` and passes its signal to the dispatch pipeline + +#### Scenario: Stream event transformation +- **WHEN** a stream event arrives (text, reasoning, tool_start, tool_end, tool_error, compaction_start, compaction_end, todo_status) +- **THEN** `useStreaming()` transforms the event into the appropriate state update via `handleStreamEvent()` + +#### Scenario: Auto-continue circuit breaker +- **WHEN** the agent returns zero text output +- **THEN** `useStreaming()` sends a "Please continue." signal, repeating up to `config.agent.autoContinueLimit` (default 1000) times before triggering a circuit breaker error + +#### Scenario: Streaming interrupt +- **WHEN** the user presses Escape during streaming +- **THEN** `useStreaming()` aborts the controller, clears the streaming cursor, and handles cleanup (pops user message, clears partial assistant message, deletes checkpointer thread) + +### Requirement: Concern-Based File Structure +The TUI SHALL restructure `src/tui/` into a concern-based hierarchy with directories for `state/`, `hooks/`, `components/`, `panels/`, and `utils/`. + +#### Scenario: File organization +- **WHEN** the TUI is restructured +- **THEN** files are organized as: `state/reducer.js`, `state/types.js`, `state/selectors.js`, `hooks/useStreaming.js`, `hooks/useScroll.js`, `hooks/useInput.js`, `hooks/useCommand.js`, `components/ConversationPanel.js`, `components/InputPanel.js`, `components/StatusBar.js`, `components/MessageBubble.js`, `components/Banner.js`, `panels/OnboardingPanel.js`, `utils/commandParser.js`, `utils/contextTokens.js`, `utils/markdownText.js`, `utils/format.js` + +#### Scenario: Root component location +- **WHEN** the TUI is loaded +- **THEN** `src/tui/app.js` remains at the root as the root component with providers and reducer, and `src/tui/index.js` remains as the entry point + +### Requirement: Panel System Removal +The TUI SHALL remove `panels.js`, `skillsPanel.js`, `memoryPanel.js`, and `settingsPanel.js`. Skills and memory inspection SHALL be provided via commands (`/skills`, `/memory`) that produce output in the conversation stream. + +#### Scenario: Panel files removed +- **WHEN** the TUI is restructured +- **THEN** `panels.js`, `skillsPanel.js`, `memoryPanel.js`, and `settingsPanel.js` are deleted from `src/tui/` + +#### Scenario: Skills via command +- **WHEN** the user types `/skills` +- **THEN** the TUI displays registered skills as output in the conversation stream + +#### Scenario: Memory via command +- **WHEN** the user types `/memory` +- **THEN** the TUI displays memory context as output in the conversation stream + +#### Scenario: OnboardingPanel preserved +- **WHEN** the TUI initializes with no user profile +- **THEN** `OnboardingPanel` in `panels/` renders the first-run onboarding flow + +### Requirement: Event-Driven Command Registry +The TUI SHALL refactor `commandParser.js` from a switch-driven dispatch table to an event-driven command registry where commands are registered as objects with `validate`, `execute`, and `help` properties. + +#### Scenario: Command registration +- **WHEN** a new command is added +- **THEN** it is registered as a `Command` object with `name`, `description`, `usage`, `validate`, and `execute` properties — no switch case editing required + +#### Scenario: Command validation +- **WHEN** a command is invoked with arguments +- **THEN** the `validate` function is called first; if it returns a string (error message), the error is displayed and execution is skipped + +#### Scenario: Command execution +- **WHEN** a command passes validation +- **THEN** the `execute` function is called with `(args, state, dispatch, helpers)` and can be async + +#### Scenario: Unknown commands +- **WHEN** the user types an unrecognized command +- **THEN** the TUI responds: "Unknown command: /. Type /help for available commands." + +### Requirement: Runtime Toggle System +The TUI SHALL implement bitchx-inspired `/toggle` commands for runtime configuration overrides. Toggles SHALL be stored in memory only; `config.yaml` `tui` section is the source of truth on restart. + +#### Scenario: Toggle command +- **WHEN** the user types `/toggle timestamps` +- **THEN** the timestamps toggle is flipped (on → off, off → on) + +#### Scenario: Toggle list +- **WHEN** the user types `/toggle` with no arguments +- **THEN** the TUI displays all toggles and their current states + +#### Scenario: Toggle persistence +- **WHEN** the TUI restarts +- **THEN** toggle states revert to `config.yaml` `tui` section defaults + +#### Scenario: Toggleable settings +- **WHEN** the TUI initializes +- **THEN** the following toggles are available: `autoScroll` (default: true), `timestamps` (default: true), `commandEcho` (default: true), `cursorBreathe` (default: true), `debugOutput` (default: false) + +### Requirement: Status Bar Toggle Indicators +The TUI SHALL display toggle/filter status indicators in the status bar for quick glance visibility of active runtime features. + +#### Scenario: Status bar indicators +- **WHEN** the status bar renders +- **THEN** it displays toggle states such as `[ts:1 scroll:1]` alongside existing elements (status indicator, skill count, message count, context size) + +### Requirement: Message Display +The TUI SHALL render messages as role-colored bubbles with markdown support, tool call display, and reasoning content. + +#### Scenario: Role-based styling +- **WHEN** a message is rendered +- **THEN** user messages are green/right-aligned, system messages are yellow/left-aligned, assistant messages are cyan/left-aligned + +#### Scenario: Markdown rendering +- **WHEN** an assistant message contains markdown +- **THEN** it is rendered through `marked` + `marked-terminal` with a module-level parse cache + +#### Scenario: Memoization +- **WHEN** a message is unchanged during streaming +- **THEN** `React.memo` with a custom `areEqual` function prevents unnecessary re-renders + +### Requirement: Scrolling & Viewport +The TUI SHALL use `ink-scroll-view`'s `ScrollView` for the conversation area with auto-scroll, keyboard scrolling, and terminal resize handling. + +#### Scenario: Auto-scroll +- **WHEN** a new message arrives and the user is at the bottom +- **THEN** `scrollToBottom()` is called (deferred via setTimeout 0ms) + +#### Scenario: User-initiated scroll +- **WHEN** the user scrolls up +- **THEN** the TUI stays at the user's position (no forced scroll) + +#### Scenario: Terminal resize +- **WHEN** the terminal is resized +- **THEN** `remeasure()` is called on the scroll ref to update viewport dimensions + +#### Scenario: Keyboard scrolling +- **WHEN** the user presses Up/Down/PageUp/PageDown with input unfocused +- **THEN** the conversation area scrolls accordingly + +### Requirement: Command Set +The TUI SHALL support the following commands: + +#### Scenario: Core commands +- **WHEN** the user types `/quit`, `/clear`, `/new`, or `/help` +- **THEN** the TUI executes the corresponding action (exit, clear conversation, new session, show help) + +#### Scenario: Config command +- **WHEN** the user types `/config set ` +- **THEN** the TUI sets the specified config value + +#### Scenario: Provider command +- **WHEN** the user types `/provider set ` +- **THEN** the TUI switches the AI provider + +#### Scenario: Schedule commands +- **WHEN** the user types `/schedule list`, `/schedule pause `, `/schedule resume `, or `/schedule run-now ` +- **THEN** the TUI manages scheduled tasks accordingly + +#### Scenario: GC command +- **WHEN** the user types `/gc` or `/gc status` +- **THEN** the TUI triggers V8 garbage collection or shows GC status + +#### Scenario: Skill execution +- **WHEN** the user types `/skillName [args]` matching a registered skill +- **THEN** the skill body is loaded and streamed to the agent as a prompt + +### Requirement: Input Lifecycle +The TUI SHALL manage the input lifecycle with cursor visibility toggling, command history, and input focus state. + +#### Scenario: Input focus +- **WHEN** the user presses a key +- **THEN** the cursor appears and input is focused + +#### Scenario: Command history +- **WHEN** the user presses Up/Down arrows while at the bottom of the output +- **THEN** the TUI scrolls through command history (not output) + +#### Scenario: Cursor fade +- **WHEN** the user is idle at the input for 2 seconds +- **THEN** the cursor fades to dark gray + +### Requirement: Edge Cases & Resilience +The TUI SHALL handle terminal resize, streaming overflow, connection loss, model stuck in thinking loop, and output retention gracefully. + +#### Scenario: Connection loss +- **WHEN** an error occurs in `dispatchProvider` +- **THEN** a system message is displayed, the streaming message is cleared, and the session is saved + +#### Scenario: Model stuck in thinking loop +- **WHEN** the agent returns zero text output for `config.agent.autoContinueLimit` (default 1000) consecutive times +- **THEN** an error message is shown, the counter resets, and the user must rephrase or start a new session diff --git a/openspec/changes/tui-redesign/tasks.md b/openspec/changes/tui-redesign/tasks.md new file mode 100644 index 0000000..7da14cd --- /dev/null +++ b/openspec/changes/tui-redesign/tasks.md @@ -0,0 +1,97 @@ +## 1. Setup — Directory Structure + +- [ ] 1.1 Create `src/tui/state/`, `src/tui/hooks/`, `src/tui/components/`, `src/tui/panels/`, `src/tui/utils/` directories +- [ ] 1.2 Move existing files to new directories: `commandParser.js` → `utils/`, `contextTokens.js` → `utils/`, `markdownText.js` → `utils/`, `OnboardingPanel.js` → `panels/` + +## 2. State Types & Reducer + +- [ ] 2.1 Create `src/tui/state/types.js` with `TUIState` interface, `TUIAction` union type, and `Message` interface +- [ ] 2.2 Create `src/tui/state/reducer.js` with `initialState` and `tuiReducer` handling all action types +- [ ] 2.3 Create `src/tui/state/selectors.js` with derived state functions (`contextSize`, `statusMessage`, etc.) + +## 3. Streaming Hook + +- [ ] 3.1 Create `src/tui/hooks/useStreaming.js` with AbortController lifecycle management +- [ ] 3.2 Implement `handleStreamEvent()` for all stream event types (text, reasoning, tool_start, tool_end, tool_error, compaction_start, compaction_end, todo_status) +- [ ] 3.3 Implement auto-continue circuit breaker logic with configurable limit +- [ ] 3.4 Implement interrupt handling (abort, clear cursor, cleanup) +- [ ] 3.5 Export `useStreaming()` hook that returns `{ streamingState, handleSubmit, handleInterrupt }` + +## 4. Scroll Hook + +- [ ] 4.1 Create `src/tui/hooks/useScroll.js` with ScrollView ref, resize handling, and keyboard scroll logic + +## 5. Input Hook + +- [ ] 5.1 Create `src/tui/hooks/useInput.js` with keyboard routing (scroll vs history vs input) + +## 6. Command Hook + +- [ ] 6.1 Create `src/tui/hooks/useCommand.js` with command parsing and dispatch + +## 7. Command Registry Refactor + +- [ ] 7.1 Refactor `src/tui/utils/commandParser.js` to event-driven command registry with `Command` interface (`name`, `description`, `usage`, `validate`, `execute`) +- [ ] 7.2 Register all existing commands as registry entries: `/quit`, `/clear`, `/new`, `/help`, `/config`, `/provider`, `/schedule`, `/gc` +- [ ] 7.3 Implement unknown command handler with "Unknown command" message +- [ ] 7.4 Implement skill execution fallback for unrecognized `/command` patterns matching registered skill names + +## 8. Component Extraction + +- [ ] 8.1 Create `src/tui/components/ConversationPanel.js` extracted from `app.js` (ScrollView + MessageBubble[]) +- [ ] 8.2 Create `src/tui/components/InputPanel.js` extracted from `app.js` (text input with cursor) +- [ ] 8.3 Create `src/tui/components/StatusBar.js` extracted from `app.js` (status indicator, metrics, toggle indicators) +- [ ] 8.4 Create `src/tui/components/MessageBubble.js` extracted from `app.js` (role-colored, markdown-rendered, React.memo) +- [ ] 8.5 Create `src/tui/components/Banner.js` extracted from `app.js` (ASCII art banner, dismiss on key press) + +## 9. Runtime Toggle System + +- [ ] 9.1 Add `/toggle` command to command registry for runtime config overrides +- [ ] 9.2 Implement toggle flip logic (`/toggle timestamps` flips on/off) +- [ ] 9.3 Implement toggle list display (`/toggle` with no args shows all toggles and states) +- [ ] 9.4 Wire toggles into `TUIState.toggles` and connect to component rendering (timestamps, autoScroll, commandEcho, cursorBreathe, debugOutput) + +## 10. Panel Removal + +- [ ] 10.1 Delete `src/tui/panels.js` +- [ ] 10.2 Delete `src/tui/skillsPanel.js` +- [ ] 10.3 Delete `src/tui/memoryPanel.js` +- [ ] 10.4 Delete `src/tui/settingsPanel.js` +- [ ] 10.5 Add `/skills` command that displays registered skills in the conversation stream +- [ ] 10.6 Add `/memory` command that displays memory context in the conversation stream + +## 11. Status Bar Enhancement + +- [ ] 11.1 Add toggle/filter indicators to `StatusBar.js` (e.g., `[ts:1 scroll:1]`) +- [ ] 11.2 Wire toggle state changes to status bar re-render + +## 12. Root App Integration + +- [ ] 12.1 Refactor `src/tui/app.js` to use `useReducer` instead of `useState` +- [ ] 12.2 Wire `useStreaming()` hook into `app.js` for chat and command submission +- [ ] 12.3 Wire `useScroll()`, `useInput()`, `useCommand()` hooks into `app.js` +- [ ] 12.4 Replace inline component definitions with imports from `components/` directory +- [ ] 12.5 Ensure cursor visibility toggling works with new architecture + +## 13. Format Utility + +- [ ] 13.1 Create `src/tui/utils/format.js` with format specifier support (`%T`, `%t`, `%B`, `%n`, `%I`, `%R`, `%C`, `%M`) +- [ ] 13.2 Implement `/format` command for runtime format customization (YAGNI — implement only if clear need demonstrated; stub for now) + +## 14. Testing + +- [ ] 14.1 Write unit tests for `reducer.test.js` — all action types, edge cases (empty state, concurrent updates) +- [ ] 14.2 Write unit tests for `commandParser.test.js` — command validation, execution, unknown commands +- [ ] 14.3 Write unit tests for `contextTokens.test.js` — tiktoken calculation, character-count fallback +- [ ] 14.4 Write unit tests for `markdownText.test.js` — markdown rendering, streaming cursor stripping, cache behavior +- [ ] 14.5 Write unit tests for `useStreaming.test.js` — event transformation, auto-continue circuit breaker, abort handling +- [ ] 14.6 Write integration test `full-flow.test.js` — user input → streaming → message display → command execution +- [ ] 14.7 Run full test suite and verify all 1129+ tests pass + +## 15. Cleanup & Verification + +- [ ] 15.1 Verify no lint errors in new files +- [ ] 15.2 Verify all imports resolve correctly in new directory structure +- [ ] 15.3 Verify TUI launches and functions correctly with new architecture +- [ ] 15.4 Verify streaming, interrupt, scroll, and command functionality all work +- [ ] 15.5 Verify panel files are removed and `/skills`/`/memory` commands work as replacements