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
Toolkit elements that carry per-instance state (scroll position, selection, cursor, expand/collapse, text-input buffer) must be held as fields on the ToolkitApp and re-used across renders. Building a fresh list(...), table(...), tree(...), or textInput(...) inside render() discards the state every frame. Why: the render cycle replaces the element instance, but events targeting the previous instance have already mutated its now-orphaned state — from the outside it looks identical to "scroll/selection doesn't work."
val / final field on the ToolkitApp subclass: private final ListElement<String> chatList = list().stickyScroll().scrollbar()...;render(), refresh the content of that field — chatList.elements(*chatItems) or .items(...) — rather than calling the factory againTableElement, TreeElement, anything ending in *Element that exposes a *State or a setter for its own collectionprivate final *State field (e.g., ListState, TreeState, TextInputState)State — if it has one, it is statefultextInput() called inlineoverride fun render() = panel("CHAT",
list(*chatItems).stickyScroll().scrollbar()
).rounded()private val chatList = list<String>().stickyScroll().scrollbar()
.id("chat").focusable()
override fun render() = panel("CHAT",
chatList.also { it.elements(*chatItems) }
).rounded()evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
rules
skills