0034 — Studio project settings (themes · avatars) + the Reel tab¶
Status: IMPLEMENTED (design signed off with Matt 2026-07-18; all four phases landed).
Phase 1 backend (schema, themes/avatars CRUD, ref-upload + analyse, bundle-asset + article
render); phase 2 Project Settings UI (Themes + Avatars + Advanced); phase 3 Reel tab two
layouts (linked article + cast, unassigned relink); phase 4 Scenes per-scene avatar
checklist wired to Compose (the studio card generator now directs the cast, unified with
the CLI path).
Date: 2026-07-18
Relates: 0033 (post-first reels — the rail/picker this builds on), 0029 §9
(4-tab IA — the Reel tab is tab 1), 0006 (avatar + theme model — the assets this
surfaces), 0001 §6 (themes are config-driven), 0028 (component decomposition).
Supersedes: nothing — surfaces existing models in the studio.
1. The gap¶
The studio is now post-first (0033), but a reel leans on two project-level asset sets that the studio can't manage:
- Themes — the aesthetic profile (palette, image style, music tone, voice). The
studio can list them (
GET /themes) and pick one per reel, but not create / edit / remove; that's CLI-only (keryx theme add/edit/rm). - Avatars — the reel's characters. These exist as a config-driven, cast-capable
model (
internal/avatar) with a CLI (keryx avatar add/list/show), but are entirely invisible in the studio — no management, no selection.
And the Reel tab itself (4-tab IA, tab 1) is a thin form today: theme picker + a raw "Associated content" panel (source paste, a free-text bundle path, cover, portrait). It doesn't reflect the post-first world — the reel is linked to a post now, so the tab should present that post and configure the reel against the project's assets.
2. Key realisation — the models already exist¶
This is about studio surfaces, not new models (much like 0033).
- Avatars (
internal/avatar/avatar.go):Avatar{Ref, Likeness, Style, Palette, Postamble}stored under configavatars.<name>.Compose(cast []Named, scene, themeStyle)already renders a prompt for multiple avatars in one scene — so per-scene multi-avatar selection has a real backend. First-use analysis (spec 0006 §4) extracts Likeness/Style/Palette from a reference photo; the CLIavatar add <name> <image>already drives it. - Themes:
theme.Catalog, per-project, config-driven; studioGET /themes+ per-reelPUT /workspace/{slug}/themealready exist. Editing is the missing verb. - Post content (0033): the content source reads a post's frontmatter; reading its body and rendering markdown → HTML (goldmark, already a dependency) gives the Reel tab's rendered-article view.
So the work is: studio management UIs + a few CRUD endpoints over the existing catalogs, then wire the Reel/Scene tabs to consume them.
3. Scope¶
3.1 Project Settings — manage the reusable assets (build first)¶
A studio settings area (reached from the menu, as today) gains proper managers:
- Themes manager — list · add · edit · remove, over the theme catalog. Fields per
the catalog's theme shape (palette, image style, music, voice,
type). - Avatars manager — list · add · edit · remove, over
avatars.<name>. Add = upload a reference image → first-use analysis (spec 0006, a vision spend) → the extracted Likeness/Style/Palette shown editable. This is where the blog registers Matt and Hailey.
Backend: studio CRUD endpoints over the existing catalog/registry (write back to the
project's .keryx.yaml via the config layer, the same file the CLI edits); a ref-image
upload + an "analyse" action reusing the CLI's analysis path.
3.2 The Reel tab — two layouts¶
- Linked reel (has a
bundle) → two columns: - Left: reel config — theme picker, the reel's avatar cast (which project avatars this reel uses), source, cover — plus the linked post's frontmatter (title, date, taxonomy) as a read-out.
- Right: the rendered article — the post's markdown body rendered to HTML, so the reel author sees the source material beside the config.
- Unassigned reel (no
bundle) → one column: the same config, plus a "link to a post" action that opens the 0033 picker (relink mode).
The old free-text "Content bundle (directory)" input is replaced by a "Linked to:
3.3 Scenes — per-scene avatar selection¶
The Scenes card-config Avatars stub (0033) becomes real: pick which avatars appear on
each scene, drawn from the reel's cast / the project's registry. Feeds Compose(cast,
scene, themeStyle) for illustration generation.
4. Decisions (locked 2026-07-18)¶
| # | Decision | Resolution |
|---|---|---|
| D1 | Studio-only? | Yes. CLI/model untouched; the studio adds a management surface over the same config (mirrors 0033 D1). |
| D2 | Settings container | Menu-reached Settings overlay, COMPLETELY restructured into purpose-built Themes + Avatars managers + an Advanced (raw config) section — not the current key/value dump. The per-reel pipeline tabs stay clean; project-level assets live in Settings. |
| D3 | Avatar creation | Full AI-analysis flow: upload a reference photo → analyse (a spend-guarded async job reusing the CLI's analysis engine) → extracted Likeness/Style/Palette come back editable → save. Manual entry also allowed. |
| D4 | Avatar selection | Reel cast + per-scene checklist. The reel selects its cast (workspace.yaml → avatars: [...]); each scene shows that cast as a checklist (tick who appears; empty/absent = whole cast) → per-card avatars: [...]. Feeds Compose(cast, scene, themeStyle). No giant per-scene picker. |
| D5 | Rendered article | Clean, studio-typeset goldmark render of the post's markdown body (see Q4). |
5. Resolved questions (2026-07-18)¶
- Q1 — Theme editor depth → B (curated + escape hatch). First-class UI for the fields actually tweaked — palette (swatches), image prompt, music prompt, voice pick — with the fiddly/nested bits (voice stability/similarity, card internals, aspect) behind an Advanced panel (raw YAML of that theme). Iterate after field-testing.
- Q2 — Cast location → folded into D4. Reel cast =
workspace.yaml avatars: [...]; per-scene subset = cardavatars: [...]. - Q3 — Config write-back → A now (reuse), B spec'd separately. The studio owns the
write, reusing the existing whole-file viper config write (same path as the CLI); the
.keryx.yamlwatcher (R-CFG-3) reconciles concurrent CLI/hand edits by reload. Comment/format stomping is the pre-existing status quo of that path — the structure-preserving refactor (CLI + studio together) is spec 0035, deferred. - Q4 — Rendered article → A (clean studio-typeset). Blog-accurate styling is rejected (a brittle rule-set chasing the live site, and it would couple keryx to a consumer's CSS — wrong layer). Include inline body images (via the bundle-asset endpoint, §5.1); exclude the cover (that's separate reel config).
- Q5 — Avatar refs → A. Uploads saved to a top-level
avatars/<name>.<ext>dir (parallel toreels/, git-friendly, portable);Ref= that project-relative path. Thumbnails served via the same bundle-asset endpoint (§5.1). The CLI keeps its reference-in-place behaviour (unchanged; a future tidy could align it).
5.1 Data-model & surface deltas (from the decisions)¶
workspace.Meta: addAvatars []string(the reel's cast) — additive/optional.- Storyboard card: add
avatars: [...](per-scene subset; empty = whole cast). - New project dir:
avatars/(uploaded reference images). - New studio endpoints: themes CRUD, avatars CRUD, avatar ref-upload + analyse (async, spend-guarded), a bundle-asset endpoint (serves post images + avatar refs), and a post-body markdown → HTML render (the Reel-tab article pane).
6. Non-goals¶
- Changing the avatar / theme models or the CLI (D1). The analysis engine itself (spec 0006 — exists). Blog-accurate article styling (Q4). Per-reel publish schedules (a separate future spec, 0033 §7).
7. Phasing (proposed)¶
- Backend (DONE): studio CRUD endpoints for themes + avatars (over the
catalog/registry, writing
.keryx.yaml); avatar ref-upload + analyse;GETpost body + a markdown render helper. Tests. Landed as: POST/PUT/DELETE /api/v1/themes[/{name}]— theme catalog CRUD (project-scoped).GET/POST/PUT/DELETE /api/v1/avatars[/{name}]— avatar registry CRUD.POST /api/v1/avatars/{name}/ref— save a reference photo (manual path, no vision).POST /api/v1/avatars/{name}/analyse— upload → first-use vision → editable profile.GET /api/v1/content/article?bundle=<dir>— the linked post's body rendered to clean HTML (goldmark), inline images rewritten to the asset endpoint, cover excluded (Q4).GET /api/v1/content/asset?path=<project-rel>— serve a bundle image or avatar ref (nosniff, path cleaned to the project root).- Project Settings UI (DONE): the menu Settings overlay restructured into Themes +
Avatars managers + an Advanced (raw config) tab (D2).
GET /themes/catalogbacks the Themes manager's full view. - Reel tab (DONE): the two layouts — linked = 2-col (config: theme · cast · source · cover + the post's frontmatter read-out | rendered article); unassigned = 1-col config
- "link to a post" relink. The reel cast persists via
PUT /workspace/{slug}/avatars(validated against the registry); source seeds from the linked post's gist. - Scenes (DONE): the card inspector's Avatars stub is now a per-scene cast checklist
(over the reel cast, writing
card.avatars; none checked = the whole cast). The studio card generator threads that cast throughgencmd.Request.Avatars→resolveCardGen→Compose, unifying the studio and CLI card-illustration paths.
The Reel tab answers "what post is this reel for, and how should it look/who's in it?" — which needs the project's themes and avatars to exist and be manageable first. This spec builds that foundation, then the tab that leans on it.