---
name: create-mode
description: Create or fork a Pneuma mode, scaffold its manifest, viewer, skill, seeds, and showcase. Use for new-mode authoring in this repository; discover unresolved requirements, document the design brief, then implement and verify.
---

# Create Mode

A guided journey for adding a new mode to Pneuma Skills. The journey has **three phases** — Discovery (ask the right questions), Brief (write down key choices and resolve material unknowns), Implementation (generate files). Each phase has a clear handoff to the next; **never skip Brief**. Pneuma has a stable contract layer; a concise design brief keeps the viewer aligned with the user's intended work.

This skill runs in Claude Code and Codex. Use the active harness's file, planning, browser, and question tools; native Claude tool names are not prerequisites. Resolve paths from this skill directory.

Apply the root [product and architecture guidance](../../../AGENTS.md#product-and-architecture)
and [Engineering Judgment](../../../AGENTS.md#engineering-judgment) to the brief:
identify domain invariants and ownership before choosing Source kinds or actions,
and inspect existing modes and mature implementations before building anew.

The reference material in `references/` is where the **knowledge** lives — go read the relevant one whenever you're about to make a meaningful decision. SKILL.md is the **journey**, not the textbook.

---

## When to use

Trigger this skill when the user asks for any of:

- "create a new mode for X"
- "fork slide / webcraft / … for a different domain"
- "scaffold a mode"
- "add a [mindmap | spreadsheet | timeline | annotator | …] mode"
- "design the viewer for a mode that …"

If the user *only* asks about an existing mode's behavior, this skill is **not** the right tool — direct them to `docs/reference/viewer-agent-protocol.md` or the mode's own SKILL.md.

---

## Phase 1 — Discovery interview

Goal: fill the design brief from the user's request, existing decisions, and repository evidence. Ask only for missing choices that materially affect the result. Use the question tool available in the active harness, or a concise chat question; never require a tool named `AskUserQuestion`. Bundle closely related unknowns and continue independent work while waiting.

### Discovery checklist (reuse known answers; ask only where needed)

1. **Identity** — name (kebab-case), one-line displayName, two-line description, intended icon style. *This is the only question you can pose as a single multi-line form.*
2. **Domain in one sentence** — what is the user creating with this mode? A document? A canvas of objects? A timeline? Let the user answer free-form before you offer Source-kind options. Read `references/domain-and-sources.md` while they think.
3. **Inspiration vs original** — does this mode borrow content (commands, references, design language, taxonomy) from an existing tool, library, or project? If yes, ask the upstream's name + URL + license. This determines whether you'll write `NOTICE.md` and set `inspiredBy`. Read `references/external-integrations.md` for the borrow-vs-inspiration line.
4. **Source kind** *(branch on Q2 + Q3)* — present `file-glob` / `json-file` / `aggregate-file` / `memory` with the one that fits Q2's domain pre-selected as "Recommended". Explain *why* it fits in the option's `description`. If `aggregate-file` wins, note that you'll also generate `domain.ts`.
5. **Workspace model** *(when Q4 is not `memory`)* — `"all"` / `"manifest"` / `"single"`; do users author many independent files, an ordered/structured set, or one main document? See `references/viewer-contract-patterns.md` for the FileWorkspaceModel matrix.
6. **ViewerAddress vocabulary** — "what's the smallest thing the user can point at?" Propose a draft `{ contentSet?, ... }` based on Q2's domain noun (slide / page / row / node / heading). Confirm with the user; explicitly name the coarse "where" key and any fine "within" key. See `references/viewer-contract-patterns.md::ViewerAddress`.
7. **Initial action space** — propose the smallest sufficient set with id / label / category / agentInvocable, tying each action to a concrete task. Include `navigate-to` when users need addressable navigation; add `ui` and `custom` for demonstrated needs. Don't list `capture` — it's framework-built-in.
8. **External integrations** *(conditional — only ask if Q2 or Q3 implied an external API / SDK / CDN / library / API key)* — does the viewer fetch external APIs (→ `proxy`)? does the agent or viewer need API keys (→ `init.params` with `sensitive: true` + `envMapping`)? does this need an MCP server (→ `skill.mcpServers`)? Read `references/external-integrations.md` for the proxy / Babel-JIT / NOTICE patterns.
9. **Cloud surfaces** — resolve both choices in every brief; reuse existing decisions and ask only when unclear. Two independent questions, in this order: (a) *should a finished piece of work in this mode be shareable as a read-only page a stranger can open with no Pneuma installed?* — that's the hosted player, and it obligates the viewer to render from workspace files alone, with no live backend; (b) *does the mode produce a deployable static site?* — that's Vercel / Cloudflare Pages deploy. Put the real trade-off in each option's `description`: "yes" buys shareability and costs a read-only degradation path plus a browser verification pass on every viewer change; "no" costs nothing and can be revisited in a later release. Read `references/cloud-surfaces.md` before you ask — the compatibility checklist there is what "yes" actually commits to.
10. **Seed strategy** — single file, multiple use-case content sets, or language×theme matrix? What's the *first* seed's narrative — what story does it tell to a brand-new user? See `references/seed-and-showcase.md`.
11. **Evolution directive** — give the evolve agent a one-sentence "what should it learn for this mode?" (e.g., "Learn the user's slide design preferences: typography, palette, density, structure"). This is what makes the mode personalize over time.

### What to read while interviewing

| When you're about to ask … | Read first |
|---|---|
| Q2 / Q4 (domain → source kind) | `references/domain-and-sources.md` |
| Q5 / Q6 / Q7 (workspace / address / actions) | `references/viewer-contract-patterns.md` |
| Q3 / Q8 (inspiration / external deps) | `references/external-integrations.md` |
| Q9 (cloud surfaces) | `references/cloud-surfaces.md` |
| Q10 (seed strategy) | `references/seed-and-showcase.md` |
| Q11 (evolution directive) | `references/skill-md-patterns.md` (evolution section) |

If you ever find yourself stuck choosing between two patterns, open `references/case-studies.md` — it indexes which existing mode made which choice, so you can read that mode's manifest as a concrete precedent.

---

## Phase 2 — Design brief & user confirmation

Goal: write down **every key choice** with a one-line rationale in one place. Use the brief for Phase 3; label assumptions and resolve material unknowns before dependent implementation. An approved brief or explicit instruction to implement already supplies authorization.

### Brief structure

Present a concise brief in chat, or link the design document if the task already uses one. Cover the applicable fields below without asking the user to repeat known decisions:

```markdown
# Mode design brief — <displayName>

## Identity
- name: <kebab-case>
- displayName: <string or LocalizedString>
- description: <one sentence>
- icon: <SVG approach: e.g. "lucide-style line icon, single path">

## Domain
<one paragraph — what the user is creating; what the viewer renders; what the agent does>

## Invariants and implementation choice
- required invariants: <what must remain true, and how it will be verified>
- state and writers: <persistent work, transient UI state, and who may change each>
- reuse: <existing mode / project implementation / library, or the concrete gap>
- added abstraction or complexity: <its contract or real variation and benefit; omit if none>

## Source layer
- kind: <file-glob | json-file | aggregate-file | memory>
- domain type T: <the TypeScript type the viewer subscribes to, sketched>
- why this kind: <one sentence — see references/domain-and-sources.md>
- domain.ts needed: <yes | no>

## Workspace model
- type: <"all" | "manifest" | "single">
- multiFile: <true | false>
- ordered: <true | false>
- hasActiveFile: <true | false>
- supportsContentSets: <true | false>

## ViewerAddress vocabulary
- coarse keys: <e.g. `contentSet?`, `slide`>
- fine keys: <e.g. `selector?`, `anchor?`>
- example address: `{ contentSet: "en-light", slide: 3 }`
- documented in: skill/SKILL.md (will write a sub-section)

## Action space
| id | label | category | agentInvocable | params |
|----|-------|----------|----------------|--------|
| navigate-to | Go to … | navigate | true | { address: object } |
| … | … | … | … | … |

(framework provides `capture` automatically — not listed)

## Seed strategy
- shape: <single | content-sets-by-use-case | language×theme>
- content sets: <list, with each name + one-line purpose>
- first seed narrative: <one sentence>

## External integrations
- proxy: <none | list routes>
- init.params: <none | list with sensitive flag>
- skill.mcpServers: <none | list>
- viewer.refreshStrategy: <"auto" | "manual">
- NOTICE.md required: <yes | no — if yes, upstream name + license + version pinned>
- inspiredBy: <none | { name, url }>
- external effects (if any): <observable completion, retry/idempotency, cancellation, and recovery or undo limits>

## Cloud surfaces
- hosted player: <yes | no — and the reason, in the vocabulary of references/cloud-surfaces.md>
- artifact deploy: <none | vercel + cf-pages>
- obligations (only when either is yes):
  - registration: <`core/player-support.ts` whitelist entry / `compatibleModes` entry in BOTH deploy plugins + an `/export/<name>` route>
  - read-only degradation: <which affordances hide when `editing === false`; which `/api/*` calls gate on the `staticPlayer` store flag>
  - verification: <build the player, load a real package for THIS mode in a browser, exercise it read-only, console clean>

## Launcher surface
- visibility: <public (in gallery) | hidden (internal-only, manifest.hidden=true)>
- featured-eligible: <yes (default; showcase highlights present) | no (no showcase or hidden mode)>

## Evolution directive
> <one sentence to the evolve agent>

## Open questions / deferred
- <anything we punted on; e.g. "showcase imagery defers to /showcase">
```

### Before implementation

If the user has authorized implementation and the brief fits that scope, proceed. Ask only when a material unresolved choice would change the product or exceed the agreed scope. Keep the brief current as the user steers; do not request approval again for decisions already made.

---

## Phase 3 — Implementation

Once the brief is sufficiently resolved and implementation is authorized, generate files in this order. Use templates from `assets/templates/`; replace the `TODO:` placeholders against the brief. Don't ad-lib structure — the templates encode the conventions extracted from existing modes.

### Step 1 — Scaffold the directory

```
modes/<name>/
├── manifest.ts          ← from assets/templates/manifest.ts.template
├── pneuma-mode.ts       ← from assets/templates/pneuma-mode.ts.template
├── domain.ts            ← only if Source kind is aggregate-file; from domain.ts.template
├── skill/
│   └── SKILL.md         ← from assets/templates/SKILL.md.template
├── seed/
│   └── <content sets per brief>
├── viewer/
│   └── <ModeName>Preview.tsx   ← scaffold a stub PreviewComponent
└── showcase/
    └── showcase.json    ← from assets/templates/showcase.json.template (with concept descriptions)

NOTICE.md                ← only if brief said "NOTICE.md required: yes"; from NOTICE.md.template
```

### Step 2 — Wire up file-by-file

For each file, fill in templates against the brief. Specifics:

- **manifest.ts** — every brief field maps to a manifest field. The template has marked sections (`// TODO: identity`, `// TODO: sources`, etc.) — fill each from the brief. Don't add fields the brief doesn't have; brevity over completeness for v0.1.0.
- **pneuma-mode.ts** — the `ModeDefinition` binding: import manifest, wire it to a stub `ViewerContract` that imports the PreviewComponent and implements `extractContext`, `workspace.resolveItems`, `workspace.createEmpty`. See `references/viewer-contract-patterns.md::pneuma-mode.ts` for the binding pattern.
- **domain.ts** (aggregate-file only) — write the `load(files) → T | null` and `save(value, current) → { writes, deletes }` pair as pure functions. Read existing modes' `domain.ts` for the pattern (slide / illustrate / kami use this).
- **skill/SKILL.md** — follow `references/skill-md-patterns.md`: Scene → Viewer Contract → Core Rules → Workflow → Commands → References. Include a `## ViewerAddress vocabulary` sub-section that names every key from the brief and a one-line meaning per key.
- **viewer/`<Name>Preview.tsx`** — stub. Renders a placeholder ("Mode initialized — start authoring"). Imports the Source from `props.sources` via `useSource`. The user (or you in a follow-up) will flesh this out.
- **seed/** — write the first content set's files per the brief's narrative.
- **showcase/showcase.json** — from template, with brief's tagline + 3 highlight concept descriptions. *Images are generated in Step 4.*
- **NOTICE.md** *(if required)* — pin upstream name + URL + license + version + sync date; include the "what we borrowed / what we adapted / what we dropped" mapping table. Template at `assets/templates/NOTICE.md.template`.

### Step 3 — Register the mode and its supported surfaces

Complete frontend registration (**3a**), launcher discovery (**3b**), and
public documentation (**3c**) as applicable. These serve different consumers:
a mode can launch successfully while remaining absent from the gallery or docs.

Use the visibility decision recorded in the brief. Public modes need all three;
hidden modes still need frontend registration but stay out of public catalogs
and may omit the gallery entry as described below. Resolve visibility only if
it is still unknown. Cloud registration (**3d**) is conditional on the brief's
`## Cloud surfaces` decision and the verification pass described there.

#### 3a. Frontend dynamic-import registry — `core/mode-loader.ts`

Add an entry to the `builtinModes: Record<string, ModeSource>` map
so the frontend can dynamic-import the mode's manifest and viewer.
Without this, the mode 404s when a user opens its URL ("Unknown
mode: <name>").

```ts
// core/mode-loader.ts — inside `const builtinModes: Record<string, ModeSource> = { ... }`
<name>: {
  type: "builtin",
  manifestLoader: () =>
    import("../modes/<name>/manifest.js").then((m) => m.default),
  definitionLoader: () =>
    import("../modes/<name>/pneuma-mode.js").then((m) => m.default),
},
```

Copy the shape from the neighboring entry rather than from memory —
the field names are `manifestLoader` / `definitionLoader`, and the
`type: "builtin"` discriminant is required.

#### 3b. Launcher gallery discovery and distribution

The launcher's `/api/registry` scans on-disk `modes/*/manifest.ts` files.
A new directory is discovered automatically; do not add a hardcoded name to
`server/index.ts`. `hidden: true` is the public-picker filter.

For a bundled mode, add its name to `modes/distribution.json` and its directory
to `package.json`'s `files`, then add the frontend entry in Step 3a. A catalog
mode follows the catalog publishing workflow instead. Hosted-player support
requires a viewer in the separate player build registry. Verify both the registry response and the
actual gallery; source discovery and release packaging are separate concerns.

#### 3c. Docs — public mode catalogs

If the mode is **not hidden**, add its row to the "Built-in Modes" table
and update CLI usage in **both** `README.md` and `README.zh.md`. Hidden
modes stay out of the public catalog. `AGENTS.md` links to the catalog;
it does not maintain another mode list. `CLAUDE.md` remains the one-line
`@AGENTS.md` import: never add content to it or copy it over AGENTS.md.

#### 3d. Cloud surfaces — *conditional*, driven by the brief

Unlike 3a–3c, this one is **not universal**. Add each entry only if
the brief's `## Cloud surfaces` section said yes; a mode that answered
"no" is correctly absent from both files, and adding it speculatively
ships a broken share link.

- **Hosted player** — add the viewer import to `src/player/player-modes.ts`
  so the player build contains it, then append the mode name to
  `WEB_PLAYER_SUPPORTED_MODES` in `core/player-support.ts` after verification.
  The whitelist entry is the *last* thing you do: the whitelist
  is a claim that the viewer has been exercised in a real player build.
  See the verification obligation below.
- **Artifact deploy** — add the mode name to `compatibleModes` in
  **both** `plugins/vercel/manifest.ts` and
  `plugins/cf-pages/manifest.ts`. Membership there only makes the
  deploy providers resolve for the session; the button itself lives on
  the mode's `/export/<name>` page (`server/routes/export.ts` +
  `server/routes/deploy-ui.ts`), which needs a mode-specific
  `collectDeployFiles()`. Listing the mode without building that page
  produces nothing — `doc` and `gridboard` are both listed today and
  neither has an export route.

**Verification obligation (hosted player).** Never whitelist a mode on
the strength of reading code. Build the player
(`bunx vite build --config vite.player.config.ts`), materialize a real
package for *this* mode and serve it from one origin (copy
`scripts/smoke-player.ts`; `scripts/smoke-webcraft.ts` and
`scripts/smoke-kami.ts` are the mode-specific precedents), open it in a
browser, and exercise the viewer read-only — content sets, item
navigation, timeline scrub — with the console clean. The failure modes
here all look fine in source: an empty viewer because the mode's file
extension isn't in the package's text allowlist, an asset path the
content service worker can't resolve, a viewer stuck on "Loading…"
waiting for a signal the player never sends.
`references/cloud-surfaces.md` carries the full checklist.

#### Featured vs. hidden

Use the visibility decision already recorded in the brief. Public modes with
showcase highlights are eligible for the random featured slot. `hidden: true`
removes the mode from user pickers entirely; it is not a separate "do not feature"
switch. Do not ask again after registration or hide a public mode to avoid featuring.

### Step 4 — Generate showcase imagery

Hand off to the existing showcase workflow. Read `.agents/skills/showcase/SKILL.md` and execute its **Step 3 (Generate Showcase Images)** for the new mode — hero + 3 highlight images, 1376×768, "Ethereal Tech Dark Mockup" style, saved to `modes/<name>/showcase/`. The descriptions you put in `showcase.json` during Step 2 become the briefs for image generation.

> This is the only Phase-3 step that takes appreciable time. If image generation isn't available right now (no API key, offline), surface that to the user and let them decide whether to defer — `showcase.json` with the right descriptions but missing images is a valid intermediate state.

### Step 5 — Sanity check

Don't claim the mode is ready until you verify these:

1. `modes/<name>/manifest.ts` type-checks against `core/types/mode-manifest.ts` (`bun run typecheck` runs clean from the repository root).
2. A disposable `bun run dev <name> --no-open --viewing` session starts without error. Use an isolated workspace; do not resume an unrelated agent just to inspect the viewer.
3. **The launcher's `/api/registry` includes the new entry.** Test via `curl -s http://localhost:17996/api/registry | jq '.builtins[].name'` (or whatever port the launcher is on). If the name is missing, check its on-disk manifest and public visibility; Step 3b describes current discovery and packaging.
4. The launcher's mode gallery shows the new entry (same — say so if you can't run the launcher).
5. There are no lingering `TODO:` comments from the template you didn't address.
6. **Cloud surfaces match the brief.** If the brief said *no* to both,
   verify the mode's name appears in **neither** `core/player-support.ts`
   nor either deploy plugin's `compatibleModes` — a speculative entry
   ships a broken share link or a dead Deploy button. If the brief said
   *yes* to the hosted player, the browser pass from Step 3d must have
   actually happened: player built, real package for this mode loaded,
   viewer exercised read-only, console clean. If you couldn't run it,
   say so explicitly and leave the whitelist entry **out** until someone
   can — an unverified whitelist entry is worse than a missing one,
   because `supported` is baked into every package at share time and a
   package exported while the flag was wrong stays wrong until it's
   re-shared.

---

## Closing principles

These show up in every existing mode; honor them in the one you're creating too.

1. **Domain-first, transport later.** Define the domain type `T` before choosing how it serializes. Source kind is a *consequence* of T, not a prior decision.
2. **One noun for "which object" — `ViewerAddress`.** Every action that takes an object reference, every notification that reports one, every locator card that points to one, must use the *same* address shape. Mode owns the vocabulary; framework owns the slot.
3. **Each action serves a concrete task.** Start with the smallest sufficient action space; two to five actions is a common pattern, not a quota. Distinguish agent-to-viewer actions from user-to-agent commands (⑥), and reuse built-in actions.
4. **`manifest.ts` declares; `pneuma-mode.ts` implements.** Keep the split. Manifest is read by skill-installer + backend; `pneuma-mode.ts` is read by the frontend mode-loader. Don't put React imports in `manifest.ts`.
5. **`SKILL.md` is the agent's project guide for *this* mode** — it follows the same "scene → contract → rules → examples → references" rhythm as the root `AGENTS.md` does for the project. Put depth in `skill/references/<topic>.md` files, not in the main body.
6. **Borrowed content needs a `NOTICE.md`; borrowed ideas don't.** Direct transcription, license excerpts, command tables, font subsets → declare upstream + license + version. Architectural metaphors, aesthetic direction, workflow philosophy → no notice needed.
7. **Showcase is mandatory, but imagery can defer.** `showcase.json` with descriptions and a tagline is the minimum bar (so the launcher gallery has copy); imagery generation can happen later via the existing `/showcase` flow.

---

## References

Open the matching file when you're about to make the corresponding decision. Don't load them all eagerly — progressive disclosure.

| File | When to read |
|---|---|
| `references/mode-anatomy.md` | First touch — overview of the directory shape, required vs optional files, manifest field matrix |
| `references/domain-and-sources.md` | Picking Source kind, designing domain type T, writing `domain.ts` |
| `references/viewer-contract-patterns.md` | Wiring `ViewerContract`, choosing `ViewerAddress` vocabulary, designing `workspace.resolveItems` |
| `references/skill-md-patterns.md` | Writing `skill/SKILL.md` and the evolution directive |
| `references/seed-and-showcase.md` | Designing seed content sets and `showcase.json` |
| `references/external-integrations.md` | proxy routes, JIT compilation, API-key params, NOTICE.md mechanics |
| `references/cloud-surfaces.md` | Deciding hosted-player support and artifact deploy — what the player environment is, the viewer compatibility checklist, the static-web fast path, the disqualifiers, how to verify before whitelisting |
| `references/case-studies.md` | "Where did <existing mode> make this choice?" — index by pattern, not by mode |

Templates in `assets/templates/` are the concrete files you'll write from. Each template has `TODO:` markers where the brief plugs in.
