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.
87
90%
Does it follow best practices?
Impact
84%
1.44xAverage score across 5 eval scenarios
Passed
No known issues
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 elsewhereevals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
rules
skills