Skip to content

fix: 修复重采样导出时长误报、overwrite 幽灵轨道、IndexedDB 连接泄漏、下载截断、normalize-media 挂死,及 Windows 测试兼容性 - #137

Merged
0xsline merged 4 commits into
0xsline:mainfrom
qwert702:fix/bug-scan-batch-1
Sep 8, 2026
Merged

Conversation

@qwert702

@qwert702 qwert702 commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

概述

本 PR 来自一次全量代码扫描(oxlint + tsc -b + 549 个 verify 套件 + 对编辑器命令层、持久化层、导出管线和服务端插件的人工审查),包含两个提交:运行时 bug 修复、测试套件 Windows 兼容性修复。

运行时修复(37dac88)

  1. 重采样导出必被自己的时长 QA 误报server/plugins/export-plan.ts
    durationSeconds 原来按时间线 fps 计算(frames / fps),但重采样(retime)是保持帧数、只改容器 fps。把 60 秒的 30fps 时间线导出为 24fps 时,记录的是 60 秒,而产出文件实际是 75 秒——远超 auto-QA 容差(quality.tsmax(0.25, 2/fps)),每次改帧率导出都会报一条确定的 duration_mismatch,导出历史里的时长也是错的。修复:frames / (retimeFps ?? fps)

  2. overwrite 放置丢失刚创建的轨道(幽灵轨道)src/editor/storeCommandBuilder.ts
    placeItem 的 overwrite 分支把 track.create 暂存进同一个 batch,但 planOverwritesetFullState 预览状态里不含这些轨道——全量替换会悄悄撤销建轨,新片段引用一个不存在的轨道(无 kind、lane 渲染错乱)。修复:规划 overwrite 前先把暂存轨道折叠进状态。(当前没有调用方传 overwrite: true,所以是潜在缺陷,但任何新调用方一用即损坏状态。)

  3. IndexedDB 连接泄漏src/persist/sharedKv.tssrc/persist/mediaBlobStore.tssrc/plugins/store.ts
    每次读写都新开一条连接且从不关闭;长会话(自动保存 + 媒体 + 聊天 + 版本)连接数持续累积,直到浏览器拒绝新连接——届时持久化静默退化为内存存储,刷新即丢。修复:每个文档复用单条连接,并在 versionchange / close 时重置。

  4. 同步 URL.revokeObjectURL 会截断下载src/agent/tools/generate-tool-handlers.ts(FCPXML 导出)、src/generate/media-export.ts
    anchor.click() 后立刻 revoke 可能在浏览器开始读取 blob 之前掐断下载。两处都改为延迟 1 秒,与 exportFiles.ts 中已注释说明的既有模式一致。

  5. /api/normalize-media 对超大/非法 JSON body 挂死server/plugins/normalize-media.ts
    readJson 在 body 超 8KiB 或 JSON 非法时 reject(并销毁 socket),但没有任何 catch——产生未处理的 promise rejection,且不返回任何响应。中间件现在会返回 400。

测试套件的 Windows 兼容性(58fb199)

CI 门禁只在 ubuntu 上跑,以下套件在 Windows 开发机上必失败:

  • keystore-profile / hf-proxy:Windows 上 os.homedir() 解析的是 USERPROFILE,只重定向 HOME 时 profile 根仍指向开发者真实的 ~/.openchatcut(并报 ENOENT)。
  • data-dir / mcp-token / embedded-port / media-references:Windows 不报 POSIX owner-only 权限位(0666 而非 0600),权限断言按 keystore-profile 既有模式加 process.platform 守卫。
  • mcp-token:chmod 0o500 在 Windows 上无法制造不可写目录,只读 HOME 模拟在 Windows 跳过。
  • directory-watch-import:清理候选路径混用 POSIX 钉住的目录与 resolve 后的上传目录,Windows 上分隔符和盘符前缀不同;断言改为按位置比较。
  • run-affected-verifies:选中的套件改用 posix 分隔符报告;平台 join() 产生的反斜杠路径会 miss 掉 package.json 的 canonical 命令表(run-check.mjs 套件退回错误 runner),也匹配不上套件自身的 posix 期望。

验证

  • npx tsc -b 干净,oxlint 干净(1758 文件 0 警告 0 错误)
  • verify:export-adaptiveverify:editorverify:media-persistverify:server-extraverify:components 全部通过
  • run-affected-verifies.verify.mjs 在 Windows 上通过(修复前失败)
  • 所有改动的 verify 套件在 Windows 上通过(keystore-profilehf-proxydata-dirmcp-tokenembedded-portmedia-referencesdirectory-watch-import 修复前均失败)
  • export-media-plan / export-params / quality verify 在时长改动后通过

已知未包含的发现

同一次扫描还发现了几个严重度较低的问题,为保持本 PR 可审查暂未包含,乐意后续跟进:重叠片段的 ripple 删除可能整体静默失败(src/editor/linkGroups.ts)、kvSet 乱序完成导致本地缓存滞后(src/persist/sharedKv.ts)、无硬链接文件系统上 ifAbsent 上传误报(server/plugins/upload-route-storage.ts)、blob 素材导出产生的临时上传文件从不清理(src/export/useExportWorkflow.ts)。

… download revocation, normalize-media errors

- export-plan: expected durationSeconds must follow the output fps
  (retimeFps), not the timeline fps. A retime pass keeps the frame count
  and retimes the container, so every fps-resampled export reported a
  duration far off the produced file and failed its own duration QA
  (duration_mismatch) plus a wrong export-history duration.
- storeCommandBuilder: fold staged track.create actions into the timeline
  state BEFORE planning an overwrite placement. The overwrite commits via
  setFullState, which used to replace the timeline without the just-staged
  tracks and left the new item referencing a ghost track (no kind, wrong
  lane rendering).
- sharedKv / mediaBlobStore / plugins store: reuse a single IndexedDB
  connection per document. Opening a fresh connection per read/write and
  never closing leaked connections until the browser refused new ones,
  at which point persistence silently degraded to the in-memory store.
- generate-tool-handlers (FCPXML export) / media-export: delay
  URL.revokeObjectURL after anchor.click(). A synchronous revoke can cut
  the download short before the browser starts reading the blob - the
  same failure exportFiles.ts already documents and avoids.
- normalize-media: wrap the request handler so oversized/invalid JSON
  bodies get a 400 response instead of an unhandled rejection and a
  hanging socket.
The CI gates run on ubuntu, so a handful of verify suites carried POSIX
assumptions that fail on Windows developers' machines:

- keystore-profile / hf-proxy: os.homedir() resolves USERPROFILE on
  Windows, so redirecting HOME alone did not move the profile root and
  the suites wrote into the developer's real ~/.openchatcut.
- data-dir / mcp-token / embedded-port / media-references: Windows
  reports no POSIX owner-only mode bits (0666, not 0600); guard the
  permission-bit assertions with process.platform, matching the existing
  pattern in keystore-profile.
- mcp-token: chmod 0o500 cannot make a directory unwritable on Windows;
  skip the read-only-HOME simulation there.
- directory-watch-import: cleanup candidates mix a POSIX-pinned
  destination with a resolved upload directory, which on Windows differs
  in separators and drive prefix; compare paths positionally.
- run-affected-verifies: report selected suites with posix separators;
  platform join() produced backslash paths that missed package.json's
  canonical command map (falling back to the wrong runner) and the
  suites' own posix expectations.
@qwert702 qwert702 changed the title fix: export QA duration, overwrite ghost tracks, IDB connection leak, downloads, Windows test portability fix: 修复重采样导出时长误报、overwrite 幽灵轨道、IndexedDB 连接泄漏、下载截断、normalize-media 挂死,及 Windows 测试兼容性 Sep 5, 2026
Merge the current main refactors while preserving the contributor commits.
Reset failed IndexedDB connection promises and release cached connections on
version change or forced close. Use the project reducer to stage tracks and
assets before overwrite planning so placement preserves ordering and one-step
undo. Keep export duration based on timeline fps: FFmpeg fps retiming drops or
duplicates frames without changing playback duration. Return explicit 400/413
responses for malformed or oversized normalization requests without destroying
the response socket. Retain deferred download revocation and portable verify
paths from the original PR.

Shared-path regression evidence:
- TypeScript build with --force and oxlint pass.
- Connection recovery, normal/overwrite placement, export parameters,
  normalization HTTP, shared KV fallback/migration, media persistence,
  export geometry, and affected-runner verifications pass.
- Native Chrome tests cover all three IndexedDB stores: actual VersionError,
  recovery without reload, and connection release for native version upgrade.
- Actual FFmpeg 30-to-24 fps retiming yields the planned 4-second duration.
- Real DeepSeek v4-flash runAgent calls submit_export for existing audio and
  downloads a valid 1132-byte FCPXML document with a successful final reply.
- Real /export rendering and submitMediaExport download produce a 192078-byte,
  1-second WAV. The Chrome response and downloaded file SHA-256 are identical.
  Initial renderer startup failed during local resource pressure; two warm
  attempts succeeded. No paid media generation or user project was involved.

Full test and production build gates remain with the aggregate integration.
@0xsline
0xsline merged commit 769a9fc into 0xsline:main Sep 8, 2026
4 checks passed
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