tui

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Oct 2, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package tui holds the two Bubble Tea screens --tui opens: the classification reasons screen and the plan screen (ADR-002).

The line printer is the default and stays the default. ADR-002's reason is the artefact and not the cost: the transcript survives the run and pastes into a compliance ticket, while an alternate-screen app erases itself on exit. So `lazyslice` prints lines, and these two screens are entered only on a TTY, only when --tui or a question's "?" asks for them, and only for the two tables that do not fit a screen — the classifier's reasons on a two-hundred-column schema, and the plan on forty tables.

The TUI owns no logic. It is one more sink on the same event channel as render.Lines (every event.Event arrives as a tea.Msg), it reaches no stage, and the only thing it produces is a core.Request — the same struct cmd/lazyslice builds from flags, field for field. Every action it offers is a flag in ARCHITECTURE.md section 8 first, which Binding.Flag records and TestEveryTUIActionHasFlag in cmd/lazyslice enforces.

Leaving either screen returns to the line printer with the screen's contents echoed into scrollback, together with the flags that would make the same request again.

Index

Constants

This section is empty.

Variables

View Source
var ErrNoTerminal = errors.New("tui: no terminal")

ErrNoTerminal is returned when Run is called with no terminal to draw on. It exists so that the caller's TTY check and this package's own cannot disagree about whether the screens were entered.

Functions

This section is empty.

Types

type Binding

type Binding struct {
	// Bind is the framework's own binding, so that key.Matches decides what was
	// pressed and the help text has one home.
	Bind key.Binding
	// Flag is the long flag name without its dashes. It is empty exactly when
	// Nav is true.
	Flag string
	// Nav marks a binding that moves the cursor, switches screen or leaves the
	// TUI. It changes no field of the core.Request being built, which is why it
	// needs no flag; TestNavigationBuildsNoRequest holds the other half of that
	// claim by pressing every one of them and comparing the request.
	Nav bool
	// contains filtered or unexported fields
}

Binding is one keybinding together with the CLI flag that reaches the same action.

Flag is the enforcement point for root CLAUDE.md's hardest rule for this package: every TUI action must be reachable by a CLI flag first. TestEveryTUIActionHasFlag in cmd/lazyslice walks Bindings() and fails on any binding that is not Nav and whose Flag is not a flag registered on the real command tree (ADR-002, enforcement mechanism 1). A screen therefore cannot grow a choice the command line cannot make: the flag has to exist in ARCHITECTURE.md section 8 and in cmd/lazyslice first, and the binding second.

func Bindings

func Bindings() []Binding

Bindings is every keybinding the TUI has, for the test in cmd/lazyslice that fails on one whose action has no CLI flag (ADR-002).

func (Binding) Desc

func (b Binding) Desc() string

Desc is what the binding does, as the footer prints it.

func (Binding) Key

func (b Binding) Key() string

Key is what the operator presses, as the footer prints it.

type Collector

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

Collector is the TUI's event.Sink: it passes every event on to the renderer behind it and keeps the ones the two screens are built from.

It is what makes internal/tui "one more sink on the same event channel" and nothing more (ARCHITECTURE.md section 1). It reads no stage, holds no database handle, and keeps only identifiers and counts, because an event.Event carries nothing else by construction (THREAT_MODEL.md T4).

func NewCollector

func NewCollector(next event.Sink) *Collector

NewCollector returns a Collector in front of next, which is the line printer in every real run: the transcript is printed as it happens and the screens are opened afterwards, so nothing is hidden by the TUI having been asked for.

func (*Collector) Dropped

func (c *Collector) Dropped() int

Dropped is how many events the bound refused. It is printed in the header rather than swallowed.

func (*Collector) Events

func (c *Collector) Events() []event.Event

Events returns what the screens are built from, in the order the run produced it.

func (*Collector) Send

func (c *Collector) Send(e event.Event)

Send implements event.Sink.

type Input

type Input struct {
	// Request is what the flags built. The screens start from it and hand back
	// the same struct with what the operator changed.
	Request core.Request
	// Events are the classification decisions and the plan steps a run has
	// already produced, from a Collector. Each is applied to the model as a
	// tea.Msg, which is the same path a live event would take.
	Events []event.Event
	// Dropped is the collector's refused count, printed in the header so that a
	// truncated screen says so.
	Dropped int
	// Target is the endpoint the plan pass resolved (core.Reviewed.Target),
	// named in the confirmation Accept opens on a mode that would drop and
	// rewrite it, and in the line printed when the operator leaves instead
	// (T-0345). Empty for a mode that opens no target (classify, plan).
	Target string
	// In and Out are the terminal. Both are required in a real run; a test
	// passes a buffer to each.
	In  io.Reader
	Out io.Writer
}

Input is one visit to the two screens.

type Result

type Result struct {
	// Request is what the screens built.
	Request core.Request
	// Flags is the command line that would build the same request without the
	// TUI, in the order ARCHITECTURE.md section 8 groups them.
	Flags []string
	// Run reports that the operator left with "leave and run" rather than with
	// cancel. A cancel is not an error: it is a decision, and the caller prints
	// nothing further and exits 0.
	Run bool
	// Interrupted reports that Run is false because ctrl+c reached the
	// screens, rather than esc or "q". The caller reports this the same way it
	// reports a SIGINT during the run itself: exit 130, not the plain 0 a
	// decision to leave gets (T-0345, ADR-005).
	Interrupted bool
	// Transcript is what was echoed into scrollback.
	Transcript string
}

Result is what the operator left with.

func Run

func Run(ctx context.Context, in Input) (Result, error)

Run opens the two screens over the events a run produced and returns the request the operator built.

It is the whole of this package's surface. It starts nothing, reads no database and holds no credential: the caller runs the pipeline, hands over what the classifier and the planner said, and calls core.Run again with the Result's request if the operator asked for it.

Jump to

Keyboard shortcuts

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