47 contract-listed tests authored + GREEN (43 tui + 4 issue-#3
amendments). 164/164 tests GREEN suite-wide; ruff clean.
Vertical-slice ordering: _render_event_to_log → _cancel_via_sse →
CLI amendments → RatatoskrApp class + on_mount + on_unmount →
on_input_submitted → _stream_turn_worker → action_interrupt +
action_quit → run_tui.
Two in-flight contract amendments caught during TDD:
- PRE-002 of run_tui was `(args.session_id is None) != args.new` —
backwards (fails when --session is set + new=False). Corrected to
`bool(args.session_id) != bool(args.new)`.
- RichLog created with markup=False (contract drafted markup=True).
Rich interprets `[xxx]` as style markup and strips it, which would
break every labeled stderr-style line ([cancel_failed], [done],
[error], etc.). The post-Done Markdown rendering still works
because rich.markdown.Markdown is a Renderable and doesn't need
widget-level markup.
Implementation notes:
- _stream_turn_worker takes the log widget as a parameter passed
from on_input_submitted. Querying #transcript from inside a
Textual worker context fails with NoMatches; capturing the
reference once at handler-time and threading it through the
worker sidesteps the issue.
- _spy_writes(monkeypatch) test helper records every RichLog.write
call. RichLog's `.lines` Strip buffer isn't populated
synchronously after .write() returns, which makes
post-app-shutdown inspection unreliable; a write-spy gives
deterministic verification.
- SIGINT-mid-stream tests use custom httpx.AsyncByteStream
subclasses with asyncio.Event gates to make timing deterministic
without sleep-based polling — the cancel-respx-mock sets the
gate event when its endpoint is observed, releasing the next
SSE chunk.
- _submit_and_wait test helper needs `await pilot.pause()` BEFORE
the polling loop so the Input.Submitted message has a chance to
dispatch. Discovered via debug-print trace; tracked in the test
helper.
CLI amendments (per issue #4 in-place amendment of #3 contract):
- ParsedArgs.send_content: str | None (was str)
- ParsedArgs.raw: bool added
- _parse_args: --send default=None; empty-string still rejected;
--raw added
- main: branches on args.send_content — None → lazy
`from ratatoskr.tui import run_tui` + run_tui(args); else
asyncio.run(_amain(args)). Lazy import preserves issue #3 INV-001.
Persistent-memory updated per the commit-along rule: tui module
landed, recent-decisions entries for #4 (contract + Volva + TDD),
next natural moves rotated to Volva code-review + manual smoke
against the personal Worldtree (key landed in env.sh per
infra-ops's earlier delivery).