OR Key
drop another .md file to compare - side-by-side diff against openui-lang

openui-lang

The format your assistant uses to build live screens for you.
personal 2 files

What it does for you

The format your assistant uses to build live screens for you.

What it produces

A recent result, so you can see the kind of work it returns.

loading…

How to get it

These run inside the Snappy workspace. Want this working in your business? I set skills like this up with you, in one focused week.

Work with me
For developers how this skill is built, graded, and how it runs

at a glance- the short version

eval modeauto-shape
stages1

what's inside - the parts that make up a skill 2/4 present

A skill is just a few plain-text files. Only the main one is required. The rest are optional, added as the work needs them. This is what the skill is made of; how it runs is just below.

The skill
state/skills/openui-lang/SKILL.md present
the skill itself, in plain text
The main file. It says what the skill is and lays out the steps in plain English.
Code
state/lib/openui-lang.ts not present
code the skill can run
Optional. Many skills are just words and need no code at all.
Scripts
state/bin/openui-lang/ not present
helper scripts
Optional. Added when a skill has a few commands to run.
Loader
state/skills/openui-lang/AGENTS.md present
what the AI loads on the fly
Loaded automatically the moment this skill is needed. Kept short on purpose.

how it's graded - what counts as a good run 3 criteria · 2 deterministic · 1 judge

Each row is one thing a good run has to get right. deterministic means a quick check decides, pass or fail. judge means the AI reads the result and rates it. Grading each piece on its own (instead of one overall score) shows exactly where a run fell short, so the fix is obvious.

name
kind
check
reference_completeness
deterministic
All critical sections present: Current Contract, What is OpenUI Lang, Statement Types, Language Primitives, Operators, @Builtins, Reactive State, Data Fetching, Worked Example, Anti-Patterns, Where it Lives.
example_correctness
deterministic
The worked example (Query + $variable + @Count + Card) parses correctly and demonstrates reactive data binding without hardcoded values.
agent_loader_effective
judge
When a future agent mentions 'Lang' or 'generative UI', they receive the same live-composition + artifact-persistence model from SKILL.md and AGENTS.md and do not reach for markers, per-shape .tsx files, or resources/ui.openui.

how it runs - the shared frame every skill uses 2/5 present

Every skill runs the same way. One part does the work, a separate part checks it, and a short loader hands the AI exactly what it needs for the job. Anything this skill doesn't use shows a one-line note saying why, on purpose, not by accident.

makes the work The worker
inferred
grep '"[A-Z][A-Za-z]+"' from a command
No worker named, so the first command in the skill is treated as the worker.
checks the work The reviewer
inferred
shape gate an automatic check
The check is an automatic pass or fail on the shape of the result, run separately from the work itself.
frame
learns Self-correction
not present

This skill doesn't fix its own gaps yet.

tidies up Background fixes
present
queued for rewrite runs in the background
Bigger fixes that can't be made on the spot get queued and rewritten in the background later.
remembers Run history
present
state/log/evals.ndjson auto-shape runs
Every run is written down here, so the next time this skill is used it already knows how the last runs went.
Critical rules the things this skill must not get wrong
  1. compose_inline / Lang for operational/admin/status dashboards, data views, mirror lists, schedules, and structured replies
  2. HTMLPreview (via compose_inline) for visual / brand-faithful / pixel-perfect / gallery / demo / media / website-like surfaces
  3. FollowUpBlock for next-step chips
  4. OpenArtifact when a concrete saved surface already exists

what it has learned - fixes written back in over time sample

When a run hits something this skill didn't handle, the fix gets written back into the skill so it doesn't happen again. FIXED means it was corrected on the spot. LOGGED means it's queued for a bigger rewrite. Either way, the skill gets a little better and never makes the same mistake twice.

  1. Loading feedback rows…

how the work flows- step by step

1 stage
grep
grep -oE '"[A-Z][A-Za-z]+"' \

SKILL.md- the skill, written out in plain English

openui-lang - First-Class Composition Contract

Purpose

OpenUI Lang is the assignment-based, line-oriented DSL used by Snappy OS's OpenUI renderer. The current product contract is larger than the syntax: structured UI is live-composed by the Snappy harness, rendered by snappy-os, and persisted as artifact lang_body so it can be reopened, refined, and versioned as a Live App.

The 2026-05-11 cutover retired the old file-backed surface model. The normal path is no longer "emit a [[TOOL:Lang]] marker" or "write resources/ui.openui." The normal path is:

user intent -> Snappy harness -> compose_inline / Lang -> OpenUI Lang body
            -> artifact store lang_body -> OpenArtifact / Live Apps

This skill exists because stale UI doctrine is expensive. If a future agent reintroduces per-shape .tsx files, server regexes, marker emission, or per-skill .openui dashboards as the default path, it is rebuilding retired machinery instead of using the platform.

When to Use This Skill

Use this skill when working on the OpenUI platform contract itself:

  • Any prompt mentioning openui-lang, OpenUI Lang, Lang,

compose_inline, artifact lang_body, renderer behavior, component definitions, prompt generation, or UI-contract audits

  • The prompt artifacts in state/config/openui-lang-prompt*.txt
  • The Snappy harness tools that compose, open, and refine UI:

compose_inline, OpenArtifact, QueryState, Bash, and the deferred component-catalog lookup path when available. Lang remains the internal renderer event name, not the primary model-facing composition tool.

  • state/config/openui-lang-prompt.txt (the auto-generated component

signature catalog injected at run time)

  • The Lang entry in web/src/dispatch-card.tsx DISPATCH_REGISTRY
  • web/src/genui-library.tsx (re-exports openuiChatLibrary as

genuiLibrary)

  • Artifact lang_body persistence and Live Apps reopening behavior
  • Adding a new generative-UI affordance - primitives-first, not

per-shape file, regex matcher, or retired .openui resource

Do not use this skill as the main executor for ordinary product asks like "build a Live App", "show a FreshBooks dashboard", "make a LinkedIn preview", "create an image gallery", or "make this pixel perfect." Those are concrete surface requests. The agent should ground the relevant data and call compose_inline or exact Lang so the user sees the UI. For reusable app capabilities, app-builder / openui-app owns the lifecycle; this skill only supplies syntax and platform constraints.

Rubric

criteria:
  - name: reference_completeness
    kind: deterministic
    check: "All critical sections present: Current Contract, What is OpenUI Lang, Statement Types, Language Primitives, Operators, @Builtins, Reactive State, Data Fetching, Worked Example, Anti-Patterns, Where it Lives."
  - name: example_correctness
    kind: deterministic
    check: "The worked example (Query + $variable + @Count + Card) parses correctly and demonstrates reactive data binding without hardcoded values."
  - name: agent_loader_effective
    kind: judge
    check: "When a future agent mentions 'Lang' or 'generative UI', they receive the same live-composition + artifact-persistence model from SKILL.md and AGENTS.md and do not reach for markers, per-shape .tsx files, or resources/ui.openui."

What is OpenUI Lang

Plain-English summary: a compact text DSL that says "this UI is built from these named pieces, which compose like this." Each line is one assignment of the form identifier = Expression. The root assignment is special - it's the entry point. Everything else is forward-resolvable: references work both up and down the file.

Why the LLM emits it instead of JSX or JSON:

  • Streaming-friendly. The parser validates line-by-line, so partial

output renders incrementally as tokens arrive. JSON's strict bracket pairing and JSX's nesting both stall progressive rendering.

  • Token-efficient. Documented as ~67% fewer tokens than the

equivalent JSON tree. On a Card-with-chart-and-table dispatch this is the difference between a 1.2s and a 4s first-paint.

  • Graceful invalid-output handling. A malformed sub-expression is

dropped; the rest of the tree still renders. JSON would fail entirely.

  • Positional-only. Card([children], "subtle") not

Card(items: [...], variant: "subtle"). Less syntax for the model to hallucinate; the prop order in defineComponent({props: z.object({...})}) IS the call order.

Conceptual model (four pieces work together):

  1. Library - Zod schemas + React renderers (in our codebase:

openuiChatLibrary from @openuidev/react-ui, re-exported as genuiLibrary in web/src/genui-library.tsx).

  1. Prompt Generator - converts the library into the system-prompt

text the LLM reads. Emitted to state/config/openui-lang-prompt.txt and loaded by state/bin/head-screen/server.ts at startup.

  1. Parser - validates streaming Lang text into typed elements.
  2. Renderer - <Renderer library={genuiLibrary}> from

@openuidev/react-lang; maps parsed elements to React components.

The separation lets the LLM focus on what to render while the framework handles how.


Statement Types

Three statement shapes total. Memorize this:

# 1. Component (the common case)
welcomeCard = Card([CardHeader("Welcome"), TextContent("Hello")])

# 2. State (reactive variable)
$days = 7

# 3. Data (Query / Mutation binding)
agents = Query("get_agents", {}, {agents: []}, 30)

Root rule: every program must define root = Card(...) (or any top-level component). That assignment is the entry point.

Forward references work. You can use a name before it's defined; the parser resolves all references after the full input is parsed. This is the "hoisting" rule from state/config/openui-lang-prompt.txt:148.

Reference-or-die rule. Every variable except root MUST be referenced by at least one other variable. Unreferenced variables are silently dropped and will NOT render. Always include defined variables in their parent's children/items array.


Language Primitives

The compositional building blocks. One-line each. Find the full list in state/config/openui-lang-prompt.txt (auto-generated from the library).

Layout

  • Card([children], variant?) - outer container. Variants: default, sunk, subtle, raised.
  • CardHeader(title?, subtitle?) - title row inside a Card.
  • Stack([children], direction?, gap?) - flex layout. Direction: row|column. Gap: s|m|l.
  • Col([children], gap?) / Row([children], gap?) - explicit vertical/horizontal stacks.
  • Separator(orientation?, decorative?) - divider.

Lists

  • ListBlock([ListItem...]) - bulleted/numbered runs.
  • ListItem(text, secondary?)
  • Steps([StepsItem...], current?) - ordered phase markers.
  • StepsItem(label, description?, status?) - status: complete|active|pending.
  • Accordion([AccordionItem...]) / AccordionItem(title, body)
  • Tabs([TabItem...]) / TabItem(label, [body])

Content

  • TextContent(text, size?) - sizes: small | default | large | small-heavy | large-heavy. Supports markdown.
  • MarkDownRenderer(textMarkdown, variant?) - full GFM markdown.
  • Callout(variant, title, description, visible?) - info|warning|error|success|neutral.
  • TextCallout(variant?, title?, description?) - lighter inline callout.
  • CodeBlock(language, codeString) - syntax-highlighted code.
  • Image(alt, src?) / ImageBlock(src, alt?) / ImageGallery([{src,alt,details}]) - fidelity caveat: ImageGallery is for operational/admin thumbnail lists (settings pages, saved-image pickers). For brand-faithful gallery surfaces (user-facing gallery generation), use HTMLPreview with custom HTML instead. See state/config/prompt-fragments/image-gallery.md.

Tables

  • Table([Col...]) - column-oriented (each Col holds its own data array).
  • Col(label, data, type?) - type: string|number|action.

Charts (positional series shape)

  • BarChart(labels[], series[], variant?, xLabel?, yLabel?) - grouped|stacked.
  • LineChart(labels[], series[], variant?, xLabel?, yLabel?) - linear|natural|step.
  • AreaChart(labels[], series[], variant?, xLabel?, yLabel?).
  • PieChart(labels[], series[]) / RadarChart(labels[], series[]).
  • RadialChart(labels[], series[]) / ScatterChart(labels[], series[]).
  • HorizontalBarChart(labels[], series[], variant?, xLabel?, yLabel?).
  • Series(category, values[]) - one data series.

Tags / Buttons / Inputs

  • Tag(label, variant?) / TagBlock([Tag...]).
  • Button(label, action?) / Buttons([Button...]) - action is an Action expression.
  • Input(label, $binding<string>) / TextArea(label, $binding<string>).
  • Select(label, options[], $binding) / RadioGroup / CheckBoxGroup / Slider / DatePicker.
  • Form([FormControl...]) / FormControl(label, [child]).

The LLM has the full ~80 primitive vocabulary at run time. Find every registered name with:

grep -oE '"[A-Z][A-Za-z]+"' \
  ~/projects/snappy-os-app/apps/snappy-os/web/node_modules/@openuidev/react-ui/dist/genui-lib/openuiChatLibrary.js \
  | sort -u

Or look up the d.ts at web/node_modules/@openuidev/react-ui/dist/genui-lib/openuiChatLibrary.d.ts.


Operators

Arithmetic: +, -, *, /, % Comparison: ==, !=, >, <, >=, <= Logical: &&, || Unary: !, - Ternary: cond ? a : b


@Builtins (closed allowlist)

Functions prefixed with @. Enforced by state/lint/builtin-whitelist.ts

  • invented operators silently fail to render. **Do not extend this list

in prose** - it must round-trip with the lint allowlist.

Aggregation

  • @Count(array) - number of elements.
  • @Sum(numbers[]), @Avg(numbers[]), @Min(numbers[]), @Max(numbers[]).

Array

  • @First(array), @Last(array) - pluck endpoints.
  • @Filter(array, field, op, value) - op is a comparison operator string.
  • @Sort(array, field, direction?) - direction: asc|desc.
  • @Each(array, varName, template) - iterate; bind each row to varName.

Math

  • @Round(number, decimals?), @Abs(number), @Floor(number), @Ceil(number).

Actions (mutation triggers; only inside Action expressions)

  • @Run(ref) - fire a Mutation by name.
  • @Set($var, value) / @Reset($var, $var2, ...) - write reactive state.
  • @ToAssistant(msg) - send a follow-up user message.
  • @OpenUrl(url) - navigate.

Reactive State and Bindings

Declare state with $name = default:

$days = 7
agents = Query("get_agents", {days: $days}, {agents: []}, 30)

Passing $days to a Slider or Input creates two-way binding - user input updates $days, Query re-fetches automatically. Args marked $binding<type> in primitive signatures accept a $variable reference.


Data Fetching

Query - runs on load, refetches when $variable args change:

data = Query("tool_name", {arg: value}, {default_shape: []}, 30)

Four positional args: tool name, arg object, default shape (rendered while loading), cache TTL in seconds.

Mutation - declared but only fires via @Run():

saveResult = Mutation("save_thread", {title: $title})

Trigger: Button("Save", Action([@Run(saveResult)])).

Member access (dot-pluck):

agents = Query("get_agents", {}, {agents: []}, 30)
names = agents.agents.name        # plucks .name from every row → array
count = @Count(agents.agents)

System-Prompt Patterns (canonical)

The OpenUI docs ship a getSystemPrompt(library, options) helper that produces a complete system prompt from a registered library. Our implementation does the equivalent at startup - state/config/openui-lang-prompt.txt is the auto-generated artifact.

Feature flags (set on the prompt generator):

FlagEnablesDefault
toolCallsQuery(), Mutation(), @Runtrue if tools provided
bindings$variables, @Set, @Resettrue if toolCalls enabled
editModeIncremental edit (patches only)false
inlineModeText + fenced code responsesfalse

Customizable additions: preamble (project context), additionalRules (domain-specific), toolExamples (worked Query/Mutation examples).

In Snappy OS, the prompt artifacts are generated once and then used by the Snappy harness during UI turns. The current doctrine is:

  • Default to native compose_inline for dashboards, galleries, previews,

boards, schedules, and structured replies.

  • Use Lang directly only when the exact authored OpenUI body matters.
  • Persist reusable generated UI as artifact lang_body.
  • Reopen or refine durable UI through OpenArtifact / the active-surface

loop, not by reading skill-owned .openui files.

  • Ground counts, queues, schedules, artifacts, and metrics with real tool

results (QueryState, Query, or domain tools) before composing.


Variants - Where the Library Choice Matters

Different ecosystems publish different openui*Library exports. Pick by target surface:

VariantWhen to lean on itLibrary export
snappy-osChat and Live Apps surface using OpenUI Lang for state/control and typed React renderers for semantic DOM / HTML targetsopenuiChatLibrary plus narrow Snappy component groups
vercel-ai-chatStreaming chat with multi-step tool calling via @ai-sdk/openai and useChat - transport-agnostic, <Renderer /> stays unchangedopenuiChatLibrary
dashboardKPI cards, tables, charts driven by MCP-style tool registry; LLM composes UIs that themselves call backend toolsopenuiChatLibrary + tool registry
react-emailStatic, non-interactive content with inline-style requirement (email clients strip <style>) - 44 components in emailLibraryemailLibrary (from @openuidev/react-email)
react-nativeNative mobile via Expo - twin libraries (real renderers on device, null renderers for backend prompt generation, since CLI can't import RN in Node)openuiNativeLibrary

snappy-os uses the snappy-os variant: openuiChatLibrary from @openuidev/react-ui plus a narrow set of Snappy domain components, re-exported as genuiLibrary / canvasLibrary. Do not bulk-spread openuiLibrary or register Shadcn* generated adapters into the model-visible catalog. The harness calls compose_inline / Lang, the client renders the resulting Lang with <Renderer library={genuiLibrary}>, and durable surfaces persist as artifact lang_body.

For dashboard-like surfaces, still use KPI tiles, Query-driven tables, and charts. The lifecycle changed: dashboards are composed and saved as Live Apps / artifacts, not authored as per-skill resources/ui.openui files.


Worked Example - Query + $variable + @Count + Card

This is the canonical pattern the LLM should produce when asked "how many agents have run?":

$days = 7
agents = Query("get_agents", {days: $days}, {agents: []}, 30)
filtered = @Filter(agents.agents, "lastRun", "!=", null)
$total = "" + @Count(filtered)

root = Card([
  CardHeader("Agents", "Last " + $days + " days"),
  Stack([
    Card([TextContent("Active", "small"), TextContent($total, "large-heavy")], "subtle"),
    Slider("Window (days)", $days, 1, 30, 1)
  ], "row", "m")
])

What this demonstrates:

  • Query for live data (NEVER literal counts)
  • $variable for derived display values
  • @Filter + @Count to aggregate without server round-trip
  • String coercion for KPI text: "" + @Count(...)
  • Reactive: dragging the Slider updates $days → Query refetches → Count

updates


Anti-Patterns

These hard-block. Each has a lint or has cost a session of debugging.

  • No raw HTML / JSX strings. Lang is the only path. The previous

KNOWN_COMPONENTS regex catalog in server.ts is being deprecated precisely because it bypassed Lang.

  • No name=value or name: value arg syntax. Args are POSITIONAL

ONLY. Colon syntax silently breaks the parser. Burned 2026-04-29T07:42:54Z.

  • No invented @Operators. Whitelist enforced by

state/lint/builtin-whitelist.ts. Adding @Mean to prose without also adding it to the lint allowlist = silent render failure.

  • No string concatenation hacks for literal numbers. When the user

asks for a count, USE Query() + @Count(). Hardcoded "47 dispatches" is an LLM fabrication and the DATA-DRIVEN COMPOSITION rule explicitly forbids it.

  • No defining variables you don't reference. Unreferenced ≠

rendered. Always thread defined variables back through the parent's children array.

  • No per-shape .tsx file as the default path for new UI. The

legacy recipe (per-shape file + DISPATCH_REGISTRY entry + server.ts matcher + welcome chip) is dead. Default = describe what you want in a prompt, let the LLM compose primitives.

  • No [[TOOL:Lang]] marker emission as the current contract. That was

the retired inline transport. Use native harness tools (compose_inline, Lang, OpenArtifact) so the run ledger, active surface, and artifact persistence stay coherent.

  • No per-skill .openui dashboards as the normal persistence layer.

state/skills/<slug>/resources/ui.openui and resources/*.openui are retired for new reusable surfaces. Persist reusable UI as artifact lang_body and reopen it through OpenArtifact / Live Apps.

  • No getSystemPrompt() direct calls in a per-request hot path.

Generated once at startup and cached in state/config/openui-lang-prompt.txt.


Where it Lives in Snappy OS

These layers own the current Lang surface end-to-end:

  1. Prompt artifacts - state/config/openui-lang-prompt.txt and

state/config/openui-lang-prompt.channel-compose.txt

  • Generated by apps/snappy-os/web/scripts/build-prompt.mts
  • Describe component signatures, component groups, and usage notes
  • Loaded by the Snappy harness / head-screen dispatch path for UI turns
  1. Harness tools - compose_inline, OpenArtifact, QueryState,

Bash, plus deferred component-catalog lookup when available.

  • Own the runtime transition from natural intent to structured surface
  • Preserve active-surface context for follow-ups like "better" and "again"
  • Persist reusable output as artifact lang_body
  1. Client renderer - web/src/dispatch-card.tsx,

web/src/right-panel.tsx, web/src/components/artifacts-view.tsx

  • langDispatchEntry() wraps <Renderer library={genuiLibrary}>
  • Right panel and Live Apps render saved artifact lang_body
  • Five "promoted canned shapes" (DispatchCard, AgentDetail,

ProgressList, PhaseDisclosure, HTMLPreview) also route through Lang

  • Threaded shape memory + patch merging in lang-history.ts
  1. Library - web/src/genui-library.tsx
  • genuiLibrary = openuiChatLibrary (from @openuidev/react-ui)
  • 5 promoted shapes registered alongside via defineComponent
  • Three-line re-export pattern for promoted-shape files (TS

isolatedModules constraint): export { View, Component }, export type { Props }, import { Component } (local binding for the components array)


Resource Files

This skill is prose-only; companion files live in this folder:

  • AGENTS.md - the per-turn loader. Critical Rules + commands.
  • prompt-fragment.md - compact reminder for the current live-composition

contract.

  • resources/v05-quickref.md - syntax and component reference. Some older

resource-file audit notes inside it are historical; the lifecycle contract in AGENTS.md and this SKILL.md wins.


  • crayon-sdk - the broader Crayon/C1/OpenUI ecosystem

reference. OpenUI Lang is the language layer; crayon-sdk covers the outer chat shell, SSE wire format, thread persistence.

  • openui-mcp - Context7 MCP for live OpenUI docs lookup

during work.

prompt and runs the DISPATCH_REGISTRY Lang entry.

authoring guide. Read for historical context only; do NOT extend.


Skill Status: ACTIVE Eval: auto-shape (frontmatter + body presence; failure mode is silent: agent doesn't load Lang model and writes per-shape .tsx files) Reference Implementation: snappy-os (web/src/dispatch-card.tsx Lang entry + state/config/openui-lang-prompt.txt)

AGENTS.md- what the AI loads when this skill comes up

openui-lang - loader

Per-turn reference for the current OpenUI contract. Full skill: state/skills/openui-lang/SKILL.md.

This loader is the source of truth for how UI is generated now, after the 2026-05-11 live-composition cutover.

Recurring traps (read before composing brand-row surfaces)

  • ListBlock(@Each(rows.rows, "r", BrandRow(...))) renders blank dots. ListBlock.items is z.array(ListItem.ref); any non-ListItem child gets coerced to {title, subtitle} and the brand row's props are silently dropped. Use Stack(@Each(...)) instead. The snappy-os server-side rewriter at state/lib/rewrite-mirror-rows.ts auto-fixes this on every compose_inline emission - you do not need to fix it in prose, and prompt-level swaps get reverted by autoformatters anyway. Just emit; the rewriter handles it.
  • Stack IS available in the Snappy OS genuiLibrary even though the upstream openuiChatLibrary omits it. Snappy OS registers a narrow local Stack renderer in apps/snappy-os/web/src/genui-library.tsx; do not reintroduce the full openuiLibrary bulk import just to recover Stack.
  • Brand-row click-into-detail. Query-bound rows (GmailRow, etc.) carry an id as the last positional arg and dispatch snappy:open-mirror-detail on click - App.tsx handles the rest. See ui-components skill § "Brand-row click-into-detail" for the contract. Do NOT wire row clicks through chat-inject-push.
  • Static connector binding tables are retired. Connector rows come from Printing Press/local mirror/provider handles discovered at runtime. state/lint/mirror-recipe-coverage.ts fails if static mirror providers or baked binding tables are registered. Adding connector data means exposing a registered Query()/Mutation() provider handle, not adding a baked table.

Activation Boundary

This is a reference loader, not the executor for ordinary product asks.

  • If the user asks to **build, show, create, render, redesign, theme, or

refine** a concrete dashboard, preview, gallery, workbench, or Live App, do not answer with OpenUI doctrine. Ground the domain state and call compose_inline (or exact Lang) so the user sees the surface.

  • If the user asks for a reusable app capability, stable app identity, saved

Live App, or reopen/refine workflow, treat app-builder / openui-app as the owning skill and use this loader only for syntax constraints.

  • Use this loader as the main answer only for platform questions: OpenUI Lang

syntax, renderer behavior, component-library authoring, prompt generation, artifact persistence rules, or audits of the UI contract itself.

Critical Rules

  1. Concrete UI asks must render. When the user asks for a visual result,

the deliverable is a visible surface. Do not stream this reference, explain the contract, or list guardrails unless the user explicitly asks for documentation. Compose first; explain only if needed after.

  1. UI is live-composed. The primary path is the native harness tool

compose_inline. It produces OpenUI Lang at runtime from the current intent plus grounded tool results.

  1. Native tool calls only. Never emit [[TOOL:Lang]]...[[/TOOL]]

markers. Never rely on inline marker parsing. Use the current callable tools: compose_inline for UI, OpenArtifact for saved artifacts, and QueryState or Bash for grounding. Lang is an internal renderer event, not the model-facing composition tool.

  1. No per-skill UI files. Do not teach or expect

state/skills/<slug>/resources/ui.openui, resources/*.openui, or skill-owned defineComponent exports. The constitution retired that model. Reusable UI now lives as persisted artifacts with lang_body.

  1. Artifact store is the persistence layer. A composed surface can be

saved, reopened, and refined because its lang_body is persisted in the artifact store. Reopen durable UI via OpenArtifact, not by reading a skill-owned .openui file.

  1. Use Lang only when you need exact authored Lang. Default to

compose_inline for structured UI. Reach for Lang directly when you need to hand-author or patch the exact OpenUI body yourself.

  1. Positional, root-first Lang. If you emit Lang directly: positional

args only, root = Card(...) or root = Stack(...) first, forward refs allowed, no JSX, no name: value syntax.

  1. Ground data before you compose. Use QueryState, Query, or other

real tool results for counts, queue state, artifacts, schedules, and metrics. Do not hardcode numbers or fabricate rows.

  1. Use ListComponents before non-obvious components. The component

catalog is lazy by design. For anything beyond the always-on essentials, inspect the actual signature first instead of guessing.

  1. Render-location claims must match reality. Do not say a surface is

"open on the right" unless the runtime actually mounted an active surface or reopened a saved artifact. If the result is inline, describe it as inline. If the runtime decides the location, do not overclaim.

  1. HTMLPreview is the high-fidelity renderer for new/rich/visual/brand-faithful/pixel-perfect/demo/gallery/media/website-like surfaces. Use it when the surface is a custom designed artifact, not a generic-component dashboard. For visual image galleries, wrap inside compose_inline with Lang state (url_1..N variables) and root = HTMLPreview(html) so the artifact persists, follow-up prompts mutate the SAME surface, and Save Gallery / Publish flows attach. Do NOT use HTMLPreview for operational/admin/status surfaces (System pages, Loop Health, Schedules, Settings) - those use primitives.
  • compose_inline / Lang for operational/admin/status dashboards, data views, mirror lists, schedules, and structured replies
  • HTMLPreview (via compose_inline) for visual / brand-faithful / pixel-perfect / gallery / demo / media / website-like surfaces
  • FollowUpBlock for next-step chips
  • OpenArtifact when a concrete saved surface already exists
  1. Refinement means improve the existing surface. For prompts like

"better", "again", "polish", "make it world class", preserve the useful data and improve composition, density, preview fidelity, or placement. Do not reroll an unrelated surface.

Routing

  • Use openui-inline-ui when the user needs a one-shot structured reply in

the current conversation.

  • Use openui-app when the user wants a reusable live app, saved surface, or

durable cockpit view they can reopen and refine later.

  • Use app-builder when the user explicitly wants to create a new reusable

app capability, not just see one answer.

All three still obey the same live-composition + artifact-persistence model. The difference is lifecycle, not transport.

Commands

This loader is reference-only. The current runtime contract is expressed through harness tools, not loader-local scripts.

purposetool / surface
compose structured UIcompose_inline
hand-author exact LangLang
render HTML pageHTMLPreview
add next-step chipsFollowUpBlock
inspect grounded stateQueryState
inspect component signaturesListComponents
reopen saved artifact / live appOpenArtifact

Reference docs:

  • Full skill: state/skills/openui-lang/SKILL.md
  • Constitution: state/CONSTITUTION.md
  • Prompt artifact: state/config/openui-lang-prompt.txt
  • Renderer: web/src/dispatch-card.tsx
  • Libraries: web/src/genui-library.tsx, web/src/right-panel.tsx

Self-Test

  • [ ] I know the default UI path is compose_inline, not marker emission.
  • [ ] I will not reference resources/ui.openui or per-skill .openui files.
  • [ ] I know saved UI persists as artifact lang_body.
  • [ ] I will use native tool calls, not [[TOOL:Lang]].
  • [ ] If I emit Lang directly, I will make it positional and root-first.
  • [ ] I will ground numbers and queue state through tools before composing UI.
  • [ ] I will check component signatures with ListComponents before guessing.
  • [ ] I will not claim a surface is on the right unless the runtime actually mounted it there.

Found a gap? Edit this file. <!-- footer-injection-point -->

From snappy-os/CLAUDE.md: Component primitives (the 12 to reach for first)

The 12 most-used OpenUI primitives the LLM will reach for first:

PrimitiveWhat it's for
Card / CardHeaderOuter container + title row for any structured response
Stack / ColVertical and horizontal flex layouts (replaces 90% of the old shapes)
ListBlock / ListItemBulleted or numbered runs of items
Tabs / TabItemTabbed content panels
Accordion / AccordionItemExpand/collapse rows
Steps / StepsItemOrdered phase markers (status timelines)
BarChart / LineChart / AreaChart / PieChartCharts with the OpenUI data shape
Tag / TagBlockInline labels and tag groups
Button / ButtonsInline action buttons (single or grouped)
Callout / TextCalloutInline highlight or warning blocks
TableTabular data
MarkDownRendererLong-form prose with markdown features

Find the rest:

grep -oE '"[A-Z][A-Za-z]+"' web/node_modules/@openuidev/react-ui/dist/genui-lib/openuiChatLibrary.js | sort -u

Or look up types and signatures in web/node_modules/@openuidev/react-ui/dist/genui-lib/openuiChatLibrary.d.ts and the per-component .d.ts files under web/node_modules/@openuidev/react-ui/dist/components/.

Provenance: moved from ~/projects/snappy-os-app/apps/snappy-os/CLAUDE.md 2026-05-14.

api.ts- the code it can call

⚠ no api.ts - this skill has no typed action surface

scripts- helper scripts it can run

prose-only skill - 9 inline code blocks live in SKILL.md above (no state/bin/ sidecar yet).

how we check it- the checks, plus the last 10 runs

rubric auto-shape no rubric declared
recent no runs actor/auditor: unverifiable
deps none declared

no recent runs logged - the eval contract is declared but nothing has been graded yet