widget

package
v1.7.3 Latest Latest
Warning

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

Go to latest
Published: Sep 5, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package widget holds kb's hand-crafted TUI elements: the last resort of the charm-first sourcing rule of map #136, allowed only for the elements charm does not ship. Their reference shape is crush's internal/ui/common.

Every function here is a pure render helper. State lives with the caller, and so do styles: a widget takes a *theme.Styles and the palette slot of the surface it is drawn onto, because no code under internal/tui outside the theme package may construct a lipgloss style.

Index

Constants

View Source
const (
	DensityNormal  = theme.DensityNormal
	DensityCompact = theme.DensityCompact
)

The two densities, re-exported so callers of this package do not need to name the theme package for a widget argument.

View Source
const ScrollbarW = 1

ScrollbarW is the column a shown scrollbar spends. The column is reserved for the whole time the body overflows and never appears or disappears with activity: kb composes strings, so a column that came and went would reflow the body measure of every row under it, twice, for a cue (spec sections 10.3.4 and 10.4.4).

Variables

This section is empty.

Functions

func Band

func Band(styles *theme.Styles, opts BandOpts) string

Band renders one column header band. Unfocused it sits on the Raised tier with secondary text; focused it uses the shared accent on the same neutral surface and the rail becomes a focus caret.

The status dot survives the focus change. Spec section 10.4.4, ratified as contestable call 11: dropping it moved the label from column 5 to column 4, so moving focus across the board jittered every label one cell. Keeping the dot restores the BandHeadW reserve in both states and holds the label column fixed, and it costs nothing.

func Brand

func Brand(o BrandOpts) []string

Brand renders the launch block of spec section 10.6: the mark, the blank rows under it and the meta row, one string per row, each carrying On edge to edge at the caller's frame width. Vertical placement is the caller's, which is what keeps the block itself frame-height independent.

Below the frame floors of section 10.6.7 the mark is dropped and the meta row is returned alone, because the meta row is the half that carries facts.

func BrandMarkWidth

func BrandMarkWidth(metrics theme.Metrics, stretch int) int

BrandMarkWidth is the assembled mark's column count: the two letterforms, the kern between them and the memoized stretch.

func Busy

func Busy(styles *theme.Styles, opts BusyOpts) string

Busy renders the loading row of spec section 10.8.4 as exactly one row: frame, BusyGap columns, then the label in FgSubtle on the row's own surface.

The label is lowercase and present continuous and carries no ellipsis, because the animation is the ellipsis. A label too long for the row is cut with the section 3.3 primitive rather than hard-truncated, so a narrow column still says that something was dropped.

func Button

func Button(styles *theme.Styles, opts ButtonOpts) string

Button renders one button. State precedence is armed, then selected, then hovered, then resting: the two-step confirm has to win, because it is the state the user must not misread.

func ButtonGroup

func ButtonGroup(styles *theme.Styles, on theme.Slot, gap int, buttons ...string) string

ButtonGroup lays buttons out left to right with gap cells between them. The gap is rendered on the surface behind the group so the group does not punch a hole in the shade tier it sits on.

func Card

func Card(styles *theme.Styles, opts CardOpts) []string

Card renders one card as its content rows, without the inter-card gutter: stacking and gutters belong to the panel. Spec section 3.1 as issue #243 re-cut it: title rows, description rows when there is a description, one meta row and label rows when there are labels, each taking what it needs under the section 2.6 ceiling; two rows when compact.

func CardHeight

func CardHeight(styles *theme.Styles, opts CardOpts) int

CardHeight is the number of rows Card will draw for these options. It is the measure half of the measure-before-render rule of spec section 2.5: the column packs its stack against this and the card then draws exactly it, because both come out of the same plan.

func Check

func Check(styles *theme.Styles, label string, state CheckState, on theme.Slot, focused bool) string

Check renders one checklist row: the state mark and its label, hued by state so a finished item reads as finished without a second column.

func Chip

func Chip(styles *theme.Styles, opts ChipOpts) string

Chip renders one pill. Cost is width(Mark)+width(Key)+width(Text)+2 columns for the padded form and width(Mark)+width(Text) for the flat form, in every state (spec section 10.4.4): the padding is one cell per end whatever the state, spec section 10.5.1 spends an underline on the body run for hover, and keyboard focus bolds that same run. Both cues are attributes, so no state in the widget moves a column.

func Effort

func Effort(styles *theme.Styles, value string, on theme.Slot, flat bool) string

Effort renders the effort marker of spec section 3.4. Issue #232 replaced the colored square the marker wore between #223 and #230 with the effort letter on a colored fill: a one-letter pill in the section 3.6 anatomy, pad, letter, pad, three cells at normal density.

The pill form is the anatomy verbatim rather than a tighter one because the tighter one already means something. A colored letter with no padding is the flat chip of section 2.6 step 7, the compact degradation of a pill, and spending it at normal density would say the row had run out of width when it had not. The compact row does spend it, and the marker is one cell there.

ASCII on background color is the whole of it: the letter carries the value, the hue reinforces it, and neither depends on a terminal font drawing a pictograph. That is the trade #232 recorded - the squares carried one of three values and lost which one whenever a font substituted or clipped them.

A value the S/M/L scale does not name keeps the Diamond fallback the marker wore before the squares: the mark, the column section 10.4.1's adjacency rule gives it, and the value as the board spells it.

func Empty

func Empty(styles *theme.Styles, opts EmptyOpts) string

Empty renders the empty state of spec section 10.8.3 as exactly one row: the Empty glyph in FgMuted, a lowercase headline in FgSubtle, then the action tail, whose key is FgBase bold because it is the only part of the row the user has to act on.

The width ladder is applied whole at each rung rather than scanned part by part, because the tail is the actionable half and a narrow surface is more useful saying what to press than saying what is missing:

>= EmptyHeadlineMin  glyph + headline + tail
>= EmptyActionMin    glyph + tail
below                glyph alone

The rule this exists to enforce is that no surface renders a bare blank panel. A section that is simply absent when it holds nothing stays absent; this row is for a surface that would otherwise have no body at all.

func Error

func Error(styles *theme.Styles, opts ErrorOpts) []string

Error renders the error block of spec section 10.8.5 as one to MaxLines rows.

It owns the whole treatment: sanitize, greedy wrap to the caller's measure, the hanging indent that makes the block read as one object, the truncation mark of spec section 3.3 - never the bare ansi.Truncate the fit helpers use - and the retry tail.

The hue is the message's foreground and never a filled chip. Spec section 10.8.5 measured the pairs: StatusDanger fails AA on OverlaySurf at 2.96, so a panel takes TintDanger at 4.91 and the board tiers keep StatusDanger.

func Field

func Field(styles *theme.Styles, label, value string, width int) string

Field renders one key/value row of spec section 4: the label at inset OverlayInsetX in FgMuted, a fixed OverlayLabelW gutter, and the value in FgBase at inset plus the gutter.

func FieldRun

func FieldRun(styles *theme.Styles, label, value string, width int) (row string, column, cells int)

FieldRun is Field for a value that arrives already styled: the blocker chip row of issue #222, whose chips carry their own hover and pressed runs and so cannot be handed to FieldValue as text. Layout is Field's, cell for cell, so a row that gains an activatable chip moves nothing (spec section 10.4.4).

It reports where the value field starts, as a cell column from the row's own left edge, and how many cells that field holds. Those are the recorded bounds spec section 10.5.3 asks a widget-rendered control to be anchored at: the caller placed the runs inside the value, so it knows their offsets, and never has to recover them by scanning the rendered row.

func FieldWrap

func FieldWrap(styles *theme.Styles, label string, runs []string, width int) []string

FieldWrap is Field for a value that arrives as already-styled runs rather than as text: the label pill row of spec sections 3.5 and 3.6, whose runs carry their own fills and cannot be handed to FieldValue.

The runs are laid left to right one column apart and wrap whole to the next row, which keeps the caps of section 3.6 paired: a pill is the smallest unit the row can break on, and one split across two rows would read as two pills with one cap each. A run wider than the value field is truncated onto a row of its own rather than dropped, because a field row is the card's only statement of that label. Continuation rows repeat the gutter and leave the label column blank, so the value column is one straight edge.

func FilterLabel

func FilterLabel(styles *theme.Styles, tag string, on theme.Slot, selected, focused, hovered bool) string

FilterLabel renders one filter-bar label pill: the section 3.6 pill the board cards already carry, plus the two states the toolbar needs on top of it.

Both states keep the label's muted wheel fill, so the filter stays matchable by eye to the same label on a card. The leading mark distinguishes selected from unselected without changing the tag's color identity. focused bolds the body run, the zero-cell cue that replaced the thickened end caps when issue #227 retired them. Neither changes a cell count, so toggling or traversing a label never reflows the toolbar (section 10.4.4), and the leading toggle mark keeps both distinctions legible at the flat fidelity floor, where neither hue nor tier survives.

func Gutter

func Gutter(styles *theme.Styles, focused bool, accent, on theme.Slot) string

Gutter renders the focus gutter of spec section 10.4.3: one column carrying the Rail glyph in accent when the row has the keyboard and a surface space when it does not, followed by FocusGutterGap columns of surface.

The gutter is always reserved. Both states cost FocusGutterW + FocusGutterGap cells, which is the whole point: a row that only drew its bar when focused would reflow its own text as focus moved onto it (section 10.4.4).

It is a literal cell rendered here rather than a lipgloss left border, because the widget owns the wrap and so owns the continuation lines, and because a block-level border paints cells the row's own background does not reach under.

func Highlight

func Highlight(base, hit lipgloss.Style, text string, matched []int) string

Highlight renders text in base with the matched runs re-rendered in hit.

Spec section 10.4.4: the cue costs zero cells. Both styles are the caller's already-cached tokens and this composes them, so the rule that only the theme package builds a style is untouched.

func Hints

func Hints(styles *theme.Styles, ladder Ladder, width int) (string, []int)

Hints packs a ladder to width and reports the start column of every rung it admitted, in the order Head, Middle, Tail. A dropped rung reports -1, and so does every rung on the truncating path: a truncated line has no offset a hit region could trust.

The separator belongs to the rung that follows it, so dropping a rung drops its leading separator with it and a packed line can never begin or end with HintSep. A dropped rung is terminal: the rungs behind it are not attempted, which is the opposite of the meta chip row of section 3.4 and is deliberate. A chip row is a set of independent facts about one card; a ladder is ordered by importance, and admitting a short rung after dropping a more important one misreports what the ladder is for.

The hit regions of a rendered ladder are built from the returned columns, never by re-splitting the rendered line on the separator: a rendered separator carries its own SGR runs and is not a safe split key.

func Hotkey

func Hotkey(label string, message tea.Msg) (text string, underline int)

Hotkey resolves a button's rendered label and its underline offset from the message the control already sends. Spec section 10.4.2: every button label underlines exactly one rune, the key that drives it, and the resolver is a display convention that never touches the keymap.

The three steps are normative:

  1. the message is not a single printable rune with no modifier: the label is unchanged and carries no underline;
  2. the label spells the rune, case-insensitively: the first matching rune offset is underlined;
  3. otherwise the key is appended as " (k)", four cells for a one-cell key, and the underline lands on the key inside the parentheses.

The returned offset is a rune offset into the label, never into a rendered button: a primitive that styles the padded string offsets it by the button's left padding, and Button, which renders its padding runs separately and slices the label, does not.

func Label

func Label(styles *theme.Styles, tag string, on theme.Slot, flat, hovered bool) string

Label renders one label pill, wheel-hued by the hash of spec section 1.6. hovered underlines the body run per section 10.5.1 and costs no cell in either form.

func LabelWheel

func LabelWheel(tag string) int

LabelWheel is the label color hash of spec section 1.6, unchanged. It lives beside the palette now, because the per-board accent of section 10.7.2 derives from the same wheel and the two must not fork.

func MarkRun

func MarkRun(styles *theme.Styles, mark, content string, style lipgloss.Style, on theme.Slot) string

MarkRun renders one composed run that may begin with a display mark and the column spec section 10.4.1's adjacency rule gives that mark.

A mark two cells wide is a color pictograph: section 10.4.1 admits a pictograph only as a single East Asian Wide code point, and East Asian Wide is exactly what Emoji_Presentation guarantees, so a two-cell mark paints its own colors and has no foreground to lose. Such a mark is also drawn by font machinery whose advance is wider than the two columns the cell grid gives it, and a terminal draws one styled run as one shaped run: every glyph after the pictograph inside that run is drawn pushed right by the excess, and the run after it - placed by the cell grid rather than by the pen - is painted over the tail of the last glyph. That is what cut the effort chip's letter in half and the last character off an emoji-bearing card title in issue #229.

The mark and the column it owns are therefore a run of their own, painted in the ground they sit on: the overhang stays on the column the adjacency rule bought for it, and the text beside it starts a fresh run on the cells the grid gave it. A one-cell mark keeps the single run - it has a real foreground and no advance to overrun - and so does a run the caller already truncated past its own mark.

func MatchRuns

func MatchRuns(text string, matched []int) [][2]int

MatchRuns coalesces the byte offsets a fuzzy search matched into contiguous display-column runs, each a half-open [start, end) pair.

Two conversions happen here and nowhere else. The first is offsets to columns: sahilm/fuzzy reports byte offsets into the haystack, while lipgloss.StyleRanges cuts by display cell, so a match on a multi-byte or double-width rune would otherwise land on the wrong cells. Runs are measured per grapheme cluster, which is what keeps a combining mark styled with the rune it modifies rather than as a zero-width run of its own.

The second is coalescing: adjacent matched clusters become one run. A fuzzy hit on "select card" for the query "sel" is three offsets and must be one range, because StyleRanges re-renders its style around every range it is given and three abutting ranges emit three redundant SGR pairs for one word.

Offsets outside text, and repeated offsets inside one cluster, are ignored: the caller passes a library's output straight through and this is where it is made safe rather than at every call site.

func Meter

func Meter(styles *theme.Styles, opts MeterOpts) string

Meter renders the gradient progress pill. The bar is the adopted progress component with the GradMeter ramp spanning its full width and the fill cutting it, wrapped in the section 3.6 end caps: the left cap in the ramp's lead color, the right in its tail.

Cost is Cells+2 columns. Below MeterMinCells the caps and the bar are dropped and the empty string is returned, so the caller's own i/N text stands alone.

func MeterWidth

func MeterWidth(styles *theme.Styles, cells int) int

MeterWidth is the cell cost of a meter of this bar width, end caps included, or zero when the width is below the floor that drops the bar.

func Overlay

func Overlay(styles *theme.Styles, opts OverlayOpts) string

Overlay renders the panel of spec section 4 steps 3 to 6: the OverlaySurf fill, the solid Brand header band, the body rows and the OverlayBand footer. Rows the body does not fill carry the panel surface to the edge.

func OverlayLayers

func OverlayLayers(styles *theme.Styles, opts OverlayOpts, x, y int) []*lipgloss.Layer

OverlayLayers returns the panel and its drop shadow as compositor layers anchored at x, y. Spec section 4 step 2: two Shadow-filled bands offset one cell down and right of the panel. The shadow is folded in here and is never separately callable, so no caller can elevate a panel without it.

func OverlayRow

func OverlayRow(styles *theme.Styles, content string, width int) string

OverlayRow renders one body row: already-styled content inset OverlayInsetX, padded to the panel width so the row carries the panel surface edge to edge.

func OverlayRowOn

func OverlayRowOn(styles *theme.Styles, content string, width int, on theme.Slot) string

OverlayRowOn is OverlayRow on a named surface, which is how a choice row wears its hovered fill. Spec section 10.5.1: hover raises the whole row, and the raise is panel edge to panel edge, so the row's own padding has to carry the same slot as its content or the raise would stop at the text.

Callers reach the slot through Styles.RowSurface rather than naming a tier here; a row that is not activatable is not hoverable and keeps OverlayRow.

func Panel

func Panel(styles *theme.Styles, opts PanelOpts) []string

Panel renders one column panel as its rows. The body rows are already rendered cards; the panel insets them and carries the Surface tier behind them so the shade step between panel and card is the only separation.

func Priority

func Priority(styles *theme.Styles, priority int, on theme.Slot, flat bool) string

Priority renders the priority marker of spec section 3.4. Issue #232 replaced the "P1" text treatment with the digit alone on a fill of the priority hue: the one-character pill of section 3.6, the same anatomy the effort marker now wears, so the meta row reads as one grammar rather than as four unrelated treatments that happen to share a line.

The digit carries the fact and the hue reinforces it, which is section 1.9's floor: the four priority hues are all readable against FgOnAccent, and a terminal that renders no color at all still shows a numeral. The "P" the marker used to carry said nothing the column and the digit did not - the rail beside the card is already the priority hue - and it cost a cell on the row section 3.4 spends the most effort keeping short.

Cost is three cells padded and one flat, against the two the old marker spent in both densities. It is still the chip that survives longest: nothing on the row is shorter.

func Rail

func Rail(styles *theme.Styles, priority int, surface theme.Slot, selected bool) string

Rail renders a card's left edge: one cell, always reserved. Spec section 2.4: the glyph thickens from a half block to a full block on selection and the selected cards use the shared focus accent. Resting priority one keeps its danger cue; ordinary priorities use the muted text scale.

func RollBrand

func RollBrand(metrics theme.Metrics) (stretch int, seed int64)

RollBrand draws the once-per-process stretch and reveal seed of spec section 10.6.2. It is called from NewModel and nowhere else: a mark that re-rolled its width on every render would jitter on resize and read as a rendering fault rather than as character.

func ScrollHint

func ScrollHint(styles *theme.Styles, current, total int, on theme.Slot) string

ScrollHint renders the scroll position of an overlay as "12/40" in FgMuted. kb's overlays scroll by a hand-managed offset and bubbles/viewport does not expose that offset in a form the pointer regions can consume, which is why this is a kb widget and not a charm component (spec section 5.1).

func Scrollbar

func Scrollbar(styles *theme.Styles, opts ScrollbarOpts) []string

Scrollbar renders the affordance as one cell per row: a thumb of the track's own length proportional to the visible share, positioned by the offset. It returns nil when the body fits, which is the signal to reserve no column.

The thumb and the track are distinguished by glyph as well as by hue, so the affordance still states a position under a profile that strips color.

func ScrollbarShown

func ScrollbarShown(total, visible int) bool

ScrollbarShown reports whether a body of this size overflows its viewport, and so whether the caller reserves the affordance column at all. A body that fits carries no affordance and no reserved column.

func Section

func Section(styles *theme.Styles, label, count string, width int) string

Section renders one section break of spec section 4 step 5: an OverlayBand row carrying a bold label, never a rule. Count is rendered right-aligned when it is not empty, which is how a section says how much it holds without spending a body row on it.

The label carries the resting ramp of spec section 10.1.2. The donor gradient-paints a diagonal rule trailing its dialog title; section 4 forbids that shape, so the ramp moves onto the label and the band keeps one element.

func SectionRamp

func SectionRamp(styles *theme.Styles, label, count string, width int, ramp theme.Ramp) string

SectionRamp is Section with the state-dependent ramp of spec section 10.1.4 named by the caller: GradSectionDanger while a destructive action is pending, GradSectionArmed once it is armed. The lead is the same tint in both, so arming deepens the tail rather than re-tinting the label - an escalation of a state the user is already in, not a new one.

The mode is a property of the overlay and is passed in structurally; it is never recovered by matching a rendered label.

func Table

func Table(styles *theme.Styles, rows [][]string) []string

Table lays out aligned rows with lipgloss/v2 table. Map #136 component sourcing: the settings pane's label/value rows used to be concatenated strings whose values started wherever the label happened to end, and the charm component that exists for that is a table.

It is the one function in this package that is not a hand-crafted element: it is the adapter that lets a kb view feed a charm component and get rows back in the shape the overlay widget consumes. The output is deliberately plain text - the cell styles carry the column gutter and no color (theme.TableStyles) - because the caller paints each row with the token its role names, and a pre-colored cell would fight the surface the row is composed onto.

Every row must have the same number of cells. The result is one line per row, so a caller keying pointer hit regions to row indices keeps the mapping it had. Columns size to their content: the caller cuts its cells to the measure it can afford, and the table decides where the columns land. A forced table width would spread the slack across every column instead of leaving it on the last one, which is the opposite of what a label gutter wants.

func Truncate

func Truncate(styles *theme.Styles, content string, width int) string

Truncate is the width-aware shortening primitive of spec section 3.3, exported for the spin subpackage, which composes rows the same way the widgets in this package do and must not carry a second copy of the rule.

Types

type BandOpts

type BandOpts struct {
	Index   int
	Label   string
	Count   int
	Hue     theme.Slot
	Focused bool
	Width   int

	// Hovered thickens the unfocused band's rail glyph, per spec section
	// 10.5.1. The band is already bold, so bold is not available as a cue, and
	// it cannot change background without becoming the focused band; the rail
	// slot is the one cell it has spare. A focused band renders no hover: it is
	// already the acting column, and ratified call 9 keeps focus off the
	// pointer, so there is nothing for hover to promise.
	Hovered bool
}

BandOpts describes one column header band. Spec section 2.2: one row, full column width, no rule, no border and no separator line under it. The tier step from the band to the panel body is the separation.

type BrandOpts

type BrandOpts struct {
	Styles *theme.Styles
	Width  int // frame width
	Height int // frame height

	Stretch int   // extra columns of bowl, memoized by the caller
	Frame   int   // reveal frame; at or above Timing.BrandBirthSteps the mark is settled
	Seed    int64 // reveal seed, memoized by the caller

	Status     string     // the board's resolved state string
	StatusSlot theme.Slot // the hue that resolver returned
	Version    string     // the build version, unprefixed
	On         theme.Slot // the surface every returned row carries edge to edge
}

BrandOpts is the whole input of the launch block. Stretch, Frame and Seed are all resolved by the caller, which is what makes both the memoized width of spec section 10.6.2 and the reveal of section 10.6.6 pinnable without any package-level cache: this widget is pure and draws nothing on its own.

type BusyOpts

type BusyOpts struct {
	Frame string
	Label string
	On    theme.Slot
	Width int
}

BusyOpts describes one loading row. Spec section 10.8.6.

Frame is the tier's already-rendered spinner frame, and it is the one part of the row that carries a color of its own. It is empty for a surface whose motion is already spoken for: spec section 10.8.4 rule 4 allows one moving thing per surface, and the suppressed one renders its label with no frame.

On is the surface slot the row sits on; the caller names its tier, never its hue.

type ButtonOpts

type ButtonOpts struct {
	Text           string
	Variant        theme.ButtonVariant
	Selected       bool
	Hovered        bool
	Armed          bool
	Pressed        bool
	UnderlineIndex int
	Padding        [2]int
}

ButtonOpts describes one button. The shape is crush's internal/ui/common ButtonOpts (map #136), plus Armed, kb's addition for the purge and remove two-step of spec section 5.1.

UnderlineIndex is the rune offset of the hotkey letter, or a negative value for no hotkey. Padding is the left and right padding in cells.

Variant is what the button does (issue #157): the zero value is Neutral, so a caller that states no meaning gets the calmest surface rather than an accidental accent.

type CardOpts

type CardOpts struct {
	Title    string
	Emoji    string
	Seq      string
	Desc     string
	Meta     []string
	Labels   []string
	Priority int
	Selected bool
	Alt      bool
	Width    int
	Density  Density

	// Blocked draws the section 3.2 alarm beside the sequence number. Issue
	// #232 moved it off the meta chip row: the fact is about the card's
	// identity rather than about its schedule, and a two-cell mark beside the
	// reference costs the meta row nothing.
	Blocked bool

	// The row ceilings of spec section 2.6, resolved by the caller from
	// theme.Metrics. Issue #243 turned them from allotments into caps: the title
	// takes one row or two as its text needs, the description takes the rows its
	// rendered text fills and none at all when it is empty, and the labels take
	// one row or two as the pills wrap. Each is a function of density and frame
	// height; what the card draws inside it is a function of this card.
	TitleLines int
	DescLines  int
	LabelRows  int

	// PadRows is the interior vertical rhythm of issue #240: blank rows carrying
	// the card's own fill, spent on the boundaries between its sections. One row
	// separates the prose block from the meta row; a second, on a frame tall
	// enough to afford it, separates the meta row from the label rows. Issue
	// #243 made them conditional on both neighbours existing, so a separator
	// never abuts a section the card did not draw. Compact ignores this outright.
	PadRows int

	// Hovered raises the card's rail cell one tier, per spec section 10.5.1.
	// It is an affordance cue and nothing more: ratified call 9 keeps the board
	// cursor off the pointer, so a hovered card is never the acting selection.
	Hovered bool
	// HoverTag is the label pill under the pointer, empty for none. A card's
	// labels are a set, so the tag identifies the pill without an index whose
	// zero value would name the first one.
	HoverTag string
}

CardOpts describes one board card. Spec section 3 as issue #243 re-cut it: the row grid is a ceiling set by density, and every section takes only the rows its content fills, so a card with no description is shorter than one with five lines of it rather than carrying four blank rows.

Meta entries arrive already rendered because the chip row of section 3.4 mixes pill and non-pill runs (the priority marker, the age text and the effort marker are not pills). Labels arrive as raw tags because the wheel hue, the surface and the compact degradation are all card-local knowledge.

type CardSpan

type CardSpan struct {
	Row    int
	X0, X1 int
	Index  int    // position in CardOpts.Labels
	Tag    string // the label as the card rendered it
}

CardSpan locates one rendered label pill inside the rows a card returned. Coordinates are relative to the card's own top-left cell, so a view that places the card only has to add the card's origin to reach a hit region.

func CardWithSpans

func CardWithSpans(styles *theme.Styles, opts CardOpts) ([]string, []CardSpan)

CardWithSpans is Card plus the position of every label pill it drew. The board keys a pointer hit region to each label, and the card owns the wheel, the compact degradation and the individual chip-survival rule of spec section 3.4, so it is the only place those positions are known.

type CheckState

type CheckState uint8

CheckState is the state of one checklist row. Spec section 5.1 names three marks; a source that only knows done and not-done uses the first two.

const (
	CheckOpen CheckState = iota
	CheckDone
	CheckDropped
)

The three checklist marks of spec section 5.1.

type ChipOpts

type ChipOpts struct {
	Text    string     // the pill body
	Key     string     // non-empty selects the scoped two-tone form
	Mark    string     // optional state mark drawn at the head of the body
	Fill    theme.Slot // the pill fill
	On      theme.Slot // the surface the pill is drawn onto
	Flat    bool       // the compact degradation: flat colored bold text
	Hovered bool       // the pointer rests on this pill
	Dim     bool       // the inactive form: the fill withdrawn, the hue kept on the text
	Focused bool       // the keyboard cursor rests on this pill
	Neutral bool       // ordinary label treatment: neutral depth and text, no category hue
}

ChipOpts describes one pill. Spec section 3.6: the pill is the language's chip primitive, a flat span of colored cells with one padding cell at each end, so it owes a terminal font nothing but the ability to paint a cell.

type Density

type Density = theme.Density

Density is the layout density of spec section 2.6, resolved by the caller from theme.Metrics.DensityFor.

type EmptyOpts

type EmptyOpts struct {
	Headline string
	Key      string
	Verb     string
	On       theme.Slot
	Width    int
}

EmptyOpts describes one empty-state row. Spec section 10.8.6.

On is the surface slot the row sits on: the caller names its tier and never its hue. Key and Verb are the action tail; a surface with no eligible action leaves them empty and gets the headline alone.

type ErrorOpts

type ErrorOpts struct {
	Message  string
	Key      string
	Verb     string
	On       theme.Slot
	Width    int
	MaxLines int
}

ErrorOpts describes one panel-scope error block. Spec section 10.8.6.

On is the surface slot the block sits on, which is what selects the hue: the caller names its tier and never its slot. Key and Verb are the retry tail, which names the control that started the operation rather than growing a Retry button of its own; an error with no retryable trigger leaves them empty.

type Ladder

type Ladder struct {
	Head   []string
	Middle []string
	Tail   []string
}

Ladder is one hint line declared once and packed to the frame. Spec section 10.4.6: a ladder is a pinned head, a droppable middle and a pinned tail, and the rungs are ordered by importance rather than by where they fit.

The tail is pinned because a frame too narrow for the ladder is exactly the frame where a user most needs the rungs that get them out.

type MeterOpts

type MeterOpts struct {
	Done   int        // work finished
	Total  int        // work in total; zero or less renders an empty bar
	Cells  int        // bar width; zero or less takes the MeterCells default
	Ground theme.Slot // the surface the pill is drawn onto
}

MeterOpts describes one progress meter. Spec section 10.1.3: the meter is the only pill whose interior carries a gradient, because it is the only pill whose interior is a bounded fraction. A pill with no denominator would encode a position that does not exist, so every other pill stays flat.

type OverlayOpts

type OverlayOpts struct {
	Title  string   // header band title, inset OverlayInsetX
	Seq    string   // header band reference, right-aligned
	Body   []string // panel-width body rows
	Footer string   // footer band hints, inset OverlayInsetX
	Hint   string   // footer band scroll indicator, right-aligned
	Width  int
	Height int

	// Armed re-fills the header band to StatusAlarm with FgBase bold. Spec
	// section 10.1.4, ratified call 6: this is the only header-band recolor in
	// the TUI, it fires on the Armed state of section 1.9 and on nothing else,
	// and it carries the mode structurally rather than recovering it from a
	// rendered label.
	Armed bool
}

OverlayOpts describes one elevated panel. Spec section 4: elevation is a shade step plus a shadow, never a frame, so the panel is a header band, an OverlaySurf body and a footer band, with the shadow cast behind it.

Body rows arrive already rendered at the panel width because the caller owns the scroll window: an overlay slices its own body lines, and a section break or a field row is just one more row in that slice.

type PanelOpts

type PanelOpts struct {
	Header  BandOpts
	Meta    string
	MetaLit bool
	Body    []string
	More    int
	Width   int
	Height  int
	Density Density
}

PanelOpts describes one column panel. Spec sections 2.1 to 2.3 and 3.7: a header band, an optional meta line, the card stack inset by ColumnPadX, and an overflow cue on the last row when the stack did not fit.

More is the overflow count. It is not in the spec's API sketch, but section 3.7 puts the cue inside the panel and no caller may style a row itself, so the panel has to own it.

MetaLit is the ship celebration of issue #191 at its lit phase. It moves the meta row's foreground from FgMuted to StatusOK and nothing else: no cell changes width, so the no-reflow parity of spec section 10.4.4 holds across the phase. The caller owns whether the effect runs at all - the widget is told, never asked, because the decision is a fidelity gate and spec section 10.7.5 keeps that out of every render path.

type ScrollbarOpts

type ScrollbarOpts struct {
	Total   int
	Visible int
	Offset  int
	Height  int
	Active  bool
	On      theme.Slot
}

ScrollbarOpts describes one scroll affordance. Total is the content in lines, Visible the lines the viewport shows, Offset the first visible line, and Height the rows the bar is rendered over.

Active is the linger state of spec section 10.3.4, resolved by the caller against its own timing token: kb dims rather than hides, so a settled bar is FgMuted and a bar within the linger of the last scroll is FgSubtle. Geometry is identical in both.

Directories

Path Synopsis
Package spin is the branded spinner engine of spec section 10.2.5: the gradient label that wipes in column by column while kb waits on a network round trip or a model inference.
Package spin is the branded spinner engine of spec section 10.2.5: the gradient label that wipes in column by column while kb waits on a network round trip or a model inference.

Jump to

Keyboard shortcuts

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