Help us shape the future of Go! Take the 2026 Go Developer Survey

chrome

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Sep 9, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ListWrapperStyle = lipgloss.NewStyle().
	Padding(1, 2, 2, 2)

ListWrapperStyle is the frame around the two body lists. Its padding is what separates the list content from the panel edges, and its frame size is subtracted from the panel box when the inner list is sized.

View Source
var WrapperStyle = lipgloss.NewStyle().
	Padding(1, 2)

Functions

func ActionDescription

func ActionDescription(action, target string, isGroup bool) string

ActionDescription returns a full description of the pending action.

func BarColumn

func BarColumn(fg color.Color, bg color.Color, content string) string

BarColumn renders the nav's ▌ indicator once per line of content, so the bar spans a multi-line row's full height instead of a sliver at its top. bg may be nil to leave the cell background unset.

func DockerActionFor

func DockerActionFor(msg tea.KeyPressMsg) (string, bool)

DockerActionFor returns the `docker compose` action a keypress asks for. Both details panels read it, which is what makes "s starts a group" and "s starts a service" the same fact rather than two switch statements that happen to agree. Remove is absent on purpose: it is destructive, so it goes through a confirmation instead of straight to a command.

func EmptyCard

func EmptyCard(width, availHeight int, bg color.Color, title, body, key, hint string) string

EmptyCard renders a dim, centered, rounded-border card used for the empty / onboarding states. `key` is shown in the accent color inside brackets, `hint` is the trailing description in a dim color. `availHeight` is the vertical space in which the card should be centered. `bg` is the panel's background tier, which the space around the card sits on; the card itself is recessed below that tier so it reads as inset into the panel.

func FitBox

func FitBox(s lipgloss.Style, w, h int) lipgloss.Style

FitBox constrains a style to an exact w x h box: Width/Height pad it out, Max* clip anything that would otherwise overflow (Width alone pads but never truncates, which is how a too-wide panel ends up wrapped by the terminal). Non-positive dimensions are left unset so a component still renders naturally before the first SetBodyLayoutMsg arrives.

func HealthColor

func HealthColor(health string) color.Color
func Hyperlink(text, url string) string

Hyperlink wraps already-sized text in an OSC 8 terminal hyperlink pointing at url. text must already be truncated and padded to its column - OSC 8's escape sequence is not text, and handing a not-yet-truncated string to runewidth.Truncate (chrome.Truncate) will cut through the middle of it, corrupting the rest of the screen. Terminals without OSC 8 support simply do not render the sequence, so text is what they show.

func ListRow added in v0.7.0

func ListRow(in ListRowInput) string

ListRow renders one row of a body list: a full-height color bar, then a title row on the row's own surface with an optional status dot pinned right, then any body lines.

The services and groups delegates drew this identically for about fifty lines each, down to the comments. They differ only in the item type they cast to, the dot they compute, and whether a description follows the title - so those are what ListRowInput carries and nothing else.

func ListRowBg

func ListRowBg(isActive bool) color.Color

ListRowBg is the background a list row renders on. The active row is lifted to the surface tier; every other row sits flush on the panel's elevated tier. Rows need an explicit background (rather than inheriting the panel's) because each row is rendered and sealed on its own - see appstyles.FillBackground.

func ListRowBgOn added in v0.7.0

func ListRowBgOn(isActive bool, panelBg color.Color) color.Color

ListRowBgOn is ListRowBg for a list inside a panel whose tier moves: the inactive rows have to sit flush on whatever tier the panel is actually on, or an unfocused panel shows its rows floating a tier above their own frame. It is DESIGN.md's rule that a component inside a panel takes that panel's tier as a parameter instead of picking a tint of its own.

The active row's ModalBg is unchanged by focus: it is its own register rather than a tint derived from the panel tiers, so it reads as the cursor on either tier.

func ModalBodyWidth added in v0.7.0

func ModalBodyWidth(termWidth int) int

ModalBodyWidth is how wide a modal's text body may be: half the terminal, or 60 columns, whichever is smaller, and never less than 20 - below that a message is unreadable whatever the terminal says.

func ModalHints

func ModalHints(hints ...KeyHint) string

ModalHints renders a modal's own help line, in the footer bar's format but with the lighter description color the modal surface needs. Every modal carries one: the footer bar is hidden behind the modal while it is open, so the keys the modal takes over are advertised here or nowhere.

func ModalListHeight

func ModalListHeight(items, termHeight int) int

ModalListHeight is the height to build a modal's list with so the modal fits a terminal termHeight rows tall.

renderWithModal (src/model/View.go) centers a modal by clamping y to 0, so a modal taller than the terminal does not scroll or shrink - it loses its hint line and bottom border off the bottom of the screen. A list sized to len(items) is therefore a latent overflow on any project big enough, and the caller pairs this with SetShowPagination(height < len(items)) so the rows that no longer fit stay reachable and say so.

The floor of 3 is deliberate: below about 12 rows there is no honest answer, and a terminal that short cannot show the modal's own chrome either.

func ModalListHeightWith added in v0.7.0

func ModalListHeightWith(items, termHeight, extra int) int

ModalListHeightWith is ModalListHeight for a modal that draws extra rows of its own above or below the list - the env modal's column header and rule, for instance. extra is how many.

It exists because modalListChrome describes the chrome every list modal has, and a modal with two rows more than that overflows by exactly two: enough to push its own bottom border off a short terminal.

func ModalSurface

func ModalSurface(bg color.Color, content string) string

ModalSurface wraps a modal's content in the shared modal chrome: an accent rounded border, padding, and a background sealed against `bg` so the modal reads as one opaque surface over the page it is composited onto. Modals in particular cannot afford an unpainted cell - the page shows through it.

BorderBackground is set explicitly because lipgloss leaves border cells on the default background otherwise, which outlines the modal in the terminal's color.

func ModalTitle

func ModalTitle(text string) string

ModalTitle renders a modal's heading. Every modal names itself, so a user who lands on one mid-flow can tell what it is about to do without having to infer it from the fields. It is the shared accent chip - appstyles.NormalTitle - stood off the body by its own margin, so a style or theme change to the chip lands on modals and panes alike.

The margin replaces the blank line each caller would otherwise have to add - it matches the blank row the hint line sits above: the heading and the footer are the modal's chrome, and both stand off the body.

func NewSpinner

func NewSpinner() spinner.Model

NewSpinner creates a spinner styled with the active theme's accent color.

func PanelBg

func PanelBg() color.Color

PanelBg is the background tier a body panel renders on. Both body panels are always active now that focus is gone, so they share the elevated tier (what the previously "focused" panel used to get). Focus used to lift the whole panel rather than adding a border, so the panel's box stayed the same size either way - that lift is now the steady state.

func PanelBgFor added in v0.7.0

func PanelBgFor(isFocused bool) color.Color

PanelBgFor is PanelBg for the two panels that still have focus to show: the Backups list and its preview. The focused one sits on the elevated tier, the unfocused one a tier below, which is the lift DESIGN.md documents - focus changes the tier, never the border, so the box stays the same size either way.

It is a second function rather than a parameter on PanelBg because every other panel in the app is always active and has no answer to give: adding the argument there would mean threading a constant `true` through a dozen call sites to say nothing.

func PanelBodyHeight

func PanelBodyHeight(total int) int

func PanelBodyWidth

func PanelBodyWidth(total int) int

PanelBodyWidth and PanelBodyHeight are the space a panel body gets inside a frame of the given total size: the frame's own padding taken off, plus the title row for the vertical axis. Callers size their content with these rather than with hardcoded offsets, so a change to WrapperStyle's padding doesn't silently push content out of the panel.

func PanelBodyWithFooter

func PanelBodyWithFooter(width, avail int, bg color.Color, content, footer string) string

PanelBodyWithFooter lays out a details panel's body: `content` at the top, `footer` on the body's last rows, and blank rows between them. Both details panels build their body through here, which is what makes the pending-action spinner land on the same line of both rather than wherever each panel's content happens to end - see "The panel footer" in docs/DESIGN.md.

The content is clipped before the footer is attached, so a panel whose content outgrows its box loses its last rows rather than its footer. Doing it the other way round - joining first and clipping the result - takes the bottom off, which is exactly the row that has to survive.

An empty footer costs no rows. lipgloss.Height("") is 1, so passing "" would otherwise reserve a blank line at the foot of every panel that has nothing to pin there - which is most frames, now that the panels only use the footer for the spinner and the service panel's transient confirmations.

func PanelFrame

func PanelFrame(title string, titleRight string, width int, height int, body string) string

PanelFrame renders the title chrome shared by DetailsPanel and GroupDetailsPanel, filling exactly the width x height box the panel was given. Both body panels are always active now that focus is gone, so they share the elevated background tier.

`titleRight` is an optional accessory pinned to the right end of the title row - the group status pill uses it. A blank row separates the title row from the body, so the panel's own label doesn't read as part of whatever the body starts with; PanelBodyHeight accounts for both rows.

Callers embed their action buttons at the bottom of `body`, which pins the action row to the bottom of the panel. The chip itself is appstyles.NormalTitle; the MarginLeft(2) here is the frame's own left gutter, matching the 2 columns the bubbles list TitleBar adds inside the list wrappers - see appstyles.NormalTitle.

func PanelFrameOn added in v0.7.0

func PanelFrameOn(title string, titleRight string, width int, height int, bg color.Color, body string) string

PanelFrameOn is PanelFrame on an explicit background tier, for the two Backups panels: focus lifts a whole panel, frame included, so the tier has to reach the frame rather than only the body inside it. See PanelBgFor.

func PanelRule

func PanelRule(width int) string

PanelRule is the thin horizontal line both details panels separate their sections with. One helper rather than five copies of the same three-line style, so the panels cannot drift to different rules.

func PortLabel

func PortLabel(p types.ServicePortConfig) string

PortLabel is one published port in full: the form the service details panel shows, where there is room for it.

14533->4533/tcp
127.0.0.1:14533->4533/tcp   (bound to loopback: not reachable off-host)
4533/tcp                    (exposed, not published)

func PublishedPorts

func PublishedPorts(ports []types.ServicePortConfig) []string

PublishedPorts returns the host-side ports a service publishes, in file order, deduplicated, for a column too narrow for the full mapping. A service that publishes nothing returns an empty slice.

[]{14533:4533/tcp}                        -> ["14533"]
[]{8080:8080/tcp, 6881:6881/tcp+udp}      -> ["8080", "6881"]
[]{4533/tcp}                              -> []          (not published)
[]{53:53/udp}                             -> ["53/udp"]

func RenderKeyHints

func RenderKeyHints(hints []KeyHint, descColor color.Color) string

RenderKeyHints renders hints as "key desc · key desc": the key bold in the primary text color, the description in descColor. Modals render their own help lines through this so they read the same as the footer bar, passing a lighter descColor when they sit on a lighter surface than the bar does.

func RenderPendingAction added in v0.7.0

func RenderPendingAction(s spinner.Model, action *PendingAction, width int, bg color.Color) string

RenderPendingAction renders a spinner with the action description, centered on the panel's footer row, while a docker action is in progress.

Both details panels drew this identically. It lives here so the two footers cannot drift apart the way the "is this service running" answers once did.

func ResizeModalList added in v0.7.0

func ResizeModalList(l *list.Model, items, termHeight int)

ResizeModalList re-fits a modal's list to a new terminal height.

The height is chosen once at construction, which is right until the terminal changes size underneath an open modal: a picker opened on a tall window and then shrunk kept its old row count, so its bottom border - and the hint line under it - ended up below the last row on screen, with no way to see them short of closing the modal.

Pagination is re-derived alongside, because whether the list needs it is a function of the same height.

func ShortImage

func ShortImage(ref string, width int) string

ShortImage renders an image reference to fit width columns, giving up whole parts of the reference in a fixed order rather than truncating the tail — the name is the part a reader needs and it is the part a plain truncation destroys first.

The ladder, widest first, each rung tried in full - the first that fits wins:

  1. as written, minus a ":latest" tag
  2. drop the registry host
  3. drop the namespace
  4. drop the tag (or digest)
  5. Truncate, as a backstop

func StateColor added in v0.7.0

func StateColor(tier apptypes.StateTier) color.Color

StateColor returns the colour a tier draws in.

TierStopped keeps StatusError rather than StatusStopped's grey: at the size of a single glyph the grey did not carry - see the note in serviceslist.statusDot, which is the failure that put it there.

func StateDot added in v0.7.0

func StateDot(state string, bg color.Color) string

StateDot renders the status glyph for a container state, on bg so it stays legible across a row's selection and focus states.

func StateGlyph added in v0.7.0

func StateGlyph(tier apptypes.StateTier) string

StateGlyph returns the character a tier draws with.

func StateLabel added in v0.7.0

func StateLabel(state string) string

StateLabel returns the pill text for a container state: docker's own word, upper-cased, so a service that is paused says PAUSED rather than being flattened into STOPPED.

An absent container is STOPPED, which is what "no container" has always meant to the panels; an empty state only reaches here once docker has answered.

func StatusPill added in v0.7.0

func StatusPill(label string, bg color.Color) string

StatusPill renders a filled, bold pill carrying label on the fill color bg. Callers choose the label and the fill; this decides the ink and the shape, which is the part all three pills were repeating.

The pill's ink (fg) does not follow the app's theme: InkOnLight/InkOnDark are fixed regardless of Dark, because the pill's own fill is a status color rather than a surface tier. What the fill *is* varies per theme, though, so appstyles.InkOn picks whichever of the two inks reads on it rather than the call site guessing - see appstyles/Contrast_test.go, which holds every theme's pill ink to 4.2:1.

func Truncate

func Truncate(s string, w int) string

Truncate hard-truncates s to w display columns, appending an ellipsis when it is shortened. lipgloss Width wraps rather than truncates, so cells are pre-truncated to keep every row on a single line.

The ansi.StringWidth check is a fast path, not an alternate algorithm: a plain string that already fits returns unchanged either way, but a string carrying a zero-width escape sequence - an OSC 8 hyperlink built by chrome.Hyperlink, already sized to its column before being wrapped - does not, because runewidth.Truncate is not ANSI-aware and would cut through the middle of the sequence. See docs/plans/service-urls.md D5, which is why Hyperlink's own doc comment requires already-sized text rather than truncating after wrapping.

Types

type KeyHint

type KeyHint struct {
	Key  string
	Desc string
}

KeyHint represents a single keybinding for display in the bottom bar. Key is the literal key (e.g. "n", "space", "←/→"). Desc is a short verb describing what the key does.

func HintAs

func HintAs(binding key.Binding, desc string) KeyHint

func HintFor

func HintFor(binding key.Binding) KeyHint

HintFor is one binding as a hint. HintAs overrides the description for the places where a shared key does something more specific than its general help text says - Enter is "confirm" everywhere, but "create group" in the checklist that creates a group.

type ListRowInput added in v0.7.0

type ListRowInput struct {
	// Title is the row's label, truncated to whatever the dot leaves it.
	Title string

	// Dot is the pre-styled status glyph pinned to the right of the title
	// row, or "" for no dot. The groups list omits it for a stopped group;
	// the services list always draws one.
	Dot string

	// Body is any further lines drawn under the title row, already styled.
	// The services list passes its description; the groups list passes
	// nothing. A delegate's Height() must match the lines actually emitted
	// here - see GroupsListCustomDelegate.Height.
	Body []string

	// Width is the list's width, not the row's: ListRow subtracts the bar
	// column itself so the two lists cannot disagree about it.
	Width int

	// IsSelected marks the row under the cursor; IsActive marks the row the
	// panel is acting on. They differ while the cursor moves in an unfocused
	// list.
	IsSelected bool
	IsActive   bool
}

ListRowInput is the per-row material a body-list delegate supplies. Only the parts that genuinely differ between the two lists are here; everything else about a row - its bar, its surface, how the title yields to the dot - is ListRow's business.

type PendingAction

type PendingAction struct {
	Action  string
	Target  string
	IsGroup bool
}

PendingAction tracks a docker action that is currently running.

Jump to

Keyboard shortcuts

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