mirror of
https://github.com/calesthio/OpenMontage.git
synced 2026-08-25 01:20:18 +08:00
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.
This commit is contained in:
@@ -141,16 +141,50 @@
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"minItems": 1
|
||||
},
|
||||
"scene_type": {
|
||||
"type": "string",
|
||||
"description": "Default Remotion cut.type this style composes scenes as, e.g. 'anime_scene'. See remotion-composer/SCENE_TYPES.md."
|
||||
},
|
||||
"multi_image_per_scene": {
|
||||
"type": "boolean",
|
||||
"description": "Whether a scene is built from several stills that crossfade (the anime_scene grammar) rather than a single image."
|
||||
},
|
||||
"images_per_scene": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"description": "How many stills to generate per scene when multi_image_per_scene is set."
|
||||
},
|
||||
"image_variation_guidance": {
|
||||
"type": "string",
|
||||
"description": "How the per-scene stills should differ from each other so the crossfade reads as motion."
|
||||
},
|
||||
"default_particles": {
|
||||
"type": "string",
|
||||
"description": "Default particle overlay for scenes in this style (maps to the renderer's particles prop)."
|
||||
},
|
||||
"default_particle_color": {
|
||||
"type": "string",
|
||||
"description": "Default particle color (maps to the renderer's particleColor prop)."
|
||||
},
|
||||
"default_vignette": {
|
||||
"type": "boolean",
|
||||
"description": "Whether scenes in this style get a vignette by default (maps to the renderer's vignette prop)."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"overlays": {
|
||||
"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`).",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"stat_card": { "$ref": "#/$defs/overlay_style" },
|
||||
"key_term": { "$ref": "#/$defs/overlay_style" },
|
||||
"code_block": { "$ref": "#/$defs/overlay_style" }
|
||||
"code_block": { "$ref": "#/$defs/overlay_style" },
|
||||
"section_title": { "$ref": "#/$defs/overlay_style" },
|
||||
"stat_reveal": { "$ref": "#/$defs/overlay_style" },
|
||||
"hero_title": { "$ref": "#/$defs/overlay_style" },
|
||||
"provider_chip": { "$ref": "#/$defs/overlay_style" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
@@ -236,7 +270,11 @@
|
||||
"text": { "type": "string" },
|
||||
"radius": { "type": "number" },
|
||||
"shadow": { "type": "string" },
|
||||
"highlight": { "type": "string" }
|
||||
"highlight": { "type": "string" },
|
||||
"accent": {
|
||||
"type": "string",
|
||||
"description": "Accent color for overlay types that render one (maps to the renderer's accentColor prop)."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
identity:
|
||||
name: "Anime Ghibli"
|
||||
category: anime-illustration
|
||||
category: custom
|
||||
mood: warm, whimsical, contemplative, magical
|
||||
pace: gentle
|
||||
pace: slow
|
||||
best_for: "Narrative animations, nature-themed stories, emotional storytelling, fantasy visuals, educational content with wonder"
|
||||
|
||||
visual_language:
|
||||
color_palette:
|
||||
primary: ["#2D5016", "#1B4332"] # Deep forest greens
|
||||
accent: ["#FFB347", "#FF6B9D"] # Warm golden, soft cherry blossom pink
|
||||
# Warm golden, soft cherry blossom pink, spirit glow (magical elements),
|
||||
# golden-hour light. The last two are referenced by name in consistency_anchors.
|
||||
accent: ["#FFB347", "#FF6B9D", "#A8E6CF", "#FFF3B0"]
|
||||
background: "#0A0A1A" # Deep night sky
|
||||
text: "#F5F0E8" # Warm parchment white
|
||||
muted: "#8B9A7E" # Mossy sage
|
||||
spirit_glow: "#A8E6CF" # Soft teal glow for magical elements
|
||||
golden_hour: "#FFF3B0" # Warm golden hour light
|
||||
composition: centered subjects, rule of thirds for landscapes, generous negative space, layered depth
|
||||
texture: soft watercolor edges, painterly brushstrokes, organic shapes, no hard geometric lines
|
||||
|
||||
|
||||
80
tests/styles/test_playbook_catalog.py
Normal file
80
tests/styles/test_playbook_catalog.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""Every shipped style playbook must actually load.
|
||||
|
||||
`list_playbooks()` advertises a playbook as selectable, `pipeline_defs/*.yaml`
|
||||
offer them by name, and `lib.checkpoint._validate_style_playbook` fails closed
|
||||
on anything `load_playbook()` rejects. So a playbook that is listed but does not
|
||||
validate is not a cosmetic problem — it blocks `init_project()`/`write_checkpoint()`
|
||||
outright, taking the whole pipeline down for that style.
|
||||
|
||||
These tests iterate the catalog rather than a hardcoded list, which is how
|
||||
`anime-ghibli` and `premium-minimalist` slipped through: the pre-existing
|
||||
coverage in tests/qa/test_07_playbook_intelligence.py names three playbooks
|
||||
that were the only ones in the repo when it was written.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from lib.checkpoint import init_project
|
||||
from styles.playbook_loader import list_playbooks, load_playbook
|
||||
|
||||
PLAYBOOK_NAMES = sorted(list_playbooks())
|
||||
|
||||
# The overlay types remotion-composer actually renders, from the `Overlay`
|
||||
# union in remotion-composer/src/Explainer.tsx. A playbook must be able to
|
||||
# style any of them.
|
||||
RENDERER_OVERLAY_TYPES = (
|
||||
"section_title",
|
||||
"stat_reveal",
|
||||
"hero_title",
|
||||
"provider_chip",
|
||||
)
|
||||
|
||||
|
||||
def test_catalog_is_not_empty() -> None:
|
||||
assert PLAYBOOK_NAMES, "list_playbooks() found no playbooks at all"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("name", PLAYBOOK_NAMES)
|
||||
def test_listed_playbook_loads_and_validates(name: str) -> None:
|
||||
"""A playbook offered by the catalog must pass its own schema."""
|
||||
playbook = load_playbook(name)
|
||||
assert playbook["identity"]["name"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("name", PLAYBOOK_NAMES)
|
||||
def test_listed_playbook_is_accepted_by_init_project(name: str, tmp_path) -> None:
|
||||
"""The checkpoint writer validates style_playbook fail-closed.
|
||||
|
||||
Regression: `anime-ghibli` was listed and offered by pipeline_defs/animation.yaml
|
||||
but raised CheckpointValidationError here, so no run using that style could
|
||||
write a checkpoint.
|
||||
"""
|
||||
init_project(
|
||||
f"catalog-{name}",
|
||||
title="Catalog probe",
|
||||
pipeline_type="animation",
|
||||
pipeline_dir=tmp_path,
|
||||
style_playbook=name,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("overlay_type", RENDERER_OVERLAY_TYPES)
|
||||
def test_schema_allows_every_renderer_overlay_type(overlay_type: str) -> None:
|
||||
"""The playbook schema's overlay vocabulary must track the renderer's.
|
||||
|
||||
Regression: the schema allowed only stat_card/key_term/code_block — none of
|
||||
the four overlay types Explainer renders — so styling `section_title` made
|
||||
the whole playbook invalid.
|
||||
"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
schema_path = (
|
||||
Path(__file__).resolve().parents[2]
|
||||
/ "schemas"
|
||||
/ "styles"
|
||||
/ "playbook.schema.json"
|
||||
)
|
||||
schema = json.loads(schema_path.read_text(encoding="utf-8"))
|
||||
allowed = schema["properties"]["overlays"]["properties"]
|
||||
assert overlay_type in allowed
|
||||
Reference in New Issue
Block a user