--- name: pneuma-eli5 description: > Pneuma ELI5 Mode workspace guidelines. Use for ANY task in this workspace: explaining a topic, a piece of code, an error message, or a document to a specific audience, and building the audience ladder that holds those explanations. Trigger on any request to explain something *for a named reader* — a person, an age, a role or a relationship — including "ELI5"-style asks, and on requests for the same thing at several levels or a comparison of two audience versions. Consult before your first edit in a new conversation. --- # Pneuma ELI5 Mode — Audience Ladder Explainers ## Scene Someone just understood something hard, and now they have to hand that understanding to a person who does not have their background: a manager who needs to approve the work, an eight-year-old who asked a real question, a teammate three layers down the stack. You are the writer they sit next to. In front of the user is an **audience ladder** — an ordered rail of people, simplest on the left, most technical on the right. Click a rung and the page written *for that person* fills the canvas: not the same words at a different reading level, but a differently designed document — huge rounded type and a toy-box analogy on one rung, a one-screen memo with a cost callout on the next, precise mechanics with a code sample on the last. A compare toggle puts two rungs side by side so the shift is visible in one glance. You write those pages as files. The viewer is a player, not an editor — it never writes back. Every explainer is a directory holding a `manifest.json` and one self-contained HTML page per audience, and one workspace holds as many explainers as the user has topics. ## Viewer contract The viewer is a live player for the explainers you write under `/manifest.json` + `/pages/*.html`. Files you write appear in the user's preview immediately — you never need to prove a page "saved". The user interacts with the viewer to hand you grounded context. ### What the user can select The user can select an **audience** by clicking a rung on the ladder, and an **element inside a rendered page** by clicking the page surface. Either way, their next message carries a `` block with an `Address:` line — a JSON-serialized `ViewerAddress` naming exactly what they had in front of them. When they say "this page", "make this shorter", or "the callout here", resolve the referent against that address rather than guessing from the conversation. ### ViewerAddress vocabulary | Key | Kind | Meaning | |---|---|---| | `contentSet` | framework-reserved (coarse) | The explainer directory — one topic (`database-index`, `oauth-flow`). Passed through automatically when present; include it explicitly whenever you point at a topic other than the one on screen. | | `audience` | coarse | The audience id within the active explainer, matching an `audiences[].id` in that set's `manifest.json` (`age-5`, `manager`, `engineer`). Names one rung of the ladder — one whole page. | | `anchor` | fine (optional) | An element id or CSS selector resolved inside the rendered page (`#cost-callout`, `.analogy`, `h2`). Narrows an address from "this page" to "this part of this page". | Use only the keys you need. `{"audience":"manager"}` names a whole page in the open explainer; `{"contentSet":"database-index","audience":"engineer","anchor":"#tradeoffs"}` names one section of one page in a specific explainer. When the user hands you an address, copy that JSON verbatim into your locator cards and action calls — retyping it is how targets drift. ### Locator cards After writing or editing a page, embed a `` card so the user can jump to it in one click. Always emit fully-formed cards with real values. ```xml ``` Include `contentSet` whenever the card points at a different explainer than the one currently on screen; a bare `audience` resolves inside the active set, and an address naming a topic this workspace does not have is refused rather than silently redirected. ### Actions you can invoke Actions go to `POST $PNEUMA_API/api/viewer/action`. Both actions below take a `params.address` object — a `ViewerAddress`, **wrapped under the `address` key**. A bare `{"audience":"manager"}` as `params` will not resolve. - **`navigate-to`** — move the viewer to one rung of the ladder. Invoke it right after you finish writing or substantially editing a page, so the user lands on the version you just changed instead of hunting for it. ```bash curl -s -X POST "$PNEUMA_API/api/viewer/action" \ -H 'Content-Type: application/json' \ -d '{"actionId":"navigate-to","params":{"address":{"audience":"age-5"}}}' ``` Cross-explainer navigation carries the set: `{"address":{"contentSet":"how-llms-work","audience":"pm"}}`. - **`capture`** (framework built-in) — screenshot the live viewer and get a PNG path back; `Read` that path to see it. Reach for it when you need *visual* judgement that reading your own HTML cannot give you: is the hierarchy right, does the kid page actually feel playful, does the manager memo fit the first screen. Omit `params` for a full-viewer shot; pass an address to target one page or one element. ```bash curl -s -X POST "$PNEUMA_API/api/viewer/action" \ -H 'Content-Type: application/json' \ -d '{"actionId":"capture","params":{"address":{"audience":"manager","anchor":"#impact"}}}' ``` Do not open an external browser, headless Chrome, or the chrome-devtools MCP to check a page. `capture` returns what the user actually sees, inside the viewer's own chrome; an external render is a different picture and judging it wastes a loop. ## Core rules ### The content-set layout is the contract One topic is one content set — a top-level directory in the workspace: ``` / ├── manifest.json # the ladder ├── pages/.html # one self-contained page per audience └── assets/ # optional images — pages reference them as ../assets/ ``` `manifest.json` has exactly this shape: ```json { "title": "What is a database index?", "topic": "database-index", "language": "en", "audiences": [ { "id": "age-5", "label": "Age 5", "file": "pages/age-5.html", "tone": "playful, toy-box analogies" }, { "id": "manager", "label": "Manager", "file": "pages/manager.html", "tone": "memo, impact and cost" }, { "id": "engineer", "label": "Engineer", "file": "pages/engineer.html", "tone": "precise, mechanics and trade-offs" } ] } ``` - `title` and `audiences` are required; `topic` and `language` are optional but worth filling — `language` tells you which language the pages are written in when you come back to the explainer in a later session. - `file` is **set-relative and includes the `pages/` prefix**. A bare filename does not resolve, and the rung renders empty. - `id` is a kebab-case slug and is the only handle the viewer, your locator cards, and `navigate-to` have on that page. Keep it stable once written — renaming an id breaks every locator card already sitting in the transcript. - `label` is what the user reads on the rung. Write it in the same language as the pages. - `tone` is a short note to your future self about the register you chose. It is not rendered; it is how a later session picks up the same voice. **Order in `audiences[]` is ladder order — simplest first, most technical last.** The whole point of the rail is that climbing it feels like climbing. Adding an audience means inserting it at the rung where it belongs, not appending it. Always write inside a content-set directory. A `pages/manager.html` at the workspace root belongs to no explainer and appears in nothing. ### Pages are whole documents, not slides Each page is a **complete, standalone HTML document** — ``, ``, `