Teaches coding agents how to build TUIs with TamboUI correctly: API-level selection, render-thread discipline, display-width safety, CSS-aware element authoring, and JFR conventions.
76
95%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Process steps in order, do not skip ahead. The end state is a ListElement held as a field, configured with the log-style defaults (no selection, no highlight symbol, no inverted row), sticky scroll, a visible scrollbar, mouse-wheel capture enabled, focus chain id, and a pre-wrap helper for long content. This skill replaces several traps the default list(...) factory leaves open — see rules/persistent-stateful-elements.md, rules/enable-mouse-capture-when-scrollable.md, rules/focusable-needs-id.md, and rules/pick-the-text-element.md.
list(...) instead"chat", "trace", "log") — it becomes the focus-chain id and any future CSS selector targetToolkitApp subclass, declare the list as a final / val field: private final ListElement<String> chatList = list();render() is wrong — see rules/persistent-stateful-elements.md. A fresh instance every frame loses scroll position and the user-scrolled-away flagprivate final List<String> chatLines = new ArrayList<>();) so background threads can mutate it under runOnRenderThreadlist<String>()
.selected(-1) // no row is "selected"
.highlightSymbol("") // no "> " prefix
.highlightStyle(Style.EMPTY) // no inverted row colorslist(...) is built for menus; log/chat use needs all three overrides.stickyScroll() so new lines auto-scroll into view unless the user has scrolled away, and .scrollbar() so the user has a visible position indicator.id("<role>").focusable() — the id must be stable and unique within the apprules/focusable-needs-id.md — .focusable() without an id is silently dropped and the pane never receives keyboard or click eventsconfigure()configure() on the ToolkitApp and return TuiConfig.builder().mouseCapture(true).build()rules/enable-mouse-capture-when-scrollable.md — without this, wheel scroll never reaches ListElement.handleMouseEvent and the pane appears brokenconfigure() override covers every scrollable pane in the app — you do not repeat the call per elementrules/pick-the-text-element.md) — for variable-width content, pre-wrap into multiple short rows at insert timewrap(text: String, width: Int): List<String> that does greedy word-wrap with a hard-break fallback for tokens longer than widthchatList.runOnRenderThread { chatLines.addAll(wrap(line, WRAP)); chatList.elements(*chatLines.toTypedArray()) } — mutate the backing collection, then feed the listStyle.EMPTY defaults elsewhererules
skills