Repository navigation
Implement Static component integration for completed messages #11768
Copy link
Copy link
Closed as not planned
Labels
opentuiThis relates to changes in v1.0, now that opencode uses opentuiThis relates to changes in v1.0, now that opencode uses opentui
Description
Parent Epic
Part of #11762 (Ink-based TUI Rewrite)
Objective
Use Ink's <Static> component to render completed messages permanently above the dynamic area. This is the key to solving the streaming + live updates problem.
How Static Works
import { Static, Box, Text } from 'ink'
const App = () => {
const [messages, setMessages] = useState<Message[]>([])
return (
<Box flexDirection="column">
{/* Completed messages - rendered once, never re-rendered */}
<Static items={messages}>
{(message) => (
<Box key={message.id} flexDirection="column">
<UserPrompt text={message.prompt} />
<AssistantResponse
text={message.text}
tools={message.tools}
tasks={message.tasks}
/>
</Box>
)}
</Static>
{/* Dynamic area - updates frequently */}
<LiveSection />
</Box>
)
}Why This Solves Our Problem
- Completed messages are written to stdout ONCE and stay there
- Dynamic area (streaming text, active tools) re-renders below
- No conflicts between "frozen" and "active" content
- Terminal scrollback preserved naturally
Tasks
- Create
src/cli/ink/components/Message.tsx- wrapper for completed message - Create
src/cli/ink/components/UserPrompt.tsx- displays user input - Create
src/cli/ink/components/AssistantResponse.tsx- displays response with tools - Integrate Static in App.tsx
- Handle message completion transition:
- When streaming ends, dispatch
MESSAGE_COMPLETE - Move current streaming state to messages array
- Static picks up new message, renders permanently
- When streaming ends, dispatch
- Test with multiple message exchanges
Component Structure
<Static items={messages}>
<Message>
<UserPrompt>
"project-manager ❯ /start"
</UserPrompt>
<AssistantResponse>
<ProseBlock text="I'll initialize..." />
<ToolList>
<ToolDisplay name="bash" summary="remory search..." status="done" />
<TaskDisplay agent="git-agent" description="Get state" status="done" />
</ToolList>
</AssistantResponse>
</Message>
</Static>
Acceptance Criteria
- Completed messages render permanently
- New messages append below existing
- Dynamic area stays at bottom
- Terminal scrollback works correctly
- No duplicate rendering of completed content
Estimated Effort
1 day
Risk
Medium - Understanding Static's behavior is crucial
Dependencies
- Implement core App component and state management #11764 (App component)
- Implement StreamingProse component with markdown rendering #11765 (StreamingProse)
- Implement ToolDisplay component #11766 (ToolDisplay)
- Implement TaskDisplay component with child tool status #11767 (TaskDisplay)
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
opentuiThis relates to changes in v1.0, now that opencode uses opentuiThis relates to changes in v1.0, now that opencode uses opentui
Activity
randomm commentedon Feb 2, 2026
Research: Ink Static Component
Key Pattern:
Critical Gotchas:
setItems(prev => [...prev, newItem])keypropImplementation Approach:
<Static>, current message belowfix(opencode): fix Ink test infrastructure and add MessageList (anoma…
fix(opencode): fix Ink test infrastructure and add MessageList (anoma…
feat(opencode): add Ink-based TUI components for oclite