Skip to content

Commit 2f16b07

Browse files
committed
fix(styles): make the anime-ghibli playbook loadable again
`load_playbook("anime-ghibli")` raised a ValidationError, and since `lib.checkpoint._validate_style_playbook` fails closed, that took down every run using the style: init_project(..., style_playbook="anime-ghibli") CheckpointValidationError: Unknown or invalid style_playbook 'anime-ghibli'. Available playbooks: [..., 'anime-ghibli', ...] The playbook is listed by `list_playbooks()` and offered by name in `pipeline_defs/animation.yaml`, so the error even names it as an available alternative to itself. Four schema violations, repaired on whichever side the evidence pointed to — widen the schema where a shipped renderer feature backs the key, conform the playbook where it is only vocabulary drift: - `overlays.section_title` — the schema allowed stat_card/key_term/code_block and none of the four overlay types `Overlay` in Explainer.tsx actually renders. Added section_title/stat_reveal/hero_title/provider_chip, plus `accent` on overlay_style to match the renderer's accentColor prop. - `asset_generation` anime_scene knobs — scene_type, multi_image_per_scene, images_per_scene, image_variation_guidance, default_particles, default_particle_color, default_vignette all map onto real AnimeScene props. Added to the schema. - `identity.category: anime-illustration` and `pace: gentle` — no code reads either field for behavior, so the playbook now uses the canonical `custom` and `slow` rather than expanding the enums for one style. - `color_palette.spirit_glow` / `golden_hour` — ad-hoc named colors. Folded into the `accent` array; both hexes are preserved and still referenced by name in consistency_anchors. With this, `_build_theme_from_playbook("anime-ghibli")` returns the Ghibli palette instead of `{}`, so the style no longer silently falls back to the default dark theme at render time. Regression coverage iterates the catalog instead of a hardcoded list, which is how this slipped: tests/qa/test_07_playbook_intelligence.py names the three playbooks that existed when it was written, so anime-ghibli and premium-minimalist never had load coverage. The new tests assert that every playbook `list_playbooks()` advertises both validates and is accepted by `init_project`, and that the schema's overlay vocabulary tracks the renderer's. Verified: 6 of the new tests fail on the unfixed tree; full suite goes 964 -> 979 passed with no regressions.
1 parent 4eab34c commit 2f16b07

3 files changed

Lines changed: 125 additions & 7 deletions

File tree

schemas/styles/playbook.schema.json

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,16 +141,50 @@
141141
"type": "array",
142142
"items": { "type": "string" },
143143
"minItems": 1
144+
},
145+
"scene_type": {
146+
"type": "string",
147+
"description": "Default Remotion cut.type this style composes scenes as, e.g. 'anime_scene'. See remotion-composer/SCENE_TYPES.md."
148+
},
149+
"multi_image_per_scene": {
150+
"type": "boolean",
151+
"description": "Whether a scene is built from several stills that crossfade (the anime_scene grammar) rather than a single image."
152+
},
153+
"images_per_scene": {
154+
"type": "integer",
155+
"minimum": 1,
156+
"description": "How many stills to generate per scene when multi_image_per_scene is set."
157+
},
158+
"image_variation_guidance": {
159+
"type": "string",
160+
"description": "How the per-scene stills should differ from each other so the crossfade reads as motion."
161+
},
162+
"default_particles": {
163+
"type": "string",
164+
"description": "Default particle overlay for scenes in this style (maps to the renderer's particles prop)."
165+
},
166+
"default_particle_color": {
167+
"type": "string",
168+
"description": "Default particle color (maps to the renderer's particleColor prop)."
169+
},
170+
"default_vignette": {
171+
"type": "boolean",
172+
"description": "Whether scenes in this style get a vignette by default (maps to the renderer's vignette prop)."
144173
}
145174
},
146175
"additionalProperties": false
147176
},
148177
"overlays": {
178+
"description": "Per-overlay styling. Covers the in-scene overlay boxes plus the timed overlay types the Explainer composition renders (see remotion-composer/src/Explainer.tsx, `Overlay.type`).",
149179
"type": "object",
150180
"properties": {
151181
"stat_card": { "$ref": "#/$defs/overlay_style" },
152182
"key_term": { "$ref": "#/$defs/overlay_style" },
153-
"code_block": { "$ref": "#/$defs/overlay_style" }
183+
"code_block": { "$ref": "#/$defs/overlay_style" },
184+
"section_title": { "$ref": "#/$defs/overlay_style" },
185+
"stat_reveal": { "$ref": "#/$defs/overlay_style" },
186+
"hero_title": { "$ref": "#/$defs/overlay_style" },
187+
"provider_chip": { "$ref": "#/$defs/overlay_style" }
154188
},
155189
"additionalProperties": false
156190
},
@@ -236,7 +270,11 @@
236270
"text": { "type": "string" },
237271
"radius": { "type": "number" },
238272
"shadow": { "type": "string" },
239-
"highlight": { "type": "string" }
273+
"highlight": { "type": "string" },
274+
"accent": {
275+
"type": "string",
276+
"description": "Accent color for overlay types that render one (maps to the renderer's accentColor prop)."
277+
}
240278
},
241279
"additionalProperties": false
242280
},

styles/anime-ghibli.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
identity:
22
name: "Anime Ghibli"
3-
category: anime-illustration
3+
category: custom
44
mood: warm, whimsical, contemplative, magical
5-
pace: gentle
5+
pace: slow
66
best_for: "Narrative animations, nature-themed stories, emotional storytelling, fantasy visuals, educational content with wonder"
77

88
visual_language:
99
color_palette:
1010
primary: ["#2D5016", "#1B4332"] # Deep forest greens
11-
accent: ["#FFB347", "#FF6B9D"] # Warm golden, soft cherry blossom pink
11+
# Warm golden, soft cherry blossom pink, spirit glow (magical elements),
12+
# golden-hour light. The last two are referenced by name in consistency_anchors.
13+
accent: ["#FFB347", "#FF6B9D", "#A8E6CF", "#FFF3B0"]
1214
background: "#0A0A1A" # Deep night sky
1315
text: "#F5F0E8" # Warm parchment white
1416
muted: "#8B9A7E" # Mossy sage
15-
spirit_glow: "#A8E6CF" # Soft teal glow for magical elements
16-
golden_hour: "#FFF3B0" # Warm golden hour light
1717
composition: centered subjects, rule of thirds for landscapes, generous negative space, layered depth
1818
texture: soft watercolor edges, painterly brushstrokes, organic shapes, no hard geometric lines
1919

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"""Every shipped style playbook must actually load.
2+
3+
`list_playbooks()` advertises a playbook as selectable, `pipeline_defs/*.yaml`
4+
offer them by name, and `lib.checkpoint._validate_style_playbook` fails closed
5+
on anything `load_playbook()` rejects. So a playbook that is listed but does not
6+
validate is not a cosmetic problem — it blocks `init_project()`/`write_checkpoint()`
7+
outright, taking the whole pipeline down for that style.
8+
9+
These tests iterate the catalog rather than a hardcoded list, which is how
10+
`anime-ghibli` and `premium-minimalist` slipped through: the pre-existing
11+
coverage in tests/qa/test_07_playbook_intelligence.py names three playbooks
12+
that were the only ones in the repo when it was written.
13+
"""
14+
15+
import pytest
16+
17+
from lib.checkpoint import init_project
18+
from styles.playbook_loader import list_playbooks, load_playbook
19+
20+
PLAYBOOK_NAMES = sorted(list_playbooks())
21+
22+
# The overlay types remotion-composer actually renders, from the `Overlay`
23+
# union in remotion-composer/src/Explainer.tsx. A playbook must be able to
24+
# style any of them.
25+
RENDERER_OVERLAY_TYPES = (
26+
"section_title",
27+
"stat_reveal",
28+
"hero_title",
29+
"provider_chip",
30+
)
31+
32+
33+
def test_catalog_is_not_empty() -> None:
34+
assert PLAYBOOK_NAMES, "list_playbooks() found no playbooks at all"
35+
36+
37+
@pytest.mark.parametrize("name", PLAYBOOK_NAMES)
38+
def test_listed_playbook_loads_and_validates(name: str) -> None:
39+
"""A playbook offered by the catalog must pass its own schema."""
40+
playbook = load_playbook(name)
41+
assert playbook["identity"]["name"]
42+
43+
44+
@pytest.mark.parametrize("name", PLAYBOOK_NAMES)
45+
def test_listed_playbook_is_accepted_by_init_project(name: str, tmp_path) -> None:
46+
"""The checkpoint writer validates style_playbook fail-closed.
47+
48+
Regression: `anime-ghibli` was listed and offered by pipeline_defs/animation.yaml
49+
but raised CheckpointValidationError here, so no run using that style could
50+
write a checkpoint.
51+
"""
52+
init_project(
53+
f"catalog-{name}",
54+
title="Catalog probe",
55+
pipeline_type="animation",
56+
pipeline_dir=tmp_path,
57+
style_playbook=name,
58+
)
59+
60+
61+
@pytest.mark.parametrize("overlay_type", RENDERER_OVERLAY_TYPES)
62+
def test_schema_allows_every_renderer_overlay_type(overlay_type: str) -> None:
63+
"""The playbook schema's overlay vocabulary must track the renderer's.
64+
65+
Regression: the schema allowed only stat_card/key_term/code_block — none of
66+
the four overlay types Explainer renders — so styling `section_title` made
67+
the whole playbook invalid.
68+
"""
69+
import json
70+
from pathlib import Path
71+
72+
schema_path = (
73+
Path(__file__).resolve().parents[2]
74+
/ "schemas"
75+
/ "styles"
76+
/ "playbook.schema.json"
77+
)
78+
schema = json.loads(schema_path.read_text(encoding="utf-8"))
79+
allowed = schema["properties"]["overlays"]["properties"]
80+
assert overlay_type in allowed

0 commit comments

Comments
 (0)