PizzaPocket

ui-craft

Building or editing any UI surface in Eleblorb (Hud, DialogUI, ShopUI, InventoryUI, PauseMenu, or a new one) through the ui_theme.gd/ui_kit.gd design system. Use before shipping a first pass on new UI, and whenever text is wrapping unexpectedly, a colored element reads illegible, a layout looks unbalanced/stretched, or a panel resizes when it shouldn't.

PizzaPocket 0 Updated 2w ago
GitHub

Install

npx skillscat add pizzapocket/eleblorb/ui-craft

Install via the SkillsCat registry.

SKILL.md

Eleblorb UI craft: catching first-pass mistakes before they ship

Every UI surface in this project builds through scripts/ui_theme.gd (design tokens) and scripts/ui_kit.gd (builder functions) — that system is already well-documented (see ui_theme.gd's own "Design language" section, which is the authoritative rule list, not this file). This skill isn't a restatement of those rules; it's the specific, recurring set of mistakes that have made it into a first pass anyway despite the rules existing, so they can be checked for deliberately instead of caught by the user after the fact. If a first pass at a UI surface has needed a correction round, the fix belongs in this list once, generalized, so the next surface doesn't repeat it.

Checklist before shipping any new or changed UI surface

Run through this before considering a UI change done — every item below is a mistake that has actually shipped once already:

  • Every Label sitting in a flexible row/column has explicit wrap behavior chosen, not left at the default. caption_label()/body_label() word-wrap by default, and word-wrap has no minimum-width floor — nothing stops the layout from squeezing it down to rendering one character per line the instant its container isn't generously sized. This exact bug has shipped three separate times (a coin count in a header, "Press I to close", and a Blorbs-tab "Type: Normal" field). Use inline_caption() (no-wrap, shrink-to-content) for anything short sitting next to other controls; reserve wrapping labels for text of genuinely unpredictable length inside a block that's guaranteed wide.
  • Any background color that isn't a fixed palette constant gets its text color derived, not assumed. A tint passed in dynamically (an element color, a per-instance accent) can land anywhere in the lightness range — pairing it with a hardcoded text color works until a light tint shows up (this shipped once: a near-white "Normal" blorb tag with light text on top of it, illegible). Compute contrast from the actual color at build time (see UIKit._relative_luminance()), don't assume the palette's usual dark-background case.
  • A text/label column defaults to shrink-to-content (SIZE_SHRINK_*), not SIZE_EXPAND_FILL. Giving a text block EXPAND_FILL in a row that also has other elements stretches it across all leftover width, stranding its actual (short) content on one side with a large dead gap before whatever comes next — read as "bizarre and unbalanced" when it shipped. If something in the row needs to visually anchor to the far edge, add a dedicated empty spacer Control with SIZE_EXPAND_FILL between them to absorb the leftover space — don't make content stretch to create positioning.
  • A layout constant that depends on the panel's own size (column count, row count) is measured, not guessed. A hardcoded GridContainer.columns tuned by eye at one panel size silently stops making sense the moment the panel's real size changes (this shipped as a fixed 3-4 columns leaving most of a much-wider-than-assumed fixed panel empty). If the container's size is knowable at runtime, connect to its resized signal and compute the count from the real measured width instead of a constant.
  • Any panel with more than one internal view (tabs, states) has one fixed outer size. Sizing the panel to shrink-wrap its current content (UIKit.anchor_to_edge()) is right for a single-purpose readout, but visibly resizes/jumps the whole panel every time the internal view changes — wrong for anything with tabs. Use the fixed viewport-relative sizing pattern (set_anchors_preset(PRESET_FULL_RECT) + equal margins, matching ShopUI/InventoryUI) and put each view's own content in a ScrollContainer that fills the remaining fixed space, so growth scrolls in place instead of resizing the panel.
  • A new decorative treatment (a border, a frame, a texture) is checked against the design language before adding it, not after. An unrequested wood-bordered "portrait frame" shipped once, directly contradicting the already-documented rule that passive elements are borderless (design language item 1 in ui_theme.gd). When a new element needs visual weight, reach for an existing stylebox/pattern first (slot_stylebox(), chip_stylebox(), a tint at reduced alpha) rather than inventing a new decorative look — and if a genuinely new pattern really is needed, it goes into UIKit/UITheme with a design-language entry, per that file's own instructions, not inline in the calling script.
  • Color is used to encode a distinct signal, not to decorate something already encoded another way. A per-stat rainbow (STR red, DEF gold, HP green, MP blue) shipped once and read as noise, because the bars' own length already carried the actual comparison — the color added nothing. Before adding color-coding, check whether the thing being colored already communicates the same distinction through position, size, or grouping; if so, one uniform accent color reads cleaner than a rainbow that isn't doing real work.
  • A first-pass review actually looks at the real data the surface will show, not just the abstract layout. Both the illegible-tag and the too-narrow-panel mistakes above trace back to reviewing the structure of a change without checking it against the specific values (a near-white default color; a much-wider-than-assumed real panel size) that would have surfaced the bug immediately.

Where the actual rules live

ui_theme.gd's own "Design language" section (currently 11 numbered items) is the source of truth for conventions — borderless passive elements, superellipse shapes everywhere, drawn icons instead of color swatches, the tab-switch pattern, the fixed-size-modal pattern, meters vs. badges for comparable values, and so on. This skill doesn't duplicate that list; it's the failure-mode checklist for enforcing it in practice. If a new mistake makes it into a shipped first pass, add it here as a checklist item (mistake → the specific fix pattern), the same way each item above was added — not as a vague "be more careful," but as a concrete, checkable thing to look for next time.

Related skills

  • figure-rig — the player/NPC rig's own axis-and-rotation-sign discipline; a different domain (3D transform math, not 2D layout), but the same underlying practice of recording a confirmed fix once instead of re-guessing it.
  • object-photography — rendering a 3D object to a texture for a UI surface to display (portraits, icons); read this one when the content of a UI element is a live-rendered capture rather than a drawn icon or plain text.

Categories