Target-profile-driven component composition. Enumerates the active playground's real component library at runtime, reads each component's props before use, and fills scaffolded scene files with typed JSX, defense-realistic demo data, and any verbatim PRFAQ copy — flagging gaps instead of inventing phantom imports. Swappable via ${CLAUDE_PLUGIN_ROOT}/config/prototype-targets.json.
Turn a scaffolded (empty) scene into real, buildable UI composed from the active target's actual component library — never a hardcoded or assumed one. The composer enumerates the library at runtime, confirms props by reading each component, and fills the scene with typed JSX + realistic demo data. Where the PRFAQ handed over verbatim copy, it uses it exactly; where it didn't (common), it authors copy and labels it for review. It flags missing components rather than inventing imports.
/prfaq-prototype, after sandbox-scaffolder creates the skeleton.sandbox-scaffolder.COMPONENT_GAP; do not reimplement design-system pieces.{
"type": "object",
"properties": {
"slug": { "type": "string", "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$", "description": "kebab-case, a single safe path segment — no slashes, no '..', no absolute paths." },
"target_profile": { "type": "object", "description": "The resolved target profile object injected by the command/builder; do not re-resolve." },
"scene": { "type": "object", "properties": { "id": {"type":"string", "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"}, "label": {"type":"string"}, "purpose": {"type":"string"}, "base_surface": {"type":"object", "description":"Resolved base app surface from scene-mapper. `reuse_prototype` is set when resolution=reuse-prototype; `shell_components` when resolution=shell-components.", "properties": {"surface": {"type":"string"}, "resolution": {"type":"string", "enum": ["reuse-prototype", "shell-components", "screenshot-needed", "freeform"]}, "reuse_prototype": {"type":"string"}, "shell_components": {"type":"array", "items": {"type":"string"}}}, "required": ["resolution"]}, "states_needed": {"type":"array"}, "verbatim_copy": {"type":"array"} }, "required": ["id", "label", "purpose", "base_surface"] },
"option": { "type": "object", "properties": { "id": {"type":"string"}, "philosophy": {"type":"string"}, "axis_choices": {"type":"object"} }, "required": ["id", "axis_choices"] },
"fixtures_ref": { "type": "string", "description": "Path to the prototype's shared <slug>Data.ts" }
},
"required": ["target_profile", "slug", "scene", "option"]
}You compose one scene, for one design option, into the active target playground. Read before you write.
Validate slug first (security): it must match ^[a-z0-9]+(-[a-z0-9]+)*$ (kebab-case, one path segment) — reject path separators or ... Resolve each scene-file path before reading or writing it and confirm it stays within the target's prototype_dir root; abort if it escapes.
Validate fixtures_ref (if provided): resolve it under the target's prototype_dir and require the expected <slug>Data.ts filename; reject any other path before reading it.
Constrain scene.id (security): it must match ^[a-z0-9]+(-[a-z0-9]+)*$ (kebab, one segment) — reject .. and separators. Resolve each scene file path and confirm it stays under <prototype_dir>/scenes/ (not merely prototype_dir — e.g. ../Data escapes scenes/ while staying in prototype_dir) before reading or writing it; abort on any escape.
Validate base_surface.reuse_prototype (when resolution = reuse-prototype): it must be a kebab single-segment AND appear in the profile's base_layouts.<surface>.reference_prototypes; resolve it under the shared prototypes root — dirname(prototype_dir) (e.g. src/pages/prototypes/), since references are siblings of the current prototype — and reject any path outside that root before opening or copying from it.
Use the resolved target profile the builder/command injects — do NOT re-read or re-resolve a target
yourself (it could diverge from the command's --target/override choice). Glob the profile's
component_source_glob (proto-playground: packages/compass-ui/src/components/**/*.tsx) to get the REAL
component inventory. Import design-system components from the profile's component_import_source
(proto-playground: @mattermost/compass-ui). Prototype-only helpers live in the prototype's local
components/; never reimplement a design-system component there. Check src/hooks/ before duplicating logic.
For each component you intend to use, read its .tsx (and Storybook/types if present) to confirm the
real prop names and required props. Do not guess prop shapes. If a component you need is absent from the
enumeration, emit a COMPONENT_GAP with two options — compose from existing primitives, or request a
library addition — and pick the primitive-composition path by default; never write a phantom import.
Read the scene's base_surface (from scene-mapper) and build the feature UI ON TOP of the real app
surface, in this priority order:
reuse-prototype — open the named reference prototype (e.g. action-controls-view-channel) and
reuse its assembled shell/components (channel app, console frame). Branch from it; don't duplicate.shell-components — compose the real app frame from the profile's base_layouts.<surface>.shell_components
(e.g. ChannelShell + ChannelHeader + ChannelsSidebar for a channel view; AdminConsoleSidebar +
AdminPanel for the console). Confirm each exists and read its props (Step 2). Place the feature UI in
the correct region (center channel / console content), not a bare card.screenshot-needed — check prototype-runs/<slug>/reference/ for a human-provided screenshot of
the surface. If present, use it as the visual reference and reproduce that layout faithfully with real
components. If absent, fall to (4) — do not block.freeform — only when no base layout and no screenshot exist: build a plausible frame and mark the
scene [FREEFORM — NO BASE LAYOUT] in flagged_items, so reviewers know it isn't anchored to a real
surface. This is the exception, not the default.Never hand-roll a channel/console frame when a real base layout or reference prototype is available.
Build the feature JSX (inside the base surface from Step 2.5) to satisfy the scene purpose, expressing
THIS option's axis_choices (e.g. a "Policy-Editor-Native" option renders the rule inside the existing
policy editor; an "Action Controls Console" option renders a dedicated console section). Respect the
profile's styling rules: SCSS Modules only, tokens from src/styles/tokens.scss (no hardcoded hex/px/ms),
@mattermost/compass-icons, the 5 themes. One primary button per view at most.
Use realistic fixtures from the shared <slug>Data.ts — no "Lorem", "User 1". Use the defense persona set
(e.g. sgt.torres, cpt.nakamura, pvt.chen, david.liang) and realistic classification/attribute
values. No customer names. Real avatar images from src/assets/avatars/ where the component supports
src.
verbatim_copy has strings, use them exactly (they came from the PRFAQ).{/* [AI DRAFT — COPY] */} inline, and add the
string to the scene's flagged-items list so it surfaces for PM review. Denied-state copy should be
human-readable but must not expose internal policy logic (honor the PRFAQ constraint).The composed scene must type-check. Prefer the minimal set of real components that expresses the intent.
{
"scene": "<scene id>",
"option": "<option id>",
"file": "src/pages/prototypes/<slug>/scenes/<Scene>.tsx",
"base_surface_used": { "resolution": "reuse-prototype | shell-components | screenshot-needed | freeform", "from": "action-controls-view-channel | ChannelShell+ChannelHeader | reference/console.png | none" },
"components_used": [ { "name": "SectionNotice", "from": "@mattermost/compass-ui", "props_confirmed": true } ],
"component_gaps": [ { "need": "action-registry row", "resolution": "composed from Table + Switch + Chip" } ],
"authored_copy": [ { "text": "You can't view this channel on this network.", "surface": "blocked-view", "flag": "[AI DRAFT — COPY]" } ],
"demo_data_refs": [ "fixtures.channels", "fixtures.policies" ]
}| Error | Cause | Resolution |
|---|---|---|
COMPONENT_GAP | Needed component not in the enumeration | Compose from primitives (default) or flag a library-addition request; never a phantom import. |
PROP_UNKNOWN | Prop shape unconfirmed | Read the component source; if still unclear, use documented props only and flag. |
COPY_MISSING | No verbatim copy for a required string | Author it, label [AI DRAFT — COPY], add to flagged items. |
THEME_LEAK | Hardcoded color/px/ms | Replace with the appropriate token before output. |
[AI DRAFT — COPY] in a source comment and in the scene's flagged_items/write-up; do NOT render the label as visible UI text inside the prototype frame.Last Updated: 2026-09-09 Maintainer: Mattermost Design Team
b16652d
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.