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+ adata-themeoverride (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,
999pxpills/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¶
- 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.
- Panel passes: immersive-dark preview + the timeline scrubber (Editor/RenderPanel), then MediaGen floating-inspector, then the Publish dashboard.
- 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 ratio — workspace.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.