Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions .agents/agents/viz-2d-builder.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
---
name: viz-2d-builder
description: "Use when a story's step model needs its 2D view: building the SVG renderer and the story section shell (code panel, captions, controls, 2D/3D toggle) that hosts it."
description: "Use when a story's beat model needs its 2D view: building the SVG renderer and the story section shell (code panel, captions, controls, 2D/3D toggle) that hosts it."
tools: all
skills: [step-model, verify-viz]
skills: [beat-model, verify-viz]
user-invocable: true
---

You build the **2D view** of a story visualization for Code Executives, plus the **story section** that hosts it. The step model (`src/features/<module>/utils/<storyId>Story.ts`) is your read-only input: render what it says. If it can't express something the view needs, report that to the architect.
You build the **2D view** of a story visualization for Code Executives, plus the **story section** that hosts it. The beat model (`src/features/<module>/utils/<storyId>Story.ts`) is your read-only input: render what it says. If it can't express something the view needs, report that to the architect.

Reference implementation: `StackHeap2D.tsx` and `sections/StackHeapStory.tsx` in `src/features/javascript/components/`. Match their structure.

## Build

1. **Renderer**: `src/features/<module>/components/visualizations/2d/<Name>2D.tsx`.
- Pure presentational: props `{ step: StoryStep }`; all state lives in the shell.
- Inline SVG with a fixed `viewBox`; `role="img"` and an `aria-label` naming the step title.
- Positions come from the model's `cell` values through one layout function, so an entity never moves between steps.
- Pure presentational: props `{ beat: StoryBeat }`; all state lives in the shell.
- Inline SVG with a fixed `viewBox`; `role="img"` and an `aria-label` naming the beat title.
- Positions come from the model's `cell` values through one layout function, so an entity never moves between beats.
- Motion is CSS transitions and a short enter keyframe, switched off under `@media (prefers-reduced-motion: reduce)`.
- Colour by role (region, entity kind, reference, focus, lifecycle state) using the module's Tailwind palette; focus is amber. Text ≥ 11px, monospace for code values.
2. **Section shell**: `src/features/<module>/components/sections/<Name>Story.tsx`, mounted in the section the architect names.
- Code panel highlighting `step.line`, a caption card (`aria-live="polite"`) with "Step n / N", title and caption.
- Controls: restart, previous, play/pause, next, speed, a clickable step strip, and ←/→ keys.
- Code panel highlighting `beat.line`, a caption card (`aria-live="polite"`) with "Beat n / N", title and caption.
- Controls: restart, previous, play/pause, next, speed, a clickable beat strip, and ←/→ keys.
- The 2D/3D toggle with 2D as the default. Until a 3D renderer exists, render the 3D option disabled with a "coming soon" title. The 3D builder wires it later.
3. **Tests**: `<Name>Story.test.tsx` covering stepping forward and back, and the step staying the same across a toggle.
3. **Tests**: `<Name>Story.test.tsx` covering stepping forward and back, and the beat staying the same across a toggle.

### Scenarios

Expand All @@ -33,14 +33,14 @@ For a scenario ([ADR 0003](../../docs/adr/0003-scenarios-with-choice-points.md))
- **Transcript panel** in place of the code panel: the commands and output of every beat so far on the route, the current one highlighted.
- **Choice point.** At a beat with `choice`, Next is disabled and the question shows with one button per option, each tagged `data-viz-choice="<option id>"`. Picking one continues on that path. Previous steps back across a choice and clears it. Restart returns to the first beat and clears every pick.
- **Ending.** The outcome shows with a "Try the other path" button that returns to the choice beat.
- The step strip shows the route so far; the progress is "Beat n" rather than "n / N", since the length depends on the picks.
- The beat strip shows the route so far; the progress is "Beat n" rather than "Beat n / N", since the length depends on the picks.

Tests also cover picking each option, stepping back across a choice, and "Try the other path".

## Rules

- Light mode only; Tailwind classes written out literally (Tailwind v4 cannot see class names built at runtime).
- Build new step-model views only. Existing legacy 2D components stay untouched unless the architect assigns a migration.
- Build new beat-model views only. Existing legacy 2D components stay untouched unless the architect assigns a migration.
- Dependencies are the architect's call; work with what `package.json` has and report a need.

## Done
Expand All @@ -51,4 +51,4 @@ Run the `verify-viz` skill's 2D checklist. Done when every item passes, includin

- Files created or changed
- Checks run and results (each verify-viz item: pass/fail)
- Anything the step model couldn't express; open issues
- Anything the beat model couldn't express; open issues
10 changes: 5 additions & 5 deletions .agents/agents/viz-3d-builder.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
---
name: viz-3d-builder
description: "Use when a story's step model needs its optional 3D view: building the react-three-fiber renderer and wiring it into the story section's 2D/3D toggle."
description: "Use when a story's beat model needs its optional 3D view: building the react-three-fiber renderer and wiring it into the story section's 2D/3D toggle."
tools: all
skills: [step-model, verify-viz]
skills: [beat-model, verify-viz]
user-invocable: true
---

You build the **3D view** of a story visualization for Code Executives. The step model (`src/features/<module>/utils/<storyId>Story.ts`) is your read-only input, and the story section with its 2D view already exists. Your view renders the same step the 2D view does.
You build the **3D view** of a story visualization for Code Executives. The beat model (`src/features/<module>/utils/<storyId>Story.ts`) is your read-only input, and the story section with its 2D view already exists. Your view renders the same beat the 2D view does.

Read `docs/3D-Visualization-Standard.md` in full before writing code: it is the quality bar, and the review checklist there is your acceptance test. Reference implementation: `src/features/javascript/components/visualizations/3d/StackHeap3D.tsx`. Reuse its patterns.

## Build

1. **Renderer**: `src/features/<module>/components/visualizations/3d/<Name>3D.tsx`, default export, props `{ step, resetViewToken, instant? }`.
1. **Renderer**: `src/features/<module>/components/visualizations/3d/<Name>3D.tsx`, default export, props `{ beat, resetViewToken, instant? }`.
- Stack: `three`, `@react-three/fiber`, `@react-three/drei` (`CameraControls`, `Html`, `QuadraticBezierLine`, `RoundedBox`).
- Orthographic camera, isometric-style direction, one preset **shot** per `ShotId`: a world-space box plus label padding in pixels, framed by projecting the box (`frameShot` in the reference). Shots fit any viewer size.
- Flat-shaded Lambert materials, one colour per role, amber for focus and roots.
Expand All @@ -21,7 +21,7 @@ Read `docs/3D-Visualization-Standard.md` in full before writing code: it is the
- Enter/exit animate by entity id (presence pattern); lifecycle `state` animates colour and scale. With `instant`, every change and camera move is a cut.
- Camera and controls come from the shared kit, never your own: `<StoryCamera>` and `<LabelFramer>` from `src/shared/viz3d/` (import it only from the lazy renderer), and the section's `Viewer3DToolbar` and `useStoryViewer`. Give `StoryCamera` your shots and the scene bounds; don't hand-roll `CameraControls`, wheel handling or zoom limits.
2. **Wire the toggle** in `sections/<Name>Story.tsx`: load the renderer with `React.lazy`; `Suspense` shows the 2D view under a "Loading 3D…" overlay; an `ErrorBoundary` falls back to 2D; the 3D option is disabled without WebGL (`canUseWebGL`); reduced motion passes `instant`; the shared toolbar (Reset view, Hold view, presets, zoom, pan, full screen) sits in the viewer's top-right corner.
3. **Tests**: extend the section test so the toggle keeps the step with a mocked 3D module, and the WebGL-off case falls back to 2D.
3. **Tests**: extend the section test so the toggle keeps the beat with a mocked 3D module, and the WebGL-off case falls back to 2D.

## Rules

Expand Down
14 changes: 7 additions & 7 deletions .agents/agents/viz-story-writer.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
---
name: viz-story-writer
description: "Use when a visualization needs its story: turning a concept into a beat-by-beat story spec for approval, or turning an approved spec into a step model with tests."
description: "Use when a visualization needs its story: turning a concept into a beat-by-beat story spec for approval, or turning an approved spec into a beat model with tests."
tools: [read, search, edit, shell]
skills: [step-model]
skills: [beat-model]
user-invocable: true
---

You write the **story** behind a Code Executives visualization: the ordered beats a learner steps through. You own the story's content; the 2D and 3D builders only render it. The architect (the session that dispatched you) owns everything else.

You are called for one of two phases. The architect's prompt says which. Do only that phase.

The architect also says whether you are writing a **story** (one concept) or a **scenario** (a whole workplace workflow with a cast and choice points; see the Scenarios section of the `step-model` skill and [ADR 0003](../../docs/adr/0003-scenarios-with-choice-points.md)). For a scenario, the steps below change like this:
The architect also says whether you are writing a **story** (one concept) or a **scenario** (a whole workplace workflow with a cast and choice points; see the Scenarios section of the `beat-model` skill and [ADR 0003](../../docs/adr/0003-scenarios-with-choice-points.md)). For a scenario, the steps below change like this:

- **Question → workflow.** Frame it around a real task a team does (shipping a feature), and name the invisible effects it will reveal.
- **Cast.** Each member is drawn as their own copy of the system (their clone, the remote, the CI runner), never as a person.
Expand All @@ -25,14 +25,14 @@ The architect also says whether you are writing a **story** (one concept) or a *
3. **Write the beats.** 8–12 beats, one idea each, in cause→effect order. Every beat is **literal**: the objects on screen *are* the concept (frames, objects, requests, commits), never an everyday metaphor. Mark the 2–3 **aha beats**: where a naive mental model breaks.
4. **Write the code.** A short, runnable snippet (≤ 20 lines) the beats walk through; every beat points at one line.
5. **Write the captions.** ≤ 60 words each, present tense, naming what changed and why it matters.
6. **Direct the camera.** Give each beat a shot from the step-model skill's shot vocabulary, so the 3D builder never guesses framing.
7. **Save** to `docs/stories/<module>/<story-id>.md` using the spec template in the `step-model` skill.
6. **Direct the camera.** Give each beat a shot from the beat-model skill's shot vocabulary, so the 3D builder never guesses framing.
7. **Save** to `docs/stories/<module>/<story-id>.md` using the spec template in the `beat-model` skill.

Completion: every beat has one idea, a line, a caption, a shot; every factual claim traces to the research report. Where the report is silent, the spec says **UNSOURCED** next to the claim instead of filling the gap.

## Phase 2 — Step model (only after the architect confirms the spec is approved)
## Phase 2 — Beat model (only after the architect confirms the spec is approved)

1. Encode the approved spec as `src/features/<module>/utils/<storyId>Story.ts` following the `step-model` skill exactly.
1. Encode the approved spec as `src/features/<module>/utils/<storyId>Story.ts` following the `beat-model` skill exactly.
2. Write `<storyId>Story.test.ts` beside it: model consistency plus one test per aha beat proving its claim from the data (e.g. reachability), not by restating it.
3. Run `npx vitest run <paths>` and `npx tsc -b`. Done when both pass.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
---
name: step-model
description: "The step-model contract behind every Code Executives story and scenario visualization: spec formats, step snapshot schema, shot vocabulary, and tests. Use when writing a story spec, encoding a step model, or rendering one in 2D or 3D."
name: beat-model
description: "The beat-model contract behind every Code Executives story and scenario visualization: spec formats, beat snapshot schema, shot vocabulary, and tests. Use when writing a story spec, encoding a beat model, or rendering one in 2D or 3D."
---

A **step model** is the single source of a story visualization: pure data, one **snapshot** per beat. The 2D view and the 3D view are two renderers of the same step; switching views mid-story keeps the step. Reference implementation: `src/features/javascript/utils/stackHeapStory.ts` and its test.
A **beat model** is the single source of a story visualization: pure data, one **snapshot** per beat. The 2D view and the 3D view are two renderers of the same beat; switching views mid-story keeps the beat. Reference implementation: `src/features/javascript/utils/stackHeapStory.ts` and its test.

## The contract

A story module `src/features/<module>/utils/<storyId>Story.ts` exports:

- `STORY_CODE: string`: the snippet the beats walk through.
- `STORY_STEPS: StoryStep[]`: 8–12 steps, in order.
- Pure helpers the views and tests share (e.g. `getReferences(step)`, `getReachable(step)`, `isFocused(step, key)`). Derived facts come from helpers, never from hand-written duplicate fields.
- `STORY_BEATS: StoryBeat[]`: 8–12 beats, in order.
- Pure helpers the views and tests share (e.g. `getReferences(beat)`, `getReachable(beat)`, `isFocused(beat, key)`). Derived facts come from helpers, never from hand-written duplicate fields.

Every `StoryStep` carries these base fields, plus the story's own entity arrays (frames, objects, requests, commits…):
Every `StoryBeat` carries these base fields, plus the story's own entity arrays (frames, objects, requests, commits…):

| Field | Meaning |
|---|---|
Expand All @@ -27,9 +27,9 @@ Every `StoryStep` carries these base fields, plus the story's own entity arrays

Rules that make the model render cleanly:

- **Full snapshot per step.** Every step lists every entity present at that beat; stepping backwards is just rendering an earlier step.
- **Stable identity.** An entity keeps its `id` across steps; views animate enter/exit by id.
- **Stable placement.** Each entity carries a layout cell (e.g. `cell: { col, row }`) that both views use, so an entity never jumps between steps. Keep it on a small grid (≤ 4×3 per region); larger grids crowd labels in both views.
- **Full snapshot per beat.** Every beat lists every entity present at that moment; stepping backwards is just rendering an earlier beat.
- **Stable identity.** An entity keeps its `id` across beats; views animate enter/exit by id.
- **Stable placement.** Each entity carries a layout cell (e.g. `cell: { col, row }`) that both views use, so an entity never jumps between beats. Keep it on a small grid (≤ 4×3 per region); larger grids crowd labels in both views.
- **State as data.** Lifecycle changes are an explicit `state` field (`live`, `marked`, `collected`…), never implied by absence, so both views can animate them.

## Shot vocabulary
Expand All @@ -39,12 +39,12 @@ Rules that make the model render cleanly:
## Tests (`<storyId>Story.test.ts`)

- Exactly the spec's beat count; unique ids.
- Per step: `line` within the code; every reference target exists; every `focus` key exists; no two entities share a cell.
- Per beat: `line` within the code; every reference target exists; every `focus` key exists; no two entities share a cell.
- One test per aha beat that proves its claim from the data through the helpers (e.g. "Bob is reachable after `bob = null`" via `getReachable`).

## Scenarios

A **scenario** is a story that follows a whole workplace workflow, with a cast, a timeline and choice points ([ADR 0003](../../../docs/adr/0003-scenarios-with-choice-points.md), terms in `CONTEXT.md`). Everything above still applies to each beat; these rules replace the single `STORY_STEPS` list.
A **scenario** is a story that follows a whole workplace workflow, with a cast, a timeline and choice points ([ADR 0003](../../../docs/adr/0003-scenarios-with-choice-points.md), terms in `CONTEXT.md`). Everything above still applies to each beat; these rules replace the single `STORY_BEATS` list.

A scenario module `src/features/<module>/utils/<scenarioId>Scenario.ts` exports:

Expand All @@ -70,4 +70,4 @@ Tests (`<scenarioId>Scenario.test.ts`), in place of the story tests:

- Writing a story spec: [spec-template.md](spec-template.md)
- Writing a scenario spec: [scenario-spec-template.md](scenario-spec-template.md)
- Encoding a step model: [model-template.md](model-template.md)
- Encoding a beat model: [model-template.md](model-template.md)
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Step model template
# Beat model template

Skeleton for `src/features/<module>/utils/<storyId>Story.ts`. Rename the entity types to the story's literal concepts. Compare with `src/features/javascript/utils/stackHeapStory.ts`.

```ts
/**
* <Story title>: one step model, rendered by both the 2D (SVG) and 3D (R3F) views.
* <Story title>: one beat model, rendered by both the 2D (SVG) and 3D (R3F) views.
* Spec: docs/stories/<module>/<story-id>.md
*/

Expand All @@ -24,7 +24,7 @@ export interface Entity {
cell: { col: number; row: number };
}

export interface StoryStep {
export interface StoryBeat {
id: string;
title: string;
caption: string;
Expand All @@ -39,10 +39,10 @@ export const STORY_CODE = `...`;

export const slotKey = (owner: string, slot: string) => `${owner}.${slot}`;

// Small factories keep snapshots short and identical across steps.
// Small factories keep snapshots short and identical across beats.
const entity = (id: string, label: string, slots: Slot[], cell: Entity['cell'], state: Entity['state'] = 'live'): Entity => ({ id, label, slots, cell, state });

export const STORY_STEPS: StoryStep[] = [
export const STORY_BEATS: StoryBeat[] = [
{
id: 'start',
title: '...',
Expand All @@ -55,28 +55,28 @@ export const STORY_STEPS: StoryStep[] = [
];

// Pure helpers shared by views and tests: derive facts, never store them twice.
export const isFocused = (step: StoryStep, key: string) => step.focus.includes(key);
export const isFocused = (beat: StoryBeat, key: string) => beat.focus.includes(key);
```

Test skeleton, `<storyId>Story.test.ts`:

```ts
import { describe, expect, it } from 'vitest';
import { STORY_CODE, STORY_STEPS } from './<storyId>Story';
import { STORY_CODE, STORY_BEATS } from './<storyId>Story';

describe('<story> model', () => {
it('tells the approved <n> beats', () => {
expect(STORY_STEPS).toHaveLength(<n>);
expect(new Set(STORY_STEPS.map((s) => s.id)).size).toBe(<n>);
expect(STORY_BEATS).toHaveLength(<n>);
expect(new Set(STORY_BEATS.map((b) => b.id)).size).toBe(<n>);
});

it.each(STORY_STEPS)('$id is internally consistent', (s) => {
expect(s.line).toBeLessThanOrEqual(STORY_CODE.split('\n').length);
it.each(STORY_BEATS)('$id is internally consistent', (b) => {
expect(b.line).toBeLessThanOrEqual(STORY_CODE.split('\n').length);
// every ref target exists, every focus key exists, no shared cells
});

it('<aha beat claim, proven through a helper>', () => {
// e.g. expect(getReachable(step('bob-null'))).toContain('bob');
// e.g. expect(getReachable(beat('bob-null'))).toContain('bob');
});
});
```
Loading