tui

package
v0.4.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 29, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package tui is the Bubble Tea three-pane review cockpit (files / findings / detail). Lands in M4.

Package tui implements the interactive TUI for reviewing findings using the Bubble Tea framework.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Run

func Run(m *Model) error

Run launches the interactive Bubble Tea program for the given model and blocks until the user quits. The model is mutated in place; callers read approved findings via Model.GetApprovedFindings after Run returns.

Types

type InboxAction

type InboxAction int

InboxAction encodes how the user exited the inbox screen. The top-level loop in app/inbox.go switches on this to decide whether to open a review, refresh the list, or quit the program.

const (
	InboxActionNone InboxAction = iota
	InboxActionOpen
	InboxActionRefresh
	InboxActionQuit
)

type InboxModel

type InboxModel struct {
	// contains filtered or unexported fields
}

InboxModel renders a single-pane selectable list of PRSummary rows. It is intentionally separate from the review Model so we can run a fresh tea.NewProgram for each (per the step-out-between-picks pattern from the design spec §7).

func NewInboxModel

func NewInboxModel(summaries []provider.PRSummary, owner, name string) *InboxModel

func (*InboxModel) Action

func (m *InboxModel) Action() InboxAction

Action returns how the user exited the inbox. None until Update sees a terminal key.

func (*InboxModel) Init

func (m *InboxModel) Init() tea.Cmd

func (*InboxModel) Pick

func (m *InboxModel) Pick() *provider.PRSummary

Pick returns the chosen summary, only meaningful when Action() == InboxActionOpen.

func (*InboxModel) ResetSession added in v0.3.0

func (m *InboxModel) ResetSession()

ResetSession clears the exit state (action, pick) so a reused model can host a fresh Bubble Tea session while keeping the cached list. The app reuses one InboxModel across sessions (spec §7: the list persists between picks); without this reset a teardown-without- keypress after the first open replayed the stale InboxActionOpen and re-launched the previous review. diffsmith-qe5.

func (*InboxModel) Selected

func (m *InboxModel) Selected() int

Selected returns the current cursor index. Exposed for tests.

func (*InboxModel) Update

func (m *InboxModel) Update(msg tea.Msg) (tea.Model, tea.Cmd)

func (*InboxModel) View

func (m *InboxModel) View() string

type LoadErrorMsg

type LoadErrorMsg struct{ Err error }

LoadErrorMsg surfaces a pipeline failure. The loader renders the error and waits for the user to dismiss with q/ctrl+c.

type LoadReadyMsg

type LoadReadyMsg struct {
	Findings    []review.Finding
	Quarantined []review.Quarantined
}

LoadReadyMsg hands validated findings + quarantined ones to the loader. After this arrives the loader transitions to the inner ReviewModel.

The per-run summary string (which models ran, counts, synthesis lead) is intentionally NOT carried on this message — it lives as a closure variable in the caller (app/review.go) and is written to stdout after the loader exits. Keeping it out of the loader avoids dead state on the model and keeps the run-summary write outside the TUI's render loop where it belongs.

type LoaderModel

type LoaderModel struct {
	// contains filtered or unexported fields
}

LoaderModel wraps the review Model so the TUI can launch immediately when the user runs `diffsmith review` and stream pipeline phases (fetch → model → validate) through an animated spinner + status text before findings arrive. Once LoadReadyMsg fires, View delegates to the inner ReviewModel for the rest of the session.

Pattern: a goroutine in the app layer drives the pipeline and pushes messages via tea.Program.Send. The loader stays in the bubbletea event loop the whole time, so the spinner keeps animating without any extra goroutine inside the TUI.

func NewLoaderModel

func NewLoaderModel(initialStatus string) *LoaderModel

NewLoaderModel constructs a LoaderModel with a default-styled spinner and an initial status line. The status updates as PhaseStatusMsg messages arrive from the pipeline.

func (*LoaderModel) Err

func (m *LoaderModel) Err() error

Err returns any pipeline error pushed via LoadErrorMsg. The app layer reads this after the TUI quits and propagates as the process exit code.

func (*LoaderModel) GetApprovedFindings

func (m *LoaderModel) GetApprovedFindings() []review.Finding

GetApprovedFindings delegates to the inner ReviewModel; returns nil before findings have loaded.

func (*LoaderModel) GetFindingsMarkedForPost

func (m *LoaderModel) GetFindingsMarkedForPost() []review.Finding

GetFindingsMarkedForPost delegates to the inner ReviewModel; returns nil before findings have loaded.

func (*LoaderModel) Init

func (m *LoaderModel) Init() tea.Cmd

Init starts the spinner tick.

func (*LoaderModel) Quarantined

func (m *LoaderModel) Quarantined() []review.Quarantined

Quarantined returns the validator-rejected findings (or nil before load completes).

func (*LoaderModel) ReviewModel

func (m *LoaderModel) ReviewModel() *Model

ReviewModel returns the inner ReviewModel once findings have loaded, or nil before LoadReadyMsg arrives. Used by the app-layer test seam to drive the existing test fakes against the inner model without exposing the loader's transition wiring to tests.

func (*LoaderModel) TotalReviewed

func (m *LoaderModel) TotalReviewed() int

TotalReviewed is the number of valid findings handed to the inner ReviewModel (i.e., what the user saw in the TUI), used by writeFindings to disambiguate "model returned nothing" vs "user approved nothing".

func (*LoaderModel) Update

func (m *LoaderModel) Update(msg tea.Msg) (tea.Model, tea.Cmd)

Update routes messages either to the loader (during the wait) or to the inner ReviewModel (after findings have arrived). The transition is one-way: once a LoadReadyMsg arrives we hand off and never look back.

func (*LoaderModel) View

func (m *LoaderModel) View() string

View renders the loading state or delegates to the inner ReviewModel.

type Model

type Model struct {
	// contains filtered or unexported fields
}

Model is the Bubble Tea model representing the TUI state and findings.

markedForPost is a TUI-local intent set kept out of review.Finding so the validated finding contract stays unchanged. Approval (State) and post-intent are orthogonal: the user can approve without posting (for the M5a clipboard workflow), post without approving, or both.

editMode + editor implement F9's "edit the suggested comment" contract (diffsmith-axv). When editMode is true the textarea owns key input; the normal-mode key bindings resume on exit.

func NewModel

func NewModel(findings []review.Finding) *Model

NewModel constructs a TUI Model with the given findings.

func (*Model) ApproveCurrent

func (m *Model) ApproveCurrent()

ApproveCurrent marks the currently selected finding as approved.

func (*Model) ApprovedCount

func (m *Model) ApprovedCount() int

ApprovedCount returns the number of approved findings.

func (*Model) CurrentFinding

func (m *Model) CurrentFinding() *review.Finding

CurrentFinding returns the currently selected finding, or nil if none.

func (*Model) DismissCurrent

func (m *Model) DismissCurrent()

DismissCurrent marks the currently selected finding as dismissed and drops it from the post-bound set. Without the second step a sequence like a→p→d→a would silently resurrect the post mark on re-approve (markedForPost[i] was never cleared), bypassing the rule that only an explicit 'p' on an approved finding enqueues it for posting.

func (*Model) DismissedCount

func (m *Model) DismissedCount() int

DismissedCount returns the number of dismissed findings.

func (*Model) EditCurrentComment

func (m *Model) EditCurrentComment(newComment string)

EditCurrentComment updates the suggested comment of the currently selected finding.

func (*Model) EditCurrentEvidence

func (m *Model) EditCurrentEvidence(newEvidence string)

EditCurrentEvidence updates the evidence of the currently selected finding.

func (*Model) EditCurrentFixHint

func (m *Model) EditCurrentFixHint(newFixHint string)

EditCurrentFixHint updates the fix hint of the currently selected finding.

func (*Model) EditCurrentTitle

func (m *Model) EditCurrentTitle(newTitle string)

EditCurrentTitle updates the title of the currently selected finding.

func (*Model) EditMode

func (m *Model) EditMode() bool

EditMode reports whether the model is currently in edit mode. Tests and the View consult this to gate behavior.

func (*Model) GetApprovedFindings

func (m *Model) GetApprovedFindings() []review.Finding

GetApprovedFindings returns a slice of all approved findings in order.

func (*Model) GetFindingsMarkedForPost

func (m *Model) GetFindingsMarkedForPost() []review.Finding

GetFindingsMarkedForPost returns findings the user explicitly intends to post upstream, in the original order they appeared in the model. Only currently-approved findings are returned — a finding that was approved, marked, then later dismissed is filtered out so the dismiss always wins. Callers (the confirmation prompt count, the upstream submitter) can rely on this as a single source of truth.

func (*Model) Init

func (m *Model) Init() tea.Cmd

func (*Model) IsPostBoundRow added in v0.1.6

func (m *Model) IsPostBoundRow(i int) bool

IsPostBoundRow reports whether the finding at index i is currently in the post-bound set. View renderers consult this to draw the '[post]' badge; it must match the semantics of GetFindingsMarkedForPost so the badge can never claim a finding is queued for posting that the filter will silently drop.

func (*Model) MarkCurrentForPost

func (m *Model) MarkCurrentForPost()

MarkCurrentForPost flags the currently selected finding for upstream posting. Only approved findings are eligible: pressing 'p' on a Pending or Dismissed finding leaves the marked set untouched and sets a transient status explaining what to do. 'a' remains the only path to approval — this keeps the trust boundary explicit so a user can never post a finding they have not first approved.

func (*Model) MoveDown

func (m *Model) MoveDown()

MoveDown moves the selection down (towards the last finding).

func (*Model) MoveUp

func (m *Model) MoveUp()

MoveUp moves the selection up (towards the first finding).

func (*Model) TransientStatus

func (m *Model) TransientStatus() string

TransientStatus returns the current ephemeral status message, or "" if none. The View consults this to render a transient line in the footer; tests assert against it to verify user-visible feedback without rendering the full TUI.

func (*Model) Update

func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd)

func (*Model) View

func (m *Model) View() string

View renders the three-pane TUI per docs/tui-workflow.md and PRD F8: files (left) | findings (middle) | detail or editor (right). The detail pane swaps to the textarea when editMode is true (PRD F9).

type ModelPickerItem

type ModelPickerItem struct {
	Name        string
	Available   bool
	Unavailable string // reason shown when !Available
	// contains filtered or unexported fields
}

ModelPickerItem represents one row in the picker.

type ModelPickerModel

type ModelPickerModel struct {
	// contains filtered or unexported fields
}

ModelPickerModel is the multi-select Bubble Tea screen shown at startup. After Update sees a terminal key, Confirmed() or Cancelled() returns true; the app layer reads SelectedNames() to build the SelectedModels for the session.

func NewModelPickerModel

func NewModelPickerModel(items []ModelPickerItem) *ModelPickerModel

NewModelPickerModel constructs a picker with default checks applied: every known available model (the keys of pickerPriority) is pre-checked. If codex is unavailable, the highest-priority available model is pre-checked alone as a fallback.

func (*ModelPickerModel) Cancelled

func (m *ModelPickerModel) Cancelled() bool

Cancelled returns true if the user pressed q / ctrl+c.

func (*ModelPickerModel) Confirmed

func (m *ModelPickerModel) Confirmed() bool

Confirmed returns true if the user pressed enter.

func (*ModelPickerModel) Init

func (m *ModelPickerModel) Init() tea.Cmd

func (*ModelPickerModel) IsChecked

func (m *ModelPickerModel) IsChecked(name string) bool

IsChecked reports whether the given name is currently checked.

func (*ModelPickerModel) SelectedNames

func (m *ModelPickerModel) SelectedNames() []string

SelectedNames returns the names of currently-checked items, in priority order (codex > claude > antigravity > unknown names after). Both the ordering and the known-name set derive from pickerPriority, the single source of truth in this file — no hand-maintained name lists.

func (*ModelPickerModel) Update

func (m *ModelPickerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd)

func (*ModelPickerModel) View

func (m *ModelPickerModel) View() string

type ModelStatusMsg

type ModelStatusMsg struct {
	Name  string
	State string // "queued" | "running" | "done" | "failed"
	Err   error  // populated when State == "failed"
}

ModelStatusMsg reports a state transition for a single model in the multi-model review fan-out. Loader uses these to render per- model rows.

type PhaseStatusMsg

type PhaseStatusMsg string

PhaseStatusMsg updates the loader's status text.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL