ui

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Jul 16, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package ui — banner.go draws the startup logo.

The banner is a 7x8 pixel-font rendering of "FUTILS" with a two-pass colour gradient (light yellow-green → dark forest green) and a drop shadow. The design mirrors frefresh-go's banner for visual family resemblance — same pixel metrics, same shadow offset — but swaps the orange-to-yellow palette for green so the two tools are distinguishable at a glance when the user has both in muscle memory.

Index

Constants

This section is empty.

Variables

View Source
var AccentColor = lipgloss.Color("#22c55e")

AccentColor is the brand green used for cursors, highlights and focus states throughout the UI. This is the futils-specific palette — frefresh-go uses orange (#e8712a); futils uses green (Tailwind green-500) to keep the two tools visually distinct when muscle memory would otherwise confuse them.

View Source
var BuildInfo string

BuildInfo is an optional compact freshness line shown under the version in the banner — set by main for dev builds, empty (and hidden) for releases.

View Source
var DimColor = lipgloss.Color("8")

DimColor is the muted gray used for secondary text (hints, labels, deselected rows). Terminal-theme-agnostic — ANSI 8 reads as grey on both light and dark backgrounds.

View Source
var ErrGoBack = errors.New("go back")

ErrGoBack is returned when the user presses Esc/b to go back one step. Callers handle it non-fatally (re-show parent menu) so TUI navigation feels like browser back rather than an error state.

View Source
var ErrGoHome = errors.New("go to main menu")

ErrGoHome is returned when the user presses m to abandon whatever flow they are in and jump straight back to the main menu. Intermediate loops must let it propagate (never swallow it as a local soft-failure); only the root menu loop catches it.

View Source
var ErrQuit = errors.New("quit")

ErrQuit is returned when the user presses Ctrl+C or q to quit outright.

View Source
var Version = "dev"

Version is set by main at startup.

View Source
var WarnColor = lipgloss.Color("3")

WarnColor is the yellow used for "pay attention" tags like inline badges. ANSI 3 stays within the terminal-adaptive 0-7 range, same rationale as DimColor, so it reads as yellow on both light and dark backgrounds instead of a fixed hex that could clash with either.

Functions

func Banner() string

Banner returns the full startup banner: pixel-art logo, centred version string, and centred keymap hint. Intended to be printed once at launch.

func Confirm

func Confirm(message string) (bool, error)

Confirm shows a yes/no prompt and returns true for yes. Esc/Ctrl+C return ErrGoBack so callers treat a cancel as "go back", matching the menus.

func DefaultFilterRowRenderer

func DefaultFilterRowRenderer(opt FilterOption, selected bool) string

DefaultFilterRowRenderer renders the Label only, with the cursor row highlighted in the accent color. Used when callers don't need custom per-row styling.

func FilterMenu

func FilterMenu(title string, options []FilterOption, render FilterRowRenderer) (string, error)

FilterMenu shows a searchable single-select list. Typing filters the visible rows by case-insensitive substring match on Label; arrow keys navigate the filtered subset; Enter selects.

`render` controls per-row appearance and is responsible for honoring the `selected` flag — a custom renderer that ignores selection state will produce an invisible cursor. Pass DefaultFilterRowRenderer when no custom styling is needed.

Returns the chosen option's Value, or ErrGoBack on esc, ErrQuit on ctrl+c.

func FitWidth

func FitWidth(s string, width int) string

FitWidth sizes s to exactly width display columns: padded with trailing spaces when shorter, or truncated with a trailing … when longer. Rune-aware (counts runes, not bytes) so accented names still line up in a column.

func ItemTypeColor

func ItemTypeColor(itemType string) lipgloss.Color

ItemTypeColor returns the lipgloss color used to render a Fabric item type label in the move picker. Notebooks are accent green (matching the brand), Reports are orange (matching the sibling tool's accent), Semantic Models stay at the default terminal foreground. Unknown types fall back to the default fg.

func MultiSelect

func MultiSelect(title string, options []string, initial []string) ([]string, error)

MultiSelect shows an interactive checkbox list. Items in `initial` are pre-checked — useful for "edit existing favourites" flows where you want the user to see and toggle current state.

Returns the checked items in the order they appear in `options` (not selection order), so favourites look the same regardless of whether the user clicked top-to-bottom or bottom-to-top.

Navigation:

↑/↓ k/j        single row
alt+↑/↓ pgup/pgdown / alt+k/j   jump 5 rows
home/g • end/G jump to first / last
space          toggle cursor row
a              select all (or clear if already full)
enter          confirm • esc/b go back • ctrl+c/q quit

Returns ErrGoBack on esc, ErrQuit on ctrl+c/q.

func MultiSelectRich added in v0.4.0

func MultiSelectRich(title string, items []CheckItem) ([]int, error)

MultiSelectRich shows an interactive checkbox list of styled items and returns the indices of the checked items in list order. Unlike MultiSelect (which keys on the label string), callers map the returned indices back to their own data, so two rows with identical labels stay distinct. Returns ErrGoBack on esc, ErrQuit on ctrl+c/q.

func NumberMenu

func NumberMenu(message string, options []MenuOption) (string, error)

NumberMenu shows a single-select menu with arrow-key navigation and digit shortcuts. Returns ErrGoBack on esc/b and ErrQuit on ctrl+c/q so callers can handle cancellation cleanly.

Options with IsHeader=true render as non-selectable section labels — the cursor lands on the first non-header row and skips headers when arrow keys move through the list.

func ParameterForm

func ParameterForm(params []fabric.Parameter) ([]fabric.JobInput, error)

ParameterForm prompts the user to override each discovered notebook parameter. The form renders:

  • Text / Int / Float → free-text input, empty means "keep notebook default"
  • Bool → Yes/No confirm, pre-set to the notebook's default

The return value contains ONLY genuine overrides. Fields the user didn't change are omitted so Fabric falls back to the notebook's own Python default — and because Fabric rejects empty-string Text values with a 400, we must NOT send them.

Returns ErrGoBack if the user presses esc, ErrQuit on ctrl+c.

func PickDirectory added in v0.4.0

func PickDirectory(title, startDir string) (string, error)

PickDirectory shows a searchable, navigable directory browser rooted at startDir and returns the absolute path the user selects. Type to filter the current folder's contents; Enter on a subfolder descends into it; choose "Use this folder" to pick the current directory. Git repos are marked with a ● . Returns ErrGoBack on esc, ErrQuit on ctrl+c.

func RenderBar added in v0.4.0

func RenderBar(frac float64, width int) string

RenderBar renders a progress bar of the given cell width: round(frac*width) filled cells (▰, brand green) followed by the rest empty (▱, dim). frac is clamped to [0,1]. Matches the spinner's ▰▱ block aesthetic. Pure and deterministic — the ANSI color codes wrap the runes but don't change the cell counts, so callers can count ▰/▱ to assert progress.

Types

type Categorizer

type Categorizer func(tableName string) string

Categorizer maps a table name to a group label used by TableCheckbox's cascade picker. Callers supply this so internal/ui stays free of any customer-specific naming conventions (e.g. Norwegian "Fakta", German "Faktentabelle"). A nil categorizer means "no grouping" — every table lives under a single bucket.

type CheckItem added in v0.4.0

type CheckItem struct {
	Label   string
	Style   lipgloss.Style
	Checked bool
	// SkipBulkSelect excludes this row from the select-all (a) key — used for
	// destructive (delete) rows so a bulk select never marks one.
	SkipBulkSelect bool
}

CheckItem is one row for MultiSelectRich: a display label, an optional lipgloss style (zero value renders plain), and whether it starts checked.

type FilterOption

type FilterOption struct {
	Label string
	Value string
	Meta  any
}

FilterOption is one row in a FilterMenu. Label is what the user sees and what the filter matches. Value is returned on selection. Meta is arbitrary per-row data the caller's renderer can use (e.g. the Fabric item type for color-coding).

type FilterRowRenderer

type FilterRowRenderer func(opt FilterOption, selected bool) string

FilterRowRenderer turns a FilterOption + selection state into a rendered string. Selection state takes precedence: a renderer MUST return a uniformly-highlighted row when selected, regardless of any per-row coloring it would otherwise apply.

type MenuOption struct {
	Label       string
	Value       string
	IsHeader    bool
	Description string // one-line footer hint shown when this option is highlighted
	Info        string // fuller text shown in a box when the user presses ?
	Badge       string // short inline tag, e.g. "MUST SET" (rendered accent/warn)
	// Preselect puts the initial cursor on this row (first flagged row wins),
	// so the likeliest answer is one Enter away.
	Preselect bool
}

MenuOption is one row in a NumberMenu. Label is what the user sees, Value is what gets returned when they select it — decoupled so display names can differ from internal identifiers (e.g. notebook display name vs. item ID).

IsHeader marks a non-selectable section label. Header rows render in dim style with no number/pointer, are skipped by cursor navigation, and don't consume a digit shortcut — selectable items are numbered independently so "1, 2, 3" stays sane even when headers sit between them.

func MenuOptionsFromStrings(values []string) []MenuOption

MenuOptionsFromStrings is a shortcut for menus where the user sees the same text they'd pass programmatically (e.g. customer names).

type Spinner

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

Spinner shows a non-blocking animated spinner on stdout. Suitable for wrapping long API calls so the terminal doesn't look frozen.

func NewSpinner

func NewSpinner(message string) *Spinner

func (*Spinner) SetMessageFunc added in v0.4.0

func (s *Spinner) SetMessageFunc(fn func() string)

SetMessageFunc installs a provider that the animation goroutine calls on every frame to compute the live message. Use it when the text must stay fresh even while the caller's own goroutines are stalled (e.g. all workers sleeping on a 429) — the spinner's repaint drives it, so no external timer is needed. A nil provider falls back to the static message set by NewSpinner.

func (*Spinner) Start

func (s *Spinner) Start()

Start begins the animation in a goroutine. Call Stop to end it.

func (*Spinner) Stop

func (s *Spinner) Stop()

Stop halts the animation and blocks until the goroutine has exited, so the caller's next stdout write doesn't race with a final frame.

type TableSelection

type TableSelection struct {
	FullRefresh bool
	Tables      []string
	Summary     string
}

TableSelection is what TableCheckbox returns. FullRefresh=true means "no objects in the refresh body" (i.e. refresh the entire model). Otherwise Tables is the explicit list passed to the Enhanced Refresh API.

func TableCheckbox

func TableCheckbox(message string, tables []string, categorizer Categorizer) (TableSelection, error)

TableCheckbox shows the refresh-specific table picker. Returns a TableSelection describing what to refresh, or ErrGoBack/ErrQuit if the user backed out.

categorizer may be nil — in that case every table goes into a single "Tables" bucket and the cascade only has the global All toggle. Pass a customer-specific categorizer (e.g. Dim/Fakta/Log) from the cmd layer to get domain-aware grouping without coupling internal/ui to any one customer's naming convention.

Jump to

Keyboard shortcuts

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