# AutoShorts Dashboard — Handoff

## Current commit
- Branch: `main`
- Latest commit SHA: pending Sprint 8 final commit; final SHA will be reported after push.
- Preview URL: `http://100.85.29.67:5176/dashboard`
- Backend port: `8012`
- Frontend port: `5176`
- Updated: `2026-06-07T11:15:45+02:00`

## Product direction
- Dashboard is the creator control point between prepared packages, analytics, website companion pages and controlled platform uploads.
- Website updates must be explicit creator decisions. The dashboard may prepare/generate/QA/show diff, but must not silently push the website repo.
- Sprint 8.8 adds quality-first production: Reject/Request changes workflows, Dashboard-owned quality profile, mandatory Director skill, import quality gate and Review Quality Gate card.
- Visible UI should remain creator-facing: prepared packages, review, quality gate, account readiness, private upload, audit trail, website guide status.

## Sprint 8.8 — Quality Gate handoff

Mandatory docs:
```text
docs/agent-production-contract.md
docs/director-production-skill.md
```

Key runtime/API rules:
- `/api/agent/production/next` returns `quality_profile.version = autoshorts-quality-v1` and `director_skill.required = true`.
- ProductionQueue prepared packages require `manifest.quality.overall = passed` before they can become active Review candidates.
- Missing/failed quality on `source=prepared_package` + `queue_item_id` imports as `needs_internal_fix`, not `ready_for_review`.
- Review page exposes `Reject video` and `Request changes` as explicit modal workflows.
- Rejected videos set VideoAsset/PostDrafts to `rejected`, ProductionQueueItem to `needs_changes`, create ReviewDecision and ActivityEvent, and leave active Review.
- Request changes sets VideoAsset/ProductionQueueItem to `needs_changes`, creates RevisionRequest + ReviewDecision and leaves normal Review.
- Upload/export actions are blocked for `rejected`, `needs_changes`, `quality_failed`, `needs_internal_fix`, `archived`, `failed`, or missing/failed Quality Gate.

## Sprint 7.2 baseline
- Real YouTube analytics imported from `Youtube 260606`:
  - 41 videos
  - 436 views
  - 18 linked
  - 23 unlinked
- Source index links Analytics -> Script -> Family -> Website where high-confidence YouTube-ID matches exist.
- Top linked families:
  - Everyday Red Flags — 132 views
  - Creator Account Traps — 80 views
  - Text & Delivery Scams — 41 views

## Sprint 8 — YouTube Private Upload MVP

### Implemented backend
New/updated pieces:
```text
backend/app/services/youtube_upload.py
backend/app/security/tokens.py
backend/app/api/routes/accounts.py
backend/app/api/routes/publish.py
backend/app/api/routes/logs.py
backend/app/api/routes/settings.py
backend/app/models/core.py
backend/app/db/session.py
```

Dependencies added:
```text
google-api-python-client
google-auth
google-auth-oauthlib
google-auth-httplib2
```

### OAuth / Accounts
Endpoints:
```text
GET  /api/accounts/youtube/connect/start
GET  /api/accounts/youtube/connect/callback
POST /api/accounts/youtube/disconnect/{account_id}
POST /api/accounts/youtube/reconnect/{account_id}
POST /api/accounts/youtube/test/{account_id}
GET  /api/accounts/youtube/status
GET  /api/accounts
```

Scope:
```text
https://www.googleapis.com/auth/youtube.upload
```

Security:
- OAuth state stored server-side and validated in callback.
- Access/refresh tokens encrypted with Fernet derived from `AUTOSHORTS_SECRET_KEY`.
- Token fields are never returned to frontend; only `has_access_token` / `has_refresh_token` booleans.
- Current live config has no Google client ID/secret, so OAuth start shows a clear config blocker instead of fake connecting.

Current OAuth config status:
```text
configured: false
client_id_present: false
client_secret_present: false
redirect_uri: http://127.0.0.1:8012/api/accounts/youtube/connect/callback
scope: https://www.googleapis.com/auth/youtube.upload
default_privacy: private
notify_subscribers: false
category_id: 22
dry_run: false
```

### Private upload endpoint
Endpoint:
```text
POST /api/publish/youtube/private-upload/{post_draft_id}
```

Payload:
```json
{
  "account_id": "...",
  "confirm_private_upload": true,
  "confirm_metadata_reviewed": true,
  "confirm_disclosure_reviewed": true,
  "dry_run": true
}
```

Validation:
- VideoAsset exists.
- Video file exists.
- PostDraft provider is YouTube.
- Title and description are present.
- Disclosure is reviewed.
- Privacy is private.
- Confirmation booleans are true.
- Real upload requires connected YouTube account.
- Dry-run can use a clearly marked `YouTube Dry-run Account` with `account_type=dry_run` and `status=dry_run`.

Sprint 8 hard blocks:
- no public upload
- no unlisted upload
- no notifySubscribers
- no Website push
- no TikTok

### Upload audit model
New model:
```text
YouTubeUploadAudit
```

Fields include:
```text
video_asset_id
post_draft_id
platform_account_id
publish_job_id
youtube_video_id
youtube_url
privacy_status
title
description_hash
tags_json
category_id
contains_synthetic_media
self_declared_made_for_kids
notify_subscribers
upload_status
api_endpoints_called
oauth_scopes_used
website_link_allowed
request_redacted_json
response_redacted_json
error_code
error_message
created_at / updated_at
```

Activity events:
```text
youtube_upload_queued
youtube_upload_started
youtube_upload_private_succeeded
youtube_upload_failed
```

### Dry-run result
Dry-run executed against prepared package:
```text
Package: Login Prompt You Didn’t Start? Don’t Approve
PostDraft ID: 7c6cac62-3e11-4450-be2e-4f46fc93df6e
```

Result:
```text
Dry-run upload: succeeded
Real YouTube API called: no
YouTube ID: dryrun_6c8db17d8ce14520
Upload audit ID: dcac1ac5-0274-4d5c-aca6-a8db683741e0
PublishJob ID: ca736b6f-e2ab-4fb9-b11d-8f48a891a932
Privacy: private
notifySubscribers: false
website_link_allowed: false
```

Context updates verified:
```text
PostDraft.external_post_id = dryrun_6c8db17d8ce14520
VideoAsset.external_youtube_id = dryrun_6c8db17d8ce14520
ExternalPost created/linked
ActivityEvent created
WebsiteCompanion status = waiting_for_public_video
```

### Frontend implemented
Updated:
```text
frontend/src/pages/Accounts.vue
frontend/src/pages/Review.vue
frontend/src/pages/Settings.vue
frontend/src/pages/PublishLogs.vue
frontend/src/api/client.ts
```

Accounts page:
- YouTube OAuth config status.
- Connect YouTube disabled if config missing.
- Reconnect / Disconnect / Test connection for stored YouTube account.
- TikTok remains explicitly planned for Sprint 9; no fake connect.

Review page:
- YouTube private upload readiness card.
- Checks video/title/description/tags/disclosure/account/privacy.
- Explicit confirmation before upload.
- Dry-run button available without real OAuth.
- Result shows YouTube ID/URL and Website link status.

Settings page:
- YouTube Upload section shows client ID/secret present/missing only.
- Shows redirect URI, scope, default privacy, notify subscribers, category ID, synthetic-media default, dry-run setting.
- Secret values are never displayed.

Logs page:
- YouTube uploads filter.
- Shows time/title/status/YouTube ID/privacy/endpoints/scopes/error with redacted payloads.

## Website Bridge status — unchanged and not pushed
Website repo:
```text
/home/agent/projects/TrueTraceShorts_WebSite
```
Branch:
```text
website-mvp-v0
```

Pending Website files remain intentionally uncommitted/unpushed:
```text
?? data/candidates/package_real_003.json
?? public/redflags/thumbnails/login-prompt-you-didn-t-start-don-t-approve-start.webp
?? src/content/redflags/login-prompt-you-didn-t-start-don-t-approve.md
```

Status after Sprint 8 dry-run:
```text
Guide: Login Prompt You Didn’t Start? Don’t Approve
WebsiteCompanion status: waiting_for_public_video
Files: 3
Push status: waiting for approval
Website push occurred: no
Discard occurred: no
```

Important: `video_url_internal` may contain the dry-run/private YouTube URL internally, but `website_link_allowed=false`; public website output must not reveal private links until explicitly allowed and pushed after QA.

## Analytics after Sprint 8
Analytics CSV state unchanged:
```text
41 videos
436 views
18 linked
23 unlinked
```

Dry-run upload created an `ExternalPost` with the dry-run ID. Later real CSV imports with real YouTube IDs will map by YouTube ID.

## Sprint 8.1 — Existing Upload Reconciliation, Uploader Bridge & Production Queue

### Reconciled existing YouTube uploads
Existing Jarvis uploader audits were imported from:
```text
/home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/data/youtube_upload_audits
```

Reconciled videos:
```text
erf-027-small-delivery-fee-director-pass -> egZgCZCGGhU -> website slug small-delivery-fee -> pushed
erf-026-fake-support-number-director-pass -> VT669BkNdnw -> website slug fake-support-number -> pushed
erf-025-mfa-prompt-without-login -> J6ErKh55-KQ -> website slug mfa-prompt-without-login -> pushed
```

Dashboard state updated:
```text
VideoAsset.external_youtube_id set to real YouTube IDs
PostDraft.external_post_id set to real YouTube IDs
ExternalPost created/linked
YouTubeUploadAudit created with upload_status external_existing_upload
WebsiteCompanion status set to pushed where website markdown exists
ActivityEvent emitted: existing_youtube_upload_detected, existing_website_guide_detected, website_state_reconciled
```

Dry-run audit remains available for Sprint 8 testing:
```text
dryrun_6c8db17d8ce14520 -> dry_run_succeeded
```
Real IDs win over dry-run IDs for matching candidates/checksums.

### Upload executors
New model:
```text
UploadExecutor
```

Current executor statuses:
```text
existing_jarvis_uploader -> active
dashboard_oauth -> configured_required
dry_run -> active
imported_existing -> active
```

Default active executor for real work is `existing_jarvis_uploader`:
```text
Uses the existing upload workflow that Jarvis already uses after "private upload approved".
```

### UploadRequest bridge
New model:
```text
UploadRequest
```

Review Page now offers upload method choices:
```text
Existing Jarvis uploader
Dashboard OAuth uploader
Dry-run only
```

For existing Jarvis uploader, the dashboard creates an approved UploadRequest and writes a token-free outbox JSON:
```text
storage/outbox/upload_requests/<request_id>.json
```

Approval command shape:
```text
APPROVE_UPLOAD <candidate_id> <video_sha_prefix>
```

Agent endpoints:
```text
GET  /api/agent/upload-requests/pending
POST /api/agent/upload-requests/{id}/mark-sent
POST /api/agent/upload-requests/{id}/attach-audit
POST /api/agent/upload-requests/{id}/complete
POST /api/agent/upload-requests/{id}/fail
```

Import script:
```text
scripts/import_youtube_upload_audits.py
```

Usage:
```bash
python scripts/import_youtube_upload_audits.py \
  --audit-dir /home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/data/youtube_upload_audits \
  --api-base http://127.0.0.1:8012 \
  --dry-run

python scripts/import_youtube_upload_audits.py \
  --audit-dir /home/agent/jarvis_runtime/autoshortsbot/AutoShortsBot/data/youtube_upload_audits \
  --api-base http://127.0.0.1:8012 \
  --import
```

### Website duplicate status
Current pending website files remain untouched:
```text
?? data/candidates/package_real_003.json
?? public/redflags/thumbnails/login-prompt-you-didn-t-start-don-t-approve-start.webp
?? src/content/redflags/login-prompt-you-didn-t-start-don-t-approve.md
```

Duplicate detection result:
```text
Potential duplicate website guide
existing slug: mfa-prompt-without-login
pending slug: login-prompt-you-didn-t-start-don-t-approve
recommendation: merge / discard pending / keep as new — requires explicit approval
push status: waiting for approval
```

No website files were deleted, committed, or pushed.

### Production Queue
New model:
```text
ProductionQueueItem
```

UI route:
```text
/production-queue
```

Agent production endpoints:
```text
GET  /api/agent/production/next
POST /api/agent/production/{id}/mark-started
POST /api/agent/production/{id}/attach-package
POST /api/agent/production/{id}/mark-failed
```

Current locked item:
```text
Title: If This Message Says Your Account Will Be Locked, Stop
Family: Everyday Red Flags
Status: locked_next
Expected manifest: storage/incoming/package_774e1ed1/manifest.json
```

Agent response includes:
```text
queue_item_id
family
title
brief
recommended_reason[]
expected_package_manifest
```

## Sprint 8.2 — End-to-End Production Loop

### Scope and hard limits
Sprint 8.2 validates the mechanical production loop:
```text
Dashboard Production Queue
→ Jarvis/agent fetches next item
→ Test prepared package is generated
→ Package lands in storage/incoming
→ Dashboard imports package
→ Video appears in Inbox/Review
→ Creator can create UploadRequest for existing_jarvis_uploader
→ Agent status transitions can complete without a real upload
```

Hard limits preserved:
```text
No TikTok
No website push
No real YouTube upload without explicit creator approval
No final/expensive Wan render without explicit creator approval
```

### Production Queue changes
ProductionQueueItem now carries:
```text
script_constraints
visual_constraints
voice_constraints
expected_package_id
expected_package_manifest
actual_package_id
video_asset_id
```

Ordering rule:
```text
locked_next wins
else highest approved_next by position/priority
else no production item
```

Current queue item used for E2E:
```text
Queue item: 774e1ed1-6500-4b9a-bea2-453708902e65
Title: If This Message Says Your Account Will Be Locked, Stop
Family: Everyday Red Flags
Package: package_774e1ed1
Status after E2E: in_review
```

### Agent production API
Endpoints:
```text
GET  /api/agent/production/next
POST /api/agent/production/{id}/mark-started
POST /api/agent/production/{id}/attach-package
POST /api/agent/production/{id}/mark-ready-for-review
POST /api/agent/production/{id}/mark-failed
```

`GET /api/agent/production/next` includes:
```text
queue_item_id
family / family_id
idea / idea_id
content_script_id
title / brief
script_constraints
visual_constraints
voice_constraints
recommended_reason[]
expected_package_id
expected_package_manifest
production_rules
```

Production rules always include:
```text
no_final_wan_render_without_approval: true
test_preview_allowed: true
renderer_owned_text: true
no_unreviewed_upload: true
```

### Agent contract documentation
New document:
```text
docs/agent-production-contract.md
```

It defines:
```text
Chat command
API sequence
package folder
manifest fields
status transitions
failure cases
things Jarvis must never do automatically
```

### Test package generator
New script:
```text
scripts/create_test_package_from_queue.py
```

Command used:
```bash
python scripts/create_test_package_from_queue.py \
  --api-base http://127.0.0.1:8012 \
  --import
```

Generated package:
```text
Package ID: package_774e1ed1
Manifest: storage/incoming/package_774e1ed1/manifest.json
Video: storage/incoming/package_774e1ed1/video.mp4
```

Imported VideoAsset:
```text
900c887c-f76c-40ef-a491-a402133f654e
```

The first test run exposed a script bug: it selected the last imported `video_id` instead of the log matching `folder == package_id`. Fixed in `scripts/create_test_package_from_queue.py`; queue link repaired to the correct VideoAsset above.

### Review / UploadRequest E2E
Review page now shows Production Queue origin:
```text
Queue item
Family
Status
Package ID
Reason
Brief / script / visual / voice constraints
```

UploadRequest tested with existing Jarvis uploader:
```text
UploadRequest ID: 0faaf66c-9596-41a7-a95f-b31abe6e5a8f
Outbox: storage/outbox/upload_requests/0faaf66c-9596-41a7-a95f-b31abe6e5a8f.json
Transition: approved -> sent_to_executor -> completed
Real upload: no
YouTube ID: null
```

Outbox was verified to contain no:
```text
token
secret
refresh
access_token
client_secret
```

### Dashboard UI changes
Dashboard home now shows:
```text
Next production
Package waiting for review
Upload requests waiting
Website updates waiting
Analytics summary
```

Production Queue UI now shows:
```text
Next to produce
Locked next
Suggested by performance
Manual queue
Recently produced
Family rotation
Status / reason / expected package manifest
Actions: Set as next, Lock next, Move up/down, Send to production, Hold, Archive, Edit brief
```

### Verification Sprint 8.2
Backend:
```text
uv run pytest -q -> 52 passed, 3 warnings
uv run ruff check app tests ../scripts/canonicalize_youtube_csvs.py ../scripts/import_youtube_upload_audits.py ../scripts/create_test_package_from_queue.py -> All checks passed
```

Frontend:
```text
npm run build -> success
```

Website state remains unchanged:
```text
No website push
No delete
No merge
pending package_real_003 duplicate still waiting for approval
```

## Sprint 8.3 — Verification-Ready TikTok Demo Flow

### Scope and hard limits
Sprint 8.3 prepares a recordable TikTok Developer verification demo flow.

Hard limits preserved:
```text
No real TikTok posting
No TikTok Direct Post
No public upload
No YouTube upload
No website push
No final/expensive Wan render
```

### TikTok posture
The Dashboard presents itself as:
```text
Creator review and publishing workspace for prepared short-form videos.
```

Avoid external/product wording like:
```text
Jarvis Bot
private uploader
internal upload utility
automation bot
```

TikTok capability shown:
```text
Scope: video.upload
Endpoint: /v2/post/publish/inbox/video/init/
Upload method: FILE_UPLOAD
Flow: Upload Draft / Inbox notification
Direct Post: disabled
```

### Backend additions
New model:
```text
TikTokUploadAttempt
```

Fields include:
```text
video_asset_id
post_draft_id
platform_account_id
mode: mock | sandbox | real_disabled
scope: video.upload
source: FILE_UPLOAD | PULL_FROM_URL
publish_id
upload_status
transfer_status
status_fetch_result_json
request_redacted_json
response_redacted_json
error_code / error_message
created_at / updated_at
```

New/updated endpoints:
```text
POST /api/publish/tiktok/upload-draft/{post_draft_id}
GET  /api/publish/tiktok/attempts
GET  /api/publish/tiktok/status
GET  /api/verification/tiktok
GET  /api/logs?kind=tiktok_verification
```

Activity events:
```text
tiktok_demo_started
tiktok_upload_init_mocked
tiktok_file_upload_succeeded
tiktok_upload_failed
```

### Modes
Mock mode:
```text
Creates fake publish_id
Writes TikTokUploadAttempt
Writes ApiCallLog with mock=true
Writes ActivityEvents
No external API call
Status: mock_uploaded_to_draft
```

Sandbox mode:
```text
Requires TikTok client id/secret config.
If credentials are missing, UI/API honestly reports Sandbox not configured.
Current Sprint 8.3 live status: sandbox_configured=false.
```

Real mode:
```text
Direct Post is not enabled.
real_disabled blocks with explicit error.
```

### Live mock demo result
Test video workflow used:
```text
VideoAsset: 900c887c-f76c-40ef-a491-a402133f654e
Package: package_774e1ed1
Title: TEST PREVIEW — If This Message Says Your Account Will Be Locked, Stop
TikTok draft: 027ce33c-1fc2-41db-a333-f876409cf332
```

Mock attempt:
```text
TikTokUploadAttempt: bc8e9697-df3c-421a-9b4b-f442df7516df
Publish ID: mock_publish_7b61b68d6631
Mode: mock
Scope: video.upload
Source: FILE_UPLOAD
Status: mock_uploaded_to_draft
Transfer: mock_file_transfer_succeeded
External TikTok API call: no
```

Verification readiness:
```text
Ready for internal rehearsal: yes
Ready for TikTok submission video: no
Reason: TikTok Sandbox credentials/account are not configured in this local preview.
```

### UI changes
Routes:
```text
/verification
/settings/verification
```

Verification page shows:
```text
TikTok Verification Demo
Readiness score
Mode: Mock / Sandbox / Real disabled
Scope: video.upload
Upload method: FILE_UPLOAD
Current test video
Connected TikTok account
Last OAuth/upload init/transfer/status check
Blocking issues
Recordable demo path
Recent attempts
```

Review page TikTok section now shows:
```text
Video preview
TikTok caption
Hashtags
Synthetic media / AIGC disclosure
Commercial content disclosure
Upload mode: TikTok Draft
Scope: video.upload
Account/Sandbox status
Consent checkbox
Create TikTok draft button
Status card with publish_id/upload status/log link
```

Logs page now includes:
```text
TikTok Verification filter
mode / endpoint / scope / publish_id / status
redacted request/response
```

### Docs added
```text
docs/tiktok-verification-demo-script.md
docs/tiktok-verification-domain-plan.md
```

Domain recommendation:
```text
Use public staging domain for dashboard, e.g. https://dashboard.truetraceshorts.com, protected with login and matching submitted TikTok Website URL.
```

### Verification Sprint 8.3
Backend:
```text
uv run pytest -q -> 57 passed, 3 warnings
uv run ruff check app tests ../scripts/canonicalize_youtube_csvs.py ../scripts/import_youtube_upload_audits.py ../scripts/create_test_package_from_queue.py -> All checks passed
```

Frontend:
```text
npm run build -> success
```

Browser QA:
```text
/verification loads, no console errors
/videos/900c887c-f76c-40ef-a491-a402133f654e/review loads, TikTok Draft card visible, no console errors
/publish-logs?kind=tiktok_verification loads, log visible, no console errors
```

Website state remains untouched:
```text
No website push
No delete
No merge
pending package_real_003 duplicate remains waiting for approval
```

## Sprint 8.4 — German UI/UX Clarity & Visual Polish

### Scope
Sprint 8.4 is UI/UX only:
```text
German visible UI copy
clearer navigation
simpler overview
consistent thumbnails
clearer review, analytics, production-plan and verification pages
visual alignment/polish
```

Hard limits preserved:
```text
No TikTok Sandbox expansion
No real TikTok upload
No YouTube upload expansion
No website push
No final Wan render
```

### Main UI changes
Navigation changed to German creator-facing information architecture:
```text
Übersicht
Videos
Upload
Kalender
Produktionsplan
Analyse
Content-Bibliothek
Protokolle
Verifizierung
Einstellungen
```

New helper/component layer:
```text
frontend/src/utils/labels.ts
frontend/src/components/ui/PageHeader.vue
frontend/src/components/media/VideoThumb.vue
```

`labels.ts` centralizes:
```text
statusLabel
statusTone
providerLabel
uploadMethodLabel
verificationModeLabel
```

### Simplified pages
Overview `/dashboard` now focuses on:
```text
Nächster Schritt
Heute offen
Kurze Auswertung
Letzte Videos
```

Videos `/videos` now uses:
```text
Vorbereitete Videos
collapsible filters
German status badges
disabled planned buttons for non-connected actions
consistent thumbnails
```

Review page:
```text
Video
Prüfung
Plattformtexte
TikTok-Verifizierung
YouTube private upload
Ursprung im Produktionsplan
```

Production queue is now `Produktionsplan`:
```text
Nächstes zu produzierendes Video
Fixiert
Vorschläge
Manuell geplant
Technische Details anzeigen
```

Verification page `/verification` is now recording-oriented German copy:
```text
TikTok API-Verifizierung
Bereit für interne Generalprobe
Noch nicht bereit für TikTok-Einreichung
Grund: Sandbox-Zugangsdaten fehlen
8-step Ablaufkarte
```

Logs and Settings are Germanized for main visible copy.

### Thumbnails
`VideoThumb.vue` now has:
```text
9:16 default
small / medium / large / hero / free variants
cover/contain fit option
neutral gradient fallback with title initials
elegant load-error fallback
deziertes Play icon
optional status badge
```

### UX audit
New doc:
```text
docs/ux-audit-sprint-8-4.md
```

Documents:
```text
found UI problems
overloaded pages
English terms replaced
layout decisions
recording-ready pages
what was intentionally not built
```

### Verification
Frontend build:
```text
npm run build -> success
```

Required copy scan:
```text
grep -R "Readiness score\|Recordable demo path\|Current test video\|Open inbox\|Create TikTok draft\|Production Queue\|Upload requests waiting" frontend/src || true
-> no matches
```

Final backend/browser QA is recorded in the Sprint 8.4 completion report.

## Sprint 8.5 — TikTok-Verifizierungs-Generalprobe

### Current decision / risk posture
Sprint 8.5 prepares an internal rehearsal and possible first best-effort submission recording with the current dashboard preview.

No additional public dashboard domain will be purchased or configured for this first attempt.

Known review risk:
```text
TikTok requests sandbox usage for first-time review.
TikTok requests the web-app domain shown in the demo to match the submitted website URL.
Current setup is a Tailnet/internal preview, not a public staging dashboard domain.
```

Decision:
```text
Proceed with a best-effort recording first.
Keep UI focused on the app in fullscreen/browser-app mode.
Do not claim sandbox or public staging if not true.
```

Hard limits preserved:
```text
No new domain
No Sandbox credentials
No real TikTok upload
No Direct Post
No YouTube upload
No website push
No final Wan render
```

Docs updated/created:
```text
docs/tiktok-verification-domain-plan.md
docs/screenrecording-runbook.md
docs/tiktok-app-review-submission-draft.md
```

Recording URL:
```text
/verification/recording?recording=1
```

The normal `/verification` page gets an `Aufnahme-Modus starten` action that opens the guided recording flow.

## Sprint 8.6 — Clean TikTok Recording Page

### Superseded by Sprint 8.7
Sprint 8.6 remains in git history as a clean recording-stage implementation, but the product decision has changed: automatic TikTok upload and mock demo are paused. TikTok now uses manual MP4/caption export from Review.

### Scope
Sprint 8.6 narrows `/verification/recording?recording=1` into a clean creator-app recording stage only.

Visible recording flow:
```text
Play prepared short video
Edit Description
Edit Hashtags
Check AI-generated / synthetic media
Approve creating a TikTok draft
Create TikTok draft
Show short success card
```

Explicitly removed from the recording page:
```text
YouTube
Website / Domain / Cloudflare
Sandbox / Mock / risk explanations
Direct Post / API endpoint / scope / FILE_UPLOAD / PULL_FROM_URL
publish_id as visible headline
raw JSON / redacted request / redacted response
protocol/detail panels
```

These details remain documented on `/verification`, logs, and internal docs. The recording page is the stage, not the compliance appendix.

### Recording demo video selection
Backend now exposes the selected recording video through `/api/verification/tiktok` using `select_recording_demo_video`.

Selection priority:
```text
real prepared package with TikTok draft and duration > 10s
real prepared package with caption and existing media
newer ready/reviewable package
5s test package only as last fallback
```

Current selected video after Sprint 8.6 QA:
```text
Title: This Small Fee Can Expose Your Card
VideoAsset ID: 7a291e28-24fd-4980-a680-8f8708799bb0
Package ID: package_real_001
Duration: 23.75s
File size: 14248080 bytes
Media endpoint: 200
Thumbnail endpoint: 200
Caption present: yes
```

## Sprint 8.7 — Production System Dashboard

### Product decision
Automatic TikTok upload and TikTok mock-demo recording are paused. The Dashboard is now the production control system:

```text
Content Library → Production Plan → Jarvis next item → Package Review → YouTube private upload/schedule → TikTok manual export → Analytics import → next recommendation
```

### Implemented UI direction
- `/dashboard` is a decision cockpit: next to produce, waiting review, scheduled uploads, analytics linking, upload requests, recent results.
- `/production-queue` remains the source of truth for “produce the next Short”. Jarvis must call `/api/agent/production/next`; chat improvisation is not the authority.
- `/videos/:id/review` now uses:
  - large 9:16 video player;
  - TikTok Manual Export: Download MP4, copy caption/hashtags, mark exported/posted;
  - YouTube Private Upload: edit title/description/tags, confirm private/disclosure/metadata, upload now when OAuth is connected, schedule private upload, or approve token-free request for existing Jarvis uploader;
  - optional Website Companion workflow remains separate and approval-gated.
- The main navigation no longer promotes TikTok verification/mock recording.

### Current YouTube real-upload readiness
```text
Dashboard OAuth configured: no
Connected OAuth YouTube account: no
Available real path: existing Jarvis uploader UploadRequest/outbox
Available verification path: dry-run private upload
```

A real dashboard-OAuth private upload requires Google client ID/secret and a connected test/creator account. Until then the dashboard can approve an UploadRequest for the existing Jarvis uploader, but it cannot truthfully call YouTube directly.

## Sprint 8.7 — Production Plan, Reset & first real production run readiness

### Product rule
- Production Plan is the source of truth. Jarvis must call `/api/agent/production/next` and must not improvise from chat.
- `GET /api/agent/production/next` returns `requires_concept_confirmation=true` and `confirm_command=APPROVE_PRODUCTION <queue_item_id>`.
- Jarvis must first call/post concept via `/api/agent/production/{id}/concept-proposed` and present the concept in chat.
- Production may start only after `/api/agent/production/{id}/approve-generation`; `/mark-started` rejects unapproved items with HTTP 409.

### Implemented
- `/production-queue` now supports 10+ recommendations, Duplicate Risk, Similar Content, Rotation Hint, Performance Reference, Move up/down, Lock, Unlock, Replace locked, Hold, Archive, Send to concept.
- Recommendation seeding endpoint: `POST /api/production-queue/seed-recommendations`.
- Concept endpoints: `concept-proposed`, `approve-generation`, `reject-concept`.
- Safe reset endpoint/script:
  - `POST /api/dev/reset-active-workspace`
  - `python scripts/reset_active_workspace.py --archive-active-review --keep-analytics --keep-content-library`
- Active Videos/Inbox default view shows only `ready_for_review`, `in_review`, `needs_metadata`; after reset it shows: `No videos waiting for review. Production plan ready. Next production not started yet.`
- Existing Jarvis YouTube uploader is the primary real route:
  - name: `Existing Jarvis YouTube uploader`
  - status: `ready`
  - credentials: `available`
  - mode: `private_upload`
  - handoff: token-free outbox or agent polling.
- Calendar exposes reschedule/cancel/upload now/retry and only shows actionable scheduled/failed items, not archived history.

### Current live state after reset/seed
```text
Active review queue: 0
Production suggestions: 12 visible/actionable (current locked item appears in Fixiert + list)
Current next item: Delivery fee traps: the courier chatbot pressure trick
Confirm command: APPROVE_PRODUCTION 1a52f20e-549e-4879-bf78-7f9bdcc5a239
Duplicate risk for locked item: low
Account-locked duplicates: detected and warned, not blindly selected
```

## Google Drive / CSV status
Correct folder remains:
```text
TrueTraceShorts/Analysen/260606 Youtube/
```

Current CSV status:
```text
Tabellendaten imported
Gesamtwerte missing
Diagrammdaten missing
```

Drive/gog remains diagnosed as failing due keyring/token unwrap issues. This does not block local video-level analytics.

## Verification
Backend:
```text
uv run pytest -q -> 42 passed, 3 warnings
uv run ruff check app tests ../scripts/canonicalize_youtube_csvs.py -> All checks passed
```

Frontend:
```text
npm run build -> success
```

Live smoke:
```text
GET /api/health -> 200
GET /api/settings -> YouTube config status visible, no secrets
GET /api/accounts -> YouTube config visible, dry-run account redacted
POST /api/publish/youtube/private-upload/{draft_id} with dry_run=true -> succeeded
GET /api/logs?kind=youtube_uploads -> dry-run audit visible, tokens absent
GET /api/website/status -> 3 pending website files, waiting for approval
```

## Git hygiene
- Commit should include code, tests, docs, pyproject/lock and `.env.example` only.
- Do not commit local DB, storage, media, CSVs, generated frontend dist, or Website repo pending files.
- Website repo must remain unpushed until explicit approval.

## Next work
- Configure Google OAuth app/test user and set local `.env` values if real upload is desired.
- Run a real private upload only after Account page shows connected YouTube account.
- Keep Website link hidden until the YouTube video is public/unlisted and link_allowed is explicitly set.
- Sprint 9 remains TikTok Sandbox / Verification Flow.
