fix(tui): pin todo panel above live output
This commit is contained in:
@@ -30,6 +30,8 @@ const TranscriptPane = memo(function TranscriptPane({
|
|||||||
<>
|
<>
|
||||||
<ScrollBox flexDirection="column" flexGrow={1} flexShrink={1} ref={transcript.scrollRef} stickyScroll>
|
<ScrollBox flexDirection="column" flexGrow={1} flexShrink={1} ref={transcript.scrollRef} stickyScroll>
|
||||||
<Box flexDirection="column" paddingX={1}>
|
<Box flexDirection="column" paddingX={1}>
|
||||||
|
<LiveTodoPanel />
|
||||||
|
|
||||||
{transcript.virtualHistory.topSpacer > 0 ? <Box height={transcript.virtualHistory.topSpacer} /> : null}
|
{transcript.virtualHistory.topSpacer > 0 ? <Box height={transcript.virtualHistory.topSpacer} /> : null}
|
||||||
|
|
||||||
{transcript.virtualRows.slice(transcript.virtualHistory.start, transcript.virtualHistory.end).map(row => (
|
{transcript.virtualRows.slice(transcript.virtualHistory.start, transcript.virtualHistory.end).map(row => (
|
||||||
@@ -58,8 +60,6 @@ const TranscriptPane = memo(function TranscriptPane({
|
|||||||
|
|
||||||
{transcript.virtualHistory.bottomSpacer > 0 ? <Box height={transcript.virtualHistory.bottomSpacer} /> : null}
|
{transcript.virtualHistory.bottomSpacer > 0 ? <Box height={transcript.virtualHistory.bottomSpacer} /> : null}
|
||||||
|
|
||||||
<LiveTodoPanel />
|
|
||||||
|
|
||||||
<StreamingAssistant
|
<StreamingAssistant
|
||||||
cols={composer.cols}
|
cols={composer.cols}
|
||||||
compact={ui.compact}
|
compact={ui.compact}
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
import { Box, Text } from '@hermes/ink'
|
import { Box, Text } from '@hermes/ink'
|
||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
|
|
||||||
import { todoGlyph } from '../lib/todo.js'
|
import { todoGlyph, todoTone } from '../lib/todo.js'
|
||||||
import type { Theme } from '../theme.js'
|
import type { Theme } from '../theme.js'
|
||||||
import type { TodoItem } from '../types.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({ t, todos }: { t: Theme; todos: TodoItem[] }) {
|
export const TodoPanel = memo(function TodoPanel({ t, todos }: { t: Theme; todos: TodoItem[] }) {
|
||||||
if (!todos.length) {
|
if (!todos.length) {
|
||||||
return null
|
return null
|
||||||
@@ -23,19 +29,12 @@ export const TodoPanel = memo(function TodoPanel({ t, todos }: { t: Theme; todos
|
|||||||
</Text>
|
</Text>
|
||||||
<Box flexDirection="column" marginLeft={2}>
|
<Box flexDirection="column" marginLeft={2}>
|
||||||
{todos.map(todo => {
|
{todos.map(todo => {
|
||||||
const done = todo.status === 'completed'
|
const tone = todoTone(todo.status)
|
||||||
const cancel = todo.status === 'cancelled'
|
const color = rowColor(t, todo.status)
|
||||||
const active = todo.status === 'in_progress'
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text color={color} dim={tone === 'dim'} key={todo.id}>
|
||||||
color={done || cancel ? t.color.dim : active ? t.color.cornsilk : t.color.statusFg}
|
<Text color={color}>{todoGlyph(todo.status)} </Text>
|
||||||
dim={done || cancel}
|
|
||||||
key={todo.id}
|
|
||||||
>
|
|
||||||
<Text color={active ? t.color.amber : done ? t.color.ok : cancel ? t.color.error : t.color.dim}>
|
|
||||||
{todoGlyph(todo.status)}{' '}
|
|
||||||
</Text>
|
|
||||||
{todo.content}
|
{todo.content}
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
|
|||||||
9
ui-tui/src/lib/liveLayout.test.ts
Normal file
9
ui-tui/src/lib/liveLayout.test.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import { liveTailOrder } from './liveLayout.js'
|
||||||
|
|
||||||
|
describe('liveTailOrder', () => {
|
||||||
|
it('keeps todo before transcript and assistant live output', () => {
|
||||||
|
expect(liveTailOrder()).toEqual(['todo', 'history', 'assistant'])
|
||||||
|
})
|
||||||
|
})
|
||||||
1
ui-tui/src/lib/liveLayout.ts
Normal file
1
ui-tui/src/lib/liveLayout.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const liveTailOrder = () => ['todo', 'history', 'assistant'] as const
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
import { todoGlyph } from './todo.js'
|
import { todoGlyph, todoTone } from './todo.js'
|
||||||
|
|
||||||
describe('todoGlyph', () => {
|
describe('todoGlyph', () => {
|
||||||
it('uses fixed-width ASCII markers so the active row does not render wide or emoji-like', () => {
|
it('uses fixed-width ASCII markers so the active row does not render wide or emoji-like', () => {
|
||||||
@@ -10,3 +10,12 @@ describe('todoGlyph', () => {
|
|||||||
expect(todoGlyph('cancelled')).toBe('[-]')
|
expect(todoGlyph('cancelled')).toBe('[-]')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('todoTone', () => {
|
||||||
|
it('keeps todo status rows neutral instead of red/green', () => {
|
||||||
|
expect(todoTone('completed')).toBe('dim')
|
||||||
|
expect(todoTone('cancelled')).toBe('dim')
|
||||||
|
expect(todoTone('pending')).toBe('body')
|
||||||
|
expect(todoTone('in_progress')).toBe('active')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import type { TodoItem } from '../types.js'
|
import type { TodoItem } from '../types.js'
|
||||||
|
|
||||||
|
export type TodoTone = 'active' | 'body' | 'dim'
|
||||||
|
|
||||||
export const todoGlyph = (status: TodoItem['status']) =>
|
export const todoGlyph = (status: TodoItem['status']) =>
|
||||||
status === 'completed' ? '[x]' : status === 'cancelled' ? '[-]' : status === 'in_progress' ? '[>]' : '[ ]'
|
status === 'completed' ? '[x]' : status === 'cancelled' ? '[-]' : status === 'in_progress' ? '[>]' : '[ ]'
|
||||||
|
|
||||||
|
export const todoTone = (status: TodoItem['status']): TodoTone =>
|
||||||
|
status === 'in_progress' ? 'active' : status === 'pending' ? 'body' : 'dim'
|
||||||
|
|||||||
Reference in New Issue
Block a user