Originally created by: fu351
src/doberman/tui.py is a single-file Textual app that browses the redacted decision log (doberman.storage.log.read_decisions). Today it loads decisions once at startup (on_mount, line 99) and only refreshes on an explicit, manually-triggered key binding (action_reload, line 105, wired via BINDINGS, line 66) — there is no live-tail: a decision the running proxy writes while the TUI is open never appears until the user presses reload. Design and build a live-tail mode: the TUI polls (or is otherwise notified of) new rows in decisions and appends them automatically while open, without breaking the existing redaction/explainability guarantees (module docstring, lines 1–24) or freezing the UI (the existing debounced background-worker pattern for LLM enrichment, lines 14–18, is the template for "never block the event loop").
Additive, but not a local tweak to the existing manual-reload action — it's a genuinely new capability (a background polling/notification mechanism, new Textual reactive state, a decision on polling interval vs. a push mechanism from the proxy process, and how it interacts with the existing debounced-enrichment worker so two background workers don't fight for the UI thread). Needs a design discussion (this issue) before any code — in particular whether the mechanism is DB-polling only (simplest, no cross-process signaling) or something the long-lived proxy process pushes to the TUI (more complex, cross-process).
read_decisions (never a raw file, raw payload, or any other data source), and all row-derived / LLM-narrated text still renders through rich.text.Text with markup disabled (a crafted value in a stored row must still never restyle or spoof the display).@work enrichment worker (line 155 today) — a slow or failing poll must never freeze scrolling/reload/enrichment, and must degrade to "no new rows yet" rather than crash the app.tui.py must keep its stated constraint of never importing doberman.proxy (policy-core decoupling, CLAUDE.md §9 / import-linter) — a DB-polling design satisfies this for free; a cross-process push design must be designed not to violate it.src/doberman/tui.py — whole file (single-file Textual app); on_mount (line 99), action_reload (line 105), BINDINGS (line 66), the existing @work(thread=True, exclusive=True, group="explain") enrichment worker (line 155) as the background-work template.src/doberman/storage/log.py — read_decisions (the only data source the TUI is allowed to use).action_reload binding still works unchanged.tui.py still does not import doberman.proxy (lint-imports / import-linter contract, if one is extended to cover this).ruff check . && ruff format --check . and lint-imports passpytest passesdoberman tui
# in a second terminal, generate a decision (any mediated tool call) and confirm
# it appears in the open TUI without pressing the reload key.
pytest tests/ -k tui -v
doberman.explain) works — the live-tail worker and the enrichment worker are separate concerns; this issue only adds the former.New here? Start with CONTRIBUTING.md. Issues are labelled level-1 (docs only) through level-10 (new subsystem) — pick one at your level and climb. Comment to claim an issue before starting.
Originally posted by: fu351
Re-leveled 10 → 7. A live-tail mode is a second Textual worker plus reactive state over the existing decision-log browser, not a new subsystem — substantial, but not top-of-ladder.