Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ADR-021: TTY progress via indicatif, gated by is_rich()

Date: 2026-06-05 Status: Accepted Author: Rômulo Febasi


Context

Three commands do visible work with no feedback: syn embed --all downloads ~920 MB of models then embeds the whole workspace, syn reindex rebuilds the SQLite index over every entity, and syn watch reindexes on each change. Today embed --all streams one [n/total] line per item to stderr; reindex shows nothing until its final summary. On a slow embed run the tool looks hung.

The CLI-UX brief (docs/researchs/2026-06-05-cli-ux-and-agent-ergonomics.md) set a premium (“AAA”) bar: a real progress indicator on a TTY, but no animation when stdout is not a TTY (progress bars become “christmas trees” in CI logs), and nothing that pollutes the data a pipe or an agent reads.

Two constraints, as with ADR-019: (1) no new dependency without an ADR, and (2) never regress the machine-readable path.

A relevant finding: indicatif (0.17 and 0.18) and its console backend are already in Cargo.lock: fastembed/hf-hub pull them for the model download bar. Adopting indicatif 0.18 as a direct dependency adds zero new transitive weight to the default build; it shares the copy fastembed already compiles.

Decision

Adopt indicatif 0.18 as a direct dependency and wrap it in output/progress.rs behind a single gate: a Progress draws only when output::is_rich() and not --quiet: i.e. a TTY, text mode, no --plain/SYNAPSE_AGENT. Otherwise it is a ProgressBar::hidden() no-op.

  • embed --all: a determinate bar on a rich TTY; the existing one-line-per-item stream is kept for non-TTY text (readable in CI logs, no \r spam).
  • reindex / watch: a steady-tick spinner with the current stage + counts, cleared on completion so the final summary stands alone.

Progress draws to stderr, so stdout (and --format json) is never touched. Colour is left to console, which already honours NO_COLOR/CLICOLOR.

Consequences

  • Better. Long operations show live progress on a TTY; the tool no longer looks hung. The bar is auto-suppressed in CI/pipes/agents and under --quiet.
  • Worse. A direct dep on a non-trivial crate. On a core-only build (no v05_backends) indicatif is newly compiled (small); in the default build it is already present. indicatif 0.17 and 0.18 coexist in the tree (via hf-hub 0.4 vs 0.5/fastembed): a pre-existing duplication this change does not add to (we pin 0.18, fastembed’s copy).
  • Open. The bar templates are fixed; per-command tuning can come later. embed --all’s model-download phase still uses fastembed/hf-hub’s own bar.

Alternatives considered

  • Hand-roll a spinner. Rejected: doing TTY width, steady ticks, redraw and NO_COLOR correctly is exactly what indicatif already does, and it is already compiled in. Reinventing it is strictly worse and more code to own.
  • A heavier TUI (ratatui). Rejected: massive new surface for a few progress bars; Synapse is a line-oriented CLI, not a full-screen app.
  • Keep the line stream everywhere. Rejected: no live feedback on a TTY, and the brief explicitly asked for a premium indicator there.
  • Draw to stdout. Rejected: would corrupt piped output and --format json. Progress is stderr-only.