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
_As of 2026-05-24 (post-v0.4.0 §5 entry point: layout reshape +
Tools pane):_
_As of 2026-05-24 (post-v0.4.1 Australis theme retheme):_
**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
startup error visibility #6, presenter contract semantics amendment
#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.
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)
- `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)
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "ratatoskr"
version = "0.4.0"
version = "0.4.1"
description = "Worldtree Conversation API debug TUI — multi-pane observability dashboard"
readme = "README.md"
requires-python = ">=3.12"
+103 -3
View File
@@ -17,6 +17,7 @@ import httpx
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.containers import Horizontal, Vertical
from textual.theme import Theme
from textual.widgets import (
Footer,
Header,
@@ -61,6 +62,64 @@ from ratatoskr.sse_client import (
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 -------------------------
#
# TuiPresenterState replaces the stateless _render_event_to_log with a stateful
@@ -150,8 +209,14 @@ class TuiPresenterState:
from rich.text import Text as RichText
def _dim(s: str) -> RichText:
"""Wrap a demoted-telemetry line in dim style for the RichLog."""
return RichText(s, style="dim")
"""Wrap a demoted-telemetry line in Australis dark-60 grey.
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:
# Thinking events: accumulate into buffer, update widget per delta.
@@ -257,9 +322,16 @@ class AgentPickerApp(App[str | None]):
dock: top;
height: 1;
padding: 0 1;
color: $au-bright-cyan;
background: $surface;
}
#agent-list {
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.
assert agents
self.agents = agents
self.register_theme(AUSTRALIS_THEME)
self.theme = "australis"
def compose(self) -> ComposeResult:
yield Header()
@@ -314,12 +388,18 @@ class RatatoskrApp(App[int]):
# in the left column doesn't reflow the right column's TabbedContent.
# The v0.2.1 layout-stability property is preserved within the left
# 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 = """
#main-row {
height: 1fr;
}
#left-column {
width: 2fr;
border-right: solid $panel;
}
#right-column {
width: 1fr;
@@ -327,24 +407,39 @@ class RatatoskrApp(App[int]):
#thinking-current {
dock: top;
height: auto;
color: $au-dark-60;
}
#transcript {
height: 1fr;
background: $background;
}
#tools-log {
background: $background;
}
#prompt {
dock: bottom;
border: tall $panel;
}
#prompt:focus {
border: tall $primary;
}
#identity {
dock: bottom;
height: 1;
color: $au-bright-blue;
padding: 0 1;
}
#pane-name {
dock: bottom;
height: 1;
color: $au-bright-cyan;
padding: 0 1;
}
#hint {
dock: bottom;
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.stream_worker = None
self.hint: str = self.HINT_IDLE
self.register_theme(AUSTRALIS_THEME)
self.theme = "australis"
def compose(self) -> ComposeResult:
yield Header()
@@ -444,7 +541,10 @@ class RatatoskrApp(App[int]):
content = event.input.value.strip()
if not content:
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 = ""
self.state = "streaming"
self._set_hint(self.HINT_STREAMING)
+4 -2
View File
@@ -430,9 +430,11 @@ class TestTuiPresenterState:
raw=False,
)
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 renderable.style == "dim"
assert renderable.style, "demoted telemetry must carry SOME style"
text = renderable.plain
assert text.startswith("· worker_phase:")
assert "[worker_phase]" not in text
Generated
+1 -1
View File
@@ -968,7 +968,7 @@ wheels = [
[[package]]
name = "ratatoskr"
version = "0.4.0"
version = "0.4.1"
source = { editable = "." }
dependencies = [
{ name = "httpx" },