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 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
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
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
// 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
// Display-only context for the header bar.
Server 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
// Version is the mecatui build version, shown on the first-run welcome splash
// (e.g. "v0.3.1" or "dev"). Threaded from the shared
// internal/buildinfo.Version (ldflags-set); "" 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) 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 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
- conversation.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
- reflections.go
- render.go
- sanitize.go
- schedule.go
- selection.go
- sessions.go
- sessions_surface.go
- skills.go
- soul.go
- surface.go
- team.go
- textarea_guard.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 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. |