From 503c1201ff69db2f9c4f2ea0b570a9184a35f69d Mon Sep 17 00:00:00 2001 From: alt-glitch Date: Tue, 9 Jun 2026 05:13:37 +0000 Subject: [PATCH] =?UTF-8?q?opentui(v5b):=20visual=20hierarchy=20=E2=80=94?= =?UTF-8?q?=20color-code=20roles=20+=20clean=20turn=20spacing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The transcript read as one undifferentiated gold blob (user/assistant/tool all the same color). Adopt the Ink model where color IS the hierarchy, in 3 brightness tiers: - USER input → label (gold) — the human's turn stands out. - ASSISTANT answer → text (bright) — the primary content, brightest. - TOOL / REASONING → muted (dim) with an ACCENT glyph (⚡/▶/▼ amber) that marks the block — clearly the secondary 'working area' below the answer. Spacing: one blank line above every turn (was cramped: user had a blank, the reply didn't) + the existing gap:1 between parts. Dropped the transcript's extra marginTop (turns own their spacing now). 85 pass; verified live — gold ask, dim tool, white answer read as three distinct things. --- ui-tui-opentui-v2/src/view/messageLine.tsx | 12 +++++++++--- ui-tui-opentui-v2/src/view/reasoningPart.tsx | 8 ++++---- ui-tui-opentui-v2/src/view/toolPart.tsx | 6 ++++-- ui-tui-opentui-v2/src/view/transcript.tsx | 2 +- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/ui-tui-opentui-v2/src/view/messageLine.tsx b/ui-tui-opentui-v2/src/view/messageLine.tsx index 87aa8654717..8ca6f4d6e9f 100644 --- a/ui-tui-opentui-v2/src/view/messageLine.tsx +++ b/ui-tui-opentui-v2/src/view/messageLine.tsx @@ -22,12 +22,18 @@ export function MessageLine(props: { message: Message }) { const theme = useTheme() const m = () => props.message const glyph = () => (m().role === 'assistant' ? theme().brand.icon : m().role === 'user' ? theme().brand.prompt : '·') + // Role-distinct color IS the hierarchy (Ink model): the human's turn is tinted + // GOLD (label), the agent's answer is BRIGHT (text), system notes are DIM (muted). const glyphFg = () => - m().role === 'assistant' ? theme().color.accent : m().role === 'user' ? theme().color.accent : theme().color.muted + m().role === 'user' ? theme().color.label : m().role === 'assistant' ? theme().color.accent : theme().color.muted + const bodyFg = () => + m().role === 'user' ? theme().color.label : m().role === 'system' ? theme().color.muted : theme().color.text const hasParts = () => (m().parts?.length ?? 0) > 0 return ( - + // One blank line above every turn so user / assistant / tool blocks read as + // distinct turns (item: spacing). The gold-vs-bright color split does the rest. + {/* the role glyph is decorative — exclude it from mouse selection (item 4). Bold so the user `❯` / assistant `⚕` turn boundaries pop (item 8). */} @@ -51,7 +57,7 @@ export function MessageLine(props: { message: Message }) { when={m().streaming && !hasParts()} fallback={ - {m().text} + {m().text} } > diff --git a/ui-tui-opentui-v2/src/view/reasoningPart.tsx b/ui-tui-opentui-v2/src/view/reasoningPart.tsx index 58a3c3f387a..e5ba311ef45 100644 --- a/ui-tui-opentui-v2/src/view/reasoningPart.tsx +++ b/ui-tui-opentui-v2/src/view/reasoningPart.tsx @@ -47,13 +47,13 @@ export function ReasoningPart(props: { text: string; streaming?: boolean }) { > - {expanded() ? '▼' : '▶'} + {expanded() ? '▼' : '▶'} - {/* label color matches tool headers (label, not warn) so reasoning + tool - read as one family of collapsible asides — warn is for warnings (item 8). */} - {label()} + {/* accent chevron marks it; muted label keeps reasoning in the dim, + secondary tier alongside tool calls (Ink hierarchy). */} + {label()} {`: ${summary().title}`} diff --git a/ui-tui-opentui-v2/src/view/toolPart.tsx b/ui-tui-opentui-v2/src/view/toolPart.tsx index cb7da6f4f3d..3292788ffba 100644 --- a/ui-tui-opentui-v2/src/view/toolPart.tsx +++ b/ui-tui-opentui-v2/src/view/toolPart.tsx @@ -86,7 +86,9 @@ export function ToolPart(props: { part: ToolPartState }) { const body = createMemo(() => collapseToolOutput(result(), EXPANDED_MAX, bodyWidth() - 2)) const headGlyph = () => (collapsible() ? (expanded() ? '▼' : '▶') : '⚡') - const headColor = () => (props.part.error ? theme().color.error : theme().color.muted) + // accent glyph MARKS the tool (draws the eye); the rest is muted so tools read + // as the dim, secondary tier below the bright assistant answer (Ink hierarchy). + const headColor = () => (props.part.error ? theme().color.error : theme().color.accent) const subWidth = () => Math.max(1, bodyWidth() - props.part.name.length - 2) return ( @@ -102,7 +104,7 @@ export function ToolPart(props: { part: ToolPartState }) { - {props.part.name} + {props.part.name} diff --git a/ui-tui-opentui-v2/src/view/transcript.tsx b/ui-tui-opentui-v2/src/view/transcript.tsx index fb6e2390adc..631f163e7a1 100644 --- a/ui-tui-opentui-v2/src/view/transcript.tsx +++ b/ui-tui-opentui-v2/src/view/transcript.tsx @@ -25,7 +25,7 @@ import { ScrollAnchorProvider } from './scrollAnchor.tsx' export function Transcript(props: { store: SessionStore }) { const [scroll, setScroll] = createSignal() return ( - + {/* empty-transcript home screen (item 12); replaced by messages on the first turn */}