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 ¶
- Variables
- func Banner() string
- func Confirm(message string) (bool, error)
- func DefaultFilterRowRenderer(opt FilterOption, selected bool) string
- func FilterMenu(title string, options []FilterOption, render FilterRowRenderer) (string, error)
- func FitWidth(s string, width int) string
- func ItemTypeColor(itemType string) lipgloss.Color
- func MultiSelect(title string, options []string, initial []string) ([]string, error)
- func NumberMenu(message string, options []MenuOption) (string, error)
- func ParameterForm(params []fabric.Parameter) ([]fabric.JobInput, error)
- type Categorizer
- type FilterOption
- type FilterRowRenderer
- type MenuOption
- type Spinner
- type TableSelection
Constants ¶
This section is empty.
Variables ¶
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.
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.
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.
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.
var ErrQuit = errors.New("quit")
ErrQuit is returned when the user presses Ctrl+C or q to quit outright.
var Version = "dev"
Version is set by main at startup.
Functions ¶
func Banner ¶
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 ¶
Confirm shows a yes/no prompt. Returns true for yes. Themed to the accent colour for visual consistency with the rest of the UI.
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 ¶
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 ¶
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 ¶
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 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 ¶
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.
Types ¶
type Categorizer ¶
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 FilterOption ¶
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 ¶
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 ¶
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 ¶
type TableSelection ¶
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.