| 1 | --- |
| 2 | description: Optional quality-gate stage for per-page rubric-based visual review. |
| 3 | --- |
| 4 | |
| 5 | # Visual Review Stage |
| 6 | |
| 7 | > Optional Generate-PPTX quality stage. Goal: reduce human iteration by letting AI subagents visually self-check each rendered slide against a fixed rubric and apply atomic position/spacing fixes. |
| 8 | > |
| 9 | > Reads `<project>/svg_output/<page>.svg` and a pre-rendered PNG of each slide, then either applies a fix or flags `needs_human`. When an installed/fused Style exists, it also reads that workspace's `Review Focus` as supplemental acceptance context. **Never touches** brand decisions, layout structure, or other files. |
| 10 | > |
| 11 | > This stage is **context-independent** — invokable in a fresh chat session with only `<project_path>` as input. No upstream conversation context required. |
| 12 | |
| 13 | ## Positioning |
| 14 | |
| 15 | This is an **optional auxiliary loop**, opt-in only. The [`generate-pptx`](../generate-pptx.md) Step 1–7 pipeline does not invoke it; trigger only when the user explicitly asks for a visual re-pass on the generated SVGs before export. |
| 16 | |
| 17 | **Token cost**: each batch subagent re-reads the rubric + `design_spec.md` + `spec_lock.md` + the short Style Review Focus when present, and processes K SVG+PNG pairs. For a 20-page deck with K=5, expect on the order of 100–150K additional input tokens on top of the main generation run. |
| 18 | |
| 19 | ## When to Run |
| 20 | |
| 21 | - Executor ([`generate-pptx`](../generate-pptx.md) Step 6) has finished all pages |
| 22 | - `svg_quality_checker.py` has passed |
| 23 | - Post-processing (`finalize_svg.py`, `svg_to_pptx.py`) has **not** yet run |
| 24 | - The user has explicitly requested visual review |
| 25 | |
| 26 | For decks containing data charts, run [`verify-charts`](./verify-charts.md) first — visual-review focuses on visual rhythm / collision / alignment, not chart coordinate math. |
| 27 | |
| 28 | ## When NOT to Run |
| 29 | |
| 30 | - The project has no `svg_output/<page>.svg` files yet — finish Executor first |
| 31 | - `svg_quality_checker.py` has not been run or has failed — fix static violations first |
| 32 | - User has already applied annotations via the `live-preview` stage and is in a fixed-edit loop — describe changes directly, do not re-trigger rubric |
| 33 | - The user has not asked for it — do not auto-invoke based on inferred model capability or deck size |
| 34 | |
| 35 | --- |
| 36 | |
| 37 | ## Prerequisites |
| 38 | |
| 39 | ```bash |
| 40 | # 1. playwright + chromium installed (the PNG renderer) |
| 41 | pip install playwright |
| 42 | python3 -m playwright install chromium |
| 43 | |
| 44 | # 2. live-preview server running for this project (provides inlined SVG fetch) |
| 45 | python3 skills/ppt-master/scripts/svg_editor/server.py <project_path> --no-browser |
| 46 | # (single instance per project — if it's already running, skip) |
| 47 | ``` |
| 48 | |
| 49 | The renderer (`visual_review.py`) does **not** auto-start the live-preview server. Without `--server-url`, it discovers the actual port from the target project's `live_preview/lock.json`; an explicit `--server-url` overrides discovery. In either case it validates `/api/health` against the resolved target project before rendering and rejects a server for another project. |
| 50 | |
| 51 | > **Why playwright, not cairosvg**: cairo's text API has no font-fallback chain, so CJK characters render as tofu boxes for any deck whose font-family list relies on system fallback (Microsoft YaHei / PingFang SC / etc.). Playwright drives a real chromium and produces output identical to what the live-preview browser shows — the only fidelity-preserving option for bilingual decks. |
| 52 | |
| 53 | --- |
| 54 | |
| 55 | ## Step 1 — Pre-render all PNGs |
| 56 | |
| 57 | ```bash |
| 58 | python3 skills/ppt-master/scripts/visual_review.py <project_path> |
| 59 | ``` |
| 60 | |
| 61 | This writes one PNG per page to `<project_path>/.preview/<page>.png` at 1280×720, with `<use data-icon>` inlined and `<image href>` resolved exactly as the live-preview browser sees them. Renders are serialized via a project-local file lock — safe to invoke concurrently. |
| 62 | |
| 63 | Exit codes: |
| 64 | |
| 65 | - `0` — all pages rendered |
| 66 | - `2` — live-preview server unreachable or serving a different project (start the target project's server per Prerequisites) |
| 67 | - `3` — playwright python / chromium not installed (or browser failed to launch) |
| 68 | - `4` — one or more page-level render failures (see stderr; partial output is on disk) |
| 69 | |
| 70 | If any page comes back with `"all_background": true` in the JSON summary, that page rendered to a blank surface — investigate before continuing (broken `<use>` reference, missing image asset, etc.). |
| 71 | |
| 72 | --- |
| 73 | |
| 74 | ## Step 2 — Spawn the review team |
| 75 | |
| 76 | Create a team and dispatch one orchestrator agent. The orchestrator partitions the N pages into batches of ≤ K pages (default **K = 5**) and spawns one subagent per batch **in parallel** (single message, `ceil(N/K)` parallel `Agent` calls). Each batch subagent reads the fixed inputs (rubric + `design_spec.md` + `spec_lock.md` + conditional Style Review Focus) **once**, then iterates over its assigned pages sequentially. |
| 77 | |
| 78 | Before dispatch, inspect `<project>/templates/design_spec.md`. If it declares `kind: style` or its fused provenance contains an active Style segment, read only `## VII. Review Focus` once and include those checks in every batch prompt. Otherwise pass no Style supplement. This lookup never triggers visual review; it runs only after the user has already activated this stage. The supplement cannot weaken the fixed rubric or widen edit permissions. |
| 79 | |
| 80 | ```text |
| 81 | TeamCreate(team_name="visual-review-<project>", agent_type="orchestrator") |
| 82 | Agent( |
| 83 | team_name="visual-review-<project>", |
| 84 | subagent_type="general-purpose", |
| 85 | name="orchestrator", |
| 86 | prompt=<orchestrator-prompt>, |
| 87 | ) |
| 88 | ``` |
| 89 | |
| 90 | The orchestrator prompt must be self-contained and is the **single** place where dispatch shape, batch size, and forbid lists are stated — the rubric (`references/visual-review.md`) defines the contract those prompts must satisfy. Required fields (all absolute paths): |
| 91 | |
| 92 | - `<project_path>` — project root |
| 93 | - Full page list with `page_role` per page (parse `<project>/design_spec.md` §IX outline; **fixed compatibility default**: if an existing `design_spec.md` lacks §IX, use `content` for every page and flag this in the final report; if `design_spec.md` itself is missing, restore it through [`failure-recovery.md`](../governance/failure-recovery.md) §3 before dispatch) |
| 94 | - Batch size `K` (default 5; raise to 10 for token-sensitive runs on large decks, lower to 3 for high-fidelity short decks — see rubric §6.1) |
| 95 | - Iteration budget per page (default 1; 2 only for high-stakes / final-cut runs — see [Appendix: Iteration loop](#appendix-iteration-loop-opt-in)) |
| 96 | - Path to the rubric: `skills/ppt-master/references/visual-review.md` |
| 97 | - Style Review Focus excerpt, only when the conditional lookup above found one; preserve its wording and source path |
| 98 | - Dispatch contract reference: rubric [§6](../../references/visual-review.md#6-dispatch--messaging-contract) (batched parallel spawn, self-contained prompts, mandatory `SendMessage` on idle, anonymous-name tolerance) |
| 99 | - Subagent forbid list: do not edit any other page, `design_spec.md`, `spec_lock.md`, `templates/design_spec.md`, `animations.json`, `image_prompts.json`, or `images/` |
| 100 | |
| 101 | **Host compatibility**: `TeamCreate` and `SendMessage` are Claude-Code-specific multi-agent primitives. On hosts without those primitives (Cursor, VS Code + Copilot, Codebuddy, etc.) the main agent processes batches sequentially — same partitioning, same per-batch prompts, no parallel dispatch. Token savings from shared fixed inputs still apply; wall-clock time grows roughly N/K-fold. |
| 102 | |
| 103 | --- |
| 104 | |
| 105 | ## Step 3 — Aggregate findings |
| 106 | |
| 107 | The orchestrator emits the aggregate Markdown table back to you (the main agent): |
| 108 | |
| 109 | ``` |
| 110 | | page | role | status | hard_hits | soft_hits | fixes_applied | needs_human_reason | |
| 111 | |------|------|--------|-----------|-----------|---------------|---------------------| |
| 112 | ``` |
| 113 | |
| 114 | Statuses: |
| 115 | |
| 116 | - `ok` — page passed clean, no fixes applied |
| 117 | - `fixed` — at least one fix applied, all Hard rules now pass |
| 118 | - `needs_human` — fix attempted but rolled back (rule §4.2), or rule violation requires brand/structure decision outside the rubric's scope |
| 119 | - `render_failed` — Iteration 0 PNG sanity failed (rare; usually means renderer / server issue) |
| 120 | - `prereq_failed` — static checker hadn't been run |
| 121 | |
| 122 | Plus a brand-token aggregate at `<project>/.review/brand_review.json` if any §1.1 escalations occurred — review this once at the end of the run, not per page. |
| 123 | |
| 124 | --- |
| 125 | |
| 126 | ## Step 4 — Decide next move |
| 127 | |
| 128 | For each row in the table: |
| 129 | |
| 130 | - `ok` / `fixed` — no action; the SVG has been updated in-place (originals are at `<project>/.review/backup/<page>.iter<N>.svg`) |
| 131 | - `needs_human` — read the page's JSON `needs_human_items[].suggested_fix_summary`, decide with the user whether to apply or defer |
| 132 | - `render_failed` — re-run `visual_review.py` for that page only (`--pages <token>`); if it persists, hand off to manual review |
| 133 | - `prereq_failed` — go back and run `svg_quality_checker.py` |
| 134 | |
| 135 | If `brand_review.json` is non-empty, that's a single decision applied across the deck (e.g., bump footer text color from `#6E7681` to `#8B949E` — one change, every page benefits). Do this once, then optionally re-run visual-review for the affected pages only. |
| 136 | |
| 137 | After the table is clean, continue to [`generate-pptx`](../generate-pptx.md) |
| 138 | Step 7. That authority owns the serial commands, gates, and success criteria for |
| 139 | post-processing and export. |
| 140 | |
| 141 | --- |
| 142 | |
| 143 | ## Notes & invariants |
| 144 | |
| 145 | - **Single source of truth for rules**: [`references/visual-review.md`](../../references/visual-review.md). This stage file is just the orchestration — never restate or paraphrase rules here. |
| 146 | - **Concurrency**: `visual_review.py` serializes renders via `<project>/.preview/.render.lock`. Subagents must never call the renderer directly without the lock. |
| 147 | - **Iteration budget**: default 1 iteration. Bumping to 2 doubles render cost and roughly triples token cost. Only worth it for high-stakes / final-cut decks. |
| 148 | - **Don't-touch (rubric §3)** is hard-enforced by subagents. If you want the subagent to e.g. change a brand color, that is **out of scope** — make the change manually first, then re-render & re-review. |
| 149 | - **Backups**: every modified SVG has a `.review/backup/<page>.iter<N>.svg` rollback anchor. Restore by `cp`. |
| 150 | - **The rubric is not the designer**: it catches collisions, drift, and rhythm errors — it does not improve a fundamentally weak layout. If 80%+ of pages come back `needs_human`, the Design Spec's pattern selection or Executor's realization geometry is the root cause, not this stage. |
| 151 | - **Playwright output discipline**: when an agent uses the playwright MCP tool `browser_take_screenshot` directly (outside the `visual_review.py` script), the `filename` parameter is resolved against the CWD (typically the repo root) — passing a bare relative path will create stray directories inside the repository. Always pass an absolute path: |
| 152 | - One-off probe / ad-hoc inspection → `/tmp/probe-<topic>-<n>.png` |
| 153 | - Project artifact (replaces what the script would have produced) → `<project_path>/.preview/<page>.png` (absolute) |
| 154 | - Never write to `<repo>/<anything>.png` or `<repo>/<some_dir>/...` — those are caught by `.gitignore` patterns but the cleanup burden is real |
| 155 | |
| 156 | The `visual_review.py` script handles output paths correctly on its own; this rule only applies to direct playwright MCP usage during interactive exploration or recovery. |
| 157 | |
| 158 | --- |
| 159 | |
| 160 | ## Appendix: Iteration loop (opt-in) |
| 161 | |
| 162 | Default behavior is single-iteration review: one scan, fix in place, write the report. The full iteration loop in [`references/visual-review.md`](../../references/visual-review.md) §4.1 supports: |
| 163 | |
| 164 | 1. Iteration 1: scan + fix |
| 165 | 2. Re-render via `visual_review.py --pages <token>` |
| 166 | 3. Iteration 2: re-verify changed elements + scan for new Hard hits |
| 167 | 4. Rollback on any new Hard hit introduced by a fix |
| 168 | |
| 169 | To enable, set iteration budget = 2 in the orchestrator prompt (this is a prompt-level instruction to subagents; neither `visual_review.py` nor the harness enforces it). Each added iteration roughly doubles render cost and triples token cost on the affected pages — reserve for final-cut runs only. |
| 170 |