Compare commits

...

5 Commits

Author SHA1 Message Date
vh 82437bd4b9 style(tui): picker highlighted item → Aurora blue (v0.6.4)
Operator request: the agent picker's highlighted selection should
get the brand-color treatment — Aurora blue background — instead of
the v0.6.1 dark-30 muted bg.

## Two-fix landing

**The selector**: v0.6.1's `ListView > ListItem.--highlight` (double
dash) never actually matched. Textual's class is `-highlight` (single
dash). The v0.6.1 "fix" silently fell through to Textual's defaults,
which happened to be invisible because $block-cursor-background was
configured but the selector path didn't reach the rendered widget.

Probed live: `item.classes = frozenset({'-highlight'})`. Selector
corrected, plus dropped the `>` combinator since Textual's internal
DOM puts wrappers between `ListView` and `ListItem`.

**The background**: explicit `#agent-list:focus ListItem.-highlight
{ background: $primary }` — Aurora blue (#6388D8) for the focused-
list highlight band.

**The contrast**: bright-blue id-line text on Aurora-blue background
would be unreadable. Highlighted-state child overrides:

  .agent-id-line  →  $au-bright-white (#cce7ec) + bold
  .agent-desc     →  $au-bright-80    (#b3cbcf)

Non-highlighted items keep their default colors (bright-blue id +
bright-70 desc on App bg).

## Verified live

13 fill rects of `#6388d8` in the picker SVG export (was 0
before this commit). Other Australis brand colors intact:
chrome surface #373b46, dark-50 #6e7882, dark-30 #414751.

## Tests

241/241 GREEN; ruff clean. No test rewrites needed — picker tests
assert structure (widget tree, key bindings), not colors.

Patch bump (v0.6.3 → v0.6.4) — cosmetic; no public-API change.
2026-05-24 18:33:53 -07:00
vh ac690c11d5 style(tui): restore Australis, $background → pure black (v0.6.3)
Reverts v0.6.2's over-correction. The operator clarified: the
complaint was specifically about the APP BACKGROUND going from
black to a shade of blue, not about the cumulative cast across
all Australis dark surfaces. v0.6.2 globally neutralized Ice + Sea
darks → too far.

## v0.6.3 = v0.6.1 palette + $background override only

Restored verbatim from v0.6.1:

  $foreground       #a9bcc3 (Ice white)
  $surface          #373b46 (Sea bright-black, chrome bg)
  $panel            #414751 (Sea dark 30, borders)
  $au-dark-30..60   Australis Sea palette
  $au-bright-70/80  Australis Sea brights
  $au-bright-white  #cce7ec (Ice highlight)
  Aurora accents    bright-blue/cyan/green — verbatim
  Dawn accents      red/yellow — verbatim
  _AU_DEMOTED       #86929d (Sea dark 60)
  _AU_DEMOTED_FAINT #6e7882 (Sea dark 50)

ONE deviation from Australis spec:

  $background  #222531 (Ice black) → #000000 (pure black)

Rationale: Ice black is RGB(34, 37, 49) — blue +44% over red. At
App-wide scale (the dominant fill across the entire screen) the
cumulative cast reads as "the app is blue" even though no single
rect is in the conventional-blue range. Other dark surfaces are
smaller chrome bands where the cool lean reads as character not
background; only $background gets the override.

## What stayed Australis

Every cosmetic element where the operator hasn't pushed back:
identity widget (Aurora bright-blue), pane-name (Aurora bright-cyan),
[done]/[error]/[cancelled] labels (Aurora green / Dawn red/yellow),
focus borders (Aurora blue / accent cyan), demoted telemetry text
(Sea dark-60), placeholder lines (Sea dark-50), Header/Footer chrome
(Sea bright-black bg + Ice white-blue fg), separators (Sea dark-30).

Brand fidelity preserved; only the dominant background surface
neutralized.

## Tests + smoke

241/241 GREEN; ruff clean. Live screenshot export:
- $background = #000000 (230 fill rects — dominant surface)
- $surface = #373b46 (29 fill rects — Australis Sea bright-black)
- Sea panels + dark-50 still present in chrome
- Aurora #6388D8 still primary

Patch bump (v0.6.2 → v0.6.3) — cosmetic refinement; no public-API
change.
2026-05-24 18:20:57 -07:00
vh d845b20efd style(tui): neutralize Australis dark palette (v0.6.2)
Operator-flagged third pass: "overall background for the whole app is
blue." The previous "zero blue rects" investigations missed the
structural cause — Australis's design principle "all colors are
cooler than neutral" bakes a blue cast into every dark surface:

  Ice black    #222531 = RGB(34, 37, 49)   — blue +44% over red
  Sea bright   #373b46 = RGB(55, 59, 70)   — blue +27% over red
  Sea dark-30  #414751 = RGB(65, 71, 81)   — blue +25% over red
  Sea dark-60  #86929d = RGB(134,146,157)  — blue +17% over red

Every chrome surface inherits the lean. The user reading "the whole
app is blue" is correct — the SVG export just rendered hex values
that aren't named "blue" but ARE measurably blue-tinted.

## Fix: keep accents, neutralize darks

Australis brand signature lives in the ACCENTS — Aurora blue, cyan,
green; Dawn red, yellow. Those are unchanged. The Ice/Sea dark
palette is replaced with LAB-matched neutral grays (R=G=B) so the
chrome reads truly neutral:

  $background  #222531 → #1a1a1a   (neutral near-black)
  $surface     #373b46 → #2a2a2a   (neutral dark gray)
  $panel       #414751 → #3a3a3a   (neutral mid gray)
  $foreground  #a9bcc3 → #bdbdbd   (neutral light gray)
  $au-dark-30  #414751 → #3a3a3a
  $au-dark-40  #565f69 → #4f4f4f
  $au-dark-50  #6e7882 → #6b6b6b
  $au-dark-60  #86929d → #878787
  $au-bright-70 #9daeb6 → #9e9e9e
  $au-bright-80 #b3cbcf → #bdbdbd
  $au-bright-white #cce7ec → #e0e0e0

`_AU_DEMOTED` and `_AU_DEMOTED_FAINT` constants (Rich Text styling
for demoted telemetry + placeholders) updated to the neutral
equivalents. The Aurora bright variants (`$au-bright-blue`,
`$au-bright-cyan`, `$au-bright-green`) stay verbatim — those are
where the brand voice lives.

## What this preserves vs sacrifices

**Preserved**:
- Aurora accents: focus borders, active-tab indicator, pane-name
  widget, user-prompt echo all still render in cyan/blue/green.
- Done/Error/Cancelled labels still tinted in Aurora green / Dawn
  red / Dawn yellow.
- Identity widget still Aurora bright-blue.
- The "Australis" theme name + variable slugs ($au-*) — downstream
  CSS rules don't have to change.

**Sacrificed**:
- The "all colors cooler than neutral" Australis design principle.
  Deliberate per-operator-feedback deviation; documented in the
  AUSTRALIS_THEME docstring as a v0.6.2 conscious break with spec.

## Tests + smoke

241/241 GREEN; ruff clean. Live screenshot exports:
- Main app: chrome colors are #1a1a1a / #2a2a2a / #3a3a3a / #6b6b6b
  / #bdbdbd — all neutral grays. Aurora accents preserved as
  textual highlights.
- Agent picker: same — neutral chrome, Aurora accents intact for
  highlighted item border + agent-id-line.

Patch bump (v0.6.1 → v0.6.2): purely cosmetic palette adjustment;
no public-API change.
2026-05-24 18:15:54 -07:00
vh 8463eb22ff docs(contract): amend issue #13 INV-024 for v0.6.1 thinking-current relocation 2026-05-24 15:47:02 -07:00
vh eb93e6d5f0 style(tui): kill remaining blue + thinking-current into pane (v0.6.1)
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.
2026-05-24 15:46:26 -07:00
5 changed files with 93 additions and 25 deletions
+1 -1
View File
@@ -163,7 +163,7 @@ New `Static(id="pane-name")` widget alongside the existing `identity` + `hint` w
- **INV-021** *(new v0.6.0)*: `Text` events do NOT route to `log` per-delta. They accumulate into `TuiPresenterState.text_buffer` and update a single `current_text` Static (docked above the prompt). On terminal event (`Done`/`Error`/`Cancelled`), `current_text` is cleared and (raw mode) accumulated text or (non-raw) post-Done `Markdown(response)` is written to `log`. The pre-v0.6.0 per-token RichLog spam is retired.
- **INV-022** *(new v0.6.0)*: Closed thinking runs route to `thinking_log`, NOT `debug_log`. Each closed run writes three entries: `Rule(title=f"turn N · thinking #K start")`, `Markdown(content)`, `Rule(title=f"turn N · thinking #K end")` — the model's chain-of-thought is presented as rendered Markdown (model reasoning often has lists / code / structure) wrapped in operator-visible start/end markers. `thinking_run_index` increments per-run within a turn.
- **INV-023** *(new v0.6.0)*: Turn-ID header `Rule(title=f"turn N")` is written to all four log panes (`log`, `tools_log`, `debug_log`, `thinking_log`) by `_stream_turn_worker` on the first event of each turn — enables cross-pane visual correlation during multi-turn debugging.
- **INV-024** *(new v0.6.0)*: `thinking-current` Static remains in the right column (above TabbedContent) per INV-017 — live per-delta thinking visibility persists across tab switches. Prefix `thinking… ` self-identifies the widget contents.
- **INV-024** *(amended v0.6.1)*: `thinking-current` Static lives INSIDE the Thinking TabPane (docked bottom, below `thinking-log`) — co-located with closed thinking runs so the operator sees streaming + history in one pane. Pre-v0.6.1 it sat above the TabbedContent (right-column header) which created a top/bottom discontinuity; the co-located shape resolves that. Trade-off: live thinking is now visible only when the Thinking tab is active (Ctrl+3). Prefix `thinking… ` self-identifies the widget contents.
## TESTS (additions / changes to test_tui.py)
+7 -3
View File
@@ -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.4 picker Aurora-blue highlight):_
**Status: v0.6.0 shipped.** Nine core issues complete (`sse_client`
**Status: v0.6.4 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,11 @@ 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.4 style(tui): picker highlighted item → Aurora blue (brand selection treatment)
- `ac690c1` style(tui): restore Australis palette, only $background → pure black (v0.6.3)
- `d845b20` style(tui): neutralize Australis dark palette (v0.6.2, reverted)
- `8463eb2` style(tui): kill remaining blue + thinking-current into pane (v0.6.1)
- `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)
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "ratatoskr"
version = "0.6.0"
version = "0.6.4"
description = "Worldtree Conversation API debug TUI — multi-pane observability dashboard"
readme = "README.md"
requires-python = ">=3.12"
+83 -19
View File
@@ -68,16 +68,25 @@ from ratatoskr.sse_client import (
# for primary surfaces; Dawn accents (red/yellow/magenta) used sparingly for
# terminal-event labels (error/cancelled).
#
# **v0.6.3 single deviation from spec**: `$background` is `#000000` (pure
# black), NOT Australis Ice black `#222531`. The Ice black is RGB(34,37,49)
# — blue dominant — and at App-wide scale the cumulative cast reads as
# "the whole app is blue" to operators (even though no single surface is
# "blue" in the strict-color sense). Pure black for the App background
# kills that perception. EVERY OTHER Australis value — Aurora accents,
# Sea darks for chrome (surface/panel), Ice white foreground, Dawn
# accents — stays verbatim per spec.
#
# Mapping to Textual's Theme semantic tokens:
# primary = Aurora blue (#6388D8) — focus rings, active selection.
# secondary = Aurora cyan (#00b1a8) — secondary highlights.
# accent = Aurora bright cyan (#42dcd1) — bright accents (pane-name, prompt echo).
# accent = Aurora bright cyan (#42dcd1) — bright accents.
# success = Aurora green (#16B866) — [done] label.
# warning = Dawn yellow (#e1c631) — [cancelled] label.
# error = Dawn red (#ff491a) — [error] label.
# foreground = Ice white (#a9bcc3) — default text.
# background = Ice black (#222531) — App background.
# surface = Sea bright black (#373b46) — raised chrome (header/footer/input).
# background = pure black (#000000) — App background (v0.6.3 deviation).
# surface = Sea bright black (#373b46) — raised chrome.
# panel = Sea dark 30 (#414751) — borders, separators.
AUSTRALIS_THEME = Theme(
@@ -89,7 +98,7 @@ AUSTRALIS_THEME = Theme(
warning="#e1c631",
error="#ff491a",
foreground="#a9bcc3",
background="#222531",
background="#000000",
surface="#373b46",
panel="#414751",
dark=True,
@@ -115,9 +124,9 @@ AUSTRALIS_THEME = Theme(
_AU_SUCCESS = "#16B866"
_AU_ERROR = "#ff491a"
_AU_WARNING = "#e1c631"
_AU_USER_ECHO = "#42dcd1" # bright cyan — operator's voice
_AU_DEMOTED = "#86929d" # dark 60 — demoted telemetry (was bare "dim")
_AU_DEMOTED_FAINT = "#6e7882" # dark 50 — empty-state placeholder text
_AU_USER_ECHO = "#42dcd1" # Aurora bright cyan — operator's voice
_AU_DEMOTED = "#86929d" # Sea dark 60 — demoted telemetry
_AU_DEMOTED_FAINT = "#6e7882" # Sea dark 50 — empty-state placeholders
# ---- Issue #12 presenter contract semantics amendment -------------------------
@@ -382,13 +391,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 +424,41 @@ 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.
v0.6.4: lock bg to $background so Textual's auto background-tint on
focus doesn't bleed through unwanted color into the non-highlighted
items. */
#agent-list > ListItem {
height: auto;
padding: 1 1;
background: $background;
}
#agent-list > ListItem.--highlight {
background: $au-dark-30;
/* v0.6.4: highlighted item gets Aurora blue background (Textual's
default $block-cursor-background = $primary). Override only the
text-color descendants so id-line/desc stay readable on blue. The
background itself comes from Textual's default ListItem.-highlight
rule — we removed our previous overriding selectors.
Textual's class is `-highlight` (single dash). Use plain descendant
combinator to bypass internal DOM wrappers. */
#agent-list:focus ListItem.-highlight {
background: $primary;
}
#agent-list:focus ListItem.-highlight .agent-id-line {
color: $au-bright-white;
text-style: bold;
}
#agent-list:focus ListItem.-highlight .agent-desc {
color: $au-bright-80;
}
/* Default (unhighlighted) item text styling. */
.agent-id-line {
color: $au-bright-blue;
text-style: bold;
}
.agent-desc {
color: $au-dark-60;
color: $au-bright-70;
}
"""
@@ -489,16 +533,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 +564,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 +693,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 +706,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")
Generated
+1 -1
View File
@@ -968,7 +968,7 @@ wheels = [
[[package]]
name = "ratatoskr"
version = "0.6.0"
version = "0.6.4"
source = { editable = "." }
dependencies = [
{ name = "httpx" },