Explanation:
**Checking commit status**

I need to commit my changes, so first, I should check the status to see what’s been modified. I’ll also want to look at the differences in the files to ensure everything is correct before proceeding. It’s important to be thorough here, so I can avoid any surprises later. Let’s take a moment to go through the status and see what’s been updated. I'll ensure my work is ready for a clean commit!

git status --short && git diff -- README.md autoshorts/rendering/manifest.py tests/test_render_manifest.py | sed -n '1,260p'


terminal

Output:
M README.md
?? autoshorts/rendering/manifest.py
?? tests/test_render_manifest.py
diff --git a/README.md b/README.md
index d3493bd..3fce936 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,7 @@ Implemented foundation:
 - `autoshorts.ideas.factory` — draft-to-candidate factory with stable ids, default hashtags, and duration classes.
 - `autoshorts.ideas.testbatch` — testbatch validation and renderable-candidate filtering.
 - `autoshorts.review.approvals` — deterministic Telegram approval command parser.
+- `autoshorts.rendering.manifest` — side-effect-free vertical render manifest generation from shot plans.
 - `autoshorts.rendering.shot_plan` — retention-led shot planner before render manifests or video generation.
 - `autoshorts.review.package` — Telegram-friendly review package renderer for human approval.
 - `autoshorts.review.render` — renders complete review batches into Telegram-safe text.
@@ -78,6 +79,19 @@ Each `ShotScene` carries timing, voiceover beat, visual action, asset mode, capt

 `validate_shot_plan()` rejects static slideshow asset modes, baked-in fake UI text, non-renderer-owned labels/captions, bad timing, missing fields, and plans whose first scene is not the first-frame hook.

+`autoshorts.rendering.manifest.build_render_manifest()` turns a validated `ShotPlan` into a machine-readable `RenderManifest` with:
+
+- vertical `1080x1920` render settings;
+- `30` fps;
+- one timeline item per shot scene;
+- motion instructions inherited from the shot plan;
+- deterministic placeholder asset paths;
+- renderer-owned caption and label layers;
+- `no_generated_text_inside_assets=True`;
+- `static_card_limit=0`.
+
+`validate_render_manifest()` rejects unsafe render settings, generated text inside assets, non-renderer-owned captions/labels, static slideshow timeline items, missing timeline fields, and bad timeline timing. The manifest is still a preparation artifact only; it renders nothing and calls no external API.
+
 ## Human approval commands

 Supported review responses:


git add README.md autoshorts/rendering/manifest.py tests/test_render_manifest.py && git commit -m "feat: add render manifest planning" && git log --oneline -n 8


Output:
[feat/core-approval-quality-gates e258635] feat: add render manifest planning
 3 files changed, 281 insertions(+)
 create mode 100644 autoshorts/rendering/manifest.py
 create mode 100644 tests/test_render_manifest.py
e258635 feat: add render manifest planning
d118a76 feat: add retention shot planning
6e6569a feat: add content demo review pipeline
72dc887 feat: convert content drafts to video candidates
9c125f9 feat: add content brief and script draft models
53b45ca feat: add demo telegram delivery cli
54863dd feat: add telegram review delivery preparation
dc1c59c feat: add demo review batch cli


Explanation:
**Finalizing German text**

I realize I need to wrap up the German text in a concise manner. But I need to verify that it's clean and correctly formatted before finalizing it. It’s important for me to ensure everything looks good, so I'll double-check for any errors or awkward phrasing. I want to provide something polished and accurate. Let's make sure it's ready to go!


Erledigt, Sir.

## Neu eingebaut: Render Manifest Planning

Neue Dateien:

```text
autoshorts/rendering/manifest.py
tests/test_render_manifest.py
```

## Neue API

```python
build_render_manifest(plan: ShotPlan) -> RenderManifest
validate_render_manifest(manifest: RenderManifest) -> ValidationResult
```

Der Flow ist jetzt:

```text
ScriptDraft
→ ShotPlan
→ RenderManifest
```

## RenderManifest-Struktur

```python
RenderManifest(
    script_id=...,
    title=...,
    settings=RenderSettings(...),
    timeline=(...)
)
```

### Render Settings

Aktuell hart und bewusst konservativ:

```python
RenderSettings(
    width=1080,
    height=1920,
    fps=30,
    no_generated_text_inside_assets=True,
    captions_renderer_owned=True,
    static_card_limit=0,
)
```

Also vertikal, Shorts-tauglich, captions/labels im Renderer — nicht in generierten Assets eingebrannt. Genau richtig, weil AI-Modelle bei Text gern aussehen, als hätte ein Drucker Dämonenbeschwörung gelernt.

## Timeline Items

Pro ShotScene entsteht ein `RenderTimelineItem` mit:

```text
scene_purpose
start_seconds
end_seconds
asset_mode
motion_instruction
placeholder_asset_path
caption_layer
label_layer
```

Beispiel verifiziert:

```text
placeholders/ai_life_systems-ai-weekly-review-routine/01-first_frame_hook.png
```

## Guardrails

`validate_render_manifest()` rejected:

- falsches Format statt `1080x1920`
- falsche FPS statt `30`
- generated text inside assets
- nicht renderer-owned captions
- `static_card_limit != 0`
- leere Timeline
- schlechte Timeline-Timings
- `static_slideshow`
- nicht renderer-owned caption/label layers
- fehlende Pflichtfelder

## TDD eingehalten

RED:

```text
ModuleNotFoundError: No module named 'autoshorts.rendering.manifest'
```

GREEN:

```text
5 passed
```

Full suite:

```text
74 passed in 0.11s
```

## Manuell verifiziert

```text
ai_life_systems-ai-weekly-review-routine
(1080, 1920, 30)
5
placeholders/ai_life_systems-ai-weekly-review-routine/01-first_frame_hook.png
True
```

## README aktualisiert

Dokumentiert:

- `autoshorts.rendering.manifest`
- RenderManifest
- Timeline Items
- Vertical Settings
- Safety Constraints
- keine Render-/API-Side-Effects

## Commit

Branch:

```text
feat/core-approval-quality-gates
```

Neuer Commit:

```text
e258635 feat: add render manifest planning
```

Arbeitsverzeichnis ist sauber.

## Nächster sinnvoller Schritt

Jetzt können wir einen lokalen Preview-Renderer bauen — weiterhin ohne ComfyUI/Wan/Qwen/API:

```text
RenderManifest
→ Local Preview Frames / HTML Preview
```

Ich würde als nächstes bauen:

```text
autoshorts/rendering/preview.py
tests/test_render_preview.py
```

Ziel:

- aus `RenderManifest` eine side-effect-free oder temp-dir-basierte Preview erzeugen
- erstmal als HTML/SVG Timeline oder simple frame manifest
- keine echten AI Assets
- keine Videos
- keine externen Calls
- aber sichtbar genug, um Timing, Captions, Scene Flow und Motion Instructions zu prüfen

Danach erst Qwen-Image/Wan/ComfyUI anbinden. Erst die Regie, dann die GPU. Sonst macht die Maschine teure Kunst mit null dramaturgischem Rückgrat.


Explanation:
**Considering skill loading**

I think we need to continue by loading the relevant skill for Test-Driven Development (TDD). Each turn, I should scan for skills, but maybe it's unnecessary to load again if they're already there. I need to assess whether to implement a preview and inspect what tools are needed. So, it feels essential to identify any relevant skills before proceeding, but I’m wondering if I might be overcomplicating things!


test-driven-development

---
name: test-driven-development
description: "TDD: enforce RED-GREEN-REFACTOR, tests before code."
version: 1.1.0
author: Hermes Agent (adapted from obra/superpowers)
license: MIT
platforms: [linux, macos, windows]
metadata:
  hermes:
    tags: [testing, tdd, development, quality, red-green-refactor]
    related_skills: [systematic-debugging, writing-plans, subagent-driven-development]
---

# Test-Driven Development (TDD)

## Overview

Write the test first. Watch it fail. Write minimal code to pass.

**Core principle:** If you didn't watch the test fail, you don't know if it tests the right thing.

**Violating the letter of the rules is violating the spirit of the rules.**

## When to Use

**Always:**
- New features
- Bug fixes
- Refactoring
- Behavior changes

**Exceptions (ask the user first):**
- Throwaway prototypes
- Generated code
- Configuration files

Thinking "skip TDD just this once"? Stop. That's rationalization.

## The Iron Law

```
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
```

Write code before the test? Delete it. Start over.

**No exceptions:**
- Don't keep it as "reference"
- Don't "adapt" it while writing tests
- Don't look at it
- Delete means delete

Implement fresh from tests. Period.

## Red-Green-Refactor Cycle

### RED — Write Failing Test

Write one minimal test showing what should happen.

**Good test:**
```python
def test_retries_failed_operations_3_times():
    attempts = 0
    def operation():
        nonlocal attempts
        attempts += 1
        if attempts < 3:
            raise Exception('fail')
        return 'success'

    result = retry_operation(operation)

    assert result == 'success'
    assert attempts == 3
```
Clear name, tests real behavior, one thing.

**Bad test:**
```python
def test_retry_works():
    mock = MagicMock()
    mock.side_effect = [Exception(), Exception(), 'success']
    result = retry_operation(mock)
    assert result == 'success'  # What about retry count? Timing?
```
Vague name, tests mock not real code.

**Requirements:**
- One behavior per test
- Clear descriptive name ("and" in name? Split it)
- Real code, not mocks (unless truly unavoidable)
- Name describes behavior, not implementation

### Verify RED — Watch It Fail

**MANDATORY. Never skip.**

```bash
# Use terminal tool to run the specific test
pytest tests/test_feature.py::test_specific_behavior -v
```

Confirm:
- Test fails (not errors from typos)
- Failure message is expected
- Fails because the feature is missing

**Test passes immediately?** You're testing existing behavior. Fix the test.

**Test errors?** Fix the error, re-run until it fails correctly.

### GREEN — Minimal Code

Write the simplest code to pass the test. Nothing more.

**Good:**
```python
def add(a, b):
    return a + b  # Nothing extra
```

**Bad:**
```python
def add(a, b):
    result = a + b
    logging.info(f"Adding {a} + {b} = {result}")  # Extra!
    return result
```

Don't add features, refactor other code, or "improve" beyond the test.

**Cheating is OK in GREEN:**
- Hardcode return values
- Copy-paste
- Duplicate code
- Skip edge cases

We'll fix it in REFACTOR.

### Verify GREEN — Watch It Pass

**MANDATORY.**

```bash
# Run the specific test
pytest tests/test_feature.py::test_specific_behavior -v

# Then run ALL tests to check for regressions
pytest tests/ -q
```

Confirm:
- Test passes
- Other tests still pass
- Output pristine (no errors, warnings)

**Test fails?** Fix the code, not the test.

**Other tests fail?** Fix regressions now.

### REFACTOR — Clean Up

After green only:
- Remove duplication
- Improve names
- Extract helpers
- Simplify expressions

Keep tests green throughout. Don't add behavior.

**If tests fail during refactor:** Undo immediately. Take smaller steps.

### Repeat

Next failing test for next behavior. One cycle at a time.

## Why Order Matters

**"I'll write tests after to verify it works"**

Tests written after code pass immediately. Passing immediately proves nothing:
- Might test the wrong thing
- Might test implementation, not behavior
- Might miss edge cases you forgot
- You never saw it catch the bug

Test-first forces you to see the test fail, proving it actually tests something.

**"I already manually tested all the edge cases"**

Manual testing is ad-hoc. You think you tested everything but:
- No record of what you tested
- Can't re-run when code changes
- Easy to forget cases under pressure
- "It worked when I tried it" ≠ comprehensive

Automated tests are systematic. They run the same way every time.

**"Deleting X hours of work is wasteful"**

Sunk cost fallacy. The time is already gone. Your choice now:
- Delete and rewrite with TDD (high confidence)
- Keep it and add tests after (low confidence, likely bugs)

The "waste" is keeping code you can't trust.

**"TDD is dogmatic, being pragmatic means adapting"**

TDD IS pragmatic:
- Finds bugs before commit (faster than debugging after)
- Prevents regressions (tests catch breaks immediately)
- Documents behavior (tests show how to use code)
- Enables refactoring (change freely, tests catch breaks)

"Pragmatic" shortcuts = debugging in production = slower.

**"Tests after achieve the same goals — it's spirit not ritual"**

No. Tests-after answer "What does this do?" Tests-first answer "What should this do?"

Tests-after are biased by your implementation. You test what you built, not what's required. Tests-first force edge case discovery before implementing.

## Common Rationalizations

| Excuse | Reality |
|--------|---------|
| "Too simple to test" | Simple code breaks. Test takes 30 seconds. |
| "I'll test after" | Tests passing immediately prove nothing. |
| "Tests after achieve same goals" | Tests-after = "what does this do?" Tests-first = "what should this do?" |
| "Already manually tested" | Ad-hoc ≠ systematic. No record, can't re-run. |
| "Deleting X hours is wasteful" | Sunk cost fallacy. Keeping unverified code is technical debt. |
| "Keep as reference, write tests first" | You'll adapt it. That's testing after. Delete means delete. |
| "Need to explore first" | Fine. Throw away exploration, start with TDD. |
| "Test hard = design unclear" | Listen to the test. Hard to test = hard to use. |
| "TDD will slow me down" | TDD faster than debugging. Pragmatic = test-first. |
| "Manual test faster" | Manual doesn't prove edge cases. You'll re-test every change. |
| "Existing code has no tests" | You're improving it. Add tests for the code you touch. |

## Red Flags — STOP and Start Over

If you catch yourself doing any of these, delete the code and restart with TDD:

- Code before test
- Test after implementation
- Test passes immediately on first run
- Can't explain why test failed
- Tests added "later"
- Rationalizing "just this once"
- "I already manually tested it"
- "Tests after achieve the same purpose"
- "Keep as reference" or "adapt existing code"
- "Already spent X hours, deleting is wasteful"
- "TDD is dogmatic, I'm being pragmatic"
- "This is different because..."

**All of these mean: Delete code. Start over with TDD.**

## Verification Checklist

Before marking work complete:

- [ ] Every new function/method has a test
- [ ] Watched each test fail before implementing
- [ ] Each test failed for expected reason (feature missing, not typo)
- [ ] Wrote minimal code to pass each test
- [ ] All tests pass
- [ ] Output pristine (no errors, warnings)
- [ ] Tests use real code (mocks only if unavoidable)
- [ ] Edge cases and errors covered

Can't check all boxes? You skipped TDD. Start over.

## When Stuck

| Problem | Solution |
|---------|----------|
| Don't know how to test | Write the wished-for API. Write the assertion first. Ask the user. |
| Test too complicated | Design too complicated. Simplify the interface. |
| Must mock everything | Code too coupled. Use dependency injection. |
| Test setup huge | Extract helpers. Still complex? Simplify the design. |

## Hermes Agent Integration

### Running Tests

Use the `terminal` tool to run tests at each step:

```python
# RED — verify failure
terminal("pytest tests/test_feature.py::test_name -v")

# GREEN — verify pass
terminal("pytest tests/test_feature.py::test_name -v")

# Full suite — verify no regressions
terminal("pytest tests/ -q")
```

### CLI Entry Points

For new `python -m package.cli.command` style entry points, keep the TDD loop boring and side-effect free:

1. Write a test that imports `main` from the missing CLI module and calls `main([])`.
2. Capture stdout/stderr with the project test framework (`capsys` in pytest) or inject a `stdout` file-like object if supported.
3. RED should usually be an import failure for the missing CLI module/package, not a vague end-to-end shell failure.
4. Implement `main(argv: Sequence[str] | None = None, *, stdout: TextIO | None = None) -> int` so tests can call it without spawning a subprocess.
5. Add the `if __name__ == "__main__": raise SystemExit(main())` guard only after the callable path is green.
6. Verify both the unit test and the real module invocation, e.g. `python -m package.cli.command`, before committing.

This catches command wiring while avoiding accidental network calls, publishing actions, or other theatrical nonsense masquerading as a CLI test.

### With delegate_task

When dispatching subagents for implementation, enforce TDD in the goal:

```python
delegate_task(
    goal="Implement [feature] using strict TDD",
    context="""
    Follow test-driven-development skill:
    1. Write failing test FIRST
    2. Run test to verify it fails
    3. Write minimal code to pass
    4. Run test to verify it passes
    5. Refactor if needed
    6. Commit

    Project test command: pytest tests/ -q
    Project structure: [describe relevant files]
    """,
    toolsets=['terminal', 'file']
)
```

### With systematic-debugging

Bug found? Write failing test reproducing it. Follow TDD cycle. The test proves the fix and prevents regression.

Never fix bugs without a test.

## Demo / Fixture Features

When adding a deterministic demo fixture for later integration wiring, still use RED-GREEN-REFACTOR. Write the wished-for import/API in the test first, verify the missing module/API failure, then implement the smallest fixture that proves the real workflow path. Assert stable IDs/order, bucket counts/classifications, and safety invariants such as review text not implying publication. See `references/deterministic-demo-fixtures.md` for the concise pattern.

## Side-Effect-Free Integration Boundaries

When building toward an external integration (Telegram, email, publishing APIs, render farms, payment flows, etc.), add a tested preparation boundary before any real I/O:

1. Write the wished-for pure function in the test first, e.g. `prepare_review_delivery(text) -> DeliveryPlan`.
2. RED should be a missing module/API or missing validation failure, not a real network/send failure.
3. The GREEN implementation should return a typed/dataclass plan object with explicit metadata: destination/topic, delivery type, human-approval requirement, and `side_effects=()`.
4. Validate safety constraints at the boundary: non-empty content, platform size limits, no forbidden action language, no implicit approval, no credentials or generated media unless explicitly expected.
5. Add a demo CLI only after the pure boundary is green. The CLI should print metadata first, then payload, and still perform no external I/O.
6. Verify both the unit tests and a real `python -m package.cli.demo_*` invocation before committing.

This pattern lets later sending/rendering/publishing adapters consume a boring, audited object instead of smuggling side effects into formatting code. Boring is how we avoid building a content catapult with a moustache.

### Media / Rendering Pipeline Boundaries

When building toward image/video generation or renderer integration, add deterministic planning artifacts before any GPU/API work:

1. Write tests for the wished-for pure API first, e.g. `create_shot_plan(script)`, `build_render_manifest(plan)`, or `prepare_preview(manifest)`.
2. RED should be a missing module/API or missing validation behavior — never a failed ComfyUI/Wan/Qwen/API invocation.
3. Keep each stage inspectable and side-effect-free until the explicit adapter layer: `ScriptDraft -> ShotPlan -> RenderManifest -> PreviewPlan/PreviewArtifact -> renderer adapter`.
4. Assert stable IDs/order, scene counts, timing continuity, vertical settings (`1080x1920`, `30fps` when building Shorts/Reels/TikTok), and placeholder paths before generating media.
5. Add guardrail tests that reject static slideshows, baked-in/generated text inside assets, non-renderer-owned captions/labels, unsafe render settings, missing fields, and bad timings.
6. Only after the manifest/preview boundary is green should a later adapter call local GPU tools or external services. Planning first; GPU roulette later.

This prevents wasting render time on unclear direction and keeps expensive media generation behind a small, audited boundary.

## Testing Anti-Patterns

- **Testing mock behavior instead of real behavior** — mocks should verify interactions, not replace the system under test
- **Testing implementation details** — test behavior/results, not internal method calls
- **Happy path only** — always test edge cases, errors, and boundaries
- **Brittle tests** — tests should verify behavior, not structure; refactoring shouldn't break them
- **Substring sentinels that collide with generated IDs** — when asserting that amounts or fixture values are excluded from a result, assert the full formatted token (e.g. `999.00`) or a typed field, not a short substring like `999` that can appear inside UUIDs/hash-like IDs.
- **Domain classes named `Test*` trigger pytest collection warnings** — if a production dataclass/model must be named like `TestBatch`, set `__test__ = False` on the class or choose a non-`Test` prefix. Keep the fix in production code when the name is part of the domain language, and verify the warning is gone before committing.
- **Defaulting with `x or default` hides invalid explicit inputs** — when a factory/test helper accepts optional overrides, use `x if x is not None else default` so tests can pass intentionally blank strings (`""`) or zero values and watch validation reject them. Otherwise invalid-input tests may silently receive defaults and give false confidence.

## Final Rule

```
Production code → test exists and failed first
Otherwise → not TDD
```

No exceptions without the user's explicit permission.

To view linked files, call skill_view(name, file_path) where file_path is e.g. 'references/api.md' or 'assets/config.yaml'


terminal

git status --short && git branch --show-current


Output: feat/core-approval-quality-gates


     1|"""Side-effect-free render manifest generation from shot plans."""
     2|
     3|from __future__ import annotations
     4|
     5|from dataclasses import dataclass
     6|
     7|from autoshorts.ideas.candidate import ValidationResult
     8|from autoshorts.rendering.shot_plan import ShotPlan, validate_shot_plan
     9|
    10|
    11|@dataclass(frozen=True)
    12|class RenderSettings:
    13|    width: int = 1080
    14|    height: int = 1920
    15|    fps: int = 30
    16|    no_generated_text_inside_assets: bool = True
    17|    captions_renderer_owned: bool = True
    18|    static_card_limit: int = 0
    19|
    20|
    21|@dataclass(frozen=True)
    22|class RenderLayer:
    23|    text: str
    24|    renderer_owned: bool = True
    25|
    26|
    27|@dataclass(frozen=True)
    28|class RenderTimelineItem:
    29|    scene_purpose: str
    30|    start_seconds: int
    31|    end_seconds: int
    32|    asset_mode: str
    33|    motion_instruction: str
    34|    placeholder_asset_path: str
    35|    caption_layer: RenderLayer
    36|    label_layer: RenderLayer
    37|
    38|
    39|@dataclass(frozen=True)
    40|class RenderManifest:
    41|    script_id: str
    42|    title: str
    43|    settings: RenderSettings
    44|    timeline: tuple[RenderTimelineItem, ...]
    45|
    46|
    47|def _is_blank(value: str) -> bool:
    48|    return not (value or "").strip()
    49|
    50|
    51|def _placeholder_path(script_id: str, index: int, purpose: str) -> str:
    52|    return f"placeholders/{script_id}/{index:02d}-{purpose}.png"
    53|
    54|
    55|def build_render_manifest(plan: ShotPlan) -> RenderManifest:
    56|    """Turn a validated shot plan into a machine-readable render manifest."""
    57|
    58|    validation = validate_shot_plan(plan)
    59|    if not validation.is_valid:
    60|        raise ValueError("; ".join(validation.errors))
    61|
    62|    timeline = tuple(
    63|        RenderTimelineItem(
    64|            scene_purpose=scene.purpose,
    65|            start_seconds=scene.start_seconds,
    66|            end_seconds=scene.end_seconds,
    67|            asset_mode=scene.asset_mode,
    68|            motion_instruction=scene.visual_action,
    69|            placeholder_asset_path=_placeholder_path(plan.script_id, index, scene.purpose),
    70|            caption_layer=RenderLayer(text=scene.caption_beat, renderer_owned=True),
    71|            label_layer=RenderLayer(text=scene.purpose.replace("_", " "), renderer_owned=True),
    72|        )
    73|        for index, scene in enumerate(plan.scenes, start=1)
    74|    )
    75|    return RenderManifest(
    76|        script_id=plan.script_id,
    77|        title=plan.title,
    78|        settings=RenderSettings(),
    79|        timeline=timeline,
    80|    )
    81|
    82|
    83|def validate_render_manifest(manifest: RenderManifest) -> ValidationResult:
    84|    errors: list[str] = []
    85|    if _is_blank(manifest.script_id):
    86|        errors.append("script_id is required")
    87|    if _is_blank(manifest.title):
    88|        errors.append("title is required")
    89|
    90|    settings = manifest.settings
    91|    if (settings.width, settings.height) != (1080, 1920):
    92|        errors.append("render size must be vertical 1080x1920")
    93|    if settings.fps != 30:
    94|        errors.append("fps must be 30")
    95|    if not settings.no_generated_text_inside_assets:
    96|        errors.append("generated text inside assets is not allowed")
    97|    if not settings.captions_renderer_owned:
    98|        errors.append("captions must be renderer-owned")
    99|    if settings.static_card_limit != 0:
   100|        errors.append("static_card_limit must be 0")
   101|
   102|    if not manifest.timeline:
   103|        errors.append("at least one timeline item is required")
   104|
   105|    previous_end: int | None = None
   106|    for index, item in enumerate(manifest.timeline, start=1):
   107|        if _is_blank(item.scene_purpose):
   108|            errors.append(f"timeline item {index} scene_purpose is required")
   109|        if item.end_seconds <= item.start_seconds:
   110|            errors.append(f"timeline item {index} end_seconds must be greater than start_seconds")
   111|        if previous_end is not None and item.start_seconds < previous_end:
   112|            errors.append(f"timeline item {index} start_seconds overlaps previous item")
   113|        previous_end = item.end_seconds
   114|
   115|        for field_name, value in {
   116|            "asset_mode": item.asset_mode,
   117|            "motion_instruction": item.motion_instruction,
   118|            "placeholder_asset_path": item.placeholder_asset_path,
   119|            "caption_layer text": item.caption_layer.text,
   120|            "label_layer text": item.label_layer.text,
   121|        }.items():
   122|            if _is_blank(value):
   123|                errors.append(f"timeline item {index} {field_name} is required")
   124|
   125|        if item.asset_mode == "static_slideshow":
   126|            errors.append("static slideshow asset mode is not allowed")
   127|        if "baked-in" in item.motion_instruction.casefold():
   128|            errors.append("baked-in text in render assets is not allowed")
   129|        if not item.caption_layer.renderer_owned:
   130|            errors.append(f"timeline item {index} caption layer must be renderer-owned")
   131|        if not item.label_layer.renderer_owned:
   132|            errors.append(f"timeline item {index} label layer must be renderer-owned")
   133|
   134|    return ValidationResult(is_valid=not errors, errors=tuple(errors))
   135|


     1|import pytest
     2|
     3|from autoshorts.content.brief import create_content_brief
     4|from autoshorts.content.script_draft import create_script_draft
     5|from autoshorts.rendering.manifest import (
     6|    RenderManifest,
     7|    RenderSettings,
     8|    build_render_manifest,
     9|    validate_render_manifest,
    10|)
    11|from autoshorts.rendering.shot_plan import ShotPlan, ShotScene, create_shot_plan
    12|
    13|
    14|def _shot_plan():
    15|    brief = create_content_brief(
    16|        pillar_id="ai_life_systems",
    17|        topic="AI weekly review routine",
    18|        audience_problem="I use AI tools but still lose control of priorities.",
    19|        payoff="a 10-minute Sunday review that keeps AI as assistant, not boss",
    20|    )
    21|    script = create_script_draft(brief)
    22|    return create_shot_plan(script)
    23|
    24|
    25|def test_build_render_manifest_turns_shot_plan_into_vertical_timeline():
    26|    plan = _shot_plan()
    27|
    28|    manifest = build_render_manifest(plan)
    29|
    30|    assert isinstance(manifest, RenderManifest)
    31|    assert manifest.script_id == "ai_life_systems-ai-weekly-review-routine"
    32|    assert manifest.title == "AI weekly review routine"
    33|    assert manifest.settings == RenderSettings(
    34|        width=1080,
    35|        height=1920,
    36|        fps=30,
    37|        no_generated_text_inside_assets=True,
    38|        captions_renderer_owned=True,
    39|        static_card_limit=0,
    40|    )
    41|    assert len(manifest.timeline) == len(plan.scenes)
    42|    assert [item.scene_purpose for item in manifest.timeline] == [scene.purpose for scene in plan.scenes]
    43|    assert manifest.timeline[0].start_seconds == 0
    44|    assert manifest.timeline[-1].end_seconds == 35
    45|    assert manifest.timeline[0].placeholder_asset_path == (
    46|        "placeholders/ai_life_systems-ai-weekly-review-routine/01-first_frame_hook.png"
    47|    )
    48|    assert all(item.caption_layer.renderer_owned for item in manifest.timeline)
    49|    assert all(item.label_layer.renderer_owned for item in manifest.timeline)
    50|    assert all("static" not in item.motion_instruction.casefold() for item in manifest.timeline)
    51|
    52|
    53|def test_validate_render_manifest_accepts_default_manifest():
    54|    result = validate_render_manifest(build_render_manifest(_shot_plan()))
    55|
    56|    assert result.is_valid is True
    57|    assert result.errors == ()
    58|
    59|
    60|def test_build_render_manifest_rejects_invalid_shot_plan():
    61|    invalid_plan = ShotPlan(
    62|        script_id="script",
    63|        title="Title",
    64|        estimated_duration_seconds=35,
    65|        scenes=(
    66|            ShotScene(
    67|                purpose="first_frame_hook",
    68|                start_seconds=0,
    69|                end_seconds=3,
    70|                voiceover_beat="Hook",
    71|                visual_action="Static card",
    72|                asset_mode="static_slideshow",
    73|                caption_beat="Hook",
    74|                retention_goal="Stop scroll",
    75|            ),
    76|        ),
    77|    )
    78|
    79|    with pytest.raises(ValueError, match="static slideshow asset mode is not allowed"):
    80|        build_render_manifest(invalid_plan)
    81|
    82|
    83|def test_validate_render_manifest_rejects_unsafe_render_settings():
    84|    manifest = build_render_manifest(_shot_plan())
    85|    unsafe = RenderManifest(
    86|        script_id=manifest.script_id,
    87|        title=manifest.title,
    88|        settings=RenderSettings(
    89|            width=1920,
    90|            height=1080,
    91|            fps=24,
    92|            no_generated_text_inside_assets=False,
    93|            captions_renderer_owned=False,
    94|            static_card_limit=1,
    95|        ),
    96|        timeline=manifest.timeline,
    97|    )
    98|
    99|    result = validate_render_manifest(unsafe)
   100|
   101|    assert result.is_valid is False
   102|    assert "render size must be vertical 1080x1920" in result.errors
   103|    assert "fps must be 30" in result.errors
   104|    assert "generated text inside assets is not allowed" in result.errors
   105|    assert "captions must be renderer-owned" in result.errors
   106|    assert "static_card_limit must be 0" in result.errors
   107|
   108|
   109|def test_validate_render_manifest_rejects_timeline_items_without_renderer_owned_layers():
   110|    manifest = build_render_manifest(_shot_plan())
   111|    first = manifest.timeline[0]
   112|    bad_item = type(first)(
   113|        scene_purpose=first.scene_purpose,
   114|        start_seconds=first.start_seconds,
   115|        end_seconds=first.end_seconds,
   116|        asset_mode=first.asset_mode,
   117|        motion_instruction=first.motion_instruction,
   118|        placeholder_asset_path=first.placeholder_asset_path,
   119|        caption_layer=type(first.caption_layer)(text=first.caption_layer.text, renderer_owned=False),
   120|        label_layer=type(first.label_layer)(text=first.label_layer.text, renderer_owned=False),
   121|    )
   122|    unsafe = RenderManifest(
   123|        script_id=manifest.script_id,
   124|        title=manifest.title,
   125|        settings=manifest.settings,
   126|        timeline=(bad_item,) + manifest.timeline[1:],
   127|    )
   128|
   129|    result = validate_render_manifest(unsafe)
   130|
   131|    assert result.is_valid is False
   132|    assert "timeline item 1 caption layer must be renderer-owned" in result.errors
   133|    assert "timeline item 1 label layer must be renderer-owned" in result.errors
   134|


Explanation:
**Planning HTML preview tests**

I need to start by writing tests for the preview. There’s some design involved—maybe I’ll work with PreviewArtifact as an HTML string with specific side effects and timeline rows to validate the preview. The user mentioned a temporary directory or a preference for side-effect-free operations, so I’ll focus on creating a side-effect-free HTML string preview. I'll use a function to build an HTML preview from the manifest and ensure the manifest's validation is included. It seems testing will be necessary, and I should avoid publish or post options and lean toward test-driven development (TDD).

import pytest

from autoshorts.content.brief import create_content_brief
from autoshorts.content.script_draft import create_script_draft
from autoshorts.rendering.manifest import RenderManifest, RenderSettings, build_render_manifest
from autoshorts.rendering.preview import RenderPreview, build_html_preview, validate_render_preview
from autoshorts.rendering.shot_plan import create_shot_plan

def _manifest():
    brief = create_content_brief(
        pillar_id="ai_life_systems",
        topic="AI weekly review routine",
        audience_problem="I use AI tools but still lose control of priorities.",
        payoff="a 10-minute Sunday review that keeps AI as assistant, not boss",
    )
    script = create_script_draft(brief)
    return build_render_manifest(create_shot_plan(script))

def test_build_html_preview_creates_side_effect_free_timeline_preview():
    manifest = _manifest()

    preview = build_html_preview(manifest)

    assert isinstance(preview, RenderPreview)
    assert preview.script_id == "ai_life_systems-ai-weekly-review-routine"
    assert preview.preview_type == "html_timeline"
    assert preview.side_effects == ()
    assert preview.external_calls == ()
    assert "<html" in preview.html
    assert "AI weekly review routine" in preview.html
    assert "1080x1920" in preview.html
    assert "30fps" in preview.html
    assert "first_frame_hook" in preview.html
    assert "0–3s" in preview.html
    assert "placeholders/ai_life_systems-ai-weekly-review-routine/01-first_frame_hook.png" in preview.html
    assert "If AI runs your week, you have already lost control." in preview.html

def test_validate_render_preview_accepts_default_preview():
    result = validate_render_preview(build_html_preview(_manifest()))

    assert result.is_valid is True
    assert result.errors == ()

def test_build_html_preview_rejects_invalid_manifest():
    manifest = _manifest()
    invalid = RenderManifest(
        script_id=manifest.script_id,
        title=manifest.title,
        settings=RenderSettings(width=1920, height=1080),
        timeline=manifest.timeline,
    )

    with pytest.raises(ValueError, match="render size must be vertical 1080x1920"):
        build_html_preview(invalid)

def test_validate_render_preview_rejects_side_effects_and_external_calls():
    preview = build_html_preview(_manifest())
    unsafe = RenderPreview(
        script_id=preview.script_id,
        title=preview.title,
        preview_type=preview.preview_type,
        html=preview.html,
        side_effects=("write_file",),
        external_calls=("comfyui",),
    )

    result = validate_render_preview(unsafe)

    assert result.is_valid is False
    assert "preview must not have side effects" in result.errors
    assert "preview must not call external renderers" in result.errors

def test_build_html_preview_escapes_timeline_text():
    manifest = _manifest()
    first = manifest.timeline[0]
    dangerous_item = type(first)(
        scene_purpose=first.scene_purpose,
        start_seconds=first.start_seconds,
        end_seconds=first.end_seconds,
        asset_mode=first.asset_mode,
        motion_instruction="Move <script>alert('x')</script>",
        placeholder_asset_path=first.placeholder_asset_path,
        caption_layer=type(first.caption_layer)(text="Human < approval", renderer_owned=True),
        label_layer=first.label_layer,
    )
    dangerous = RenderManifest(
        script_id=manifest.script_id,
        title=manifest.title,
        settings=manifest.settings,
        timeline=(dangerous_item,) + manifest.timeline[1:],
    )

    preview = build_html_preview(dangerous)

    assert "<script>" not in preview.html
    assert "<script>" in preview.html
    assert "Human < approval" in preview.html


LSP diagnostics introduced by this edit:
<diagnostics file="/home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/tests/test_render_preview.py">
ERROR [6:6] Import "autoshorts.rendering.preview" could not be resolved [reportMissingImports] (Pyright)
</diagnostics>


python -m pytest tests/test_render_preview.py -q


Output:
==================================== ERRORS ====================================
________________ ERROR collecting tests/test_render_preview.py _________________
ImportError while importing test module '/home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/tests/test_render_preview.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
../../../.local/share/uv/python/cpython-3.11.15-linux-x86_64-gnu/lib/python3.11/importlib/__init__.py:126: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_render_preview.py:6: in <module>
    from autoshorts.rendering.preview import RenderPreview, build_html_preview, validate_render_preview
E   ModuleNotFoundError: No module named 'autoshorts.rendering.preview'
=========================== short test summary info ============================
ERROR tests/test_render_preview.py
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
1 error in 0.14s


Explanation:
**Considering implementation details**

I’m thinking about how to implement the manifest validation process. It will only reject baked-in strings, so that’s something to consider. I might need an external_calls type tuple for this. Additionally, I should think about HTML escaping to ensure everything is safe and functional. It feels like I’m making progress, but it’s also a bit tricky ensuring everything fits together, you know?

"""Side-effect-free HTML preview generation for render manifests."""

from __future__ import annotations

from dataclasses import dataclass
from html import escape

from autoshorts.ideas.candidate import ValidationResult
from autoshorts.rendering.manifest import RenderManifest, validate_render_manifest

@dataclass(frozen=True)
class RenderPreview:
    script_id: str
    title: str
    preview_type: str
    html: str
    side_effects: tuple[str, ...] = ()
    external_calls: tuple[str, ...] = ()

def _is_blank(value: str) -> bool:
    return not (value or "").strip()

def _timeline_row(index: int, item) -> str:
    time_range = f"{item.start_seconds}–{item.end_seconds}s"
    return "".join(
        (
            '
',
            f"
{index:02d}. {escape(item.scene_purpose)}
",
            f"
Time: {escape(time_range)}
",
            f"
Asset mode: {escape(item.asset_mode)}
",
            f"
Motion: {escape(item.motion_instruction)}
",
            f"
Caption: {escape(item.caption_layer.text)}
",
            f"
Label: {escape(item.label_layer.text)}
",
            f"
Placeholder: {escape(item.placeholder_asset_path)}
",
            "
",
        )
    )

def build_html_preview(manifest: RenderManifest) -> RenderPreview:
    """Build an inspectable HTML timeline preview without rendering media."""

    validation = validate_render_manifest(manifest)
    if not validation.is_valid:
        raise ValueError("; ".join(validation.errors))

    settings = manifest.settings
    rows = "\n".join(_timeline_row(index, item) for index, item in enumerate(manifest.timeline, start=1))
    html = f"""<!doctype html>

  <meta charset="utf-8">
  <title>{escape(manifest.title)} — render preview</title>

{escape(manifest.title)}

Script: {escape(manifest.script_id)}

Format: {settings.width}x{settings.height}, {settings.fps}fps

Text policy: captions and labels are renderer-owned; no generated text inside assets.

    {rows}

"""
    return RenderPreview(
        script_id=manifest.script_id,
        title=manifest.title,
        preview_type="html_timeline",
        html=html,
        side_effects=(),
        external_calls=(),
    )

def validate_render_preview(preview: RenderPreview) -> ValidationResult:
    errors: list[str] = []
    if _is_blank(preview.script_id):
        errors.append("script_id is required")
    if _is_blank(preview.title):
        errors.append("title is required")
    if preview.preview_type != "html_timeline":
        errors.append("preview_type must be html_timeline")
    if _is_blank(preview.html):
        errors.append("html is required")
    if "<html" not in preview.html.casefold():
        errors.append("html preview must contain an html document")
    if preview.side_effects:
        errors.append("preview must not have side effects")
    if preview.external_calls:
        errors.append("preview must not call external renderers")
    return ValidationResult(is_valid=not errors, errors=tuple(errors))


Output:
.....                                                                    [100%]
5 passed in 0.05s


Output:
........................................................................ [ 91%]
.......                                                                  [100%]
79 passed in 0.12s


Explanation:
**Updating README and verifying**

I need to update the README to ensure it reflects the latest changes. After that, I should probably double-check everything manually to make sure nothing’s overlooked before committing. It's important to be thorough here! I want to avoid any mistakes that could cause confusion later on. A clear README helps everyone understand the project better, so I’ll take my time with this step. Once it’s all good, I’ll go ahead and commit the updates.

patch

- `autoshorts.rendering.manifest` — side-effect-free vertical render manifest generation from shot plans.
- `autoshorts.rendering.shot_plan` — retention-led shot planner before render manifests or video generation.

- `autoshorts.rendering.manifest` — side-effect-free vertical render manifest generation from shot plans.
- `autoshorts.rendering.preview` — side-effect-free HTML timeline previews for render manifests.
- `autoshorts.rendering.shot_plan` — retention-led shot planner before render manifests or video generation.


--- a//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/README.md
+++ b//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/README.md
@@ -21,6 +21,7 @@
 - `autoshorts.ideas.testbatch` — testbatch validation and renderable-candidate filtering.
 - `autoshorts.review.approvals` — deterministic Telegram approval command parser.
 - `autoshorts.rendering.manifest` — side-effect-free vertical render manifest generation from shot plans.
+- `autoshorts.rendering.preview` — side-effect-free HTML timeline previews for render manifests.
 - `autoshorts.rendering.shot_plan` — retention-led shot planner before render manifests or video generation.
 - `autoshorts.review.package` — Telegram-friendly review package renderer for human approval.
 - `autoshorts.review.render` — renders complete review batches into Telegram-safe text.

No linter for .md files


`validate_render_manifest()` rejects unsafe render settings, generated text inside assets, non-renderer-owned captions/labels, static slideshow timeline items, missing timeline fields, and bad timeline timing. The manifest is still a preparation artifact only; it renders nothing and calls no external API.

`autoshorts.rendering.preview.build_html_preview()` turns a validated `RenderManifest` into a side-effect-free `RenderPreview` HTML document. It exposes script metadata, vertical format, fps, every timeline item, placeholder asset paths, motion instructions, and renderer-owned caption/label text for local inspection before any media generation.

`validate_render_preview()` rejects blank previews, wrong preview types, missing HTML documents, side effects, and external renderer calls. Preview generation still does not write files, render videos, call ComfyUI, or touch Telegram.

## Human approval commands

`validate_render_manifest()` rejects unsafe render settings, generated text inside assets, non-renderer-owned captions/labels, static slideshow timeline items, missing timeline fields, and bad timeline timing. The manifest is still a preparation artifact only; it renders nothing and calls no external API.

## Human approval commands


--- a//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/README.md
+++ b//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/README.md
@@ -93,6 +93,10 @@

 `validate_render_manifest()` rejects unsafe render settings, generated text inside assets, non-renderer-owned captions/labels, static slideshow timeline items, missing timeline fields, and bad timeline timing. The manifest is still a preparation artifact only; it renders nothing and calls no external API.

+`autoshorts.rendering.preview.build_html_preview()` turns a validated `RenderManifest` into a side-effect-free `RenderPreview` HTML document. It exposes script metadata, vertical format, fps, every timeline item, placeholder asset paths, motion instructions, and renderer-owned caption/label text for local inspection before any media generation.
+
+`validate_render_preview()` rejects blank previews, wrong preview types, missing HTML documents, side effects, and external renderer calls. Preview generation still does not write files, render videos, call ComfyUI, or touch Telegram.
+
 ## Human approval commands

 Supported review responses:

No linter for .md files


terminal

python -m pytest tests -q && python - <<'PY'
from autoshorts.content.brief import create_content_brief
from autoshorts.content.script_draft import create_script_draft
from autoshorts.rendering.shot_plan import create_shot_plan
from autoshorts.rendering.manifest import build_render_manifest
from autoshorts.rendering.preview import build_html_preview, validate_render_preview
brief = create_content_brief(
    pillar_id='ai_life_systems',
    topic='AI weekly review routine',
    audience_problem='I use AI tools but still lose control of priorities.',
    payoff='a 10-minute Sunday review that keeps AI as assistant, not boss',
)
script = create_script_draft(brief)
manifest = build_render_manifest(create_shot_plan(script))
preview = build_html_preview(manifest)
print(preview.script_id)
print(preview.preview_type)
print(preview.side_effects, preview.external_calls)
print(len(preview.html))
print('first_frame_hook' in preview.html)
print(validate_render_preview(preview).is_valid)
PY


Output:
........................................................................ [ 91%]
.......                                                                  [100%]
79 passed in 0.12s
ai_life_systems-ai-weekly-review-routine
html_timeline
() ()
3296
True
True


git status --short && git diff -- README.md autoshorts/rendering/preview.py tests/test_render_preview.py | sed -n '1,260p'


Output:
M README.md
?? autoshorts/rendering/preview.py
?? tests/test_render_preview.py
diff --git a/README.md b/README.md
index 3fce936..0a53748 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,7 @@ Implemented foundation:
 - `autoshorts.ideas.testbatch` — testbatch validation and renderable-candidate filtering.
 - `autoshorts.review.approvals` — deterministic Telegram approval command parser.
 - `autoshorts.rendering.manifest` — side-effect-free vertical render manifest generation from shot plans.
+- `autoshorts.rendering.preview` — side-effect-free HTML timeline previews for render manifests.
 - `autoshorts.rendering.shot_plan` — retention-led shot planner before render manifests or video generation.
 - `autoshorts.review.package` — Telegram-friendly review package renderer for human approval.
 - `autoshorts.review.render` — renders complete review batches into Telegram-safe text.
@@ -92,6 +93,10 @@ Each `ShotScene` carries timing, voiceover beat, visual action, asset mode, capt

 `validate_render_manifest()` rejects unsafe render settings, generated text inside assets, non-renderer-owned captions/labels, static slideshow timeline items, missing timeline fields, and bad timeline timing. The manifest is still a preparation artifact only; it renders nothing and calls no external API.

+`autoshorts.rendering.preview.build_html_preview()` turns a validated `RenderManifest` into a side-effect-free `RenderPreview` HTML document. It exposes script metadata, vertical format, fps, every timeline item, placeholder asset paths, motion instructions, and renderer-owned caption/label text for local inspection before any media generation.
+
+`validate_render_preview()` rejects blank previews, wrong preview types, missing HTML documents, side effects, and external renderer calls. Preview generation still does not write files, render videos, call ComfyUI, or touch Telegram.
+
 ## Human approval commands

 Supported review responses:


git add README.md autoshorts/rendering/preview.py tests/test_render_preview.py && git commit -m "feat: add side-effect-free render preview" && git log --oneline -n 8 && git status --short


Output:
[feat/core-approval-quality-gates 42ac32f] feat: add side-effect-free render preview
 3 files changed, 210 insertions(+)
 create mode 100644 autoshorts/rendering/preview.py
 create mode 100644 tests/test_render_preview.py
42ac32f feat: add side-effect-free render preview
e258635 feat: add render manifest planning
d118a76 feat: add retention shot planning
6e6569a feat: add content demo review pipeline
72dc887 feat: convert content drafts to video candidates
9c125f9 feat: add content brief and script draft models
53b45ca feat: add demo telegram delivery cli
54863dd feat: add telegram review delivery preparation


Erledigt, Sir.

## Neu eingebaut: Side-effect-free Render Preview

Neue Dateien:

```text
autoshorts/rendering/preview.py
tests/test_render_preview.py
```

## Neue API

```python
build_html_preview(manifest: RenderManifest) -> RenderPreview
validate_render_preview(preview: RenderPreview) -> ValidationResult
```

Der Flow ist jetzt:

```text
ScriptDraft
→ ShotPlan
→ RenderManifest
→ HTML RenderPreview
```

## RenderPreview

```python
RenderPreview(
    script_id=...,
    title=...,
    preview_type="html_timeline",
    html=...,
    side_effects=(),
    external_calls=(),
)
```

Wichtig: Das Ding rendert **noch kein Video**, ruft **kein ComfyUI**, schreibt **keine Dateien**, schickt **nichts an Telegram**. Es ist nur eine inspectable HTML-Timeline.

Langweilig? Ja. Genau deshalb überlebt es den Kontakt mit Realität.

## Inhalt der HTML Preview

Die Preview zeigt:

- Script ID
- Titel
- Format `1080x1920`
- `30fps`
- Text Policy:
  - captions/labels renderer-owned
  - no generated text inside assets
- pro Timeline Item:
  - Scene Purpose
  - Zeitfenster, z. B. `0–3s`
  - Asset Mode
  - Motion Instruction
  - Caption
  - Label
  - Placeholder Asset Path

Beispiel-Pfad verifiziert:

```text
placeholders/ai_life_systems-ai-weekly-review-routine/01-first_frame_hook.png
```

## Safety / Guardrails

`validate_render_preview()` rejected:

- blank previews
- falscher Preview Type
- fehlendes HTML-Dokument
- Side Effects
- externe Renderer Calls

Zusätzlich ist HTML-Escaping getestet:

```text
<script> → <script>
```

Also kein fröhliches Self-XSS in unserer eigenen Preview. Kleine Dinge, grosse Wirkung.

## TDD eingehalten

RED:

```text
ModuleNotFoundError: No module named 'autoshorts.rendering.preview'
```

GREEN:

```text
5 passed
```

Full suite:

```text
79 passed in 0.12s
```

## Manuell verifiziert

```text
ai_life_systems-ai-weekly-review-routine
html_timeline
() ()
3296
True
True
```

Bedeutung:

- richtige Script ID
- Preview Type `html_timeline`
- keine Side Effects
- keine External Calls
- HTML Länge: `3296`
- enthält `first_frame_hook`
- Preview validiert erfolgreich

## README aktualisiert

Dokumentiert:

- `autoshorts.rendering.preview`
- HTML Timeline Preview
- side-effect-free Boundary
- keine ComfyUI-/Video-/Telegram-Calls

## Commit

Branch:

```text
feat/core-approval-quality-gates
```

Neuer Commit:

```text
42ac32f feat: add side-effect-free render preview
```

Arbeitsverzeichnis ist sauber.

## Nächster sinnvoller Schritt

Jetzt fehlt der praktische Demo-Entry für die komplette Render-Planung:

```text
ContentBrief
→ ScriptDraft
→ ShotPlan
→ RenderManifest
→ HTML Preview
```

Ich würde als nächstes bauen:

```text
autoshorts/cli/demo_render_preview.py
tests/test_demo_render_preview_cli.py
```

Ziel:

- `python -m autoshorts.cli.demo_render_preview`
- druckt Preview-Metadaten
- druckt danach HTML auf stdout
- weiterhin:
  - keine Datei schreiben
  - kein Video rendern
  - kein ComfyUI
  - keine externen Calls

Dann haben wir einen lokalen, reproduzierbaren Preview-Command, den wir später entweder in eine Datei umleiten oder als Telegram/Browser-Artifact anzeigen können. Danach erst kommt der erste echte Renderer-Adapter.


Explanation:
**Planning for implementation**

I’m thinking we need to continue implementing the CLI for demo_render_preview and also consider the roadmap ahead. I want to load relevant skills, like Test-Driven Development (TDD) and potentially social media shorts to prepare for the user’s questions about next steps. It seems that social media automation might also be relevant, and I’m considering how to make it test-driven too. It’s all connected!


social-media-shorts-automation

---
name: social-media-shorts-automation
description: Design, generate, review, publish, and optimize automated short-form social videos across YouTube Shorts, TikTok, Instagram Reels, and related channels.
version: 1.0.0
tags: [shorts, tiktok, youtube-shorts, reels, creator-monetization, content-automation, social-media]
---

# Social-Media Shorts Automation

Use this skill when building or operating systems that generate short-form social videos, especially faceless or semi-automated pipelines for YouTube Shorts, TikTok, Instagram Reels, Facebook Reels, Pinterest video pins, X video, or LinkedIn video.

## Core principle

Treat the system as a **Shorts Growth Engine** / **semi-automatic Retention Studio**, not merely a renderer. A working video generator can still fail if it produces generic, low-retention, AI-slop content. Optimize for audience identity, hook quality, retention mechanics, visual mechanism reveals, platform fit, and analytics feedback. For the user's strongest strategic direction, see `references/retention-studio-strategy.md`.

## Retention-Studio / Mechanism-Reveal Pipeline

For quality-first automated Shorts, especially Hidden Money & AI Systems concepts, prefer the pipeline documented in `references/retention-studio-pipeline-pattern.md`:

```text
strategy → scored ideas → mechanism explainer → shot plan → keyframe/I2V prompt plan → seeded clip jobs → clip review → deterministic final renderer
```

When implementing a new repo or core pipeline, first scaffold the approval-first typed artifact chain in `references/approval-first-retention-studio-core.md`:

```text
ContentPillar → ContentBrief → ScriptDraft → VideoCandidate → TestBatch → ReviewBatch → Telegram ReviewDelivery → ShotPlan
```

Hard rule for this class: AI-video clips provide photorealistic texture and motion only. Captions, numbers, labels, arrows, UI text, price/risk/margin layers, and factual claims are renderer-owned. Final content should keep `static_card_limit = 0`, use visual changes every 1–2 seconds, and reject technically successful clips that do not serve the retention beat.

Before video generation, build an inspectable content-to-review spine: `ContentBrief → ScriptDraft → CandidateDraft → VideoCandidate → TestBatch → ReviewBatch → Telegram ReviewDelivery`. Keep it deterministic, side-effect free, and human-approval-first so hooks, retention beats, visual concepts, policy notes, and review copy can be optimized before spending GPU/API time. See `references/content-to-review-pipeline.md` for the implementation pattern and guardrails.

Hard rule for this class: AI-video clips provide photorealistic texture and motion only. Captions, numbers, labels, arrows, UI text, price/risk/margin layers, and factual claims are renderer-owned. Final content should keep `static_card_limit = 0`, use visual changes every 1–2 seconds, and reject technically successful clips that do not serve the retention beat.

## Recommended workflow

1. **Define channel position**
   - Niche(s): e.g. life optimization, AI life systems, Christian faith/FaithTok.
   - Target audience and emotional problem.
   - Language and tone.
   - Whether the brand is faceless, AI-persona-led, or creator-led.
   - No-go topics and theological/ethical boundaries if faith-related.

2. **Generate test ideas before code-heavy automation**
   - Create 20–30 candidate ideas across 2–4 content pillars.
   - For each: hook, script kernel, visual concept, caption, CTA, risk/quality score.
   - Select a small first batch (e.g. 3 per pillar) for real testing.
   - Before renderers or external AI APIs, add a deterministic content-development core: `ContentPillar → ContentBrief → ScriptDraft → CandidateDraft → VideoCandidate`. See `references/content-development-core.md`.

3. **Use hook-first, retention-led scripting**
   - First 1–2 seconds must contain a clear tension, identity statement, pain point, contrarian line, or immediate self-recognition.
   - Prefer one idea per video over generic “4 tips” unless the list format is the explicit series.
   - Avoid generic advice viewers have already heard unless framed with a new angle, concrete payoff, or strong story.
   - Reward hooks with direct address, specificity, open loops, tension, and a visible payoff; reject slow intros such as “In this video...”.
   - See `references/retention-led-shorts.md` for scoring signals, penalties, and voiceover finalization rules.

4. **Quality gate before rendering**
   Score drafts on:
   - hook strength
   - novelty
   - emotional pull
   - clarity
   - retention risk
   - comment/share potential
   - AI-slop risk
   - platform/policy risk
   Render only drafts above the chosen threshold, e.g. 8/10.

5. **Visual strategy**
   - Avoid obvious AI artifacts, fake UI text, illegible generated lettering, generic charts, and soulless stock visuals.
   - Prefer high-contrast kinetic captions, visible hook text in first seconds, relatable B-roll, screen recordings for AI/tutorial content, phone/screen close-ups, before/after transformations, cards, and timer visuals.
   - Use pattern interrupts every 2–3 seconds for retention.
   - Before rendering, generate a shot plan from the finalized voiceover: first-frame hook, pattern interrupt, core explanation, screen/card visual, CTA, caption beats, and asset type.
   - Before writing a renderer, convert shot plans into deterministic render manifests: machine-readable timeline items, asset placeholders, caption tracks, style constraints, and approval constraints. See `references/render-manifest-pipeline.md`.
   - Treat this as a deterministic intermediate artifact, not optional prose. See `references/retention-render-planning.md` for the five-scene shot-plan pattern and renderer-facing fields.
   - For the first video output, build a manifest-driven preview renderer before any AI-video or polished asset pipeline: render vertical scene/card frames from manifest fields, assemble them with FFmpeg, then use the MP4 for timing/approval review. See `references/manifest-preview-renderer.md`.
   - When the user rejects static/slideshow output, add an `asset_plan.v2` layer that maps each scene to motion-led local asset modes such as `cinematic_ai_video`, `screen_recording_simulation`, `mechanism_animation`, or `kinetic_text_overlay` with no baked text. If the user requires a free workflow, do not depend on paid video-credit tools; use local procedural motion and optional free NVIDIA LLM text refinement. See `references/ai-video-asset-planning.md`.
   - For hybrid workflows, add an imported-clip assembly layer: provider handoff prompts create short manual AI-video clips, humans drop them into deterministic `data/imported_ai_clips/<slug>/scene_XX.mp4` paths, and the final renderer combines imported clips with local mechanism animations/captions. See `references/imported-ai-clip-assembly.md`.
   - For local Wan2.2 quality workflows, prefer keyframe/image-to-video over raw text-to-video: generate/select a clean keyframe, run native Wan2.2 I2V variants, review clips bluntly for subject clarity/motion/artifacts, then add all labels/numbers/captions via deterministic renderer. See `references/wan-i2v-retention-studio.md`.
   - When using NVIDIA Build/API Catalog for keyframes, prefer hosted `ai.api.nvidia.com` calls before considering self-hosted NIM deployment; implement a configurable model fallback chain, store the key as a runtime secret, and inspect/clean logos, emblems, license plates, and fake text before I2V. See `references/nvidia-keyframe-generation.md`.

6. **Human approval loop before posting**
   - Send preview to the user through the requested channel, usually Telegram.
   - Include: video, title, caption, hashtags, platform recommendation, duration, quality/risk score.
   - Wait for explicit approval such as `Freigabe alle`, `Freigabe YouTube`, `Freigabe TikTok`, `Ablehnen`, or `Ändern: ...`.
   - Do not publish automatically unless the user has explicitly authorized that workflow.

7. **Publish and measure**
   Track at minimum:
   - platform, video id/url, niche, format, hook, duration
   - views at 2h/24h/7d
   - likes, comments, shares, saves, follows
   - retention metrics if available
   Use results to generate the next batch.

## Platform duration/monetization notes

See `references/monetization-and-approval-notes.md` for current working notes captured from official YouTube/TikTok docs and the recommended approval/posting setup. See `references/retention-led-shorts.md` for retention scoring signals, no-faith/adjacent-pillar handling, and voiceover finalization rules. See `references/render-manifest-pipeline.md` for the script → shot plan → render manifest → renderer sequence that prevents generic AI slideshow output. See `references/manifest-preview-renderer.md` for the first deterministic Pillow/FFmpeg preview-renderer pattern. See `references/ai-video-asset-planning.md` for motion-led final-render asset planning with Kling/Hailuo/Runway-style clips and screen simulations. See `references/imported-ai-clip-assembly.md` for hybrid assembly where manually generated provider clips are imported into a deterministic local renderer. See `references/nvidia-keyframe-generation.md` for NVIDIA-hosted keyframe generation, model fallback, secret handling, and pre-I2V logo/plate cleanup. Re-check official docs before making business-critical decisions because platform programs change often.

## Project/repo hygiene for automated video bots

- Keep source code in Git; keep generated videos, assets, `.env`, OAuth tokens, API keys, and downloaded secrets out of Git.
- Use `.gitignore` aggressively for outputs, media caches, local credentials, and temp files.
- Store platform credentials as runtime secrets; never print raw tokens.
- Build the repo as modular components: idea generation, scripting, rendering, review, publishing, analytics.
- For approval-first Retention Studio implementations, use a typed side-effect-free core before real I/O: `ContentBrief`, `ScriptDraft`, `VideoCandidate`, review batches, Telegram delivery plans, then shot plans. See `references/approval-first-retention-studio-core.md`.

## Sensitive-content positioning

When AI/productivity automation is the primary brand, be cautious about mixing in religious/FaithTok, health, finance, or other high-trust themes. If the user flags the combination as sensitive, pause that pillar rather than forcing it into the same testbatch. Replace it with a safer adjacent pillar (for example `attention_design`) and document the paused pillar in config so it can be revisited only with explicit approval and a separate brand strategy.

## Pitfalls

- Do not over-optimize renderer quality before testing whether the content format earns retention.
- Do not jump directly from a script idea to ComfyUI/Wan/Qwen/Image/video generation. First build and inspect the content-to-review spine so weak hooks, generic scripts, policy issues, and static visual concepts are killed before render time.
- Do not create a parallel candidate path for content experiments; convert content artifacts into the existing candidate factory input so IDs, hashtags, duration classes, quality gates, and pillar/platform validation stay centralized.
- Do not use `x or default` for optional content/script overrides in factories; explicit blank strings and zero durations must pass through to validation so tests can catch them.
- Do not assume TikTok monetization rules match YouTube: TikTok Creator Rewards emphasizes 1min+ eligible videos, while shorter videos may still be better for growth.
- Do not publish religious or health-adjacent content with overconfident claims; preserve humility, nuance, and platform safety.
- Do not rely on platform payout as the only monetization path; plan indirect monetization such as newsletter, templates, affiliates, digital products, or services.


terminal

Output: feat/core-approval-quality-gates


     1|"""Print the deterministic sample Telegram review delivery."""
     2|
     3|from __future__ import annotations
     4|
     5|import sys
     6|from collections.abc import Sequence
     7|from typing import TextIO
     8|
     9|from autoshorts.demo.sample_batch import render_sample_review_batch
    10|from autoshorts.telegram.review_sender import prepare_review_delivery
    11|
    12|
    13|def main(argv: Sequence[str] | None = None, *, stdout: TextIO | None = None) -> int:
    14|    """Print delivery metadata and review text without sending anything."""
    15|
    16|    _ = argv or ()
    17|    output = stdout or sys.stdout
    18|    delivery = prepare_review_delivery(render_sample_review_batch(), topic="review")
    19|    side_effects = ", ".join(delivery.side_effects) if delivery.side_effects else "none"
    20|    approval = "yes" if delivery.requires_human_approval else "no"
    21|
    22|    print(f"Delivery type: {delivery.delivery_type}", file=output)
    23|    print(f"Topic: {delivery.topic}", file=output)
    24|    print(f"Requires human approval: {approval}", file=output)
    25|    print(f"Side effects: {side_effects}", file=output)
    26|    print(f"Text length: {len(delivery.text)}", file=output)
    27|    print("", file=output)
    28|    print(delivery.text, file=output)
    29|    return 0
    30|
    31|
    32|if __name__ == "__main__":
    33|    raise SystemExit(main())
    34|


     1|from autoshorts.cli.demo_telegram_delivery import main
     2|
     3|
     4|def test_demo_telegram_delivery_cli_prints_metadata_and_review_text(capsys):
     5|    exit_code = main([])
     6|
     7|    captured = capsys.readouterr()
     8|
     9|    assert exit_code == 0
    10|    assert "Delivery type: telegram_review" in captured.out
    11|    assert "Topic: review" in captured.out
    12|    assert "Requires human approval: yes" in captured.out
    13|    assert "Side effects: none" in captured.out
    14|    assert "Text length:" in captured.out
    15|    assert "## Review Batch: sample-ai-life-systems" in captured.out
    16|    assert "Ready: 1" in captured.out
    17|    assert "Blocked: 1" in captured.out
    18|    assert "Invalid: 1" in captured.out
    19|    assert captured.err == ""
    20|    assert "publish" not in captured.out.casefold()
    21|    assert "post" not in captured.out.casefold()
    22|


from autoshorts.cli.demo_render_preview import main

def test_demo_render_preview_cli_prints_metadata_and_html_preview(capsys):
    exit_code = main([])

    captured = capsys.readouterr()

    assert exit_code == 0
    assert "Preview type: html_timeline" in captured.out
    assert "Script: ai_life_systems-ai-weekly-review-routine" in captured.out
    assert "Side effects: none" in captured.out
    assert "External calls: none" in captured.out
    assert "HTML length:" in captured.out
    assert "<!doctype html>" in captured.out
    assert "AI weekly review routine" in captured.out
    assert "first_frame_hook" in captured.out
    assert "1080x1920" in captured.out
    assert "30fps" in captured.out
    assert captured.err == ""
    assert "comfyui" not in captured.out.casefold()
    assert "publish" not in captured.out.casefold()
    assert "post" not in captured.out.casefold()


LSP diagnostics introduced by this edit:
<diagnostics file="/home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/tests/test_demo_render_preview_cli.py">
ERROR [1:6] Import "autoshorts.cli.demo_render_preview" could not be resolved [reportMissingImports] (Pyright)
</diagnostics>


python -m pytest tests/test_demo_render_preview_cli.py -q


Output:
==================================== ERRORS ====================================
____________ ERROR collecting tests/test_demo_render_preview_cli.py ____________
ImportError while importing test module '/home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/tests/test_demo_render_preview_cli.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
../../../.local/share/uv/python/cpython-3.11.15-linux-x86_64-gnu/lib/python3.11/importlib/__init__.py:126: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_demo_render_preview_cli.py:1: in <module>
    from autoshorts.cli.demo_render_preview import main
E   ModuleNotFoundError: No module named 'autoshorts.cli.demo_render_preview'
=========================== short test summary info ============================
ERROR tests/test_demo_render_preview_cli.py
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
1 error in 0.11s


"""Print the deterministic HTML render preview without rendering media."""

from __future__ import annotations

import sys
from collections.abc import Sequence
from typing import TextIO

from autoshorts.content.brief import create_content_brief
from autoshorts.content.script_draft import create_script_draft
from autoshorts.rendering.manifest import build_render_manifest
from autoshorts.rendering.preview import build_html_preview
from autoshorts.rendering.shot_plan import create_shot_plan

def _build_demo_preview():
    brief = create_content_brief(
        pillar_id="ai_life_systems",
        topic="AI weekly review routine",
        audience_problem="I use AI tools but still lose control of priorities.",
        payoff="a 10-minute Sunday review that keeps AI as assistant, not boss",
    )
    script = create_script_draft(brief)
    shot_plan = create_shot_plan(script)
    manifest = build_render_manifest(shot_plan)
    return build_html_preview(manifest)

def main(argv: Sequence[str] | None = None, *, stdout: TextIO | None = None) -> int:
    """Print preview metadata and HTML without file writes or renderer calls."""

    _ = argv or ()
    output = stdout or sys.stdout
    preview = _build_demo_preview()
    side_effects = ", ".join(preview.side_effects) if preview.side_effects else "none"
    external_calls = ", ".join(preview.external_calls) if preview.external_calls else "none"

    print(f"Preview type: {preview.preview_type}", file=output)
    print(f"Script: {preview.script_id}", file=output)
    print(f"Side effects: {side_effects}", file=output)
    print(f"External calls: {external_calls}", file=output)
    print(f"HTML length: {len(preview.html)}", file=output)
    print("", file=output)
    print(preview.html, file=output)
    return 0

if __name__ == "__main__":
    raise SystemExit(main())


Output:
.                                                                        [100%]
1 passed in 0.05s


python -m pytest tests -q && python -m autoshorts.cli.demo_render_preview >/tmp/autoshorts-render-preview.html && python - <<'PY'
from pathlib import Path
p=Path('/tmp/autoshorts-render-preview.html')
text=p.read_text()
print(len(text))
print(text.splitlines()[0])
print(text.splitlines()[1])
print(text.splitlines()[2])
print(text.splitlines()[3])
print(text.splitlines()[4])
print('<!doctype html>' in text)
print('first_frame_hook' in text)
PY


Output:
........................................................................ [ 90%]
........                                                                 [100%]
80 passed in 0.12s
3433
Preview type: html_timeline
Script: ai_life_systems-ai-weekly-review-routine
Side effects: none
External calls: none
HTML length: 3296
True
True


Need README patch commit.

Need README patch commit.

patch

- `autoshorts.cli.demo_render_preview` — local CLI entry point that prints side-effect-free render-preview metadata plus HTML.
- `autoshorts.cli.demo_review_batch` — local CLI entry point that prints the deterministic review demo.
- `autoshorts.cli.demo_telegram_delivery` — local CLI entry point that prints side-effect-free Telegram delivery metadata plus review text.


--- a//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/README.md
+++ b//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/README.md
@@ -8,6 +8,7 @@

 Implemented foundation:

+- `autoshorts.cli.demo_render_preview` — local CLI entry point that prints side-effect-free render-preview metadata plus HTML.
 - `autoshorts.cli.demo_review_batch` — local CLI entry point that prints the deterministic review demo.
 - `autoshorts.cli.demo_telegram_delivery` — local CLI entry point that prints side-effect-free Telegram delivery metadata plus review text.
 - `autoshorts.config.pillars` — content pillar registry, default AI/life-systems positioning, and pillar fit validation.

No linter for .md files


`validate_render_preview()` rejects blank previews, wrong preview types, missing HTML documents, side effects, and external renderer calls. Preview generation still does not write files, render videos, call ComfyUI, or touch Telegram.

Run the same deterministic preview locally with:

```bash
python -m autoshorts.cli.demo_render_preview
```

It prints preview metadata first, then the HTML document. Redirect stdout to a local `.html` file if you want to inspect it in a browser; the CLI itself still writes nothing.

## Human approval commands

`validate_render_preview()` rejects blank previews, wrong preview types, missing HTML documents, side effects, and external renderer calls. Preview generation still does not write files, render videos, call ComfyUI, or touch Telegram.

## Human approval commands


--- a//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/README.md
+++ b//home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/README.md
@@ -98,6 +98,14 @@

 `validate_render_preview()` rejects blank previews, wrong preview types, missing HTML documents, side effects, and external renderer calls. Preview generation still does not write files, render videos, call ComfyUI, or touch Telegram.

+Run the same deterministic preview locally with:
+
+```bash
+python -m autoshorts.cli.demo_render_preview
+```
+
+It prints preview metadata first, then the HTML document. Redirect stdout to a local `.html` file if you want to inspect it in a browser; the CLI itself still writes nothing.
+
 ## Human approval commands

 Supported review responses:

No linter for .md files


terminal

python -m pytest tests -q && git status --short && git add README.md autoshorts/cli/demo_render_preview.py tests/test_demo_render_preview_cli.py && git commit -m "feat: add demo render preview cli" && git log --oneline -n 8 && git status --short


Output:
........................................................................ [ 90%]
........                                                                 [100%]
80 passed in 0.12s
 M README.md
?? autoshorts/cli/demo_render_preview.py
?? tests/test_demo_render_preview_cli.py
[feat/core-approval-quality-gates f8c5588] feat: add demo render preview cli
 3 files changed, 81 insertions(+)
 create mode 100644 autoshorts/cli/demo_render_preview.py
 create mode 100644 tests/test_demo_render_preview_cli.py
f8c5588 feat: add demo render preview cli
42ac32f feat: add side-effect-free render preview
e258635 feat: add render manifest planning
d118a76 feat: add retention shot planning
6e6569a feat: add content demo review pipeline
72dc887 feat: convert content drafts to video candidates
9c125f9 feat: add content brief and script draft models
53b45ca feat: add demo telegram delivery cli


Erledigt, Sir.

## Neu eingebaut: Demo Render Preview CLI

Neue Dateien:

```text
autoshorts/cli/demo_render_preview.py
tests/test_demo_render_preview_cli.py
```

## Neuer Command

```bash
python -m autoshorts.cli.demo_render_preview
```

Der läuft jetzt deterministisch durch:

```text
ContentBrief
→ ScriptDraft
→ ShotPlan
→ RenderManifest
→ HTML RenderPreview
→ stdout
```

Er druckt zuerst Metadaten:

```text
Preview type: html_timeline
Script: ai_life_systems-ai-weekly-review-routine
Side effects: none
External calls: none
HTML length: 3296
```

Dann das komplette HTML.

## Verifiziert

```text
80 passed in 0.12s
```

Manueller CLI-Test:

```text
3433
Preview type: html_timeline
Script: ai_life_systems-ai-weekly-review-routine
Side effects: none
External calls: none
HTML length: 3296
True
True
```

Bedeutung:

- CLI läuft.
- HTML wird erzeugt.
- enthält `<!doctype html>`.
- enthält `first_frame_hook`.
- keine Datei wird geschrieben.
- keine externen Calls.
- kein ComfyUI.
- kein Video-Rendering.

Commit:

```text
f8c5588 feat: add demo render preview cli
```

Arbeitsverzeichnis sauber.

---

## Nächste Schritte bis zum ersten Social-Media-Freigabeentwurf

Wir sind jetzt hier:

```text
ContentBrief
→ ScriptDraft
→ VideoCandidate
→ ReviewBatch
→ Telegram ReviewDelivery
→ ShotPlan
→ RenderManifest
→ HTML Preview
```

Für den ersten echten Freigabeentwurf fehlen noch diese Bausteine:

## 1. Social Media Draft Package

Nächster sinnvoller Core-Baustein:

```text
autoshorts/social/draft.py
tests/test_social_draft.py
```

Ziel:

```text
VideoCandidate + RenderPreview/Manifest
→ SocialMediaDraft
```

Enthält:

- Plattformen:
  - YouTube Shorts
  - TikTok
  - Instagram Reels
- Titelvarianten
- Caption
- Hashtags
- CTA
- geplante Dauer
- Hook
- Risk/Quality summary
- Preview-Hinweis
- Status: `requires_human_approval`
- keine API Calls

Das ist der erste eigentliche “Social-Media-Beitragsentwurf”.

## 2. Review Renderer für SocialMediaDraft

Danach:

```text
autoshorts/social/review.py
tests/test_social_review.py
```

Ziel:

```text
SocialMediaDraft
→ Telegram-safe approval message
```

Also eine Nachricht wie:

```text
## Social Media Draft Review

Candidate: ai_life_systems-ai-weekly-review-routine-35s
Platforms: YouTube Shorts, TikTok, Instagram Reels
Duration: 35s

Hook:
If AI runs your week, you have already lost control.

Title:
AI should not run your week

Caption:
...

Hashtags:
#AI #LifeSystems #Productivity

Preview:
HTML timeline available / video preview pending

Approval commands:
Freigabe YouTube
Freigabe TikTok
Freigabe Instagram
Freigabe alle
Ablehnen
Ändern: <...>
```

Wichtig: Noch kein Upload. Nur Freigabeentwurf.

## 3. Draft Delivery Preparation

Dann:

```text
autoshorts/social/delivery.py
tests/test_social_delivery.py
```

Ziel:

```text
SocialMediaDraft Review Text
→ Telegram Delivery Plan
```

Analog zur bisherigen ReviewDelivery:

- `requires_human_approval=True`
- `side_effects=()`
- Telegram-Limit validieren
- keine Credentials
- keine externen Calls
- kein automatischer Plattform-Upload

## 4. Demo CLI für ersten Freigabeentwurf

Dann:

```text
autoshorts/cli/demo_social_draft.py
tests/test_demo_social_draft_cli.py
```

Command:

```bash
python -m autoshorts.cli.demo_social_draft
```

Output:

```text
Delivery type: social_draft_review
Requires human approval: yes
Side effects: none

## Social Media Draft Review
...
```

Das wäre der erste komplette Social-Media-Freigabeentwurf — noch ohne Video-Datei, aber mit Content, Caption, Plattformen, Hashtags, Approval Commands und Preview-Referenz.

## 5. Danach: Video Preview Artefakt

Erst danach würde ich den ersten visuellen Artefakt-Renderer bauen:

```text
RenderManifest
→ local preview artifact
```

Optionen:

- HTML only — haben wir schon
- SVG frames
- PNG scene cards
- MP4 timing preview via local renderer/FFmpeg

Für Freigabe wäre ein MP4-Preview natürlich besser, aber der Freigabeentwurf kann bereits vorher existieren.

## 6. Danach: TTS / Voice Layer

TTS sollten wir **nach** Social Draft und Preview-Struktur sauber designen, nicht vorher.

Dann kommen Fragen wie:

- Stimme:
  - deine Präferenz: `de-DE-KillianNeural`
  - für englischen Content evtl. eigene Voice
- Voiceover-Script finalisieren
- Timing:
  - Wörter pro Sekunde
  - Scene durations
  - Caption sync
- Output:
  - `.wav` oder `.mp3`
  - später eventuell `.srt`
- Provider:
  - lokal/free vs Edge TTS vs andere
- Approval:
  - Text-to-speech preview braucht eigene Freigabe, weil schlechte Voice jedes Video sofort in die “LinkedIn Motivational NPC”-Hölle schiebt.

---

## Meine Empfehlung für die Reihenfolge

Als nächstes baue ich:

```text
autoshorts/social/draft.py
tests/test_social_draft.py
```

Danach direkt:

```text
autoshorts/social/review.py
tests/test_social_review.py
```

Dann haben wir den ersten echten Social-Media-Freigabeentwurf als typisiertes Objekt + Telegram-tauglichen Text.

Kurz gesagt:

```text
Jetzt:
Render Preview CLI fertig.

Nächster Block:
SocialMediaDraft → Review Text → Telegram Delivery Preview.

Danach:
MP4 Preview.

Danach:
TTS.
```

Sauber, kontrolliert, keine magische Upload-Katastrophe mit Konfetti.


Explanation:
**Loading relevant skills**

I need to act now, focusing on loading the necessary skills. Should I confirm if TDD is already ready? Before replying, I want to scan for anything relevant, particularly the social media shorts automation and TDD. It's possible that this was previously loaded, but with a new turn in the conversation, I should double-check. Let’s use the skill view to ensure everything is in place.


social-media-shorts-automation

---
name: social-media-shorts-automation
description: Design, generate, review, publish, and optimize automated short-form social videos across YouTube Shorts, TikTok, Instagram Reels, and related channels.
version: 1.0.0
tags: [shorts, tiktok, youtube-shorts, reels, creator-monetization, content-automation, social-media]
---

# Social-Media Shorts Automation

Use this skill when building or operating systems that generate short-form social videos, especially faceless or semi-automated pipelines for YouTube Shorts, TikTok, Instagram Reels, Facebook Reels, Pinterest video pins, X video, or LinkedIn video.

## Core principle

Treat the system as a **Shorts Growth Engine** / **semi-automatic Retention Studio**, not merely a renderer. A working video generator can still fail if it produces generic, low-retention, AI-slop content. Optimize for audience identity, hook quality, retention mechanics, visual mechanism reveals, platform fit, and analytics feedback. For the user's strongest strategic direction, see `references/retention-studio-strategy.md`.

## Retention-Studio / Mechanism-Reveal Pipeline

For quality-first automated Shorts, especially Hidden Money & AI Systems concepts, prefer the pipeline documented in `references/retention-studio-pipeline-pattern.md`:

```text
strategy → scored ideas → mechanism explainer → shot plan → keyframe/I2V prompt plan → seeded clip jobs → clip review → deterministic final renderer
```

When implementing a new repo or core pipeline, first scaffold the approval-first typed artifact chain in `references/approval-first-retention-studio-core.md`:

```text
ContentPillar → ContentBrief → ScriptDraft → VideoCandidate → TestBatch → ReviewBatch → Telegram ReviewDelivery → ShotPlan
```

Hard rule for this class: AI-video clips provide photorealistic texture and motion only. Captions, numbers, labels, arrows, UI text, price/risk/margin layers, and factual claims are renderer-owned. Final content should keep `static_card_limit = 0`, use visual changes every 1–2 seconds, and reject technically successful clips that do not serve the retention beat.

Before video generation, build an inspectable content-to-review spine: `ContentBrief → ScriptDraft → CandidateDraft → VideoCandidate → TestBatch → ReviewBatch → Telegram ReviewDelivery`. Keep it deterministic, side-effect free, and human-approval-first so hooks, retention beats, visual concepts, policy notes, and review copy can be optimized before spending GPU/API time. See `references/content-to-review-pipeline.md` for the implementation pattern and guardrails.

Hard rule for this class: AI-video clips provide photorealistic texture and motion only. Captions, numbers, labels, arrows, UI text, price/risk/margin layers, and factual claims are renderer-owned. Final content should keep `static_card_limit = 0`, use visual changes every 1–2 seconds, and reject technically successful clips that do not serve the retention beat.

## Recommended workflow

1. **Define channel position**
   - Niche(s): e.g. life optimization, AI life systems, Christian faith/FaithTok.
   - Target audience and emotional problem.
   - Language and tone.
   - Whether the brand is faceless, AI-persona-led, or creator-led.
   - No-go topics and theological/ethical boundaries if faith-related.

2. **Generate test ideas before code-heavy automation**
   - Create 20–30 candidate ideas across 2–4 content pillars.
   - For each: hook, script kernel, visual concept, caption, CTA, risk/quality score.
   - Select a small first batch (e.g. 3 per pillar) for real testing.
   - Before renderers or external AI APIs, add a deterministic content-development core: `ContentPillar → ContentBrief → ScriptDraft → CandidateDraft → VideoCandidate`. See `references/content-development-core.md`.

3. **Use hook-first, retention-led scripting**
   - First 1–2 seconds must contain a clear tension, identity statement, pain point, contrarian line, or immediate self-recognition.
   - Prefer one idea per video over generic “4 tips” unless the list format is the explicit series.
   - Avoid generic advice viewers have already heard unless framed with a new angle, concrete payoff, or strong story.
   - Reward hooks with direct address, specificity, open loops, tension, and a visible payoff; reject slow intros such as “In this video...”.
   - See `references/retention-led-shorts.md` for scoring signals, penalties, and voiceover finalization rules.

4. **Quality gate before rendering**
   Score drafts on:
   - hook strength
   - novelty
   - emotional pull
   - clarity
   - retention risk
   - comment/share potential
   - AI-slop risk
   - platform/policy risk
   Render only drafts above the chosen threshold, e.g. 8/10.

5. **Visual strategy**
   - Avoid obvious AI artifacts, fake UI text, illegible generated lettering, generic charts, and soulless stock visuals.
   - Prefer high-contrast kinetic captions, visible hook text in first seconds, relatable B-roll, screen recordings for AI/tutorial content, phone/screen close-ups, before/after transformations, cards, and timer visuals.
   - Use pattern interrupts every 2–3 seconds for retention.
   - Before rendering, generate a shot plan from the finalized voiceover: first-frame hook, pattern interrupt, core explanation, screen/card visual, CTA, caption beats, and asset type.
   - Before writing a renderer, convert shot plans into deterministic render manifests: machine-readable timeline items, asset placeholders, caption tracks, style constraints, and approval constraints. See `references/render-manifest-pipeline.md`.
   - Treat this as a deterministic intermediate artifact, not optional prose. See `references/retention-render-planning.md` for the five-scene shot-plan pattern and renderer-facing fields.
   - For the first video output, build a manifest-driven preview renderer before any AI-video or polished asset pipeline: render vertical scene/card frames from manifest fields, assemble them with FFmpeg, then use the MP4 for timing/approval review. See `references/manifest-preview-renderer.md`.
   - When the user rejects static/slideshow output, add an `asset_plan.v2` layer that maps each scene to motion-led local asset modes such as `cinematic_ai_video`, `screen_recording_simulation`, `mechanism_animation`, or `kinetic_text_overlay` with no baked text. If the user requires a free workflow, do not depend on paid video-credit tools; use local procedural motion and optional free NVIDIA LLM text refinement. See `references/ai-video-asset-planning.md`.
   - For hybrid workflows, add an imported-clip assembly layer: provider handoff prompts create short manual AI-video clips, humans drop them into deterministic `data/imported_ai_clips/<slug>/scene_XX.mp4` paths, and the final renderer combines imported clips with local mechanism animations/captions. See `references/imported-ai-clip-assembly.md`.
   - For local Wan2.2 quality workflows, prefer keyframe/image-to-video over raw text-to-video: generate/select a clean keyframe, run native Wan2.2 I2V variants, review clips bluntly for subject clarity/motion/artifacts, then add all labels/numbers/captions via deterministic renderer. See `references/wan-i2v-retention-studio.md`.
   - When using NVIDIA Build/API Catalog for keyframes, prefer hosted `ai.api.nvidia.com` calls before considering self-hosted NIM deployment; implement a configurable model fallback chain, store the key as a runtime secret, and inspect/clean logos, emblems, license plates, and fake text before I2V. See `references/nvidia-keyframe-generation.md`.

6. **Social-media draft approval package before TTS/rendering polish**
   - Before TTS, platform APIs, or polished video generation, build a typed `SocialMediaDraft` from the approved candidate plus render manifest/preview.
   - Include platform targets, title variants, caption, hashtags, CTA, hook, duration, quality/risk summary, and preview reference.
   - Render this into a Telegram-safe approval message and wrap it in a side-effect-free delivery plan with `requires_human_approval=True` and `side_effects=()`.
   - Add a local demo CLI that prints metadata first, then the draft review text; it must not write files, call renderers, call Telegram, or access credentials.
   - See `references/social-draft-approval-package.md` for the concrete artifact sequence and guardrails.

7. **Human approval loop before posting**
   - Send preview to the user through the requested channel, usually Telegram.
   - Include: video or preview reference, title, caption, hashtags, platform recommendation, duration, quality/risk score.
   - Wait for explicit approval such as `Freigabe alle`, `Freigabe YouTube`, `Freigabe TikTok`, `Ablehnen`, or `Ändern: ...`.
   - Do not publish automatically unless the user has explicitly authorized that workflow.

8. **TTS / voice layer after draft approval**
   - Treat TTS as a later layer, not the starting point for a first post draft.
   - Decide voice, final voiceover script, words-per-second, scene timing, caption sync, audio format, provider, and separate voice approval only after the social draft package is reviewable.
   - This avoids spending TTS/rendering effort on unapproved hooks, captions, or platform positioning.

9. **Publish and measure**
   Track at minimum:
   - platform, video id/url, niche, format, hook, duration
   - views at 2h/24h/7d
   - likes, comments, shares, saves, follows
   - retention metrics if available
   Use results to generate the next batch.

## Platform duration/monetization notes

See `references/monetization-and-approval-notes.md` for current working notes captured from official YouTube/TikTok docs and the recommended approval/posting setup. See `references/retention-led-shorts.md` for retention scoring signals, no-faith/adjacent-pillar handling, and voiceover finalization rules. See `references/render-manifest-pipeline.md` for the script → shot plan → render manifest → renderer sequence that prevents generic AI slideshow output. See `references/manifest-preview-renderer.md` for the first deterministic Pillow/FFmpeg preview-renderer pattern. See `references/social-draft-approval-package.md` for the typed first-post approval package to build before TTS or platform APIs. See `references/ai-video-asset-planning.md` for motion-led final-render asset planning with Kling/Hailuo/Runway-style clips and screen simulations. See `references/imported-ai-clip-assembly.md` for hybrid assembly where manually generated provider clips are imported into a deterministic local renderer. See `references/nvidia-keyframe-generation.md` for NVIDIA-hosted keyframe generation, model fallback, secret handling, and pre-I2V logo/plate cleanup. Re-check official docs before making business-critical decisions because platform programs change often.

## Project/repo hygiene for automated video bots

- Keep source code in Git; keep generated videos, assets, `.env`, OAuth tokens, API keys, and downloaded secrets out of Git.
- Use `.gitignore` aggressively for outputs, media caches, local credentials, and temp files.
- Store platform credentials as runtime secrets; never print raw tokens.
- Build the repo as modular components: idea generation, scripting, rendering, review, publishing, analytics.
- For approval-first Retention Studio implementations, use a typed side-effect-free core before real I/O: `ContentBrief`, `ScriptDraft`, `VideoCandidate`, review batches, Telegram delivery plans, then shot plans. See `references/approval-first-retention-studio-core.md`.

## Sensitive-content positioning

When AI/productivity automation is the primary brand, be cautious about mixing in religious/FaithTok, health, finance, or other high-trust themes. If the user flags the combination as sensitive, pause that pillar rather than forcing it into the same testbatch. Replace it with a safer adjacent pillar (for example `attention_design`) and document the paused pillar in config so it can be revisited only with explicit approval and a separate brand strategy.

## Pitfalls

- Do not over-optimize renderer quality before testing whether the content format earns retention.
- Do not jump directly from a script idea to ComfyUI/Wan/Qwen/Image/video generation. First build and inspect the content-to-review spine so weak hooks, generic scripts, policy issues, and static visual concepts are killed before render time.
- Do not create a parallel candidate path for content experiments; convert content artifacts into the existing candidate factory input so IDs, hashtags, duration classes, quality gates, and pillar/platform validation stay centralized.
- Do not use `x or default` for optional content/script overrides in factories; explicit blank strings and zero durations must pass through to validation so tests can catch them.
- Do not assume TikTok monetization rules match YouTube: TikTok Creator Rewards emphasizes 1min+ eligible videos, while shorter videos may still be better for growth.
- Do not publish religious or health-adjacent content with overconfident claims; preserve humility, nuance, and platform safety.
- Do not rely on platform payout as the only monetization path; plan indirect monetization such as newsletter, templates, affiliates, digital products, or services.


# Social Draft Approval Package Pattern

Use this when moving from render planning toward the first user-facing social-media post draft for approval.

## Goal

Produce a complete approval draft before TTS, video rendering, or platform APIs:

```text
VideoCandidate + RenderManifest/Preview
→ SocialMediaDraft
→ SocialMediaDraft review text
→ side-effect-free delivery plan
→ demo CLI output
```

This gives the user a concrete post candidate to approve or revise while still avoiding accidental publishing, external calls, and wasted GPU/TTS work.

## Recommended typed artifacts

1. `SocialMediaDraft`
   - candidate id
   - target platforms: YouTube Shorts, TikTok, Instagram Reels
   - title variants
   - caption
   - hashtags
   - CTA
   - hook
   - planned duration
   - quality/risk summary
   - preview reference, e.g. HTML timeline available or MP4 pending
   - `requires_human_approval=True`
   - `side_effects=()`

2. Social review renderer
   - Telegram-safe text block
   - includes candidate, platforms, hook, titles, caption, hashtags, preview note, quality/risk summary
   - includes approval commands: `Freigabe alle`, platform-specific approvals, `Ablehnen`, `Ändern: <...>`
   - must not imply automatic upload or publishing

3. Draft delivery preparation
   - validates message length and safety language
   - returns typed delivery plan with `requires_human_approval=True` and `side_effects=()`
   - no Telegram API call yet

4. Demo CLI
   - e.g. `python -m autoshorts.cli.demo_social_draft`
   - prints delivery metadata first, then the review text
   - no file writes, platform calls, renderer calls, or credential access

## Sequence before TTS

Do not start with TTS. First get approval on the content package:

```text
ContentBrief
→ ScriptDraft
→ VideoCandidate
→ ShotPlan
→ RenderManifest
→ Preview
→ SocialMediaDraft
→ Review text
→ Approval delivery preview
```

Only after the draft is structurally approved should TTS design start:

- voice selection
- final voiceover script
- words-per-second / timing
- caption sync
- audio format
- provider choice
- separate voice approval if needed

## Guardrails

- No credentials in draft output.
- No platform API calls.
- No publish/post semantics except inside explicit approval-command documentation if the codebase already uses that language carefully.
- Preview references are allowed; generated media paths should be placeholders unless a later renderer created real artifacts.
- Keep captions, numbers, labels, arrows, UI text, claims, and platform metadata renderer-owned / system-owned, never baked into generated AI assets.