agentleFS
OpenScribblerOpenScribbler/syllago/.claude/skills/tui-builder/SKILL.md

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

  • Other versions2 projects use a version of this.

    Word for word · 0

    None found.

    Edited · 0

    None found.

    Wrote their own version · 2

    1. TheDecipherist/claude-code-mastery-project-starter-kit★ 337same name
    2. 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.

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
  1. Distinctive:

    Before any layout or component work, verify:

    line 13
  2. Very specific:

    Never rely on auto-wrap — truncate explicitly (maxTextWidth = panelWidth - 4)

    line 17
  3. Very specific:

    Use MaxWidth() not Width() — Width word-wraps

    line 18
  4. Very specific:

    Use borderedPanel() for all bordered panels

    line 19
  5. Very specific:

    All async work via tea.Cmd — never goroutines

    line 21
  6. Very specific:

    All state changes returned from Update() — never mutate elsewhere

    line 22
  7. Very specific:

    Always propagate Update() to active sub-models

    line 23
  8. Very specific:

    lipgloss.Width() is ANSI-aware, len() is not: Always use lipgloss.Width() for rendered strings.

    line 43
  9. Distinctive:

    goimports strips "unused" imports between edits: Add import and usage in a single Edit call.

    line 45
  10. Very specific:

    Cursor initialization with Reset(): When active = -1, Open() must default cursor to 0, not -1.

    line 46
  11. Distinctive:

    Manual string splitting destroys zone markers: Never split rendered strings containing zone.Mark() on newlines or truncate by rune — the …

    line 48
  12. Distinctive:

    App-level keys intercept search input: When the library search input is active, keys like 'a' (add), 'q' (quit), '1' (group switch) must be …

    line 50
  13. Distinctive:

    Help bar separator is middle dot (·): not asterisk (*). Cleaner look.

    line 51
  14. Very specific:

    Modal lipgloss.Place() centering: Use lipgloss.Place(width, height, lipgloss.Center, lipgloss.Center, rendered) for modal centering — not …

    line 53

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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:

  1. 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.
  2. Subtract 2 from height for borders, 2 from width for borders
  3. Never rely on auto-wrap — truncate explicitly (maxTextWidth = panelWidth - 4)
  4. Use MaxWidth() not Width() — Width word-wraps
  5. Use borderedPanel() for all bordered panels
  6. Parent owns child sizes — call SetSize(), never let children calculate
  7. All async work via tea.Cmd — never goroutines
  8. All state changes returned from Update() — never mutate elsewhere
  9. Always propagate Update() to active sub-models
  10. Handle tea.WindowSizeMsg — store and recalculate

Component Message Contracts

ComponentReceivesSends
topbartea.WindowSizeMsg, tea.KeyMsg, tea.MouseMsgtabChangedMsg, actionPressedMsg, helpToggleMsg
itemstea.WindowSizeMsg, tea.KeyMsgitemSelectedMsg
explorertea.WindowSizeMsg, tea.KeyMsg, content itemsexplorerDrillMsg, explorerCloseMsg
gallerytea.WindowSizeMsg, tea.KeyMsg, tea.MouseMsgcardSelectedMsg, cardDrillMsg
modaltea.KeyMsg, tea.MouseMsgeditSavedMsg, editCancelledMsg
installtea.KeyMsg, tea.MouseMsg, step datainstallResultMsg, installDoneMsg, installCloseMsg
toasttea.KeyMsg, toastTickMsg(dismisses via Dismiss() cmd)

Gotchas

  • AdaptiveColor mutates renderer state. Fix: warmup render in TestMain().
  • bubbletea v1 uses tea.KeyMsg, not tea.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 use lipgloss.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.searching before 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() AND View(). If only one is updated, the table renders at the wrong height on resize vs initial draw.
  • Modal lipgloss.Place() centering. Use lipgloss.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.yaml using > have \n at 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 via computeMetaPanelData().
  • 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 corresponding zone.Get().InBounds() check in updateMouse. The checkboxList component automates this via zonePrefix — set it and call HandleClick() in the parent's mouse handler.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.