tui-builder
Skill15 starsChanged 4 months agoApache-2.0View on GitHub
Use when building, modifying, or debugging the syllago TUI. Provides golden rules checklist, component message contracts, and collected gotchas. TRIGGER when working on any file in cli/internal/tui/.
2 other projects use a version of this.
Adopted by
Most recently taken up by
- MatanelGordon/skillstheir own version, first seen Aug 21, 2026
- TheDecipherist/claude-code-mastery-project-starter-kittheir own version, first seen Jun 26, 2026
Other versions2 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 2
- TheDecipherist/claude-code-mastery-project-starter-kit★ 337same name
- MatanelGordon/skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository4 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- cli/CLAUDE.mdCLAUDE.md
- releaseSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
14 rules. About 0 are common, 5 are distinctive, and 9 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈5
Distinctive · estimate
This author's own choices. Worth a look.
9
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 13
Before any layout or component work, verify:
- Very specific: line 17
Never rely on auto-wrap — truncate explicitly (maxTextWidth = panelWidth - 4)
- Very specific: line 18
Use MaxWidth() not Width() — Width word-wraps
- Very specific: line 19
Use borderedPanel() for all bordered panels
- Very specific: line 21
All async work via tea.Cmd — never goroutines
- Very specific: line 22
All state changes returned from Update() — never mutate elsewhere
- Very specific: line 23
Always propagate Update() to active sub-models
- Very specific: line 43
lipgloss.Width()is ANSI-aware,len()is not: Always use lipgloss.Width() for rendered strings. - Distinctive: line 45
goimports strips "unused" imports between edits: Add import and usage in a single Edit call.
- Very specific: line 46
Cursor initialization with Reset(): When active = -1, Open() must default cursor to 0, not -1.
- Distinctive: line 48
Manual string splitting destroys zone markers: Never split rendered strings containing zone.Mark() on newlines or truncate by rune — the …
- Distinctive: line 50
App-level keys intercept search input: When the library search input is active, keys like 'a' (add), 'q' (quit), '1' (group switch) must be …
- Distinctive: line 51
Help bar separator is middle dot (·): not asterisk (*). Cleaner look.
- Very specific: line 53
Modal
lipgloss.Place()centering: Use lipgloss.Place(width, height, lipgloss.Center, lipgloss.Center, rendered) for modal centering — not …
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
TUI Builder — Golden Rules + Quick Reference
Context gate: On first invocation in a session, run:
touch /tmp/syllago-tui-builder-${PPID}This signals the tui-context-gate hook that the skill was loaded.
Golden Rules Checklist
Before any layout or component work, verify:
- Mouse + keyboard parity on EVERY interactive element —
zone.Mark()in View,zone.Get().InBounds()in updateMouse. No exceptions. If it responds to a keypress, it must respond to a click. - Subtract 2 from height for borders, 2 from width for borders
- Never rely on auto-wrap — truncate explicitly (
maxTextWidth = panelWidth - 4) - Use
MaxWidth()notWidth()— Width word-wraps - Use
borderedPanel()for all bordered panels - Parent owns child sizes — call
SetSize(), never let children calculate - All async work via
tea.Cmd— never goroutines - All state changes returned from
Update()— never mutate elsewhere - Always propagate
Update()to active sub-models - Handle
tea.WindowSizeMsg— store and recalculate
Component Message Contracts
| Component | Receives | Sends |
| topbar | tea.WindowSizeMsg, tea.KeyMsg, tea.MouseMsg | tabChangedMsg, actionPressedMsg, helpToggleMsg |
| items | tea.WindowSizeMsg, tea.KeyMsg | itemSelectedMsg |
| explorer | tea.WindowSizeMsg, tea.KeyMsg, content items | explorerDrillMsg, explorerCloseMsg |
| gallery | tea.WindowSizeMsg, tea.KeyMsg, tea.MouseMsg | cardSelectedMsg, cardDrillMsg |
| modal | tea.KeyMsg, tea.MouseMsg | editSavedMsg, editCancelledMsg |
| install | tea.KeyMsg, tea.MouseMsg, step data | installResultMsg, installDoneMsg, installCloseMsg |
| toast | tea.KeyMsg, toastTickMsg | (dismisses via Dismiss() cmd) |
Gotchas
- AdaptiveColor mutates renderer state. Fix: warmup render in
TestMain(). - bubbletea v1 uses
tea.KeyMsg, nottea.KeyPressMsg(v2 API). Check version before using spec code. - Golden files with raw ANSI are fragile. Strip ANSI before storing.
lipgloss.Width()is ANSI-aware,len()is not. Always uselipgloss.Width()for rendered strings.- Children that never receive WindowSizeMsg render at zero size.
- goimports strips "unused" imports between edits. Add import and usage in a single Edit call.
- Cursor initialization with Reset(): When
active = -1,Open()must default cursor to 0, not -1. - lipgloss Width() wraps, MaxWidth() truncates. For bordered panels, always use both Width+MaxWidth and Height+MaxHeight. Without MaxHeight, content overflow pushes the header offscreen.
- Manual string splitting destroys zone markers. Never split rendered strings containing zone.Mark() on newlines or truncate by rune — the invisible zero-width markers get broken. Use lipgloss styling for dimension control instead.
- Sort indicators overflow short columns. "Files ▲" is 7 visual chars but the Files column is 5. Use
headerCell()which truncates the label to make room for the indicator within the column width. - App-level keys intercept search input. When the library search input is active, keys like 'a' (add), 'q' (quit), '1' (group switch) must be passed through to the search handler instead of triggering app shortcuts. Check
table.searchingbefore handling global letter keys. - Help bar separator is middle dot (·) not asterisk (*). Cleaner look.
- Metadata bar steals table height. When adding a fixed-height panel below a variable-height component (like table + metadata bar), always reduce the variable component's height in both
SetSize()ANDView(). If only one is updated, the table renders at the wrong height on resize vs initial draw. - Modal
lipgloss.Place()centering. Uselipgloss.Place(width, height, lipgloss.Center, lipgloss.Center, rendered)for modal centering — not manual padding math. Handles terminal resize automatically. - YAML folded scalars add trailing newlines. Descriptions from
loadout.yamlusing>have\nat the end. Always sanitize catalog text fields before rendering in the TUI. - Unified frame requires manual border construction.
borderedPanel()can't create shared borders between sections. Build frames manually:╭/╰for corners,├──┤for internal separators,├──┬──┤/╰──┴──╯for split pane junctions. - Metadata panel height is constant (metaBarLines=3 + 1 separator). Type-specific line 3 is blank for simple types, not omitted. This prevents the frame from shifting when scrolling between hooks and skills.
- Explorer needs providers for metadata. Pass providers + repoRoot to
newExplorerModel()so it can compute installed status viacomputeMetaPanelData(). - Keyboard-only components are bugs. If a View renders interactive elements without
zone.Mark(), mouse users can't interact with them. Every radio option, checkbox row, list item, button, and text input needs a zone mark in View and a correspondingzone.Get().InBounds()check in updateMouse. ThecheckboxListcomponent automates this viazonePrefix— set it and callHandleClick()in the parent's mouse handler.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

