Skip to content

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 config avatars.<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 CLI avatar add <name> <image> already drives it.
  • Themes: theme.Catalog, per-project, config-driven; studio GET /themes + per-reel PUT /workspace/{slug}/theme already 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: " read-out + relink. Source text may seed from the linked post (body / gist) rather than manual paste.

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 = card avatars: [...].
  • 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.yaml watcher (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 to reels/, 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: add Avatars []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)

  1. Backend (DONE): studio CRUD endpoints for themes + avatars (over the catalog/registry, writing .keryx.yaml); avatar ref-upload + analyse; GET post body + a markdown render helper. Tests. Landed as:
  2. POST/PUT/DELETE /api/v1/themes[/{name}] — theme catalog CRUD (project-scoped).
  3. GET/POST/PUT/DELETE /api/v1/avatars[/{name}] — avatar registry CRUD.
  4. POST /api/v1/avatars/{name}/ref — save a reference photo (manual path, no vision).
  5. POST /api/v1/avatars/{name}/analyse — upload → first-use vision → editable profile.
  6. 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).
  7. GET /api/v1/content/asset?path=<project-rel> — serve a bundle image or avatar ref (nosniff, path cleaned to the project root).
  8. Project Settings UI (DONE): the menu Settings overlay restructured into Themes + Avatars managers + an Advanced (raw config) tab (D2). GET /themes/catalog backs the Themes manager's full view.
  9. 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
  10. "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.
  11. 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 through gencmd.Request.AvatarsresolveCardGenCompose, 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.