Skip to content

Parker's#3

Open
parrrkerli wants to merge 11 commits into
mainfrom
parker's
Open

Parker's#3
parrrkerli wants to merge 11 commits into
mainfrom
parker's

Conversation

@parrrkerli

Copy link
Copy Markdown
Collaborator

No description provided.

@nanxingw

nanxingw commented Apr 1, 2026

Copy link
Copy Markdown
Owner

Thanks for the contribution! I took a look at the changes.

A few issues need to be addressed before this can be merged:

  1. node_modules/ committed — This directory should never be checked in. It adds ~1.3M lines and 5000+ files. Please remove it and ensure .gitignore excludes node_modules/.

  2. dist/ committed — Build artifacts shouldn't be in version control either. These are generated by npm run build and should also be in .gitignore.

  3. Commit "Upload local working copy" — It looks like the entire local working directory was pushed as-is. Please clean up the history so only source changes are included.

Suggested next steps:

  • Rebase onto latest main
  • Remove node_modules/ and dist/ from tracking (git rm -r --cached node_modules dist)
  • Squash or clean up commits so the actual feature changes are easy to review
  • Re-push the branch

Happy to review again once cleaned up!

parrrkerli and others added 10 commits April 2, 2026 11:56
…ements

- Abort running tasks when leaving Studio (server kills CLI on disconnect)
- Add /api/works/:id/abort endpoint
- Don't auto-start tasks when opening draft works
- Add genre-specific guides and emotional hooks modules to all skills
- Move creator-analytics into trend-research, remove standalone skills
- UI redesign with improved pipeline steps, asset panel, and i18n

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…hance UI

Add new skill modules (beat-sync, music-search, video-understanding),
improve Explore/Studio/Works pages with better UX flow, and refine
emotional hooks across all skill pipelines.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nd explore showcase

- Add titleLocked flag to prevent auto-title overwrites after first agent-set
- Add per-category title rules (envy: short+identity, anxiety: short+pain, conflict: short+controversy, comedy: suspense+no-spoiler)
- Replace dropdown tags in Studio header with plain static tags
- Envy cover image must be visually stunning/breathtaking (not candid)
- Add Harvard showcase work to Explore page under envy category
- Chat history fallback for showcase copytext extraction
- Fix ContentCategory type to include anxiety/conflict/envy

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…motion labels

- Complete i18n: replace all hardcoded Chinese with tt() calls across 14 files (~150 new keys)
- Language-aware agent: store language on Work model, switch all prompts to English when lang=en
- Voice narration for short-video: edge-tts integration with auto voice selection (zh/en)
- Agent announces voice choice before generating narration
- Neutral emotion labels: replace 焦虑→共鸣, 愤怒→争议感 in all user-facing text
- Explore showcase: image carousel with EN/ZH versions, chat fallback copytext
- Install voice skill (edge-tts based TTS)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…plore page

- Inspiration page: fix empty state copy, remove English override for showcase entry
- Rename 素材库 → 我的素材, move from Works page into settings drawer
- Restyle AssetLibrary to match product design system (blockquote guide, pill tabs)
- Remove research config and save button from settings drawer
- Remove grid/list view toggle

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Rename settings to "你的画像", topbar shows icon + text horizontally
- Remove AssetLibrary dropdown, show content directly in sidebar
- Merge theme/language switches into one right-aligned row
- Upload button → full-width dashed drop zone
- Inspiration empty state → 暂无优秀作品展示

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…load API

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…e page redesign

- Fix Link Portrait tooltip z-index overlap with tabs
- Output tab now shows phone-frame preview (Douyin style for video, XHS style for image-text)
- Remove topicHint fallback in output tab — only show final copytext results
- Explore page: add video playback support with Douyin-style fullscreen player
- Explore page: portrait card layout (3/4 ratio), video cards fill with title overlay
- Explore page: equal-height grid, author row pinned to bottom
- Add "5:47. No one's up yet." as first showcase entry in 向往拥有 tab
- Add AI watermark removal step (4.5) in content-assembly skill
- Add talking-head/口播 default style in content-planning skill
- Fix Dreamina default model from seedance2.0fast to seedance2.0
- Pass modelVersion through /api/generate/video endpoint
- Cover image priority: output/cover.* > final video > output images
- Remove dist/ from git tracking (already in .gitignore)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add Jimeng lip-sync API (realman_change_lips) for video lip-syncing
- Rewrite talking-head style: one-shot phone-selfie feel, coach presence
- Smart pipeline skip: auto-detect user-provided assets/direction and
  skip unnecessary steps (research, plan, assets) for both video and
  image-text content types
- Auto-detect person gender from video before selecting TTS voice
- Add Quality Review info tooltip with i18n
- Remove watermark removal step
- Remove node_modules from version control

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
nanxingw added a commit that referenced this pull request May 11, 2026
… Bug #3)

Symptom (2026-05-09):
  导出走完所有阶段(render → duck → loudnorm → burn → encode)
  但进度条始终是 0%;走完后 modal 1.5s 自动消失,没有任何产物入口。

Root cause #1 — progress reset between stages:
  worker.ts:160 把 onProgress 的 pct 直接写进 store。每个 stage 在
  render-pipeline.ts 内只 emit 两次:onP(stage, 0) 进入 + onP(stage, 1)
  完成。下一 stage 进入时又 onP(next, 0) 把 progress 拉回 0。前端看
  到 stage 切换但 progress 反复 0→1→0→1,UI 显示全程 0%。

Root cause #2 — outputPath 不在 done event 里:
  worker.ts:208 done event 只包含 {status, progress}。outputPath 仅
  存到 store,前端只能在 reconnect 时通过 snapshot 拿到。如果 modal
  在 run 期间已开,永远收不到 path。

Root cause #3 — done state UI 1.5s 后消失:
  ExportProgress.tsx:41 done 后 setTimeout(onClose, 1500),用户看到
  "Export complete" 闪一下 modal 关掉,再也找不到产物。

Fix:
  • worker.ts: aggregateProgress(stage, pct) — 5 stage × 0.2 slice,
    每个 stage 内的 pct 线性映射到自己的 slice。新增导出供以后扩展
    (ffmpeg -progress 解析)使用。
  • worker.ts: done event 带 outputPath。WorkerProgressEvent 加可选
    outputPath 字段。
  • render-ws.ts: snapshot 帧也带 outputPath + error,确保 reconnect
    到 terminal job 时 UI 能恢复完整状态。
  • ExportProgress: 移除 done auto-close。done 状态新增"open output"
    affordance(path 缩略 + 跳转到 /api/works/<id>/assets/output/...)。
    Cancel 按钮在 terminal 状态变为 Close 同槽位(用户始终有出口)。
  • TopBar: 把 workId 传给 ExportProgress 用于拼 URL。
  • i18n: 新增 btnClose / btnOpen(en + zh)。

Tests:
  • worker.test.ts: AC1 progress=0.5 改为 0.1(render is stage 0 of 5)。
  • ExportProgress.test.tsx 改两个测试匹配新契约:
    - "auto-closes after 1500ms" → "stays open with Close + open link"
    - "Cancel disabled in terminal" → "Cancel slot becomes Close"
  • 全量 server (15/15) + render-status (5/5) 通过。
  • 5 个预存失败的 integration test (No QueryClient / Remotion mount)
    跟此 commit 无关 — git stash 验证过。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
nanxingw added a commit that referenced this pull request May 11, 2026
…opt #3)

This is the LOAD-BEARING PRIMITIVE for the parallel streaming-encode
path. The full Stage 1 replacement (Puppeteer worker pool → ffmpeg
image2pipe) is a 2-3 week sprint; this commit ships the ~10% that
unblocks the remaining 90%.

## What's shipped

### `src/server/render/frame-reorder-buffer.ts` (~150 LOC, full impl)
The synchronization primitive that lets parallel workers render frames
out-of-order yet write to ffmpeg stdin in-order:
  - `waitForFrame(n)` — async barrier; resolves when n === nextFrame
  - `advanceTo(n+1)` — release the next waiter
  - Idempotent advance, range-clamped, graceful dispose
  - Double-write detection (rejects waitForFrame for past frames)
Ported nearly verbatim from heygen-com/hyperframes
packages/engine/src/services/streamingEncoder.ts:51-95.

10 dedicated unit tests including the canonical 3-worker / 6-frame
out-of-order completion test.

### `src/server/render/streaming-encoder.ts` (~180 LOC)
Sequential streaming encoder that's already correct + production-ready:
  - Spawns ffmpeg with `-f image2pipe -vcodec mjpeg -i -`
  - Walks frames 0..N, awaits buffer.waitForFrame, writes JPEG to stdin
  - Honours backpressure via stdin.once("drain")
  - Wires AbortSignal → ffmpeg SIGTERM
  - Uses GPU encoder (R46 #1) for output codec choice
The producer interface (`StreamingEncodeProducer.produceFrame(n)`) is
the contract the future parallel implementation conforms to.

## What's NOT shipped (TODOs documented inline)

The skeleton's bottom comment block details four follow-up files needed
to fully replace Stage 1's Remotion render path:
  3.a — headless-chrome.ts (Puppeteer pool, mirror browserManager.ts)
  3.b — parallel-coordinator.ts (worker count + range partitioning)
  3.c — composition-to-html.ts (replace @remotion/bundler for our
        AbsoluteFill/Sequence/Video/Audio/Img subset)
  3.d — feature flag wiring in render-pipeline.ts

Each TODO references the hyperframes file it should mirror + LOC
estimate. Total remaining work ~2-3 engineer-weeks. Honest scope:
this commit is the foundation, not the finished house.

## Why ship the primitive separately

  - Zero coupling to Chromium / Remotion / ffmpeg → trivially testable
  - Future incremental adopters benefit (e.g. parallel speed-ramp
    pre-pass on input clips also wants reorder coordination)
  - Reading the two files together gives a coherent architectural
    narrative even before the parallel coordinator exists

## Tests

  - 10/10 frame-reorder-buffer (waiter ordering, dispose, range checks,
    idempotent advance, parallel-worker simulation)
  - 69/69 render-area tests pass overall

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
nanxingw added a commit that referenced this pull request May 11, 2026
Cinematic transition between two video clips using ffmpeg's xfade filter
combined with a procedurally generated "light leak" overlay sweep. Looks
like vintage film burn / lens flare; very common in viral editorial cuts.

## Why ffmpeg-only and not WebGL shaders

hyperframes' shader-transitions package ships ~6 transitions as GLSL
shaders rendered in their custom WebGL renderer. To port any of them
we'd either:
  (a) Embed the shader as a Remotion canvas component — needs the
      streaming-render path (R46 opt #3) for performance
  (b) Express it as an ffmpeg filter graph — limited to what xfade /
      geq / overlay can do without proper texture sampling

Light-leak is the easy first one because the visual effect (bright
streak sweeping during a cross-fade) decomposes into:
  - xfade transition=fade between the two clips
  - format=rgba overlay of a static gradient PNG
  - overlay's x position animated via if(between(t, ...)) expression
  - acrossfade for audio

No WebGL needed. Pure ffmpeg, runs in our existing pipeline.

## What's shipped

`src/server/render/transitions.ts` (~210 LOC):
  - `applyLightLeakTransition(opts)` — full ffmpeg invocation
  - `ensureLightLeakOverlay(width, height)` — caches overlay PNG
    generated via lavfi `gradient` + pad+format=rgba. No binary asset
    shipped; lazy-built on first use.
  - `buildLightLeakFilterGraph(opts)` — pure-function filter graph
    builder, exposed for unit testing without spawning ffmpeg.

`src/server/render/transitions.test.ts`:
  - 6 tests on the filter-graph builder: xfade offset math,
    enable= gate timing, audio crossfade duration, fps in overlay,
    single-line graph (ffmpeg requirement), output stream labels.

## Roadmap for additional transitions

Documented inline as TODOs at end of transitions.ts. Three more worth
porting:
  5.a — Glitch cut (ffmpeg geq feasible, ~1 day)
  5.b — Domain warp (ffmpeg geq feasible, ~2 days)
  5.c — Gravitational lens (needs Remotion canvas, blocked on R46 #3)

## Tests

  - 6/6 transitions tests pass
  - 75/75 tests across all R46 changes (gpu-encoder, frame-reorder-
    buffer, aggregate-progress, transitions, render-pipeline,
    render-queue, render-ws, store) — full pipeline still healthy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
nanxingw added a commit that referenced this pull request May 11, 2026
…act (R46 #5 wire-in)

Closes the "POC primitive shipped, no API path" gap from R46 #5
commit (12897ac). transitions.ts had the working filter graph + caching;
now agent can invoke it via REST and the system prompt documents when
to reach for it.

## Endpoint

POST /api/transitions/light-leak
Body:
  workId, clipARelative, clipBRelative, outputFilename,
  clipADuration: number (seconds),
  transitionDuration: number (seconds, default 0.8)

Response:
  { ok: true, outputPath, previewUrl }
  or { errorCode, error } on failure (4-layer path-traversal defence)

ffprobe-based dimension detection: probes clipA for width/height/fps so
the overlay PNG cache picks the right size without the caller having
to specify. Falls back to 1080×1920×30 if ffprobe fails (then ffmpeg
will emit its own clear error).

## ffmpeg 8.x compat fix

ensureLightLeakOverlay() previously used the `gradient` source filter,
which doesn't exist in ffmpeg 8.x (was added later, then removed in
favour of `gradients` plural — confusing). Replaced with `color@0`
source + `geq` per-pixel expression for the streak shape:

  alpha(X,Y) = if X∈[0.35W, 0.65W]:
                 180 * (1 - (2*(X/W - 0.5) / 0.3)^2)
                 (smooth quadratic bump centered at W/2)
               else: 0

End-to-end verified: 12kB streak overlay PNG generated, 5.3MB mp4
transition output produced from two real clips.

## System prompt (ws-bridge.ts)

Assembly module description gains a "**过场转场**" line teaching the
agent:
  - When to use light-leak (editorial / 文艺 / 蒙太奇)
  - When NOT to use it (快剪情节驱动 / 口播单镜 → use direct cut or fade)
  - Default 0.8s transitionDuration (viral pacing); 1.2-1.5s for
    editorial slow-feel
  - Hard "不要手写 ffmpeg drawtext / xfade filter" — must go through
    the endpoint (preserves the cached overlay + path-traversal
    defence + error consistency)

## Tests

  - 269/269 server tests pass (no regression — 48 files including
    transitions, render-pipeline, render-queue, providers, etc.)
  - Direct e2e: curl POST → 200 ok response, mp4 file on disk with
    correct dimensions + duration
  - No new unit test for the endpoint itself; transitions.test.ts
    already covers the filter graph builder, the endpoint is mostly
    plumbing + ffprobe + path-resolve which the existing safe-paths
    tests cover

## Roadmap (still open)

Three more transitions worth porting (R46 #5 file's TODOs):
  5.a glitch cut    (~1 day, ffmpeg geq feasible)
  5.b domain warp   (~2 days, ffmpeg geq feasible)
  5.c grav lens     (blocked on streaming render path R46 #3)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
nanxingw added a commit that referenced this pull request May 11, 2026
Wires @remotion/renderer's renderFrames into streamingEncode +
FrameReorderBuffer primitive shipped earlier in R46 #3. Stage 1 now
opt-routes via AUTOVIRAL_USE_STREAMING_RENDERER=1 env var (or
composition.experimentalFlags.streamingRenderer). On failure falls
back to renderCompositionToMp4 so flag-on users aren't blocked.

streaming-encoder.ts gains inputCodec option (mjpeg | png) so the
bridge can stream Remotion's PNG frames directly without a JPEG
re-encode pass.

#3.c (custom React→HTML serializer) intentionally skipped —
re-implementing ~2k LOC of Remotion compatibility surface duplicates
@remotion/bundler with no perf payoff. Streaming-encode wins come
from parallel Chromium screenshots + ffmpeg pipe, not from owning
the bundler. Keep @remotion/bundler, replace only the renderer half.

Tests: 33/33 pass across remotion-bridge / frame-reorder-buffer /
render-pipeline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
nanxingw added a commit that referenced this pull request Jun 3, 2026
… direct dialog dispatch

Next-phase plan item #3 — "人声TTS功能完整的测试" + EN support + keep both
providers. The feature was dead end-to-end despite all code existing: no
edge-tts binary, no API key, and the dialog routed TTS to a DELETED
modules/assets/scripts/tts_generate.py via a chat message. Rebuilt as a
deterministic, work-integrated, dual-provider path.

Backend (src/tts-providers/, src/server/api.ts):
- New openaiTtsProvider conforming to TtsProvider (writes outputPath, ffprobe
  best-effort, fetch/env injection seam). mapVoiceToOpenAi() translates edge
  voice ids (zh-CN-Xiaoxiao/Aria→nova, Yunjian/Guy→onyx) since OpenAI rejects
  edge ids — the #1 fallback bug, guarded by tests.
- edgeTtsProvider.resolveEdgeTtsBin(): EDGE_TTS_PATH → auto-discovered venv at
  <dataDir>/tts-venv/bin/edge-tts → bare "edge-tts". + isAvailable() probes.
- registry.generateWithFallback(): auto = edge first, OpenAI fallback, aggregated
  error naming both on total failure; edge-tts/openai single modes skip fallback.
- POST /api/works/:id/tts: SAFE_ID-guarded (write-side path-traversal fix),
  synthesizes into assets/audio/, broadcasts asset-added, returns
  {ok,relativeUri,providerId,durationSec,voice}; 500 tts_provider_error on fail.

Frontend (GenerationDialog.tsx, messages.ts):
- TTS Generate now POSTs /api/works/:id/tts DIRECTLY (mirrors video provider
  dispatch) instead of the dead chat-notification path; invalidates ["assets",id]
  + closes on success, keeps open + localized error on failure.
- 4 voices for zh+en parity (added en-US-Guy); 服务/provider select (auto/edge/openai);
  localized progress + error i18n (EN+ZH).

Verified: web 1002/1002, server 598/600 (2 ffmpeg-env, non-regression incl. real
edge-tts smoke). Browser E2E through the live daemon (pure venv auto-discovery,
no env override): generated zh (8.11s) + en (6.60s) narration via the real UI →
both appear as library audio tiles → decodable mp3. OpenAI fallback unit-tested
(no key on this machine to E2E). Test work restored, no artifacts left.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
nanxingw added a commit that referenced this pull request Jun 4, 2026
The HITL gates of PRD-0002. A grill-with-docs session pressure-tested the
PRD's recommended ContentTypeManifest + MediaProvider shapes against the
ACTUAL code and surfaced three facts the PRD missed:

1. CarouselSchema + makeEmptyCarousel are web-only (web/.../editor/types.ts);
   composition's schema is shared. I08 (carousel CLI→bridge→server zod) and
   I10 (carousel migrations) are IMPOSSIBLE as specified until the carousel
   schema reaches the server. → ADR-006 promotes it to src/shared/carousel.ts
   as I06's first step. This is the real keystone of v0.1.1.
2. providers are FOUR mechanisms not three (image / video / tts-registry /
   tts-direct synth), and TTS has a double path. → ADR-007 scopes a 4→1
   consolidation + internal TTS merge.
3. runway/sora/kling are produce-nothing stubs that violate invariant #2
   (direct vendor calls). → ADR-007 drops them; video is OpenRouter-only
   (seedance). Also collapses checkpointTargets→deliverableFile, single
   capability tag over boolean flags, seedFactory as pure factory (no
   create-time seeding behavior change).

CONTEXT.md: add `content type` + `carousel.yaml` glossary terms (carousel
was absent); make `work` content-type-aware (was video-centric); update
invariants #2 (single capability-tagged registry) + #3 (both deliverables
are SSoT) with ADR refs.

Decisions: nanxingw confirmed carousel→shared (Q1) + drop-stubs (Q8).
ADRs to be added to docs/adr/README.md index by I04.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
nanxingw added a commit that referenced this pull request Jun 4, 2026
Give "upgrade without data loss" a backbone (PRD-0002 W7.5 / deep module ③).

- Add optional `schemaVersion` to CompositionSchema + CarouselSchema. Modeled
  as `.optional()` (floor 1 via readSchemaVersion), NOT zod `.default(1)`, so the
  `z.infer` output type stays `number | undefined` and existing Composition /
  Carousel literals (fixtures, synthesisers) don't have to stamp a redundant
  field — zero collateral edits. Fresh works DO write `schemaVersion` explicitly
  via makeEmptyComposition / makeEmptyCarousel (grep-confirmable).
- New src/shared/migrations/ registry skeleton:
  - registry.ts — `migrate(kind, doc)` reads schemaVersion, chains registered
    from→to migrations to the latest, stamping the version forward. Pure; never
    writes disk (callers run migrate then hand the result to the existing
    CompositionSchema.parse + atomic write — invariant #3 unbroken).
  - members.ts — collects the two existing migrations as named members:
    `migrateLegacyTrackIds` (composition read-time normalizer, #57 behaviour
    referenced verbatim, NOT re-implemented) and the repo-root `strip-pipeline`
    batch script (lazy dynamic import — kept outside the `src/`→`dist/` rootDir
    program so the publish build stays green; its CLI path + tests untouched).
  - index.ts — public barrel.

Range discipline (PRD-0002 N6): skeleton + collect only. No boot-time runner.
#57's behaviour is byte-for-byte unchanged — it gets a hook, not a fix. Zero
version-bumping chain migrations ship in v0.1.1; the schemaVersion floor stays 1.

Isolation tests (src/shared/migrations/__tests__): optional-field default floor,
migrate() multi-step chaining + ascending order + duplicate/non-+1 guards, the
legacy-track-ids member is the SAME fn (same in/out), strip-pipeline member runs
verbatim, and a migrated legacy composition round-trips through the atomic
write/read path carrying schemaVersion. Existing migration tests stay green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
nanxingw added a commit that referenced this pull request Jun 5, 2026
…D-0003 §3.2)

New DELETE /api/works/:id/assets/* (SAFE_ID workId + traversal-rejecting path
guard, mirrors the shared-assets contract; unlinks the file + sibling
.peaks.json). Library tiles + the preview modal get a trash control behind a
two-step confirm. Referenced-asset policy = WARN-then-CASCADE: the dialog warns
how many timeline clips use the asset; confirming removes those clips +
provenance entry so the timeline never silently breaks (invariant #3).
findClipsReferencingAsset canonicalises every clip.src / asset flavour
(work-relative, single-prefix served URL, and the double-prefixed asset url)
to one work-relative key so a served-URL clip matches (browser+disk E2E
verified: tile gone, file unlinked). messages.ts also carries I24's session
strings. EN+ZH incl aria-labels.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
nanxingw added a commit that referenced this pull request Jun 5, 2026
…NewWorkCard polish

Timeline (#3): clip BODY now owns cross-track moves (CapCut/剪映/Premiere
style). New pure resolveDragTargetTrack core (zero React/DOM, unit-tested)
decides the same-kind destination lane; commitDrag applies moveClipToTrack
while preserving trackOffset. Re-runs the #88 kind guard inline against
stale targets, and prunes orphaned source-track transitions (#54) so the
next Composition.parse() superRefine can't reject the save round-trip.

Sessions: any conversation is deletable now (incl. the default s_1) as long
as one remains — swapped the "refuse-default" guard for a "refuse-last"
guard in works route, ws-bridge deleteSession (now always removes the chat
log + the s_1 legacy chat.json snapshot), and SessionStrip (× hidden when
<=1 session; active-delete falls back to first-remaining, not always s_1).

NewWorkCard: layout/CSS refresh.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
nanxingw added a commit that referenced this pull request Jun 5, 2026
…); cover error-code rules #3/#4; fix /ask comment

Wave-1 review (S3, 1 MEDIUM + 2 LOW):

- MEDIUM (gate): the "4-vs-3 covered by integration test" claim ran only via a
  separate `cd cli/autoviral && npm run test` with NO prebuild — so it silently did
  NOT run in `npm test` and could run stale gitignored dist against new source.
  Add `pretest: npm run build` to cli/autoviral (always fresh dist) + root
  `test:cli` script and append it to the root `test` chain. The coverage is now
  actually enforced and never stale.
- LOW (untested branches): every prior CLI fixture returned JSON, so client.ts's
  defensive JSON.parse(catch) (rule #3) and the 200+{ok:false} envelope (rule #4)
  were exercised by inspection only. Add 3 fixtures+tests: 400 with a non-JSON
  (HTML) body → status-class fallback exit 4 (no crash); 200 {ok:false, code:4} →
  honours code exit 4; 200 {ok:false} no code → defaults exit 3. All green.
- LOW (comment): bridgeRequest claimed "/ask returns 504 via this path with
  code:124" — false; /ask has its own fetch+exit in commands/ask.ts and never
  reaches bridgeRequest. Reworded to the true behaviour (this path honours any
  explicit numeric code incl. 124; /ask handles its own 124).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
nanxingw added a commit that referenced this pull request Jun 5, 2026
S10 POST /clip (src/server/bridge/routes.ts):
- `--duration` is a RELATIVE clip length, not an absolute `out`. The old
  `out = body.out ?? (body.duration ?? 5)` mis-read `--in 2 --duration 3`
  as out=3 (a 1s clip) instead of out=5 (a 3s clip). Now `out = in +
  duration` when no explicit `--out`; explicit `--out` still wins. Applied
  to the video + audio source-window branches (text/overlay already treat
  `duration` as relative).
- Unknown `--track` kinds are now rejected UP FRONT with 400 + code:4
  instead of relying on the kind dispatch's `else` (which treated overlay
  as a catch-all). The `else` branch is reached only for a real "overlay".

S9 transitions:
- clampHandleDuration now caps at the schema ceiling (5s), not just the
  handle + 0.05 floor, so two very long adjacent clips can't produce a
  dur > 5 that writeCompositionFor's zod parse would reject. Added
  TRANSITION_DURATION_MIN/MAX_SEC mirroring TransitionSchema.
- studio store add/removeTransition now SURFACE the op's CompositionOpError
  as a localized warn toast (same pattern as splitClip) instead of silently
  swallowing it, so the CLI (code:4) and UI error paths no longer diverge.
  New i18n key studio.toast.transitionFailed (EN + ZH).

Overlay consumption (finding #3) confirmed NOT a dead field: RemotionRoot
renders the SAME <Scene> the preview uses → OverlayTrackRenderer emits an
<Img> for the bridge-shaped overlay clip. Added a render-consumption
assertion test to guard against future regression.

Tests: +5 routes (in/out semantics x4, unknown-track-rejects), +1 clamp
ceiling, +4 transition-toast, +2 overlay-consumption. web 1154 / server 909
/ cli 101 green; web+server tsc clean of new errors (2 pre-existing
routes.test/checkpoints Hono .then() typing errors unchanged).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
nanxingw added a commit that referenced this pull request Jul 13, 2026
对抗 review 6 findings 全部指向未提交的 HEAD 前态;working tree 已含完整
S4b 实现,本 commit 将其落盘固化,令交付可复现(对齐 finding #6 交付未提交诉求):

- ws-bridge.ts:无 isWorkBound / homedir·yaml / trends_ 残留(finding #1,backend tsc 退出 0)
- routes/system.ts + infra/config.ts:GET 不回显 research/analytics/interests/douyin*,
  PUT 旧 flat 字段静默忽略返 200,无效旧 cron 不再 400(finding #2/#4)
- 删 trends/analytics/coach routers + scheduler + src/trends + 各 domain + collector
  + context/trends + CLI trendsCommand,拆出 commands/profile.ts(finding #3/#6)
- 依赖移除 node-cron / @types/node-cron / fast-xml-parser
- 预设测试:removed-product-routes.test.ts 矩阵 404 + 保留路由回归(finding #5),
  config(.redaction).test.ts 改锁 S4 新契约,doctor-setup 退休 --heavy/trends scrape

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
nanxingw added a commit that referenced this pull request Jul 15, 2026
reviewer findings 逐条处理(先证红再转绿):

#1 [high] detach→重开原声双份出声:AudioClip 加 detachedFrom 稳定回链;
   新增共享 op attachAudio(re-enable + 原子删除拆出 clip);store
   reattachClipAudio + Inspector 开关重开时走反向 op;sweep gate 收编。
#3 [high] 变速视频 detach 音画失步:detachAudio 对含非 1 speed keyframe 的
   clip 直接拒绝(CompositionOpError code:4),不静默产出失步音轨。
#4 [medium] speed-ramp 缓存键漏 sourceAudio.enabled:静态/变速两个 cache
   name 都折入 enabled(默认 true 保持旧名,disabled 加 -na 后缀),读在
   stat 之前;assets PIPELINE_INTERNAL 正则同步放行 -na 变体。
#2 [high] ducking 丢 in/out:MixTrack 携带 in/out,compositionToMixTracks
   透传,mixAudioTracks 先 atrim=start:end + asetpts;拆出/裁剪 clip 不再从
   源 0s 播。(enabled 视频源声在 duck 阶段被丢=既有 ducking 设计缺口,
   非 S5 引入,另案,见 notes)
#5 [medium] 同构测试:新增 sourceAudioCache 测试驱动真实 applySpeedRampPrePass
   路径(预置双缓存断言 enabled/disabled 落不同文件);renderer 补
   isRendering:true 导出分支(OffthreadVideo muted/volume)。
#6 [medium] CLI 假服务端:新增真实 Hono bridge 路由测试,回读持久化
   composition 断言 AudioClip(type original + detachedFrom)、源声关闭、广播。

红:9 op/cache 测试对旧源码先红;转绿:server 1688 / web 1826 全过,
backend+web tsc exit 0。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
nanxingw added a commit that referenced this pull request Jul 15, 2026
…辑失败 toast + letterbox/mask-image 契约校准

- 修复 (finding #4): setClipMask op 的 rect 边界从 `1 + 1e-9` 放宽收紧为严格
  `x+w<=1 / y+h<=1`,与持久化 MaskRectSchema 完全同步。此前亚 1e-9 的 sliver
  能过 op 却在 CompositionWriteSchema.parse 写盘时抛 ZodError → 泄漏 HTTP 500;
  现在 op 同步拒绝 → 400/code:4。补 op 层 + bridge 路由边界测试(先证红:op
  未抛 / 路由 500,转绿)。

- 修复 (finding #5): store.setClipMask 遇 CompositionOpError 不再静默吞掉,改为
  与相邻 transition 家族一致弹 warn toast(新增 studio.toast.maskFailed i18n
  键 en/zh)。新增 store.mask.test.ts(先证红:无 toast,转绿)。

- 校准 (finding #1): letterbox-2.35 preset 展开为「非 inverted 居中 rect band」
  是正确 letterbox(keep-inside 默认下 inverted 反而保留黑边、遮内容)。原 PRD
  「rect+inverted」措辞有误,已修订 S13 文档;op/bridge/Inspector 三层测试补精确
  展开断言(inverted 缺席 + key 集)杜绝漂移。

- 校准 (finding #2): 渲染用 CSS mask-image(alpha mask)而非 clip-path —— feather
  必须走 alpha 软边(feGaussianBlur),clip-path 无法羽化。修订 S13 文档并对渲染树
  加 DOM 断言(mask-image data-URI 含 SVG / feGaussianBlur / evenodd)。

- 反驳 (finding #3): S13 契约文本即为「mask 渲染组件在 renderMedia 单帧下不抛
  (为 S16 铺路)」,现测试翻 isRendering=true 走导出分支断言不抛,正合契约;真
  headless renderMedia 属 S16/E2E 纬度,暗亮主题 + 预览/导出肉眼一致为显式手验项,
  非 vitest 可自动化。

Web 242 文件/1859 测试、Server 173/1826(+1skip)、CLI 5/291 全绿;typecheck:web +
backend tsc 均 clean。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
nanxingw added a commit that referenced this pull request Jul 15, 2026
review findings 逐条处理(7 修复 + 1 部分反驳 + 1 反驳):

- #1 [high] overlay blendMode 未消费 → OverlayTrackRenderer 给 <Img> 设
  mix-blend-mode(blendModeToCss);补 OverlayTrackRenderer.blend 测试。
- #2 [high] filters/effects 双写冲突 → Inspector 三旋钮改写 effects grade
  entry(find-or-create + updateEffectParams,不再写 clip.filters);
  projectLegacyFilters 在 both-present 时把非中性 filters 折进 LEADING grade
  并清零 filters(无损、幂等);更新迁移测试锁新行为。
- #3 [high] effects 非有序栈 → 新增 effectStackSteps + wrapWithEffectStack,
  按数组顺序生成逐层嵌套包装(grade/blur=filter 包裹,vignette/grain=overlay
  包裹),跨类型 reorder 改变真实嵌套;补 DOM 行为测试。
- #4 [high] adjustment 只消费 backdrop-filter → 同时消费 vignette/grain 覆盖层,
  空 filter 但有 overlay 时不再 return null;补消费测试。
- #5 [high] POST /clip adjustment 写成 OverlayClip → 新增专用 AdjustmentClip
  构造分支(trackOffset+duration+空 effects,无 src/position);补路由测试。
- #6 [high] adjustment z 序按数组非 displayOrder → Scene 渲染前按 displayOrder
  排序轨道;补上下移作用范围测试。
- #7 [medium] effects 列表无排序交互 → 增加上/下移按钮,统一调用
  reorderClipEffect;补交互测试 + i18n。
- #8 [medium] LUT 死字段 → 反驳:LUT 从无消费者(旧 toCssFilter 也只读
  b/c/s),非 S14 回归;S14 迁移反而无损保留 lut 数据。真 LUT 渲染是独立新
  功能,超出 S14 平价范围。
- #9 [medium] remove/reorder/toggle/update/blend 静默吞错 → 统一 warnEffectFailed
  warn toast(对齐 addClipEffect);补 store.effects toast 测试。

验证:test:web 246 files/1893 绿;test:server 175 files/1853 绿;
build:backend tsc 绿。web tsc 15 error 为 S14 adjustment-kind 既存 UI 债
(describeElement/Timeline/DiveCanvas,未改动文件),改动前后计数不变。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
nanxingw added a commit that referenced this pull request Jul 15, 2026
一致性 gate 在 frame 30(0.5s 定格点之后)抓到真实预览≠导出裂缝:headless
Remotion still(A 路 == `autoviral snapshot`)在定格点之后渲成纯黑
(preview-luma=0),而导出(B 路,ffmpeg trim+tpad 烘焙的定格 MP4)正确 hold
住定格帧(export-luma=125)。根因不是 off-by-N,而是定格用 1 帧 startFrom/endAt
trim 窗口实现——<OffthreadVideo> 在 <Sequence> 里过了 endAt 就解不出帧→黑。

修复:定格时把 body 包进 Remotion `<Freeze frame={0}>`,把后代的 local frame
钉在 0,让 <Video>/<OffthreadVideo> 在每一个合成帧都渲染它那一帧 trim 帧
(源帧 freezeStart)——整条 clip 都 hold,和导出的 trim+tpad 烘焙对齐。导出侧
prepass 已 STRIP freezeAtSec,故此改动只影响预览/still/snapshot,不动导出。

gate 证据:改前 frame30 meanΔ=123.55/changed=100%(黑),改后 meanΔ=0.33/
changed=0%(时变 clock 源,见 S16 review 测试修复 commit)。web 单测更新为断言
定格帧被 <Freeze> hold 包裹(data-test=clip-freeze),trim 窗口仍选帧 45/46。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
nanxingw added a commit that referenced this pull request Jul 15, 2026
逐条处置独立 reviewer 的 6 条 finding(#1 CI 单独 commit):

#2 speed fixture 无牙:flat baseSrc 下导出回退 1× 也全绿。改用时变 clock 源
   (每帧一个 distinct flat 色,见下),并加 Layer-2 ffmpeg 自检"注入旧 1× 回退
   → 判红"(源 t=15 ≠ 2× 预览 t=30)。

#3 去除"改比较点保绿":freeze fixture 从 frame 0(定格点,掩盖裂缝)改回
   honest frame 30,配时变 clock 源。配合 <Freeze> 产品修复后真正转绿
   (meanΔ=0.33/changed=0%),不再掩盖发现。

#4 reverse fixture 纯色倒放不变、prepass 不跑也绿:reverse 是 export-only
   (预览正播 + 仅导出生效徽标),A==B 本就不成立。新增 Layer-2 自检用真
   timeWarpVideoFilterChain 在 clock 源上验证 reversed[k]==source[last-k]
   且 !=source[k](证明真倒放取帧)。FULL ⑦ 改为诚实契约:排除徽标区后正片
   一致(meanΔ=0.00),且徽标区确实发散(hard=4.33%,证明徽标真是 preview-only)。

#5 补 adjustment 轨 fixture ⑧(ordered grade→vignette 调整窗)+ 把 effects ⑤
   从单 grade 升为两效果有序栈 grade(亮度+)→vignette。(顺序反转输出不同已在
   VideoTrackRenderer.effects.test.tsx DOM 单测覆盖。)

#6 双阈值盲区:5% 像素 × Δ60 时 meanΔ≈3≤4、changed≈5%≤6% 两闸皆过却是可见
   局部损坏。加第三闸 hardPct(|Δ|>48 的像素占比 ≤1%)关闭盲区;配 Layer-1
   自检(5%×Δ60→红 / sub-1% sliver→仍绿)。新增 ignoreRect 精确排除 reverse
   徽标(非放宽容差——另断言徽标区确实发散)。

clock 源:flat 每帧 distinct 色(geq r/g/b 仿射于 N),既有牙(错帧=大 meanΔ)
又无 testsrc2 硬边的 chroma-subsampling 噪声(否则误触 changedPct 次闸)。

验证:RUN_CONSISTENCY_GATE=1 全 19 pass(8 FULL fixtures meanΔ 0.0–2.33/
changed 0%/hard 0%);非门控 test:server 1953 pass|10 skip 无回归。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants