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):
# 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'])
")
# 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 "$(find ~/.claude -path "*/report-slides/scripts/setup.sh" | head -1)" "$SLIDES_DIR"
Windows (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):
# 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.
# 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):
# 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:
inspect schema -> migrate-state --dry-run -> migrate-state -> workflow action
Use the existing migration entry point; it is the only state-migration command:
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:
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:
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:
- Check for a passport YAML file (default:
docs/passport.yaml; override with--passport <path>) - Run the bridge script to extract stage data:
BRIDGE="$(find ~/.claude -path "*/research-lab-skills/bridge/scripts/passport_to_log.py" 2>/dev/null | head -1)" python3 "$BRIDGE" --passport docs/passport.yaml - 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:
- 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) - Audience? (advisor / team meeting / conference)
- Charts? (
list= output paths tochart_list.md/embed= base64 into SVG) - Language? (follow log language / force English / force another language)
- Emphasis? (progression / final results / failure analysis / let Claude decide)
- Style? (skip = use
$SLIDES_DIR/_style.mdif present / name a built-in /customto 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:
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:
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:
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:
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:
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:
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 ids (e.g. module_xxxxx), never the
Complex Visual Specification's module_keys — 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
ModuleSpecwhosedependencieslist is non-empty cannot be created until every module it depends on already exists as a record — topologically sortmodulesbydependenciesbefore issuing any--create-visual-modulecall. - The orchestrator must maintain a
module_key -> generated idmapping. Each--create-visual-module --jsoncall returns the new record's generatedid; store it keyed by thatModuleSpec'sid(itsmodule_key). Before creating a later module, translate itsModuleSpec.dependencies(module_keys) through this mapping into the corresponding generated ids and pass only those to--dependencies.
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:
planned -> ready. Once Stage 8 finishes creating a module's record, transition it toready:python3 "$PSTATE" --set-module-status --module-id "$MODULE_ID" --status ready --json.- For each Visual Module at
readywhose dependencies are allpassed(query with--query --deck-id "$DECK_ID" --jsonand inspectvisual_modules), write its Worker Assignment document to.research/presentations/decks/$DECK_ID/slides/<plan_slide_id>/modules/<module_key>/assignment.yaml(fields:module_idthe store's generated id,worker_typeone ofdata_visualization|architecture|conceptual|annotation,assigned_at,inputs_resolved: bool,blocker: str|null), then validate it:DVM="$(find ~/.claude -path "*/report-slides/scripts/validate_visual_module.py" | head -1)" python3 "$DVM" --assignment ".../modules/<module_key>/assignment.yaml" --json ready -> assigned.python3 "$PSTATE" --set-module-status --module-id "$MODULE_ID" --status assigned --json, then dispatch the matching worker agent bymodule_type:data_visualization_worker_agent,architecture_diagram_worker_agent,conceptual_illustration_worker_agent, orannotation_worker_agent— pass the Worker Assignment's path in the dispatch so the worker knows where to read its assignment from.assigned -> producing.python3 "$PSTATE" --set-module-status --module-id "$MODULE_ID" --status producing --json— this call itself fails if a dependency is not yetpassed, 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:
Plan: create
diagram-plan.yamlwith one entry per visual.Discover: search project
manifest.yamlfiles by purpose, diagram type, and semantic regions before drawing.Classify: select exactly one route: native, data, generative, or hybrid, and record its route tag.
Reference: load only the relevant references below for the selected route.
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 — seereferences/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 byvalidate_native_objects.py(step 8), not style preferences.Render: render both the subfigure and the complete slide to pixels.
Review: inspect both pixel renders with model vision, revise the source, and repeat the render/vision loop until both gates pass.
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>, andpython3 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.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 gatestatuses.pptx_renderis the authoritative final visual gate for a PPTX deliverable: a source SVG preview and a passingstatuses.pptx_structurenever override it. Unavailable conversion or unavailable/partial direct final-PNG inspection isblocked, notpassed— a source PNG, a review sheet, or the structure report cannot satisfy the missingmodel_vision.inspected_pathsevidence. Ablockedorfailedstatus on any required gate setsoverall.completion_allowedtofalse.
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 — plans, manifests, identity, and completion records.
- diagram-patterns.md — route recipes and failure checks.
- generative-visuals.md — runtime generation and reference edits.
- 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
reusedwhen unchanged, including for placement-only changes. If the core message and model stay the same, modify the samediagram_idwithbased_on_revisiononly for a content/layout revision. A changed core message or model derives a new ID withderived_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_routeand route tag,diagram_id, action (created,reused,modified, orderived), 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 withreviewed_by,inspected_paths,findings,revision_required, and its review-round number — never the three gates collapsed into one status. For a PPTX deliverable, thestatuses.pptx_renderentry also carriesrenderer.name,renderer.version,renderer.conversion_format,conversion_artifacts,rendered_png_paths,model_vision.inspected_paths, andvisual_checks;model_vision.inspected_pathsmust equal the converted PNG set named inrendered_png_paths—comparison_reference_pathsare optional diagnostics only and never substitute for that set.overallreportsoverall.authority(pptx-renderfor PPTX output,source-pixelotherwise) andoverall.completion_allowed; an open finding or an incomplete direct-inspection set keepscompletion_allowedfalse.
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,changeswith a numericbboxexpressed as[x1, y1, x2, y2]endpoint coordinates,change, andreason, anddelivery_summary; do not answer with instructions such asreport <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 indiagram-plan.yamland 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 actualdeck.pptxwas converted with LibreOffice (or an equivalent office renderer) and that every converted PNG underrendered_png_pathswas directly inspected bymodel_visionbeforestatuses.pptx_renderwas recorded — a source-pixel pass and a passingstatuses.pptx_structureare not substitutes for that direct inspection.generationandeditability: Forroute: generative/authoring_route: generative[V:AI]androute: hybrid/authoring_route: hybrid[V:HYBRID], nameprompt.mdand report manifestgeneration.prompt,generation.output, andgeneration.referencesprovenance, 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, andchanges: State that the asset-library/manifest search happened first. Same-core-message-and-model revisions use a concrete, non-placeholderbased_on_revisionvalue and list every changedregionwith itsbboxandreason. Unchanged placement-only assets remainreusein the plan andreusedin the response; core-message or model changes use a new ID withderived_from. Reuse/change prompts must return a filled record (not instructions), withmanifest_asset_searchset to a found result or blocker and all ofslide,diagram_id,action,reused_source,based_on_revision,changes(namedregion, numericbboxin[x1, y1, x2, y2]endpoint-coordinate form,change, andreason), anddelivery_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.
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:
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)