# Report Slides

> Use when creating presentations and research reports, especially diagram-heavy decks with architecture, flowcharts, timelines, charts, conceptual illustrations, or editable PPTX output.

- Skill: `zi-yue-1129/report-slides` (Agent Skill, multi-file: 222 files)
- Install (CLI): `npx skillmds@latest add zi-yue-1129/report-slides`
- Raw SKILL.md: https://api.skillmd.com/api/skills/zi-yue-1129/report-slides/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- Author: zi-yue-1129 (https://skillmd.com/u/zi-yue-1129)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/zi-yue-1129/report-slides

---


# Report Slides

Generates a slide deck from research log entries using three source paths:
- **[A]** `generate_slides.py` — data-driven slides (charts, tables, metrics)
- **[B]** Mermaid (`mmdc`) — diagram slides (flowcharts, architectures, state machines)
- **[C]** Claude SVG — free-form slides (conceptual layouts, text-heavy content)

Every non-trivial visual goes through the mandatory visual-authoring gate below.
After generation, slides can optionally be exported as native editable PPTX shapes,
with SVG embedding retained as the backward-compatible fallback.

---

## Setup (first use in a project)

### Step 1 — Resolve directories (always first)

**Resolve the slides and research-log directories before anything else** — in
particular before the auto-setup check in Step 2, which creates directories
under `$SLIDES_DIR` (see `skills/resource-resolver/SKILL.md`):

```bash
# macOS / Linux / Git Bash:
RESOLVE="$(find ~/.claude -path "*/resource-resolver/scripts/resolve.py" | head -1)"
SLIDES_JSON=$(python "$RESOLVE" --role slides --json)
SLIDES_DIR=$(echo "$SLIDES_JSON" | python3 -c "
import json, sys
d = json.load(sys.stdin)
if d.get('status') == 'resolved':
    print(d['primary'])
")
LOG_JSON=$(python "$RESOLVE" --role research_log --json)
RESEARCH_LOG_DIR=$(echo "$LOG_JSON" | python3 -c "
import json, sys
d = json.load(sys.stdin)
if d.get('status') == 'resolved':
    print(d['primary'])
")
```

```powershell
# Windows (PowerShell):
$RESOLVE = (Get-ChildItem $env:USERPROFILE\.claude -Recurse -Filter resolve.py |
    Where-Object FullName -like "*resource-resolver*" | Select-Object -First 1).FullName
$SlidesJson = python $RESOLVE --role slides --json | ConvertFrom-Json
$SLIDES_DIR = if ($SlidesJson.status -eq "resolved") { $SlidesJson.primary } else { "" }
$LogJson = python $RESOLVE --role research_log --json | ConvertFrom-Json
$RESEARCH_LOG_DIR = if ($LogJson.status -eq "resolved") { $LogJson.primary } else { "" }
```

Only a `"resolved"` status yields a usable path — a `stale_mapping` response
also carries a non-empty `primary`, so never read `primary` without checking
`status`. If either variable comes back empty, inspect the corresponding JSON
(`$SLIDES_JSON` / `$LOG_JSON`) and follow the branching rules in the "Calling
convention for other skills" section of `skills/resource-resolver/SKILL.md`:
an `error` key means surface `message` and stop; `status: "stale_mapping"`
means the configured directory is gone and the user must re-confirm it (do not
silently recreate it); only `"unresolved"` / `"no_candidates"` leads to the
normal first-use confirmation flow.

**Do not run Step 2 until `$SLIDES_DIR` is a confirmed, non-empty path.**
Setup creates directories under it, and creating them from an unconfirmed or
empty value would write outside the intended project layout.

The rest of this file refers to the resolved slides directory as
`$SLIDES_DIR` and the resolved research log directory as `$RESEARCH_LOG_DIR`.

Shell state does not persist across separate tool-call invocations. When a
later section needs these paths in a new bash/PowerShell call, either re-run
the resolve snippet above in that same call, or substitute the already-resolved
path as a literal value into the command.

### Step 2 — Install project scripts

**macOS / Linux / Git Bash:**
```bash
bash "$(find ~/.claude -path "*/report-slides/scripts/setup.sh" | head -1)" "$SLIDES_DIR"
```

**Windows (PowerShell):**
```powershell
& (Get-ChildItem $env:USERPROFILE\.claude -Recurse -Filter setup.ps1 |
    Where-Object FullName -like "*report-slides*" | Select-Object -First 1).FullName $SLIDES_DIR
```

This copies `generate_slides.py`, `validate_diagram_manifest.py`, and `render_review_sheet.py` into `scripts/` and creates both `$SLIDES_DIR/reports/` and `$SLIDES_DIR/assets/diagrams/`. `to_pptx.py` stays in the skill bundle and is invoked directly from there.

**Auto-setup:** if you invoke `/report-slides` and `scripts/generate_slides.py` is missing, run the setup command above automatically before proceeding — no need to ask the user. This is only ever automatic *after* Step 1 has produced a confirmed `$SLIDES_DIR`; if the `slides` role is still unconfigured or stale, resolve and confirm it with the user first, because setup creates directories.

Check for Mermaid (optional, for diagram slides):
```bash
# macOS / Linux
which mmdc && echo "Mermaid OK" || echo "Mermaid missing (npm i -g @mermaid-js/mermaid-cli)"
# Windows
Get-Command mmdc -ErrorAction SilentlyContinue && "Mermaid OK" || "Mermaid missing (npm i -g @mermaid-js/mermaid-cli)"
```

---

## Style system

Slides inherit colors and fonts from a **style file** — a `.md` file with YAML frontmatter.
Three built-in styles ship with this skill: `default`, `minimal`, `dark`, `paper`.
Full schema and color role descriptions are in `references/styles/STYLES.md` (read it when resolving styles).

**Project default:** if `$SLIDES_DIR/_style.md` exists it is applied automatically to every deck.

### Design tokens (the machine contract)

Sizes, spacing, radii, connector geometry, contrast floors, and density budgets
come from a design-token file, not from style Markdown. The shipped default is
`references/tokens/default.tokens.yaml`; select another with `--tokens`.

```bash
# Validate a token file before use:
python3 "$(find ~/.claude -path "*/report-slides/scripts/validate_design_tokens.py" | head -1)" \
    --tokens <file>

# Render with a specific token file:
python3 scripts/generate_slides.py --tokens <file> --data <json> --out <dir> --deck-id <id>
```

Every ModuleSpec must name a token file in `style_tokens_ref`; `null` is rejected
and the path is resolved and validated at the gate.

`--style` and `--tokens` are composed before rendering, not applied in sequence
afterwards. The result is written to `<out>/_effective.tokens.yaml`, and that
file — not the file passed to `--tokens` — is what `$STYLE_TOKENS_REF` must
point at for the rest of the pipeline. A style file may set the value of a
colour role or the sans font family; a key naming no role is an error, because
the role names are the vocabulary every downstream check is written against.

### set-style \<name\>

Copy a built-in style as the project default (one command):

```bash
# macOS / Linux / Git Bash:
bash "$(find ~/.claude -path "*/report-slides/scripts/set-style.sh" | head -1)" <name>
# Windows (PowerShell):
& (Get-ChildItem $env:USERPROFILE\.claude -Recurse -Filter set-style.ps1 |
    Where-Object FullName -like "*report-slides*" | Select-Object -First 1).FullName <name>
# built-in names: default  minimal  dark  paper
```

To **create a custom style**: make `$SLIDES_DIR/styles/<name>.md` using the schema in
`references/styles/STYLES.md`, then copy it to `$SLIDES_DIR/_style.md` to activate it as the project default.

---

## Workflow

This is a 15-stage, approval-gated, multi-agent pipeline. `presentation_state.py`
is the state machine of record: every stage transition below is a call into it,
not a prose convention. Nothing that is not deterministic orchestrator logic is
performed by the orchestrator itself — planning, review, and visual authoring
are always dispatched to a named agent via the Task tool, and the orchestrator's
job is to create records, validate agent output, and gate transitions.

### Schema-v2 migration preflight

Run the presentation workflow from the project root. Before the first workflow
action for an existing presentation state, inspect the `version` or
`schema_version` header in every YAML file under
`.research/presentations/state/`. All existing state stores must have one
shared schema version; mixed, malformed, or unsupported headers are not safe to
infer from a single file.

The required operator flow is:

```text
inspect schema -> migrate-state --dry-run -> migrate-state -> workflow action
```

Use the existing migration entry point; it is the only state-migration command:

```bash
PROJECT_ROOT="$(git rev-parse --show-toplevel)"
MIGRATE="$(find ~/.claude -path "*/report-slides/scripts/migrate_presentation_state.py" | head -1)"

STATE_DIR="$PROJECT_ROOT/.research/presentations/state"
if [ -d "$STATE_DIR" ]; then
  rg -n --glob '*.yaml' '^(version|schema_version):' "$STATE_DIR"
else
  echo "No presentation state exists; continue with Stage 1."
fi

# Preview first: this creates no locks, directories, sidecars, journals,
# backups, CAS objects, or mtimes.
python3 "$MIGRATE" --project-root "$PROJECT_ROOT" --dry-run --json

# Apply only after reviewing the dry-run JSON report.
python3 "$MIGRATE" --project-root "$PROJECT_ROOT" --json
```

Schema 0 and schema 1 are read-only workflow states. Any workflow write before
migration fails with the structured JSON error `MigrationRequiredError` and
identifies its source and required target schema version. After successful
migration, schema 2 is the only workflow-write schema; re-running migration on
schema 2 is an exact no-op.

Schema-2 gates authorize only the immutable evidence envelope selected by the
current deck pointer and the corresponding verified CAS bytes. They never fall
back to a legacy event or a path-based assertion. Operational lock sidecars
coordinate access only: they are not workflow evidence. Historical envelopes
remain audit history, but an envelope marked `historical_unavailable` cannot
authorize a current gate. A targeted revision preserves prior immutable
evidence and clears the current preview, draft-approval, and completion
evidence pointers; create and validate fresh current evidence before the next
gated action.

### 1. Create the Deck

Orchestrator. After Setup, before asking the user anything, create the Deck
record so every later stage has a `$DECK_ID` to attach state to:

```bash
PSTATE="$(find ~/.claude -path "*/report-slides/scripts/presentation_state.py" | head -1)"
DECK_JSON=$(python3 "$PSTATE" --create-deck --title "<deck working title>" --json)
DECK_ID=$(echo "$DECK_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])")
```

`deck.status` starts at `planning`. Enforcement Mechanism (the exact rule from
the design spec): no SVG, PNG, PPTX, or manifest is ever written before
`deck.status` reaches `approved`, and this is enforced by
`--check-production-allowed` (Stage 14), not by prose discipline — a
production call against an unapproved deck fails closed.

### 2. Ask (one message)

Before asking, show the user what already exists to select from — reuse the
existing log-discovery step:

```bash
cat "$RESEARCH_LOG_DIR/INDEX.md" 2>/dev/null \
  || find "$RESEARCH_LOG_DIR" -maxdepth 1 -name "*.md" ! -name "INDEX.md" | sort -r | head -20
```

Show the user which entries exist and which have already been made into slide
decks. If no log files exist, tell the user to run `/research-log add` first
and stop.

**Academic data source (optional).** When the user passes `--source academic`
or selects "academic pipeline" as source in the question below:

1. Check for a passport YAML file (default: `docs/passport.yaml`; override with `--passport <path>`)
2. Run the bridge script to extract stage data:
   ```bash
   BRIDGE="$(find ~/.claude -path "*/research-lab-skills/bridge/scripts/passport_to_log.py" 2>/dev/null | head -1)"
   python3 "$BRIDGE" --passport docs/passport.yaml
   ```
3. Use the extracted stage records as input for slide generation instead of research-log entries.

If no passport file exists, fall back to research-log source and notify the user.

This is the existing questionnaire — copy it verbatim from the current file,
unchanged; it still runs before any planning:

1. Source? (`research-log` = experiment logs (default) / `academic` = pipeline passport data)
   If research-log: which logs? (`all` / `recent-N` / by name / date range)
   If academic: passport file path? (default: `docs/passport.yaml`)
2. Audience? (advisor / team meeting / conference)
3. Charts? (`list` = output paths to `chart_list.md` / `embed` = base64 into SVG)
4. Language? (follow log language / force English / force another language)
5. Emphasis? (progression / final results / failure analysis / let Claude decide)
6. Style? (skip = use `$SLIDES_DIR/_style.md` if present / name a built-in / `custom` to create one)

Read the selected log files (or the academic bridge output) and any
`CLAUDE.md` for project context and baselines. This resolved log/passport
content is exactly what Stage 3 receives.

### 3. Narrative planning

Dispatch `research_narrative_planner_agent` (Task tool) with: the resolved
log/passport content from Stage 2, `$DECK_ID`, and instructions to write
`.research/presentations/decks/$DECK_ID/plan.yaml` (a Deck Plan document per
the design spec §3 contract table) and return its path. Orchestrator then
runs:

```bash
DDP="$(find ~/.claude -path "*/report-slides/scripts/validate_deck_plan.py" | head -1)"
python3 "$DDP" --plan ".research/presentations/decks/$DECK_ID/plan.yaml" --json
```

A non-`valid` result is a bug in the agent's output, not a workflow state —
re-dispatch the same agent with the validator's errors, do not proceed. Once
valid, `deck.status: planning -> content_review`.

### 4. Content review

Dispatch `content_reviewer_agent` with the Deck Plan path. The agent returns a
Review Result (`subject_type: plan`, `subject_id: $DECK_ID`); orchestrator
writes it and validates:

```bash
python3 "$PSTATE" --record-review --subject-type plan --subject-id "$DECK_ID" \
    --reviewer-role content_reviewer --status <passed|failed> \
    --findings-json "$(cat review_result_findings.json)" --round 1 --json
VVR="$(find ~/.claude -path "*/report-slides/scripts/validate_visual_review.py" | head -1)"
python3 "$VVR" --review-result review_result.json
```

If `status: failed`, `deck.status` stays `content_review`; feed the findings
back into the Research Narrative Planner (Stage 3) as a Revision Request
(`--create-revision-request --subject-type plan --subject-id "$DECK_ID"
--requested-by reviewer --instructions "<findings summary>"`) and re-run
Stage 3-4 against a new `plan_version`. If `status: passed`, `deck.status:
content_review -> awaiting_approval`.

### 5. Approval gate

Orchestrator, interactive by default. Present the approved-by-content-review
Deck Plan to the user in the same style as the current outline-confirmation
prompt — a numbered slide list, one line per slide: title plus intended
visual type tag:

```
Proposed slide structure (N slides):

#01  Title                   [C]
#02  Background & Goal       [C: two_column]
#03  Experiment Timeline     [A: bullet_list] [V:DATA]
#04  Changes                 [A: bullet_list] [V:DATA]
#05  Results                 [A: bar_chart]   [V:DATA]
#06  Comparison              [A: table]       [V:DATA]
#07  Architecture            [C: native SVG]  [V:NATIVE]
#08  Conclusion & Next Steps [C: conclusion]

[A] Python  [B] Mermaid  [C] Claude SVG

Confirm? (say "ok" to proceed, or specify changes)
```

Wait for one of: `approve`, or a revision instruction (`revise a slide`, `add
a slide`, `remove a slide`, `reorder`, `change emphasis`, `change audience`,
`change duration`). A revision instruction becomes a Revision Request fed
back to Stage 3 exactly as in Stage 4's revise path (`deck.status:
awaiting_approval -> planning`), and the plan re-enters Stage 3-5. On
`approve`:

```bash
python3 "$PSTATE" --set-deck-status --deck-id "$DECK_ID" --status approved --json
```

Write `.research/presentations/decks/$DECK_ID/approval.yaml` (Deck Approval
document: `decision: approve`, `approved_by`, `approved_at`).

#### Non-interactive escape hatch (applies to Stages 1-5 as a whole)

If invoked with `--yes`, skip the interactive wait in Stage 5 only — Stages
3-4 (planning and content review) still run and must still pass — and
auto-approve with `approved_by: "auto (--yes)"`. If invoked with
`--approved-plan-file PATH`, skip Stages 3-4-5 entirely: validate the given
file with `validate_deck_plan.py --plan`, copy it to
`.research/presentations/decks/$DECK_ID/plan.yaml`, and go directly to
`deck.status: planning -> approved` with `approved_by: "pre-approved
(--approved-plan-file)"`. Without either flag, legacy single-message
invocation (today's default) still creates a Deck and passes through
`content_review -> awaiting_approval` and stops for the interactive gate —
this is the concrete mechanism satisfying "Legacy invocation must now enter
the approval workflow unless an explicit non-interactive option is
provided."

### 6. Slide specification

Before creating any Slide record, move the deck out of `approved` and into
production — `approved` only ever transitions to `producing` (or `blocked`),
and Stage 13's later `producing -> draft_review` transition depends on the
deck having reached `producing` here:

```bash
python3 "$PSTATE" --set-deck-status --deck-id "$DECK_ID" --status producing --json
```

For each `SlidePlanEntry` in the approved plan:
`python3 "$PSTATE" --create-slide --deck-id "$DECK_ID" --plan-slide-id
<slide-01> --title "<title>" --json`, then dispatch `slide_architect_agent`
with that slide's plan entry, returning a Slide Specification written to
`.research/presentations/decks/$DECK_ID/slides/<plan_slide_id>/spec.yaml`,
including the `complexity_signals` object (`region_count`, `route_count`,
`multi_stage`, `mixed_technique`, `heavy_cross_region_connections`,
`expected_reuse`, `not_atomic`). `slide.status: planned -> ready`.

### 7. Complexity detection

Orchestrator, deterministic, per slide:

```bash
CVD="$(find ~/.claude -path "*/report-slides/scripts/complex_visual_detector.py" | head -1)"
python3 "$CVD" --signals ".../spec.yaml#complexity_signals-as-json" --json
```

(Extract `complexity_signals` from the Slide Specification into a small JSON
file first — `complex_visual_detector.py` takes `--signals PATH` pointing at
a signals-only document, not the full spec.) The result's
`requires_complex_workflow` decides the branch: `false` → skip to Stage 9
using exactly today's `generate_slides.py`/agent-authored-SVG path for this
slide (Compatibility Criterion 2 — no module is created, the slide moves
`ready -> assigned -> producing -> review_required -> passed` directly
against its own single visual). `true` → continue to Stage 8.

### 8. Complex visual decomposition

Only for slides where Stage 7 returned `true`. Dispatch
`complex_visual_decomposer_agent` with the Slide Specification; it returns a
Complex Visual Specification (written to
`.research/presentations/decks/$DECK_ID/slides/<plan_slide_id>/visual_spec.yaml`).
Validate:

```bash
DVM="$(find ~/.claude -path "*/report-slides/scripts/validate_visual_module.py" | head -1)"
python3 "$DVM" --spec ".../visual_spec.yaml" --json
```

Then create one Visual Module record per `ModuleSpec`. `--dependencies` takes
the state store's own generated `id`s (e.g. `module_xxxxx`), never the
Complex Visual Specification's `module_key`s — `create_visual_module` raises
`VisualModuleNotFoundError` for anything not already a real record in the
store. This means:

- **Modules must be created in dependency order.** A `ModuleSpec` whose
  `dependencies` list is non-empty cannot be created until every module it
  depends on already exists as a record — topologically sort `modules` by
  `dependencies` before issuing any `--create-visual-module` call.
- **The orchestrator must maintain a `module_key -> generated id` mapping.**
  Each `--create-visual-module --json` call returns the new record's
  generated `id`; store it keyed by that `ModuleSpec`'s `id` (its
  `module_key`). Before creating a later module, translate its
  `ModuleSpec.dependencies` (module_keys) through this mapping into the
  corresponding generated ids and pass only those to `--dependencies`.

```bash
declare -A MODULE_ID_MAP  # module_key -> generated id, populated as each module is created
# For a module whose ModuleSpec.dependencies are module_keys already present in MODULE_ID_MAP:
RESOLVED_DEPS=()
for dep_key in "${MODULE_SPEC_DEPENDENCIES[@]}"; do
    RESOLVED_DEPS+=("${MODULE_ID_MAP[$dep_key]}")
done
MODULE_JSON=$(python3 "$PSTATE" --create-visual-module --slide-id "$SLIDE_ID" --module-key <module-id-from-spec> \
    --module-type <data_visualization|architecture|conceptual|annotation> \
    --dependencies "${RESOLVED_DEPS[@]}" --json)
MODULE_ID_MAP[<module-id-from-spec>]=$(echo "$MODULE_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])")
```

### 9. Module production

Every Visual Module created in Stage 8 starts at `planned`. The legal path to
`producing` is `planned -> ready -> assigned -> producing` — `planned ->
producing` is illegal and raises `ValueError` at runtime, so the orchestrator
must walk each intermediate state explicitly:

1. **`planned -> ready`.** Once Stage 8 finishes creating a module's record,
   transition it to `ready`: `python3 "$PSTATE" --set-module-status
   --module-id "$MODULE_ID" --status ready --json`.
2. For each Visual Module at `ready` whose dependencies are all `passed`
   (query with `--query --deck-id "$DECK_ID" --json` and inspect
   `visual_modules`), write its **Worker Assignment** document to
   `.research/presentations/decks/$DECK_ID/slides/<plan_slide_id>/modules/<module_key>/assignment.yaml`
   (fields: `module_id` the store's generated id, `worker_type` one of
   `data_visualization|architecture|conceptual|annotation`, `assigned_at`,
   `inputs_resolved: bool`, `blocker: str|null`), then validate it:
   ```bash
   DVM="$(find ~/.claude -path "*/report-slides/scripts/validate_visual_module.py" | head -1)"
   python3 "$DVM" --assignment ".../modules/<module_key>/assignment.yaml" --json
   ```
3. **`ready -> assigned`.** `python3 "$PSTATE" --set-module-status
   --module-id "$MODULE_ID" --status assigned --json`, then dispatch the
   matching worker agent by `module_type`: `data_visualization_worker_agent`,
   `architecture_diagram_worker_agent`, `conceptual_illustration_worker_agent`,
   or `annotation_worker_agent` — pass the Worker Assignment's path in the
   dispatch so the worker knows where to read its assignment from.
4. **`assigned -> producing`.** `python3 "$PSTATE" --set-module-status
   --module-id "$MODULE_ID" --status producing --json` — this call itself
   fails if a dependency is not yet `passed`, so it doubles as the readiness
   check.

Independent modules (no shared dependency) may be dispatched in the same turn
since each is a separate Task-tool call; a module whose dependency has not yet
reached `passed` stays at `ready` (or `blocked`) — it does not advance to
`assigned`/`producing` until that dependency resolves. Each worker writes its
module's own manifest via the mandatory visual-authoring gate below
(§9.1-9.4) — reused unchanged from the pre-redesign workflow per §5 of the
design spec, since each module is its own `diagram_id`-equivalent asset — and
returns; orchestrator sets `--status review_required`.

#### 9.1 Mandatory visual-authoring gate

Immediately after outline confirmation, before drawing or generating any visual,
run this ordered gate for every non-trivial visual, including charts and
conceptual illustrations:

1. **Plan:** create `diagram-plan.yaml` with one entry per visual.
2. **Discover:** search project `manifest.yaml` files by purpose, diagram type,
   and semantic regions before drawing.
3. **Classify:** select exactly one route: native, data, generative, or hybrid,
   and record its route tag.
4. **Reference:** load only the relevant references below for the selected route.
5. **Author:** create or modify a reusable source; resolve reuse, modification,
   or derivation identity before route-specific generation. **Tabular data and
   charts MUST be authored as `<g data-pptx-role="table"|"chart"
   data-pptx-source="..." data-pptx-bbox="x,y,w,h">` around the preview
   markup, never as hand-drawn grid rects or bars — see
   `references/diagram-patterns.md`. Any semantic diagram node (a box plus
   icon plus label, a timeline event, a legend entry) MUST be wrapped in
   `<g data-pptx-role="group" data-node-id="...">`. These are hard
   requirements enforced by `validate_native_objects.py` (step 8), not style
   preferences.**
6. **Render:** render both the subfigure and the complete slide to pixels.
7. **Review:** inspect both pixel renders with model vision, revise the source,
   and repeat the render/vision loop until both gates pass.
8. **Manifest:** validate the plan, each manifest, and the asset root:
   `python3 scripts/validate_diagram_manifest.py --plan <plan>`,
   `python3 scripts/validate_diagram_manifest.py --manifest <manifest>`, and
   `python3 scripts/validate_diagram_manifest.py --root <asset-root>`. Also
   run the native-object safety net against the slide's SVG directory:
   `python3 scripts/validate_native_objects.py --svg-dir <dir>` — a non-zero
   exit is a hard blocker; fix the missing marker and re-render.
9. **output-format branch — export, convert, and directly inspect, or mark
   not applicable:**

   ```
   if output_format is pptx:
       require statuses.svg_preview passed before export
       export the actual deck.pptx
       run `python3 scripts/validate_native_objects.py --pptx <deck.pptx>` as
           a hard blocker before validating package structure -- a non-zero
           exit means a table/chart/node pattern reached the PPTX without
           its native construct; fix the source marker or converter branch
           and re-export
       validate package structure into statuses.pptx_structure
       convert the actual deck.pptx with LibreOffice or an equivalent
           office renderer (never the source SVG)
       produce exactly one final PNG for every expected slide under
           rendered_png_paths
       send every final PNG path directly to model_vision as
           model_vision.inspected_paths
       record statuses.pptx_render
       allow completion only when statuses.svg_preview,
           statuses.pptx_structure, and statuses.pptx_render are all passed
   otherwise:
       record both statuses.pptx_structure and statuses.pptx_render as
           not_applicable with a non-empty reason
       use statuses.svg_preview as the final, authoritative visual gate
   ```

   `statuses.pptx_render` is the authoritative final visual gate for a PPTX
   deliverable: a source SVG preview and a passing `statuses.pptx_structure`
   never override it. Unavailable conversion or unavailable/partial direct
   final-PNG inspection is `blocked`, not `passed` — a source PNG, a review
   sheet, or the structure report cannot satisfy the missing
   `model_vision.inspected_paths` evidence. A `blocked` or `failed` status on
   any required gate sets `overall.completion_allowed` to `false`.

Missing rendering or model-vision review is a hard blocker: retain the failing
artifact, record the blocker, and do not mark the visual or deck complete.

#### 9.2 Visual routes and references

In `diagram-plan.yaml`, set the validator-facing `route` field. In
`manifest.yaml`, set the validator-facing `authoring_route` field. Both fields
must use exactly one enum value: `native`, `data`, `generative`, or `hybrid`.
The bracketed values below are display/report/outline tags, not validator enum
values:

| Display/report/outline tag | Route and default |
|---|---|
| `[V:NATIVE]` | Editable SVG shapes and connectors; default for architecture and flowcharts. |
| `[V:DATA]` | Deterministic data-driven SVG; default for timelines, statistical charts, and status/matrix views. |
| `[V:AI]` | Runtime-generated raster illustration for conceptual visuals when native shapes are not sufficient. |
| `[V:HYBRID]` | Runtime-generated raster base plus an editable SVG overlay for factual annotations and structure. |

Direct native SVG is the default for editable architecture and flow diagrams.
Mermaid is optional only when its output converts correctly; if conversion
loses editability, disclose that loss in the manifest and completion report.
Do not label an embedded or raster-only Mermaid result as `[V:NATIVE]`.

Read only the references needed for the selected route and gate:

- [diagram-workflow.md](references/diagram-workflow.md) — plans, manifests, identity, and completion records.
- [diagram-patterns.md](references/diagram-patterns.md) — route recipes and failure checks.
- [generative-visuals.md](references/generative-visuals.md) — runtime generation and reference edits.
- [visual-review.md](references/visual-review.md) — pixel rendering, vision review, and blockers.

#### 9.3 Generation, reuse, and reporting contract

- `[V:AI]` and `[V:HYBRID]` require runtime image generation for creation or
  editing. For an edit, provide the earlier asset to the image-generation
  capability and name every changed region and reason. Never substitute an
  arbitrary web image or unrelated redraw.
- Search manifests before authoring. Assets remain `reused` when unchanged,
  including for placement-only changes. If the core message and model stay the same, modify
  the same `diagram_id` with `based_on_revision` only for a content/layout
  revision. A changed core message or model derives a new ID with `derived_from`.
  Missing generation capability, an edit reference, rendering, vision, or
  factual input blocks the affected visual; do not silently fall back.
- The completion report records, for every visual: `diagram_type`, `slide`,
  `authoring_route` and route tag, `diagram_id`, action (`created`, `reused`,
  `modified`, or `derived`), reused source, changed regions with reasons,
  editability, remaining raster layers, and the rationale for each raster
  layer. It records the review evidence using the exact record field names —
  `statuses.svg_preview`, `statuses.pptx_structure`, `statuses.pptx_render`,
  each with `reviewed_by`, `inspected_paths`, `findings`, `revision_required`,
  and its review-round number — never the three gates collapsed into one
  status. For a PPTX deliverable, the `statuses.pptx_render` entry also
  carries `renderer.name`, `renderer.version`, `renderer.conversion_format`,
  `conversion_artifacts`, `rendered_png_paths`, `model_vision.inspected_paths`,
  and `visual_checks`; `model_vision.inspected_paths` must equal the converted
  PNG set named in `rendered_png_paths` — `comparison_reference_paths` are
  optional diagnostics only and never substitute for that set. `overall`
  reports `overall.authority` (`pptx-render` for PPTX output,
  `source-pixel` otherwise) and `overall.completion_allowed`; an open finding
  or an incomplete direct-inspection set keeps `completion_allowed` `false`.

#### 9.4 Response-facing contract

Fresh plans and completion responses must expose these required fields and
claims:

- **High-priority reuse scenario:** When the user asks whether to modify/redraw
  an existing visual, the answer **MUST** be a filled concrete record using
  `manifest_asset_search`, `slide`, `diagram_id`, `action`, `reused_source`,
  `based_on_revision`, `changes` with a numeric `bbox` expressed as
  `[x1, y1, x2, y2]` endpoint coordinates, `change`, and `reason`,
  and `delivery_summary`; do not answer with instructions such as `report
  <actual slide>`, angle-bracket placeholders, `[x,y]` placeholders, or a
  prose-only checklist. If no source or revision can actually be discovered,
  output an explicit blocker record instead of fabricating continuity. Planning
  examples may use the concrete representative record already shown.

- `regions`: Architecture and flow plans enumerate every named subfigure or
  semantic region in `diagram-plan.yaml` and repeat the same names in the
  response.
- `review`: State explicitly that each subfigure and the complete slide were
  rendered to pixels, inspected with model vision, and revised and re-rendered
  until passing; validation-status lists alone are insufficient. For a PPTX
  deliverable, additionally state that the actual `deck.pptx` was converted
  with LibreOffice (or an equivalent office renderer) and that every
  converted PNG under `rendered_png_paths` was directly inspected by
  `model_vision` before `statuses.pptx_render` was recorded — a source-pixel
  pass and a passing `statuses.pptx_structure` are not substitutes for that
  direct inspection.
- `generation` and `editability`: For `route: generative` /
  `authoring_route: generative` `[V:AI]` and `route: hybrid` /
  `authoring_route: hybrid` `[V:HYBRID]`, name `prompt.md` and report manifest
  `generation.prompt`, `generation.output`, and `generation.references`
  provenance, plus editability and any raster, embedding, or conversion-loss
  disclosures.
- Filled example: `manifest_asset_search: "found assets/diagram-library/human-review-flow.svg"; slide: 4; diagram_id: human-review-flow-v2; action: modified; reused_source: assets/diagram-library/human-review-flow.svg; based_on_revision: git:4b29f2a; changes: [{region: human-review-branch, bbox: [612, 184, 860, 316], change: "clarified approval branch", reason: "approval branch"}]; delivery_summary: "Modified slide 4 using the reused human-review flow asset."`
- `reuse.action`, `based_on_revision`, and `changes`: State that the
  asset-library/manifest search happened first. Same-core-message-and-model
  revisions use a concrete, non-placeholder `based_on_revision` value and
  list every changed `region` with its `bbox` and `reason`. Unchanged
  placement-only assets remain `reuse` in the plan and `reused` in the
  response; core-message or model changes use a new ID with `derived_from`.
  Reuse/change prompts must return a filled record (not instructions), with
  `manifest_asset_search` set to a found result or blocker and all of `slide`,
  `diagram_id`, `action`, `reused_source`, `based_on_revision`, `changes`
  (named `region`, numeric `bbox` in `[x1, y1, x2, y2]` endpoint-coordinate
  form, `change`, and `reason`), and
  `delivery_summary`.

##### Reuse/change response template

Every fresh execution answer must be a filled concrete record, not
instructions. Begin by stating the `manifest/asset-library search` result,
including the found source or blocker. Then include `slide`, `diagram_id`,
`action`, `reused_source`, and `based_on_revision`; list `changes` with a
named `region`, numeric `bbox`, `change`, and `reason` for each entry; finish
with a change-focused `delivery_summary`.
Every `bbox` is an endpoint-coordinate tuple `[x1, y1, x2, y2]` within the
1200x675 slide; never use `[x, y, width, height]`.

Planning examples may use clearly labeled representative concrete values.
Execution responses must use the discovered revision and never fabricate
continuity. An unavailable source or revision blocks completion. Placement-
only changes remain `reuse` in plans and `reused` in responses; a changed
core message or model still derives a new ID with `derived_from`.

```yaml
manifest_asset_search: "Found source via assets/manifest.yaml: assets/diagram-library/human-review-flow.svg"
slide: 4
diagram_id: human-review-flow-v2
action: modified
reused_source: assets/diagram-library/human-review-flow.svg
based_on_revision: "git:4b29f2a"
changes:
  - region: human-review-branch
    bbox: [612, 184, 860, 316]
    change: "Added the reviewer decision split."
    reason: "Make approval and rejection outcomes explicit."
  - region: failure-return-path
    bbox: [780, 356, 1084, 452]
    change: "Rerouted the return connector to the retry node."
    reason: "Show the actual failure recovery path."
delivery_summary: "Modified slide 4 from git:4b29f2a; changed two regions and preserved placement-only reuse."
```

**Dynamic inclusion rules:**
- Timeline: only with ≥2 entries linked via `follows:`
- Architecture: only if logs describe structural/model changes
- Failure slide: only if logs have content under `## Failures`
- Fewer slides (4–5) for single-entry logs; more for conference talks

### 10. Visual integration

Once every module for a slide is `review_required` or later, dispatch
`visual_integration_agent` with all of that slide's module manifests and the
Complex Visual Specification's `connections`/`layout`. It assembles the
integrated SVG and writes the integration manifest (`modules_ref` pointing at
the Complex Visual Specification, per §5 of the design spec).

**Visual-style gate (deterministic, blocking).** Before any reviewer is
dispatched for a slide, run the linter over that slide's authored SVG:

```bash
VVS="$(find ~/.claude -path "*/report-slides/scripts/validate_visual_style.py" | head -1)"
# $SLIDE_SVG is the integrated SVG this stage just wrote; $STYLE_TOKENS_REF is
# the `_effective.tokens.yaml` from §"Tokens and style", not the file passed to
# `--tokens` at generation time.
timeout 120 python3 "$VVS" \
  --svg "$SLIDE_SVG" --tokens "$STYLE_TOKENS_REF" --json \
  --record "$PROJECT_ROOT" --subject-type slide --subject-id "$SLIDE_ID"
```

The validator is run out of the installed skill bundle, like every other
validator in this file. It is not copied into the project by `setup.sh`: it
imports `visual_style/`, `fonts.py`, and `design_tokens.py` from beside itself,
and those resolve only in the bundle. Run it once per slide: `--subject-id`
names the slide the recorded result belongs to, so a single invocation covering
several slides would file every result under one of them.

The exit code is not the gate; the recorded result is. `assert_slide_passable`
refuses a slide with no lint evidence, with evidence older than the current SVG
or token file, or with outstanding hard errors — so re-running the linter after
every edit is not diligence, it is the only way the slide ever passes. It also
hashes the published SVG on disk and re-reads the recorded token file, so
overwriting either after the run refuses the slide rather than inheriting the
old result; a token file that has been moved or deleted is refused too, because
a check that cannot run is not a check that passed. Both are cleared by running
the linter again. Exit
code 1 also blocks the slide immediately: the module returns to
`revision_required` with the findings attached, and Stages 11–12 are not
entered.

This gate is deterministic and measures only what a ruler can settle — safe
area, overlap, spacing, type floors, contrast, palette conformance, connector
attachment and routing, component consistency, and slide load. It replaces no
human judgement; it removes from human judgement the defects that never needed
it.

Warnings do not block. They are handed to the art-direction reviewer, who must
answer each one by rule id in `linter_warnings_answered`; an `art_direction`
review that passes with a warning unanswered is refused.

### 11. Scientific review

Dispatch `scientific_visual_reviewer_agent` per slide (simple or integrated)
with the rendered visual and its manifest. Record the result:
`--record-review --subject-type slide --subject-id "$SLIDE_ID"
--reviewer-role scientific --status <passed|failed> ...`. `failed` → every
module still `producing`/`review_required` for that slide (or the simple
slide itself) moves to `revision_required`, a new Revision Request is created
(`--requested-by reviewer`), and only the affected module(s) re-enter
`producing` (Stage 9) — siblings stay `passed`, satisfying the
partial-regeneration requirement.

### 12. Visual review (two independent gates)

Dispatch `render_integrity_reviewer_agent` with `--reviewer-role
render_integrity`, same mechanics as Stage 11. It judges the rendered pixels
against the source and nothing else; the deterministic gate at the end of Stage
10 has already settled every measurable property.

Dispatch `art_direction_reviewer_agent` with `--reviewer-role art_direction`.
It judges composition, hierarchy, imagery, and whether the slide states its
claim, and it receives the linter's warnings as context.

All three reviews — scientific, render integrity, art direction — are
independent. A slide reaches `passed` only when all three pass; any one failing
triggers the `revision_required` path scoped to that reviewer's findings.

### 13. Draft review gate

Orchestrator, interactive. Once every slide/module for the deck is `passed`,
`

…(truncated)
