Documentation
¶
Overview ¶
Package ui is the Bubble Tea (Elm) layer of mecatui: the root Model state machine, its Update reducer, the View assembly, and the conversation/block renderers. It imports only the client and theme packages (plus charm libs and stdlib) — never contracts/gen or any internal/... package — so it renders purely from the plain msg structs the client layer translates proto Events into. All glamour rendering happens here on the single update goroutine.
Package ui models_catalog.go owns the durable root-owned model catalog and selection reconciliation state, which survives /models picker instances.
Package ui models_surface.go owns the dynamic picker-local /models surface; durable catalog reconciliation and selection effects remain root-owned by Model.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ClickableRegion ¶
type ClickableRegion struct {
// contains filtered or unexported fields
}
ClickableRegion is a surface body-relative rectangle tagged with an opaque, frame-scoped handle.
type ConnectAction ¶ added in v0.0.22
type ConnectAction uint8
ConnectAction is the closed set of operations main may perform after the TUI exits. The zero value is invalid so an incomplete intent fails loudly.
const ( ConnectSaved ConnectAction = iota + 1 Reauthenticate RetryAfterCleanup AddTarget )
ConnectAction values are the closed set of operations main may perform after the TUI exits.
type ConnectController ¶ added in v0.0.22
type ConnectController interface {
ListConnectTargets(context.Context) ([]ConnectTarget, error)
}
ConnectController is the narrow composition-owned saved-target listing seam. The UI can only display its public metadata and emit an intent; it cannot authenticate or launch a browser.
type ConnectRestartIntent ¶ added in v0.0.22
type ConnectRestartIntent struct {
Target string
Action ConnectAction
// ResumeSessionID is retained only for same-target recovery actions. Main
// re-checks ownership through GetSession before adopting it.
ResumeSessionID string
}
ConnectRestartIntent is consumed by main after Bubble Tea exits. Target is public canonical registry metadata only.
type ConnectTarget ¶ added in v0.0.22
ConnectTarget is public saved-target metadata. It deliberately has no token, credential reference, CA contents, or other secret-shaped fields.
type Converser ¶
Converser opens one Converse run as a *client.Stream. *client.Client satisfies it (its OpenConverse, wrapped to fix the mode/ctx); tests supply a fake that returns a Stream over a scripted Recver.
type Deps ¶
type Deps struct {
Session SessionCreator
Conv Converser
MCP client.MCP // MCP/ToolHive inventory + resources/prompts; nil disables the overlay
Cmds client.Commander // slash-command discovery for the input palette; nil disables it
// ServerInfo reads the safe build and composition identities when /diagnostics is invoked against a remote server.
ServerInfo ServerInfoGetter
// ServerImpl is the locally-known embedded server family. It is used without
// an RPC when Embedded is true.
ServerImpl string
Skills client.SkillLister // skills-inventory discovery for the /skills panel; nil disables it
Agents client.AgentLister // agent-definition discovery for the /agents panel; nil disables it
Soul client.SoulFetcher // soul (persona) inspection for the /soul panel; nil disables it
UserModel client.UserModelLister // user-model inspection for the /usermodel panel; nil disables it
Reflections client.ReflectionClient // proposal review and explicit reflection; nil disables it
Dream client.DreamClient // manual memory consolidation review; nil disables /dream
Compactor client.SessionCompactor // out-of-band session compaction; nil disables /compact
Models client.ModelLister // selectable-model discovery for the /models picker; nil disables it
Worktrees client.WorktreeLister // worktree discovery for the /worktrees overlay (issue #102); nil disables it
// Sched is the schedule discovery + management surface for the /schedule overlay
// (issue #234); nil disables it (the overlay is honestly absent). The overlay can
// create/inspect/pause/resume/fire-now on any store-backed server; auto-firing on
// a cadence is the server's tick loop (ON by default on a schedule-capable store,
// ADR 0073 — `mecated --no-scheduler` opts out).
Sched client.ScheduleLister
// Sessions is the stored-session inventory surface for the /sessions picker
// (issue #245 Phase 2); nil disables it (the overlay is honestly absent). It is
// the lister the picker calls to enumerate stored sessions. Unlike the
// caps-gated overlays it is NOT gated on a ServerCapabilities bit — the picker
// is available whenever a lister + authoritative transcript loader are wired
// (a no-FS/cloud server with a durable SessionStore still has stored sessions).
Sessions client.SessionPager
// StorageHealth is the authenticated aggregate health surface. The capability
// bit controls whether the Sessions panel advertises its maintenance tab.
StorageHealth client.StorageHealthFetcher
// Migration and Cleanup are deliberately distinct management seams. Their
// server capability bits independently gate the semantics-preserving and
// destructive workflows.
Migration client.SessionMigrator
Cleanup client.SessionCleaner
// SessionManagement mutates stored main-chat metadata. nil leaves rename/delete
// undiscoverable even if a custom lister advertises those capabilities.
SessionManagement client.SessionManager
// Adoption is the authenticated legacy-copy surface. Eligibility is always
// taken from its source-correlated preflight, never inferred from row IDs.
Adoption client.SessionAdopter
// Transcript is the authoritative snapshot-derived conversation surface used
// by /sessions for both continuation and read-only inspection. Event replay is
// optional activity and never substitutes for this seam.
Transcript client.SessionTranscripter
// Replayer is the optional durable-event-log activity surface used by live
// delivery catch-up. It never attests conversation completeness.
Replayer client.SessionReplayer
// LiveStream is the LIVE per-session event feed (ADR 0075 Scenario 5): the server
// pushes fire-result delivery notes for the active session as they occur. The ui
// holds the interface (not a *Client) so it is injectable with a fake for offline
// tests. nil disables the live bridge (the ui still renders deliveries via the
// replay on a session switch/reload, just not live). *Client satisfies it.
LiveStream client.LiveStreamer
// SelectionStore persists the picked model (last-used). nil disables persistence
// (the pick still applies to the next create this run, just isn't remembered).
SelectionStore SelectionStore
Learning LearningSettings // operator mecatl settings.yaml; nil disables /learning
// Connect lists public saved remote-target metadata. It cannot authenticate,
// launch a browser, or expose credentials; selection exits via ConnectRestartIntent.
Connect ConnectController
// ConnectOpen reopens /connect after a host-side login/dial failure. ConnectError
// is host-sanitized display text only.
ConnectOpen bool
ConnectError string
// ConnectReason controls the auth-recovery affordance without exposing a
// transport error or credential to the renderer.
ConnectReason client.AuthReason
ConnectTarget string
ConnectResumeSessionID string
// BearerBacked records credential provenance for classifying server auth
// responses. It contains no credential material.
BearerBacked bool
// InitialModel is the persisted selection loaded at launch (composition-side,
// from the state file). The picker seeds its active selection from it (the ●
// marker) and the startup CreateSession carries it — AFTER the connect-time
// ListModels reconcile clears it if its PROVIDER is no longer available. The
// reconcile is provider-level only (issue #41): a model absent from the (possibly
// embedded-floor) snapshot is still carried verbatim — the server validates it,
// and a rejection falls back to the default loudly (connectFallbackMsg).
InitialModel client.ModelSelection
// WorkspaceDefault / GlobalDefault are the SEPARATE raw state-file values loaded at
// launch (composition-side): the per-workspace entry (zero when none — see
// WorkspaceDefaultSet) and the global `default:` block. They are display-only
// provenance inputs for the /models picker's "current: <model> (<provenance>)"
// line and the ★ global-default row marker — the ui derives a best-effort label
// from client-held state, never a server round-trip. (InitialModel is the RESOLVED
// Load result = workspace-or-global; these are the un-collapsed pieces.)
WorkspaceDefault client.ModelSelection
WorkspaceDefaultSet bool
GlobalDefault client.ModelSelection
// Clipboard reads the OS clipboard for ctrl+v paste (image-first, text-fallback).
// nil cleanly disables ctrl+v image paste (same convention as nil MCP/Cmds);
// main.go populates it with client.NewClipboard().
Clipboard client.Clipboard
Theme theme.Theme
// ThemeAutoDetect enables the terminal-background light/dark auto-detect
// (ADR 0280): composition sets it true only when no explicit --theme/
// MECATUI_THEME was supplied AND stdout is a real TTY (never on redirected
// output, which must never see the OSC background-colour query). When true,
// Init requests the terminal's background color (tea.RequestBackgroundColor)
// and a light response switches the active theme to the built-in "solar"
// theme; a dark or absent response keeps Theme as given. Explicit theme
// selection always wins — this field is simply never set true then.
ThemeAutoDetect bool
// StatusSource is composed outside ui. The UI only submits display facts and
// consumes semantic snapshots through one Bubble Tea listener.
StatusSource statusline.Source
// ClientBuild is the local mecatui build identity and Embedded selects the
// local server identity path for /diagnostics.
ClientBuild string
Embedded bool
// Display-only context for the header bar.
Server string
ConnectionMode string
Workspace string
Mode string
Model string
// Resume is a statically validated existing chat selected before Bubble Tea
// starts. Its authoritative transcript is adopted without CreateSession; nil
// preserves the new-session default.
Resume *client.ResumeSelection
// BrowseSessions launches into the same inventory used by /sessions without
// creating a throwaway session. New-chat creation remains gated on the startup
// model-list reconcile.
BrowseSessions bool
// InitialPrompt is a CLI-supplied seed prompt auto-submitted once the first
// session is ready (the equivalent of typing the prompt and pressing enter).
// Empty = today's behavior (no seed). Cleared after the first use so a
// /models restart or /clear never re-submits it. Populated by main.go from
// -p/--prompt + --prompt-file.
InitialPrompt string
// DebugTarget is immutable launch metadata for a dedicated analysis session.
// It is presentation/control state only; the client adapter owns wire projection.
DebugTarget string
// Version is the mecatui build identity, shown on the first-run welcome splash
// (e.g. "v0.3.1", "dev+0123456789ab", or "dev"). Threaded from the shared
// internal/buildinfo.BuildID; "" omits the version line.
// Display-only.
Version string
// NoBanner suppresses the rich first-run welcome splash (mascot + gradient
// wordmark): the zero-state then renders the LEGACY plain card (title + prompt
// hint + affordances). Set by --no-banner, --quiet, or a non-interactive stdin
// (composed in main). Default false (full splash).
NoBanner bool
// Ctx is the program-level context; per-run stream contexts derive from it.
Ctx context.Context //nolint:containedctx // stored to parent per-run stream cancels
// NoAltScreen disables the alternate screen buffer, rendering inline in the
// terminal's normal buffer. Default false (full-screen TUI on the alt screen).
// Set true by the --no-alt-screen/--inline flag — a first-class user opt-out
// for streaming the session into native scrollback (so it stays
// searchable/scrollable after exit) — and by golden tests so the final frame
// persists in the captured output instead of being cleared on exit.
NoAltScreen bool
// NoMouse disables mouse capture on the alt screen (View leaves MouseMode at
// MouseModeNone), so the terminal's OWN click-drag selection works again — at
// the cost of in-app mouse-wheel scroll and the in-app drag-select/copy layer
// (selectable() returns false). Keyboard scroll is unaffected. Default false
// (mouse captured). Set true by --no-mouse / MECATUI_NO_MOUSE=1. Inert under
// NoAltScreen (mouse is already off inline). The escape hatch for terminals
// that strip OSC52 or users who prefer native selection.
NoMouse bool
// NoWindowTitle suppresses the dynamic terminal window/tab title, leaving the
// title at the bare "mecatui" (no phase word, no session title). Default
// false (the title is dynamic: "<title> — <status word> mecatui"). Set true by
// --terminal-title=off / MECATUI_NO_TERMINAL_TITLE=1 — the escape hatch for
// terminals/multiplexers where a set title does more harm than good (or where
// the per-phase churn is unwanted).
NoWindowTitle bool
// DebugMouse turns on a footer diagnostic overlay (env MECATUI_DEBUG_MOUSE=1):
// on every mouse press/motion the footer-left is overridden with the raw mouse
// coordinates and their content mapping (top=convTopRow, yoff, viewport height,
// and the screenToContent result) — the durable instrument for diagnosing
// selection/coordinate issues (it is what surfaced the highlight-on-wrong-line
// bug). Default OFF (zero cost when unset); main.go reads the env var.
DebugMouse bool
// DebugSteer turns on a steer correlation trace in the status line (env
// MECATUI_DEBUG_STEER=1): each steer ack/echo logs the incoming message_id,
// the live bundle's id, and the match/burn/drop decision, so a stuck or
// mis-correlated steer lifecycle is visible in the TUI rather than opaque.
// Default OFF (zero cost when unset); main.go reads the env var.
DebugSteer bool
// DebugAsk registers the /debug-ask built-in (env MECATUI_DEBUG_ASK=1): it
// injects a fake permission ask with long Bash args through the REAL ask
// reducer, so the modal's wrap/scroll/full-screen-args behaviour (issue #488)
// can be exercised by hand without driving a live run. Default OFF (the
// built-in is absent); main.go reads the env var — deliberately never a flag,
// so it stays out of --help.
DebugAsk bool
// KeyOverrides maps a keyMap field name (e.g. "Agents") to its replacement chord(s).
// nil = no overrides, byte-identical to today. Resolved+validated in composition.
KeyOverrides map[string][]string
// contains filtered or unexported fields
}
Deps are the ui's injected collaborators and presentation config. The ui imports client + theme only — never contracts/gen or any internal/... package; all proto contact happens behind Converser/SessionCreator.
type HitID ¶
type HitID uint64
HitID is an opaque, frame-scoped handle for a clickable surface region. It has no model-wide meaning: only the surface that minted it during the current View frame may interpret it.
type LearningSettings ¶
type LearningSettings interface {
Advance() (fromLabel, toLabel, restart string, err error)
AdvanceSensitivity() (fromLabel, toLabel, restart string, err error)
}
LearningSettings atomically advances the operator's completed-trajectory learning mode and returns display-ready labels plus restart guidance. Policy ordering stays outside ui.
type Model ¶
type Model struct {
// contains filtered or unexported fields
}
Model is the root Elm model. It owns the conversation, the bubbles widgets, the renderer (glamour cache), the active run stream, and the per-run cancel func.
func New ¶
New builds the root model from deps. It wires the widgets but does not connect; Init kicks off CreateSession.
func (Model) ActiveSessionID ¶
ActiveSessionID returns the currently bound opaque session ID. The process entry point reads it only after Bubble Tea has restored the terminal.
func (Model) ConnectRestartIntent ¶ added in v0.0.22
func (m Model) ConnectRestartIntent() (ConnectRestartIntent, bool)
ConnectRestartIntent reports a deliberate internal restart request. It is intentionally a value with only public metadata.
func (Model) Init ¶
Init starts the spinner and kicks off connect.
Connect SEQUENCING (§4 key-removed safety): when a model lister is wired, it fetches ListModels FIRST and lets the connecting-phase ModelsMsg reconcile the persisted selection against PROVIDER availability BEFORE firing CreateSession — so a removed provider key can never hard-fail the connect with InvalidArgument. The reconcile is provider-level only (issue #41): the model string rides through verbatim (the boot snapshot may be the embedded floor), the server validates it, and a server rejection degrades to the default loudly via createSessionCmd's fallback leg. With no lister wired (old server / persistence off) it fires CreateSession directly (the historical path, with an empty selection).
func (Model) Update ¶
Update is the Elm reducer. It is split by message type; all model mutation and all glamour rendering happen here on the single update goroutine (the stream reader never touches the model). After most state changes it calls refreshView to re-render the conversation into the viewport.
Streamed deltas (AssistantDeltaMsg/ReasoningDeltaMsg) do NOT refreshView per token: they append to the conversation and mark the view dirty (markDirty), which arms a single one-shot renderTickMsg (~16ms ≈ one 60fps frame) that flushes the dirty view at most once per frame and disarms. The invariant is "ONLY the delta cases defer; every other transition force-flushes" — turn/tool/result/error AND the permission.ask gate all flush (via afterEvent/endRun), so the pending tail is always rendered before any boundary (the final frame and event ordering are unchanged; only the per-token re-render churn is coalesced). refreshView clears viewDirty, making "rendered ⟺ not dirty" an invariant.
Within each 16ms flush, the per-BLOCK render cache (renderer.blockCache, keyed on block.rev/width/expand) means only blocks whose rev, the wrap width, or the expand toggle changed actually re-render — in practice just the live tail block; every settled block joins the conversation string from cache. The selection splice and vp.SetContent still see the full joined string, so selection/scroll behaviour is unchanged.
func (Model) View ¶
View assembles the three-region layout (header / viewport / input / footer) into a tea.View. While a permission modal is open it overlays the modal, centred, over the conversation region. Bubble Tea v2 returns a tea.View struct (not a string); we set Content and request the alt screen.
type SelectionStore ¶
type SelectionStore interface {
Save(workspace string, sel client.ModelSelection) error
SaveGlobalDefault(sel client.ModelSelection) error
}
SelectionStore persists + loads the client-side model selection (last-used). It is satisfied by a main-owned concrete type backed by an XDG state file; nil cleanly disables persistence (the active selection then lives only for the run). The ui touches no os/xdg itself — persistence is composition-side, like SessionCreator. Save is given the workspace so the store can key per-workspace; SaveGlobalDefault writes the workspace-agnostic global `default:` block (the model new/unseen workspaces inherit) — the picker's ctrl+g affordance.
type ServerInfoGetter ¶ added in v0.0.22
type ServerInfoGetter interface {
GetServerInfo(ctx context.Context, providerID string) (client.ServerInfo, error)
}
ServerInfoGetter reads safe identity and sanitized diagnostic display data for the caller's already-known active provider from a remote server. It never returns connection instructions.
type SessionCreator ¶
type SessionCreator interface {
CreateSession(ctx context.Context, sel client.ModelSelection, mode string) (string, client.Capabilities, client.ResolvedModel, error)
// CreateSessionInWorkspace creates a session bound to an explicit workspace
// root (the /worktrees switch path, issue #102). CreateSession (above)
// delegates to this with the launch workspace, so the /models restart + the
// connect paths are byte-identical and only the /worktrees switch passes a
// different root. The workspace becomes the session's tool root (Read/Edit/
// Write/Grep/Glob/Bash cwd all resolve there); osfs confinement is unchanged.
CreateSessionInWorkspace(ctx context.Context, workspace string, sel client.ModelSelection, mode string) (string, client.Capabilities, client.ResolvedModel, error)
// CreateSessionWithCarryover is CreateSession seeded with sourceSessionID's
// conversation history (issue #20, model-switch carryover): it restarts on a
// picked model AND carries the current session's transcript onto the new
// session. The server is the authority on same-vs-cross (a same-provider
// carryover replays verbatim; a cross-provider carryover strips the prior
// provider's replay blobs) and a turn-boundary source; the ui offers the
// switch unconditionally when a live session exists. The caller owns closing
// the source session AFTER the new one is ready (the server snapshotted it
// at create time). Same return shape as CreateSession so the
// footer/effective-model heal path is shared.
CreateSessionWithCarryover(ctx context.Context, sourceSessionID string, sel client.ModelSelection, mode string) (string, client.Capabilities, client.ResolvedModel, error)
// CloseSession ends a server-side session by id. The /models restart-now handoff
// closes the OLD session before creating the new one so a model switch leaves no
// orphaned server-side session. Best-effort: the caller proceeds with the new
// create even if the close errors.
CloseSession(ctx context.Context, id string) error
// GetSession re-reads the EFFECTIVE resolved model for an existing session. The
// footer context-meter heal (issue #66) fires it on a turn boundary while the
// meter's denominator is still unknown — the create-time echo can carry a 0 /
// curated-floor window for a session on a LIVE-ONLY model whose async live
// model-list swap had not yet landed, and the server's ResolvedModel resolves to
// the real live window once it has. SessionCreator is the canonical ui-injection
// seam for it (the method also satisfies the narrower client.SessionGetter that
// RefreshResolvedModelCmd consumes); *client.Client (via the sessionAdapter) and
// the test fakes satisfy both.
GetSession(ctx context.Context, id string) (client.SessionSnapshot, error)
// SetMode asks the server to change the current session's permission mode and
// returns the server-confirmed mode. Mid-turn attempts may be rejected; the ui
// defers and retries at the next prompt boundary.
SetMode(ctx context.Context, id, mode string) (string, error)
// ForkSession creates a peer session from srcID's conversation-history snapshot
// (ADR 0065) with an OPTIONAL reasoning-effort override (ADR 0068; empty
// inherits the source's) and returns the new session id. The /effort fork-resume
// handoff uses it: the transcript SURVIVES the effort switch because the fork
// carries it (title is omitted — the fork inherits the source's title; provider
// and model ALWAYS inherit). The caller owns closing the source session and the
// GetSession refetch for the fork's resolved-model echo.
ForkSession(ctx context.Context, srcID, reasoningEffort string) (string, error)
}
SessionCreator creates a server-side session and returns its id together with the server's advertised capabilities. *client.Client satisfies it (via the sessionAdapter); tests supply a fake. Keeping it an interface lets the ui be driven entirely offline. Capabilities is the proto-free relayed truth the ui stores for its honest discoverability affordances (Phase B); an older server yields the all-false zero value. ResolvedModel is the EFFECTIVE provider+model the server resolved the session to (echoed verbatim); the ui shows it in the header from turn zero, and an older server yields the zero value (no model segment).
Source Files
¶
- agents_inventory.go
- agents_overlay.go
- approval.go
- approval_surface.go
- builtins.go
- clipboard.go
- connect.go
- conversation.go
- diagnostics.go
- dream.go
- effort.go
- emoji.go
- footer.go
- geom.go
- help.go
- hit_regions.go
- keys.go
- layout.go
- mcp.go
- mention.go
- model.go
- models.go
- models_catalog.go
- models_surface.go
- palette.go
- prompt_input.go
- reflections.go
- render.go
- sanitize.go
- schedule.go
- selection.go
- sessions.go
- sessions_surface.go
- skills.go
- soul.go
- statusline.go
- statusline_source.go
- surface.go
- team.go
- update.go
- usermodel.go
- view.go
- window.go
- wintitle.go
- worktrees.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package platform reports host-platform facts the TUI adapts its presentation to.
|
Package platform reports host-platform facts the TUI adapts its presentation to. |
|
Package prompttextarea owns the upstream textarea behind Mecatl's prompt-editor policy.
|
Package prompttextarea owns the upstream textarea behind Mecatl's prompt-editor policy. |
|
Package welcome renders the first-run splash for the mecatui zero-state: a faithful mascot (half-block on any truecolor terminal, a zero-dependency kitty Unicode-placeholder high-res path on capable terminals), a gradient "mecatl" wordmark, and an info block.
|
Package welcome renders the first-run splash for the mecatui zero-state: a faithful mascot (half-block on any truecolor terminal, a zero-dependency kitty Unicode-placeholder high-res path on capable terminals), a gradient "mecatl" wordmark, and an info block. |