Skip to content

refactor(style): redesign config into canonical v2 schema (look/motion) - #863

Closed
tombeckenham wants to merge 1 commit into
mainfrom
858-style-schema-design-aesthetics-blob-vs-identity-columns-split-+-inconsistent-optionality
Closed

refactor(style): redesign config into canonical v2 schema (look/motion)#863
tombeckenham wants to merge 1 commit into
mainfrom
858-style-schema-design-aesthetics-blob-vs-identity-columns-split-+-inconsistent-optionality

Conversation

@tombeckenham

Copy link
Copy Markdown
Contributor

Summary

Redesign the styles schema around a single canonical type (issue #858). A Style row was fractured into two shapes — identity columns and an aesthetics JSON blob config — with no canonical type, backwards optionality, and motion under-specified by a single cameraWork string. This reshapes config into a grouped v2 structure, introduces one canonical projection type, fixes optionality, and lands it as a safe, no-DDL blob migration.

v2 StyleConfig shape

A style encodes three signatures — narrative / look / motion — with motion in the core (it can't be derived from a still). Small required core + a wide optional surround so quick/LLM/vision styles stay compact while templates stay rich:

{ summary?, tone?,
  look:  { mood, artStyle, lighting, colorPalette, colorGrading, medium?, texture?, composition? },
  motion:{ camera, shots?, pace?, energy? },     // camera <- v1 cameraWork
  references: [],                                 // <- v1 referenceFilms
  design?, casting?, sound?, source? }            // optional surround + provenance

What changed

  • src/lib/style/style-config.ts (new, drizzle-free): v2 StyleConfigSchema, one migrateStyleConfigV1ToV2 + parseStyleConfig converter (reused at read boundaries, template seed, and backfill), and the canonical StyleProjection. libraries.ts re-exports for existing import paths.
  • The three ad-hoc re-projections (EnhanceStyle, enhanceScriptInputSchema.style, StyleTemplateEntry) now derive from the canonical type; new style-brief.ts centralizes optional-field handling for the enhancer.
  • All prompt builders read grouped v2 fields; the four DB read boundaries up-convert via parseStyleConfig (v1-tolerant during rollout).
  • Optionality fixed in style.schemas.ts (category required on create; tags/useCases default []; config whole-or-omitted on update).
  • Hashing: computeStyleConfigHash → v2 fields; PROMPT_INPUT_HASH_VERSION 3→4; data-only null-sweep migration 20260608120000_null_hashes_for_style_v2 (mirrors the …v3_bump precedent).
  • 35 templates normalized to v2 at the seed mapper; scripts/backfill-style-config-v2.ts (idempotent, dry-run, validates before write).
  • Phase 2 (motion-aware prompts): delivered by the reshape — the motion-prompt LLM already receives the full serialized motion signature, so motion.shots/pace/energy surface automatically once authored.

Migration safety

The blob reshape needs no DDL and no table rebuild (config is one column), so the D1 ON DELETE CASCADE rebuild trap is not in play. Staleness is read-time and null→untracked, so the null-sweep avoids false "stale" banners with no regeneration storm.

Verification

  • bun typecheck clean · bun run test 1210 passed · bun lint 0 errors · bun format:check clean
  • bun db:migrate:local applies the null-sweep cleanly · bun db:seed:local writes 80 valid v2 styles
  • bun scripts/backfill-style-config-v2.ts --local migrated a real legacy v1 row; re-run reports 0 to migrate (idempotent)

Follow-ups (out of scope)

  • Phase 3: style reference-image conditioning at the image stage (gated to vision-derived styles).
  • Phase 4: LLM/vision style creation (structured-output → v2 config) + create-style UI.
  • Drop the v1-tolerance branch in parseStyleConfig once prod is fully backfilled.
  • Optional: promote category to a DB NOT NULL column (needs a manually-applied migration).

Closes #858

🤖 Generated with Claude Code

Reshape styles.config from a flat blob into grouped look/motion/references
with one canonical StyleProjection type, fix backwards optionality, and add a
v1->v2 converter reused by read boundaries, the template seed, and a backfill.
Motion is now a first-class core signature (camera + optional shots/pace/energy)
instead of a single cameraWork string; new optional design/casting/sound/source
slots future-proof LLM/vision style creation.

Blob-internal reshape needs no DDL/table-rebuild; bump PROMPT_INPUT_HASH_VERSION
3->4 and add a data-only null-sweep migration so legacy rows read as untracked
(no false "stale" banners, no regeneration storm).

Closes #858

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

Cloudflare Preview deployed successfully

URL https://pr-863.openstory.workers.dev
Worker pr-863
D1 Database openstory-pr-863
Commit 64700cf

@tombeckenham

Copy link
Copy Markdown
Contributor Author

Closing after a comprehensive five-agent review. The design direction survives — issue #858 stays open and the redesign will be re-implemented on current main — but this diff is not salvageable by rebase, and its migration strategy is being replaced.

Why close instead of rebase

  • 632 commits behind main, conflicting in the core files (libraries.ts, style.schemas.ts, enhance-inputs.ts, storyboard-workflow.ts), plus modify/delete conflicts: score-image.ts, style-grid.tsx, frames.test.ts, and score-style-scripts.ts no longer exist on main.
  • main independently landed part of this cleanup (fa0fa9e, server-managed library columns) and grew new style-config consumers this PR never met (shot-list.derive, showcase/sample-video tooling).

Key review findings (would block merge regardless of rebase)

The null-sweep migration is being dropped entirely. Its column list was name-matched, not derivation-traced:

  • Misses sequences.music_prompt_input_hash + sequence_music_prompt_variants.input_hash (both embed the bumped PROMPT_INPUT_HASH_VERSION) → every legacy sequence would show a false-stale music prompt — the exact storm the sweep exists to prevent.
  • Misses characters.sheet_input_hash and location_sheets.input_hash, which DO embed computeStyleConfigHash.
  • Wrongly sweeps talent_sheets.input_hash (no style data), which actively causes sheet divergence for in-flight character-sheet workflows.
  • Nulls location_library.reference_input_hash, a column never written anywhere.

Replacement strategy: a stable hash projection — hash a v1-shaped projection of the config (legacy key names, new fields included only when populated) and keep PROMPT_INPUT_HASH_VERSION at 3. A pure reshape then changes no hashes: no migration, no sweep, no staleness data destroyed.

Other confirmed defects:

  • migrateStyleConfigV1ToV2 returns unvalidated output (loose v1 schema in, StyleConfig-typed out) — a v1 row can violate the v2 schema's own min(3) constraints; the backfill validates strictly, so the same row behaves differently at runtime vs. backfill.
  • The backfill's 'look' in config skip check silently passes corrupted "v2-shaped" blobs it claims to validate.
  • Prompt hashes hash the config whole while computeStyleConfigHash narrows to look/motion/references — seeding source: {kind:'template'} would silently invalidate every frame hash.
  • getStyleGradientFromConfig runs a full Zod parse inside React render — one bad row blanks the entire style grid, unattributed.
  • getFrameStalenessFn's broad catch swallows the new ZodError (data corruption) as untracked with only a warn.
  • No test validates the ~35 curated templates against the strict v2 schema they must survive at every read.
  • references missing .default([]); isStyleConfigV2 duplicated in the backfill; missed read boundary in scripts/analyze-script.ts.

Worth carrying forward: the canonical type + single v1→v2 converter reused at every boundary, toStyleBrief's centralized optional-field handling, the abort-loudly backfill shape, and the grouped look/motion design itself (with the speculative design/casting/sound groups deferred until they have consumers).

Re-implementation follows on a fresh branch off main; the rich motion-template authoring is gated on an eval experiment via the existing score-style-previews / eval-style-sample-videos tooling.

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Style schema design: aesthetics-blob vs identity-columns split + inconsistent optionality

1 participant