# Local Preview Frame Writer Pattern

Use this when a shorts automation pipeline already has `RenderManifest` and `LocalPreviewPlan` and needs the first visible local artifact without jumping straight to FFmpeg, ComfyUI, Wan, or platform delivery.

## Sequence

```text
RenderManifest
→ LocalPreviewPlan
→ PNG scene cards
→ FFmpeg concat plan
→ MP4 timing preview
```

Do not skip directly from manifest to MP4. First make a deterministic PNG-card writer so timing, captions, labels, scene purpose, asset mode, and motion instruction can be inspected as separate artifacts.

## Recommended module shape

- `LocalFrameWriteResult`
  - `script_id`
  - `output_dir`
  - `frame_paths`
  - `side_effects=("write_png",)`
  - `external_calls=()`
- `write_local_preview_frames(plan, output_dir=None) -> LocalFrameWriteResult`
  - validates the `LocalPreviewPlan`
  - writes one PNG per frame using Pillow
  - preserves frame order and deterministic file names
  - writes only to explicit/normalized output directories

## Test checks

Write tests first for:

- missing module/API RED
- valid PNG files are written and non-empty
- Pillow can open generated files
- dimensions match preview plan, e.g. `540x960`
- output names preserve frame order, e.g. `frame-01-first_frame_hook.png`
- invalid preview plans are rejected before writes
- unsafe output paths with `..` are rejected
- result explicitly declares `side_effects=("write_png",)` and `external_calls=()`

## Guardrails

- Keep generated PNGs out of Git; runtime preview output should live under `data/previews/<script_id>/` or temporary test directories.
- Use Pillow as an explicit runtime dependency, e.g. `Pillow>=10.0`.
- This boundary writes files but must not call FFmpeg, ComfyUI, Wan, Qwen-Image, Telegram, or platform APIs.
- The card content should come from structured manifest/preview fields, not prose inference.
- Render all captions/labels via the deterministic writer; do not bake generated AI text into source assets.
