tui

package
v0.35.2 Latest Latest
Warning

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

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

Documentation

Overview

Package tui implements the bubbletea-based dev runtime that backs `hamr dev`. The headless dev runner lives in internal/devserver; this package owns the screen and adapts hotkeys, log output, and status updates between the two.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type DockerLogLineMsg

type DockerLogLineMsg struct {
	Name string
	Line string
}

DockerLogLineMsg carries one line from `docker compose logs -f` for a specific compose entry. The model routes these to a per-entry buffer so Tab can cycle between hamr logs and one log view per docker stack.

type HotkeySource

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

HotkeySource is the bubbletea-side implementation of devserver.HotkeySource. The model dispatches r/o/q keys to Send; the runner's hotkey loop consumes them via Actions().

c (clear) and m (run) are handled inside the model and never enter this channel — c clears the viewport, m opens the Makefile-target fuzzy palette.

func NewHotkeySource

func NewHotkeySource() *HotkeySource

NewHotkeySource returns a buffered source. The buffer absorbs short bursts (e.g. mash 'r' a few times) without blocking the bubbletea Update loop.

func (*HotkeySource) Actions

func (h *HotkeySource) Actions() <-chan devserver.HotkeyAction

Actions implements devserver.HotkeySource.

func (*HotkeySource) Send

func (h *HotkeySource) Send(a devserver.HotkeyAction)

Send pushes an action non-blockingly. If the buffer is full the action is dropped — the runner is already busy and another keypress is the user's problem to repeat.

type LogLineMsg

type LogLineMsg string

LogLineMsg carries one fully-formed line from a subprocess or the dev runner's slog handler. The bubbletea model appends it to the viewport.

type Model

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

Model is the top-level bubbletea model for hamr dev.

func NewModel

func NewModel(hotkeys *HotkeySource) *Model

NewModel constructs a model that pushes hotkeys to the given source.

func (*Model) Init

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

Init implements tea.Model.

func (*Model) SetMakeOutput

func (m *Model) SetMakeOutput(w io.Writer)

SetMakeOutput wires the writer that receives `make <target>` output. In the TUI runtime this is the hamr Sink so make logs land in the hamr tab. Tests that don't exercise the run feature can leave it nil — dispatchRun fails gracefully with an explanatory message.

func (*Model) Update

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

Update implements tea.Model.

func (*Model) View

func (m *Model) View() string

View implements tea.Model.

type Runtime

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

Runtime bundles the bubbletea program with the adapters the dev runner needs (hotkey source, log sink). Keep references on the same value so the dev command can wire them via WithHotkeys / WithLogWriter / WithProcessOutput / WithActionsHook in one place.

func NewRuntime

func NewRuntime() *Runtime

NewRuntime builds the TUI runtime. Call Wire on a Runner before its Run to feed all the right adapters in. Call Start to run the program (it blocks). The recommended flow is in dev.go:

rt := tui.NewRuntime()
go func() { runErr <- runDevLoop(ctx, rt, configPath, ...) }()
rt.Start()  // blocks until the model returns tea.Quit
<-runErr

func (*Runtime) AppendMCPLog

func (r *Runtime) AppendMCPLog(line string)

AppendMCPLog pushes one MCP request line to the model's dedicated MCP tab. Wired via WithMCPLogHook; fires on each gateway request. Safe from any goroutine.

func (*Runtime) HotkeyActions

func (r *Runtime) HotkeyActions() <-chan devserver.HotkeyAction

HotkeyActions exposes the underlying hotkey channel so the dev runner can react to q / Ctrl+C while parked outside Run() — e.g. waiting for a config fix, where bubbletea owns the keyboard but the runner-side loop has nothing else to select on.

func (*Runtime) Log

func (r *Runtime) Log(line string)

Log writes a single line to the TUI viewport. Intended for the dev command's own status messages (config errors, "config changed, retrying...") that don't flow through the runner's slog handler.

func (*Runtime) Quit

func (r *Runtime) Quit()

Quit asks the program to exit. Safe to call from any goroutine; if the program has already exited this is a no-op.

func (*Runtime) RegisterDockerStacks

func (r *Runtime) RegisterDockerStacks(names []string) map[string]io.Writer

RegisterDockerStacks publishes the ordered list of compose-entry names to the model (so Tab cycles through them and the status bar labels each tab) and returns the per-entry io.Writer map the runner should pass to WithDockerLogSinks. Sinks are created lazily and cached, so a config reload that re-registers the same name reuses the same docker sink (its already-buffered lines stay buffered until the new follower process emits more).

func (*Runtime) SetMCPStatus

func (r *Runtime) SetMCPStatus(enabled bool, tools int)

SetMCPStatus publishes the MCP gateway's state to the model's status-bar indicator. Wired via WithMCPStatusHook; fires at startup and on each M-toggle. Safe from any goroutine.

func (*Runtime) SetProxyURL

func (r *Runtime) SetProxyURL(url string)

SetProxyURL publishes the actual reachable proxy URL to the model so it can render in the status bar. Wired via WithProxyURLHook so the runner calls it once the listener has bound. Safe from any goroutine.

func (*Runtime) SetVersion

func (r *Runtime) SetVersion(label string)

SetVersion sets the version label shown on the right of the status bar. Safe from any goroutine.

func (*Runtime) SetVersionStatus

func (r *Runtime) SetVersionStatus(status devserver.VersionStatus, msg string)

SetVersionStatus updates the version indicator state and message.

func (*Runtime) SetVersionUpdateIfOK

func (r *Runtime) SetVersionUpdateIfOK(msg string)

SetVersionUpdateIfOK promotes the indicator to VersionUpdate only when the current status is VersionOK. Returning a bool would require synchronous access to the model state; the message is always sent and the model applies the guard. The caller logs the "update available" line unconditionally.

func (*Runtime) Start

func (r *Runtime) Start() error

Start runs the bubbletea program. Blocks until the model returns tea.Quit (q / Ctrl+C inside the model, or an external Quit call).

func (*Runtime) Wait

func (r *Runtime) Wait(ctx context.Context)

Wait runs until ctx is done, then quits the program. Useful when the runner exits first (config reload exhausted, fatal error) and the TUI needs to be torn down too.

func (*Runtime) Wire

func (r *Runtime) Wire(opts []devserver.Option) []devserver.Option

Wire applies the runtime's adapters to a Runner via the standard option constructors. Centralising the wiring here keeps dev.go from having to know which sink goes where.

type Sink

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

Sink is an io.Writer that emits one tea.Msg per newline-terminated line. toMsg builds the message from each line, so the same line-batching logic serves both the runner's combined log (LogLineMsg) and a docker stack's tagged log (DockerLogLineMsg) — see NewSink and NewDockerSink.

One Sink can fan in stdout, stderr, and the runner's slog writer because the only thing it does with each line is post a tea.Msg — colors and prefixes are preserved upstream by prefixWriter.

func NewDockerSink

func NewDockerSink(name string) *Sink

NewDockerSink returns a sink scoped to one docker compose stack, emitting DockerLogLineMsg tagged with the stack's name from hamr.toml (`[[dev.docker_compose]] name = ...`). The runtime binds the program shortly after construction.

func NewSink

func NewSink() *Sink

NewSink returns a sink emitting LogLineMsg per line. It drops lines until Bind is called; the dev runtime binds the program right after constructing it, so the drop window is bounded by the few lines emitted between sink creation and program start — acceptable for a demo runtime.

func (*Sink) Bind

func (s *Sink) Bind(p *tea.Program)

Bind attaches the bubbletea program. After this call, every complete line is delivered as the sink's message type.

func (*Sink) Write

func (s *Sink) Write(p []byte) (int, error)

Jump to

Keyboard shortcuts

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