Documentation
¶
Overview ¶
Package progress provides a shared Bubble Tea progress UI for the `prr audit` and `prr review` commands.
Both commands emit (phase, message) events as their pipelines execute. This package renders those events as a phase list with spinner / per-phase detail / active-phase progress bar / final summary box.
Design notes ¶
Mode-specific concerns (the phase list, the result type, how to render the summary, how to extract progress-bar counters from message strings) are passed in via Config so the same TUI serves both modes. Future UI improvements (token rate, ETA, sparklines) only need to be written here and both modes get them.
The background task runs in a goroutine and emits events via the `emit` closure given to RunTask. emit forwards events through the Bubble Tea program's Send channel so all model mutation happens on the main thread — the goroutine never touches the model directly.
Index ¶
- Constants
- Variables
- func ParseBatchEvent(s *State, message string) bool
- func RenderBatchesPanel(s *State, opts BatchPanelOptions) string
- func RenderPhaseList(phases []PhaseInfo, state *State, activeSpinner string, maxDetailWidth int) string
- func Run(cfg Config) error
- type BatchPanelOptions
- type BatchState
- type BatchStatus
- type Config
- type Header
- type PhaseDef
- type PhaseInfo
- type PhaseStatus
- type State
Constants ¶
const BatchByteEstimate = 4000
BatchByteEstimate seeds the per-batch progress fraction when the real per-call answer length is unknown. Median observed responses land around ~3k chars; 4000 keeps the bar honest (it fills slower than reality and never claims 100% before the terminal event).
Variables ¶
var ErrCancelled = errors.New("cancelled by user")
ErrCancelled is returned from Run when the user exits the TUI before the background task signals done.
Functions ¶
func ParseBatchEvent ¶ added in v1.9.0
ParseBatchEvent updates s.Batches from a (phase, message) pair when the message matches one of the per-batch lifecycle shapes emitted by the review and audit pipelines:
Batch K: init label="..." files=N kind=aoi-driven|general Batch K: active Batch K: stream bytes=N Batch K: done|cached|failed
K is the 1-based batch number on the wire; stored as 0-based Index. Returns true when the message was a batch event (matched or unparseable but recognized prefix), so callers can short-circuit their own parsers.
func RenderBatchesPanel ¶ added in v1.9.0
func RenderBatchesPanel(s *State, opts BatchPanelOptions) string
RenderBatchesPanel renders the per-batch panel block:
Batches active 4 · done 11 · cached 2 · failed 0 · queued 21 ▶ injection/sql [critical] 2f 0:07 ███░░░░░ 28% ▶ internal/ui 4f 0:05 ··•····· streaming +6 more active ✓ internal/git 5f 0:14 ✓ internal/config 1f 0:08 cached
Returns "" when no batches have been seen yet.
func RenderPhaseList ¶ added in v1.6.0
func RenderPhaseList(phases []PhaseInfo, state *State, activeSpinner string, maxDetailWidth int) string
RenderPhaseList renders the phase rows (no header, no progress bar, no summary) as a pure string. Used both by the bubbletea program in this package and by external callers (the in-app TUI) that want the same rendered shape inside their own layout.
activeSpinner is the glyph drawn for the active phase row; callers without a spinner can pass "" and the active row will use a plain "●". maxDetailWidth caps the truncation of each row's detail string; pass 0 to disable truncation.
Types ¶
type BatchPanelOptions ¶ added in v1.9.0
type BatchPanelOptions struct {
// MaxActiveRows caps the number of active batches rendered as full
// detail rows. Overflow becomes a single "+N more active" line.
// Defaults to 10 when <= 0.
MaxActiveRows int
// RecentTail caps the number of recently-finished rows rendered
// below the active ones. Defaults to 10 when <= 0 — same cap as
// MaxActiveRows so the panel feels symmetric.
RecentTail int
// Animation is a monotonic tick counter used to cycle the
// indeterminate dotted bar for active batches that haven't
// streamed any bytes yet. Pass int(elapsed / 100ms) so the
// animation advances at a steady 10 fps.
Animation int
// Now overrides the wall-clock used to compute per-row elapsed
// timers. Zero defaults to time.Now() — tests pin this so
// snapshot output is deterministic.
Now time.Time
}
BatchPanelOptions configures RenderBatchesPanel.
type BatchState ¶ added in v1.9.0
type BatchState struct {
// Index is the call's original 0-based index (1-based on the
// wire as "Batch K"). Stable across the run.
Index int
// Label is the human-readable description: directory ("internal/ui")
// for general batches, "category/subcategory" (with " [critical]"
// suffix for individual calls) for AOI-driven ones.
Label string
// Files is the count rendered in the panel's middle column. The
// semantic depends on Unit: "files" for deep-review batches,
// "findings" for recheck batches. Naming is legacy — kept as
// "Files" so existing snapshot tests don't churn.
Files int
// Unit is the noun shown after Files in the panel ("files",
// "findings"). Empty defaults to "files" so deep-review wire
// shape (kind=aoi-driven|general, no unit token) keeps working.
Unit string
// Kind is "aoi-driven" or "general". Drives the row's accent color.
Kind string
// Status is the lifecycle stage.
Status BatchStatus
// StartedAt is set when the batch transitions to active. Zero
// while queued. Used to compute the elapsed timer on the row.
StartedAt time.Time
// EndedAt is set when the batch transitions to done/cached/failed.
// Zero while queued or active.
EndedAt time.Time
// Bytes is the cumulative content-byte count received from the
// LLM stream for this batch. Drives the per-row progress bar
// when non-zero; the row falls back to an indeterminate spinner
// when zero.
Bytes int
// Findings is the number of findings this batch produced. Set
// from the optional `findings=N` token on terminal events
// (done/cached). Rendered next to the elapsed time on finished
// rows so the user can see what work each call delivered.
Findings int
}
BatchState is one row in the Batches panel.
type BatchStatus ¶ added in v1.9.0
type BatchStatus string
BatchStatus lifecycle for one batch row in the Batches panel.
const ( BatchQueued BatchStatus = "queued" BatchActive BatchStatus = "active" BatchDone BatchStatus = "done" BatchCached BatchStatus = "cached" BatchFailed BatchStatus = "failed" )
type Config ¶
type Config struct {
Header Header
Phases []PhaseDef
// RunTask is the background work. emit forwards (phase, message)
// events into the model on the Bubble Tea main thread. The
// returned error becomes the run's error (passed to Summary).
RunTask func(emit func(phase, message string)) error
// ParseEvent updates counters in s for a (phase, message) pair.
// Optional. The TUI also recognizes the special phase "warning"
// as a banner regardless of ParseEvent.
ParseEvent func(s *State, phase, message string)
// Summary renders the final box after the run completes.
// Optional. err is the RunTask return value; elapsed is wall time.
Summary func(err error, elapsed time.Duration) string
// OnCancel is called when the user exits the TUI (Ctrl+C / q)
// before the background task signals done. The task's goroutine
// is still in flight at this point; the canonical use is for the
// caller to cancel the context it passed to RunTask so the
// in-flight LLM call returns quickly instead of orphaning. Optional.
OnCancel func()
// BatchPhases lists the phase Names whose active state should
// trigger the Batches panel. The panel renders below the phase
// list while one of these is the active phase. Empty disables
// the panel — modes whose pipelines don't emit per-batch
// lifecycle events leave this nil.
BatchPhases []string
}
Config configures a TUI run.
func (Config) BatchPanelActive ¶ added in v1.9.0
BatchPanelActive reports whether the panel should render now: at least one of cfg.BatchPhases is in PhaseActive status. Used by the TUI's View() to gate the section.
type Header ¶
type Header struct {
Title string // e.g. " prr audit", " prr review"
Subtitle string // e.g. repo name, PR number
Info string // e.g. "review: model-x aoi: model-y"
}
Header is the title block shown at the top of the TUI.
type PhaseDef ¶
type PhaseDef struct {
// Name is the event key the pipeline emits on (e.g. "phase2", "fetch").
// Must be unique within a Config.Phases slice.
Name string
// Label is the human-readable label shown next to the spinner/check.
Label string
// ProgressFn computes the active-phase progress bar value (0..1)
// from the current State. Called only while this phase is active.
// Return 0 (or leave nil) to skip the progress bar for this phase.
ProgressFn func(s *State) float64
// Counter returns (done, total) for inline "X/Y" display next to
// the phase label. Rendered while the phase is active *and* after
// it completes (so users can see the final tally per phase).
// Skipped when total <= 0. Optional.
Counter func(s *State) (done, total int)
// CounterUnit is the unit shown after the X/Y, e.g. "batches",
// "findings", "files". Empty omits the unit (legacy behavior).
// Surfacing this is what tells the user whether 0/3 means three
// files, three batches, or three findings — the absence of a
// unit reads as ambiguous noise.
CounterUnit string
// Summary returns a stable description of what this phase
// accomplished. Rendered as the row's detail line when the phase
// reaches `done` state, replacing the last-write-wins live detail.
// Falls back to live detail when this returns "" or is nil.
//
// Use this to surface structured information that would otherwise
// be lost as the live detail line flips between transient status
// messages — e.g., "kept 11 · dismissed 4" for Recheck or
// "35 done · 58 cached · 3 failed" for Deep Review.
Summary func(s *State) string
}
PhaseDef describes one phase shown in the TUI.
type PhaseInfo ¶ added in v1.6.0
type PhaseInfo struct {
Def PhaseDef
Status PhaseStatus
Detail string
}
PhaseInfo pairs a phase definition with its current status and the last-write-wins detail string. The bubbletea program in this package owns one of these per phase, and external consumers (e.g. the in-app TUI) build their own slice for RenderPhaseList.
type PhaseStatus ¶ added in v1.6.0
type PhaseStatus string
PhaseStatus is the lifecycle of a phase row.
const ( PhaseWaiting PhaseStatus = "waiting" PhaseActive PhaseStatus = "active" PhaseDone PhaseStatus = "done" PhaseError PhaseStatus = "error" )
type State ¶
type State struct {
Counters map[string]int
Elapsed time.Duration
// Batches is populated by ParseEvent for modes whose pipelines
// emit per-batch lifecycle events (Batch K: init/active/stream/
// done/cached/failed). The Batches panel renders these as one
// row per active batch plus a recent-completions tail. Nil for
// modes that don't emit those events.
Batches map[int]*BatchState
}
State exposes live counters and metadata to ProgressFn / ParseEvent closures. Counters is mode-defined — pick any string keys; the TUI doesn't interpret them.