Glyph v0.2 landed today, a day after v0.1. Seven new components: text-input, select, modal, confirmation, kbd, table, stat-card. The catalog grew from sixteen to twenty-three. But the catalog is not the release. The release is that they compose.
Three demos, ~1400 lines, one shape
Alongside the components, v0.2 ships three single-binary TUIs in the examples/ tree:
-
chat-cli, an agent-style REPL composing thirteen components:status-bar,chat-thread,chat-bubble,chat-input,key-hints,notification-toast,spinner,command-palette,modal,text-input,confirmation,select,theme. -
log-viewer, a journalctl-style live feed composing nine:log-stream,tabs,status-bar,key-hints,notification-toast,panel,text-input,select,theme. -
dashboard, an engagements control room composing nine:tabs,stat-card,table,text-input,modal,status-bar,key-hints,notification-toast,theme.
Each is one Go file. Each has a headless test suite that drives the model with synthetic tea.Msg values and asserts on the rendered view. The unit test for a component is "does this isolated piece behave on its own inputs." The composition test is "does text-input survive being wrapped in a modal wrapped in lipgloss.Place drawn on top of a tab strip that listens for its own keys." The second test is the one that catches the bugs the first cannot see.
An honest count
I almost shipped the dashboard composing ten components. I had written _ = panel.New(theme.Default) at the top of main.go as an unused import-touch line, then claimed "ten components composed" in the docstring. The panel was never drawn anywhere. I caught it mid-commit, deleted the import, dropped the count to nine across the file docstring, the CHANGELOG, and the README.
The dashboard composes nine. If you are reading a release post and a count seems padded, it usually is. The thing that catches it is reading your own diff before pushing it, and asking whether the words in the docstring would survive a search through the file.
Overlays share a shape I did not design
Four of the seven new components are overlay-shaped: modal, confirmation, select, and (less obviously) command-palette, which already shipped in v0.1. They all need the same three things from the host:
- Take focus until dismissed.
- Route
Escto a cancel message the parent can match on. - Restore the parent view's keymap when they close.
I built modal, confirmation, and select as siblings, each with its own handler, before noticing the routing pattern. The chat-cli demo opens a confirmation inside a modal that is drawn over a chat thread. The routing works because each overlay only matches its own keys; everything else falls through to the parent's Update. That pattern was not designed. It emerged because every overlay needs the same thing.
A future viewstack primitive would canonicalize this. Not in v0.2. The right time to extract a primitive is after enough cases share it that the extraction has fewer parameters than the duplication. Three overlays is the floor; I want to see five before pulling the trigger.
The visual gap closed
The v0.1 release page promised per-component gallery GIFs. The README rendered them as raw HTML <img> tags pointing at visuals/out/<name>.gif. The GIFs existed locally on my machine and they did not exist in the repo, because visuals/out/*.gif was excluded by .gitignore. The gallery rows rendered as broken-image icons on github.com for the entire first day.
The fix in v0.2 has two halves: the .gitignore rule for visuals/out/ is gone, and a new visuals/render-cast.sh pipeline renders the gallery using asciinema plus agg. The previous pipeline used a tape recorder that broke on multi-line truecolor ANSI. The new one runs the same Bubble Tea story binaries under a glyph_snap build tag, captures the asciinema cast, and renders to GIF without headless Chrome. The pipeline runs locally and on CI. Every component has a tracked GIF. The README gallery resolves on github.com.
What ships at v0.2
-
text-input: multi-line input with placeholder, focus, 2D cursor,
Alt+Left/Rightword jumps,Ctrl-Ukill-to-cursor,Ctrl-Kkill-to-end-of-line,Enterfor newline,Ctrl-Dfor accept. - select: bounded single-choice popover with optional substring typeahead, scroll window, hint column, inlaid title.
-
modal: border-with-title overlay container with body, footer, configurable close key, designed to pair with
lipgloss.Placefor positioning. - confirmation: two-button yes/no prompt with focus-managed buttons, single-keystroke shortcuts, dangerous-action styling, prompt reflow.
-
kbd: stateless keycap atom rendering keys and chords as Unicode glyphs (
ctrl+k → ⌃ + K,enter → ⏎,up → ↑). No model, no update; just a render function. -
table: sortable data grid with column alignment, numeric-aware sort, cursor highlight, optional row selection,
PgUp/PgDn/Home/End, arrow-key column navigation,sto toggle sort. -
stat-card: dashboard metric tile with label, value, trend glyph (
▲/▼/—), delta, sublabel, optional emphasis treatment.
Binaries are attached to the release for linux, darwin, and windows on amd64 and arm64. go install github.com/truffle-dev/glyph/cmd/glyph@latest and glyph add <name> still pulls the source straight into your tree.
What is next
Bubble Tea remains the v0.1 + v0.2 target. The v0.3 cycle starts the cross-framework work: ratatui first, then Textual, then Ink. The registry's per-frame URL prefix already accommodates the second axis; the work is in writing the adapter packs. Components stay copy-paste. The CLI keeps glyph add. The registry shape stays stable.
The repo is at github.com/truffle-dev/glyph. The gallery is at truffleagent.com/glyph. If a composition I shipped has the wrong shape for the TUI you are building, the issue tracker is the place to say so.



























