No work step here. This is probably a skill that reads or coordinates, not one that produces something.
.md file to compare - side-by-side diff against shape-builder
shape-builder
What it does for you
This skill does one job for you, the same careful way every time.
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.
For developers how this skill is built, graded, and how it runs
at a glance- the short version
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.
state/skills/shape-builder/SKILL.md
present
state/lib/shape-builder.ts
not present
state/bin/shape-builder/
not present
state/skills/shape-builder/AGENTS.md
present
how it runs - the shared frame every skill uses 3/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.
state/log/evals.ndjson - genui-library.tsx: Insert the 3-line re-export stub AFTER the last existing import { XyzComponent } from "./genui/xyz"; line, BEFORE const genuiLibrary = .... Never insert inside the genuiLibrary object literal (between root: and components:).
- dispatch-card.tsx: Find the final }; that closes the DISPATCH_REGISTRY object (near end of file). Insert the new entry immediately before it. Never insert inside another shape's parse or render function — that nests your entry inside a try-block and breaks both shapes silently.
- server.ts: Both sub-steps are mandatory — (a) add the name string to KNOWN_COMPONENTS Set AND (b) add the const xyzRegex + if (xyzRegex.test(intent) && !llmEmittedNames.has("XyzName")) block in the intent-matching section. Skipping either sub-step causes silent null-render.
- TypeScript gate: Run cd ~/projects/snappy-chat/web && npx tsc --noEmit 2>&1 | head -20 BEFORE committing. Fix all errors first.
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.
- Loading feedback rows…
SKILL.md- the skill, written out in plain English
shape-builder
Rare-path builder for a new custom defineComponent shape in snappy-chat. The default path is OpenUI Lang primitives plus skill-owned state/skills/<slug>/resources/*.openui surfaces maintained by dashboard-builder. Use this skill only when the needed experience cannot be composed from the existing Lang library, CanvasFrame/CanvasPanel, Query/Mutation, or a saved resource.
If the request is for a schedule, preview, inspector, dashboard, report, checklist, queue, artifact, or channel-faithful card, create or maintain a skill-owned .openui resource instead. A custom .tsx shape is justified for platform-faithful previews or interaction primitives that many resources will reuse.
When to invoke
- User asks for a visual that cannot be composed from OpenUI Lang primitives or a saved
resources/*.openuisurface - User describes a concept ("I want a...", "show me a...", "can you make...")
- The concept is visual or displayable and would benefit from a rich card versus plain text
- A reusable React component with a fixed schema is needed across multiple resources
Steps
- Parse the request -- extract: shape name (PascalCase, e.g.
BrainCharacter), file name (kebab-case, e.g.brain-character), description (what it shows and when to use it), and the visual concept (what should render).
- Check for existing shape -- grep DISPATCH_REGISTRY to confirm the name is not already registered. Command:
grep -n "<Name>" ~/projects/snappy-chat/web/src/dispatch-card.tsx. If it exists, do not recreate it -- emit the existing shape instead.
- Read the pattern -- read
~/projects/snappy-chat/web/src/genui/weather-card.tsxas a clean reference for structure, imports, and naming conventions. Note the import pattern:import { type JSX } from "react",import { defineComponent } from "@openuidev/react-lang",import { z } from "zod/v4".
- Generate the shape file -- write
~/projects/snappy-chat/web/src/genui/<file-name>.tsxwith:
- File header comment block (shape name, what it does, what it exports).
export interface <Name>Args { ... }-- props that make sense for the concept. Keep it to 4-8 props, all optional except one anchor prop.export function <Name>View(props: <Name>Args): JSX.Element { ... }-- React component. Styling rules: onlyvar(--text),var(--surface),var(--surface-elevated),var(--accent),var(--accent-soft),var(--border),var(--text-secondary),var(--radius-sm),var(--font-mono),var(--font-display)CSS variables. No inline colors, no hex values, no external libraries beyond what is already in the React + OpenUI bundle.export const <Name>Component = defineComponent({ name: "<Name>", description: "...", props: z.object({...}), component: ({props}) => <...> })-- the OpenUI Lang wrapper.
- Add re-export stub to genui-library.tsx -- find the last stub block (before
export const genuiLibrary) and append THREE lines (not four):
export { <Name>View, <Name>Component } from "./genui/<file-name>";
export type { <Name>Args } from "./genui/<file-name>";
import { <Name>Component } from "./genui/<file-name>";
INSERTION ANCHOR (critical): Insert these three lines immediately after the last existing import { XyzComponent } from "./genui/xyz"; line - the end of the re-export stub block. Do NOT insert inside the const genuiLibrary = { ... } object literal (between root: and components:), and do NOT insert inside any JSX block or function body. The stubs MUST appear before the const genuiLibrary = definition. If you are unsure, search for const genuiLibrary and confirm your insertion point is above that line. Also add <Name>Component to the components[] array at the end of genuiLibrary.
- Register in DISPATCH_REGISTRY -- in
~/projects/snappy-chat/web/src/dispatch-card.tsx, add a new entry just before the closing};of DISPATCH_REGISTRY:
<Name>: {
parse: (raw: string) => { try { const o = JSON.parse(raw); return o && typeof o === "object" ? o : null; } catch { return null; } },
render: (id, a: <Name>Args) => (
<<Name>View key={id} {...(a as <Name>Args)} />
),
},
INSERTION ANCHOR (critical): The DISPATCH_REGISTRY object closes with a }; near the end of the file. Insert the new entry immediately before that final };. Do NOT insert inside another shape's parse or render function - that puts your entry inside a different shape's try-block and breaks both shapes silently. To find the correct location: search for the last }, followed shortly by }; at the file's end. Your new entry goes between that last }, and the };. Also add the import for <Name>View and <Name>Args at the top of the file near the other genui imports.
- Add server matcher -- MANDATORY. This leg is required. Skipping it causes silent null-render with no warning. In
~/projects/snappy-os/state/bin/head-screen/server.ts:
a. Add "<Name>" as a string to the KNOWN_COMPONENTS Set. Find the Set definition (it looks like new Set(["WeatherCard", "DonutChart", ...])) and add your name to the list before the closing ]) or new Set([...]) bracket. b. Add an intent regex constant and emitTriple call in the intent-matching section (just before writeAgUI({ type: "RUN_FINISHED"...}):
// <Name> -- <what it shows>. Added <date>.
const <name>Regex = /<regex matching natural language trigger>/i;
if (<name>Regex.test(intent) && !llmEmittedNames.has("<Name>")) {
emitTriple("<Name>", { <minimal valid props> });
llmEmittedNames.add("<Name>");
}
Verify both (a) and (b) are present before moving to the next step.
- TypeScript gate (mandatory before commit) -- Run:
cd ~/projects/snappy-chat/web && npx tsc --noEmit 2>&1 | head -20. If any errors appear, fix them before committing. Do NOT commit with outstanding type errors. Common causes: wrong import path, missing type export from genui-library.tsx, or mis-typed props in DISPATCH_REGISTRY render function.
- Build and deploy --
cd ~/projects/snappy-chat && bash scripts/build-app.sh --install 2>&1 | tail -10. Build must succeed (exit 0) before claiming the shape is live.
- Restart server -- The server.ts process does NOT hot-reload. Kill the old PID and relaunch:
pkill -f "head-screen/server" 2>/dev/null; sleep 1
cd ~/projects/snappy-os && bash state/bin/head-screen/launch.sh &
- Commit -- stage only the four changed files and commit:
cd ~/projects/snappy-chat
git add web/src/genui/<file-name>.tsx web/src/genui-library.tsx web/src/dispatch-card.tsx
git commit -m "shape-builder: generated <Name> shape from request"
cd ~/projects/snappy-os
git add state/bin/head-screen/server.ts
git commit -m "shape-builder: register <Name> in server.ts KNOWN_COMPONENTS + matcher"
- Emit the shape -- emit
[[TOOL:<Name>]]{<minimal valid props as JSON>}[[/TOOL]]so the user sees it immediately in the chat surface.
- Eval -- append one eval row to
state/log/evals.ndjsonwithscore: 1.0if the shape rendered and the build succeeded,score: 0.0if the build failed. Useskill: "shape-builder"as the skill field.
AGENTS.md- what the AI loads when this skill comes up
shape-builder - loader
Per-turn rules. Full reference: state/skills/shape-builder/SKILL.md. Wires a generative-UI shape from description to committed, rendered card.
Critical Rules
- Check DISPATCH_REGISTRY first - grep
~/projects/snappy-chat/web/src/dispatch-card.tsxfor the name. If it exists, emit the existing shape instead of recreating (real incident 2026-04-30: BrainCharacter was already built; verified and emitted).
- Use weather-card.tsx as reference -
~/projects/snappy-chat/web/src/genui/weather-card.tsxis the canonical clean template. Don't use random other shapes.
- CSS variables only - approved:
var(--text)|var(--text-secondary)|var(--surface)|var(--surface-elevated)|var(--accent)|var(--accent-soft)|var(--border)|var(--radius-sm)|var(--font-mono)|var(--font-display). No hex, rgb, or named colors (DESIGN.md + impeccable law). Real incident 2026-04-27: shape used hex #ABCDEF, regressed until replaced with var.
- Pi mis-wires wiring legs - explicit anchors required (real incident 2026-04-30: HabitTracker):
- genui-library.tsx: Insert the 3-line re-export stub AFTER the last existing
import { XyzComponent } from "./genui/xyz";line, BEFOREconst genuiLibrary = .... Never insert inside thegenuiLibraryobject literal (betweenroot:andcomponents:). - dispatch-card.tsx: Find the final
};that closes the DISPATCH_REGISTRY object (near end of file). Insert the new entry immediately before it. Never insert inside another shape'sparseorrenderfunction - that nests your entry inside a try-block and breaks both shapes silently. - server.ts: Both sub-steps are mandatory - (a) add the name string to KNOWN_COMPONENTS Set AND (b) add the
const xyzRegex+if (xyzRegex.test(intent) && !llmEmittedNames.has("XyzName"))block in the intent-matching section. Skipping either sub-step causes silent null-render. - TypeScript gate: Run
cd ~/projects/snappy-chat/web && npx tsc --noEmit 2>&1 | head -20BEFORE committing. Fix all errors first.
- Three re-export lines only - genui-library.tsx stubs MUST be exactly:
export { View, Component }+export type { Args }+import { Component }(forcomponents[]binding). Bareexport { X } from "..."doesn't satisfyisolatedModules(real incident 2026-04-28: four-line stub broke array binding).
- All three legs land or silent null-render - (a) tsx file; (b) DISPATCH_REGISTRY in dispatch-card.tsx; (c) KNOWN_COMPONENTS in server.ts. Missing any one = no warning, shape drops. Verify all three. (Real incident 2026-04-28: forgot (c), silent drop.)
- server.ts doesn't hot-reload - after editing, kill + relaunch:
pkill -f "head-screen/server" 2>/dev/null; sleep 1; cd ~/projects/snappy-os && bash state/bin/head-screen/launch.sh &. Old process continues running otherwise (real incident 2026-04-29).
- Build must exit 0 -
cd ~/projects/snappy-chat && bash scripts/build-app.sh --install 2>&1 | tail -10. Type errors block ship. Fix before committing.
- Explicit pathspecs, two repos - never
git add -A. Stage only: snappy-chat (web/src/genui/<file>.tsx web/src/genui-library.tsx web/src/dispatch-card.tsx), snappy-os (state/bin/head-screen/server.ts). Separate commits.
- Name uniqueness load-bearing - PascalCase MUST NOT collide with DISPATCH_REGISTRY keys. File name is kebab-case equivalent (BrainCharacter → brain-character.tsx).
- Eval row mandatory - append to
state/log/evals.ndjson:skill: "shape-builder",score: 1.0(success) or0.0(build failed). No exceptions.
Commands
| ui dashboard | state/skills/shape-builder/resources/ui.openui | | check existing | grep -n "<Name>" ~/projects/snappy-chat/web/src/dispatch-card.tsx | | write tsx | # ~/projects/snappy-chat/web/src/genui/<file>.tsx with ShapeView, ShapeComponent, defineComponent | | re-export stub | edit ~/projects/snappy-chat/web/src/genui-library.tsx: 3 lines + add to components[] | | registry entry | edit ~/projects/snappy-chat/web/src/dispatch-card.tsx: import + DISPATCH_REGISTRY entry | | server matcher | edit ~/projects/snappy-os/state/bin/head-screen/server.ts: add to KNOWN_COMPONENTS + regex + emitTriple | | tsc check | cd ~/projects/snappy-chat && npx tsc --noEmit 2>&1 \| head -20 | | build + deploy | cd ~/projects/snappy-chat && bash scripts/build-app.sh --install 2>&1 \| tail -10 | | restart server | pkill -f "head-screen/server" 2>/dev/null; sleep 1; cd ~/projects/snappy-os && bash state/bin/head-screen/launch.sh & | | commit snappy-chat | git add web/src/genui/<file>.tsx web/src/genui-library.tsx web/src/dispatch-card.tsx && git commit -m "shape-builder: generated <Name>" | | commit snappy-os | git add state/bin/head-screen/server.ts && git commit -m "shape-builder: register <Name>" | | emit inline | [[TOOL:<Name>]]{<minimal JSON props>}[[/TOOL]] | | eval row | append state/log/evals.ndjson: skill="shape-builder" score=1.0\|0.0 actor_session_id=UUID |
Self-Test
An agent reading this should correctly:
- [ ] Check DISPATCH_REGISTRY before generating anything
- [ ] Use weather-card.tsx as the reference, not another shape
- [ ] Write only CSS variables (no hex/rgb/named colors)
- [ ] Insert the genui-library.tsx stub BEFORE
const genuiLibrary, not inside the object literal - [ ] Insert the DISPATCH_REGISTRY entry before the final
};, not inside another shape's function - [ ] Add BOTH server.ts sub-steps: KNOWN_COMPONENTS string AND intent regex + emitTriple block
- [ ] Run
npx tsc --noEmitand fix all errors before committing - [ ] Add exactly THREE re-export lines to genui-library.tsx
- [ ] Restart server.ts after editing it
- [ ] Run build and confirm exit 0 before committing
- [ ] Commit with explicit pathspecs in two separate repos
- [ ] Emit the shape inline so the user sees it immediately
- [ ] Append an eval row to evals.ndjson
<!-- kernel-ok: historical attribution for the PID-loader pattern. -->
Self-correcting loader (PID feedback)
This is the rule that makes loaders learn. Ported from snappy-kernel, where it demonstrably worked: the snappy-dom-cartographer skill mapped Skool admin UI once, every later ship-agent inherited the full map for free.
Before you finish, do two things:
1. Fix gaps (P - proportional). MANDATORY. If this loader didn't cover your case - if you had to read another file, run an undocumented command, work around a wrong selector, or discover a quirk - you MUST attempt an Edit to this AGENTS.md before you log.
- Default action: edit this file. Surgical. Add the missing line, table
row, or code example where it belongs. No restructuring.
- Only
LOGGEDis allowed when: the fix needs >10 lines, spans multiple
files, or requires a structural rewrite. In that case the state/regen/drain.sh queue picks it up asynchronously.
- "I didn't have time" / "it's minor" / "the next agent will figure it out"
are NOT valid reasons. Minor gaps compound - that's the whole point of this loop.
- Concrete example of a valid P-fix: discover
agent-browser screenshot
takes path positionally not as --output <path>. One line in a table. Edit it in. Do not just log it.
- The goal: the next agent never has to leave the loader.
2. Log the result.
echo "[$(date -u +%FT%TZ)] shape-builder: <what was missing or fixed> [FIXED|LOGGED] action_kind=<kind>" >> state/log/loader-feedback.log
<slug>MUST be the literal folder name of this loader
(state/skills/<slug>/AGENTS.md). The class token between [ts] and : is the producer slug, the writeback class, AND the grade class - they must be equal so state/lib/controller-tune.ts can pair the brief.
FIXED= you patched this loader inline (P-fix).LOGGED= too large for inline; the PostToolUse enqueue + Stop-hook drain
will rewrite the loader from scratch on next session-end.
action_kindis the SECOND pairing predicate (added 2026-04-27, task #327).
Pick the value that describes what you actually did - same slug, different action_kind means the writeback satisfies a different brief layer:
shape-ok- only frontmatter-shape verification passed (rare from
a human; usually emitted by the lint, not a loader echo)
skill-ran- the skill ran end-to-end and an eval row landed
in state/log/evals.ndjson
loader-rewritten- you EDITED this AGENTS.md inline (the FIXED case),
OR the regen drain rewrote it
pattern-elevated- you promoted a recurring failure to a Critical Rule
(rule fix or new-skill scaffold) If you LOGGED (couldn't fix inline), omit action_kind - the inferrer will pick it up from your body keywords.
Do not skip this. Every agent run must leave the system better than it found it. The loader is the setpoint; you are the sensor; the gap is the error signal; closing the gap is the correction.
OpenUI Resource
- Skill-owned OpenUI Lang resource:
state/skills/shape-builder/resources/ui.openui. Read it before rendering or editing this skill's generated component surface. - Treat this resource as a first-class artifact of the skill, not a generic chat response. Improve it when the skill's user-facing output needs to become richer.
- System resources compose OpenUI primitives and inherit SnappyChat tokens. Use
ui_contract: brandedin SKILL.md only for deliberate platform or client visuals.
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 - no sidecar under state/bin/ yet. Steps, if any, are described in SKILL.md.
how we check it- the checks, plus the last 10 runs
| timestamp | verb | score | primary_issue | artifact |
|---|---|---|---|---|
| 2026-04-30 22:54Z | - | 1.00 | - | - |
| 2026-04-30 05:33Z | - | 1.00 | - | - |
| 2026-04-30 04:57Z | - | 0.60 | - | - |
| 2026-04-30 04:12Z | - | 1.00 | - | - |
| 2026-04-30 04:07Z | - | 1.00 | - | - |
| 2026-04-30 22:54Z | - | 1.00 | - | - |
| 2026-04-30 05:33Z | - | 1.00 | - | - |
| 2026-04-30 04:57Z | - | 0.60 | - | - |
| 2026-04-30 04:12Z | - | 1.00 | - | - |
| 2026-04-30 04:07Z | - | 1.00 | - | - |