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.
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user