theme

package
v0.0.19 Latest Latest
Warning

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

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

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func OverlayBody

func OverlayBody(r Roles) lipgloss.Style

OverlayBody is ordinary copy inside an overlay.

func OverlayError

func OverlayError(r Roles) lipgloss.Style

OverlayError and OverlaySuccess are the two outcomes a surface reports.

func OverlayFrame

func OverlayFrame(r Roles) lipgloss.Style

OverlayFrame is the box an overlay sits in: a panel surface inside a border, over whatever it covers.

func OverlayInput

func OverlayInput(r Roles) lipgloss.Style

OverlayInput is a text field an overlay is collecting into.

func OverlayKey

func OverlayKey(r Roles) lipgloss.Style

OverlayKey is a key name inside a hint — cyan, the sole accent, so the thing to press is the thing that stands out.

func OverlayMuted

func OverlayMuted(r Roles) lipgloss.Style

OverlayMuted is secondary copy: hints, counts, the keys a surface honours.

func OverlaySelected

func OverlaySelected(r Roles) lipgloss.Style

OverlaySelected is the highlighted row or button.

A background rather than a brighter foreground, for the reason the top bar's active folder needed one: a row can contain a colour emoji, and a colour emoji ignores the foreground it is given.

func OverlaySuccess

func OverlaySuccess(r Roles) lipgloss.Style

func OverlayTitle

func OverlayTitle(r Roles) lipgloss.Style

OverlayTitle is the name of the thing an overlay is showing.

func SenderColour

func SenderColour(id int64, r Roles) lipgloss.Color

SenderColour is the deterministic colour of a person's name.

Deterministic because the colour is an identity cue: the same person has to be the same colour on every line of every session, or it is noise rather than information. The hash is FNV-1a over the ID's bytes so that consecutive user IDs — which is what a small group of colleagues who signed up together actually have — do not land in one bucket.

It lives here rather than in the thread grid because the rail names the same people. Two implementations that agree today are two implementations that can stop agreeing, and a person shown mauve in the thread and blue in the rail beside it is two people as far as the reader is concerned.

func SupportsHyperlinks() bool

SupportsHyperlinks reports whether the terminal understands OSC 8.

An allowlist, not a denylist, and environment only — the same rule as SupportsTrueColor, for the same reason: the only way to ASK a terminal what it supports is to write a query and read the reply off stdin, and under Bubble Tea's raw-mode input loop that reply arrives as keystrokes and gets typed into the composer.

An allowlist because the failure is asymmetric. A terminal that ignores an unknown OSC loses nothing but the click; one that prints its bytes puts a URL in the middle of a message. Being wrong about a terminal that does support them costs a feature nobody had; being wrong the other way corrupts the thread.

tmux is excluded deliberately even though several terminals under it would qualify. It only forwards OSC 8 with allow-passthrough on, which is off by default, and this cannot be detected from the environment either. A user who has turned it on can say so with ui.hyperlinks = "always".

func SupportsTrueColor

func SupportsTrueColor() bool

SupportsTrueColor reports whether the terminal advertises 24-bit colour.

Environment only, never a runtime query. This app learned that lesson already: termenv's background probe writes an OSC 11 sequence and reads the reply off stdin, which under Bubble Tea's raw-mode input loop is delivered to the program as keystrokes and typed into the composer.

The original crime scene was glamour's WithAutoStyle, which resolved to that probe. glamour is no longer a dependency — the thread grid renders entities directly — so the hazard is gone rather than guarded. This function is where the rule now lives, and it must stay environment-only.

Types

type Roles

type Roles struct {
	// Surfaces, back to front.
	Bg       lipgloss.Color // app background and scroller
	Panel    lipgloss.Color // list, rail, headers, composer
	Chrome   lipgloss.Color // top and hint bars
	Sel      lipgloss.Color // selected chat row
	CurLine  lipgloss.Color // selected message row
	Rule     lipgloss.Color // panel separators
	RuleSoft lipgloss.Color // in-panel dividers
	Border   lipgloss.Color // attachment, code and poll frames

	// Text, dimmest to brightest.
	Ghost  lipgloss.Color // separators and inert glyphs
	Faint  lipgloss.Color // timestamps and byte counts
	Dim    lipgloss.Color // secondary copy
	Fg     lipgloss.Color // message body
	Bright lipgloss.Color // active titles and bold spans

	// Semantic accents.
	Cyan  lipgloss.Color // sole focus and key accent
	Amber lipgloss.Color // commands, attachments, channels, inline code
	Green lipgloss.Color // insert mode, online, own messages, sent
	Mauve lipgloss.Color // groups, italics, sender colour
	Blue  lipgloss.Color // DMs, mentions, sender colour
	Red   lipgloss.Color // errors, failures, removed diff lines
}

Roles is the TUI 2.0 semantic palette (docs/tui-2.0.md, "Palette").

Every colour here is named for what it MEANS, not what it looks like. That is the point: amber, green, mauve and blue are semantic — a channel sigil is amber because it is a channel, not because amber looks nice there — and cyan is the sole focus accent. A renderer that wants "a nice highlight" has no role to reach for, which is deliberate.

The existing Theme fields are untouched. The two coexist while the components migrate; roles are what new frame code uses.

func DarkRoles

func DarkRoles(trueColor bool) Roles

DarkRoles returns the dark palette for the terminal's colour depth.

The depth is resolved once, by the caller, at startup — see SupportsTrueColor. Re-querying per render would be both wasteful and unstable, and this app has a hard rule against asking the terminal anything at runtime: an OSC reply arrives as keystrokes and gets typed into whatever is focused. Environment variables only.

func LightRoles

func LightRoles(trueColor bool) Roles

LightRoles inverts the surface and text ramps while keeping the semantic accents recognisable. Dark is the high-fidelity reference (decision: the design record specifies only that light is "an inversion of the same roles"), so this is deliberately a mechanical inversion rather than a second hand-tuned palette pretending to be one.

func MarkerRoles

func MarkerRoles() (Roles, map[string]string)

MarkerRoles is a palette in which every role is a colour nothing could arrive at by accident — #0100xx, one per field, in declaration order — and a lookup from the "r;g;b" a terminal sequence carries back to the role's name.

It answers one question: did this component draw ONLY the palette it was given? Rendering through it and checking every colour on screen against the map fails both ways a component gets that wrong — ignoring the palette (drawing some default instead) and reaching past it for a literal.

Exported from the production package for the same reason [OpenStyle] and [cell.PaintedWidth] are: the invariant belongs to every component, so the tool for asserting it cannot live inside one component's test package. It is built by reflection deliberately — a hand-written table would need updating whenever Roles gains a field, and the field it forgot would be the one nobody was checking.

func RolesFor

func RolesFor(name string, trueColor bool) Roles

RolesFor picks the palette for a theme name and colour depth. It is the single entry point the app uses, so the two decisions are made together and once.

Jump to

Keyboard shortcuts

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