Sandbox Scaffolder
Target-profile-driven scaffolding for a multi-scene, multi-option prototype into the active playground defined in ${CLAUDE_PLUGIN_ROOT}/config/prototype-targets.json. Reads the target's real conventio
Files1
View on GitHubSKILL.mdplugins/prfaq-prototype/skills/sandbox-scaffolder/SKILL.md
GitHubSandbox Scaffolder
Purpose
Create the file skeleton for a /prfaq-prototype prototype in whatever playground is the active
target, without hardcoding that playground. The command/builder resolves the active target (project
override → .claude/prototype-targets.json → the bundled default, plus any --target) and injects the
resolved target_profile; this skill consumes only that injected profile — it does not read the config
or re-resolve the target. It confirms the profile's real conventions at runtime and scaffolds a
multi-scene, multi-option prototype that matches them. Point the pipeline at a newer playground later by
adding a profile and flipping active_default — this skill does not change.
When to Use
- Stage 2 of
/prfaq-prototype, after the analyst's scene plan + clarifications are settled, before composition. - Whenever the prototype-builder needs the file skeleton for the chosen scenes/options.
When NOT to Use
- To populate screens with real components — that is
sandbox-composer. - To decide scenes/options — that is
scene-mapper+ the analyst's clarification round. - To scaffold into a hardcoded path — always resolve the target profile.
Input Requirements
Input Schema
{
"type": "object",
"properties": {
"slug": { "type": "string", "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$", "example": "attribute-based-action-controls", "description": "kebab-case, a single safe path segment — no slashes, no '..', no absolute paths." },
"label": { "type": "string", "example": "Attribute-Based Action Controls" },
"target_profile": { "type": "object", "description": "The resolved target profile object injected by the command/builder (already resolved, incl. --target / project override); do not re-resolve." },
"options": { "type": "array", "minItems": 1, "items": { "type": "object", "properties": { "id": {"type":"string"}, "label": {"type":"string"}, "philosophy": {"type":"string"} }, "required": ["id", "label"] }, "description": "The design directions (e.g. option-a, option-b)." },
"scenes": { "type": "array", "minItems": 1, "items": { "type": "object", "properties": { "id": {"type":"string"}, "label": {"type":"string"} }, "required": ["id", "label"] }, "description": "The screens to build (e.g. blocked-view / authoring / registry)." }
},
"required": ["target_profile", "slug", "label", "options", "scenes"]
}
System Prompt
You scaffold a prototype into the active target playground. Never assume conventions — read them.
Validate slug first (security): it must match ^[a-z0-9]+(-[a-z0-9]+)*$ (kebab-case, one path segment) — reject any value containing path separators or ... After forming the prototype directory, resolve the path and confirm it stays within the target's prototype_dir root; abort if it escapes.
Constrain scene IDs (security): require every scenes[].id (and the active scene.id) to match ^[a-z0-9]+(-[a-z0-9]+)*$ (kebab, one segment); resolve each scene file path and confirm it stays under <prototype_dir>/scenes/ before creating, reading, or writing it — abort on any escape. Apply the same kebab single-segment check to every options[].id (it can become a manifest/route path segment in the sibling-entry fallback).
Step 1 — Use the resolved target profile (passed in — do not re-resolve)
Use the resolved target profile the builder/command injects (already resolved via the command's order:
project prototype-targets.json → .claude/prototype-targets.json → the bundled
${CLAUDE_PLUGIN_ROOT}/config/prototype-targets.json, plus any --target override). Do NOT re-read or
re-select a target yourself — that could diverge from the command's choice. From the injected profile take:
workspace_relative_root, prototype_dir, manifest_file, manifest_entry_shape, route_pattern,
scene_api, starter_template, example_prototype, conventions_docs. All paths below are relative to
workspace_relative_root.
Step 2 — Confirm conventions at runtime (they drift)
- Read the profile's
conventions_docs(for proto-playground:src/pages/prototypes/AGENTS.md). - Read the
starter_template(if the profile has one) andexample_prototypeorchestrators for the canonical shape (for proto-playground,external-call-participants/ExternalCallParticipants.tsxshows the realusePrototypeChrome+SceneSwitcherpattern). - Read the
manifest_fileto confirm the currentPrototypeEntryshape before adding an entry.
Step 3 — Collision / reuse check (do not overwrite)
- If
prototype_dirfor thisslugalready exists, STOP and report — offer: reuse/extend, pick a new slug, or explicit overwrite. Never silently overwrite. - If an existing prototype clearly overlaps (e.g. a neighboring
external-call-participantscovers a scene), surface it so the builder can branch from it rather than duplicate. Reuse beats rebuild.
Step 4 — Create the skeleton (multi-scene, multi-option)
Follow the profile's folder_convention. For proto-playground (kebab-slug), create
src/pages/prototypes/<slug>/:
<PascalSlug>.tsx # thin orchestrator: option state + scene state + chrome + switch
<slug>Scenes.ts # scene ids + labels (feeds SceneSwitcher)
<slug>Data.ts # shared demo fixtures (composer fills)
<Slug>.module.scss # shared styles (tokens only)
components/ # prototype-only shared UI (NOT design-system)
scenes/ # one file per (scene) — options handled via an `option` prop or per-option files
Multi-option mapping (per the profile's multi_option_convention): one slug. The orchestrator holds
BOTH an option toggle (the design directions) and a scene switch (the screens). Put the option toggle
SceneSwitcherin the center slot viasetCenterSlot(...)inside auseEffect(cleanupsetCenterSlot(null); re-run on change; neverposition:fixed). If the profile'sscene_api.scene_register_hookis set (and resolves), call it with the active scene so reviewer comments scope per screen; if it'snull, skip it — never phantom-import. Each scene file receives the activeoption(and state) as props and renders that direction's take. Only split into per-option files when a shared scene file would mislead. Mirror the exact import paths from the profile'sscene_api(verify they resolve).
Step 5 — Register in the manifest
Append a PrototypeEntry matching the profile's manifest_entry_shape (proto-playground:
{ id, label, path, component }), importing the orchestrator; path = route_pattern with <slug>.
Re-read the interface first; do not hardcode fields if the manifest changed.
Step 6 — Header labels
Top every generated file with an // [AI DRAFT] comment. Leave scene bodies as thin placeholders for
sandbox-composer to fill — do not compose components here.
Output Format
{
"target": "proto-playground",
"root": "prototype-playground/proto-playground",
"files_created": [ { "path": "src/pages/prototypes/<slug>/<PascalSlug>.tsx", "type": "orchestrator" } ],
"files_modified": [ { "path": "src/manifests/prototypes.ts", "changes": ["import", "PROTOTYPES entry"] } ],
"route": "/prototypes/<slug>",
"scenes_scaffolded": [ "..." ],
"options_scaffolded": [ "..." ],
"reuse_notes": [ "..." ],
"collisions": [ ]
}
Error Handling
| Error | Cause | Resolution |
|---|---|---|
TARGET_UNRESOLVED |
No resolved target_profile injected, or it is missing required keys |
Stop; report that the command/builder must inject a resolved target profile. |
ROOT_MISSING |
workspace_relative_root absent on disk |
Stop. The command's playground-bootstrap pre-flight should have offered the clone; tell the user to re-run so it can, or to point the profile at their playground. |
PROTOTYPE_EXISTS |
Slug dir already present | Offer reuse/extend, new slug, or explicit overwrite; never silent overwrite. |
MANIFEST_SHAPE_DRIFT |
Manifest entry fields differ from the profile | Trust the live manifest; update the profile note and match the live shape. |
Tone & Calibration
- Convention-first, runtime-verified. Read the real files; never assume the profile is perfectly current.
- Reuse over rebuild. Surface overlapping prototypes so the builder branches instead of duplicating.
- Thin orchestrator. Chrome + switching only; no composition here.
Related Skills
- sandbox-composer — fills the scaffolded scene files with real components + demo data.
- scene-mapper — supplies the scenes/options this skill lays out.
Last Updated: 2026-09-09 Maintainer: Mattermost Design Team