build

package
v0.5.0 Latest Latest
Warning

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

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

Documentation

Overview

Package build constructs live tuilib components and a layout.Node tree from the YAML config. The output is owned by the screen, which holds the component pointers for state preservation across theme swaps.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AnchoredKey added in v0.5.0

func AnchoredKey(cursor, key string) string

AnchoredKey is the key tuilib holds for an Anchored item.

func ApplyChrome added in v0.2.2

func ApplyChrome(themes []theme.Theme, a *cfg.App) []theme.Theme

ApplyChrome writes the config's `app.glyphs` / `app.borders` overrides onto every theme in themes and returns the result.

Every theme, not just the one `app.theme` names: a palette is a choice of color, while glyphs and border shapes are a choice of vocabulary. Cycling palettes at runtime (`t`) walks the whole slice, and a cursor that turned back into "▸" partway through that cycle would read as a bug.

Unset fields are left alone rather than filled in here. tuilib resolves a zero border shape to its own default and a zero glyph via glyph.Default, so "unset" keeps exactly one meaning and it lives upstream — which is also why a palette that ships its own glyphs one day will keep the ones this config doesn't mention.

func ApplyData

func ApplyData(c *Component, data any, th theme.Theme)

ApplyData maps a data source response onto a bound component. Component shape and binding fields decide how the response is consumed:

  • list Cfg.Item (dot-path to display string)
  • table Column.Value (dot-path per column)
  • inspector field.Path (dot-path per field; static Value preserved when Path is empty)

The iterable root is selected via Cfg.Source's root path (already applied in screen.Model before this is called) so data is the already-rooted value. All three updates happen in place; tuilib's SetItems/SetRows/SetFields preserve cursor + filter + InitialDepth pre-expansion across refreshes.

func ApplyRead added in v0.5.0

func ApplyRead(c *Component, rd activity.Read, data any, err error, th theme.Theme) bool

ApplyRead applies a fetch an activity component stamped with BeginRead: the rows, keyed and carrying their data so every row's status is re-evaluated, or the error, which marks reads failing and stops the spinners that were waiting on a read. False means it was dropped: overtaken by a newer read, or failed.

func ApplyStreamLine

func ApplyStreamLine(c *Component, line string, th theme.Theme)

ApplyStreamLine handles one line from a streaming source bound to a non-logview component. Logview-bound streams are handled directly in screen.handleStream (just Append the line); this function exists for the cases that need parsing + projection.

Two table modes, decided by whether Cfg.RowKey is set:

  • Ring buffer (no RowKey): prepend each event as a new row, trimmed to Cfg.MaxRows. The live-tape pattern — chat, log overlays, recent-trades feeds.
  • Keyed upsert (RowKey set): each event identifies the row it updates via Cfg.RowKey. Matching key → update in place; unseen key → append. The L1 order-book / status-grid pattern.

In both modes, lines that don't parse as JSON or that project to all-empty cells are skipped silently — that's how diagnostic / handshake frames ("(connecting to …)", an empty subscription_succeeded confirmation) drop out of the live data view. Color rules apply per cell exactly as they do on a fetch-driven table.

func BeginRead added in v0.5.0

func BeginRead(c *Component) activity.Read

BeginRead stamps a fetch for an activity component when it's issued, so the reply can be told apart from one a newer fetch overtook, and from a read that raced a write. See ApplyRead.

func CursorKey added in v0.5.0

func CursorKey(c *Component) string

CursorKey is the focused row's key, for a selection taken from the cursor rather than from marks. "" for components without keys.

func Dispatch added in v0.5.0

func Dispatch(c *Component, keys []string, label string, mode activity.Mode, settle int) (activity.Op, tea.Cmd)

Dispatch opens a claim on rows of c: they spin with label from now until Done says the work is over (Held) or the data takes over (Observed). settle is this claim's own allowance of unchanged reads (an Observed claim's `settle:`). The command is the spinner's first tick.

func Done added in v0.5.0

func Done(c *Component, op activity.Op, err error) activity.Outcome

Done reports that a claim's request answered, err non-nil when it failed.

func EventlogItems added in v0.5.0

func EventlogItems(c *Component, items []any, th theme.Theme) []eventlog.Item

EventlogItems turns a page of source items into eventlog items: the key and mark plucked by dot-path, the text split into lines with the component's color rules applied per line, and the raw item kept as Data for enter and the inspector.

func HasActivity added in v0.5.0

func HasActivity(c *Component) bool

HasActivity reports whether c shows row activity, and so applies its fetches through BeginRead / ApplyRead rather than ApplyData.

func ItemInspector added in v0.5.0

func ItemInspector(title string, data any, th theme.Theme) inspector.Model

ItemInspector builds the inspector enter opens on an eventlog item when no action is bound to it: the raw item, every field, first level open. An eventlog draws only an item's text, so this is the way to reach the rest of it.

func KeyedTableRows added in v0.2.2

func KeyedTableRows(c *Component, items []any, th theme.Theme) ([]table.KeyedRow, map[string]Selection)

KeyedTableRows projects items exactly as TableRows does, then pairs each row with the identity at Cfg.MarkKey and the Selection that key resolves back to.

The map is what makes a mark usable: tuilib hands back keys, and every consumer downstream wants the row. Building it in the same pass that renders the cells is what keeps the two from disagreeing.

func MarkedCount added in v0.2.2

func MarkedCount(c *Component) int

MarkedCount is how many rows a verb would act on, for a confirm string or a menu title. Mirrors MarkedSelections, so the number the user is shown and the set that runs cannot disagree.

func NewRemoteEventlog added in v0.5.0

func NewRemoteEventlog(c *Component, th theme.Theme, shape remote.Shape[eventlog.Item])

NewRemoteEventlog binds c to its windowed source through tuilib's pkg/remote. Eventlog keeps pointing at the binding's embedded model, so everything that reads the eventlog reads this one.

func NewRemoteTable added in v0.5.0

func NewRemoteTable(c *Component, th theme.Theme, shape remote.Shape[table.KeyedRow])

NewRemoteTable turns c into a table bound to a remote source through tuilib's pkg/remote, which routes the viewport, the filter and the sort to the source and installs what comes back. c.Table keeps pointing at the embedded table, so everything that reads a table reads this one.

func SetTitleSuffix added in v0.5.0

func SetTitleSuffix(c *Component, suffix string)

SetTitleSuffix redraws c's pane title as its configured title plus suffix ("" to clear), for state the screen reports on the border such as "· reads failing".

func SplitAnchoredKey added in v0.5.0

func SplitAnchoredKey(k string) (cursor, key string)

SplitAnchoredKey recovers the cursor and component key from an AnchoredKey. A key with no separator is a plain (Seekable) key.

func Substitute

func Substitute(s string, sel Selection) string

Substitute resolves ${selection.*} and ${env.*} tokens in s against the given Selection. Used by push-site `bind:` resolution to turn a template like `${selection.Namespace}` into the focused row's actual namespace value before that value is passed into a destination source's parameter binder.

func SubstituteAll

func SubstituteAll(argv []string, sel Selection, inputs map[string]string) []string

SubstituteAll runs ${selection.*}/${env.*}/${inputs.*} substitution on every entry in argv and returns a new slice. Used by action dispatch — the run argv references the focused row's selection AND any input values bound or entered moments before dispatch. Pass nil for inputs when there are none.

func SubstituteCursor

func SubstituteCursor(s string, cursor Selection) string

SubstituteCursor resolves both ${cursor.*} (against the live cursor Selection) AND every other namespace (env, input-less) so a cursor bind template like `${cursor.Name}-${env.CLUSTER}` composes cleanly. Selection tokens pass through as literals — this is the runtime path, not the push-time one, and no push-site sel is in scope.

func SubstituteScreen

func SubstituteScreen(s *cfg.Screen, components map[string]*cfg.Component, sources map[string]*cfg.Source, sel Selection, params map[string]string) (*cfg.Screen, map[string]*cfg.Component, map[string]*cfg.Source, error)

SubstituteScreen returns deep-copied Screen + Components + Entries with every ${selection*} token replaced from sel. Original config is left untouched, so each push can re-substitute against a fresh selection. Leaf-kind entries go through the same substitution so a child's URL / headers / body can reference the parent row.

When params is non-nil, every cloned leaf entry that declares `parameters:` ALSO has BindLeafParams called against the subset of params it actually declares. This is how the explicit push-site bind: block feeds into the destination screen's parameterized sources. Missing required params surface as an error so the caller (pushAction) can pop an alert instead of building a broken screen.

Operator entries (filter, sort, …) are cloned without substitution — they don't carry templated string fields and the expression language is bind-time, not screen-time.

Pass nil for params to skip parameter binding (single-screen New and initial multi-screen construction — no push site context). Parameterized sources in that path will be left with unresolved ${params.*} templates and will fail at fetch with a clear URL.

func TableRows added in v0.2.1

func TableRows(c *Component, items []any, th theme.Theme) []table.Row

TableRows projects items through a table component's columns — dot-path pluck per column, then color rules. Shared by the whole-set path (applyTable) and the windowed path (a remote table's Page) so a window's rows are built exactly like any other row.

Types

type Component

type Component struct {
	Cfg  *cfg.Component
	Kind Kind

	// Exactly one of these is non-nil based on Kind.
	List      *list.Model
	Table     *table.Model
	Logview   *logview.Model
	Tree      *tree.Model
	Inspector *inspector.Model
	Textview  *textview.Model
	Eventlog  *eventlog.Model

	// Remote is set on a table bound to a windowed source, which tuilib's
	// pkg/remote drives; Table then points at its embedded table. Every
	// message goes through Remote.Update rather than Table.Update.
	Remote *remote.Table
	// AtNewest is set on a span table anchored at its newest row. tuilib
	// puts a span table's cursor on its first row whenever a new answer
	// lands, which for a view anchored at the newest is the wrong end and
	// leaves it at the older edge, loading older rows by itself. The
	// screen moves it to the newest row instead; landed records which
	// answer it last did that for.
	AtNewest bool

	// RemoteLog is the eventlog's counterpart: an eventlog always binds a
	// windowed source, and Eventlog points at this binding's embedded
	// model once the screen has built it.
	RemoteLog *remote.Eventlog

	// StreamRows is the ring buffer used by KTable components bound to
	// a streaming source. Newest events first; trimmed to Cfg.MaxRows
	// (defaulted to 100 for streaming bindings) on every append. nil
	// for non-streaming tables and non-table kinds.
	StreamRows []any

	// RowsByKey resolves a markable table's mark key back to the row it
	// names. Rebuilt on every data application, alongside the rows
	// themselves, so it can't describe a set the table no longer holds.
	// nil unless Cfg.Markable is set on a table.
	RowsByKey map[string]Selection
	// contains filtered or unexported fields
}

Component is a live, themed component pointer plus the originating config — kept so SetTheme can rebuild the same component against a new theme.

func NewComponent

func NewComponent(c *cfg.Component, th theme.Theme) (*Component, error)

NewComponent builds a Component from a config leaf and the active theme.

func (*Component) LandAtNewest added in v0.5.0

func (c *Component) LandAtNewest()

LandAtNewest puts a span table anchored at its newest row on that row each time a new answer lands: the first page, and the first page of a re-anchor after a filter. Called after every message the remote table handles; it does nothing between answers, so scrolling is left alone.

func (*Component) Rebuild

func (c *Component) Rebuild(th theme.Theme)

Rebuild reconstructs the component against a new theme. Options carry the palette, so tuilib can't restyle a component in place (its rule 4); instead each component's State captures everything the user and the data did to it, and Restore puts that back on the fresh build. That includes the data itself, so a fetched table or list stays populated across a theme swap rather than waiting for a refetch a one-shot source will never make.

Cells already rendered keep the colors they were rendered with: a `theme:` token in a color rule shows the old palette until the next fetch re-renders the rows.

type Kind

type Kind int

Kind enumerates the supported leaf component types.

const (
	KList Kind = iota
	KTable
	KLogview
	KTree
	KInspector
	KTextview
	KEventlog
)

type Selection

type Selection struct {
	// String is the primary representation: a list's selected item, or a
	// table's first cell. Drives bare ${selection}.
	String string
	// Cells are the full row of cells for a table source. Drives
	// ${selection.N} (1-based) and ${selection.COLNAME}.
	Cells []string
	// Columns are the parallel column titles for Cells. Used to resolve
	// ${selection.COLNAME} to the right cell.
	Columns []string
	// Key is the row's identity in its component (a table's mark_key, a
	// list's item, a tree's node path), when it has one: what a claim
	// targets.
	Key string
}

Selection is the data captured from a source component (list or table) when an action binding fires. It seeds the ${selection} token in the pushed screen's config.

func ItemSelection added in v0.5.0

func ItemSelection(key string, data any) Selection

ItemSelection is the Selection for an eventlog item, which has no columns: bare ${selection} is the item's key, and every scalar field of its data is a column named by its dot-path, so ${selection.event} and ${selection.event_data.host} read straight off the raw item.

func MarkedSelections added in v0.2.2

func MarkedSelections(c *Component) []Selection

MarkedSelections resolves a component's live selection into rows.

It follows tuilib's Selection() contract rather than reading marks directly: marked rows when there are any, otherwise the cursor row. That is deliberate — a hand-written "if marked, else cursor" branch is how a verb quietly acts on one row when the user marked six.

Returns nil for kinds that carry no row identity, and for a markable table whose keys have not been installed yet (no fetch has landed).

type Tree

type Tree struct {
	Root       *cfg.Node
	Components map[string]*Component
	Order      []string // names, in the order encountered while walking Root
}

Tree owns the live components and the layout config they're wired into. Components are keyed by the name used in the layout's `component:` refs; Order keeps an iteration order for fanout (Update/SetTheme/Help/etc.).

func Build

func Build(root *cfg.Node, defs map[string]*cfg.Component, th theme.Theme) (*Tree, error)

Build instantiates every component defined in the config (lazily — only names referenced by the layout are built) and wires the layout tree.

func (*Tree) All

func (t *Tree) All() []*Component

All returns components in walk order — handy for screen-level fanout.

func (*Tree) RenderNode

func (t *Tree) RenderNode() layout.Node

RenderNode walks the config tree producing a live layout.Node tree. Component leaves are resolved by name through Tree.Components.

Jump to

Keyboard shortcuts

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