CtrlK
BlogDocsLog inGet started
Tessl Logo

jbaruch/tamboui

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

Quality

95%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

SKILL.mdskills/wrap-widget-as-element/

name:
wrap-widget-as-element
description:
Add a Toolkit `Element` wrapping an existing TamboUI widget so it gains CSS support, styled sub-components, focus integration, and a Toolkit factory method. Use when the user says "wrap a widget", "add an element for X widget", "make widget Y CSS-aware", "expose widget Z in the DSL", or asks to integrate a custom widget into the Toolkit.

Wrap a Widget as a Toolkit Element

Process steps in order, do not skip ahead. The end state is an Element class that goes through resolveEffectiveStyle for every sub-component, with a Toolkit factory method, JavaDoc-documented CSS selectors, and tests that assert against BufferAssertions.

Step 1 — Identify the Widget and Its Styled Sub-Components

  • Locate the widget under tamboui-widgets/src/main/java/dev/tamboui/widgets/{area}/ and read its render method to enumerate every visually distinct piece (e.g., for a Gauge: filled bar, label; for a List: items, scrollbar thumb/track)
  • Each sub-component becomes one CSS child selector; pick a short stable name (e.g., filled, unfilled, cursor, placeholder, item, header)
  • Run scripts/list-tamboui-modules.sh to confirm the toolkit module path, then list tamboui-toolkit/src/main/java/dev/tamboui/toolkit/element/ for any existing {Name}Element.java — if one exists, stop and tell the user; the right move is to extend the existing Element, not write a duplicate

Step 2 — Create the Element Class

  • Create tamboui-toolkit/src/main/java/dev/tamboui/toolkit/element/<Name>Element.java extending StyledElement (or another existing element base if the widget is stateful)
  • Constructor takes the widget's required inputs as parameters; optional configuration goes through fluent setters that return this
  • Field declarations come first, then constructor, then setters, then renderContent last — match the existing element files

Step 3 — Declare Nullable Style Fields and Defaults

  • For each sub-component identified in Step 1, add private Style <name>Style; (nullable, no initializer) and private static final Style DEFAULT_<NAME>_STYLE = ...;
  • Defaults should be minimal: Style.EMPTY plus a single modifier like .reversed() or .dim() — they compose with whatever the user adds
  • Do not initialize the field to Style.EMPTY — that breaks CSS resolution; see rules/css-element-style-resolution.md
  • Add a fluent setter for each: public <Name>Element <name>Style(Style s) { this.<name>Style = s; return this; }

Step 4 — Implement renderContent Using resolveEffectiveStyle

  • Inside renderContent(RenderContext context, Rect area, Buffer buffer), resolve every sub-component style via resolveEffectiveStyle(context, "<name>", <name>Style, DEFAULT_<NAME>_STYLE) (or the pseudo-class overload for selected/focused state)
  • Then either render directly to the buffer using the resolved styles, or instantiate the widget, configure it with the resolved styles, and call widget.render(area, buffer) — pick whichever is less duplication
  • Honor RenderThread.checkRenderThread() if the element holds mutable state — StyledElement already does this, but custom render helpers may not

Step 5 — Add the Toolkit Factory Method

  • Open dev/tamboui/toolkit/Toolkit.java and add a public static <Name>Element <name>(...) method that constructs and returns the element
  • The method name is the lower-camel-case widget name (e.g., gauge, barChart, lineGauge); match the table in the project README
  • Place the new method alphabetically among existing factories; do not reorder unrelated entries

Step 6 — Document the CSS Selectors

  • In the Element class JavaDoc, add a <h2>CSS Child Selectors</h2> block listing each <ElementName>-<name> selector and its default
  • Update the "Available CSS Child Selectors" table in AGENTS.md with one new row
  • Add a brief widget entry to docs/src/docs/asciidoc/widgets.adoc if the widget itself is new; if the widget already exists, mention only the new Element wrapper

Step 7 — Write Tests Using BufferAssertions

  • Create tamboui-toolkit/src/test/java/dev/tamboui/toolkit/element/<Name>ElementTest.java using JUnit 5
  • At minimum: render at a fixed Rect, assert the buffer contents with BufferAssertions, then assert that an explicit style overrides the default and (if you have a TCSS test fixture) that a CSS rule overrides the default but is overridden by an explicit setter
  • Test data is fixed — never randomize; see testing-standards.md from the user's coding-policy tile

Step 8 — Verify Locally

  • Run ./gradlew -q :tamboui-toolkit:test and then ./gradlew -q javadoc — both must pass
  • If the new element introduces a public type, also run ./gradlew -q assemble to confirm Java 8 source compatibility
  • Run scripts/check-display-width.sh (it emits JSON candidates) and triage any new entries against rules/char-width-for-display.md — false positives are expected, but a real violation in your render path means a glyph-width bug
  • If any check fails, stop and surface the failure — do not paper over it by widening exception handlers or reducing assertions
  • Finish here.

skills

wrap-widget-as-element

CHANGELOG.md

README.md

tile.json