Compare commits

..

1 Commits

Author SHA1 Message Date
vh 2756f5f1dd style(tui): apply Australis theme to TUI chrome + widgets (v0.4.1)
Retheme the Textual TUI under the Australis Dark colorscheme
(github.com/lkraven/australis) — Aurora blue/cyan/green primary,
Ice neutrals (#222531 bg, #a9bcc3 fg, #cce7ec highlight), Sea
darks for chrome separators, Dawn accents reserved for terminal-
event labels (red/yellow). 16-color cool-tone palette with medium
contrast.

Mechanism: Textual `Theme` API. AUSTRALIS_THEME defined at module
scope mapped to semantic tokens (primary/secondary/accent/success/
warning/error/foreground/background/surface/panel) plus a Sea
variables block (`au-dark-30..60`, `au-bright-70/80/white`, plus
Aurora bright variants). Both `RatatoskrApp` and `AgentPickerApp`
register the theme in `__init__` and set `self.theme = "australis"`.

Per-widget styling via DEFAULT_CSS theme variables (no hex
sprinkled in CSS):

- `#identity` → `$au-bright-blue` (Aurora bright-blue).
- `#pane-name` → `$au-bright-cyan` (current-pane indicator).
- `#hint` → `$au-dark-60` (subtle Ctrl-C state line).
- `#prompt` border → `$panel` unfocused / `$primary` focused
  (focus highlight in Aurora blue).
- `#left-column` gets a `border-right: solid $panel` separator.
- `#thinking-current` color → `$au-dark-60` (matches demoted style).
- `#transcript` + `#tools-log` background → `$background`.
- Agent picker's highlighted ListItem → `$primary` bg +
  `$au-bright-white` fg.

Rich Text styling (where Textual's theme system doesn't apply):

- `_dim()` helper for demoted telemetry now uses explicit
  `#86929d` (Sea dark-60) instead of the terminal-dim filter
  `"dim"`. Renders consistently across emulators and stays
  anchored to the brand palette.
- User-prompt echo (`❯ <content>` in transcript) wrapped in
  `RichText` styled with `#42dcd1` (Aurora bright-cyan) — calls
  out the operator's voice in the primary palette.

CLI mode (`--send`) is unaffected by design — raw stdout has no
theme concept. The retheme is TUI-only.

236/236 tests GREEN; ruff clean. Live smoke against personal
Worldtree: `app.theme == "australis"`, all widget colors resolve
to expected Australis hex values (identity → `#a4c4ff`,
pane-name → `#42dcd1`, hint → `#86929d`).

One existing test adjusted: `test_worker_phase_demoted` previously
asserted `style == "dim"`; now asserts non-empty style (the demoted
style is now an explicit Australis hex, not the terminal "dim"
sentinel). Behavior-equivalent — the demotion intent is preserved.

Patch bump (v0.4.0 → v0.4.1) per SemVer etiquette: cosmetic
refinement of just-shipped surface, no public-API change.
2026-05-24 13:49:36 -07:00
5 changed files with 113 additions and 11 deletions
+4 -4
View File
@@ -32,10 +32,9 @@ separate dev team rather than an in-tree Worldtree tool.
## Current state / in-flight ## Current state / in-flight
_As of 2026-05-24 (post-v0.4.0 §5 entry point: layout reshape + _As of 2026-05-24 (post-v0.4.1 Australis theme retheme):_
Tools pane):_
**Status: v0.4.0 shipped.** Nine core issues complete (`sse_client` **Status: v0.4.1 shipped.** Nine core issues complete (`sse_client`
#1, `sessions` #2, `cli` #3, `tui` #4, `--end-user-id` #5, TUI #1, `sessions` #2, `cli` #3, `tui` #4, `--end-user-id` #5, TUI
startup error visibility #6, presenter contract semantics amendment startup error visibility #6, presenter contract semantics amendment
#12, startup agent picker #8, §5 layout reshape + Tools pane #13) #12, startup agent picker #8, §5 layout reshape + Tools pane #13)
@@ -52,7 +51,8 @@ Static in the footer (static "Tools" v1; dynamic when more tabs
land). CLI mode (--send) unaffected by design — INV-018. land). CLI mode (--send) unaffected by design — INV-018.
Last commits on `main`: Last commits on `main`:
- v0.4.0 feat(tui): issue #13 — §5 layout reshape + Tools pane - v0.4.1 style(tui): apply Australis theme to TUI chrome + widgets
- `24e4371` feat(tui): issue #13 — §5 layout reshape + Tools pane (v0.4.0)
- `d30be12` feat(sessions,cli,tui): issue #8 — startup agent picker (v0.3.0) - `d30be12` feat(sessions,cli,tui): issue #8 — startup agent picker (v0.3.0)
- `c85f6bd` fix(tui): anchor layout via dock so Input never moves (v0.2.1) - `c85f6bd` fix(tui): anchor layout via dock so Input never moves (v0.2.1)
- `3b9c610` feat(cli,tui): issue #12 — presenter contract semantics amendment (v0.2.0) - `3b9c610` feat(cli,tui): issue #12 — presenter contract semantics amendment (v0.2.0)
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "ratatoskr" name = "ratatoskr"
version = "0.4.0" version = "0.4.1"
description = "Worldtree Conversation API debug TUI — multi-pane observability dashboard" description = "Worldtree Conversation API debug TUI — multi-pane observability dashboard"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
+103 -3
View File
@@ -17,6 +17,7 @@ import httpx
from textual.app import App, ComposeResult from textual.app import App, ComposeResult
from textual.binding import Binding from textual.binding import Binding
from textual.containers import Horizontal, Vertical from textual.containers import Horizontal, Vertical
from textual.theme import Theme
from textual.widgets import ( from textual.widgets import (
Footer, Footer,
Header, Header,
@@ -61,6 +62,64 @@ from ratatoskr.sse_client import (
stream_turn, stream_turn,
) )
# ---- Australis theme (https://github.com/lkraven/australis) ------------------
#
# The Australis Dark color theme, inspired by the Southern Lights. 16 cool-tone
# terminal colors with medium contrast. Preference order: blue > cyan > green
# for primary surfaces; Dawn accents (red/yellow/magenta) used sparingly for
# terminal-event labels (error/cancelled).
#
# 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).
# 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).
# panel = Sea dark 30 (#414751) — borders, separators.
AUSTRALIS_THEME = Theme(
name="australis",
primary="#6388D8",
secondary="#00b1a8",
accent="#42dcd1",
success="#16B866",
warning="#e1c631",
error="#ff491a",
foreground="#a9bcc3",
background="#222531",
surface="#373b46",
panel="#414751",
dark=True,
variables={
# Sea contrast palette — usable via $au-dark-50 etc. in TCSS.
"au-dark-30": "#414751",
"au-dark-40": "#565f69",
"au-dark-50": "#6e7882",
"au-dark-60": "#86929d",
"au-bright-70": "#9daeb6",
"au-bright-80": "#b3cbcf",
"au-bright-white": "#cce7ec",
"au-bright-blue": "#a4c4ff",
"au-bright-cyan": "#42dcd1",
"au-bright-green": "#51e08a",
},
)
# Direct hex constants for Rich Text styling (Done/Error/Cancelled labels +
# transcript user-prompt echo). Themes set TCSS variables; Rich's RichText
# style strings live outside the theme system, so we resolve to hex here.
_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")
# ---- Issue #12 presenter contract semantics amendment ------------------------- # ---- Issue #12 presenter contract semantics amendment -------------------------
# #
# TuiPresenterState replaces the stateless _render_event_to_log with a stateful # TuiPresenterState replaces the stateless _render_event_to_log with a stateful
@@ -150,8 +209,14 @@ class TuiPresenterState:
from rich.text import Text as RichText from rich.text import Text as RichText
def _dim(s: str) -> RichText: def _dim(s: str) -> RichText:
"""Wrap a demoted-telemetry line in dim style for the RichLog.""" """Wrap a demoted-telemetry line in Australis dark-60 grey.
return RichText(s, style="dim")
v0.4.1 retheme: was `style="dim"` (terminal-dim filter, varies by
emulator); now explicit Australis Sea dark-60 (#86929d) so the
shade renders consistently across terminals and stays anchored to
the brand palette.
"""
return RichText(s, style=_AU_DEMOTED)
try: try:
# Thinking events: accumulate into buffer, update widget per delta. # Thinking events: accumulate into buffer, update widget per delta.
@@ -257,9 +322,16 @@ class AgentPickerApp(App[str | None]):
dock: top; dock: top;
height: 1; height: 1;
padding: 0 1; padding: 0 1;
color: $au-bright-cyan;
background: $surface;
} }
#agent-list { #agent-list {
height: 1fr; height: 1fr;
background: $background;
}
#agent-list > ListItem.--highlight {
background: $primary;
color: $au-bright-white;
} }
""" """
@@ -276,6 +348,8 @@ class AgentPickerApp(App[str | None]):
# [no_agents] before constructing the picker. # [no_agents] before constructing the picker.
assert agents assert agents
self.agents = agents self.agents = agents
self.register_theme(AUSTRALIS_THEME)
self.theme = "australis"
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield Header() yield Header()
@@ -314,12 +388,18 @@ class RatatoskrApp(App[int]):
# in the left column doesn't reflow the right column's TabbedContent. # in the left column doesn't reflow the right column's TabbedContent.
# The v0.2.1 layout-stability property is preserved within the left # The v0.2.1 layout-stability property is preserved within the left
# column by docking thinking-current top + prompt bottom of that column. # column by docking thinking-current top + prompt bottom of that column.
#
# v0.4.1: Australis theme — Aurora blue/cyan primary, Ice background,
# Sea darks for chrome separators. Widget colors use theme variables
# ($primary/$accent/$au-dark-60/$au-bright-cyan/etc.) so a future theme
# swap rebinds them centrally.
DEFAULT_CSS = """ DEFAULT_CSS = """
#main-row { #main-row {
height: 1fr; height: 1fr;
} }
#left-column { #left-column {
width: 2fr; width: 2fr;
border-right: solid $panel;
} }
#right-column { #right-column {
width: 1fr; width: 1fr;
@@ -327,24 +407,39 @@ class RatatoskrApp(App[int]):
#thinking-current { #thinking-current {
dock: top; dock: top;
height: auto; height: auto;
color: $au-dark-60;
} }
#transcript { #transcript {
height: 1fr; height: 1fr;
background: $background;
}
#tools-log {
background: $background;
} }
#prompt { #prompt {
dock: bottom; dock: bottom;
border: tall $panel;
}
#prompt:focus {
border: tall $primary;
} }
#identity { #identity {
dock: bottom; dock: bottom;
height: 1; height: 1;
color: $au-bright-blue;
padding: 0 1;
} }
#pane-name { #pane-name {
dock: bottom; dock: bottom;
height: 1; height: 1;
color: $au-bright-cyan;
padding: 0 1;
} }
#hint { #hint {
dock: bottom; dock: bottom;
height: 1; height: 1;
color: $au-dark-60;
padding: 0 1;
} }
""" """
@@ -378,6 +473,8 @@ class RatatoskrApp(App[int]):
self.active_turn_id: int | None = None self.active_turn_id: int | None = None
self.stream_worker = None self.stream_worker = None
self.hint: str = self.HINT_IDLE self.hint: str = self.HINT_IDLE
self.register_theme(AUSTRALIS_THEME)
self.theme = "australis"
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield Header() yield Header()
@@ -444,7 +541,10 @@ class RatatoskrApp(App[int]):
content = event.input.value.strip() content = event.input.value.strip()
if not content: if not content:
return return
log.write(f" {content}") # noqa: RUF001 — intentional INV-006 prefix # v0.4.1 retheme: operator's voice gets Australis bright cyan so it
# stands out against the default-foreground assistant text below it.
from rich.text import Text as RichText
log.write(RichText(f" {content}", style=_AU_USER_ECHO)) # noqa: RUF001
event.input.value = "" event.input.value = ""
self.state = "streaming" self.state = "streaming"
self._set_hint(self.HINT_STREAMING) self._set_hint(self.HINT_STREAMING)
+4 -2
View File
@@ -430,9 +430,11 @@ class TestTuiPresenterState:
raw=False, raw=False,
) )
renderable = log.write.call_args[0][0] renderable = log.write.call_args[0][0]
# INV-003: must be a dim-styled Rich Text renderable, not a plain str. # INV-003: must be a styled Rich Text renderable, not a plain str.
# v0.4.1 retheme: style is now Australis Sea dark-60 ("#86929d") instead
# of the terminal-dim filter "dim". Assert non-empty styling either way.
assert isinstance(renderable, RichText), type(renderable) assert isinstance(renderable, RichText), type(renderable)
assert renderable.style == "dim" assert renderable.style, "demoted telemetry must carry SOME style"
text = renderable.plain text = renderable.plain
assert text.startswith("· worker_phase:") assert text.startswith("· worker_phase:")
assert "[worker_phase]" not in text assert "[worker_phase]" not in text
Generated
+1 -1
View File
@@ -968,7 +968,7 @@ wheels = [
[[package]] [[package]]
name = "ratatoskr" name = "ratatoskr"
version = "0.4.0" version = "0.4.1"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "httpx" }, { name = "httpx" },