Skip to content

0029 — Studio UI restyle (cleaner, more modern)

Status: IN PROGRESS (implementation started 2026-07-12 on feat/studio-rework, in tandem with 0028). Direction taste-gated (Command Deck, §2); IA resolved (§8). Date: 2026-07-12 Source: ideation 2026-07-12 — the studio is functional but visually basic. Informed by krites' 0017-ui-ux-improvements.md, 0018-ui-mockups.md, and its 8 view mockups. Relates: pairs with 0028 (decompose first, restyle the smaller components). Consumes the brand palette already in app.css. Concepts: 0029-studio-ui-concepts.html — three wireframe directions for the reel Editor (Light Table · Immersive Dark · Command Deck), the visual mood board this spec draws on (2026-07-12).

1. Where we are

pkg/studio/web/src/app.css already has the right foundation — the publication palette (--teal #14534f, --amber #e8923b, --cream #f2ead8, --charcoal #282a2c), semantic surface/text tokens, and a started prefers-color-scheme: dark block. But it's under-used: components are largely un-themed, light-only in practice, system-font, flat, minimal chrome — "functional but basic." The tokens exist; the studio doesn't yet feel like a designed product.

krites faced the same and shipped a restrained light-mode system (semantic CSS-var tokens, aubergine-as-accent + gold + cream, rounded soft panels, ring-selection, verdict pills, corner-badge tiles). Its mockups reach further — dark-immersive, glassmorphism, timeline-scrubber — but that richer vision was never built. keryx's opportunity is to deliver it, because a video studio genuinely wants a dark immersive canvas (video reads better on dark — the "loupe" rationale).

2. Direction

Chosen 2026-07-12: the "Command Deck" direction (concept 03 of 0029-studio-ui-concepts.html). Rationale (Matt): the studio is a complex utility, and Command Deck gives the best visibility / power-user experience — timing, per-card asset state, spend/quota, and per-platform token-health all legible at a glance, which the calmer Light Table and preview-first Immersive Dark don't. The other two aren't discarded: their strengths are absorbed per-phase — an immersive-dark 9:16 preview belongs on the Produce surface, and Light Table's editorial calm suits the text-heavy Author surface. The component placement in the concept mock was a first pass; the information architecture is being refined (§8) — "not all components are in the right places yet."

The direction is still dual-surface, built on the token architecture krites proved:

  • Dark charcoal immersive shell for evaluation surfaces — preview / RenderPanel / Publish — where a 9:16 reel reads best against dark.
  • Cream light-table for text-heavy authoring — storyboard Editor / Chat / Settings.
  • Palette roles (keryx → krites-role mapping):
  • charcoal → dark chrome / immersive review background (the role krites only mocked).
  • petrol-teal → brand + active-selection (krites' plum-as-accent): active tabs, selected cards, primary chrome tint.
  • amber-orange → the accent/CTA (krites' gold, near 1:1): primary buttons, selection borders, the reel playhead, accent-word highlights, render/publish CTAs.
  • cream → light canvas + panel fills; body text on dark.
  • Ship real theming keryx already started: complete prefers-color-scheme + a data-theme override (light / dark / auto) with a toggle — the thing krites never built.

3. Design system (concrete)

  • Tokens (extend app.css): keep the semantic set (--bg/--surface/--surface-2/--text/ --muted/--border/--on-accent), add soft-tint pairs for accent + status (--teal-soft, --amber-soft, and if reels gain per-card status: --ok/--warn/--bad + -soft). Both light and dark fully specified.
  • Selection = ring, not shadow: box-shadow: 0 0 0 3px var(--teal-soft) selected; outline: 2px solid var(--amber) for a primary/active target. Minimal drop shadows (overlays only).
  • Radius/spacing: rounded soft — 8–14px panels, 999px pills/chips; an 8px spacing rhythm. Dense-but-breathable (krites' 12.5px workhorse is a good small-text baseline; keryx can go slightly larger for a less cramped feel).
  • Typography: system stack for UI + one embedded display face for the wordmark/headings (self-hosted/inlined — no external font fetch, honouring the studio's strict CSP and go:embed). Gives the "designed" feel the system-only stack lacks.
  • Iconography: replace the current emoji/unicode glyphs (⚙ ↶ ✓ ✕ ★ ♪ …) with a consistent inline-SVG line-icon set (no runtime dep, CSP-safe, matches krites' zero-dep posture).
  • Motion: restrained — hover/selection transitions, progress, a subtle take-appear; respect prefers-reduced-motion.

4. The load-bearing borrowings (and what to drop)

Adopt (map krites views → keryx panels): - Timeline scrubber (krites view 8) — the single most valuable steal. keryx's reel Editor is a timeline (storyboard cards in sequence, VO-driven durations, ~0.4s crossfades). A segmented track with a playhead and card thumbnails along the axis becomes the Editor / RenderPanel scrubber; per-section spans (cover · body cards · outro) as coloured segments. - Immersive dark preview (views ⅖) — RenderPanel/preview: dark charcoal frame around the 9:16 preview, minimal floating controls, a bottom filmstrip of cards/takes. - Floating inspector (view 5) — MediaGen: a per-card panel with generate controls (voice/music/card) + a status readout (VO length, timing, take actions), matching the options→taste-gate loop. - Three-zone bracket — left ProjectSwitcher/Library nav · centre Editor/preview canvas · right inspector. Corner-badge cards (card # · duration · has-VO/music · accent-word) and a status/filter pill bar.

Drop (photo-culling metaphors that don't fit reels): per-tile AI cull scores (Focus/Emotion/…), keep/maybe/reject verdicts, star ratings, the "elimination game", burst clusters, loupe pixel-zoom / focus-peaking / RAW before-after, EXIF panels, and mega-thumbnail virtualised grids. keryx's analogue of "cull" is the narrow take-selection within a card, not triage across thousands of stills. Keep krites' structure, not its hexes or its culling model.

5. Phasing

  1. Foundation (low-risk, high-return): finish the token system (light+dark, soft-tints), the icon set, the display font, ring-selection, the pill/card/badge primitives. Re-skins the whole studio without restructuring.
  2. Panel passes: immersive-dark preview + the timeline scrubber (Editor/RenderPanel), then MediaGen floating-inspector, then the Publish dashboard.
  3. Ideally lands on top of 0028 (decomposed components are far easier to restyle in isolation).

6. Testing / DoD

Visual/behavioural: svelte-check + eslint + prettier --check clean; Puppeteer headless screenshots (light + dark) of each panel as the before/after record — the honest way to verify a restyle on this headless box (per CLAUDE.md). No behaviour regressions (the 0028 characterization tests still pass). Accessibility: contrast checked in both themes, prefers-reduced-motion honoured, focus-visible rings. Docs: a "Studio look & feel" concepts page documenting the token system + theming.

7. Open questions / decisions

  • D1 — direction. ✅ Resolved 2026-07-12: Command Deck (dense, high-visibility, power-user), absorbing the other concepts' strengths per-phase (§2). Build order still foundation-first (tokens/icons/primitives) then per-surface, to deliver value early.
  • D2 — dark as default? Auto (prefers-color-scheme) with a toggle, defaulting to the system preference (rec) vs shipping dark-first (video studios often do).
  • D3 — display font. Which embedded face for the wordmark/headings (needs a licence that permits self-hosting/embedding).
  • D4 — visual mockups first? ✅ Done — three wireframe concepts produced 2026-07-12 (0029-studio-ui-concepts.html): Light Table (editorial/ cream), Immersive Dark (video-first/timeline), Command Deck (dense cockpit/telemetry). All three well-received; Command Deck chosen (D1). Placement resolved in §8.
  • D5 — status model. Do reels/cards gain a status axis (draft/approved, per-platform publish state) worth the verdict-pill treatment, or is that over-fit from krites? Leaning yes — the Reels-list status pills + per-card asset pills in §8 use exactly this.
  • D6 — Chat home. A slide-over drawer available on Author + Produce (rec), vs a fixed Author right-column panel. Rec: drawer — the assistant is useful while producing too.
  • D7 — Author/Produce media-gen split. ✅ Resolved 2026-07-16 (two wireframe variants compared — 0029-produce-ia-paths.html): full move (Path B), not the additive path. The Author card inspector is fields only (text · narration · mode · scene · roles · flags); all per-card media generation — VO + illustration — moves to Produce's right column (CardMediaPanel). Reel-level asset setup (Associated: source · bundle · cover · portrait · music) stays on Author as part of framing the reel. One home for making, one for shaping — the cleaner separation of concerns, chosen over keeping generation inline for the tighter loop. Trades a bigger change (the editor is gutted + rewired) for no later reconciliation.

8. Information architecture (Command Deck) — resolved 2026-07-12

The reel's life is Author → Produce → Publish; each is a phase tab (D-str resolved: tabs, one surface at a time — not one mega-screen) inside a consistent frame. Telemetry is scoped to its phase (D-tel resolved: no persistent global cluster — quota on Produce, token-health on Publish), keeping each surface focused.

Persistent chrome (all tabs): - Top bar: keryx studio · ProjectSwitcher · Author │ Produce │ Publish · active reel + save/commit state · Settings/overflow menu. (No global telemetry cluster.) - Left rail: Reels list (from Library, with status pills) · Theme.

Author — shape the reel: - Centre: the storyboard spine — a compact timeline strip (cover/body/outro section proportions) above the editable card grid (Editor: per-card narration, roles, flags). Timeline and grid share selection: the timeline shows proportion + scrub, the grid is where you edit (not redundant). - Right: card inspector (selected card's fields) · Associated (source post, cover, bundle setup). - Chat (AI assistant) as a slide-over drawer (D6) — the home of the context-aware, action-taking assistant specced in 0032-studio-ai-assistant.md.

Produce — make the media: - Centre: the same timeline+grid showing per-card asset state (img/vo/music pills) + a 9:16 preview hero (immersive-dark framed — the borrowing from concept 02) with RenderPanel progress/cancel. - Right: MediaGen (generate/audition/pick for the selected card; incl. the 0030 record action) · ContactSheet (text-leak screening) · Spend & quota (scoped here).

Publish — ship it: - Centre: per-platform copy composition + approve/schedule (Publish). - Right: Connections (per-platform token health; scoped here) · post/ledger state.

Component → home map (all 13): App→shell/routing; ProjectSwitcher→top bar; Library→left-rail Reels; Editor→Author+Produce spine; Chat→drawer; Associated→Author right; MediaGen/CardMedia/ContactSheet/RenderPanel→Produce; Publish→Publish; Connections→Publish right; Settings/OverflowMenu→top-bar menu.

The concept mock (0029-studio-ui-concepts.html · 03) crammed all phases onto one screen; this §8 splits them into the three phase tabs with phase-scoped telemetry — the "components in the right places" refinement.

9. Implementation progress

Foundation — token system + theming (§5.1, T2) — DONE. - app.css: full semantic token set — brand palette, surfaces/text, soft-tint pairs (--teal-soft/--amber-soft), a status axis (--ok/--warn/--bad + -soft, D5), an 8px spacing rhythm, radii (--radius-sm|--radius|--radius-lg|--radius-pill), a type stack (--font-ui; --font-display aliased to it pending D3), and an overlay elevation. Both light + dark fully specified. - Real theming shipped (the krites-never-built piece, D2): light = :root; dark applies under prefers-color-scheme and an explicit data-theme="dark", so the toggle beats the OS preference in both directions. lib/theme.js (auto|light|dark, best-effort persistence, applyTheme) is unit-tested (9) and applied in main.js before mount (no flash; auto is a behaviour-identical no-op for existing users).

Command Deck shell (§8, T4) — DONE. The IA is rebuilt on the 0028 view tree: a persistent TopBar (wordmark · Author │ Produce │ Publish phase tabs · save-state chip · theme toggle · menu) over a two-column body — the always-present ReelRail and the active reel's phase-routed workspace. Replaces the old library→full-screen-editor flow (both removed); the reel list never leaves view. Phase tabs route Author/Produce/Publish onto the shared ReelSession. All new chrome is styled through the T2 tokens and verified in both themes across all three phases (Puppeteer, §6 DoD — light + dark, author/produce/publish). Added Menu.svelte, a real popover dropdown, for the rail's per-row actions (OverflowMenu is a responsive collapse, wrong for a narrow row).

Produce preview hero (§8) — DONE. PreviewHero.svelte reframes the old small inline RenderPanel preview into an immersive-dark 9:16 stage at the top of Produce — a cinematic vignetted surround (deliberately dark in both themes, a media player not page chrome) centring the vertical frame, with the render/cancel controls and an in-frame progress overlay beneath (indeterminate shimmer when the job reports no percentage; prefers-reduced-motion honoured). Render still gates on the saved board (dirty → controls off + note). RenderPanel.svelte removed; the engine (ProduceSession) is unchanged.

Timeline strip (§8) — DONE. TimelineStrip.svelte is the storyboard spine's temporal view, above both the Author grid and the Produce hero: one segment per card, width ∝ its real VO-driven on-screen duration, grouped cover · body · outro (from the cover/mono flags — the accent bookend, teal body, muted closer), with selection shared with the grid (click a segment → select that card; the selected card's segment rings). It reads the saved timing and flags when the board has unsaved edits the timing predates. The duration data is real, not faked: the "add timing to the API" decision (2026-07-13) added GET /workspace/{slug}/timeline (0002 R-UI-31), which reports the same VO-driven timing reel build assembles (reusing reelcmd's BuildPlan), degrading to the storyboard dur when no VO sizes it (vo_driven:false, "draft timing").

Save + Theme relocated into the chrome (§8) — DONE. The persistent-chrome contract now holds: the Save action + save/commit state live in the TopBar (a real Save button — enabled/accent only when dirty, "Saved" disabled when clean — plus a validation-issue badge), and the Theme picker moved into the ReelRail beneath the reels list. The workspace bar is now just the reel title + unsaved dot. The active reel's session stays owned by the (per-reel-remounted) workspace, which surfaces its save/theme handles up via an onstate callback the shell binds to the chrome — no session hoist, so per-reel remount (and its child re-init) is preserved. The rail is collapsible (a top-bar sidebar toggle, choice persisted) — hiding it drops it from the grid so the workspace takes the full width, reclaiming real estate on smaller screens; the rail stays mounted when hidden, so reopening is instant with no reel refetch.

CardGrid (§8) — DONE. The storyboard card list is now CardGrid.svelte — visual tiles instead of plain rows: each carries a corner index (accent when selected), the on-screen text, a mode chip, its VO-driven duration (from the shared timeline), and illustration/VO asset dots, with ring-selection (box-shadow teal-soft, 0029 §3) shared with the TimelineStrip. Drag-reorder and the ↑/↓/✕ controls carry over; a dashed "+ add card" affordance closes the stack.

Author/Produce media-gen split (§8, D7) — DONE. The shape→make split is implemented (Path B, D7). The old Author editor monolith is decomposed into CardInspector.svelte — the selected card's fields only (text · narration · mode · scene · roles · flags), no generation — and its per-card media (VO takes + illustration) is lifted into CardMediaPanel.svelte on the Produce right, composing the shared MediaGen + CardMedia engines unchanged (only their home moved). So Author = shape (timeline · card grid · card inspector · chat · Associated reel-level setup on a full-width row) and Produce = make (the shared timeline+grid spine + 9:16 PreviewHero, with the per-card CardMediaPanel and ContactSheet in a right column). Associated moved Produce→ Author (its cover/portrait/music is reel framing); ContactSheet moved Author→Produce. Both views keep the mobile bottom-tab / desktop-grid pattern — the bottom switcher is now a shared TabBar.svelte (extracted so Author, Produce, and the next phase view stop each carrying a verbatim copy). Verified in both themes across Author + Produce (Puppeteer/Playwright, §6 DoD).

Publish decomposition (§8, 0028 §3.2) — DONE. The Publish cockpit is split onto the three 0028-named surfaces, all bound to one shared PublishSession owned by PublishView: PlatformCopy.svelte (centre — per-platform copy · AI compose · approve · the irreversible Post-now behind its confirm dialog), ConnectionsPanel.svelte (right — per-platform token health as a dashboard, with the S5 live "check now" probe), and LedgerPanel.svelte (right — the per-platform posted-state record + view-post links). The monolithic Publish.svelte is removed; its state machine already lived in PublishSession (untouched). Same TabBar + desktop-grid pattern as Produce (compose beside a stacked connections/ledger column, slack row so the panels top-align). Raw status hex swapped for the --ok/--warn/--bad axis so the health chips theme; the shared StatusBadge.svelte (draft/approved/posted chip + view-post link) is reused by PlatformCopy and LedgerPanel so the status→colour mapping lives in one place. Verified both themes (§6 DoD).

SpendQuotaPanel (§8) — DONE. The last Produce-right panel landed with the spend ledger + guard of 0031: SpendQuotaPanel.svelte shows the reel's accrued generation spend (per-unit + total, from the new spend.json ledger via GET …/spend) beside the voice quota meter — phase-scoped telemetry (D-tel), cost a cue not an invoice. Paired with the spend guard (spend.confirm_above, a 412/confirm gate on over-cap generation, surfaced inline by MediaGen). All three studio phases now realise the 0028 §3.2 decomposition.

Deferred (each its own slice): the display font (D3 — blocked on an embeddable licence); and growing the inline-SVG icon set per panel.

Gate at this checkpoint: Vitest 151 passed, Playwright 12 passed, svelte-check 0 errors, eslint + prettier clean; Go studio pkg + godog + golangci-lint green.


9. Workflow rework — 4-tab IA (2026-07-17, IN PROGRESS)

Live driving of the studio against the blog surfaced that the §8 three-phase split (Author · Produce · Publish) put cards in two places (Author held per-card text + the storyboard spine; Produce held per-card media) — the workflow felt disjointed and forced constant tab-hopping with no single "live" preview. Reworked on feat/studio-author-layout into a four-tab flow that reads as the pipeline:

Tab Owns Notes
Reel (was Author) reel-global setup: blog-post bundle, theme (moved off the rail), cover, portrait answers "what is this reel?"
Scenes (was Produce) one card list (left) · live HTML preview (middle) · per-scene config (right, wide): text · colour · background · VO + illustration the storyboard and media, one place; card→scene in the UI language
Preview (new) the fully-rendered 9:16 reel + Render, and reel-wide content (music) reel-wide settings left, rendered result right
Publish unchanged (§8)

Key moves: render + music left Scenes for Preview; the theme picker moved from the rail to Reel; the assistant chat became a floating, draggable bubble (FloatingChat, mounted at ReelWorkspace so it persists across all tabs) instead of a permanent column. The Scenes timeline stays the compact temporal spine (duration-proportional) — an attempt to make it carry the whole card list was reverted; the vertical card list does navigation/reorder, the timeline does timing.

New components: CardPreview (live HTML scene preview), PreviewView, MusicBed. Bug fixed en route: cards omitting mode/cover/mono dirtied the board on mount (the <select>/checkbox bindings wrote defaults) — switched to value+onchange so mounting never mutates, and reels load clean.

Open (the per-tab deep-dives, likely with backend impact): per-scene multi-avatar selection (✅ resolved by 0034 — Meta.Avatars cast + card.avatars per-scene checklist); making the floating chat context-aware per tab; and the detailed reshaping of each tab's content. This section supersedes §8's phase split as the current IA; §8 stands as the Command-Deck shell design it built on.

Driving notes — 2026-07-23 review (Matt), triaged

A full pass over the 4-tab IA against real blog reels. Items classed fix (landing on the current studio slice), feature (needs design → its own spec), or answered (explained, no change). The three large features graduated to their own specs: 0036 (theme editor: rgba palette + full type-scoped theme controls), 0037 (settings IA: Version Control · Socials · Auth panels + YAML advanced view), 0038 (text positioning + multiple text blocks — core model + renderer, not just studio).

Project settings 1. Palette editor: colour picker is poor; hex-only. → feature, 0036 (proper picker + rgba — alpha is real intent: scrims/overlays gain a designed transparency axis). 2. Theme controls incomplete: only palette editable; fonts · style prompt · script definition · music · voice belong too, scoped by theme type. → feature, 0036. 3. Advanced tab: extract raw settings into proper panels (Version control · Socials · Auth); repurpose Advanced as an editable YAML representation; themes belong in the Themes tab. → feature, 0037.

Rail 4. Long reel lists become unwieldy: keep date order, add a small free-text filter. → fix.

Reel tab 5. Show the post's cover in the article preview column. → fix. 6. What is Associated content?answered: the reel-level framing inputs (source text · cover · portrait); "Seed from post" pulls the linked post's gist/summary (not the body) as a storyboard starting point. 7. "Use post cover" + crop selection for the cover. → feature (Reel-tab cover UX, with 8). 8. Cover prompt should be a popup on Generate; show the generating prompt under the image. → feature (same Reel-tab cover UX slice). 9. Portrait avatar redundant vs the 0034 avatar registry + cast. → fix (drop the Associated portrait block; the cast section owns identity now).

Scenes tab 10. Textareas should size to content. → fix (field-sizing: content). 11. Newlines not honoured in the preview. → fix (CardPreview lacked pre-wrap). 12. Text positioning controls (drag preferred, else d-pad). → feature, 0038. 13. Multiple text areas per scene. → feature, 0038. 14. "Mode doesn't pull through" (burnout scene 1 loads as block with a full scene prompt). → answered + fix: the data genuinely says mode: block — block cards have no illustration by definition (internal/reel/storyboard.go); the card carries an orphaned scene+media from an overlay past. Fix = surface the inconsistency (warn when a block card carries scene/media it will ignore) instead of hiding the scene field silently. 15. fg cryptic + free-text. → answered + fix: fg/bg/accent are palette colour roles (values = the theme palette's colour names); spell them out and make them palette-swatch selects. 16. bg same. → same fix. 17. VO takes: no feedback on which take is in use. → fix (backend marks the promoted take in the takes listing — content-match against the selected slot — and the gallery badges it). 18. Illustration control sits too low and isn't in the preview. → fix (illustration above VO in the scene media panel; CardPreview renders the picked illustration as the overlay frame fill).

Driving notes — round 2 (2026-07-23, after the settings + cover slices)

Bugs found in review (all → fix on this slice): 19. Cover generate 500s when cover/ doesn't exist yet — scene.txt write lacked MkdirAll (MemMapFs auto-creates parents; the OS fs doesn't). 20. The crop dialog's pan doesn't move the preview — bgStyle ignored panX/panY (state changed, image didn't; a 16:9 post cover thus seemed locked to centre). 21. The assistant bubble vanished — FloatingChat's persisted position is only clamped while dragging, so a position saved on one window size restores off-viewport on another. Clamp on load + resize. 22. Scenes preview jumps size between scenes — the 9:16 frame grows when the text (or note) exceeds the aspect box. Pin the frame; content clips.

Quick UX (this slice): 23. Themes: image-prompt textarea auto-sizes (same field-sizing treatment). 24. Illustration panel shows thumbnails only — the big CardMedia preview is superfluous now the scene preview renders the pick. 25. Tooltips/comms pass: mono closer + cover flags explained; Associated content gets a purpose line ("what these inputs do"). 26. Providers/Socials panels: per-section descriptions; provider fields become selects of the registered backends; Socials sections link help.

Features (✅ ALL BUILT 2026-07-23, same slice): 27. Per-reel aspect ratioworkspace.Meta.Aspect ("W:H") over a reel.aspect config default (allowlisted), else 9:16. reel.AspectDims (short side 1080, long side even-rounded) threads the whole chain: cards renderer (Options.Width/Height → gg contexts), reel build (cards + Timeline; --aspect flag), postcmd video meta, cover generation (the provider ImageRequest.Aspect follows the reel, not the article theme), the scene preview, and the cover crop. Reel-tab select; parity guard: unset renders pixel-identically. 28. VC panel: GET /vc/status probes the repo with go-git — remote presence + URL (preferring origin over alphabetical helpers) and a push-auth heuristic (ssh-agent / ~/.ssh key / https). Status chips above the VC settings. 29. "Summarise post (AI)": POST …/source/summarise briefs the linked post's body via the chat provider (review-first — nothing saves until Save source). 30. Scenes config regrouped into Background · Audio · Text (SceneConfig absorbing CardInspector + CardMediaPanel).

Round 3 (mid-review, 2026-07-23) — cover flow + assistant: 31. Cover pick after the first didn't refresh the image — the #fragment cache-buster never triggers a reload; ?v= does. Fixed. 32. Cover generation flow reworked: the takes gallery is a filmstrip in the cover block (all rounds accumulate on disk, so history persists for settling on an earlier take); the popup holds only the prompt, drives the same engine, and closes when the generation settles (MediaGen onsettled). Built. 33. The reel aspect propagates to cover image generation (see 27). Built. 34. The assistant chat failed — root cause: the chat seams were wired ONCE at startup, gated on providers.chat.provider being set at that moment (and the env has BOTH Anthropic + Gemini keys, so single-key inference refused to guess). Fixed at the root: the seams are always wired and the provider resolves per request, so configuring chat in settings takes effect live; the blog project is set to claude. Verified: streaming keryx-personality reply.

Round 4 (2026-07-23) — aspect everywhere, validation locality, chat polish: 35. Aspect must reach EVERY visual: scene-illustration generation (was 9:16 regardless), the cover display + filmstrip (CSS was hardcoded 9/16), and the Preview render stage. Centralised: gencmd resolves the reel's aspect itself (meta → reel.aspect → default) for card AND cover image generation. 36. A cover made under another aspect is STALE — the workspace payload now carries the cover's pixel size; the studio warns on mismatch with a regenerate/re-crop cue (it cannot silently re-crop). 37. Validation messages were disconnected from their cause ("empty text" floating at the panel top) — issues now attach to the input that raised them (keyword→field mapping; unmatched issues still list at the top). 38. More ratios, both orientations: 1:1 · portrait 9:16/4:5/3:4/2:3 · landscape 16:9/5:4/4:3/3:2 (grouped select). 39. The Scenes card column becomes collapsible (persisted) — landscape frames need the preview width. 40. Chat: (a) panel resizable (persisted); (b) the reply double-printed — the streamed text was kept AND the final summary appended; the stream buffer now resolves INTO the reply; © transcript persists per reel (localStorage, keyed by slug) with a clear button + /clear command. 41. Scene-illustration pick froze the preview on the first image — the promoted slot path never changes (cards/NN.png overwritten in place), so the src was identical and never refetched. Same class as 31: a pick now bumps a media revision that cache-busts the preview URL (contact sheet included). 42. Publish AI-compose was unmoored from the post — it grounded ONLY on the storyboard, so a placeholder child-in-a-sandbox scene produced #kidsplay copy for a wasm-security post. ✅ Fixed: the source brief (source.md — the post the reel promotes) is now the compose prompt's SUBJECT; the storyboard is explicitly framed as the reel's visuals, not the topic. (Compose quality therefore depends on the source being seeded — Seed/Summarise first.) 43. Save ergonomics (DESIGN, next slice) — Matt: the Save button is an odd call to action and dirty-gating blocks VO/illustration generation mid-flow. Direction: a fixed bottom action bar hosting the phase tabs, natural back/next, and Save; ideally saves become automatic (on blur / field commit). Needs decisions before building: - D-save-1 auto-save vs explicit: commit-on-save (0012) would commit on every blur — auto-save likely needs commit debouncing/batching or a "commit on phase-leave" policy. - D-save-2 invalid boards are 422-rejected by design (R-API-1) — an auto-save must hold dirty-but-invalid state gracefully (save-when-valid, inline issues meanwhile), never spam errors on half-typed cards. - D-save-3 a cheap immediate win either way: Generate (VO/illustration) could auto-save a VALID dirty board before generating instead of disabling itself ("save-on-generate"). 44. Source reframed as creative direction (✅ built) — Matt HAD summarised, but the draft only filled the textarea (nothing persisted without "Save source"), so compose never saw it and a reload lost it. Reframe: a linked reel's SUBJECT is the article itself (no lossy intermediary — compose + CLI social gen resolve it via the content source); source content (paste-in) is the subject only for UNLINKED reels (field hidden otherwise); a new creative direction (direction.md) carries intent + emotions and grounds generation alongside the subject. Direction/content auto-save on blur ("saved ✓" flash); Summarise now drafts into direction and persists. 45. Chat context (✅ built) — the assistant saw only the board + source; it couldn't discuss the post at all. It now receives the reel's full picture: settings (theme · aspect · cast), the linked article (truncated), the creative direction, and any pasted source — with the system prompt widened from "revise the board" to "discuss the topic and advise on creative decisions". 46. Scene preview fills the available width (round-5): width = min(100%, 62vh × ratio) — landscape frames finally use the space, especially with the card list collapsed.

Round 6 (2026-07-23) — save semantics resolved, chat robustness: 47. Summarise must produce DIRECTION, not a summary — emotional beats, intent, tone; the prompt reframed accordingly. 48. The chat bubble anchors to the bottom bar (half-on/half-off, right side); the bar pads right so its controls clear the bubble. 49. D-save-½/3 RESOLVED (Matt): saves happen dynamically on blur/change (ungating VO/illustration generation); commit-on-save was never the intent per save — the explicit button becomes Commit & push (save + commit + push on demand). Implementation: implicit saves stop committing; a new explicit commit endpoint carries the git action; auto-save only fires on a VALID board (invalid states hold locally with inline issues). 50. Chat robustness: the model sometimes dumps storyboard JSON into its prose (belongs only in the patch channel — prompt hardened); an error mid-stream left the input locked (streaming never reset without a done event — the client now always unlocks); the clear control is always visible.

Round 8 (2026-07-23) — the AI-populated-board cascade + chat maturity: 51. (round 7 additions: render-staleness flag · chat toggle bubble · resize grip · font picker + project assets/fonts — see the round-7 commits.) 52. Chat transcripts persist IN the workspace (chat.json travels with the reel — localStorage didn't survive Matt's navigation); recent transcript also feeds the model, so replying to a pending proposal REFINES it (the accept/reject one-way street opens up). Thinking indicator (animated dots) covers the prose→proposal gap; distinct blocked-input styling; accept now reports what changed and where to go next. Bulk "generate all media after accept" recorded as a queued feature. 53. The AI-board cascade fixed at three levels: (a) the model is TOLD the palette's actual role names (it invented "ink" and every card 422'd); (b) accepted boards sanitize unknown roles → default; © the autosave retry bug — the effect only re-armed on dirty TRANSITIONS, so a 422 hold never retried and issues could never clear; it now tracks board content. 54. Timeline covers the LIVE board (unsaved cards were invisible) with per- segment error badges — orientation with the card list collapsed. 55. Scenes preview column flexes (its 22rem grid cap made landscape shallow); config column bounded instead. "Seed from post" removed (a gist isn't creative direction). Preview stage placeholder no longer hardcodes "9:16"; Render's grey-out always explains itself (unsaved/issues/missing illustrations, itemised).

Round 9 (2026-07-23) — preview wobble, root-caused: 56. Scenes preview STILL resized scene-to-scene (same reel, four sizes in four screenshots). Two compounding causes, both fixed: (a) the workshop's minmax() grid tracks resolve to their content's min-content size, which differs per scene (illustration thumbs, cast, VO takes) — the cards/config tracks are now FIXED (26rem, stepping to 34rem ≥1200px) so the 1fr preview track is identical for every scene; (b) the frame's .preview wrapper was a centered flex item — fit-content width — so the frame's min(100%, 62vh·ratio) resolved 100% against the card's own TEXT length whenever the viewport made 100% the winner; the wrapper is now width: 100% (definite). Verified headlessly: 8/8 scenes identical at both a wide viewport (62vh branch) and a narrow one (100% branch). 57. Mixed VO/no-VO boards mangled the timeline (one segment swallowing the strip, the rest slivers; times never populated after Generate VO). Root cause was in the CLI core, not the strip: promoted VO was discovered by a sorted vo/*.mp3 glob and mapped POSITIONALLY onto cards 1..N — a sparse set (the normal studio state: VO generated per scene, any order) shifted a lone vo/02.mp3 onto card 1 and timed everything else at zero, in reel plan/the timeline endpoint, reel build's mux (wrong card's narration on the wrong scene!), and both music-bed sizers. Replaced with BoardVOClips/ReelTimingForBoard: each clip binds to ITS line, VO-less cards fall back to their storyboard dur (build errors by name when a card has neither). Frontend: the strip refreshes on VO pick/settle (not just save) and renders 0-dur entries as draft-width, not slivers. 58. "Use reel cover" as card media (feature): any overlay card can reuse the reel's cover art as its panel — media: {source: cover, path: cover.png}, no spend, no new file; the natural fill for opening/closing cards. New reel.SourceCover; the illustration panel offers it whenever the reel has a cover; plan's missing_media gap now respects board-recorded media. 59. Uncommitted-reel indicator (feature): the rail marks reels whose workspace carries saved-but-uncommitted changes (a quiet amber dot beside the name, tooltip → "Commit & push"). One git status per list (GitOps.DirtySlugs over gitrepo.ChangedPaths, both backends); the workspace bumps gitRev on every save/commit so the rail refetches and the dot tracks reality. CAVEAT found while verifying: the blog currently gitignores reels/*, so on that project git sees no workspace changes — the dot never lights AND Commit & push has always been "nothing to commit". Matt to decide: un-ignore reels/ (matches the owning-project-holds-state design) or accept git actions stay inert for the blog.