ui.py
ui.py — one voice for everything documate says.
Overview
ui.py — one voice for everything documate says.
Every human-facing line the tool prints goes through here: a fixed glyph vocabulary (✓ ok, ✗ fail, ! warn, → doing), consistent colors, a spinner for the slow silent parts, and a live progress bar while the model layer drafts. On a real terminal the output is dynamic and colored; captured or piped (tests, CI, the pre-commit hook) the exact same words come out as plain single-line text — rich resolves sys.stdout/stderr lazily and drops styling for non-terminals, and soft_wrap keeps messages greppable at any width.
Stream contract is preserved from the print() era: successes and advisories go to stdout, gate failures to stderr — CI redirects keep meaning.
used by src/documate/briefs.py, src/documate/check.py, src/documate/cli.py, src/documate/docs.py, src/documate/prose.py, src/documate/rewrap.py, src/documate/site.py, src/documate/stats.py, src/documate/undo.py
API
F_emit(console: Console, glyph: str, gstyle: str, msg: str, style=None) -> None
One glyph-prefixed line on console — the shape every helper shares.
_PlainTracker.working, detail, fail, header, line, note, ok, warnFok(msg: str) -> None
A green ✓ line on stdout — a step succeeded or a summary is healthy.
_PlainTracker.done · calls _emitFfail(msg: str) -> None
A red ✗ line on stderr — a gate failed or a step went wrong.
_PlainTracker.failed · calls _emitFwarn(msg: str) -> None
A yellow ! line on stdout — degraded or advisory, never a failure.
_emitFheader(msg: str) -> None
A bold cyan line announcing a phase (e.g. what --fix is about to do).
card, plan · calls _emitFnote(msg: str, err: bool=False) -> None
A dim context line — explanation under a finding, next-step hints.
_Spin.__init__, status · calls _emitFdetail(msg: str, err: bool=False, style: str | None=None) -> None
A two-space-indented per-item row under a header (STALE/FAIL/~ lines).
card, plan · calls _emitFline(msg: str, style: str | None=None) -> None
A raw styled line on stdout — the diff view's building block.
diff · calls _emitFdiff(label: str, old: str, new: str) -> tuple[int, int]
A compact colored unified diff of one file's change — ~ label header,
green +/red − body, two-space indent. What --watch shows for every doc
change and --fix shows for every model edit, so nothing lands invisibly.
Returns (added, removed) line counts so callers can total a batch.
lineFresult(*parts: tuple[str, str | None]) -> None
A ✓ summary line built from (text, style) segments, so one line can carry differently-colored facts (pages cyan, coverage green) and still read as a single greppable string when styling is stripped.
Fplan(title: str, lines: list[tuple[str, str | None]]) -> None
The pre-flight card: a framed panel on a terminal; the same facts as plain indented lines when captured, so a CI log still records what a run was about to spend before --yes let it through.
detail, headerFcard(title: str, rows: list[list[tuple[str, str | None]]]) -> None
A framed dashboard card: each row is (text, style) segments composing one line — the plan panel's shape, but multi-colored within a line (a coverage bar next to its +/− delta). Captured/CI gets the same words as plain indented lines, so a log still records what the dashboard said.
detail, headerCclass _Spin
A stoppable spinner line for a wait of unknown length (the gap between sending a model call and its first visible token). Animated on a terminal, one dim note when captured; update() retitles it, stop() is idempotent and must run before printing anything else.
spinnerF_Spin.__init__(self, msg: str)
Start spinning (or print the one captured-mode note).
note, statusF_Spin.update(self, msg: str) -> None
Change the spinner's text (no-op when captured or stopped).
_LiveTracker.workingF_Spin.stop(self) -> None
Clear the spinner so normal printing can resume; safe to call twice.
Fspinner(msg: str) -> _Spin
Start a stoppable spinner — for waits that end on an event (first model
token) rather than a scope, where the status context manager can't fit.
_SpinFconfirm(msg: str) -> bool | None
A yes/no on the terminal — Enter alone means yes, so consenting is one keypress; n / no / q (or Ctrl-C) declines. Returns None when there is no terminal to ask (captured/CI) — the caller owns that policy.
Fask(msg: str, default: str) -> str | None
A one-line free-text prompt — Enter alone takes the default. Returns None when there is no terminal to ask (captured/CI) or on Ctrl-C — the caller owns that policy.
Fstatus(msg: str)
Show what's happening during a slow, otherwise-silent step: an animated spinner on a terminal, a plain dim line everywhere else (so CI logs still say what the pause was).
_Spin.__init__ · calls noteCclass _PlainTracker
Non-terminal tracker: one line when an item starts, one when it ends — a CI log reads as a faithful transcript of what the run was doing.
trackerF_PlainTracker.working(self, label: str) -> None
Announce the item now being worked on.
_emitF_PlainTracker.done(self, msg: str) -> None
Record one item finished successfully.
okF_PlainTracker.failed(self, msg: str) -> None
Record one item failed (stderr, like every failure).
failCclass _LiveTracker
Terminal tracker: a live bar (spinner, count, elapsed) whose description is the item in flight; finished items print as ✓/✗ lines above the bar.
trackerF_LiveTracker.__init__(self, progress, task)
Bind to an already-started rich Progress and its single task.
F_LiveTracker.working(self, label: str) -> None
Put the in-flight item on the live bar.
_Spin.updateF_LiveTracker.done(self, msg: str) -> None
Advance the bar and log the finished item above it.
F_LiveTracker.failed(self, msg: str) -> None
Advance the bar and log the failure above it, in red.
Ftracker(total: int)
Track a batch of slow items (the --fix drafting loop): a transient live progress bar on a terminal, started/finished lines otherwise. Yields an object with working(label) / done(msg) / failed(msg).
_LiveTracker, _PlainTracker