ADR 0006: Jinja2 prompt templates and TOML layout specs¶
Status¶
Superseded by [ADR 0018](0018-shared-models-live-in-core.md) — 2026-09-25
Only the placement of the layout-spec model (templates/schema.py) was revised; it now lives in core/layout.py. Every other decision below stands. See ADR 0018.
Context¶
A template must express two things: the text prompt sent to an image provider (varies per video: title, part number, channel, user variables) and the layout of the deterministic overlay (font, position, colours, badge). Templates must be versioned so a resumed batch renders the same prompt, and thumbforge template render must show the exact prompt without calling a provider.
Decision¶
- A template is a pair of files with the same stem:
NAME.j2(prompt) andNAME.toml(layout spec). Builtin templates live insrc/thumbforge/templates/builtin/; user templates in<config_dir>/templates/. - Jinja2 renders prompts with
Environment(undefined=StrictUndefined, autoescape=False, trim_blocks=True, lstrip_blocks=True). Context variables:video(VideoMeta),playlist(PlaylistMeta | None),part(int | None),channel, andvarsfrom--var key=value. A missing variable raisesTemplateError(exit2). - TOML layout specs are validated by a Pydantic model in
templates/schema.py(title box, font, size range, alignment, colours, badge style, negative-space hint passed to the prompt). - Templates are stored immutably in the
templatetable withUNIQUE(name, version);spec_hash = sha256(prompt_template + canonical_json(layout_spec))feeds the idempotency key (ADR 0012). Editing a template createsversion + 1;NAME@VERSIONselects a version explicitly, bareNAMEmeans latest. - Builtin set:
bold-title,minimal,series-parts. - Commands:
template list|show|new|validate|import|render(PLAN.md§5.2).
Consequences¶
- Prompt text is fully reproducible from
(template version, video, part, vars), which makes the idempotency key meaningful. StrictUndefinedturns typos into immediate, testable errors instead of silently empty prompts.- Layout and prompt are decoupled, so the same layout can be reused with provider-specific prompts.
- Autoescape off means templates must not be rendered into HTML; prompts are plain text only.
Alternatives considered¶
- Python f-strings /
str.format— rejected: no conditionals or loops (needed for optional "Part N" and reference hints), no strict-undefined behaviour. - YAML for layout specs — rejected: ADR 0003 already standardises on TOML;
tomllibis stdlib. - Storing templates only as files (no DB rows) — rejected: a resumed run must find the exact prompt template even after the user edited the file; immutable versions in the DB give that guarantee.
- Mako — rejected: smaller ecosystem and no equivalent of
StrictUndefined.