theme

package
v0.8.0 Latest Latest
Warning

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

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

Documentation

Overview

Package theme is the single source of truth for nib's visual language: a calm, warm-editorial palette of foreground-only inks (no background is ever set, so nib respects the user's terminal theme), typographic glyphs (no emoji), and the lipgloss styles built from them. Both the TUI (tui/) and the CLI (cmd/cli.go) render through these styles so the two modes look like one product.

Index

Constants

View Source
const (
	BrandName = "nib"

	// LabelYouText labels the user's own chat messages (paired with LabelYou,
	// the style, in theme.go).
	LabelYouText = "you"

	HelpDefault      = "enter send · ctrl+y use command · G/end newest · esc exit"
	HelpApproval     = "1 once · 2 always · 3 this turn · 4 this session · n no · e edit · esc deny"
	HelpApprovalEdit = "enter submit · esc cancel"
	ApproveEditHint  = "describe the change · enter submit · esc cancel"

	// YoloOn/YoloOff are the transcript notices the /yolo toggle appends.
	YoloOn  = "yolo on — every tool call is auto-approved"
	YoloOff = "yolo off — tool calls need approval again"

	// NewOutputText is the footer marker shown when the viewport is scrolled up
	// and content has arrived below the fold. Composed with NewOutputGlyph by
	// NewOutputMarker() in theme.go — the glyph is swappable, this text isn't.
	NewOutputText = "new output"

	// The numbered approval menu. Line 2 is dynamic — the TUI composes
	// ApproveAlwaysPrefix + chat.GrantScope(...) + ApproveAlwaysSuffix.
	ApproveOnce         = "[1] run it once"
	ApproveAlwaysPrefix = "[2] always allow "
	ApproveAlwaysSuffix = "  (this session)"
	ApproveTurn         = "[3] yes to everything this turn"
	ApproveSession      = "[4] yes to everything this session"
	ApproveDenyEdit     = "[n] no · [e] edit"

	EmptyTagline = "a calm assistant for your terminal."
	EmptyTryLead = "try:"
	EmptySlash   = "type /  for skills, agents & commands"
	SlashHint    = "/ for skills"
	Starting     = "starting…"

	CLIWelcome = "a calm assistant for your terminal."
	CLIExit    = "ctrl+c or 'exit' to leave · 'help' for commands"

	// CLIHelp is cmd/cli.go's help() output — the CLI's own command list, kept
	// separate from the TUI's slash-completion popup. /yolo works in CLI mode
	// (cmd/cli.go's KindYolo case) and belongs here alongside exit/clear/help.
	CLIHelp = "commands:  exit  ·  clear  ·  help  ·  /yolo"

	// CLINotAvailable is the CLI dispatch loop's catch-all for a resolved
	// slash.Action whose Kind has no explicit case there — a %s format string
	// naming the command. It exists so a Kind with no CLI meaning (no picker,
	// no popup, nothing to wire up) is refused with a clear message instead of
	// silently falling through to KindSend and reaching the model as chat
	// text — see cmd/cli.go's default arm.
	CLINotAvailable = "%s is not available in CLI mode."

	// CLIResumeHint is appended to CLINotAvailable for /resume specifically:
	// unlike /loop or /goal, it has a real non-interactive equivalent already
	// wired up (app.applyResumeFlag), so the refusal can point at it instead
	// of just saying no.
	CLIResumeHint = "restart with `nib --resume` (or `nib --resume <id>`) to load a recorded session."

	// Shown when a CLI approval prompt gets no answer at all. A closed stdin
	// (the piped one-shot idiom) and a cancelled run are both "nobody
	// decided", which is not a yes, so the call is denied.
	CLIDeniedNoInput  = "denied: stdin closed, nobody left to approve this"
	CLIDeniedNoAnswer = "denied: no answer (the run was cancelled)"

	// Shown when --yolo / NIB_YOLO auto-approves every tool call. The header
	// carries the compact badge; the CLI prints the fuller notice at startup.
	YoloBadge  = "yolo"
	YoloNotice = "yolo — auto-approving every tool call (no prompts)"

	// StatusRunning is shown between an approved tool call and its result.
	StatusRunning = "running…"

	// Reasoning box copy. A collapsed box shows the trailing
	// ReasoningMaxLines lines of the live trace; the TUI composes the hint
	// line as "… " + n + ReasoningMore + ReasoningExpand.
	ReasoningMore     = " more · "
	ReasoningExpand   = "ctrl+r expand"
	ReasoningCollapse = "ctrl+r collapse"

	// ask_user dialog copy (Phase 3 Task 11). HelpAsk is the footer help line
	// while a question is pending; the AskHint* lines sit beneath the option
	// list itself and, unlike HelpAsk, always mention the free-text escape
	// hatch (typing instead of picking), since that's the one thing every ask
	// dialog offers regardless of how it's answered.
	HelpAsk             = "up/down move · pgup/pgdn page · enter pick · esc cancel"
	AskHintSingleSelect = "up/down/pgup/pgdn move · enter pick · or type your answer"
	AskHintMultiSelect  = "up/down/pgup/pgdn move · space toggle · enter confirm · or type your answer"
	AskHintFreeText     = "type your answer"

	// AskBlockedByApproval replaces the ask dialog's normal hint when a tool
	// approval is also pending: the approval's key-driven choice mode swallows
	// every keypress (arrows, space, typed text) except its own, so none of
	// the usual ask-dialog affordances actually do anything until it resolves
	// — a silent dead end without this note.
	AskBlockedByApproval = "waiting on the tool approval above — resolve that first"

	// /resume picker copy (Phase 3 Task 15). ResumeTitle is the dialog's
	// heading; HelpResume is the footer help line while the picker is open —
	// no free-text escape hatch here (unlike HelpAsk), since a session id
	// picked from a list has no meaningful typed alternative. ResumeEmpty is
	// the notice for a cwd-scoped picker with nothing to show; ResumeRestored
	// (a %d format string for the message count) confirms a successful
	// restore.
	ResumeTitle    = "resume a session"
	HelpResume     = "up/down move · pgup/pgdn page · enter resume · d delete · esc cancel"
	ResumeEmpty    = "no recorded sessions here · /resume --all to look wider"
	ResumeRestored = "restored session · %d messages"

	// ResumeDeleteConfirm replaces the picker's normal hint once its delete
	// key has been pressed once (Task 20): deleting a recorded session
	// removes the file outright with no trash/undo (chat.SessionStore has
	// neither), so a single "d" only ARMS deletion of the highlighted row —
	// this is the prompt shown while armed. A second "d" (with nothing else
	// pressed in between) performs the delete; any other key cancels the arm.
	ResumeDeleteConfirm = "press d again to delete this session · any other key cancels"

	// YoloUsage is the /yolo slash command's usage error, shown when the
	// argument after "yolo" is neither empty, "on" nor "off".
	YoloUsage = "usage: /yolo [on|off]"

	// Built-in `/` completion entries (tui/completion.go's buildCompItems).
	// Name is the verb shown, matched against the typed query, and used to
	// build the option's Insert token; Desc is the one-line summary shown
	// beside it in the popup.
	CompLoopName    = "loop"
	CompLoopDesc    = "recurring or self-paced task"
	CompCompactName = "compact"
	CompCompactDesc = "compact the conversation"
	CompGoalName    = "goal"
	CompGoalDesc    = "set a goal nib checks before stopping"
	CompModelName   = "model"
	CompModelDesc   = "switch the session model"
	CompModelsName  = "models"
	CompModelsDesc  = "list the models this endpoint serves"
	CompAttachName  = "attach"
	CompAttachDesc  = "stage a file for the next message"
	CompYoloName    = "yolo"
	CompYoloDesc    = "toggle (or on/off) auto-approve every tool call"
	CompResumeName  = "resume"
	CompResumeDesc  = "resume a recorded session"

	// ToolResultNoOutput is fmtBashResult's (chat/resultfmt.go) fallback for a
	// failed bash/bash_job_output call whose stdout and stderr were both
	// empty — a %d format string for the exit code.
	ToolResultNoOutput = "(exit %d, no output)"

	// UsageEstimatedPrefix marks the session usage badge (tui/model.go's
	// usageBadge) when its figure is chat.Session.EstimatedUsage's byte/4
	// guess rather than measured spend — the same "~" convention prunedNotice
	// and compactNotice already use for their own estimates. Kept to a single
	// ASCII character on purpose: footerBadges drops the whole usage badge
	// when the footer is tight, so a longer marker only makes it disappear
	// sooner.
	UsageEstimatedPrefix = "~"
)

Microcopy — calm, lowercase, no wizard metaphor, no emoji.

View Source
const (
	VerbThinking = "thinking"
	VerbWorking  = "working"
	VerbReading  = "reading"
)

Status verbs shown while the agent works.

View Source
const ReasoningMaxLines = 5

ReasoningMaxLines is how many trailing lines a collapsed reasoning box shows.

Variables

View Source
var (
	Accent = lipgloss.Color("173") // clay — brand, prompt, affordances
	Sage   = lipgloss.Color("108") // muted green — success / done
	Danger = lipgloss.Color("131") // muted brick — errors / denials
	Dim    = lipgloss.Color("245") // labels, rules, help
	Faint  = lipgloss.Color("240") // ghost hints, metadata
)

Inks — 256-color, foreground only. Body text uses the terminal default fg.

View Source
var (
	Sep            = "·"  // separator between label and message / list items
	PromptGlyph    = "›"  // input prompt
	ApprovalGutter = "▏"  // left rule on a tool-approval block
	MsgGutter      = "▏"  // left rule marking a user/assistant message block (full surface)
	SubAgent       = "↳"  // sub-agent line marker
	Cross          = "×"  // error marker
	Arrow          = "→"  // tool-call / edit / mapping arrow
	Loop           = "↻"  // recurring-loop footer marker
	Goal           = "◎"  // active-goal footer marker
	ShellJob       = "▷"  // shell-jobs footer marker
	ScrollKeys     = "↑↓" // up/down navigation hint
	ReasoningGlyph = "✻"  // marks a block of model thinking/reasoning
	NewOutputGlyph = "↓"  // footer marker: new content arrived while scrolled up
	HairlineGlyph  = "─"  // the one-cell rule repeated under the header
	BoxRule        = "│"  // vertical rule down the side of a collapsed trace box

	// RadioOn/RadioOff mark a single-select ask_user option; CheckOn/CheckOff
	// mark a multi-select one. Cursor marks whichever row is highlighted,
	// regardless of selection mode. All four are geometric shapes, which paint
	// as blank cells on the Linux VT console — see applyGlyphProfile.
	RadioOn  = "◉"
	RadioOff = "○"
	CheckOn  = "◼"
	CheckOff = "◻"
	Cursor   = "▸"
)

Glyphs — typographic marks, no emoji. These are vars, not consts, because RestrictedGlyphs() swaps the non-Latin-1 marks for ASCII stand-ins at startup (see init below). Render through these names rather than hardcoding the rune so a single switch covers every call site.

View Source
var (
	Brand      = lipgloss.NewStyle().Bold(true).Foreground(Accent)
	Rule       = lipgloss.NewStyle().Foreground(Dim)
	LabelYou   = lipgloss.NewStyle().Foreground(Dim)
	LabelNib   = lipgloss.NewStyle().Foreground(Accent)
	SepStyle   = lipgloss.NewStyle().Foreground(Faint)
	Prompt     = lipgloss.NewStyle().Foreground(Accent)
	Hint       = lipgloss.NewStyle().Foreground(Faint)
	Help       = lipgloss.NewStyle().Foreground(Dim)
	Meta       = lipgloss.NewStyle().Foreground(Faint)
	Reasoning  = lipgloss.NewStyle().Foreground(Dim).Italic(true)
	Subtle     = lipgloss.NewStyle().Foreground(Dim).Italic(true)
	Error      = lipgloss.NewStyle().Foreground(Danger)
	Gutter     = lipgloss.NewStyle().Foreground(Accent)
	ApproveKey = lipgloss.NewStyle().Bold(true).Foreground(Accent)
	Running    = lipgloss.NewStyle().Foreground(Accent)
	Done       = lipgloss.NewStyle().Foreground(Sage)
	// Yolo flags the auto-approve-everything mode — bold brick so it reads as a
	// standing warning that the approval gate is off.
	Yolo = lipgloss.NewStyle().Bold(true).Foreground(Danger)
)

Styles. Bold is reserved for the brand mark and the active approval keys.

View Source
var EmptyExamples = []string{
	"what changed in the last commit?",
	"undo my last git commit",
	"find every TODO in this repo",
}

EmptyExamples are the sample prompts shown on the first-run empty state.

Functions

func CLIApprovePrompt

func CLIApprovePrompt(alwaysScope string) string

CLIApprovePrompt builds the line-based CLI approval prompt (the TUI uses the numbered single-key menu instead). alwaysScope describes what `a` grants for this call — e.g. "`git …`", "any bash command", or a tool name.

func Hairline added in v0.8.0

func Hairline(width int) string

Hairline renders the dim horizontal rule that closes the header: the swappable HairlineGlyph (─ / - in restricted mode) repeated to width, in the Rule style. It lives here rather than in a presenter because both surfaces draw the same rule, and repeating the rune inline in each of them put a non-Latin-1 glyph outside RestrictedGlyphs()'s reach. A width below 1 still yields one cell, so the rule never renders as the empty string.

func NewOutputMarker added in v0.8.0

func NewOutputMarker() string

NewOutputMarker renders the dim footer marker shown when the user is scrolled up in the transcript and content has arrived below the fold — the swappable NewOutputGlyph (↓ / v in restricted mode) plus NewOutputText, both in the same dim Help style as the rest of the footer.

func ReasoningHeader

func ReasoningHeader() string

ReasoningHeader renders the labeled header that tags a block of model thinking, so it reads as a distinct channel from the assistant's answer: an accent glyph (✻ / * in restricted mode) and a dim, non-italic label. The body beneath is rendered with the Reasoning style by the caller.

func RestrictedGlyphs

func RestrictedGlyphs() bool

RestrictedGlyphs reports whether glyphs must fall back to ASCII because the terminal can only render a fixed bitmap font with no arrows, geometric shapes, or eighth-block glyphs. The Linux VT console (TERM=linux) is the canonical case — there, the unmapped runes paint as blank cells. NIB_ASCII overrides the autodetection: "1"/"true"/"yes" forces the stand-ins on any terminal, "0"/"false"/"no" forces the full set.

func SpinnerFrames added in v0.8.0

func SpinnerFrames() []string

SpinnerFrames returns the animation frames for the current terminal profile.

Types

This section is empty.

Jump to

Keyboard shortcuts

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