diff --git a/pyproject.toml b/pyproject.toml index 6d7028a..c60f373 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "ratatoskr" -version = "0.2.0" +version = "0.2.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 9776cb9..2d6a513 100644 --- a/src/ratatoskr/tui.py +++ b/src/ratatoskr/tui.py @@ -214,6 +214,33 @@ class TuiPresenterState: class RatatoskrApp(App[int]): """Textual TUI shell — single chat pane.""" + # Issue #12 follow-up: anchor layout so Input never moves. + # Pre-fix: every widget was auto-stacked. RichLog grew with content, + # thinking-current toggled display 0↔N rows per thinking-run — both pushed + # Input around mid-turn. Fix: dock the chrome to the top/bottom edges and + # let RichLog (the only `1fr` widget) absorb all layout reflows internally + # via its scroll viewport, so screen-relative positions stay stable. + DEFAULT_CSS = """ + #thinking-current { + dock: top; + height: auto; + } + #transcript { + height: 1fr; + } + #prompt { + dock: bottom; + } + #identity { + dock: bottom; + height: 1; + } + #hint { + dock: bottom; + height: 1; + } + """ + BINDINGS: ClassVar[list[Binding]] = [ Binding("ctrl+c", "interrupt", "Cancel / Exit", priority=True), Binding("ctrl+d", "quit", "Exit immediately", priority=True), @@ -243,6 +270,14 @@ class RatatoskrApp(App[int]): def compose(self) -> ComposeResult: yield Header() + # Issue #12 follow-up: thinking-current sits at the TOP under Header (via + # DEFAULT_CSS `dock: top`). Pre-fix it lived between hint and Footer in + # the auto-stacked flow, so its display=True/False toggle per + # thinking-run pushed Input + identity + hint up/down on every cycle. + # Docking top + RichLog filling middle stabilises Input's screen + # position; thinking-current grows/shrinks under Header where the + # reflow doesn't affect anything else. + yield Static("", id="thinking-current") # markup=False so labeled lines like "[cancel_failed] ..." render verbatim # (Rich would otherwise interpret square-bracket spans as style markup and # strip them). The post-Done markdown render uses Markdown() directly which @@ -255,9 +290,6 @@ class RatatoskrApp(App[int]): # always-visible. yield Static("", id="identity") yield Static(self.HINT_IDLE, id="hint") - # Issue #12: live thinking widget — hidden by default, shown per-delta - # during a thinking run, cleared+hidden at turn terminal. - yield Static("", id="thinking-current") yield Footer() async def on_mount(self) -> None: diff --git a/uv.lock b/uv.lock index 94bc506..c8f416d 100644 --- a/uv.lock +++ b/uv.lock @@ -968,7 +968,7 @@ wheels = [ [[package]] name = "ratatoskr" -version = "0.2.0" +version = "0.2.1" source = { editable = "." } dependencies = [ { name = "httpx" },