Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 15 additions & 11 deletions docs/development/SLICE_QUEUE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Canonical sources in git:
Working notes under `comunicaciones/` are session handoff only. They are
gitignored and are not canonical.

## Landed on main (as of #98)
## Landed on main (as of #100)

Asset-manifest v1 writers: Studio generate (simulated, WGP, H3, SFX), Tools
upscale/revoice, Recast/Repaint/Outpaint, MiniMax image, Series assembly, 3D,
Expand All @@ -36,7 +36,8 @@ publish so Wizard→Studio→asset can share one durable identity.
focus), developerMode, sidebar, retake dialog. Slices bind through
`bindSlice` without `as never`. `developerModeSlice` no longer writes
`mediaFilter`; the facade still leaves `auditdev` when developer mode turns
off.
off. A parallel gallery/workspace slice PR may be in flight; it has not
landed on main.

Story Lab UI extracts:

Expand All @@ -46,7 +47,8 @@ Story Lab UI extracts:
- #91 Music, Trailer, Productions and Compact workspace, with `storyLab` EN+ES.
- #97 split those extracted tabs into smaller panels and added the code-health
PR table (`scripts/code_health.py --check --markdown`).
- #98 Overview + generation-agent panel, EN+ES. Assets remains in
- #98 Overview + generation-agent panel, EN+ES.
- #100 Assets tab extracted with EN/ES. Assets is no longer remaining in
`StoryLabPanel`.

i18n: foundation + Extra info inspector + Extra info video dialog (`extraInfo`
Expand All @@ -58,8 +60,8 @@ Recipe audio duration: generated audio is sized for its consumers (#93).
Character Kits / Face Rig / cutout dialogue HOWUSEIT: #94 on `main`. #26 was
the older Cursor docs pass and is closed as superseded.

`--markdown` without `--check` prints **Ratchet not evaluated.** CI uses
`--check --markdown`.
`--markdown` without `--check` prints **Ratchet not evaluated.** (#99). CI
uses `--check --markdown`.

## Next medium PRs

Expand All @@ -68,16 +70,18 @@ the older Cursor docs pass and is closed as superseded.
3. **Story Lab simple tabs** — landed (#88).
4. **Story Lab Music + Productions** — landed (#91, split further in #97).
5. **Story Lab Overview** — landed (#98).
6. **Story Lab Assets tab** — remaining chrome in `StoryLabPanel` (importer,
style conversion, visual library), EN+ES, no `useStore.ts` /
`_launch_runtime.py`.
7. **`useStore` slice** — one moderate cohesive extract with the public facade
6. **Story Lab Assets tab** — landed (#100).
7. **Story Lab assembly + library chrome** — remaining: extract Assembly tab
and leftover library chrome (header, tab labels, project types, prepare
buttons, nav notes) with EN/ES. No `useStore.ts` / `_launch_runtime.py`.
8. **`useStore` slice** — one moderate cohesive extract with the public facade
kept and `architectureSlices.test.mjs` extended. Do not move all of
`startGeneration` in one PR. At most one open PR may touch `useStore.ts`.
8. **Backend by domain**: one complete router + services per PR (Assets, Music,
A gallery/workspace slice may already be in flight; do not claim it landed.
9. **Backend by domain**: one complete router + services per PR (Assets, Music,
Series, Comics, …). Preserve route-table ordinals. Do not split
`_launch_runtime.py` by line count.
9. **Provenance applied by flow**: Studio+Wizard landed (#95). Remaining:
10. **Provenance applied by flow**: Studio+Wizard landed (#95). Remaining:
Story Lab+videoclip, Series+Comics. 3D+Director already has folder vs
Workspace provenance (#89). Do not start Director Paso 5 until a human
decides release order; that work must begin with 5.0 PipelineRuntime.
Expand Down
12 changes: 8 additions & 4 deletions tests/test_series_lab_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,15 @@ def test_review_is_thumbnail_first_and_exposes_ordered_editable_attempt_history(


def test_story_productions_have_an_in_place_ordered_clip_timeline():
story = (ROOT / "ui" / "src" / "features" / "stories" / "StoryLabPanel.tsx").read_text(encoding="utf-8")
tabs = (ROOT / "ui" / "src" / "features" / "stories" / "storyLabTabs.ts").read_text(encoding="utf-8")
assembly = (ROOT / "ui" / "src" / "features" / "stories" / "StoryAssemblyTab.tsx").read_text(encoding="utf-8")
timeline = (ROOT / "ui" / "src" / "features" / "stories" / "StoryProductionTimeline.tsx").read_text(encoding="utf-8")
assert "StoryProductionTimeline" in story
assert "{ id: 'assembly', label: 'Montaje', icon: Play }" in story
assert "initiallyOpen={index === 0}" in story
catalog_es = (ROOT / "ui" / "src" / "i18n" / "locales" / "es" / "storyLab.json").read_text(encoding="utf-8")
assert "id: 'assembly'" in tabs
assert "icon: Play" in tabs
assert "StoryProductionTimeline" in assembly
assert "initiallyOpen={index === 0}" in assembly
assert '"assembly": "Montaje"' in catalog_es
assert "View ordered clips" in timeline and "Play all" in timeline
assert "Edit/regenerate clips" in timeline and "Join clips" in timeline
assert "fetchSavedPipeline" in timeline
Expand Down
21 changes: 15 additions & 6 deletions tests/test_story_lab_trailer_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
ROOT = Path(__file__).resolve().parents[1]
STORIES = ROOT / "ui" / "src" / "features" / "stories"
PANEL = STORIES / "StoryLabPanel.tsx"
TABS = STORIES / "storyLabTabs.ts"
TRAILER = STORIES / "StoryTrailerTab.tsx"
COMPACT = STORIES / "CompactVideoWorkspace.tsx"
VIDEO_FORMAT = STORIES / "storyLabVideoFormat.ts"
Expand All @@ -22,7 +23,7 @@
def story_lab_trailer_ui() -> str:
extracted = sorted(STORIES.glob("StoryTrailer*.tsx")) + sorted(STORIES.glob("Compact*.tsx"))
return "\n".join(path.read_text(encoding="utf-8") for path in (
PANEL, TRAILER, COMPACT, VIDEO_FORMAT, VIDEO_CONTROLS, CATALOG_EN, CATALOG_ES, *extracted,
PANEL, TABS, TRAILER, COMPACT, VIDEO_FORMAT, VIDEO_CONTROLS, CATALOG_EN, CATALOG_ES, *extracted,
))


Expand All @@ -38,13 +39,19 @@ def test_trailer_is_a_standalone_story_project_type_without_music():
types = TYPES.read_text(encoding="utf-8")
model = MODEL.read_text(encoding="utf-8")
panel = PANEL.read_text(encoding="utf-8")
tabs = TABS.read_text(encoding="utf-8")
catalog_en = CATALOG_EN.read_text(encoding="utf-8")
catalog_es = CATALOG_ES.read_text(encoding="utf-8")
backend = ROOT.joinpath("app", "_launch_runtime.py").read_text(encoding="utf-8")

assert "'full_story' | 'music_video' | 'trailer' | 'quick_video'" in types
assert "projectType === 'trailer' ? 60" in model
assert "{ id: 'trailer', label: 'Tráiler cinematográfico'" in panel
assert "{ id: 'trailer', label: 'Crear tráiler'" in panel
assert "No escribirá ni exigirá una canción" in CATALOG_ES.read_text(encoding="utf-8")
assert "id: 'trailer'" in tabs
assert '"label": "Cinematic trailer"' in catalog_en
assert '"label": "Tráiler cinematográfico"' in catalog_es
assert '"createTrailer": "Create trailer"' in catalog_en
assert '"createTrailer": "Crear tráiler"' in catalog_es
assert "No escribirá ni exigirá una canción" in catalog_es
assert "musicVideoGenerationMode: 'image_guided' as const" in panel
assert "project.projectType === 'trailer' ? 'trailer' : 'productions'" in panel
assert 'if project_type in {"trailer", "quick_video"}' in backend
Expand All @@ -66,10 +73,12 @@ def test_trailer_adapter_enforces_a_story_arc_without_revealing_the_ending():

def test_story_lab_exposes_editable_trailer_controls_and_timed_preview():
source = story_lab_trailer_ui()
panel = PANEL.read_text(encoding="utf-8")
tabs = TABS.read_text(encoding="utf-8")
trailer = source

assert "{ id: 'trailer', label: 'Tráiler'" in panel
assert "id: 'trailer'" in tabs
assert '"trailer": "Trailer"' in CATALOG_EN.read_text(encoding="utf-8")
assert '"trailer": "Tráiler"' in CATALOG_ES.read_text(encoding="utf-8")
assert "Creador de tráileres cinematográficos" in source
assert "TRAILER_ARC.map" in trailer
assert "setTrailerDuration" in trailer
Expand Down
9 changes: 9 additions & 0 deletions ui/scripts/check-i18n-catalogs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ const PILOT_FILES = [
'src/features/workspaceCollections/WorkspaceCollectionsPanel.tsx',
'src/features/workspaces/WorkspacesPanel.tsx',
'src/features/stories/storyLabChrome.tsx',
'src/features/stories/StoryLabNavigation.tsx',
'src/features/stories/storyLabTabs.ts',
'src/features/stories/StoryLabLibraryChrome.tsx',
'src/features/stories/StoryAssemblyTab.tsx',
'src/features/stories/StoryOverviewTab.tsx',
'src/features/stories/StoryAssetsTab.tsx',
'src/features/stories/StoryAssetsImporter.tsx',
Expand Down Expand Up @@ -107,6 +111,11 @@ const FORBIDDEN = [
'Cinematic trailer creator',
'Generate / refresh Lyria prompt',
'Import custom MP3',
'Montaje de producciones',
'Smart assets',
'Guided · approve stages',
'Preparar historia completa · solo texto',
'Desliza para más secciones',
]

export function forbiddenLiterals() {
Expand Down
48 changes: 48 additions & 0 deletions ui/src/features/stories/StoryAssemblyTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useUiTranslation } from '../../i18n'
import { button, panel } from './storyLabChrome'
import { StoryProductionTimeline } from './StoryProductionTimeline'
import type { StoryProject } from './types'

export function StoryAssemblyTab({
project, reopenProduction, restoreProductionSource,
}: {
project: StoryProject
reopenProduction: (id: string) => void
restoreProductionSource: (id: string) => void
}) {
const { t } = useUiTranslation('storyLab')
const productions = [...project.productions].reverse()
return (
<div className={panel}>
<div className="mb-4">
<h2 className="text-lg font-semibold text-text-primary">{t('assembly.title')}</h2>
<p className="mt-1 text-xs text-text-muted">{t('assembly.description')}</p>
</div>
{productions.length ? productions.map((item, index) => (
<div key={item.id} className="border-b border-border py-3 text-xs last:border-0">
<div className="flex flex-col justify-between gap-2 lg:flex-row lg:items-center">
<div>
<span className="text-text-primary capitalize">
{item.kind === 'music_video' ? t('assembly.musicVideo') : item.kind} · {item.targetName || item.title}
</span>
<span className="ml-2 text-text-muted">
{t('assembly.sourceMeta', { version: item.sourceVersion, when: new Date(item.createdAt).toLocaleString() })}
</span>
{item.sourceSnapshot?.sectionVersions
&& JSON.stringify(item.sourceSnapshot.sectionVersions) !== JSON.stringify(project.sectionVersions) && (
<span className="ml-2 text-amber-300">{t('assembly.sourceChanged')}</span>
)}
</div>
<div className="flex gap-2">
<button className={button} onClick={() => reopenProduction(item.id)}>{t('assembly.reopen')}</button>
{item.sourceSnapshot && (
<button className={button} onClick={() => restoreProductionSource(item.id)}>{t('assembly.restore')}</button>
)}
</div>
</div>
<StoryProductionTimeline production={item} initiallyOpen={index === 0} />
</div>
)) : <p className="text-xs text-text-muted">{t('assembly.empty')}</p>}
</div>
)
}
Loading
Loading