fix: 修复重采样导出时长误报、overwrite 幽灵轨道、IndexedDB 连接泄漏、下载截断、normalize-media 挂死,及 Windows 测试兼容性 - #137
Merged
Merged
Conversation
… 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.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概述
本 PR 来自一次全量代码扫描(oxlint +
tsc -b+ 549 个 verify 套件 + 对编辑器命令层、持久化层、导出管线和服务端插件的人工审查),包含两个提交:运行时 bug 修复、测试套件 Windows 兼容性修复。运行时修复(
37dac88)重采样导出必被自己的时长 QA 误报 —
server/plugins/export-plan.tsdurationSeconds原来按时间线 fps 计算(frames / fps),但重采样(retime)是保持帧数、只改容器 fps。把 60 秒的 30fps 时间线导出为 24fps 时,记录的是 60 秒,而产出文件实际是 75 秒——远超 auto-QA 容差(quality.ts为max(0.25, 2/fps)),每次改帧率导出都会报一条确定的duration_mismatch,导出历史里的时长也是错的。修复:frames / (retimeFps ?? fps)。overwrite 放置丢失刚创建的轨道(幽灵轨道) —
src/editor/storeCommandBuilder.tsplaceItem的 overwrite 分支把track.create暂存进同一个 batch,但planOverwrite的setFullState预览状态里不含这些轨道——全量替换会悄悄撤销建轨,新片段引用一个不存在的轨道(无 kind、lane 渲染错乱)。修复:规划 overwrite 前先把暂存轨道折叠进状态。(当前没有调用方传overwrite: true,所以是潜在缺陷,但任何新调用方一用即损坏状态。)IndexedDB 连接泄漏 —
src/persist/sharedKv.ts、src/persist/mediaBlobStore.ts、src/plugins/store.ts每次读写都新开一条连接且从不关闭;长会话(自动保存 + 媒体 + 聊天 + 版本)连接数持续累积,直到浏览器拒绝新连接——届时持久化静默退化为内存存储,刷新即丢。修复:每个文档复用单条连接,并在
versionchange/close时重置。同步
URL.revokeObjectURL会截断下载 —src/agent/tools/generate-tool-handlers.ts(FCPXML 导出)、src/generate/media-export.tsanchor.click()后立刻 revoke 可能在浏览器开始读取 blob 之前掐断下载。两处都改为延迟 1 秒,与exportFiles.ts中已注释说明的既有模式一致。/api/normalize-media对超大/非法 JSON body 挂死 —server/plugins/normalize-media.tsreadJson在 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-adaptive、verify:editor、verify:media-persist、verify:server-extra、verify:components全部通过run-affected-verifies.verify.mjs在 Windows 上通过(修复前失败)keystore-profile、hf-proxy、data-dir、mcp-token、embedded-port、media-references、directory-watch-import修复前均失败)export-media-plan/export-params/qualityverify 在时长改动后通过已知未包含的发现
同一次扫描还发现了几个严重度较低的问题,为保持本 PR 可审查暂未包含,乐意后续跟进:重叠片段的 ripple 删除可能整体静默失败(
src/editor/linkGroups.ts)、kvSet乱序完成导致本地缓存滞后(src/persist/sharedKv.ts)、无硬链接文件系统上ifAbsent上传误报(server/plugins/upload-route-storage.ts)、blob 素材导出产生的临时上传文件从不清理(src/export/useExportWorkflow.ts)。