Documentation
¶
Overview ¶
* ChatCLI - UI kit: badges * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0
* ChatCLI - UI kit: box border geometry * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0 * * The two border builders every card in the app uses, moved verbatim from * cli/agent. They return PLAIN (uncolored) lines: color belongs to the * caller — the agent wrappers keep their legacy ANSI-constant coloring * byte-identical, and kit-native components colorize through theme roles. * targetWidth is always the EXACT visible width to produce, measured with * VisibleLen (lipgloss.Width) on the matching body so every border row * agrees with every body row even under emoji-width drift.
* ChatCLI - UI kit: shared presentation components * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0 * * Package kit is the single presentation vocabulary for everything chatcli * prints: one width helper, one style API over ui/theme roles, one status * glyph set, and small pure components (Notice, Rule, Header, Badge, KVTable, * List). It exists because the CLI accreted five terminal-width helpers, * three box idioms and three success glyphs — every surface reinvented * presentation, which is exactly what read as unpolished. * * Contracts: * - LEAF package: may import ui/theme, lipgloss, runewidth, x/term and the * stdlib — never i18n or anything under cli/. Components receive * already-translated strings and only decorate them, so the package can * be shared by cli and cli/agent and stays out of the i18n gates. * - Components are pure functions returning strings; printing (and the * single blank line that separates top-level blocks) belongs to the call * site. * - Color always flows through a theme.Role. On colorless profiles every * escape vanishes (theme.ANSI/Reset return "") so piped output is plain. * - Spacing grid: 2-space indent, right margin of RightMargin columns, * components never emit leading/trailing blank lines.
* ChatCLI - UI kit: canonical status glyphs * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0 * * One glyph per meaning, defined once. The audit found three success marks, * four error marks and eight bullet variants in the wild; this file is the * whole vocabulary from now on. Every glyph is deliberately chosen WITHOUT * the Unicode Emoji property, so runewidth (StrictEmojiNeutral=false) * measures them exactly as terminals render them — 1 cell — keeping columns * and box borders aligned on every platform, including Windows. Colored * emoji remains legal only as a box Icon or in the welcome logo; the U+FE0F * variation selector is banned everywhere (it silently flips a glyph from 1 * to 2 cells).
* ChatCLI - UI kit: key/value tables * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0
* ChatCLI - UI kit: lists * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0
* ChatCLI - UI kit: status notices * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0
* ChatCLI - UI kit: horizontal rules and headers * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0
* ChatCLI - UI kit: role-based styling * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0
* ChatCLI - UI kit: terminal width * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0
* ChatCLI - UI kit: ANSI-aware wrapping * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0 * * The three wrap flavors every boxed surface uses, moved verbatim from * cli/agent so chat, agent and command surfaces share one wrap math: * * - WrapText: prose word-wrap (collapses whitespace) — reasoning, replies. * - WrapStructured: glamour-rendered bodies — preserves (and dedents) * indentation so YAML/JSON/code keeps its shape. * - WrapPreserve / WrapStreamLine: raw tool output — every fitting line * verbatim, only overflowing lines break, indentation repeated.
Index ¶
- Constants
- func Badge(text string, r theme.Role) string
- func BilateralBorder(lc, rc rune, leftLabel, rightLabel string, targetWidth int) string
- func Bold(s string, r theme.Role) string
- func Colorize(s string, r theme.Role) string
- func ContentWidth() int
- func Dim(s string) string
- func HardBreakWord(w string, limit int) []string
- func Header(title string) string
- func KVTable(rows []KVRow) string
- func List(items []string) string
- func Notice(l Level, msg string) string
- func NoticeRole(g Glyph, msg string, r theme.Role) string
- func Numbered(items []string) string
- func PadRight(s string, cols int) string
- func Rule() string
- func RuleHeader(left, right string, width int) string
- func RuleTitled(title string) string
- func SplitLeadingIndent(line string) (indent int, codes string, content string)
- func StripANSI(s string) string
- func StripVS16(s string) string
- func Style(r theme.Role) lipgloss.Style
- func Subheader(title string) string
- func Sym(g Glyph) string
- func TermWidth() int
- func TitledTopBorder(header string, targetWidth int) string
- func TrimBlankBorderRows(rows []string) []string
- func TrimBlankBoxBodyRows(rendered string) string
- func Truncate(s string, maxCols int) string
- func VisibleLen(s string) int
- func WrapPreserve(text string, limit int) []string
- func WrapStreamLine(line string, width int) []string
- func WrapStructured(text string, limit int) []string
- func WrapText(text string, limit int) []string
- type Glyph
- type KVRow
- type Level
Constants ¶
const ( // RightMargin is the number of columns every full-width component leaves // free on the right so borders never touch the terminal edge (or a // native scrollbar). Previously scattered as ad-hoc "-2" and "-1". RightMargin = 2 // MinContentWidth is the floor below which components stop shrinking and // accept horizontal overflow — matching the response envelope's historic // minimum so extremely narrow terminals degrade the same way everywhere. MinContentWidth = 40 )
Variables ¶
This section is empty.
Functions ¶
func Badge ¶
Badge renders a small bracketed chip — "[text]" — in the given role. Used for value qualifiers like source tags and "(default)" hints where a full component would be noise.
func BilateralBorder ¶
BilateralBorder constructs a horizontal border with optional left and right labels embedded between the corner glyphs:
<lc>─ LeftLabel ──────── RightLabel ─<rc>
Layout rules (in visible columns):
- <lc> + '─' + leftLabel if leftLabel != "" (else <lc> + '─')
- fill of '─' to absorb remaining width
- rightLabel + '─' + <rc> if rightLabel != "" (else '─' + <rc>)
A degenerate case where the labels alone exceed targetWidth falls back to a minimal border (the labels survive; the fill goes to zero).
func Bold ¶
Bold renders s bold in the role's color — the "title weight" of the typographic hierarchy. No escapes are emitted on colorless profiles.
func Colorize ¶
Colorize wraps s in the role's color span. On colorless profiles both the color and the reset are empty strings, so the output is exactly s.
func ContentWidth ¶
func ContentWidth() int
ContentWidth is the width components should actually occupy: the terminal width minus the right margin, clamped to MinContentWidth.
func HardBreakWord ¶
HardBreakWord parte uma palavra (sem espaços) em pedaços cuja largura visível não excede limit. A quebra é por runa, medindo com lipgloss.Width, para não cortar sequências UTF-8 / wide-runes no meio. Retorna ao menos um elemento.
func KVTable ¶
KVTable renders aligned key/value rows on the grid. The key column width is measured from the TRANSLATED keys at render time (VisibleLen), so alignment holds in every locale — unlike the fixed "%-32s" and the English-tuned literal spaces it replaces. Keys render dim; values in their role (or plain).
func Notice ¶
Notice renders a one-line status message on the grid: two-space indent, role-colored glyph, message. Error and warning messages are tinted with the glyph's role so the whole line reads as one signal; success and info keep the message in default text (the glyph alone carries the state).
func NoticeRole ¶
NoticeRole renders a notice-shaped line with an arbitrary glyph and role — the escape hatch for surfaces with domain-specific markers (e.g. the running spinner summary) that still want the grid alignment.
func Numbered ¶
Numbered renders items as a numbered list with right-aligned numbers so double-digit lists keep their text column straight:
- item
- item
func PadRight ¶
PadRight pads s with spaces to the given visible width. Measurement is ANSI/emoji-aware (VisibleLen), unlike fmt's "%-Ns" which counts BYTES and silently misaligns any accented or multibyte label — the root cause of column drift in pt-BR surfaces. Strings already at or past the width are returned unchanged.
func Rule ¶
func Rule() string
Rule renders a dim full-content-width horizontal rule — the single replacement for the fixed 39/50/60/70/80-column separators.
func RuleHeader ¶
RuleHeader renders a full-width bilateral rule with pre-formatted labels embedded — the borderless reply header of the "sóbrio" treatment:
── {left} ─────────────── {right} ──
Labels arrive pre-colored (with any breathing spaces the caller wants); the dashes are dim. width <= 0 resolves to ContentWidth; a positive width pins the line for tests. Degenerate widths fall back to a minimal rule with the labels intact.
func RuleTitled ¶
RuleTitled renders a rule with an inline title:
── Title ────────────────
The title is bold in the header role; the dashes are dim. The line always spans ContentWidth.
func SplitLeadingIndent ¶
SplitLeadingIndent separa a indentação inicial de uma linha do seu conteúdo, de forma ANSI-aware. O glamour emite sequências de cor de largura-zero ANTES (e entre) os espaços de indentação do markdown renderizado — então um strings.TrimLeft(" \t") reportaria indentação zero em YAML/JSON/código vindos do glamour. Retorna a largura visível do indent em colunas, os códigos ANSI vistos na região inicial (re-anexados ao conteúdo para que o primeiro token preserve a cor) e o conteúdo restante.
func StripANSI ¶
StripANSI removes CSI color escapes so width and emptiness checks see plain text. Loop-based to avoid a regex dependency on hot render paths.
func StripVS16 ¶
StripVS16 removes every U+FE0F variation selector from s. Used by renderers to sanitize legacy catalog values during the migration; the selector flips glyph width between terminals and breaks column math.
func Style ¶
Style returns a lipgloss style whose foreground is the role's color under the active theme and profile — the lipgloss counterpart of Colorize, mirroring the palette overlay's style(role) pattern.
func TermWidth ¶
func TermWidth() int
TermWidth returns the live terminal width in columns, or fallbackWidth when stdout is not a terminal. Queried per call so live resizes are honored by the next render.
func TitledTopBorder ¶
TitledTopBorder produces a `╭── header ─────╮` line whose VISIBLE width equals targetWidth. The two padding rules cover the two ways the header can fall short of the card width:
- normal case: header fits, fill with dashes
- header longer than card: emit a minimal top without filling (the card still closes at the right width because callers honor the body measurement; 1-2 cols of overflow is accepted degradation)
func TrimBlankBorderRows ¶
TrimBlankBorderRows drops fully-blank rows from the leading and trailing edges of a wrapped-text slice. A row is "blank" when it has zero visible width — color codes alone don't count as visible content. Blank rows in the MIDDLE are preserved so paragraph breaks the author put in markdown survive.
func TrimBlankBoxBodyRows ¶
TrimBlankBoxBodyRows removes fully-empty content rows directly adjacent to the top or bottom border of a lipgloss-rendered box. An empty row looks like "│ │" — same width as the sides but zero printable content between them. Middle blanks are kept (author-intended paragraph breaks survive).
func Truncate ¶
Truncate clamps s to at most maxCols visible columns, preserving ANSI color sequences and appending an ellipsis plus a reset when content was dropped so styling never bleeds into the next line.
func VisibleLen ¶
VisibleLen measures the display width of s in terminal cells, ignoring ANSI escapes. This is the kit's single measurement path (lipgloss.Width), shared with the agent renderer so every component agrees on geometry.
func WrapPreserve ¶
WrapPreserve quebra texto preservando a estrutura: cada linha que cabe no limite é mantida exatamente como está (indentação e espaçamento de colunas intactos) e só as que estouram são quebradas, repetindo a indentação nas continuações — usada em output cru de tool (YAML/JSON/tabelas), onde colapsar whitespace como o word-wrap de prosa destruiria o layout.
func WrapStreamLine ¶
WrapStreamLine quebra UMA linha de output cru de tool na largura visível informada. Diferente de WrapText, NÃO colapsa espaços em branco: a indentação inicial é preservada (e repetida nas continuações) para que YAML/JSON estruturado continue legível dentro do box. A quebra é por runa (ANSI/wide-rune aware via lipgloss.Width), evitando cortar sequências multibyte no meio.
func WrapStructured ¶
WrapStructured quebra um corpo já renderizado pelo glamour para exibição dentro de um box. Diferente de WrapText (word-wrap de prosa, que colapsa a indentação via strings.Fields), ele PRESERVA a indentação inicial de cada linha. Mecânica, por linha:
- Detecta o indent inicial visível (ANSI-aware: o glamour emite cores ANTES dos espaços, então um TrimLeft simples não enxerga o indent).
- Deslova (dedent) toda linha pela margem mínima comum — o glamour aplica uma margem de documento uniforme (tipicamente 2 cols) a prosa E código; como o box já tem seu próprio padding, carregar a margem do glamour também empurraria tudo para a direita.
- Linhas que cabem na largura interna são emitidas verbatim (alinhamento de colunas intacto). Só as que estouram passam por word-wrap, repetindo o indent da linha em cada continuação.
Types ¶
type Glyph ¶
type Glyph int
Glyph identifies one entry of the canonical status vocabulary.
const ( // GlyphSuccess marks a completed action. GlyphSuccess Glyph = iota // GlyphError marks a failed action. GlyphError // GlyphWarn marks a warning. GlyphWarn // GlyphInfo marks an informational line. GlyphInfo // GlyphBullet marks a list item. GlyphBullet // GlyphArrow marks user input echoes and drill-downs. GlyphArrow // GlyphRunning marks an in-progress action. GlyphRunning // GlyphAssistant marks assistant text in compact timelines. GlyphAssistant // GlyphEllipsis marks truncation. GlyphEllipsis )
type KVRow ¶
KVRow is one row of a KVTable. Key and Value arrive already translated. ValueRole tints the value; the zero role renders it as plain text (a border-colored value is never what a table wants, so the zero value is repurposed as "unstyled"). Note is an optional dim qualifier appended after the value — e.g. a "(default)" hint.