Documentation
¶
Index ¶
- func OverlayBody(r Roles) lipgloss.Style
- func OverlayError(r Roles) lipgloss.Style
- func OverlayFrame(r Roles) lipgloss.Style
- func OverlayInput(r Roles) lipgloss.Style
- func OverlayKey(r Roles) lipgloss.Style
- func OverlayMuted(r Roles) lipgloss.Style
- func OverlaySelected(r Roles) lipgloss.Style
- func OverlaySuccess(r Roles) lipgloss.Style
- func OverlayTitle(r Roles) lipgloss.Style
- func SenderColour(id int64, r Roles) lipgloss.Color
- func SupportsHyperlinks() bool
- func SupportsTrueColor() bool
- type Roles
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func OverlayBody ¶
OverlayBody is ordinary copy inside an overlay.
func OverlayError ¶
OverlayError and OverlaySuccess are the two outcomes a surface reports.
func OverlayFrame ¶
OverlayFrame is the box an overlay sits in: a panel surface inside a border, over whatever it covers.
func OverlayInput ¶
OverlayInput is a text field an overlay is collecting into.
func OverlayKey ¶
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 ¶
OverlayMuted is secondary copy: hints, counts, the keys a surface honours.
func OverlaySelected ¶
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 OverlayTitle ¶
OverlayTitle is the name of the thing an overlay is showing.
func SenderColour ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.