shape-authoring
The complete rules for writing or editing a shape's component (kind component). Use when creating, editing, patching or fixing a kind component, calling any kindcomp_* tool, or when a shape renders em
Files1
View on GitHubSKILL.md.claude/skills/shape-authoring/SKILL.md
GitHub<!-- SYNCED COPY — do not edit here.
Canonical: common-docs/skills/shape-authoring/SKILL.md
This file is distributed to every consuming repo by
common-docs/meta/scripts/sync_skills.py. Edit the canonical, run the
sync, and commit each repo. Edits made here are overwritten and lost. -->
shape-authoring — the one rulebook for shape components
<!-- rules:stamp -->Rules from
common-docs/systems/architecture/content-ir/FEATURE.md— edit there, never here (python3 common-docs/meta/scripts/one_home.py --write).
- Every schema declares
__kindat every object position:const+requiredwhere the identity is known, and declared but optional everywhere else. The only exceptions are theboolean/numberscalar roots and thejsonkind. BEFORE triggers guarantee this onkind_definition,kind_example,kind_instanceandagent.definition.output_schema(wf_066). Never add a write-time check that rejects an unmarked schema, because the trigger fixes it. - Never strip
__kindand never loosen a schema to fit the data. Bring the data up to the schema withcontent_ir.stamp_markers_from_schema. Never reintroduceinjectKind: false, and never hand-patchemitted_json_schema, because it is emitted from the kind's source. Guards: aidreamscripts/check_kind_marker_law.py --liveand matrx-frontendpnpm check:kind-marker-law. __kindis the first key. An unfenced bound-agent stream is typed from its first key (block_detector.root_kind_declaration).jsonbreorders keys, so SQL that mints an instance builds it as text/jsonand reads the row back.- An agent is never asked for plain JSON. A structured result is a kind instance or it is markdown.
- A registered kind is not a payload with a
__kindkey.content_ir.evaluate_kind_activationchecks that its example validates, it has a real output component, and its schema is unique; the result is advice shown beside the switch (warnings), never a refusal (rule 24). The generic key/value view is not a component; activation changes go throughset_kind_activation. - Machine contracts are quarantined by residence. They live in
content_ir.io_contract. The family namesaction_io,tool_ioandagent_ioare reserved and never appear on akind_definitionrow.workflow_iois a pinned slug set, anddata_onlyis informational only: it never removes a form. Guard:scripts/check_kind_family_law.py. directive_vis a reserved prefix. No hand-authored or user kind may start with it (enforced in@kind, kind authoring and the shape creator, and checked byscripts/validate_kind_directive_registry.pyat zero). Capability is derived from the directive class and never stored.- The position law has one home:
matrx_graph.content_ir.directives. Onlycreate,update,deleteandactionexecute, and only at an agent's output root. Onlyreferenceandsecretresolve inside content. A side effect found in content renders as a button and runs only on a human click. - The directive ledger key is
sha256(namespace·type·item)withkindexcluded. Changing howtypeis derived from the slug re-applies every past action. Keep"matrx"inMATRX_OWNED_BLOCK_TYPES, never seed akind_definitionrow per directive, and never attach__irto amatrxblock, or directives render as raw JSON. - Shared logic lives once, in
aidream/apps/shared/content-ir-core(kernel) andcontent-ir-react(route, resolver, shared views). No consumer keeps a local copy of a parser, a route decision or the directive decoder. The kernel refuses React, and the render layer refuses Next.js, Redux and Supabase. Consumers ride the npmlatesttag, and versions live in each package's CHANGELOG. - Detection is identical in Python and TypeScript. A container never hides a kind: when a region closes, balanced JSON objects with a non-empty
__kindbecome their own instances, in document order. XML owns its body until its root closes, and a```matrxfence is a container. Shared conformance vectors pin markdown-fence nesting. A change to one runtime lands with the other and its vectors. - A streaming partial follows the partial wire contract. Partials ride
metadata.__ir_partial. Thestateispartial,supersededorretracted, and exactly one terminal state ends every partial, so a skeleton can never stick. Every partial is closed into valid JSON. The key is absent on most events: consumers carry state forward, never reset on absence. - Runtime wrappers delegate, never reimplement.
node_outcomerenders its inner kind through the ordinary route. Elided payloads ride once, throughoutput_ref. - A skipped check is never a pass. A degraded verdict carries a named
KindDegradedReason. Onlycatalog_unreachableis a loud skip,KindContractViolationis non-retryable, and an invalid typed output is never stored as a success. - Enforcement is the code constant
matrx_graph.kinds.KIND_ENFORCEMENT, never an environment variable. The DBadmission_configratchet coexists with it on purpose. - Undeclared keys never sit at a payload's root. They go in a declared map (for example
extras). - A primitive binds; it does not get a new component. A shape that maps onto an existing primitive binds through the primitive plus
props_transform. Block-type string values, routes, dispatch outcomes and stored content bytes are frozen, and a rename that changes one is a defect. - Every emitted kind declares a disposition. A kind with none gets no storage and files an
ops.system_errorrow (kind_disposition_undeclared). New kind data lands in records, never incontent_ir.kind_instance. - A missing or broken component is replaced at once and reviewed afterwards. The replacement shows a "just built" mark and a one-click way back to the plain data view. This never replaces a working, registered component without review.
- Reuse before you mint. Well-known kinds (web results, citations, address, rating) are reused through provider translation functions, never re-minted under a new name.
metadata.maturity = verifiedis earned only by the verification pass. - Every active shape has a skill, and a hand-written one is never overwritten. Generated skills come from the non-AI template (
kind_create_skill) and carrymetadata.created_via; a weekly refresh regenerates only those. A shape with no description gets a concise one from a weekly batch run on a fast model. - Shapes picked in chat: up to a knob (default 10) go to the model in full; past it, the model gets each shape's name and description and loads the ones it uses. Picks travel as
output_kinds(kind slugs) on start, chat and continue requests, never as skill ids; an omitted field means the chat's saved picks (chat.conversation.metadata.run_configuration.outputKinds). The server alone resolves shape → skill (aidreamservices/tooling/output_kinds.py, knobchat.output_kinds.full_body_max). - An agent locked to a structured output schema answers in its own shape; a different picked shape is never an error. The picker warns at the moment of selection, and a saved conflicting pick shows the same warning; the server drops picks the agent cannot honor and sends a visible warning (
output_kind_locked), never a failed run. The agent's schema is never modified per request. - Every kind, kind component, surface and converter is born ACTIVE, from every screen, tool, seed script or automation. Switching one off is a deliberate act; turning one on is never refused. Never write
is_active=Falseon create and never add a check that blocks activation or disables the Activate button. - A streamed shape's render state is a one-way ratchet: markdown text → JSON found →
__kindfound → kind resolved and its component rendering. Once forward it never goes back (no re-mount, no generic or text flash) because a chunk is partial, unparseable or fails validation; hold the last good render and fall back only on strong, repeated evidence (the stream ended and the final value is invalid). Mount count stays 1 per block; test with token-by-token replay including mid-key and mid-__kindsplits. - Rich content helps, never hinders. Every surface offers copy as markdown and copy as plain text one click away; formatting buttons work in every mode including raw markdown; editing happens in place. Replacing a UI never ships without full parity verified by screenshot in EVERY mode, and "done" means a clipboard capture plus a screenshot of the visible button, not a green guard. Judge each surface from the user's seat (copy, select, format, edit in place, every mode, window panels, phone) and audit every surface for consistency; copy never puts raw
<artifact>tags on the clipboard. Every rename or cleanup ships with its full inventory (tools, routes, DB, tokens, copy, docs, tests) and is done 100%, never half-way; the domain tree wins on names and placement. The basics come before any side work, and when Arman says "focus on my vision" every other lane drops. - A kind's name and schema are ONE shared contract per slug; how it looks layers user → org → system, most personal wins. Anyone fixes a bad render for themselves in one click, org admins for their organization, and super admins get a link to the admin pages for a global change (never admin power on a user route). The fix ladder starts at "kind not registered".<!-- rules:stamp-end -->
A shape component is React (TSX) stored in the platform and compiled in the reader's browser. It renders one item an agent produced. This file is the whole contract. An import, prop, action or pattern that is not written here does not exist — do not guess one.
Each block below is the single home of its rule. To change a rule, change its block.
<workflow> 1. `kindcomp_get_context` with the kind (or component_id) — FIRST, every time. It returns the kind's schema, the example payload your component receives, the other components, and open render incidents. 2. Write the COMPLETE component against the example payload, following every block below. 3. Save: `kindcomp_create_component` (new), `kindcomp_update_code` (whole source), `kindcomp_patch_code` (small exact edits). 4. A save can be REFUSED with a sentence (unknown import, banned global, no `data` read). Fix exactly what it names and save again — never work around it. 5. Fixing a crash: read the incident from step 1, fix, save, then `kindcomp_resolve_incident`. 6. Every save comes back with screenshots (light and dark, desktop and phone) and findings. Read the screenshots; fix what the findings name, save again. `kindcomp_preview` shows other data, one theme or width, the sandbox frame, or a streaming replay. </workflow> <props_contract> export default function MyShape({ data, kind, config, runAction, itemState }) { … }data— the item. EVERY field is read from it:data.title,data.items?.map(…). Flat props (props.title) are never provided; a component reading them renders empty with no error. Read-only — never mutate it, never write results into it.props_transform(optional) reshapes the value before mounting; its output STILL arrives asdata.kind,config— the kind slug and this component's config.runAction(key, input)— the ONLY way anything leaves the component (see <actions>).itemState— what actions saved onto this item;{}when nothing yet. Survives reload.onResolve,uiOptions— only inside a picker the page opened; ignore otherwise.- One default export. Hooks only at the top level of components.</props_contract>
Kit (use FIRST — see <kit>): "@/components/kind-kit/KindHeaderBar" "@/components/kind-kit/KindPanel" "@/components/kind-kit/KindPanelGrid" "@/components/kind-kit/SortableList" "@/components/kind-kit/TagList" "@/components/kind-kit/StreamingSkeleton" "@/components/kind-kit/KindActionButton" "@/components/kind-kit/KindImage" "@/components/kind-kit/KindFile" "@/components/kind-kit/KindVideo" "@/components/kind-kit/KindAudio" "@/components/kind-kit/KindEmbed" "@/components/kind-kit/KindLocale"
Copy: "@/components/agent-copy/CopyButtons" "@/components/agent-copy/CopyForAiButton"
Text: "@/components/Markdown" "@/components/MarkdownStream" "@/components/markdown" "@/components/mardown"
UI controls (prefer the design system; the ui/* paths are the same parts): "@ai-matrx/design-system/controls" "@ai-matrx/design-system" "@/components/ui/accordion" "@/components/ui/alert" "@/components/ui/avatar" "@/components/ui/badge" "@/components/ui/button" "@/components/ui/card" "@/components/ui/checkbox" "@/components/ui/collapsible" "@/components/ui/dialog" "@/components/ui/dropdown-menu" "@/components/ui/input" "@/components/ui/label" "@/components/ui/popover" "@/components/ui/progress" "@/components/ui/radio-group" "@/components/ui/scroll-area" "@/components/ui/select" "@/components/ui/separator" "@/components/ui/sheet" "@/components/ui/skeleton" "@/components/ui/slider" "@/components/ui/switch" "@/components/ui/table" "@/components/ui/tabs" "@/components/ui/textarea" "@/components/ui/tooltip" "@/lib/utils"
Icons: "lucide-react" "@ai-matrx/icons" "@/components/official/icons/IconResolver"
Libraries: "react" "recharts" "motion/react" "date-fns" "lodash" "react-katex" "react-pdf" (PDFs from data: URIs or bytes only — never a URL) "xlsx" "three" "@react-three/fiber"
Applets (page only — they do not run inside the sealed frame; do not use in shapes): "@/applets" "@ai-matrx/applets/react"
Libraries load only when imported. props_transform cannot import them.
</available_imports>
KindHeaderBar— the one compact header:title(required),icon?,subtitle?,stats?: [{label, value, icon?}],streaming?,copy?(CopyButtons props,labelrequired),actions?,size?: 'sm'|'md',className?. It has NO padding of its own: the host frame pads it. Put it inside your own bordered card and you pad it to match your rows (className="px-3 py-2"besidepx-3rows), or the icon touches the card edge.KindPanelGrid— auto-fit grid of equal-height panels:minColumnWidth?(default 280, never smaller for text),maxColumns?,gap?: 'sm'|'md'|'lg'.KindPanel—title(required),icon?,count?,badge?,streaming?,actions?(≤2),menuItems?: [{label, onSelect, icon?, disabled?, destructive?, separatorBefore?}],subline?(full width under the header),footer?(pinned to the bottom),variant?: 'card'|'bare',dense?.SortableList— drag to reorder with displacement and a drop placeholder:items,onReorder(items)(required),getKey?,renderItem?(item, {index, isDragging}),onRemove?,emptyState?.TagList/KeywordChip— chips that wrap, never truncate. TagList:items(strings or{label, key?, meta?}),selected?,onToggle?,onRemove?,onEdit?,onAdd?(renders the add input),addPlaceholder?. KeywordChip:label,meta?,selected?,onSelect?,onRemove?,tone?: 'default'|'primary'|'muted'.StreamingSkeleton,useStreamingValue,streamList,streamText— see <streaming>.KindActionButton— see <actions>.KindImage,KindFile,KindVideo,KindAudio,KindEmbed,useKindLocale— see <media>.
Import each by its exact path with the named export: import { KindPanel } from "@/components/kind-kit/KindPanel".
</kit>
CopyButtons: label (REQUIRED), human?: string | () => string (plain text or markdown you
build), agent?: () => ({ kind, location, description, data, summary? }) (Copy for AI),
json?: () => value, size?: 'xs'|'icon'|'sm', export?: { items: [{ id, label, filename, content: () => string }] } (downloads), hide?: ('copy'|'ai'|'export')[].
There is no content or data prop. Pass builder functions so text is built at click time.
CopyForAiButton: label and agent required; size?, compact?.
</copy>
| key | does | input |
|---|---|---|
run_shortcut |
runs AI on this item | { shortcutId, scope?, variables?, display?, expect?, saveAs?, label? } |
save_item_state |
keeps a person's pick or edit | { patch: { key: value } } (null deletes a key) |
open_file |
opens a saved file in the platform viewer | { file_id } |
share_file |
opens the share window for a saved file | { file_id } |
download |
saves a file to the reader's device | { filename, content, mime? } (text you built) or { filename, base64, mime? } or { file_id } |
print |
opens the platform print window | { title, markdown } or { title, html } |
fetch_image |
an image's bytes (KindImage and KindFile call it for you) |
{ url } or { file_id } |
apply_surface_write |
writes a value into the page around the shape | { target, value } |
list_surface_write_targets |
which page fields accept writes | {} |
run_shortcut:
shortcutId— a named constant at the top of the file. Never a raw agent id.scope: { selection, content, … }— TEXT the agent can read (a short markdown rendering of the piece), never the raw object.- No
saveAs→ the shortcut opens in its own window (a conversation the person continues). saveAs: "idea_3_brief"→ runs in the background and the platform saves the product onto this item asitemState.idea_3_brief. Key per piece:<piece>_<n>_<what>.expect: "json"(default) |"text"|"image"(an image shortcut; saves an image ref).- Chains: await step 1 with
saveAs, pass its product into step 2'sscope,saveAsagain.
Render saved results in place, next to the piece they belong to; keep the button available to
run again (re-running replaces the value). Where the item has no saved record (a preview),
saveAs and save_item_state refuse with a sentence; opening in a window still works.
KindActionButton is the button for any action — busy state, double-click guard, error line,
confirm:
<KindActionButton runAction={runAction} action="run_shortcut"
input={() => ({ shortcutId: BRIEF_SHORTCUT, scope: { selection: ideaText(idea) }, saveAs: `idea_${idea.number}_brief` })}
label="Visual brief" confirm="Writes a detailed brief. Uses credits." />
For a chain pass run={() => chain()} instead of action + input. Every button that spends
credits or creates something passes confirm. Props: label, icon?, busyLabel?, confirm?,
onDone?, onError?, variant?, compact?, disabled?.
Platform image shortcuts: c70e1694-9b84-4843-9999-cb5471478bb4 (idea → image prompt, text),
a415f9f8-aa62-45f9-a532-5987da03ee59 (prompt → image, expect: "image").
</actions>