From eb93e6d5f0d5f012887b79b5b6ae119aae8d8cfe Mon Sep 17 00:00:00 2001 From: Vuong Hoang Date: Sun, 24 May 2026 15:46:26 -0700 Subject: [PATCH] style(tui): kill remaining blue + thinking-current into pane (v0.6.1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three operator-flagged issues: ## 1. "Background is still blue" — Header sub-widgets + scrollbar Two surviving blue sources after v0.6.0: - **Header sub-widgets** (HeaderIcon, HeaderTitle, HeaderClock) each carry their own `$primary` tint that the parent `Header { background }` rule alone doesn't override. Sub-selectors added: `Header, HeaderIcon, HeaderTitle, HeaderClock { background: $surface; color: $au-bright-blue; }`. - **Scrollbar gutter** uses Textual's `$primary-tint` (#32436a) by default. Per-widget scrollbar overrides: `ListView` (picker) and `RichLog` (every pane) get explicit Sea darks for gutter + thumb. Live verification: both AgentPickerApp and RatatoskrApp now render ZERO instances of `#6388d8` (Aurora blue) or `#32436a` (its dark derivative) in the export-screenshot SVG. ## 2. "Picker is bright cyan with unreadable text" — ListView focus Textual's default `ListView:focus > ListItem.--highlight { background: $primary }` was overriding my v0.6.0 `#agent-list > ListItem.--highlight { background: $au-dark-30 }` because `:focus` carries higher specificity. The highlighted item was rendering with Aurora-blue background + bright-blue text = unreadable. Fix: both selectors targeted explicitly with sufficient specificity: `ListView > ListItem.--highlight, ListView:focus > ListItem.--highlight { background: $au-dark-30 }`. Description text bumped to Sea bright-70 for better contrast against the dark-30 highlight. ## 3. "Streaming everywhere, should just stream in line" User flagged the disconnect: live thinking rendered above the TabbedContent in the right-column header, then on closure the content "moved" to thinking-log inside the Thinking pane. Read as jarring discontinuity. Fix: `thinking-current` Static moved INTO the Thinking TabPane (docked bottom), below `thinking-log`. Both surfaces co-located now — live streaming + closed runs share the same pane. Operator switches to Ctrl+3 (Thinking) to see chronological closed runs ABOVE + live streaming line BELOW. Same pattern as the transcript: closed history + inline streaming tail. Trade-off: live thinking is now visible only when on the Thinking tab. Pre-v0.6.1 it was always visible above the tabs. The user explicitly prefers the co-located shape; this is the right call. ## Contract amendment docs/contracts/issues/13.contract.md INV-024 amended: thinking-current now docks bottom of the Thinking TabPane (was right-column header). v0.6.0 layout-spec snapshot updated to reflect the new shape. Drift- check clean. ## Tests + smoke 241/241 GREEN; ruff clean. Live smoke against personal Worldtree confirmed: - thinking-current AND thinking-log both inside thinking-tab.walk_children(). - Post-Done state: 23 closed-run lines in thinking-log, thinking-current cleared to empty. - Picker exports zero blue rects; main App exports zero blue rects. Patch bump (v0.6.0 → v0.6.1): purely cosmetic + layout adjustment within the existing pane structure; no public-API change. --- persistent-memory.md | 7 +++-- pyproject.toml | 2 +- src/ratatoskr/tui.py | 65 ++++++++++++++++++++++++++++++++++++-------- uv.lock | 2 +- 4 files changed, 60 insertions(+), 16 deletions(-) diff --git a/persistent-memory.md b/persistent-memory.md index 21dbeeb..5d7c5b9 100644 --- a/persistent-memory.md +++ b/persistent-memory.md @@ -32,9 +32,9 @@ separate dev team rather than an in-tree Worldtree tool. ## Current state / in-flight -_As of 2026-05-24 (post-v0.6.0 streaming + Thinking pane + turn headers):_ +_As of 2026-05-24 (post-v0.6.1 picker contrast + thinking inline):_ -**Status: v0.6.0 shipped.** Nine core issues complete (`sse_client` +**Status: v0.6.1 shipped.** Nine core issues complete (`sse_client` #1, `sessions` #2, `cli` #3, `tui` #4, `--end-user-id` #5, TUI startup error visibility #6, presenter contract semantics amendment #12, startup agent picker #8, §5 layout reshape + Tools pane #13) @@ -51,7 +51,8 @@ Static in the footer (static "Tools" v1; dynamic when more tabs land). CLI mode (--send) unaffected by design — INV-018. Last commits on `main`: -- v0.6.0 refactor(tui): streaming Static + turn headers + Thinking pane + agent picker multi-line +- v0.6.1 style(tui): kill remaining blue + thinking-current into Thinking pane +- `cfee89a` refactor(tui): streaming + turn headers + Thinking pane (v0.6.0) - `7106af5` style(tui): UI polish pass — terminal label colors, placeholders (v0.5.1) - `ffd22fb` refactor(tui): content-only main pane + Debug tab + chrome dark (v0.5.0) - `2756f5f` style(tui): apply Australis theme to TUI chrome + widgets (v0.4.1) diff --git a/pyproject.toml b/pyproject.toml index 7fc8005..c5526d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "ratatoskr" -version = "0.6.0" +version = "0.6.1" description = "Worldtree Conversation API debug TUI — multi-pane observability dashboard" readme = "README.md" requires-python = ">=3.12" diff --git a/src/ratatoskr/tui.py b/src/ratatoskr/tui.py index e3fbfd8..6d12269 100644 --- a/src/ratatoskr/tui.py +++ b/src/ratatoskr/tui.py @@ -382,13 +382,28 @@ class AgentPickerApp(App[str | None]): """ DEFAULT_CSS = """ - Header { + /* v0.6.1: kill Textual's $primary-blue tints everywhere — Header sub- + widgets (HeaderIcon etc.) have their own $primary tinting that the + parent `Header { background: $surface }` rule alone doesn't cover. + Sub-selectors force the cool palette down to every level. */ + Header, HeaderIcon, HeaderTitle, HeaderClock { background: $surface; color: $au-bright-blue; } Footer { background: $surface; } + /* v0.6.1: scrollbar uses Textual's $primary-tint by default. Force + Australis Sea darks so the scrollbar gutter doesn't read as a blue + strip. Applied to ListView (the scrollable widget here). */ + ListView { + scrollbar-background: $background; + scrollbar-background-hover: $background; + scrollbar-background-active: $background; + scrollbar-color: $au-dark-50; + scrollbar-color-hover: $au-dark-60; + scrollbar-color-active: $au-bright-cyan; + } #picker-prompt { dock: top; height: 1; @@ -400,21 +415,29 @@ class AgentPickerApp(App[str | None]): height: 1fr; background: $background; } - /* v0.6.0: multi-line agent items. Each ListItem is auto-height so the - full description wraps below the agent_id/name line — no truncation. */ + /* Multi-line agent items. Each ListItem is auto-height so the full + description wraps below the agent_id/name line — no truncation. */ #agent-list > ListItem { height: auto; padding: 1 1; + background: $background; } - #agent-list > ListItem.--highlight { + /* v0.6.1: override Textual's default ListView:focus highlight, which + defaults to $primary (Aurora blue) and made the picker unreadable. + Both selectors needed — focused state has higher specificity in + Textual's defaults. */ + ListView > ListItem.--highlight, + ListView:focus > ListItem.--highlight { background: $au-dark-30; } + /* Children of highlighted items keep their colors — the dark-30 bg + provides enough contrast for bright-blue id + dark-60 desc text. */ .agent-id-line { color: $au-bright-blue; text-style: bold; } .agent-desc { - color: $au-dark-60; + color: $au-bright-70; } """ @@ -489,16 +512,27 @@ class RatatoskrApp(App[int]): # Australis theme variables ($primary/$accent/$au-dark-60/$au-bright-cyan/ # etc.) carry colors so a future theme swap rebinds centrally. DEFAULT_CSS = """ - /* Kill Textual's default $primary-blue tinting on chrome — Header, - Footer, ContentTabs strip, active-tab Underline all forced to the - Australis cool-dark palette. */ - Header { + /* v0.6.1: kill Textual's default $primary-blue tinting on chrome — + Header sub-widgets (HeaderIcon, HeaderTitle, HeaderClock) each carry + their own $primary tint that the parent `Header { background }` rule + doesn't override; sub-selectors force the cool palette down. */ + Header, HeaderIcon, HeaderTitle, HeaderClock { background: $surface; color: $au-bright-blue; } Footer { background: $surface; } + /* v0.6.1: scrollbars default to $primary-tint blue. Force Sea darks + on the scrollable widgets (RichLog instances). */ + RichLog { + scrollbar-background: $background; + scrollbar-background-hover: $background; + scrollbar-background-active: $background; + scrollbar-color: $au-dark-50; + scrollbar-color-hover: $au-dark-60; + scrollbar-color-active: $au-bright-cyan; + } #main-row { height: 1fr; } @@ -509,12 +543,17 @@ class RatatoskrApp(App[int]): #right-column { width: 1fr; } + /* v0.6.1: thinking-current Static moved INTO the Thinking pane (below + thinking-log) so streaming + closed runs co-locate. Docked bottom of + its TabPane so it acts as the live "tail" of the chronological log + above. Empty (height:0) when no thinking is active. */ #thinking-current { - dock: top; + dock: bottom; height: auto; color: $au-dark-60; padding: 0 1; text-style: italic; + background: $background; } #transcript { height: 1fr; @@ -633,7 +672,6 @@ class RatatoskrApp(App[int]): yield Static("", id="current-text") yield Input(id="prompt", placeholder="Type a message and press Enter") with Vertical(id="right-column"): - yield Static("", id="thinking-current") with TabbedContent(id="side-panes"): with TabPane("Tools", id="tools-tab"): yield RichLog( @@ -647,6 +685,11 @@ class RatatoskrApp(App[int]): yield RichLog( id="thinking-log", wrap=True, markup=False, highlight=False ) + # v0.6.1: live thinking lives INSIDE the Thinking + # pane (docked bottom) — co-located with the closed + # runs in thinking-log above. No more top/bottom + # discontinuity across the right column. + yield Static("", id="thinking-current") # INV-002 + INV-003: visible identity + hint widgets (Footer-area). # pane-name widget displays current side-pane name. yield Static("", id="identity") diff --git a/uv.lock b/uv.lock index 9622592..77656d6 100644 --- a/uv.lock +++ b/uv.lock @@ -968,7 +968,7 @@ wheels = [ [[package]] name = "ratatoskr" -version = "0.6.0" +version = "0.6.1" source = { editable = "." } dependencies = [ { name = "httpx" },