From ff6177e8489618b3f19b7e1fde78181a1248e531 Mon Sep 17 00:00:00 2001 From: TRIP Date: Fri, 21 Aug 2026 10:18:29 +0800 Subject: [PATCH 01/80] fix(ui): clamp window geometry for small screens --- src-tauri/tauri.conf.json | 4 +- web/src/components/home/HomeView.tsx | 30 ++++++++++++-- .../components/home/HomeView.visual.test.ts | 11 ++++++ web/src/components/settings/SettingsView.tsx | 18 +++++---- web/src/components/shell/EditorSplit.tsx | 39 +++++++++++-------- web/src/components/shell/SplitPane.tsx | 12 +++++- web/src/store/settingsStore.test.ts | 39 +++++++++++++++++++ web/src/store/settingsStore.ts | 31 ++++++++++----- web/src/styles/global.css | 4 ++ web/src/styles/tokens.css | 6 +-- 10 files changed, 151 insertions(+), 43 deletions(-) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 5d8c3b3a..de225031 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -20,8 +20,8 @@ { "label": "main", "title": "OpenTake", - "width": 1600, - "height": 1000, + "width": 1066, + "height": 666, "minWidth": 760, "minHeight": 480, "titleBarStyle": "Overlay", diff --git a/web/src/components/home/HomeView.tsx b/web/src/components/home/HomeView.tsx index d09d1736..8dc5b118 100644 --- a/web/src/components/home/HomeView.tsx +++ b/web/src/components/home/HomeView.tsx @@ -60,16 +60,22 @@ const homeShellStyle: CSSProperties = { display: "flex", height: "100%", width: "100%", + minWidth: 0, + minHeight: 0, + overflow: "hidden", background: "radial-gradient(1200px 760px at 88% -12%, rgba(255,255,255,0.045), transparent 54%), linear-gradient(180deg, rgba(255,255,255,0.015), transparent 30%), var(--home-bg)", color: "var(--home-foreground)", }; const homeSidebarStyle: CSSProperties = { - width: 204, + width: "clamp(168px, 18vw, 204px)", flex: "0 0 auto", display: "flex", flexDirection: "column", + minWidth: 0, + minHeight: 0, + overflowY: "auto", padding: "var(--titlebar-safe-top) var(--space-md) var(--space-xl)", background: "transparent", }; @@ -77,6 +83,7 @@ const homeSidebarStyle: CSSProperties = { const homeMainStyle: CSSProperties = { flex: 1, minWidth: 0, + minHeight: 0, padding: "var(--home-stage-inset)", }; @@ -85,6 +92,7 @@ const homeWorkspaceStyle: CSSProperties = { display: "flex", flexDirection: "column", minWidth: 0, + minHeight: 0, overflow: "hidden", background: "#111", border: "1px solid var(--home-border)", @@ -334,7 +342,7 @@ function SampleProjectsStrip({ > {t("home.samples")} -
+
{SAMPLE_PROJECTS.map((sample) => ( - {item && ( - - )} + {tabs.map((tab) => { + const active = effectivePreviewActiveTabId === tab.id; + return ( +
+ + {tab.mediaId && ( + + )} +
+ ); + })}
); } diff --git a/web/src/hooks/useKeyboardShortcuts.ts b/web/src/hooks/useKeyboardShortcuts.ts index 4bcec54f..de837780 100644 --- a/web/src/hooks/useKeyboardShortcuts.ts +++ b/web/src/hooks/useKeyboardShortcuts.ts @@ -497,9 +497,9 @@ export function useKeyboardShortcuts() { ui.focusPanel("media"); if (next.dataset.mediaFolderId) { ui.selectMediaAssets(new Set()); + ui.setPreviewMedia(null); useEditorUiStore.setState({ selectedFolderIds: new Set([next.dataset.mediaFolderId]), - previewMediaId: null, }); } else if (next.dataset.mediaAssetId) { ui.selectMediaAssets(new Set([next.dataset.mediaAssetId])); diff --git a/web/src/store/mediaDeleteActions.ts b/web/src/store/mediaDeleteActions.ts index 6f7988e8..56dac8fa 100644 --- a/web/src/store/mediaDeleteActions.ts +++ b/web/src/store/mediaDeleteActions.ts @@ -8,7 +8,7 @@ import { type MediaProjectIdentity, } from "./mediaStore"; import { useProjectStore } from "./projectStore"; -import { useEditorUiStore } from "./uiStore"; +import { previewMediaTabId, useEditorUiStore } from "./uiStore"; type MediaDeleteKind = "folder" | "media"; @@ -69,11 +69,15 @@ async function runDeleteTransaction( selectedMediaAssetIds: new Set( [...latest.selectedMediaAssetIds].filter((id) => !deleted.has(id)), ), - previewMediaId: - latest.previewMediaId && deleted.has(latest.previewMediaId) - ? null - : latest.previewMediaId, })); + const ui = useEditorUiStore.getState(); + if (ui.previewTabIds.length === 0 && ui.previewMediaId && deleted.has(ui.previewMediaId)) { + ui.setPreviewMedia(null); + } else { + for (const mediaId of ui.previewTabIds.filter((id) => deleted.has(id))) { + ui.closePreviewTab(previewMediaTabId(mediaId)); + } + } } else { useEditorUiStore.setState((latest) => ({ selectedFolderIds: new Set( diff --git a/web/src/store/uiStore.test.ts b/web/src/store/uiStore.test.ts index 80f9abda..96ea9c42 100644 --- a/web/src/store/uiStore.test.ts +++ b/web/src/store/uiStore.test.ts @@ -59,7 +59,13 @@ describe("timeline playback state", () => { activeFrame: 0, isPlaying: false, isScrubbing: false, + previewTabIds: [], + previewTabHistory: [], + previewActiveTabId: "timeline", previewMediaId: null, + selectedClipIds: new Set(), + selectedMediaAssetIds: new Set(), + selectedFolderIds: new Set(), }); }); @@ -128,4 +134,124 @@ describe("timeline playback state", () => { // Pausing must not clear it — the paused frame came from the legacy
+**リリース状況(2026-09-06):** Beta 1–5 は公開済みです。作業ツリーは `release/v1.0.0-beta.6`、マニフェストは `1.0.0-beta.6` になりました。Beta 6 は**未公開の候補版**です。[実行計画](docs/plans/active/2026-09-06-public-beta.md)、[候補版ノート](docs/releases/1.0.0-beta.6.md)、[文書とソースの確認記録](docs/documentation-sync-2026-09-06.md)を参照してください。 + +候補版ソースには、複数のメディアプレビュータブ、フォルダー/フラット/グループ表示、temporal compositor、透明 Motion 出力、ProRes 4444 書き出しが含まれます。Text パネルはテキストクリップを追加し、Effect パネルは選択した単一の映像クリップに既存の効果を保持してプリセットを追加します。Sticker はプロジェクトの画像/Lottie 素材、ローカル読み込み、選択/プレビュー、ドラッグとタイムラインへの配置に対応しました。[専用テスト記録](docs/audit/2026-09-06/sticker-panel.md)があり、新しいパッケージのネイティブ GUI 検証は未完了です。ソース上の実装とネイティブ GUI・provider・配布パッケージの検証は別々に記録します。 + +意味検索のモデルインストールは実装済みです。固定 revision の約 1.5 GB のアセットはチェックサム検証を通過し、macOS の実際の Rust 経路でオフラインインストール、画像・テキスト embedding、ランキングを検証しました。利用前にモデルのインストールが必要です。Windows ort-tract の実グラフ実行と新しいパッケージの UI は未検証です。[モデル監査記録](docs/audit/2026-09-06/semantic-search-model.md)を参照してください。 + - [プロジェクトについて](#-プロジェクトについて) - [なぜOpenTakeか](#-なぜopentakeか) - [競合との違い](#-競合との違い) @@ -29,7 +35,7 @@ ## 📖 プロジェクトについて -**OpenTake** は、**Rust + Tauri 2** で構築された**クロスプラットフォームの動画制作エンジン**です。macOS / Windows / Linux の 3 プラットフォームで動作し、プロの映像編集ワークフローに AI Agent を深く統合することを目的としています。 +**OpenTake** は、**Rust + Tauri 2** で構築された**クロスプラットフォームの動画制作エンジン**です。macOS / Windows / Linux の 3 プラットフォームを対象とし、プロの映像編集ワークフローに AI Agent を深く統合することを目的としています。 > 🌟 **革新的な点**: Agent に長大なスキルドキュメントを読ませるのではなく、OpenTake は**編集ガイダンス(Context Signal)を Agent に能動的に送信**します——各トラックの役割、各クリップの適切な編集方法、各段階で適用すべきルールを、ソフトウェアが Agent に直接伝えます。 @@ -44,9 +50,9 @@ OpenTake は CapCut / DaVinci Resolve / Final Cut Pro の代替品ではあり | 課題 | 従来の手法 | OpenTakeの手法 | |:--|:--|:--| | Agentが素材の編集方法を知らない | Agentが自らスキルドキュメントを読む | ソフトウェアがContext Signalを発信 — 「このトラックはA-roll、トーキングヘッドのリズムでカット」 | -| クロスプラットフォームに3つのコードベースが必要 | macOS: Swift/AVFoundation、Windows: C++/DirectShow | Rustの単一コードベース、FFmpeg + wgpu、全プラットフォームで同一体験 | +| クロスプラットフォームに3つのコードベースが必要 | macOS: Swift/AVFoundation、Windows: C++/DirectShow | Rustの単一コードベース、FFmpeg + wgpu、各プラットフォームを個別検証 | | 自分のAIキーを使いたい | ベンダーのクラウドサービスにロックイン | BYOK — fal.ai / Replicate / OpenAI に直接接続、バックエンド不要、運用コストゼロ | -| Agentはチャットできるが操作できない | CLI Agentがテキスト出力を読むだけ | MCP Server 31ツール — Agentが直接 add_clips / split_clip / set_keyframes を実行 | +| Agentはチャットできるが操作できない | CLI Agentがテキスト出力を読むだけ | 能力に応じた MCP Server — Agentが直接 add_clips / split_clip / set_keyframes を実行 | | 動画タイプごとに毎回プロンプトを書き直す | 「あなたは製品レビューを編集しています…」を毎回繰り返す | ワークフロープラグインシステム: レビュー/チュートリアル/ゲーム/ウェディング、各タイプの手法を事前パッケージ化 | | 新しいツールの学習コストが高い | 複雑なUI、長いオンボーディング | Agentが代わりに操作 — 「このインタビューを3分のハイライトに編集して」と言うだけ | @@ -54,16 +60,7 @@ OpenTake は CapCut / DaVinci Resolve / Final Cut Pro の代替品ではあり ## ⚡ 競合との違い -| 項目 | CapCut | DaVinci Resolve | Final Cut Pro | **OpenTake** | -|:--|:--|:--|:--|:--| -| **Agentネイティブ統合** | ❌ | ❌ | ❌ | ✅ MCP 31ツール + Context Signal | -| **クロスプラットフォーム** | ✅ macOS / Win | ✅ macOS / Win / Linux | ❌ macOSのみ | ✅ macOS / Win / Linux | -| **BYOK AI生成** | 有料テンプレート | ❌ | ❌ | ✅ fal.ai / Replicate / OpenAI に直接接続 | -| **ローカル文字起こし** | ❌ クラウドのみ | ❌ プラグイン必要 | ❌ プラグイン必要 | ✅ whisper-rs デバイス上推論 | -| **ローカル意味検索** | ❌ | ❌ | ❌ | ✅ SigLIP2 + Ort デバイス上 | -| **ワークフロープラグイン** | ❌ 固定テンプレート | ❌ | ❌ | ✅ JSON+MD コミュニティプラグイン | -| **オープンソース** | ❌ | ❌ | ❌ | ✅ GPL-3.0 | -| **Agent操作可能なキーフレーム** | ❌ | ❌ | ❌ | ✅ 全6 kfトラックをMCP経由で制御 | +OpenTake は Rust が保持するタイムライン、コマンドによる undo、認証 MCP、Context Signal、ローカル/BYOK メディア処理を組み合わせます。[能力台帳](docs/capabilities/CAPABILITY-LEDGER.md)は実装と検証を区別し、他の編集ソフトとの全機能同等性を保証するものではありません。 --- @@ -84,7 +81,7 @@ OpenTake は CapCut / DaVinci Resolve / Final Cut Pro の代替品ではあり ### 🔌 Agent ツールサーフェス -OpenTake は45個の互換 Agent ツールを提供し、メディア・生成・provider の実行可能性に +OpenTake は互換 Agent ツールを提供し、メディア・生成・provider の実行可能性に 応じて動的に公開します。利用できない機能は fail closed になります。 | グループ | 主要ツール | @@ -95,9 +92,7 @@ OpenTake は45個の互換 Agent ツールを提供し、メディア・生成 | ライブラリ | `create_folder`, `move_to_folder`, `rename_media` | | リソース | `models/video`, `models/image` | -公式 Codex / ChatGPT は、ターンごとにランダムな loopback ポート、256-bit Bearer、 -現在のプロジェクト ID を持つ一時 MCP を使用します。Beta 2 では旧来の未認証固定 -`127.0.0.1:19789` エンドポイントを無効化しています。 +公式 Codex / ChatGPT は現在のプロジェクトに紐づく認証済み loopback エンドポイントをターンごとに使用します。Beta 5 では外部 MCP クライアントの明示的なペアリング、認証情報の失効、再起動後の設定保持も追加しました。旧未認証エンドポイントは無効のままです。[MCP 実装](docs/modules/opentake-agent/mcp-server.md)を参照してください。 ### 🎬 クロスプラットフォームメディアエンジン @@ -107,7 +102,7 @@ OpenTake は45個の互換 Agent ツールを提供し、メディア・生成 | コンポジター | wgpu カスタムコンポジター | | 音声再生 | cpal | | 文字起こし | whisper-rs | -| 意味検索 | candle / ort + SigLIP2 | +| 意味検索 | SigLIP2(固定 revision のモデル導入と macOS の実 Rust 推論を検証済み) | ### 🌐 BYOK AI生成 @@ -123,12 +118,12 @@ OpenTake は45個の互換 Agent ツールを提供し、メディア・生成 ## 🖥️ 対応プラットフォーム -| プラットフォーム | 状態 | 備考 | -|:--|:--|:--| -| **macOS** (Apple Silicon + Intel) | ✅ 主要開発環境 | ネイティブARM64 + x86_64; Metal経由GPU (wgpu) | -| **Windows** (10/11 x86_64) | ✅ サポート | Vulkan / DX12 (wgpu); Tauri 2完全サポート | -| **Linux** (x86_64) | ✅ サポート | Vulkan; AppImage / deb | -| **バックエンド / ヘッドレス** | ✅ サポート | GUIなしで純Rustコア実行可能 | +| プラットフォーム | 検証範囲 | +|:--|:--| +| macOS | 主な開発環境。日付付きのネイティブ/パッケージ検証はリリース記録を参照。Intel と Apple Silicon は個別に検証します。 | +| Windows | ビルド/インストーラーの対象。候補コミットの CI と実機インストール/UI 検証が必要です。 | +| Linux | ビルド対象。新しい配布物やネイティブ GUI の検証完了は宣言していません。 | +| Headless core | Rust ライブラリとテストは GUI なしで利用可能。メディア/GPU/ブラウザー機能には実行環境が必要です。 | --- @@ -136,6 +131,7 @@ OpenTake は45個の互換 Agent ツールを提供し、メディア・生成 ``` crates/ +├── opentake-process-tree # Cross-platform child-process lifecycle ├── opentake-domain # Timeline / Track / Clip / Keyframe ├── opentake-ops # OverwriteEngine / RippleEngine / SnapEngine ├── opentake-project # プロジェクト永続化 / バンドル / エクスポート @@ -196,16 +192,16 @@ crates/ ```bash # OpenTake の兄弟ディレクトリに上流を clone -cd .. # from OpenTake/ +cd .. # from OpenTake-generation/ git clone https://github.com/palmier-io/palmier-pro.git palmier-pro-upstream -cd OpenTake +cd OpenTake-generation ``` ディレクトリ構成: ``` PRIMARY-CN/ -├── OpenTake/ # このリポジトリ +├── OpenTake-generation/ # このリポジトリ └── palmier-pro-upstream/ # 上流 Swift ソース (GPL-3.0) ``` @@ -238,9 +234,6 @@ cd web && pnpm install && pnpm build cd .. && cargo tauri dev ``` -> **現在の状態**: `1.0.0-beta.2` 候補版。ローカル編集、プレビュー、保存、 -> 書き出し、Agent、Motion Canvas、レビュー可能な AI ワークフローを実装済みです。 -> 検証範囲と制限は [Beta リリースノート](docs/releases/1.0.0-beta.2.md) を参照してください。 --- @@ -251,6 +244,10 @@ cd .. && cargo tauri dev | `0.1.0-dev` | 2026-06 | Phase 0+1: Cargo workspace + Domain models + Edit ops | | `1.0.0-beta.1` | 2026-08-01 | 初回インストール可能 Beta:ローカル編集、Agent、Motion、レビュー可能な AI ワークフロー | | `1.0.0-beta.2` | 2026-08-03 | 公式 Codex ログイン、原子的タイムライン操作、認証 MCP、操作性の強化 | +| `1.0.0-beta.3` | 2026-08-09 | Space 再生、HEVC ネイティブプレビューと配布パイプライン | +| `1.0.0-beta.4` | 2026-08-10 | 時間/トランジションの保存、書き出し整合性とアップデーター | +| `1.0.0-beta.5` | 2026-08-14 | 外部 MCP ペアリング、Agent 会話順序と Motion Studio | +| `1.0.0-beta.6` | 未公開候補 | 透明 Motion、ProRes 4444、複数プレビュー、メディア表示、Text/Effect。検証中 | | *(planned)* `1.0.0` | TBD | Phase 10: フルリリース | 📖 [完全なロードマップ](docs/architecture/ROADMAP.md) diff --git a/README.md b/README.md index 95cbaa43..6df33418 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@

- Platforms + Platforms License Stars Discord EN @@ -26,6 +26,12 @@

+**Release status (2026-09-06):** Beta 1–5 are published. The working tree is now `release/v1.0.0-beta.6`, with manifest version `1.0.0-beta.6`; Beta 6 is an **unpublished candidate**. See the [active plan](docs/plans/active/2026-09-06-public-beta.md), [candidate notes](docs/releases/1.0.0-beta.6.md) and [documentation/source review](docs/documentation-sync-2026-09-06.md). + +The candidate source includes multiple media preview tabs, folder/flat/grouped media views, temporal compositor routing, transparent Motion publishing and ProRes 4444 export. The Text panel adds text clips; the Effect panel applies presets to one selected visual clip while preserving existing effects. Sticker now supports project image/Lottie assets, local import, selection/preview and placement on the timeline. [Sticker validation](docs/audit/2026-09-06/sticker-panel.md) records focused tests; native package GUI acceptance is pending. These are source-level capabilities; native GUI, provider and package validation is recorded separately. + +Semantic search model installation is implemented. Fixed-revision assets (about 1.5 GB) passed checksum validation, and the real macOS Rust path passed offline installation, image/text embedding and ranking checks. A model installation is required before use. Windows ort-tract execution with the real graphs and the new package UI still await validation; see the [semantic model audit](docs/audit/2026-09-06/semantic-search-model.md). + ## Table of Contents - [About](#-about) @@ -45,7 +51,7 @@ ## 📖 About -**OpenTake** is a **cross-platform video production engine** built on **Rust + Tauri 2**, running on macOS / Windows / Linux, designed to deeply integrate AI Agents into professional video editing workflows. +**OpenTake** is a **cross-platform video production engine** built on **Rust + Tauri 2**, targeting macOS / Windows / Linux, designed to deeply integrate AI Agents into professional video editing workflows. > 🌟 **Core Innovation**: Instead of making the Agent parse lengthy skill documents, OpenTake **actively pushes editing guidance (Context Signal)** to the Agent — telling it exactly what each track does, how each clip should be cut, and what rules apply at every stage. @@ -60,9 +66,9 @@ OpenTake is not a replacement for CapCut / DaVinci Resolve / Final Cut Pro — i | Pain Point | Traditional Approach | OpenTake Approach | |:--|:--|:--| | Agent doesn't know how to edit | Agent reads skill docs on its own | Software pushes Context Signal — "this track is A-roll, cut with talking-head rhythm" | -| Cross-platform needs 3 codebases | macOS: Swift/AVFoundation, Windows: C++/DirectShow | Single Rust codebase, FFmpeg + wgpu, identical experience on all 3 platforms | +| Cross-platform needs 3 codebases | macOS: Swift/AVFoundation, Windows: C++/DirectShow | Single Rust codebase, FFmpeg + wgpu, platform-specific validation | | I want to use AI directly | Locked into vendor cloud services | Official Codex / ChatGPT sign-in for Agent, plus BYOK for fal.ai / Replicate / OpenAI | -| Agent can chat but can't act | CLI agent reads text output | MCP Server with 31 tools — Agent directly runs add_clips / split_clip / set_keyframes | +| Agent can chat but can't act | CLI agent reads text output | Capability-filtered MCP Server — Agent directly runs add_clips / split_clip / set_keyframes | | Rewriting prompts for every video type | "You are editing a product review..." every time | Workflow Plugin System: review/tutorial/gaming/wedding, each pre-packaged with methodology | | Steep learning curve for new tools | Complex UI, long onboarding | Agent operates for you — just say "edit this interview into a 3-minute highlight" | @@ -70,16 +76,7 @@ OpenTake is not a replacement for CapCut / DaVinci Resolve / Final Cut Pro — i ## ⚡ Competitive Edge -| Dimension | CapCut | DaVinci Resolve | Final Cut Pro | **OpenTake** | -|:--|:--|:--|:--|:--| -| **Agent-Native Integration** | ❌ | ❌ | ❌ | ✅ MCP 31 tools + Context Signal | -| **Cross-Platform** | ✅ macOS / Win | ✅ macOS / Win / Linux | ❌ macOS only | ✅ macOS / Win / Linux | -| **BYOK AI Generation** | Paid templates | ❌ | ❌ | ✅ Direct fal.ai / Replicate / OpenAI | -| **Local Transcription** | ❌ Cloud-only | ❌ Plugins needed | ❌ Plugins needed | ✅ whisper-rs on-device | -| **Local Semantic Search** | ❌ | ❌ | ❌ | ✅ SigLIP2 + Ort on-device | -| **Workflow Plugins** | ❌ Fixed templates | ❌ | ❌ | ✅ JSON+MD community plugins | -| **Open Source** | ❌ | ❌ | ❌ | ✅ GPL-3.0 | -| **Agent-Controllable Keyframes** | ❌ | ❌ | ❌ | ✅ All 6 kf tracks via MCP | +OpenTake combines a Rust-owned timeline, command-based undo, authenticated MCP, Context Signal and local/BYOK media workflows. The [capability ledger](docs/capabilities/CAPABILITY-LEDGER.md) tracks implementation and evidence; it does not imply feature parity with other editors. --- @@ -100,7 +97,7 @@ Knowledge source: [ClipSkills](https://github.com/appergb/ClipSkills) — 12-vol ### 🔌 Agent Tool Surface -OpenTake exposes 44 compatible Agent tools, filtered at runtime so unavailable media, +OpenTake exposes compatible Agent tools, filtered at runtime so unavailable media, generation, or provider capabilities fail closed instead of being advertised: | Group | Key Tools | @@ -111,10 +108,7 @@ generation, or provider capabilities fail closed instead of being advertised: | Library | `create_folder`, `move_to_folder`, `rename_media` | | Resources | `models/video`, `models/image` | -Official Codex / ChatGPT turns use a fresh loopback endpoint with a random port, -256-bit Bearer token, and current-project identity for that turn only. The legacy -fixed unauthenticated `127.0.0.1:19789` endpoint is disabled in Beta 2; external -Claude/Cursor pairing will return only with an authenticated opt-in flow. +Official Codex / ChatGPT turns use an authenticated per-turn loopback endpoint bound to the current project. Beta 5 also introduced explicit pairing for external MCP clients, with credentials, revocation and persistent client configuration. The old unauthenticated endpoint remains disabled; see [MCP behavior](docs/modules/opentake-agent/mcp-server.md). Built-in Agent chat panel shares tool definitions and system prompt with MCP. It can use direct OpenAI/Anthropic BYOK or the user-installed official Codex CLI's ChatGPT sign-in; OpenTake never @@ -127,8 +121,8 @@ reads or stores the Codex credential. | Codec | FFmpeg (`ffmpeg-next`) — battle-tested Rust bindings | | Compositor | wgpu custom compositor — multi-track layering + per-frame property sampling + affine/crop/blend | | Audio Playback | cpal | -| Transcription | whisper-rs (word/segment timestamps) | -| Semantic Search | candle / ort + SigLIP2 dual-encoder | +| Transcription | whisper-rs (requires installed model) | +| Semantic Search | SigLIP2; fixed-revision model installation and real macOS Rust inference validated | Playback routing is capability-based. Ordinary media uses WebKit playback; timelines that require supported compositing use the Rust compositor; and @@ -154,14 +148,12 @@ Community-authored JSON + Markdown plugins per video genre — review / tutorial ## 🖥️ Platforms -| Platform | Status | Notes | -|:--|:--|:--| -| **macOS** (Apple Silicon + Intel) | ✅ Primary dev platform | Native ARM64 + x86_64; GPU via Metal (wgpu) | -| **Windows** (10/11 x86_64) | ✅ Supported | Vulkan / DX12 backend (wgpu); full Tauri 2 support | -| **Linux** (x86_64) | ✅ Supported | Vulkan backend; AppImage / deb packaging | -| **Backend / Headless** | ✅ Supported | Pure Rust core runs without GUI for CI / server rendering / Agent batch processing | - -📋 macOS ≥12.0 (Monterey), Windows ≥10 (1809+), Linux glibc ≥2.31 +| Platform | Evidence boundary | +|:--|:--| +| macOS | Primary development platform; dated native/package evidence is linked from release notes. Intel and Apple Silicon artifacts require their own validation. | +| Windows | Build/installer target; candidate CI and native installer/UI evidence are required before claiming a verified distribution. | +| Linux | Build target; no new Linux distribution or native GUI verification is claimed here. | +| Headless core | Rust library APIs and tests can run without the desktop UI; media/GPU/browser capabilities still have runtime requirements. | --- @@ -169,19 +161,20 @@ Community-authored JSON + Markdown plugins per video genre — review / tutorial ``` crates/ +├── opentake-process-tree # Cross-platform child-process lifecycle ├── opentake-domain # Timeline / Track / Clip / Keyframe — pure value semantics ├── opentake-ops # OverwriteEngine / RippleEngine / SnapEngine — edit algorithm layer ├── opentake-project # Project persistence / bundle / archive / export ├── opentake-media # FFmpeg codec / thumbnails / waveform / transcription / semantic search ├── opentake-render # wgpu compositor + text rasterizer -├── opentake-motion # Native motion fallback: RGBA frame cache / alpha source scaffold +├── opentake-motion # Motion rendering, RGBA cache and transparent publishing ├── opentake-agent # MCP Server + Agent chat + context signal system ├── opentake-gen # Generative AI clients (fal.ai / Replicate / OpenAI) ├── opentake-core # Session management / DI / event bus └── src-tauri # Tauri 2 desktop shell ``` -Planned external plugin: +Motion Canvas plugin (implemented constrained runner): ``` plugins/ @@ -251,14 +244,14 @@ When porting editing logic, compare against the original Palmier Pro Swift sourc # Clone upstream alongside OpenTake (sibling directory) cd .. git clone https://github.com/palmier-io/palmier-pro.git palmier-pro-upstream -cd OpenTake +cd OpenTake-generation ``` Expected layout: ``` PRIMARY-CN/ -├── OpenTake/ # This repo +├── OpenTake-generation/ # This repo └── palmier-pro-upstream/ # Upstream Swift source (GPL-3.0) ``` @@ -303,11 +296,6 @@ cd .. cargo tauri dev ``` -> **Current Status**: `1.0.0-beta.5` candidate. The local editing, preview, -> persistence, export, authenticated external MCP, ordered Agent conversation, -> Motion Studio, and reviewed AI workflow verticals are implemented. See the -> [Beta release notes](docs/releases/1.0.0-beta.5.md) -> for validation scope and platform/provider limits. The sibling directory `palmier-pro-upstream/` contains upstream Swift sources for reference during porting. @@ -323,6 +311,7 @@ The sibling directory `palmier-pro-upstream/` contains upstream Swift sources fo | `1.0.0-beta.3` | 2026-08-09 | Playback Beta: app-wide Space transport, native HEVC source preview and release-pipeline hardening | | `1.0.0-beta.4` | 2026-08-10 | Release candidate: timing and transition persistence, export consistency, signed updater and Windows tract security upgrade | | `1.0.0-beta.5` | 2026-08-14 | Agent workflow Beta: persistent authenticated MCP, ordered inline tools, Motion Studio, project previews and interface polish | +| `1.0.0-beta.6` | Unpublished candidate | Transparent Motion, ProRes 4444, preview tabs, media views and Text/Effect panels; validation ongoing | | *(planned)* `1.0.0` | TBD | Phase 10: Full release — CapCut parity + deep Agent integration | 📖 [Full Roadmap](docs/architecture/ROADMAP.md) diff --git a/README.zh-CN.md b/README.zh-CN.md index 3224a165..f4859ad7 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -12,6 +12,12 @@

+**发布状态(2026-09-06):** Beta 1–5 已发布。当前工作树已进入 `release/v1.0.0-beta.6`,清单版本为 `1.0.0-beta.6`;Beta 6 仍是**未发布候选**。当前工作见[活动计划](docs/plans/active/2026-09-06-public-beta.md)、[候选说明](docs/releases/1.0.0-beta.6.md)和[文档/源码核对记录](docs/documentation-sync-2026-09-06.md)。 + +候选源码已包含多素材预览 tab、文件夹/平铺/分组媒体视图、temporal compositor 路由、透明 Motion 发布和 ProRes 4444 导出。文本面板可以添加文字片段;特效面板为单个选中视觉片段追加预设并保留既有特效。Sticker 已支持项目图片/Lottie 素材、本地导入、选择/预览、拖拽与落轨;[专项测试记录](docs/audit/2026-09-06/sticker-panel.md)已完成,新包原生 GUI 验收待执行。这些是源码接线状态,原生 GUI、provider 与安装包验证另按证据记录。 + +语义搜索模型安装已实现,约 1.5 GB 的固定 revision 资产已通过校验,macOS 真实 Rust 链路已验证离线安装、图文 embedding 与排名。使用前需安装模型;Windows ort-tract 真实图执行及新包 UI 仍待验收,详见[语义模型专项审计](docs/audit/2026-09-06/semantic-search-model.md)。 + - [项目介绍](#-项目介绍) - [为什么选 OpenTake](#-为什么选-opentake) - [竞品对比优势](#-竞品对比优势) @@ -29,7 +35,7 @@ ## 📖 项目介绍 -**OpenTake** 是一个基于 **Rust + Tauri 2** 构建的**跨平台视频制作引擎**,可在 macOS / Windows / Linux 三大平台上运行,旨在将 AI Agent 与专业视频编辑工作流深度集成。 +**OpenTake** 是一个基于 **Rust + Tauri 2** 构建的**跨平台视频制作引擎**,面向 macOS / Windows / Linux 三大平台,旨在将 AI Agent 与专业视频编辑工作流深度集成。 > 🌟 **核心创新**: 我们不让 Agent 去翻技能文档。OpenTake 会**主动向 Agent 发送编辑指导(Context Signal)**——时间线的每条轨道、每段素材、每个剪辑阶段,软件都能精准告知 Agent「这段该怎么做」。 @@ -44,9 +50,9 @@ OpenTake 不是剪映 / DaVinci Resolve / Final Cut Pro 的替代品——它是 | 痛点 | 传统做法 | OpenTake 的做法 | |:--|:--|:--| | Agent 不知道素材怎么剪 | Agent 自己去读 Skill 文档 | 软件主动发射 Context Signal,告诉 Agent「这条轨是主画面,素材该用口播手法剪」 | -| 跨平台需要三套代码 | macOS 用 Swift/AVFoundation,Windows 用 C++/DirectShow | Rust 单一代码库,FFmpeg + wgpu 跨平台编译,三平台体验一致 | +| 跨平台需要三套代码 | macOS 用 Swift/AVFoundation,Windows 用 C++/DirectShow | Rust 单一代码库,FFmpeg + wgpu 跨平台编译,各平台独立验证 | | 我想用自己的 AI Key | 被锁定在厂商的云服务里 | BYOK(自带 Key)直连 fal.ai / Replicate / OpenAI,零后端、零运营成本 | -| Agent 只能聊不能操作 | CLI Agent 读文本输出 | MCP Server 31 个工具——Agent 直接在时间线上 add_clips / split_clip / set_keyframes | +| Agent 只能聊不能操作 | CLI Agent 读文本输出 | 按能力发布的 MCP Server——Agent 直接在时间线上 add_clips / split_clip / set_keyframes | | 每个视频类型都要重新写提示词 | 每次重复「你要剪一个评测视频...」 | 工作流插件系统:评测/科普/游戏/婚礼每种类型封装好方法论,Agent 开机即用 | | 学新软件成本高 | 界面复杂,学习曲线陡 | Agent 替你操作,你只需要告诉它「帮我把这个采访剪成 3 分钟的精华」 | @@ -54,16 +60,7 @@ OpenTake 不是剪映 / DaVinci Resolve / Final Cut Pro 的替代品——它是 ## ⚡ 竞品对比优势 -| 维度 | 剪映 / CapCut | DaVinci Resolve | Final Cut Pro | **OpenTake** | -|:--|:--|:--|:--|:--| -| **Agent 原生集成** | ❌ | ❌ | ❌ | ✅ MCP 31 工具 + Context Signal | -| **跨平台** | ✅ macOS / Win | ✅ macOS / Win / Linux | ❌ macOS only | ✅ macOS / Win / Linux | -| **BYOK AI 生成** | 内置模板付费 | ❌ | ❌ | ✅ 直连 fal.ai / Replicate / OpenAI | -| **本地语音转写** | ❌ 云端 | ❌ 需插件 | ❌ 需插件 | ✅ whisper-rs 端侧推理 | -| **本地语义搜索** | ❌ | ❌ | ❌ | ✅ SigLIP2 + Ort 本地索引 | -| **工作流插件** | ❌ 固定模板 | ❌ | ❌ | ✅ JSON+MD 社区插件系统 | -| **开源** | ❌ | ❌ | ❌ | ✅ GPL-3.0 | -| **Agent 可操控所有关键帧属性** | ❌ | ❌ | ❌ | ✅ opacity / position / scale / rotation / crop / volume | +OpenTake 的项目重点是 Rust 权威时间线、命令式撤销、认证 MCP、Context Signal 和本地/BYOK 媒体工作流。[能力账本](docs/capabilities/CAPABILITY-LEDGER.md)区分实现与验证证据,不代表已经达到其他编辑器的全功能对等。 --- @@ -84,7 +81,7 @@ Agent 操作时间线时,每次工具返回附带 `context_signal`: ### 🔌 Agent 工具面 -OpenTake 提供 45 个兼容 Agent 工具,并按当前媒体、生成能力和 provider 授权动态发布; +OpenTake 提供兼容 Agent 工具,并按当前媒体、生成能力和 provider 授权动态发布; 未就绪能力会 fail closed,不会被虚假宣传为可执行: | 分组 | 代表工具 | @@ -95,9 +92,7 @@ OpenTake 提供 45 个兼容 Agent 工具,并按当前媒体、生成能力和 | 素材库组织 | `create_folder`, `move_to_folder`, `rename_media` | | 资源 | `models/video`, `models/image` | -官方 Codex / ChatGPT 每轮使用独立的随机回环端口、256-bit Bearer 和当前工程身份, -轮次结束即销毁。Beta 2 已关闭旧的未认证固定 `127.0.0.1:19789` 入口;Claude、 -Cursor 等外部客户端将在后续带认证、显式配对流程完成后重新开放。 +官方 Codex / ChatGPT 每轮使用绑定当前工程的认证回环端点。Beta 5 已加入外部 MCP 客户端显式配对、凭据撤销和重启保留配置;旧的未认证入口仍关闭。详见 [MCP 实现](docs/modules/opentake-agent/mcp-server.md)。 内置 Agent chat panel,与 MCP 共享工具定义和系统提示词。 @@ -109,7 +104,7 @@ Cursor 等外部客户端将在后续带认证、显式配对流程完成后重 | 帧合成 | wgpu 自写合成器 — 多轨叠加 + 逐帧属性采样 + 仿射/裁剪/混合 | | 音频播放 | cpal | | 语音转写 | whisper-rs (word/segment 时间戳) | -| 语义搜索 | candle / ort + SigLIP2 图文双编码器 | +| 语义搜索 | SigLIP2;固定 revision 模型安装与 macOS 真实 Rust 推理已验证 | ### 🌐 BYOK 生成式 AI @@ -125,12 +120,12 @@ Cursor 等外部客户端将在后续带认证、显式配对流程完成后重 ## 🖥️ 支持平台 -| 平台 | 状态 | 说明 | -|:--|:--|:--| -| **macOS** (Apple Silicon + Intel) | ✅ 主要开发平台 | 原生 ARM64 + x86_64,GPU 加速 via Metal (wgpu) | -| **Windows** (10/11 x86_64) | ✅ 支持 | Vulkan / DX12 backend (wgpu),完整 Tauri 2 支持 | -| **Linux** (x86_64) | ✅ 支持 | Vulkan backend,AppImage / deb 打包 | -| **Backend / Headless** | ✅ 支持 | 纯 Rust 核心可在无 GUI 环境下运行,用于 CI / 服务端渲染 / Agent 批量处理 | +| 平台 | 证据边界 | +|:--|:--| +| macOS | 主要开发平台;原生与安装包的日期化证据见发布记录。Intel/Apple Silicon 产物分别验证。 | +| Windows | 构建与安装器目标;候选提交 CI 和真实安装/UI 验证完成后才能称为已验证发行。 | +| Linux | 构建目标;本文不声明新增 Linux 发行或原生 GUI 验证。 | +| Headless core | Rust 库和测试可脱离桌面 UI 运行,媒体/GPU/浏览器能力仍有环境依赖。 | --- @@ -138,6 +133,7 @@ Cursor 等外部客户端将在后续带认证、显式配对流程完成后重 ``` crates/ +├── opentake-process-tree # Cross-platform child-process lifecycle ├── opentake-domain # Timeline / Track / Clip / Keyframe — 纯函数式值语义 ├── opentake-ops # OverwriteEngine / RippleEngine / SnapEngine — 编辑算法层 ├── opentake-project # 项目持久化 / bundle / archive / export @@ -213,16 +209,16 @@ plugins/ ```bash # 在 OpenTake 同级目录 clone 上游 -cd .. # from OpenTake/ +cd .. # from OpenTake-generation/ git clone https://github.com/palmier-io/palmier-pro.git palmier-pro-upstream -cd OpenTake +cd OpenTake-generation ``` 目录结构: ``` PRIMARY-CN/ -├── OpenTake/ # 本项目 +├── OpenTake-generation/ # 本项目 └── palmier-pro-upstream/ # 上游 Swift 源码 (GPL-3.0) ``` @@ -263,9 +259,6 @@ cd web && pnpm install && pnpm build cd .. && cargo tauri dev ``` -> **当前状态**:`1.0.0-beta.2` 候选版。本地剪辑、预览、持久化、导出、Agent、 -> Motion Canvas 与可审阅 AI 工作流竖切均已实现。验证范围及平台/provider 限制见 -> [Beta 发布说明](docs/releases/1.0.0-beta.2.md)。 --- @@ -276,6 +269,10 @@ cd .. && cargo tauri dev | `0.1.0-dev` | 2026-06 | Phase 0+1: Cargo workspace + Domain models + Edit ops + Tauri scaffold | | `1.0.0-beta.1` | 2026-08-01 | 首个可安装 Beta:本地编辑闭环、Agent、Motion 与可审阅 AI 工作流 | | `1.0.0-beta.2` | 2026-08-03 | 官方 Codex 登录、原子时间线手势、安全 MCP 与交互加固 | +| `1.0.0-beta.3` | 2026-08-09 | 空格播放、HEVC 原生预览与发布流水线修复 | +| `1.0.0-beta.4` | 2026-08-10 | 时间/转场持久化、导出一致性与更新器 | +| `1.0.0-beta.5` | 2026-08-14 | 外部 MCP 配对、有序 Agent 对话与 Motion Studio | +| `1.0.0-beta.6` | 未发布候选 | 透明 Motion、ProRes 4444、多预览 tab、媒体视图与文本/特效入口;验证进行中 | | *(planned)* `1.0.0` | TBD | Phase 10: 全功能发布 — 对标剪映 + Agent 深度集成 | 📖 [完整路线图](docs/architecture/ROADMAP.md) diff --git a/crates/opentake-media/src/analysis/matting.rs b/crates/opentake-media/src/analysis/matting.rs index 226d0c2c..0694d047 100644 --- a/crates/opentake-media/src/analysis/matting.rs +++ b/crates/opentake-media/src/analysis/matting.rs @@ -240,7 +240,7 @@ impl RvmMattingSession { let width = frame.width as usize; let height = frame.height as usize; let mut src = vec![0.0_f32; 3 * width * height]; - for (index, pixel) in frame.rgba.chunks_exact(4).enumerate() { + for (index, pixel) in frame.rgba.as_chunks::<4>().0.iter().enumerate() { src[index] = pixel[0] as f32 / 255.0; src[width * height + index] = pixel[1] as f32 / 255.0; src[2 * width * height + index] = pixel[2] as f32 / 255.0; diff --git a/crates/opentake-media/src/analysis/stems.rs b/crates/opentake-media/src/analysis/stems.rs index 9a7233ee..d614686f 100644 --- a/crates/opentake-media/src/analysis/stems.rs +++ b/crates/opentake-media/src/analysis/stems.rs @@ -264,7 +264,7 @@ pub fn separate_stems( .try_reserve_exact(input.len()) .map_err(|error| MediaError::Decode(format!("stem_audio_allocation_failed: {error}")))?; let mut side_energy = 0.0_f64; - for (index, frame) in input.chunks_exact(2).enumerate() { + for (index, frame) in input.as_chunks::<2>().0.iter().enumerate() { if index.is_multiple_of(8 * 1024) { if cancel.checkpoint() { return Err(MediaError::Cancelled); diff --git a/crates/opentake-media/src/decode/audio_stream.rs b/crates/opentake-media/src/decode/audio_stream.rs index bbc467ea..eaaaff8f 100644 --- a/crates/opentake-media/src/decode/audio_stream.rs +++ b/crates/opentake-media/src/decode/audio_stream.rs @@ -60,12 +60,16 @@ fn raw_to_interleaved_f32(bytes: &[u8], spec: &PcmSpec) -> Result> { match spec.format { PcmFormat::F32 => out.extend( bytes - .chunks_exact(4) + .as_chunks::<4>() + .0 + .iter() .map(|b| f32::from_le_bytes([b[0], b[1], b[2], b[3]])), ), PcmFormat::S16Le => out.extend( bytes - .chunks_exact(2) + .as_chunks::<2>() + .0 + .iter() .map(|b| i16::from_le_bytes([b[0], b[1]]) as f32 / 32768.0), ), } diff --git a/crates/opentake-media/src/frame.rs b/crates/opentake-media/src/frame.rs index 9042e45a..02dcb2ec 100644 --- a/crates/opentake-media/src/frame.rs +++ b/crates/opentake-media/src/frame.rs @@ -25,7 +25,7 @@ impl RgbaFrame { /// for tests). pub fn black(width: u32, height: u32) -> Self { let mut rgba = vec![0u8; width as usize * height as usize * 4]; - for px in rgba.chunks_exact_mut(4) { + for px in rgba.as_chunks_mut::<4>().0.iter_mut() { px[3] = 255; } RgbaFrame { @@ -64,7 +64,7 @@ mod tests { assert_eq!(f.rgba.len(), 3 * 2 * 4); assert_eq!(f.pixel_count(), 6); // every pixel is (0,0,0,255) - for px in f.rgba.chunks_exact(4) { + for px in f.rgba.as_chunks::<4>().0.iter() { assert_eq!(px, &[0, 0, 0, 255]); } } diff --git a/crates/opentake-media/src/ort_worker/mod.rs b/crates/opentake-media/src/ort_worker/mod.rs index c4139307..f301de8f 100644 --- a/crates/opentake-media/src/ort_worker/mod.rs +++ b/crates/opentake-media/src/ort_worker/mod.rs @@ -135,6 +135,7 @@ pub enum JobState { #[derive(Clone, Debug, PartialEq, Eq)] pub enum WorkerError { QueueFull, + QueueTimeout, Cancelled, Shutdown, Panicked, @@ -147,6 +148,7 @@ impl std::fmt::Display for WorkerError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::QueueFull => f.write_str("inference queue is full"), + Self::QueueTimeout => f.write_str("inference worker is busy"), Self::Cancelled => f.write_str("inference job was cancelled"), Self::Shutdown => f.write_str("inference worker is shut down"), Self::Panicked => f.write_str("inference job panicked"), @@ -326,6 +328,27 @@ where } } } + + /// Bound admission latency for interactive callers. A job that has already + /// started may finish normally; an expired queued job is cancelled while + /// holding the same status lock used by `set_running`, so it cannot start + /// after this method reports a timeout. + pub fn wait_with_queue_timeout(&self, timeout: Duration) -> Result { + let status = self.shared.status.lock().unwrap_or_else(|e| e.into_inner()); + let (status, _) = self + .shared + .changed + .wait_timeout_while(status, timeout, |status| { + status.state == JobState::Queued && status.result.is_none() + }) + .unwrap_or_else(|e| e.into_inner()); + if status.state == JobState::Queued && status.result.is_none() { + self.shared.cancel.cancel(); + return Err(WorkerError::QueueTimeout); + } + drop(status); + self.wait() + } } /// Single-worker model cache. A worker task can lazily install a typed model by diff --git a/crates/opentake-media/src/search/config.rs b/crates/opentake-media/src/search/config.rs index 22893206..2b6b715c 100644 --- a/crates/opentake-media/src/search/config.rs +++ b/crates/opentake-media/src/search/config.rs @@ -1,7 +1,7 @@ //! Search index configuration constants and the model manifest — port of //! `Search/SearchIndexConfig.swift`. The model is the ONNX build of -//! `siglip2-base-patch16-256` (dim 768, image 256, context 64); upstream's -//! CoreML hashes/bytes are replaced when the ONNX assets are hosted (SPEC T8.0). +//! `siglip2-base-patch16-256` (dim 768, image 256, context 64). The +//! ONNX Community FP32 export is pinned by revision, byte size and SHA-256. use crate::search::embedder::EmbedderSpec; use crate::search::model_download::{Manifest, ManifestFile}; @@ -15,25 +15,18 @@ pub const SEARCH_LIMIT: usize = 20; /// SigLIP2 model identity. pub const MODEL_NAME: &str = "siglip2-base-patch16-256"; -pub const MODEL_VERSION: i32 = 1; +// v2 invalidates embeddings made with the old uncalibrated export contract. +pub const MODEL_VERSION: i32 = 2; pub const EMBEDDING_DIM: usize = 768; pub const IMAGE_SIZE: u32 = 256; pub const CONTEXT_LENGTH: usize = 64; -/// Base URL the ONNX model files are fetched from (`{base}/{file}`), mirroring -/// `WhisperModel.base_url`. Placeholder until the ONNX build is hosted (SPEC -/// T8.0): the download command constructs `{base}/image_encoder.onnx` etc., and -/// SHA-256-verifies each against [`manifest`]'s (currently placeholder) hashes, -/// so a real download only succeeds once both this URL and the manifest -/// hashes/bytes are filled in. The Hugging Face `resolve/main` raw-file endpoint -/// is the intended host (same shape as the whisper model URL). +/// Public ONNX Community export of Google's model, pinned to an immutable commit. +/// Provenance and verified hashes: docs/knowledge/2026-09-06-semantic-search-model.md. pub const MODEL_DOWNLOAD_BASE_URL: &str = - "https://huggingface.co/opentake/siglip2-base-patch16-256-onnx/resolve/main"; + "https://huggingface.co/onnx-community/siglip2-base-patch16-256-ONNX/resolve/d1114256522a37ffa257a0a58017348ab0058db2"; -/// The [`EmbedderSpec`] for the configured SigLIP2 model. `normalized` defaults -/// to `false` to match upstream's assumption that the exported model L2- -/// normalizes internally (SPEC §0.8); flip it only if calibration proves the -/// embeddings need external normalization. +/// The separate encoder graphs return unnormalized pooled features. pub fn embedder_spec() -> EmbedderSpec { EmbedderSpec { model: MODEL_NAME.to_string(), @@ -45,9 +38,7 @@ pub fn embedder_spec() -> EmbedderSpec { } } -/// The download manifest for the ONNX model assets. The `sha256`/`bytes` are -/// placeholders until the ONNX build is hosted (SPEC T8.0); they are validated -/// at download time and must be filled before enabling real downloads. +/// FP32 encoder assets (no external-data sidecars) and the matching tokenizer. pub fn manifest() -> Manifest { Manifest { model: MODEL_NAME.to_string(), @@ -56,19 +47,19 @@ pub fn manifest() -> Manifest { image_size: IMAGE_SIZE, context_length: CONTEXT_LENGTH, image_encoder: ManifestFile { - name: "image_encoder.onnx".to_string(), - sha256: String::new(), - bytes: 0, + name: "onnx/vision_model.onnx".to_string(), + sha256: "f5cb16728a704703f05516ded628397e11dbca4de2eb5db04b0c0bcee988aa7a".into(), + bytes: 371_992_072, }, text_encoder: ManifestFile { - name: "text_encoder.onnx".to_string(), - sha256: String::new(), - bytes: 0, + name: "onnx/text_model.onnx".to_string(), + sha256: "d3de4a6bbbfcb429b6615ac496790353cf4a4fc0f19fbbe7179e523ae60daaef".into(), + bytes: 1_129_469_657, }, tokenizer: ManifestFile { - name: "tokenizer.zip".to_string(), - sha256: String::new(), - bytes: 0, + name: "tokenizer.json".to_string(), + sha256: "cb9140fae3ac5122c972d37adf83e1248471a38147ad76f8215c8872c6fd8322".into(), + bytes: 34_363_039, }, } } @@ -104,4 +95,19 @@ mod tests { assert_eq!(m.version, MODEL_VERSION); assert_eq!(m.embedding_dim, EMBEDDING_DIM); } + #[test] + fn public_manifest_has_pinned_revision_and_complete_checksums() { + let revision = MODEL_DOWNLOAD_BASE_URL.rsplit('/').next().unwrap(); + assert_eq!(revision.len(), 40); + assert!(revision.bytes().all(|b| b.is_ascii_hexdigit())); + let m = manifest(); + let files = [&m.image_encoder, &m.text_encoder, &m.tokenizer]; + for file in files { + assert!(file.bytes > 0); + assert_eq!(file.sha256.len(), 64); + assert!(file.sha256.bytes().all(|b| b.is_ascii_hexdigit())); + } + assert!(files.iter().map(|f| f.bytes).sum::() < 2_000_000_000); + assert_eq!(m.spec(), embedder_spec()); + } } diff --git a/crates/opentake-media/src/search/embedder.rs b/crates/opentake-media/src/search/embedder.rs index 926d6b9a..6ff872d0 100644 --- a/crates/opentake-media/src/search/embedder.rs +++ b/crates/opentake-media/src/search/embedder.rs @@ -128,7 +128,7 @@ pub(crate) mod test_support { // Average channel intensities → 4-vec [r,g,b,brightness], normalized. let mut acc = [0.0f64; 4]; let mut n = 0.0f64; - for px in frame.rgba.chunks_exact(4) { + for px in frame.rgba.as_chunks::<4>().0.iter() { acc[0] += px[0] as f64; acc[1] += px[1] as f64; acc[2] += px[2] as f64; diff --git a/crates/opentake-media/src/search/frame_sampler.rs b/crates/opentake-media/src/search/frame_sampler.rs index 2f330834..bce96849 100644 --- a/crates/opentake-media/src/search/frame_sampler.rs +++ b/crates/opentake-media/src/search/frame_sampler.rs @@ -288,7 +288,7 @@ mod tests { // Pure red (255,0,0) → luma 0.299*255 ≈ 76.245. let f = RgbaFrame::new(8, 8, { let mut v = vec![0u8; 8 * 8 * 4]; - for px in v.chunks_exact_mut(4) { + for px in v.as_chunks_mut::<4>().0.iter_mut() { px[0] = 255; px[3] = 255; } diff --git a/crates/opentake-media/src/search/mod.rs b/crates/opentake-media/src/search/mod.rs index 8b6218c0..3ff44218 100644 --- a/crates/opentake-media/src/search/mod.rs +++ b/crates/opentake-media/src/search/mod.rs @@ -52,7 +52,7 @@ mod integration_tests { let bright = RgbaFrame::new(8, 8, vec![240; 8 * 8 * 4]); let dark = RgbaFrame::new(8, 8, { let mut v = vec![0u8; 8 * 8 * 4]; - for px in v.chunks_exact_mut(4) { + for px in v.as_chunks_mut::<4>().0.iter_mut() { px[3] = 255; } v diff --git a/crates/opentake-media/src/search/model_download.rs b/crates/opentake-media/src/search/model_download.rs index 05c057d8..e5b4ec82 100644 --- a/crates/opentake-media/src/search/model_download.rs +++ b/crates/opentake-media/src/search/model_download.rs @@ -69,23 +69,100 @@ pub fn install_dir(models_dir: &Path, m: &Manifest) -> PathBuf { models_dir.join(format!("{}-v{}", m.model, m.version)) } -/// Return the installed model if all three artifacts (and `tokenizer.json`) -/// exist. Port of `installed(for:)` adapted to ONNX filenames. +/// Fast installed-state check. A receipt is written only after all checks pass. +/// Full hashes are rechecked by `verify_installed` before loading a model. pub fn installed(models_dir: &Path, m: &Manifest) -> Option { let dir = install_dir(models_dir, m); + let receipt: Manifest = + serde_json::from_slice(&std::fs::read(dir.join("manifest.json")).ok()?).ok()?; + if receipt != *m { + return None; + } let image = dir.join("image_encoder.onnx"); let text = dir.join("text_encoder.onnx"); let tokenizer = dir.join("tokenizer"); - if image.exists() && text.exists() && tokenizer.join("tokenizer.json").exists() { - Some(InstalledModel { - image_encoder: image, - text_encoder: text, - tokenizer_folder: tokenizer, - spec: m.spec(), - }) - } else { - None + for (path, file) in [(&image, &m.image_encoder), (&text, &m.text_encoder)] { + if !matches_size(path, file) { + return None; + } + } + let token = tokenizer.join("tokenizer.json"); + if !token.is_file() + || (m.tokenizer.name.ends_with(".json") && !matches_size(&token, &m.tokenizer)) + { + return None; + } + Some(InstalledModel { + image_encoder: image, + text_encoder: text, + tokenizer_folder: tokenizer, + spec: m.spec(), + }) +} + +fn matches_size(path: &Path, file: &ManifestFile) -> bool { + file.bytes > 0 + && std::fs::metadata(path) + .is_ok_and(|meta| meta.is_file() && meta.len() == file.bytes as u64) +} + +/// Verify the actual installed bytes before inference, including offline copies. +pub fn verify_installed(models_dir: &Path, m: &Manifest) -> Result { + let model = installed(models_dir, m) + .ok_or_else(|| MediaError::ModelInstall("model missing or incomplete".into()))?; + verify_sha256(&model.image_encoder, &m.image_encoder.sha256)?; + verify_sha256(&model.text_encoder, &m.text_encoder.sha256)?; + if m.tokenizer.name.ends_with(".json") { + verify_sha256( + &model.tokenizer_folder.join("tokenizer.json"), + &m.tokenizer.sha256, + )?; + } + Ok(model) +} + +#[cfg(feature = "model-download")] +fn validate_manifest(m: &Manifest) -> Result<()> { + use std::path::Component; + if m.model.is_empty() + || !m + .model + .bytes() + .all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_') + { + return Err(MediaError::ModelInstall("invalid model name".into())); + } + for f in [&m.image_encoder, &m.text_encoder, &m.tokenizer] { + if f.name.is_empty() + || !Path::new(&f.name) + .components() + .all(|p| matches!(p, Component::Normal(_))) + || f.name.contains('\\') + || f.bytes <= 0 + || f.sha256.len() != 64 + || !f + .sha256 + .bytes() + .all(|b| b.is_ascii_hexdigit() && !b.is_ascii_uppercase()) + { + return Err(MediaError::ModelInstall(format!( + "invalid manifest entry: {}", + f.name + ))); + } } + Ok(()) +} + +#[cfg(feature = "model-download")] +fn verify_file(path: &Path, file: &ManifestFile) -> Result<()> { + if !matches_size(path, file) { + return Err(MediaError::ModelInstall(format!( + "size mismatch: {} (expected {})", + file.name, file.bytes + ))); + } + verify_sha256(path, &file.sha256) } /// Streaming SHA-256 verification (1 MiB chunks). `Err(Checksum)` on mismatch. @@ -130,8 +207,8 @@ pub fn sha256_hex(bytes: &[u8]) -> String { hex } -/// Download, verify, unzip, and install the model. Requires the `model-download` -/// feature (HTTP + zip). Idempotent: returns immediately if already installed. +/// Download and verify all artifacts before atomically publishing the directory. +/// Temporary files are removed on errors and cancellation. #[cfg(feature = "model-download")] pub async fn install( models_dir: &Path, @@ -140,74 +217,142 @@ pub async fn install( on_progress: impl Fn(f64), ) -> Result { use futures_util::StreamExt; - - if let Some(existing) = installed(models_dir, m) { + validate_manifest(m)?; + if let Ok(existing) = verify_installed(models_dir, m) { + on_progress(1.0); return Ok(existing); } - - let staging = std::env::temp_dir().join(format!("opentake-model-{}", uuid_like())); - std::fs::create_dir_all(&staging)?; - + std::fs::create_dir_all(models_dir)?; + let staging = tempfile::Builder::new() + .prefix(".search-download-") + .tempdir_in(models_dir)?; let files = [&m.image_encoder, &m.text_encoder, &m.tokenizer]; - let total_bytes: i64 = files.iter().map(|f| f.bytes).sum(); - let mut done_bytes: i64 = 0; - let client = reqwest::Client::new(); - + let total_bytes: f64 = files.iter().map(|f| f.bytes as f64).sum(); + let mut done_bytes = 0.0; + let client = reqwest::Client::builder() + .connect_timeout(std::time::Duration::from_secs(30)) + .timeout(std::time::Duration::from_secs(3600)) + .build() + .map_err(|e| MediaError::ModelInstall(format!("HTTP client: {e}")))?; for file in files { let url = format!("{}/{}", base_url.trim_end_matches('/'), file.name); let resp = client .get(&url) .send() .await - .map_err(|e| MediaError::ModelInstall(format!("GET {url}: {e}")))?; + .map_err(|e| MediaError::ModelInstall(format!("GET {}: {e}", file.name)))?; if !resp.status().is_success() { return Err(MediaError::ModelInstall(format!( - "GET {url} -> {}", + "GET {} -> {}", + file.name, resp.status() ))); } - let dest = staging.join(&file.name); + let dest = staging.path().join(&file.name); + if let Some(parent) = dest.parent() { + std::fs::create_dir_all(parent)?; + } let mut out = std::fs::File::create(&dest)?; let mut stream = resp.bytes_stream(); - let base = done_bytes as f64; - let mut file_done: i64 = 0; + let mut file_done = 0u64; while let Some(chunk) = stream.next().await { - let chunk = chunk.map_err(|e| MediaError::ModelInstall(format!("stream: {e}")))?; + let chunk = chunk + .map_err(|e| MediaError::ModelInstall(format!("stream {}: {e}", file.name)))?; + file_done += chunk.len() as u64; + if file_done > file.bytes as u64 { + return Err(MediaError::ModelInstall(format!( + "size exceeded: {}", + file.name + ))); + } use std::io::Write; out.write_all(&chunk)?; - file_done += chunk.len() as i64; - if total_bytes > 0 { - on_progress((base + file_done as f64) / total_bytes as f64); - } + on_progress(((done_bytes + file_done as f64) / total_bytes).min(0.99)); } + out.sync_all()?; drop(out); - verify_sha256(&dest, &file.sha256)?; - done_bytes += file.bytes; - if total_bytes > 0 { - on_progress(done_bytes as f64 / total_bytes as f64); - } + verify_file(&dest, file)?; + done_bytes += file.bytes as f64; } + let model = publish_staging(models_dir, m, staging.path())?; + on_progress(1.0); + Ok(model) +} - // Encoders are plain .onnx; the tokenizer ships as a zip with one top-level - // folder. Unzip it. - let tokenizer_zip = staging.join(&m.tokenizer.name); - let tokenizer_extracted = unzip_single_top_level(&tokenizer_zip, &staging)?; +/// Offline installation from the same repository-relative files as the download. +/// Does not contact the network. Checks source hashes before copying, then checks +/// the staged copies; an existing installation is untouched on verification errors. +#[cfg(feature = "model-download")] +pub fn install_from_directory( + models_dir: &Path, + m: &Manifest, + source: &Path, +) -> Result { + validate_manifest(m)?; + let files = [&m.image_encoder, &m.text_encoder, &m.tokenizer]; + for file in files { + verify_file(&source.join(&file.name), file)?; + } + if let Ok(existing) = verify_installed(models_dir, m) { + return Ok(existing); + } + std::fs::create_dir_all(models_dir)?; + let staging = tempfile::Builder::new() + .prefix(".search-offline-") + .tempdir_in(models_dir)?; + for file in files { + let dest = staging.path().join(&file.name); + if let Some(parent) = dest.parent() { + std::fs::create_dir_all(parent)?; + } + std::fs::copy(source.join(&file.name), &dest)?; + verify_file(&dest, file)?; + } + publish_staging(models_dir, m, staging.path()) +} - let dir = install_dir(models_dir, m); - let _ = std::fs::remove_dir_all(&dir); - std::fs::create_dir_all(&dir)?; +#[cfg(feature = "model-download")] +fn publish_staging(models_dir: &Path, m: &Manifest, staging: &Path) -> Result { + let prepared = staging.join("installed"); + std::fs::create_dir(&prepared)?; std::fs::rename( staging.join(&m.image_encoder.name), - dir.join("image_encoder.onnx"), + prepared.join("image_encoder.onnx"), )?; std::fs::rename( staging.join(&m.text_encoder.name), - dir.join("text_encoder.onnx"), + prepared.join("text_encoder.onnx"), )?; - std::fs::rename(tokenizer_extracted, dir.join("tokenizer"))?; - std::fs::write(dir.join("spec.json"), serde_json::to_vec(&m.spec())?)?; - - let _ = std::fs::remove_dir_all(&staging); + if m.tokenizer.name.ends_with(".json") { + std::fs::create_dir(prepared.join("tokenizer"))?; + std::fs::rename( + staging.join(&m.tokenizer.name), + prepared.join("tokenizer/tokenizer.json"), + )?; + } else { + let extracted = unzip_single_top_level(&staging.join(&m.tokenizer.name), staging)?; + if !extracted.join("tokenizer.json").is_file() { + return Err(MediaError::ModelInstall( + "tokenizer.json missing from archive".into(), + )); + } + std::fs::rename(extracted, prepared.join("tokenizer"))?; + } + std::fs::write(prepared.join("spec.json"), serde_json::to_vec(&m.spec())?)?; + std::fs::write(prepared.join("manifest.json"), serde_json::to_vec(m)?)?; + let dir = install_dir(models_dir, m); + // Preserve a previous (incomplete/corrupt) directory until replacement succeeds. + let backup = staging.join("previous"); + let previous = dir.exists(); + if previous { + std::fs::rename(&dir, &backup)?; + } + if let Err(error) = std::fs::rename(&prepared, &dir) { + if previous { + std::fs::rename(&backup, &dir)?; + } + return Err(error.into()); + } installed(models_dir, m).ok_or_else(|| MediaError::ModelInstall("post-install missing".into())) } @@ -253,17 +398,6 @@ fn unzip_single_top_level(zip_path: &Path, into: &Path) -> Result { Ok(out_root.join(top_levels.into_iter().next().unwrap())) } -/// Tiny unique suffix without pulling a uuid dependency. -#[cfg(feature = "model-download")] -fn uuid_like() -> String { - use std::time::{SystemTime, UNIX_EPOCH}; - let nanos = SystemTime::now() - .duration_since(UNIX_EPOCH) - .map(|d| d.as_nanos()) - .unwrap_or(0); - format!("{nanos:x}") -} - #[cfg(test)] mod tests { use super::*; @@ -309,9 +443,12 @@ mod tests { #[test] fn installed_detected_when_all_artifacts_present() { let dir = tempfile::tempdir().unwrap(); - let m = manifest(); + let mut m = manifest(); + m.image_encoder.bytes = 1; + m.text_encoder.bytes = 1; let id = install_dir(dir.path(), &m); std::fs::create_dir_all(id.join("tokenizer")).unwrap(); + std::fs::write(id.join("manifest.json"), serde_json::to_vec(&m).unwrap()).unwrap(); std::fs::write(id.join("image_encoder.onnx"), b"i").unwrap(); std::fs::write(id.join("text_encoder.onnx"), b"t").unwrap(); std::fs::write(id.join("tokenizer/tokenizer.json"), b"{}").unwrap(); @@ -359,4 +496,174 @@ mod tests { let back: Manifest = serde_json::from_str(&json).unwrap(); assert_eq!(m, back); } + #[test] + fn installed_rejects_unverified_loose_files() { + let root = tempfile::tempdir().unwrap(); + let m = manifest(); + let dir = install_dir(root.path(), &m); + std::fs::create_dir_all(dir.join("tokenizer")).unwrap(); + std::fs::write(dir.join("image_encoder.onnx"), b"i").unwrap(); + std::fs::write(dir.join("text_encoder.onnx"), b"t").unwrap(); + std::fs::write(dir.join("tokenizer/tokenizer.json"), b"{}").unwrap(); + assert!(installed(root.path(), &m).is_none()); + } + + #[cfg(feature = "model-download")] + #[tokio::test] + async fn downloads_nested_onnx_and_raw_tokenizer() { + use std::io::Read; + let server = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); + let url = format!("http://{}", server.local_addr().unwrap()); + let mut m = manifest(); + for (f, name, bytes) in [ + ( + &mut m.image_encoder, + "onnx/vision_model.onnx", + b"image".as_slice(), + ), + ( + &mut m.text_encoder, + "onnx/text_model.onnx", + b"text".as_slice(), + ), + (&mut m.tokenizer, "tokenizer.json", b"{}".as_slice()), + ] { + f.name = name.into(); + f.sha256 = sha256_hex(bytes); + f.bytes = bytes.len() as i64; + } + let serving = std::thread::spawn(move || { + for (path, body) in [ + ("onnx/vision_model.onnx", "image"), + ("onnx/text_model.onnx", "text"), + ("tokenizer.json", "{}"), + ] { + let (mut stream, _) = server.accept().unwrap(); + let mut request = [0; 4096]; + let n = stream.read(&mut request).unwrap(); + assert!( + String::from_utf8_lossy(&request[..n]).starts_with(&format!("GET /{path} ")) + ); + write!( + stream, + "HTTP/1.1 200 OK\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", + body.len() + ) + .unwrap(); + } + }); + let root = tempfile::tempdir().unwrap(); + let result = install(root.path(), &m, &url, |_| {}).await; + // If the downloader fails early, do not block waiting for unused requests. + assert!(result.is_ok(), "{result:?}"); + serving.join().unwrap(); + let got = result.unwrap(); + assert_eq!( + std::fs::read(got.tokenizer_folder.join("tokenizer.json")).unwrap(), + b"{}" + ); + assert_eq!( + install(root.path(), &m, "http://127.0.0.1:1", |_| {}) + .await + .unwrap(), + got + ); + } + + #[cfg(feature = "model-download")] + fn offline_fixture(source: &Path) -> Manifest { + let mut m = manifest(); + for (file, name, bytes) in [ + ( + &mut m.image_encoder, + "onnx/vision_model.onnx", + b"image".as_slice(), + ), + ( + &mut m.text_encoder, + "onnx/text_model.onnx", + b"text".as_slice(), + ), + (&mut m.tokenizer, "tokenizer.json", b"{}".as_slice()), + ] { + file.name = name.into(); + file.sha256 = sha256_hex(bytes); + file.bytes = bytes.len() as i64; + let path = source.join(name); + std::fs::create_dir_all(path.parent().unwrap()).unwrap(); + std::fs::write(path, bytes).unwrap(); + } + m + } + + #[cfg(feature = "model-download")] + #[test] + fn offline_install_verifies_bytes_and_preserves_existing_on_failure() { + let source = tempfile::tempdir().unwrap(); + let models = tempfile::tempdir().unwrap(); + let m = offline_fixture(source.path()); + let got = install_from_directory(models.path(), &m, source.path()).unwrap(); + assert_eq!(verify_installed(models.path(), &m).unwrap(), got); + std::fs::write(source.path().join(&m.image_encoder.name), b"wrong").unwrap(); + assert!(matches!( + install_from_directory(models.path(), &m, source.path()), + Err(MediaError::Checksum(_)) + )); + assert!(verify_installed(models.path(), &m).is_ok()); + std::fs::write(&got.image_encoder, b"wrong").unwrap(); + assert!(verify_installed(models.path(), &m).is_err()); + std::fs::write(&got.image_encoder, b"i").unwrap(); + assert!(installed(models.path(), &m).is_none()); + } + + #[cfg(feature = "model-download")] + #[test] + fn offline_install_rejects_bad_size_hash_and_traversal() { + let source = tempfile::tempdir().unwrap(); + let models = tempfile::tempdir().unwrap(); + let m = offline_fixture(source.path()); + for mutation in 0..3 { + let mut bad = m.clone(); + match mutation { + 0 => bad.image_encoder.bytes += 1, + 1 => bad.image_encoder.sha256.clear(), + _ => bad.image_encoder.name = "../outside.onnx".into(), + } + assert!(install_from_directory(models.path(), &bad, source.path()).is_err()); + } + assert_eq!(std::fs::read_dir(models.path()).unwrap().count(), 0); + } + + #[cfg(feature = "model-download")] + #[tokio::test] + async fn http_failure_cleans_staging_and_does_not_install() { + use std::io::Read; + for (status, body, expected_size) in [ + ("404 Not Found", "", 5), + ("200 OK", "bad", 5), + ("200 OK", "wrong", 5), + ("200 OK", "toolong", 5), + ] { + let server = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); + let url = format!("http://{}", server.local_addr().unwrap()); + let serving = std::thread::spawn(move || { + let (mut stream, _) = server.accept().unwrap(); + let mut request = [0; 4096]; + let _ = stream.read(&mut request).unwrap(); + write!( + stream, + "HTTP/1.1 {status}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", + body.len() + ) + .unwrap(); + }); + let source = tempfile::tempdir().unwrap(); + let models = tempfile::tempdir().unwrap(); + let mut m = offline_fixture(source.path()); + m.image_encoder.bytes = expected_size; + assert!(install(models.path(), &m, &url, |_| {}).await.is_err()); + serving.join().unwrap(); + assert_eq!(std::fs::read_dir(models.path()).unwrap().count(), 0); + } + } } diff --git a/crates/opentake-media/src/search/ort_embedder.rs b/crates/opentake-media/src/search/ort_embedder.rs index 6ae1859e..733256cd 100644 --- a/crates/opentake-media/src/search/ort_embedder.rs +++ b/crates/opentake-media/src/search/ort_embedder.rs @@ -6,9 +6,8 @@ //! right-padded with 0. Output is a `(1, embedding_dim)` f32 vector; we assert //! the length matches the spec, mirroring upstream `vector(from:dim:)`. //! -//! IO tensor names default to the SigLIP CoreML names (`image`/`tokens` → -//! `embedding`); ONNX exports often use `pixel_values`/`input_ids` → -//! `image_embeds`/`text_embeds`. `IoNames` makes them configurable. +//! IO defaults match the pinned ONNX Community graphs: `pixel_values` / +//! `input_ids` → `pooler_output`. Features are L2-normalized for cosine ranking. use std::path::Path; use std::sync::Mutex; @@ -35,12 +34,12 @@ pub struct IoNames { impl Default for IoNames { fn default() -> Self { - // SigLIP CoreML names (upstream `VisualEmbedder`). + // ONNX Community separate encoder graphs expose pooled, unnormalized features. IoNames { - image_input: "image".into(), - image_output: "embedding".into(), - text_input: "tokens".into(), - text_output: "embedding".into(), + image_input: "pixel_values".into(), + image_output: "pooler_output".into(), + text_input: "input_ids".into(), + text_output: "pooler_output".into(), } } } @@ -92,22 +91,25 @@ impl OrtEmbedder { }) } - fn finalize(&self, mut v: Vec) -> Result> { - if v.len() != self.spec.embedding_dim { - return Err(MediaError::BadModelOutput); - } - if self.spec.normalized { - // Model already normalizes — leave as-is. - } else { - // Upstream default assumes in-graph normalization; only normalize - // when the spec explicitly requests external normalization (kept - // here for the calibration path, SPEC §0.8). Currently a no-op. - let _ = &mut v; - } - Ok(v) + fn finalize(&self, v: Vec) -> Result> { + finalize_embedding(v, &self.spec) } } +fn finalize_embedding(mut v: Vec, spec: &EmbedderSpec) -> Result> { + if v.len() != spec.embedding_dim || v.iter().any(|x| !x.is_finite()) { + return Err(MediaError::BadModelOutput); + } + let norm_squared = v.iter().map(|x| x * x).sum::(); + if !norm_squared.is_finite() || norm_squared <= f32::EPSILON { + return Err(MediaError::BadModelOutput); + } + if !spec.normalized { + l2_normalize(&mut v); + } + Ok(v) +} + fn build_session(path: &Path) -> Result { crate::initialize_ort_backend(); let builder = Session::builder().map_err(|e| MediaError::ModelInstall(format!("ort: {e}")))?; @@ -170,22 +172,101 @@ impl Embedder for OrtEmbedder { } } -// Keep `l2_normalize` referenced for the calibration path even while the default -// (in-graph-normalized) configuration does not call it. -#[allow(dead_code)] -fn _normalize_for_calibration(v: &mut [f32]) { - l2_normalize(v); -} - #[cfg(test)] mod tests { use super::*; #[test] - fn default_io_names_match_upstream_coreml() { + fn default_io_names_match_pinned_onnx() { let io = IoNames::default(); - assert_eq!(io.image_input, "image"); - assert_eq!(io.text_input, "tokens"); - assert_eq!(io.image_output, "embedding"); + assert_eq!(io.image_input, "pixel_values"); + assert_eq!(io.text_input, "input_ids"); + assert_eq!(io.image_output, "pooler_output"); + } + #[test] + fn pooled_features_are_normalized_and_invalid_outputs_rejected() { + let mut spec = crate::search::config::embedder_spec(); + spec.embedding_dim = 2; + let v = finalize_embedding(vec![3.0, 4.0], &spec).unwrap(); + assert!((v[0] - 0.6).abs() < 1e-6); + assert!((v[1] - 0.8).abs() < 1e-6); + for invalid in [vec![f32::NAN, 1.0], vec![0.0, 0.0], vec![1.0]] { + assert!(finalize_embedding(invalid, &spec).is_err()); + } + } + + /// Opt-in real-asset check; never downloads as part of the default suite. + /// The directory contains the three pinned repository-relative files plus + /// cats.png and parrots.png from the audit's public fixtures. + #[cfg(feature = "model-download")] + #[test] + #[ignore = "requires OPENTAKE_SEARCH_MODEL_TEST_DIR with verified public model assets and two images"] + fn real_model_offline_install_embeddings_and_ranking() { + use crate::search::{config, embed_store, model_download, ranker}; + let source = std::path::PathBuf::from( + std::env::var("OPENTAKE_SEARCH_MODEL_TEST_DIR").expect("model test directory"), + ); + let models = tempfile::tempdir().unwrap(); + let manifest = config::manifest(); + let installed = + model_download::install_from_directory(models.path(), &manifest, &source).unwrap(); + let verified = model_download::verify_installed(models.path(), &manifest).unwrap(); + assert_eq!(installed, verified); + let embedder = OrtEmbedder::new( + &installed.image_encoder, + &installed.text_encoder, + &installed.tokenizer_folder.join("tokenizer.json"), + installed.spec, + ) + .unwrap(); + let check_vector = |v: &[f32]| { + assert_eq!(v.len(), 768); + assert!(v.iter().all(|x| x.is_finite())); + assert!((v.iter().map(|x| x * x).sum::() - 1.0).abs() < 1e-4); + }; + let mut indexes = Vec::new(); + for name in ["cats", "parrots"] { + let image = image::open(source.join(format!("{name}.png"))) + .unwrap() + .to_rgba8(); + let frame = RgbaFrame::new(image.width(), image.height(), image.into_raw()); + let vector = embedder.encode_image(&frame).unwrap(); + check_vector(&vector); + let header = embed_store::Header { + model: manifest.model.clone(), + model_version: manifest.version, + sampler_version: 1, + dim: 768, + count: 1, + }; + let rows = [embed_store::Row { + time: 0.0, + shot_start: 0.0, + shot_end: 0.0, + }]; + // Exercise the same f16 representation used by persisted search indexes. + let index = embed_store::decode(&embed_store::encode(&header, &rows, &vector).unwrap()) + .unwrap(); + indexes.push((name.to_owned(), index)); + } + for (query, expected) in [ + ("a photo of two cats on a couch", Some("cats")), + ("a photo of colorful parrots", Some("parrots")), + ("沙发上的两只猫", Some("cats")), + ("彩色鹦鹉", Some("parrots")), + ("a photo of an airplane", None), + ] { + let vector = embedder.encode_text(query).unwrap(); + check_vector(&vector); + let hits = ranker::search( + &vector, + &indexes, + config::SEARCH_LIMIT, + config::RELATIVE_CUTOFF, + Some(config::VISUAL_MATCH_COSINE_FLOOR), + ); + println!("query={query:?} hits={hits:?}"); + assert_eq!(hits.first().map(|h| h.asset_id.as_str()), expected); + } } } diff --git a/crates/opentake-media/src/search/tokenizer.rs b/crates/opentake-media/src/search/tokenizer.rs index 2162cf74..0f78a8dc 100644 --- a/crates/opentake-media/src/search/tokenizer.rs +++ b/crates/opentake-media/src/search/tokenizer.rs @@ -1,14 +1,12 @@ //! SigLIP text tokenizer wrapper. Port of `Search/Models/TextTokenizer.swift`. //! //! SigLIP was trained on `max_length`-padded sequences with **no attention -//! mask**, so we must reproduce the Python reference exactly: encode, truncate to -//! `context_length`, then right-pad with `pad_token = 0` to the fixed length. We -//! disable the tokenizer's own padding/truncation and do it manually so the -//! behavior is deterministic and unit-testable. +//! mask**. Truncate before special-token processing so EOS survives long +//! queries, then right-pad with `pad_token = 0` to the fixed context length. use std::path::Path; -use tokenizers::Tokenizer; +use tokenizers::{Tokenizer, TruncationParams}; use crate::error::{MediaError, Result}; @@ -26,9 +24,18 @@ impl SiglipTokenizer { pub fn from_file(path: &Path, context_length: usize) -> Result { let mut inner = Tokenizer::from_file(path) .map_err(|e| MediaError::ModelInstall(format!("tokenizer load: {e}")))?; - // We pad/truncate manually; turn off the tokenizer's own behavior. + if context_length == 0 { + return Err(MediaError::ModelInstall( + "tokenizer context length must be positive".into(), + )); + } inner.with_padding(None); - let _ = inner.with_truncation(None); + inner + .with_truncation(Some(TruncationParams { + max_length: context_length, + ..Default::default() + })) + .map_err(|e| MediaError::ModelInstall(format!("tokenizer truncation: {e}")))?; Ok(SiglipTokenizer { inner, context_length, @@ -41,7 +48,7 @@ impl SiglipTokenizer { /// Tokenize `text`: encode (with special tokens), truncate to /// `context_length`, right-pad with `PAD_TOKEN`. Output length is always - /// exactly `context_length`. Verbatim port of `TextTokenizer.tokenize`. + /// exactly `context_length`; truncation preserves the model's EOS token. pub fn tokenize(&self, text: &str) -> Result> { let encoding = self .inner @@ -97,4 +104,35 @@ mod tests { fn pad_token_is_zero() { assert_eq!(PAD_TOKEN, 0); } + #[test] + fn truncation_preserves_eos_in_the_last_context_slot() { + let mut inner = Tokenizer::new( + tokenizers::models::wordlevel::WordLevel::builder() + .vocab( + [("".into(), 0), ("cat".into(), 2), ("".into(), 1)] + .into_iter() + .collect(), + ) + .unk_token("".into()) + .build() + .unwrap(), + ); + inner.with_pre_tokenizer(Some(tokenizers::pre_tokenizers::whitespace::Whitespace {})); + inner.with_post_processor(Some( + tokenizers::processors::template::TemplateProcessing::builder() + .try_single("$A ") + .unwrap() + .special_tokens(vec![("", 1)]) + .build() + .unwrap(), + )); + let file = tempfile::NamedTempFile::new().unwrap(); + inner.save(file.path(), false).unwrap(); + let tokenizer = SiglipTokenizer::from_file(file.path(), 4).unwrap(); + assert_eq!( + tokenizer.tokenize("cat cat cat cat cat").unwrap(), + vec![2, 2, 2, 1] + ); + assert_eq!(tokenizer.tokenize("cat").unwrap(), vec![2, 1, 0, 0]); + } } diff --git a/crates/opentake-media/src/thumbnail/sprite.rs b/crates/opentake-media/src/thumbnail/sprite.rs index dbe179f8..f5a5e274 100644 --- a/crates/opentake-media/src/thumbnail/sprite.rs +++ b/crates/opentake-media/src/thumbnail/sprite.rs @@ -206,7 +206,7 @@ mod tests { fn thumb(t: f64, w: u32, h: u32, fill: u8) -> VideoThumb { let mut rgba = vec![0u8; (w * h * 4) as usize]; - for px in rgba.chunks_exact_mut(4) { + for px in rgba.as_chunks_mut::<4>().0.iter_mut() { px[0] = fill; px[1] = fill; px[2] = fill; @@ -346,7 +346,7 @@ mod tests { fn avg(f: &RgbaFrame) -> f64 { let mut sum = 0.0f64; let mut n = 0.0f64; - for px in f.rgba.chunks_exact(4) { + for px in f.rgba.as_chunks::<4>().0.iter() { sum += (px[0] as f64 + px[1] as f64 + px[2] as f64) / 3.0; n += 1.0; } diff --git a/crates/opentake-media/tests/ffmpeg_integration.rs b/crates/opentake-media/tests/ffmpeg_integration.rs index 6ed0783d..390cc021 100644 --- a/crates/opentake-media/tests/ffmpeg_integration.rs +++ b/crates/opentake-media/tests/ffmpeg_integration.rs @@ -388,7 +388,7 @@ fn encode_roundtrip_produces_playable_video() { // Push 10 frames of solid color. for i in 0..10u8 { let mut rgba = vec![0u8; (w * h * 4) as usize]; - for px in rgba.chunks_exact_mut(4) { + for px in rgba.as_chunks_mut::<4>().0.iter_mut() { px[0] = i * 20; px[1] = 100; px[2] = 200; @@ -421,7 +421,7 @@ fn encode_codec_roundtrip(codec: VideoCodec, extension: &str, expected_codec: &s let mut encoder = VideoEncoder::new(&out, width, height, 10, &preset).unwrap(); for index in 0..6_u8 { let mut rgba = vec![0_u8; (width * height * 4) as usize]; - for pixel in rgba.chunks_exact_mut(4) { + for pixel in rgba.as_chunks_mut::<4>().0.iter_mut() { pixel.copy_from_slice(&[index.saturating_mul(30), 80, 180, 255]); } encoder @@ -516,7 +516,9 @@ fn prores_4444_roundtrip_preserves_alpha_plane() { .1; let alpha = decoded .rgba - .chunks_exact(4) + .as_chunks::<4>() + .0 + .iter() .map(|pixel| pixel[3]) .collect::>(); for (actual, expected) in alpha.iter().zip([0_u8, 85, 170, 255]) { @@ -548,7 +550,9 @@ fn continuous_decode_scales_real_main10_frames_without_corruption() { let neon_green = decoded .frame .rgba - .chunks_exact(4) + .as_chunks::<4>() + .0 + .iter() .filter(|pixel| pixel[0] < 32 && pixel[1] > 224 && pixel[2] < 32) .count(); assert!( diff --git a/crates/opentake-media/tests/hdr.rs b/crates/opentake-media/tests/hdr.rs index 8b40a438..70548dd6 100644 --- a/crates/opentake-media/tests/hdr.rs +++ b/crates/opentake-media/tests/hdr.rs @@ -127,7 +127,9 @@ fn packaged_hdr_decode_path_materializes_bt709_rgba_pixels() { assert_eq!((frame.width, frame.height), (160, 90)); let (min, max) = frame .rgba - .chunks_exact(4) + .as_chunks::<4>() + .0 + .iter() .flat_map(|pixel| pixel[..3].iter().copied()) .fold((u8::MAX, u8::MIN), |(min, max), value| { (min.min(value), max.max(value)) diff --git a/crates/opentake-motion/src/renderer.rs b/crates/opentake-motion/src/renderer.rs index 3ad66f8a..7e8b5d88 100644 --- a/crates/opentake-motion/src/renderer.rs +++ b/crates/opentake-motion/src/renderer.rs @@ -3059,7 +3059,12 @@ mod chromium_backend { } let mut recovered = Vec::with_capacity(black.len()); - for (black, white) in black.chunks_exact(4).zip(white.chunks_exact(4)) { + for (black, white) in black + .as_chunks::<4>() + .0 + .iter() + .zip(white.as_chunks::<4>().0) + { let mut deltas = [ white[0].saturating_sub(black[0]), white[1].saturating_sub(black[1]), diff --git a/crates/opentake-render/src/gpu/text_engine.rs b/crates/opentake-render/src/gpu/text_engine.rs index d22e4353..f864014f 100644 --- a/crates/opentake-render/src/gpu/text_engine.rs +++ b/crates/opentake-render/src/gpu/text_engine.rs @@ -456,7 +456,7 @@ mod tests { assert_eq!(frame.rgba.len(), (1024 * 108 * 4) as usize); // With fonts available, "Hello" must paint some non-transparent pixels. if r.has_fonts() { - let painted = frame.rgba.chunks_exact(4).any(|px| px[3] > 0); + let painted = frame.rgba.as_chunks::<4>().0.iter().any(|px| px[3] > 0); assert!( painted, "expected visible text pixels when fonts are present" @@ -473,7 +473,7 @@ mod tests { }; let frame = r.rasterize(&req("Hi", &style)).expect("frame"); // Background is opaque, so every pixel has full alpha regardless of fonts. - let opaque = frame.rgba.chunks_exact(4).all(|px| px[3] == 255); + let opaque = frame.rgba.as_chunks::<4>().0.iter().all(|px| px[3] == 255); assert!(opaque, "opaque background should fill the whole box"); } diff --git a/crates/opentake-render/tests/gpu_downscaled_nat.rs b/crates/opentake-render/tests/gpu_downscaled_nat.rs index 4ee0e79c..e61ae798 100644 --- a/crates/opentake-render/tests/gpu_downscaled_nat.rs +++ b/crates/opentake-render/tests/gpu_downscaled_nat.rs @@ -45,7 +45,7 @@ struct SmallTexResolver<'d> { impl TextureResolver for SmallTexResolver<'_> { fn resolve(&mut self, _s: &TextureSource, _f: i64) -> Option> { let mut buf = vec![0u8; 4 * 4 * 4]; - for px in buf.chunks_exact_mut(4) { + for px in buf.as_chunks_mut::<4>().0.iter_mut() { px.copy_from_slice(&self.rgba); } let frame = DecodedFrame::new(4, 4, buf, true); // premultiplied solid @@ -102,7 +102,7 @@ fn downscaled_texture_full_canvas_still_fills_whole_frame() { // source color. Before the fix the content rendered only in the bottom-left // 4×4 quarter (quad scaled by tex 4 instead of nat 16), leaving the rest // opaque black — so any pixel being black catches the regression. - for (i, px) in frame.rgba.chunks_exact(4).enumerate() { + for (i, px) in frame.rgba.as_chunks::<4>().0.iter().enumerate() { assert_eq!( px, &[40, 200, 80, 255], diff --git a/crates/opentake-render/tests/gpu_effects.rs b/crates/opentake-render/tests/gpu_effects.rs index 91d72380..afa81cf8 100644 --- a/crates/opentake-render/tests/gpu_effects.rs +++ b/crates/opentake-render/tests/gpu_effects.rs @@ -81,7 +81,7 @@ impl TextureResolver for SolidResolver<'_> { fn resolve(&mut self, _source: &TextureSource, _frame: i64) -> Option> { if self.cached.is_none() { let mut buf = vec![0u8; 16 * 16 * 4]; - for px in buf.chunks_exact_mut(4) { + for px in buf.as_chunks_mut::<4>().0.iter_mut() { px.copy_from_slice(&self.rgba); } let frame = DecodedFrame::new(16, 16, buf, true); diff --git a/crates/opentake-render/tests/gpu_smoke.rs b/crates/opentake-render/tests/gpu_smoke.rs index 28849356..f6781331 100644 --- a/crates/opentake-render/tests/gpu_smoke.rs +++ b/crates/opentake-render/tests/gpu_smoke.rs @@ -41,7 +41,7 @@ impl TextureResolver for SolidResolver<'_> { fn resolve(&mut self, _source: &TextureSource, _frame: i64) -> Option> { if self.cached.is_none() { let mut buf = vec![0u8; 16 * 16 * 4]; - for px in buf.chunks_exact_mut(4) { + for px in buf.as_chunks_mut::<4>().0.iter_mut() { px.copy_from_slice(&self.rgba); } let frame = DecodedFrame::new(16, 16, buf, true); // already premultiplied @@ -109,7 +109,7 @@ fn empty_plan_clears_to_opaque_black() { .expect("render"); // Every pixel opaque black. - for px in frame.rgba.chunks_exact(4) { + for px in frame.rgba.as_chunks::<4>().0.iter() { assert_eq!(px, &[0, 0, 0, 255], "clear color must be opaque black"); } } @@ -285,7 +285,7 @@ fn read_back_round_trips_through_png() { fn make_solid(device: &wgpu::Device, queue: &wgpu::Queue, rgba: [u8; 4]) -> Rc { let mut buf = vec![0u8; 16 * 16 * 4]; - for px in buf.chunks_exact_mut(4) { + for px in buf.as_chunks_mut::<4>().0.iter_mut() { px.copy_from_slice(&rgba); } let frame = DecodedFrame::new(16, 16, buf, true); diff --git a/crates/opentake-render/tests/gpu_text.rs b/crates/opentake-render/tests/gpu_text.rs index 3b0b811e..e8ae249a 100644 --- a/crates/opentake-render/tests/gpu_text.rs +++ b/crates/opentake-render/tests/gpu_text.rs @@ -138,7 +138,9 @@ fn text_clip_composites_visible_pixels() { if rasterizer.has_fonts() { let any_lit = frame .rgba - .chunks_exact(4) + .as_chunks::<4>() + .0 + .iter() .any(|px| px[0] > 8 || px[1] > 8 || px[2] > 8); assert!( any_lit, @@ -153,7 +155,13 @@ fn text_clip_composites_visible_pixels() { } fn lit_count(frame: &DecodedFrame) -> u32 { - frame.rgba.chunks_exact(4).filter(|px| px[3] > 0).count() as u32 + frame + .rgba + .as_chunks::<4>() + .0 + .iter() + .filter(|px| px[3] > 0) + .count() as u32 } fn x_centroid(frame: &DecodedFrame) -> f64 { diff --git a/crates/opentake-render/tests/gpu_y_orientation.rs b/crates/opentake-render/tests/gpu_y_orientation.rs index cb334686..325f0fbd 100644 --- a/crates/opentake-render/tests/gpu_y_orientation.rs +++ b/crates/opentake-render/tests/gpu_y_orientation.rs @@ -61,7 +61,7 @@ impl TextureResolver for SolidResolver<'_> { fn resolve(&mut self, _source: &TextureSource, _frame: i64) -> Option> { if self.cached.is_none() { let mut buf = vec![0u8; 64 * 64 * 4]; - for px in buf.chunks_exact_mut(4) { + for px in buf.as_chunks_mut::<4>().0.iter_mut() { px.copy_from_slice(&self.rgba); } let frame = DecodedFrame::new(64, 64, buf, true); diff --git a/crates/opentake-render/tests/pixel_diff.rs b/crates/opentake-render/tests/pixel_diff.rs index f522b811..4064ce67 100644 --- a/crates/opentake-render/tests/pixel_diff.rs +++ b/crates/opentake-render/tests/pixel_diff.rs @@ -138,7 +138,7 @@ fn ssim(a: &[u8], b: &[u8], w: u32, h: u32) -> f64 { fn make_solid(rgba: [u8; 4]) -> DecodedFrame { let mut buf = vec![0u8; (Q * Q * 4) as usize]; - for px in buf.chunks_exact_mut(4) { + for px in buf.as_chunks_mut::<4>().0.iter_mut() { px.copy_from_slice(&rgba); } DecodedFrame::new(Q, Q, buf, true) @@ -638,7 +638,9 @@ fn text_overlay_visible_above_video() { if rasterizer.has_fonts() { let any_text = frame .rgba - .chunks_exact(4) + .as_chunks::<4>() + .0 + .iter() .any(|px| px[0] > 30 && px[1] > 30); assert!(any_text, "expected visible text pixels above blue video"); } else { diff --git a/crates/opentake-render/tests/transitions.rs b/crates/opentake-render/tests/transitions.rs index 51f59b2f..4be2c92a 100644 --- a/crates/opentake-render/tests/transitions.rs +++ b/crates/opentake-render/tests/transitions.rs @@ -49,7 +49,7 @@ impl TextureResolver for PairResolver<'_> { other => panic!("unexpected transition source {other}"), }; let mut rgba = vec![0; 16 * 16 * 4]; - for pixel in rgba.chunks_exact_mut(4) { + for pixel in rgba.as_chunks_mut::<4>().0.iter_mut() { pixel.copy_from_slice(&color); } let frame = DecodedFrame::new(16, 16, rgba, true); diff --git a/docs/INDEX.md b/docs/INDEX.md index 3d0c9071..c9a12728 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -1,17 +1,28 @@ # OpenTake 文档总目录 -> 全项目文档的**唯一入口**。文档按「模块」组织成超链接树: +> 状态:canonical · 阶段:implementation-backed · 同步日期:2026-09-06 +> 全项目文档的**唯一索引入口**(自动入口为根 AGENTS.md)。文档按「模块」组织成超链接树: > **要开发某个模块,只需读该模块的 `OVERVIEW.md`(总览)+ `INDEX.md`(目录)**,目录里再链到该模块的各子系统文档与规格。 --- +## 当前工作与状态 + +当前分支与清单已进入 Beta 6 候选;Beta 1–5 已发布,Beta 6 尚未发布。源码接线进展与 GUI/安装包验收分别记录。 + +- [公开 Beta 活动计划](plans/active/2026-09-06-public-beta.md):当前主计划。 +- [Beta 6 候选说明](releases/1.0.0-beta.6.md):主代理维护,待验证、待发布。 +- [当日运行验证](audit/2026-09-06/public-beta-validation.md):主代理维护,未执行门槛仍标为待验证。 +- [Sticker 专项记录](audit/2026-09-06/sticker-panel.md):已实现并通过定向 Web 检查,待新包原生 GUI 验收。 +- [项目规范与意图](project/index.md) · [文档同步报告](documentation-sync-2026-09-06.md)。 + ## 🧭 如何使用本文档树 ``` docs/ ├── INDEX.md ← 你在这里(总目录) ├── modules/ ← ★ 按 crate / 前端分的模块文档树 -│ ├── INDEX.md ← 模块总目录(11 个模块一览) +│ ├── INDEX.md ← 模块总目录(含进程树支持模块) │ └── <模块>/ │ ├── OVERVIEW.md ← 模块总览:职责 / 依赖 / 数据流 / 完成状态 / 对应上游 │ ├── INDEX.md ← 模块目录:链到本模块所有子系统文档 + 规格 + 源码 @@ -29,12 +40,13 @@ docs/ | 层 | 模块 | 一句话 | 入口 | |---|---|---|---| | 领域 | `opentake-domain` | Timeline/Track/Clip/Keyframe 纯值语义(叶子 crate) | [总览](modules/opentake-domain/OVERVIEW.md) · [目录](modules/opentake-domain/INDEX.md) | +| 平台基础 | `opentake-process-tree` | 子进程树生命周期与取消 | [总览](modules/opentake-process-tree/OVERVIEW.md) · [目录](modules/opentake-process-tree/INDEX.md) | | 引擎 | `opentake-ops` | 纯引擎(Overwrite/Ripple/Snap) + EditCommand + 撤销栈 | [总览](modules/opentake-ops/OVERVIEW.md) · [目录](modules/opentake-ops/INDEX.md) | | 能力 | `opentake-project` | 工程持久化 / bundle / archive / 导出 | [总览](modules/opentake-project/OVERVIEW.md) · [目录](modules/opentake-project/INDEX.md) | | 能力 | `opentake-render` | wgpu 合成器 + 文本栅格化(预览/导出共享 RenderPlan) | [总览](modules/opentake-render/OVERVIEW.md) · [目录](modules/opentake-render/INDEX.md) | | 能力 | `opentake-media` | FFmpeg 编解码 / 缩略图 / 波形 / 转写 / 语义搜索 | [总览](modules/opentake-media/OVERVIEW.md) · [目录](modules/opentake-media/INDEX.md) | | 能力 | `opentake-motion` | Lottie / web 动态图形 | [总览](modules/opentake-motion/OVERVIEW.md) · [目录](modules/opentake-motion/INDEX.md) | -| 能力 | `opentake-agent` | MCP server(当前 44 个可发布工具:38 基础 + 4 生成 + 2 动效,能力门控后按能力发布) + 内置 Agent + Context Signal | [总览](modules/opentake-agent/OVERVIEW.md) · [目录](modules/opentake-agent/INDEX.md) | +| 能力 | `opentake-agent` | MCP server(当前 44 个可发布工具:基础 + 生成 + 动效,能力门控后按能力发布) + 内置 Agent + Context Signal | [总览](modules/opentake-agent/OVERVIEW.md) · [目录](modules/opentake-agent/INDEX.md) | | 能力 | `opentake-gen` | 生成式 AI 客户端(BYOK,无后端) | [总览](modules/opentake-gen/OVERVIEW.md) · [目录](modules/opentake-gen/INDEX.md) | | 装配 | `opentake-core` | 会话管理 / DI / 事件总线(命令路由层) | [总览](modules/opentake-core/OVERVIEW.md) · [目录](modules/opentake-core/INDEX.md) | | 装配 | `src-tauri` | Tauri 2 桌面壳 + Tauri 命令 | [总览](modules/src-tauri/OVERVIEW.md) · [目录](modules/src-tauri/INDEX.md) | @@ -46,15 +58,15 @@ docs/ 跨切面、不属单一模块的设计/规划/报告:总体架构、路线图、1:1 移植图与差距、剪映 gap、已知 Bug、编辑自动化 DOS。 -当前长期执行计划:[OpenTake 全功能与上游收敛计划](superpowers/plans/2026-08-21-opentake-full-ui-and-upstream-convergence.md)。 +长期背景计划:[OpenTake 全功能与上游收敛计划](superpowers/plans/2026-08-21-opentake-full-ui-and-upstream-convergence.md)。 -当前 Preview 对齐切片:[Preview tabs parity plan](superpowers/plans/2026-08-21-preview-tabs-parity.md)。 +已记录的 Preview 对齐切片:[Preview tabs parity plan](superpowers/plans/2026-08-21-preview-tabs-parity.md)。 -当前 Preview temporal parity 切片:[Preview temporal remap parity plan](superpowers/plans/2026-08-21-preview-temporal-remap-parity.md)。 +已记录的 Preview temporal parity 切片:[Preview temporal remap parity plan](superpowers/plans/2026-08-21-preview-temporal-remap-parity.md)。 -当前媒体导入切片:[File import dialog reliability plan](superpowers/plans/2026-08-21-file-import-dialog.md)。 +已记录的媒体导入切片:[File import dialog reliability plan](superpowers/plans/2026-08-21-file-import-dialog.md)。 -当前媒体视图切片:[Media view modes parity plan](superpowers/plans/2026-08-21-media-view-modes.md)。 +已记录的媒体视图切片:[Media view modes parity plan](superpowers/plans/2026-08-21-media-view-modes.md)。 能力清单与证据:[Capability ledger](capabilities/CAPABILITY-LEDGER.md) · [Desktop functional matrix](audit/2026-08-21/full-desktop-functional-matrix.md)。 @@ -69,11 +81,11 @@ docs/ | 文档 | 用途 | |---|---| | [README.md](../README.md) · [README.zh-CN.md](../README.zh-CN.md) · [README.ja.md](../README.ja.md) | 项目概览(多语言) | -| [CLAUDE.md](../CLAUDE.md) | 工作交接状态文档(压缩上下文后先读) | +| [CLAUDE.md](../CLAUDE.md) | historical:早期交接快照,非当前任务入口 | | [AGENTS.md](../AGENTS.md) | AI Agent 协作指南 | | [DECISIONS.md](../DECISIONS.md) | 技术选型决策(为何 Rust/Tauri/GPL-3.0) | | [CHANGELOG.md](../CHANGELOG.md) | 变更历史 | | [CONTRIBUTING.md](../CONTRIBUTING.md) | 贡献指南 | | [Specs Index](specs/INDEX.md) | 已批准/历史规格目录 | -| [Beta 发布与验证](releases/1.0.0-beta.5.md) · [Motion Studio 验证](audit/2026-08-13/beta5-motion-studio.md) | ★ 当前 Beta 5 范围、发布门槛与逐项执行证据 | +| [Beta 发布与验证](releases/1.0.0-beta.5.md) · [Motion Studio 验证](audit/2026-08-13/beta5-motion-studio.md) | Beta 5 发布时范围与历史执行证据 | | [Superpowers Recovery](superpowers/specs/2026-07-08-opentake-recovery-integration-design.md) | 历史恢复集成设计与计划入口(Beta 2 已收口) | diff --git a/docs/architecture/ADVANCED-FEATURES.md b/docs/architecture/ADVANCED-FEATURES.md index b71dd571..9d0173cb 100644 --- a/docs/architecture/ADVANCED-FEATURES.md +++ b/docs/architecture/ADVANCED-FEATURES.md @@ -1,5 +1,9 @@ # OpenTake 进阶能力设计(对标剪映模块 1–5 的差距深化) +> 状态:draft · 阶段:partial-implementation · 路由同步:2026-09-06。本文包含设计目标与已有实现;须按源码和日期化证据逐项判断。 +> 当前执行与验收:[公开 Beta 计划](../plans/active/2026-09-06-public-beta.md) · [当日验证](../audit/2026-09-06/public-beta-validation.md)。 + + > **2026-08-01 Beta 对账附录(覆盖下表“现状”列)**:本文的 `missing` / `partial` > 是实现前的历史快照,继续保留作为设计来源,不再表示当前代码状态。首个 Beta 已闭环: > 通用特效、交叉溶解、线性光调色、Lift/Gamma/Gain、HSL、3D LUT、绿幕、圆形/线性/ diff --git a/docs/architecture/ARCHITECTURE.md b/docs/architecture/ARCHITECTURE.md index 8b21fc41..9a9ecfe1 100644 --- a/docs/architecture/ARCHITECTURE.md +++ b/docs/architecture/ARCHITECTURE.md @@ -1,8 +1,12 @@ # OpenTake 架构设计 +> 状态:draft · 阶段:partial-implementation · 路由同步:2026-09-06。本文包含设计目标与已有实现;须按源码和日期化证据逐项判断。 +> 当前执行与验收:[公开 Beta 计划](../plans/active/2026-09-06-public-beta.md) · [当日验证](../audit/2026-09-06/public-beta-validation.md)。 + + > 基于对 palmier-pro-upstream(Swift macOS 视频编辑器)的逐模块拆解综合得出。 > 目标:忠实复刻其编辑逻辑,做成跨平台(macOS/Windows/Linux)、开源(GPL-3.0)、内置更强提示词与能力的版本。 -> 详见 `docs/MODULE-PORT-MAP.md`(20 模块逐项规格)与 `docs/_analysis/`(4 份横切报告)。 +> 详见 `docs/architecture/MODULE-PORT-MAP.md`(20 模块逐项规格)与 `docs/_analysis/`(4 份横切报告)。 ## 0. 一句话洞察 @@ -64,7 +68,7 @@ ## 3. Cargo workspace 布局 ``` -OpenTake/ +OpenTake-generation/ ├── crates/ │ ├── opentake-domain/ # 值类型模型:Timeline/Track/Clip/Keyframe/Transform/Crop/TextStyle/MediaAsset │ │ # + 派生函数(end_frame/source_frames_consumed/*_at 采样/fade)。零 IO,纯逻辑,可全单测 @@ -85,9 +89,9 @@ OpenTake/ └── docs/ ``` -依赖法则(经上游验证):`domain` 零依赖叶子;`ops` 只依赖 `domain`;`command` 是唯一编辑入口;UI/Agent/MCP 是命令层三个对等客户端。 +依赖法则(经上游验证):`domain` 为依赖 serde 的无 I/O 叶子;`ops` 只依赖 `domain`;`command` 是唯一编辑入口;UI/Agent/MCP 是命令层三个对等客户端。 -> Motion / AI Video v1 已由 `plugins/motion-canvas-studio/` 落地:锁定 Motion Canvas 3.17.2(MIT),渲染 materialized mp4 后由 OpenTake 当普通媒体原子导入并落轨。`crates/opentake-motion/` 同时提供离线 Chromium 宿主与 HTML/CSS fallback;透明 alpha / PNG sequence 留给后续版本。 +> Motion / AI Video v1 已由 `plugins/motion-canvas-studio/` 落地:锁定 Motion Canvas 3.17.2(MIT),渲染 materialized mp4 后由 OpenTake 当普通媒体原子导入并落轨。`crates/opentake-motion/` 同时提供离线 Chromium 宿主与 HTML/CSS fallback;透明 alpha 发布与 ProRes 4444 已进入候选;通用 PNG sequence 仍是独立后续范围。 ## 4. 领域模型(可直接复刻,见 MODULE-PORT-MAP.md「Models」) diff --git a/docs/architecture/BUGS.md b/docs/architecture/BUGS.md index 194184b9..8900a3eb 100644 --- a/docs/architecture/BUGS.md +++ b/docs/architecture/BUGS.md @@ -1,5 +1,9 @@ # OpenTake — 已知 Bug 与问题 +> 状态:historical · 阶段:partial-implementation。原日期、问题与对照结论保留;不作为当前缺口清单。 +> 当前执行与验收:[公开 Beta 计划](../plans/active/2026-09-06-public-beta.md) · [当日验证](../audit/2026-09-06/public-beta-validation.md)。 + + > 本文记录扫描确认的所有真实 Bug 和有问题部分。不包括规划中已承认的"未实现功能"(那些在 [PORT-1TO1-GAP.md](PORT-1TO1-GAP.md) 和 [ROADMAP.md](ROADMAP.md) 中)。 > 最后更新:2026-06-26 diff --git a/docs/architecture/CAPCUT-GAP.md b/docs/architecture/CAPCUT-GAP.md index 77d05ecd..00f7853e 100644 --- a/docs/architecture/CAPCUT-GAP.md +++ b/docs/architecture/CAPCUT-GAP.md @@ -1,5 +1,9 @@ # OpenTake vs 剪映 特性差距报告(模块 1–5) +> 状态:historical · 阶段:partial-implementation。原日期、问题与对照结论保留;不作为当前缺口清单。 +> 当前执行与验收:[公开 Beta 计划](../plans/active/2026-09-06-public-beta.md) · [当日验证](../audit/2026-09-06/public-beta-validation.md)。 + + > 由 5 个 max-思考子 Agent 对照 OpenTake 设计稿 + 上游源码逐特性核对。状态:has=已覆盖 / partial=部分 / missing=缺失。已排除剪映模块 6(自然语言交互/语音助手,OpenTake 已有 Agent)与模块 7(云生态/企业协作)。 ## 模块1:基础剪辑与时间线管理 diff --git a/docs/architecture/EDITING-ENGINE-PLAN.md b/docs/architecture/EDITING-ENGINE-PLAN.md index bb5805aa..5577f4d4 100644 --- a/docs/architecture/EDITING-ENGINE-PLAN.md +++ b/docs/architecture/EDITING-ENGINE-PLAN.md @@ -1,5 +1,9 @@ # 剪辑引擎实现现状与规划(EDITING-ENGINE-PLAN) +> 状态:draft · 阶段:partial-implementation · 路由同步:2026-09-06。本文包含设计目标与已有实现;须按源码和日期化证据逐项判断。 +> 当前执行与验收:[公开 Beta 计划](../plans/active/2026-09-06-public-beta.md) · [当日验证](../audit/2026-09-06/public-beta-validation.md)。 + + > **2026-08-03 状态注记:**本文是剪辑引擎的测绘文档。剪辑「算法核」(`ops/*` + `engines/*`) > 已 1:1 写通并随 Beta 1 交付;本文所述「前端接线层缺口」已收口(时间线手势单事务、原子分割 / > Transform、撤销/重做)。Beta 2 范围与发布门槛见 `docs/releases/1.0.0-beta.2.md`。 diff --git a/docs/architecture/FULL_PROJECT_SCAN_REPORT.md b/docs/architecture/FULL_PROJECT_SCAN_REPORT.md index e2f97542..b6b53813 100644 --- a/docs/architecture/FULL_PROJECT_SCAN_REPORT.md +++ b/docs/architecture/FULL_PROJECT_SCAN_REPORT.md @@ -1,5 +1,9 @@ # OpenTake vs palmier-pro-upstream 全项目扫描报告 +> 状态:historical · 阶段:partial-implementation。原日期、问题与对照结论保留;不作为当前缺口清单。 +> 当前执行与验收:[公开 Beta 计划](../plans/active/2026-09-06-public-beta.md) · [当日验证](../audit/2026-09-06/public-beta-validation.md)。 + + **扫描日期**: 2026-06-26 **最后更新**: 2026-06-26 (经代码验证修正) **指令**: 仅只读扫描(read-only),使用多 Agent 模式并行探索 + 直接工具调用。**禁止任何代码修改**。 diff --git a/docs/architecture/HANDOFF-2026-07.md b/docs/architecture/HANDOFF-2026-07.md index 14bf8a52..cd6a4b5f 100644 --- a/docs/architecture/HANDOFF-2026-07.md +++ b/docs/architecture/HANDOFF-2026-07.md @@ -1,5 +1,9 @@ # 交接 · 未完成工作规划(2026-07-04) +> 状态:historical · 阶段:partial-implementation。原日期、问题与对照结论保留;不作为当前缺口清单。 +> 当前执行与验收:[公开 Beta 计划](../plans/active/2026-09-06-public-beta.md) · [当日验证](../audit/2026-09-06/public-beta-validation.md)。 + + > **2026-08-03 状态裁决:**本文件是历史交接记录,不再是当前状态真值。Beta 1 > 已发布(`v1.0.0-beta.1`,起点 `c73c192`),Beta 2 收尾进行中;当前范围与发布 > 门槛见 `docs/releases/1.0.0-beta.2.md`,逐项执行证据见 diff --git a/docs/architecture/INDEX.md b/docs/architecture/INDEX.md index 1b90bc26..8deac148 100644 --- a/docs/architecture/INDEX.md +++ b/docs/architecture/INDEX.md @@ -1,11 +1,18 @@ # 架构与规划 — 目录 +> 状态:canonical · 阶段:implementation-backed · 路由同步:2026-09-06。 +> 当前执行与验收:[公开 Beta 计划](../plans/active/2026-09-06-public-beta.md) · [当日验证](../audit/2026-09-06/public-beta-validation.md)。 + > 上级:[docs 总目录](../INDEX.md) · 同级:[模块文档树](../modules/INDEX.md) · [上游拆解](../upstream-analysis/README.md) > > 这里收录**跨切面、不属单一模块**的设计、规划与报告文档。具体某个模块的实现文档见 [模块文档树](../modules/INDEX.md)。 --- +## 当前执行入口 + +[公开 Beta 活动计划](../plans/active/2026-09-06-public-beta.md) · [Beta 6 候选(待验证、未发布)](../releases/1.0.0-beta.6.md) · [同步报告](../documentation-sync-2026-09-06.md)。旧交接、差距和扫描报告保留原时点结论,不能作为当前待办真值。 + ## 总体设计 | 文档 | 内容 | @@ -18,7 +25,7 @@ | 文档 | 内容 | |---|---| -| [../releases/1.0.0-beta.2.md](../releases/1.0.0-beta.2.md) | **当前 Beta 2 范围、11 阶段验收顺序与发布门槛** | +| [../releases/1.0.0-beta.2.md](../releases/1.0.0-beta.2.md) | 历史 Beta 2 范围与验收记录 | | [HANDOFF-2026-07.md](HANDOFF-2026-07.md) | 2026-07-04 交接时点快照;保留作历史参考,不是当前 TODO 真值 | | [ROADMAP.md](ROADMAP.md) | 分阶段路线图(Phase 0 脚手架 → Motion Canvas 插件) | | [EDITING-ENGINE-PLAN.md](EDITING-ENGINE-PLAN.md) | 剪辑引擎现况与规划:已移植的 ops 层 + 待收口 gap | diff --git a/docs/architecture/MODULE-PORT-MAP.md b/docs/architecture/MODULE-PORT-MAP.md index 634ba218..5128ee36 100644 --- a/docs/architecture/MODULE-PORT-MAP.md +++ b/docs/architecture/MODULE-PORT-MAP.md @@ -1,5 +1,9 @@ # OpenTake 模块移植地图 +> 状态:draft · 阶段:partial-implementation · 路由同步:2026-09-06。本文包含设计目标与已有实现;须按源码和日期化证据逐项判断。 +> 当前执行与验收:[公开 Beta 计划](../plans/active/2026-09-06-public-beta.md) · [当日验证](../audit/2026-09-06/public-beta-validation.md)。 + + > 由 20 个 max-思考子 Agent 对 palmier-pro-upstream 逐模块拆解生成。verdict 含义:direct-port=Rust直写 / needs-replacement=换跨平台库 / ui-rebuild=React重建 / cloud-rebuild=自建后端。 ## 总览 @@ -1097,7 +1101,7 @@ MCPService 端口 19789 与工具注册属 Agent/MCP 子系统,App 层只做开 - [图片编码主流程 ImageEncoder.encode] 输入文件 URL。先算 fileStamp(path+size+mtime);若 stamp 命中内存缓存直接返回。否则走两条路径取其一:(1) passthrough 直通:要求能 sniff 出 MIME(仅 png/jpeg/gif/webp)且 文件字节<=3_500_000 且 像素 max(w,h)<=1568,满足则把原文件字节(mmap 读取)原样返回、保留原 MIME;(2) 否则 downscaled:用 ImageIO 生成最长边=1568 的缩略图(自动应用 EXIF 方向),再依次以 JPEG 质量 [0.85,0.7,0.55,0.4] 编码,取第一个字节数<=3_500_000 的结果,MIME 固定 image/jpeg;四档都超限则返回 nil。成功结果写回缓存;写前若缓存条目数>=32 则整表清空(粗暴全清,非 LRU)。复刻要点:1568 与 3.5MB 两个阈值、质量四档序列、缓存满清空策略、直通仅限四种格式、像素判定用 max(宽,高)。Rust 用 image crate 解码+缩放(Lanczos3),mozjpeg/jpeg-encoder 编码并循环试质量;EXIF 方向需用 kamadak-exif 手动旋转(ImageIO 的 CreateThumbnailWithTransform 在 Rust 无等价,必须显式处理)。 - [图片元数据/缩略图 ImageEncoder] metadata(url, thumbnailMaxPixelSize?):用 CGImageSourceCopyPropertiesAtIndex 读 kCGImagePropertyPixelWidth/Height;若给了 thumbnailMaxPixelSize 则同时生成缩略图。makeThumbnail 选项:CreateThumbnailFromImageAlways=true、WithTransform=true(应用方向)、ShouldCacheImmediately=true、ThumbnailMaxPixelSize=给定值;imageSource 创建时 ShouldCache=false。encodeJPEG(CGImage,quality):用 CGImageDestination 以 LossyCompressionQuality 编码为 JPEG Data。 - [时间码格式化 formatTimecode] 输入 frame:Int、fps:Int,输出 'HH:MM:SS:FF'(冒号分隔,帧号而非毫秒)。fps<=0 时返回 '00:00:00:00'。取 absFrame=abs(frame);ff=absFrame%fps;totalSeconds=absFrame/fps(整除);ss=totalSeconds%60;mm=(totalSeconds/60)%60;hh=totalSeconds/3600;负 frame 前缀 '-'。twoDigit:仅当 0<=value<10 时补前导零,>=10 或负数原样输出(即只保证个位数补零,不截断大于两位的值如小时)。复刻要点:这是非 drop-frame 时间码,帧字段用整数取模 fps;小时不会被两位截断。 -- [帧秒换算] frameToSeconds(frame,fps)= fps>0 ? Double(frame)/Double(fps) : 0。secondsToFrame(seconds,fps)= Int(seconds*Double(fps)),注意是向零截断(Int() 截断小数,不是四舍五入)——复刻必须用 truncate 而非 round,否则边界帧会差一。Double.rounded(toPlaces:)= 乘 10^places 后 .rounded()(就近,.5 向偶/远取决于平台默认 schoolbook)再除回。 +- [帧秒换算] frameToSeconds(frame,fps)= fps>0 ? Double(frame)/Double(fps) : 0。secondsToFrame(seconds,fps)= Int(seconds*Double(fps)),注意是向零截断(Int() 截断小数,不是四舍五入)——复刻必须用 truncate 而非 round,否则边界帧会差一。Double.rounded(toPlaces:)= 乘 10^places 后 .rounded()(就近,中点默认远离零;显式规则另按上游实现)再除回。 - [JSON 浮点规整 roundJSONFloatingPointNumbers] 递归遍历 Any:字典则 mapValues 递归;数组则 map 递归;若是 NSNumber 且非布尔(用 CFGetTypeID==CFBooleanGetTypeID 判定布尔)且 objCType 为 'd' 或 'f'(double/float),取 doubleValue:有限值用 NSDecimalNumber 以 .plain 模式、scale=places 四舍五入返回 NSDecimalNumber;非有限(NaN/Inf)返回 NSNull()。其它类型原样返回。复刻要点:只规整浮点,整数 NSNumber 不动;布尔不能被当数字;NaN/Inf -> null。Rust 中对 serde_json::Value 递归,仅处理 Number 里的 f64,用 (x*10^p).round()/10^p,非有限置 Null。 - [Keychain 存取 KeychainStore] service 固定为 Bundle.main.bundleIdentifier(回退 'io.palmier.pro')。save:先构造 query(class=GenericPassword,service,account),attrs 含 ValueData=utf8 数据 + Accessible=AfterFirstUnlock;先 SecItemUpdate,若返回 errSecItemNotFound 则把 attrs 合并进 query 后 SecItemAdd(典型 upsert 模式)。load:query 加 ReturnData=true、MatchLimit=One;成功后把 Data 转 utf8 字符串并 trim 空白换行,空串视为 nil。delete:按 service+account 删除。复刻要点:upsert 语义、AfterFirstUnlock 可见性、读出后 trim 且空串当无。Rust 用 keyring crate(macOS 后端即 Security framework),跨平台时 Windows=Credential Manager、Linux=Secret Service。 - [崩溃处理 CrashHandler] install():创建 crash.log 父目录;以 O_WRONLY|O_CREAT|O_APPEND 模式 open 文件得 fd(失败为 -1);NSSetUncaughtExceptionHandler 注册 C 函数指针;对 SIGSEGV/SIGABRT/SIGBUS/SIGILL/SIGFPE/SIGTRAP 六个信号注册 signalHandler。未捕获异常处理器:写入时间戳、异常名、reason、callStackSymbols 到 fd。信号处理器(必须 async-signal-safe,只用 write/backtrace/backtrace_symbols_fd/fsync/raise):写 '*** FATAL SIGNAL ***' 头,用 backtrace 抓最多 64 帧并 backtrace_symbols_fd 写出,fsync,然后 signal(sig,SIG_DFL) 复位并 raise(sig) 重新触发以产生系统崩溃报告。复刻要点:信号处理里绝不能分配堆/加锁/用非 async-signal-safe 调用。Rust 用 libc::sigaction + backtrace crate(注意 backtrace 在信号处理中不完全 async-signal-safe,生产可改用第三方如 sentry-rust 的 native crash 或 minidump),异常对应到 Rust panic hook。 diff --git a/docs/architecture/PLAYBACK-ENGINE.md b/docs/architecture/PLAYBACK-ENGINE.md index 0ae49639..44ebf720 100644 --- a/docs/architecture/PLAYBACK-ENGINE.md +++ b/docs/architecture/PLAYBACK-ENGINE.md @@ -1,21 +1,18 @@ # Playback engine architecture -> Current reviewed state: 2026-08-10. The original 2026-07-04 default-off -> MJPEG design is historical; this document records the Wave 1A implementation. +> Status: draft · Stage: implementation-backed · Source review: 2026-09-06. +> Earlier dated QA remains historical. Current candidate evidence is in the [public Beta validation record](../audit/2026-09-06/public-beta-validation.md). ## Capability route is the sole authority `resolveTimelinePlaybackRoute` selects exactly one route before runtime preference or fallback is considered: -- `webkit`: one ordinary visible video track plus image/audio, including - temporal-only reverse or speed changes that do not require compositing. -- `rust`: content requiring the implemented compositor path: text, color grade, - chroma key, up to four linear/circle masks, or multiple visible video tracks. - A WebKit video decode failure may also retry the exact project revision here. -- `unsupported`: Lottie, enabled generic effects, polygon masks, more than four - masks, or any timeline that combines compositing with reverse or a non-unit - speed. Rust unavailable/disabled is also unsupported for Rust-only content. +- `webkit`: ordinary video playback, including single-track reverse/speed changes where no compositor-only property requires Rust. +- `rust`: text, Lottie, color grade, chroma key, stabilization, supported masks/effects and native video stacks when the runtime supports them. Composited temporal remapping is implemented; it is no longer rejected merely because speed differs from one or the clip is reversed. +- `unsupported`: unknown effect names, more than four masks, or a required Rust path that is unavailable/disabled. A multi-video temporal stack requires Rust and fails closed if unavailable. + +The exact branching, including ordinary multi-video runtime fallback behavior, is defined by [playbackRoute.ts](../../web/src/components/preview/playbackRoute.ts). Missing media/source failures are also handled by the native session, rather than silently omitting authored layers. Unsupported capability is fail closed. It receives a localized explanation and disabled Play/Capture/Space controls; it is not silently rendered by an engine diff --git a/docs/architecture/PORT-1TO1-GAP.md b/docs/architecture/PORT-1TO1-GAP.md index a7608080..35853a54 100644 --- a/docs/architecture/PORT-1TO1-GAP.md +++ b/docs/architecture/PORT-1TO1-GAP.md @@ -1,5 +1,9 @@ 我先确认一下任务范围。这是一份纯粹的综合写作任务:你已经给了 5 份详尽的研读 JSON,要求我把它们综合成一份中文 Markdown 文档。我不需要读代码库或验证——所有素材都在 JSON 里。下面直接输出文档本身。 +> 状态:historical · 阶段:partial-implementation。原日期、问题与对照结论保留;不作为当前缺口清单。 +> 当前执行与验收:[公开 Beta 计划](../plans/active/2026-09-06-public-beta.md) · [当日验证](../audit/2026-09-06/public-beta-validation.md)。 + + # OpenTake 1:1 复刻差距与实现计划 ## 〇、现状真话:现在能不能完成一次真实剪辑闭环? diff --git a/docs/architecture/ROADMAP.md b/docs/architecture/ROADMAP.md index 80eb6fc1..019c7c01 100644 --- a/docs/architecture/ROADMAP.md +++ b/docs/architecture/ROADMAP.md @@ -1,14 +1,12 @@ # OpenTake 分阶段实施路线图 -> **2026-08-03 状态裁决:**Phase 0–10 已全部走完并交付 Beta 1(`v1.0.0-beta.1`, -> 2026-08-01 发布)。当前处于 **Beta 2 收尾与发布验证**阶段:范围与门槛见 -> `docs/releases/1.0.0-beta.2.md`,逐项执行证据见 `docs/audit/2026-08-02/beta-functional-verification.md`。 -> 下文各 Phase 的「进度」注记为历史里程碑,不再代表阻塞项;剩余工作集中在收尾 -> (未决 finding 清零、真机/Windows 验证、候选包验收),不涉及新的大功能开发。 +> 状态:historical · 阶段:partial-implementation · 原阶段日期保留。 +> 本文件保留早期 Phase 0–10 规划及其里程碑,不作为全功能完成证明。当前工作已进入 Beta 6 未发布候选,见[活动计划](../plans/active/2026-09-06-public-beta.md)、[候选说明](../releases/1.0.0-beta.6.md)及[当日验证](../audit/2026-09-06/public-beta-validation.md)。 +> 后续源码已加入透明 Motion/ProRes 4444、多预览 tab、temporal compositor 和媒体视图;语义搜索仍须真实模型安装/推理证据。下文早期“待做”不直接等于当前缺口。 > 原则:先把「纯逻辑层」做扎实并与上游对拍,再攻媒体引擎 blocker,最后接 UI / Agent / 生成后端。 -> 每个阶段都有明确「交付物」与「验证标准」。详见 `docs/ARCHITECTURE.md`、`docs/MODULE-PORT-MAP.md`。 -> 对标剪映的进阶能力(特效/转场/调色/蒙版/AI/音频工程等)穿插在下列阶段中,完整规划见 `docs/ADVANCED-FEATURES.md`。 +> 每个阶段都有明确「交付物」与「验证标准」。详见 `docs/architecture/ARCHITECTURE.md`、`docs/architecture/MODULE-PORT-MAP.md`。 +> 对标剪映的进阶能力(特效/转场/调色/蒙版/AI/音频工程等)穿插在下列阶段中,完整规划见 `docs/architecture/ADVANCED-FEATURES.md`。 ## Phase 0 — 工程脚手架 - **做**:Cargo workspace(空 crates 骨架)+ Tauri 2 app + React/Vite 前端 + CI(fmt/clippy/test/前端 build)+ GPL-3.0 LICENSE + README(标明「基于 Palmier Pro 的社区分支」)。 @@ -138,7 +136,7 @@ --- -**2026-07-14 完成度审计校准:** 路线条目已逐项归入 `docs/audit/2026-07-14/requirements.json`;当前裁决、证据与验收条件以同目录 `document-reconciliation.md` 为准,阶段文字不再单独作为完成证明。 +**2026-07-14 完成度审计校准:** 路线条目已逐项归入 `docs/audit/2026-07-14/requirements.json`;当时裁决、证据与验收条件以同目录 `document-reconciliation.md` 为准,阶段文字不再单独作为完成证明。 **当前有效条目的 ledger 绑定:** diff --git a/docs/architecture/STEM-SEPARATION.md b/docs/architecture/STEM-SEPARATION.md index f8a655b5..da414ba2 100644 --- a/docs/architecture/STEM-SEPARATION.md +++ b/docs/architecture/STEM-SEPARATION.md @@ -1,5 +1,9 @@ # Stem separation execution boundaries +> 状态:draft · 阶段:partial-implementation · 路由同步:2026-09-06。本文包含设计目标与已有实现;须按源码和日期化证据逐项判断。 +> 当前执行与验收:[公开 Beta 计划](../plans/active/2026-09-06-public-beta.md) · [当日验证](../audit/2026-09-06/public-beta-validation.md)。 + + OpenTake currently exposes two explicit execution choices. Only the local path is executable in this release; the hosted path is intentionally fail-closed. ## Local execution diff --git a/docs/architecture/UPDATER.md b/docs/architecture/UPDATER.md index 2e78a9e5..b1cf9bea 100644 --- a/docs/architecture/UPDATER.md +++ b/docs/architecture/UPDATER.md @@ -1,5 +1,9 @@ # OpenTake 自动更新架构 +> 状态:draft · 阶段:partial-implementation · 路由同步:2026-09-06。本文包含设计目标与已有实现;须按源码和日期化证据逐项判断。 +> 当前执行与验收:[公开 Beta 计划](../plans/active/2026-09-06-public-beta.md) · [当日验证](../audit/2026-09-06/public-beta-validation.md)。 + + OpenTake 使用 Tauri v2 官方 updater 完成「检查 → 下载 → 签名验证 → 安装 → 安全重启」。更新源只允许独立仓库 `appergb/OpenTake`,不复用 OpenLess 的 URL 或签名密钥。 ## 发布与发现契约 diff --git a/docs/audit/2026-07-14/document-reconciliation.md b/docs/audit/2026-07-14/document-reconciliation.md index 219d72df..e7b950d4 100644 --- a/docs/audit/2026-07-14/document-reconciliation.md +++ b/docs/audit/2026-07-14/document-reconciliation.md @@ -148,7 +148,7 @@ Every row below is one current `incomplete` requirement from the normative ledge | agent-settings-generation | `doc-995da31233ec3c16` | docs/architecture/CAPCUT-GAP.md:219 | Talking-head cleanup removes both configured silence and filler words through reviewable commands. | Generate reviewable cut ranges for both configured silence thresholds and a defined filler-word lexicon using transcript timestamps.; Expose per-cut accept/reject, apply as one command, and one-step undo without altering unaccepted ranges.; On a fixed 30-second transcript/audio fixture, remove the expected silence and filler ranges within one frame, keep speech order/A-V sync, and verify save/reopen/export. | open-gap:acceptance-criteria-required | | agent-settings-generation | `doc-99b9674a4dd12fc7` | docs/specs/agent/10-implementation.md:82 | The Agent implementation checklist is complete, including all tools, safety checks, and cross-system coverage. | Bind only loopback, enforce Host/Origin/content-type/body-size limits, deny unknown/nonfinite/path inputs, and redact credentials/message content from logs.; Store BYOK secrets only in the OS-backed secret path and reject traversal, symlink escape, remote URL import, and unauthorized provider/tool requests fail closed.; Run DNS-rebinding/Origin, oversized body, traversal/symlink, secret-redaction, malformed JSON-RPC, concurrency, and graceful-shutdown tests before release. | open-gap:acceptance-criteria-required | | agent-settings-generation | `doc-9b90bbb501eeed4a` | docs/modules/opentake-agent/SPEC.md:189 | Generate an image asynchronously through GenClient and finalize it as a durable media asset. | generate_image validates model/reference capabilities, submits with authorized credentials, returns a placeholder id, and atomically finalizes result bytes plus generation metadata.; Tests cover reference inputs, restart recovery, provider failure/cancel, and add-to-timeline after completion. | crates/opentake-agent/src/mcp/dispatch.rs::tests::stub_tool_reports_not_implemented | -| agent-settings-generation | `doc-9bc7c9cdadaaa081` | docs/modules/opentake-agent/prompt.md:32 | At docs/modules/opentake-agent/prompt.md:32 under “base.rs:分段 base 提示” (gap-marker), the source “- 短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](dispatch-tools.md) 契约失效)” requires this exact behavior: Tell the model to pass shortened IDs back exactly without padding or guessing. | Source binding: docs/modules/opentake-agent/prompt.md:32; signal=gap-marker; heading=base.rs:分段 base 提示; candidate=- 短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](dispatch-tools.md) 契约失效); Expected behavior: Tell the model to pass shortened IDs back exactly without padding or guessing. This closes only the promise expressed by “短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](dispatch-tools.md) 契约失效)” in “base.rs:分段 base 提示”; adjacent headings or similarly named features remain independently adjudicated.; Deterministic test: exercise “短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](dispatch-tools.md) 契约失效)” with the scenario below and register test:crates/opentake-agent/tests/completion_9bc7c9cdadaaa081.rs#completion_9bc7c9cdadaaa081_tell_the_model_to_pass_shortened_ids_back_exactl; Initial state/input/event: create an isolated temporary project fixture representing the source state in “短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](dispatch-tools.md) 契约失效)”, perform the named open/edit/save/reopen event, and include its absent, malformed, and existing-file boundary where applicable.; Code/store/API/Rust effect: at the Rust Agent/MCP dispatch, typed argument boundary, and any named Tauri/provider bridge, apply “Tell the model to pass shortened IDs back exactly without padding or guessing.” atomically in the Rust project/core layer, changing only the named project files and in-memory session state while preserving unrelated assets and metadata.; Visible/returned assertion: assert the returned project/error variant, exact post-operation files and decoded state, and save-then-reopen equality or the specified fail-closed no-write result.; Evidence required: after the deterministic test passes, record code:<tracked-file>#<declared-symbol> and test:<tracked-test-file>#<exact-test-name>; proposed concrete evidence is test:crates/opentake-agent/tests/completion_9bc7c9cdadaaa081.rs#completion_9bc7c9cdadaaa081_tell_the_model_to_pass_shortened_ids_back_exactl. | active-gap:agent-settings-generation: docs/modules/opentake-agent/prompt.md:32 lacks strict implementation and automated-test proof | +| agent-settings-generation | `doc-9bc7c9cdadaaa081` | docs/modules/opentake-agent/prompt.md:32 | At docs/modules/opentake-agent/prompt.md:32 under “base.rs:分段 base 提示” (gap-marker), the source “- 短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](../../modules/opentake-agent/dispatch-tools.md) 契约失效)” requires this exact behavior: Tell the model to pass shortened IDs back exactly without padding or guessing. | Source binding: docs/modules/opentake-agent/prompt.md:32; signal=gap-marker; heading=base.rs:分段 base 提示; candidate=- 短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](../../modules/opentake-agent/dispatch-tools.md) 契约失效); Expected behavior: Tell the model to pass shortened IDs back exactly without padding or guessing. This closes only the promise expressed by “短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](../../modules/opentake-agent/dispatch-tools.md) 契约失效)” in “base.rs:分段 base 提示”; adjacent headings or similarly named features remain independently adjudicated.; Deterministic test: exercise “短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](../../modules/opentake-agent/dispatch-tools.md) 契约失效)” with the scenario below and register test:crates/opentake-agent/tests/completion_9bc7c9cdadaaa081.rs#completion_9bc7c9cdadaaa081_tell_the_model_to_pass_shortened_ids_back_exactl; Initial state/input/event: create an isolated temporary project fixture representing the source state in “短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](../../modules/opentake-agent/dispatch-tools.md) 契约失效)”, perform the named open/edit/save/reopen event, and include its absent, malformed, and existing-file boundary where applicable.; Code/store/API/Rust effect: at the Rust Agent/MCP dispatch, typed argument boundary, and any named Tauri/provider bridge, apply “Tell the model to pass shortened IDs back exactly without padding or guessing.” atomically in the Rust project/core layer, changing only the named project files and in-memory session state while preserving unrelated assets and metadata.; Visible/returned assertion: assert the returned project/error variant, exact post-operation files and decoded state, and save-then-reopen equality or the specified fail-closed no-write result.; Evidence required: after the deterministic test passes, record code:<tracked-file>#<declared-symbol> and test:<tracked-test-file>#<exact-test-name>; proposed concrete evidence is test:crates/opentake-agent/tests/completion_9bc7c9cdadaaa081.rs#completion_9bc7c9cdadaaa081_tell_the_model_to_pass_shortened_ids_back_exactl. | active-gap:agent-settings-generation: docs/modules/opentake-agent/prompt.md:32 lacks strict implementation and automated-test proof | | agent-settings-generation | `doc-9bd256cf84a82d70` | docs/specs/agent/1-mcp-server.md:47 | At docs/specs/agent/1-mcp-server.md:47, the source “### 1.2.1 绑定 + 幂等开关(照搬上游语义)” requires this exact behavior: Honor the io.opentake.mcp.enabled preference with default enabled semantics and idempotent startup while binding exactly 127.0.0.1:19789 and never 0.0.0.0. | Source binding: docs/specs/agent/1-mcp-server.md:47; signal=heading; heading=1.2.1 绑定 + 幂等开关(照搬上游语义); candidate=### 1.2.1 绑定 + 幂等开关(照搬上游语义); Expected behavior: Honor the io.opentake.mcp.enabled preference with default enabled semantics and idempotent startup while binding exactly 127.0.0.1:19789 and never 0.0.0.0.; Deterministic test: test:crates/opentake-agent/tests/spec_agent_1_line_47_9bd256cf84a82d70.rs#spec_agent_1_line_47_9bd256cf84a82d70_loopback_preference_and_idempotent_start; Initial state/input/event: run startup with the preference missing, false, and true, then call startup twice while the first listener is active and inspect the bound SocketAddr.; Code/store/API/Rust effect: default a missing preference to enabled, skip all binding when disabled, return the existing running handle on repeated start, and create at most one listener at 127.0.0.1:19789.; Visible/returned assertion: assert default enabled starts once, disabled starts zero times, repeated calls are idempotent, the address is 127.0.0.1:19789, and 0.0.0.0 is never attempted.; Evidence required: record the owning code:<tracked-file>#<declared-symbol> and the passing test:<tracked-test-file>#<exact-test-name>; proposed concrete evidence is test:crates/opentake-agent/tests/spec_agent_1_line_47_9bd256cf84a82d70.rs#spec_agent_1_line_47_9bd256cf84a82d70_loopback_preference_and_idempotent_start. | active-gap:agent-settings-generation: docs/specs/agent/1-mcp-server.md:47 lacks strict implementation and automated-test proof | | agent-settings-generation | `doc-a466c25e65ff8024` | docs/architecture/editing-automation/EDITING-AUTOMATION/acceptance-tests.md:30 | The documented automation checks pass against live tool implementations. | Dispatch smart_reframe against decoded fixture frames and assert it returns an in-bounds 9:16 crop proposal rather than needs-vision-backend.; Apply the proposal to one clip, verify only crop/transform properties change, then undo to byte-equal timeline JSON.; Cover subject left/center/right, no-subject fallback, offline media, invalid ratio, cancellation, and save/reopen determinism. | open-gap:acceptance-criteria-required | | agent-settings-generation | `doc-a95bdb175b3dfc95` | docs/architecture/CAPCUT-GAP.md:225 | Avatar generation executes a configured provider and imports the result. | Route avatar generation through a configured provider with explicit consent, provider/model/request provenance, and source-audio identity.; Expose preview, start, progress, cancel, retry, import, and undo; cancellation/failure must import no asset.; Mock provider contracts and run one consented fixture asserting output duration matches narration within one frame and the imported result previews/exports. | open-gap:acceptance-criteria-required | @@ -425,7 +425,7 @@ Every row below is one current `incomplete` requirement from the normative ledge | media-render-playback-export | `doc-3336fb8bef6f3a49` | docs/specs/media/9-domain-contract.md:1 | At docs/specs/media/9-domain-contract.md:1 under “跨平台与合规要点” (heading), the source “# 跨平台与合规要点” requires this exact behavior: Media code follows cross-platform, frame-time, cache, and domain-boundary contracts. | Source binding: docs/specs/media/9-domain-contract.md:1; signal=heading; heading=跨平台与合规要点; candidate=# 跨平台与合规要点; Expected behavior: Media code follows cross-platform, frame-time, cache, and domain-boundary contracts. This closes only the promise expressed by “跨平台与合规要点” in “跨平台与合规要点”; adjacent headings or similarly named features remain independently adjudicated.; Deterministic test: exercise “跨平台与合规要点” with the scenario below and register test:crates/opentake-render/tests/completion_3336fb8bef6f3a49.rs#completion_3336fb8bef6f3a49_media_code_follows_cross_platform_frame_time_cac; Initial state/input/event: load a deterministic local media fixture with fixed dimensions, frame rate, timestamps, tracks, and the parameters stated by “跨平台与合规要点”, then invoke the named preview, playback, render, or export event.; Code/store/API/Rust effect: at the Rust media/render/playback pipeline and its typed preview/export adapter, route “Media code follows cross-platform, frame-time, cache, and domain-boundary contracts.” through the real decode/composition/audio/export pipeline with no placeholder success and without mutating source media.; Visible/returned assertion: assert the exact returned status plus deterministic frame/audio/container properties or typed unsupported/error output, and verify preview/export parity when both paths are named.; Evidence required: after the deterministic test passes, record code:<tracked-file>#<declared-symbol> and test:<tracked-test-file>#<exact-test-name>; proposed concrete evidence is test:crates/opentake-render/tests/completion_3336fb8bef6f3a49.rs#completion_3336fb8bef6f3a49_media_code_follows_cross_platform_frame_time_cac. | active-gap:media-render-playback-export: docs/specs/media/9-domain-contract.md:1 lacks strict implementation and automated-test proof | | media-render-playback-export | `doc-376576e91988107c` | docs/specs/media/2-ffmpeg.md:80 | At docs/specs/media/2-ffmpeg.md:80 under “2.4 编码 / 导出预设(供 `opentake-render` 导出后端调用)” (heading), the source “## 2.4 编码 / 导出预设(供 `opentake-render` 导出后端调用)” requires this exact behavior: The named FFmpeg probe/decode/PCM/encode contract is implemented with integration fixtures. | Source binding: docs/specs/media/2-ffmpeg.md:80; signal=heading; heading=2.4 编码 / 导出预设(供 `opentake-render` 导出后端调用); candidate=## 2.4 编码 / 导出预设(供 `opentake-render` 导出后端调用); Expected behavior: The named FFmpeg probe/decode/PCM/encode contract is implemented with integration fixtures. This closes only the promise expressed by “2.4 编码 / 导出预设(供 `opentake-render` 导出后端调用)” in “2.4 编码 / 导出预设(供 `opentake-render` 导出后端调用)”; adjacent headings or similarly named features remain independently adjudicated.; Deterministic test: exercise “2.4 编码 / 导出预设(供 `opentake-render` 导出后端调用)” with the scenario below and register test:crates/opentake-project/tests/completion_376576e91988107c.rs#completion_376576e91988107c_the_named_ffmpeg_probe_decode_pcm_encode_contrac; Initial state/input/event: create an isolated temporary project fixture representing the source state in “2.4 编码 / 导出预设(供 `opentake-render` 导出后端调用)”, perform the named open/edit/save/reopen event, and include its absent, malformed, and existing-file boundary where applicable.; Code/store/API/Rust effect: at the Rust media/render/playback pipeline and its typed preview/export adapter, apply “The named FFmpeg probe/decode/PCM/encode contract is implemented with integration fixtures.” atomically in the Rust project/core layer, changing only the named project files and in-memory session state while preserving unrelated assets and metadata.; Visible/returned assertion: assert the returned project/error variant, exact post-operation files and decoded state, and save-then-reopen equality or the specified fail-closed no-write result.; Evidence required: after the deterministic test passes, record code:<tracked-file>#<declared-symbol> and test:<tracked-test-file>#<exact-test-name>; proposed concrete evidence is test:crates/opentake-project/tests/completion_376576e91988107c.rs#completion_376576e91988107c_the_named_ffmpeg_probe_decode_pcm_encode_contrac. | active-gap:media-render-playback-export: docs/specs/media/2-ffmpeg.md:80 lacks strict implementation and automated-test proof | | media-render-playback-export | `doc-379e138c3e674f1b` | docs/modules/opentake-motion/integration.md:32 | At docs/modules/opentake-motion/integration.md:32 under “解码器注入(为什么不硬接 PNG 库)” (gap-marker), the source “解码器对缺失/损坏文件返回 `None`——合成器把该帧当"缺帧"处理(与视频解码失败同语义)。” requires this exact behavior: Treat a missing/corrupt decoded motion frame as an absent source frame. | Source binding: docs/modules/opentake-motion/integration.md:32; signal=gap-marker; heading=解码器注入(为什么不硬接 PNG 库); candidate=解码器对缺失/损坏文件返回 `None`——合成器把该帧当"缺帧"处理(与视频解码失败同语义)。; Expected behavior: Treat a missing/corrupt decoded motion frame as an absent source frame. This closes only the promise expressed by “解码器对缺失/损坏文件返回 `None`——合成器把该帧当"缺帧"处理(与视频解码失败同语义)。” in “解码器注入(为什么不硬接 PNG 库)”; adjacent headings or similarly named features remain independently adjudicated.; Deterministic test: exercise “解码器对缺失/损坏文件返回 `None`——合成器把该帧当"缺帧"处理(与视频解码失败同语义)。” with the scenario below and register test:crates/opentake-project/tests/completion_379e138c3e674f1b.rs#completion_379e138c3e674f1b_treat_a_missing_corrupt_decoded_motion_frame_as_; Initial state/input/event: start from the smallest valid fixture for “Treat a missing/corrupt decoded motion frame as an absent source frame.”, then issue both the valid request and the exact malformed, missing, boundary, or hostile input named by “解码器对缺失/损坏文件返回 `None`——合成器把该帧当"缺帧"处理(与视频解码失败同语义)。”.; Code/store/API/Rust effect: at the Rust media/render/playback pipeline and its typed preview/export adapter, on invalid input, reject before any store, project, filesystem, provider, or timeline mutation; on valid input, execute exactly the typed operation “Treat a missing/corrupt decoded motion frame as an absent source frame.” once.; Visible/returned assertion: assert the exact success payload for the valid case and a stable typed error for the invalid case, including zero partial side effects and no leaked internal path or credential.; Evidence required: after the deterministic test passes, record code:<tracked-file>#<declared-symbol> and test:<tracked-test-file>#<exact-test-name>; proposed concrete evidence is test:crates/opentake-project/tests/completion_379e138c3e674f1b.rs#completion_379e138c3e674f1b_treat_a_missing_corrupt_decoded_motion_frame_as_. | active-gap:media-render-playback-export: docs/modules/opentake-motion/integration.md:32 lacks strict implementation and automated-test proof | -| media-render-playback-export | `doc-3a71829a7b489aae` | docs/modules/opentake-motion/sandbox.md:67 | At docs/modules/opentake-motion/sandbox.md:67 under “谁在调用” (gap-marker), the source “- `StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](renderer.md))。” requires this exact behavior: Apply sandbox document-size checks before both stub rendering and the unavailable Chromium path. | Source binding: docs/modules/opentake-motion/sandbox.md:67; signal=gap-marker; heading=谁在调用; candidate=- `StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](renderer.md))。; Expected behavior: Apply sandbox document-size checks before both stub rendering and the unavailable Chromium path. This closes only the promise expressed by “`StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](renderer.md))。” in “谁在调用”; adjacent headings or similarly named features remain independently adjudicated.; Deterministic test: exercise “`StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](renderer.md))。” with the scenario below and register test:crates/opentake-project/tests/completion_3a71829a7b489aae.rs#completion_3a71829a7b489aae_apply_sandbox_document_size_checks_before_both_s; Initial state/input/event: create an isolated temporary project fixture representing the source state in “`StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](renderer.md))。”, perform the named open/edit/save/reopen event, and include its absent, malformed, and existing-file boundary where applicable.; Code/store/API/Rust effect: at the Rust media/render/playback pipeline and its typed preview/export adapter, apply “Apply sandbox document-size checks before both stub rendering and the unavailable Chromium path.” atomically in the Rust project/core layer, changing only the named project files and in-memory session state while preserving unrelated assets and metadata.; Visible/returned assertion: assert the returned project/error variant, exact post-operation files and decoded state, and save-then-reopen equality or the specified fail-closed no-write result.; Evidence required: after the deterministic test passes, record code:<tracked-file>#<declared-symbol> and test:<tracked-test-file>#<exact-test-name>; proposed concrete evidence is test:crates/opentake-project/tests/completion_3a71829a7b489aae.rs#completion_3a71829a7b489aae_apply_sandbox_document_size_checks_before_both_s. | active-gap:media-render-playback-export: docs/modules/opentake-motion/sandbox.md:67 lacks strict implementation and automated-test proof | +| media-render-playback-export | `doc-3a71829a7b489aae` | docs/modules/opentake-motion/sandbox.md:67 | At docs/modules/opentake-motion/sandbox.md:67 under “谁在调用” (gap-marker), the source “- `StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](../../modules/opentake-motion/renderer.md))。” requires this exact behavior: Apply sandbox document-size checks before both stub rendering and the unavailable Chromium path. | Source binding: docs/modules/opentake-motion/sandbox.md:67; signal=gap-marker; heading=谁在调用; candidate=- `StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](../../modules/opentake-motion/renderer.md))。; Expected behavior: Apply sandbox document-size checks before both stub rendering and the unavailable Chromium path. This closes only the promise expressed by “`StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](../../modules/opentake-motion/renderer.md))。” in “谁在调用”; adjacent headings or similarly named features remain independently adjudicated.; Deterministic test: exercise “`StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](../../modules/opentake-motion/renderer.md))。” with the scenario below and register test:crates/opentake-project/tests/completion_3a71829a7b489aae.rs#completion_3a71829a7b489aae_apply_sandbox_document_size_checks_before_both_s; Initial state/input/event: create an isolated temporary project fixture representing the source state in “`StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](../../modules/opentake-motion/renderer.md))。”, perform the named open/edit/save/reopen event, and include its absent, malformed, and existing-file boundary where applicable.; Code/store/API/Rust effect: at the Rust media/render/playback pipeline and its typed preview/export adapter, apply “Apply sandbox document-size checks before both stub rendering and the unavailable Chromium path.” atomically in the Rust project/core layer, changing only the named project files and in-memory session state while preserving unrelated assets and metadata.; Visible/returned assertion: assert the returned project/error variant, exact post-operation files and decoded state, and save-then-reopen equality or the specified fail-closed no-write result.; Evidence required: after the deterministic test passes, record code:<tracked-file>#<declared-symbol> and test:<tracked-test-file>#<exact-test-name>; proposed concrete evidence is test:crates/opentake-project/tests/completion_3a71829a7b489aae.rs#completion_3a71829a7b489aae_apply_sandbox_document_size_checks_before_both_s. | active-gap:media-render-playback-export: docs/modules/opentake-motion/sandbox.md:67 lacks strict implementation and automated-test proof | | media-render-playback-export | `doc-3a72ee4de1f4a46f` | docs/specs/media/0-principles.md:1 | At docs/specs/media/0-principles.md:1 under “设计原则与移植铁律(本 crate 必须遵守)” (heading), the source “# 设计原则与移植铁律(本 crate 必须遵守)” requires this exact behavior: Media code follows cross-platform, frame-time, cache, and domain-boundary contracts. | Source binding: docs/specs/media/0-principles.md:1; signal=heading; heading=设计原则与移植铁律(本 crate 必须遵守); candidate=# 设计原则与移植铁律(本 crate 必须遵守); Expected behavior: Media code follows cross-platform, frame-time, cache, and domain-boundary contracts. This closes only the promise expressed by “设计原则与移植铁律(本 crate 必须遵守)” in “设计原则与移植铁律(本 crate 必须遵守)”; adjacent headings or similarly named features remain independently adjudicated.; Deterministic test: exercise “设计原则与移植铁律(本 crate 必须遵守)” with the scenario below and register test:crates/opentake-render/tests/completion_3a72ee4de1f4a46f.rs#completion_3a72ee4de1f4a46f_media_code_follows_cross_platform_frame_time_cac; Initial state/input/event: load a deterministic local media fixture with fixed dimensions, frame rate, timestamps, tracks, and the parameters stated by “设计原则与移植铁律(本 crate 必须遵守)”, then invoke the named preview, playback, render, or export event.; Code/store/API/Rust effect: at the Rust media/render/playback pipeline and its typed preview/export adapter, route “Media code follows cross-platform, frame-time, cache, and domain-boundary contracts.” through the real decode/composition/audio/export pipeline with no placeholder success and without mutating source media.; Visible/returned assertion: assert the exact returned status plus deterministic frame/audio/container properties or typed unsupported/error output, and verify preview/export parity when both paths are named.; Evidence required: after the deterministic test passes, record code:<tracked-file>#<declared-symbol> and test:<tracked-test-file>#<exact-test-name>; proposed concrete evidence is test:crates/opentake-render/tests/completion_3a72ee4de1f4a46f.rs#completion_3a72ee4de1f4a46f_media_code_follows_cross_platform_frame_time_cac. | active-gap:media-render-playback-export: docs/specs/media/0-principles.md:1 lacks strict implementation and automated-test proof | | media-render-playback-export | `doc-41a8a7a62b46a85e` | docs/architecture/HANDOFF-2026-07.md:191 | The listed renderer, packaging, settings, solo, and stub debt is closed with automated evidence. | Implement Lottie/motion materialization, track-solo playback/export semantics, settings/model/provider persistence, and all residual InspectMedia/generate/upscale/motion/batch library dispatch paths.; Bundle verified FFmpeg/ffprobe sidecars for packaged macOS/Windows and return typed unsupported errors for any renderer/tool capability still unavailable; advertise no placeholder success.; Pass motion/Lottie pixel fixtures, solo A/V mix tests, settings secret/restart/list_models tests, per-tool schema/failure/undo matrix, and installed-app probe/decode/playback/export smoke on both targets. | open-gap:acceptance-criteria-required | | media-render-playback-export | `doc-428f02e756802f9f` | docs/architecture/PLAYBACK-ENGINE.md:88 | At docs/architecture/PLAYBACK-ENGINE.md:88 under “Project/source identity and prewarm/cache” (heading), the source “## Project/source identity and prewarm/cache” requires this exact behavior: The playback subsystem implements project/source identity and prewarm/cache with focused route/lifecycle tests. | Source binding: docs/architecture/PLAYBACK-ENGINE.md:88; signal=heading; heading=Project/source identity and prewarm/cache; candidate=## Project/source identity and prewarm/cache; Expected behavior: The playback subsystem implements project/source identity and prewarm/cache with focused route/lifecycle tests. This closes only the promise expressed by “Project/source identity and prewarm/cache” in “Project/source identity and prewarm/cache”; adjacent headings or similarly named features remain independently adjudicated.; Deterministic test: exercise “Project/source identity and prewarm/cache” with the scenario below and register test:crates/opentake-project/tests/completion_428f02e756802f9f.rs#completion_428f02e756802f9f_the_playback_subsystem_implements_project_source; Initial state/input/event: load a deterministic local media fixture with fixed dimensions, frame rate, timestamps, tracks, and the parameters stated by “Project/source identity and prewarm/cache”, then invoke the named preview, playback, render, or export event.; Code/store/API/Rust effect: at the Rust media/render/playback pipeline and its typed preview/export adapter, route “The playback subsystem implements project/source identity and prewarm/cache with focused route/lifecycle tests.” through the real decode/composition/audio/export pipeline with no placeholder success and without mutating source media.; Visible/returned assertion: assert the exact returned status plus deterministic frame/audio/container properties or typed unsupported/error output, and verify preview/export parity when both paths are named.; Evidence required: after the deterministic test passes, record code:<tracked-file>#<declared-symbol> and test:<tracked-test-file>#<exact-test-name>; proposed concrete evidence is test:crates/opentake-project/tests/completion_428f02e756802f9f.rs#completion_428f02e756802f9f_the_playback_subsystem_implements_project_source. | active-gap:media-render-playback-export: docs/architecture/PLAYBACK-ENGINE.md:88 lacks strict implementation and automated-test proof | @@ -464,7 +464,7 @@ Every row below is one current `incomplete` requirement from the normative ledge | media-render-playback-export | `doc-e36487856d25a4d1` | docs/specs/media/2-ffmpeg.md:30 | At docs/specs/media/2-ffmpeg.md:30 under “2.2 解一帧 `decode_frame_at`(缩略图/采样/取帧共用底座)” (heading), the source “## 2.2 解一帧 `decode_frame_at`(缩略图/采样/取帧共用底座)” requires this exact behavior: The named FFmpeg probe/decode/PCM/encode contract is implemented with integration fixtures. | Source binding: docs/specs/media/2-ffmpeg.md:30; signal=heading; heading=2.2 解一帧 `decode_frame_at`(缩略图/采样/取帧共用底座); candidate=## 2.2 解一帧 `decode_frame_at`(缩略图/采样/取帧共用底座); Expected behavior: The named FFmpeg probe/decode/PCM/encode contract is implemented with integration fixtures. This closes only the promise expressed by “2.2 解一帧 `decode_frame_at`(缩略图/采样/取帧共用底座)” in “2.2 解一帧 `decode_frame_at`(缩略图/采样/取帧共用底座)”; adjacent headings or similarly named features remain independently adjudicated.; Deterministic test: exercise “2.2 解一帧 `decode_frame_at`(缩略图/采样/取帧共用底座)” with the scenario below and register test:crates/opentake-render/tests/completion_e36487856d25a4d1.rs#completion_e36487856d25a4d1_the_named_ffmpeg_probe_decode_pcm_encode_contrac; Initial state/input/event: load a deterministic local media fixture with fixed dimensions, frame rate, timestamps, tracks, and the parameters stated by “2.2 解一帧 `decode_frame_at`(缩略图/采样/取帧共用底座)”, then invoke the named preview, playback, render, or export event.; Code/store/API/Rust effect: at the Rust media/render/playback pipeline and its typed preview/export adapter, route “The named FFmpeg probe/decode/PCM/encode contract is implemented with integration fixtures.” through the real decode/composition/audio/export pipeline with no placeholder success and without mutating source media.; Visible/returned assertion: assert the exact returned status plus deterministic frame/audio/container properties or typed unsupported/error output, and verify preview/export parity when both paths are named.; Evidence required: after the deterministic test passes, record code:<tracked-file>#<declared-symbol> and test:<tracked-test-file>#<exact-test-name>; proposed concrete evidence is test:crates/opentake-render/tests/completion_e36487856d25a4d1.rs#completion_e36487856d25a4d1_the_named_ffmpeg_probe_decode_pcm_encode_contrac. | active-gap:media-render-playback-export: docs/specs/media/2-ffmpeg.md:30 lacks strict implementation and automated-test proof | | media-render-playback-export | `doc-e423ae30effded27` | docs/architecture/CAPCUT-GAP.md:39 | Optical-flow interpolation produces deterministic preview/export frames. | Add an optical-flow interpolation mode with explicit source/target frame-rate and fallback policy in the render model.; Convert a 24 fps motion fixture to 60 fps with exactly the expected output-frame count and unchanged first/last timestamps.; Add pixel/temporal regression tests plus a deterministic unsupported-device fallback; preview and export must select the same interpolation mode. | open-gap:acceptance-criteria-required | | media-render-playback-export | `doc-ed457b544b875446` | docs/architecture/CAPCUT-GAP.md:109 | Transitions are editable and render with preview/export parity. | Persist a transition with kind, duration, and both adjacent clip IDs, rejecting overlaps longer than either available handle.; Expose add/change/remove transition actions in the enabled transition surface with undo/redo.; For cut, midpoint, and end frames of each advertised transition, assert preview/export pixels match and save/reopen preserves the transition exactly. | open-gap:acceptance-criteria-required | -| media-render-playback-export | `doc-ee5f3f6b2ccb9cc6` | docs/modules/opentake-media/OVERVIEW.md:55 | At docs/modules/opentake-media/OVERVIEW.md:55 under “FFmpeg sidecar 编解码(与 SPEC 的关键实现偏差)” (gap-marker), the source “**实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](probe-ff.md)。” requires this exact behavior: Use the ffmpeg/ffprobe sidecar boundary rather than a libav ABI binding. | Source binding: docs/modules/opentake-media/OVERVIEW.md:55; signal=gap-marker; heading=FFmpeg sidecar 编解码(与 SPEC 的关键实现偏差); candidate=**实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](probe-ff.md)。; Expected behavior: Use the ffmpeg/ffprobe sidecar boundary rather than a libav ABI binding. This closes only the promise expressed by “*实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](probe-ff.md)。” in “FFmpeg sidecar 编解码(与 SPEC 的关键实现偏差)”; adjacent headings or similarly named features remain independently adjudicated.; Deterministic test: exercise “*实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](probe-ff.md)。” with the scenario below and register test:crates/opentake-project/tests/completion_ee5f3f6b2ccb9cc6.rs#completion_ee5f3f6b2ccb9cc6_use_the_ffmpeg_ffprobe_sidecar_boundary_rather_t; Initial state/input/event: create an isolated temporary project fixture representing the source state in “*实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](probe-ff.md)。”, perform the named open/edit/save/reopen event, and include its absent, malformed, and existing-file boundary where applicable.; Code/store/API/Rust effect: at the Rust media/render/playback pipeline and its typed preview/export adapter, apply “Use the ffmpeg/ffprobe sidecar boundary rather than a libav ABI binding.” atomically in the Rust project/core layer, changing only the named project files and in-memory session state while preserving unrelated assets and metadata.; Visible/returned assertion: assert the returned project/error variant, exact post-operation files and decoded state, and save-then-reopen equality or the specified fail-closed no-write result.; Evidence required: after the deterministic test passes, record code:<tracked-file>#<declared-symbol> and test:<tracked-test-file>#<exact-test-name>; proposed concrete evidence is test:crates/opentake-project/tests/completion_ee5f3f6b2ccb9cc6.rs#completion_ee5f3f6b2ccb9cc6_use_the_ffmpeg_ffprobe_sidecar_boundary_rather_t. | active-gap:media-render-playback-export: docs/modules/opentake-media/OVERVIEW.md:55 lacks strict implementation and automated-test proof | +| media-render-playback-export | `doc-ee5f3f6b2ccb9cc6` | docs/modules/opentake-media/OVERVIEW.md:55 | At docs/modules/opentake-media/OVERVIEW.md:55 under “FFmpeg sidecar 编解码(与 SPEC 的关键实现偏差)” (gap-marker), the source “**实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](../../modules/opentake-media/SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](../../modules/opentake-media/probe-ff.md)。” requires this exact behavior: Use the ffmpeg/ffprobe sidecar boundary rather than a libav ABI binding. | Source binding: docs/modules/opentake-media/OVERVIEW.md:55; signal=gap-marker; heading=FFmpeg sidecar 编解码(与 SPEC 的关键实现偏差); candidate=**实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](../../modules/opentake-media/SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](../../modules/opentake-media/probe-ff.md)。; Expected behavior: Use the ffmpeg/ffprobe sidecar boundary rather than a libav ABI binding. This closes only the promise expressed by “*实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](../../modules/opentake-media/SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](../../modules/opentake-media/probe-ff.md)。” in “FFmpeg sidecar 编解码(与 SPEC 的关键实现偏差)”; adjacent headings or similarly named features remain independently adjudicated.; Deterministic test: exercise “*实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](../../modules/opentake-media/SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](../../modules/opentake-media/probe-ff.md)。” with the scenario below and register test:crates/opentake-project/tests/completion_ee5f3f6b2ccb9cc6.rs#completion_ee5f3f6b2ccb9cc6_use_the_ffmpeg_ffprobe_sidecar_boundary_rather_t; Initial state/input/event: create an isolated temporary project fixture representing the source state in “*实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](../../modules/opentake-media/SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](../../modules/opentake-media/probe-ff.md)。”, perform the named open/edit/save/reopen event, and include its absent, malformed, and existing-file boundary where applicable.; Code/store/API/Rust effect: at the Rust media/render/playback pipeline and its typed preview/export adapter, apply “Use the ffmpeg/ffprobe sidecar boundary rather than a libav ABI binding.” atomically in the Rust project/core layer, changing only the named project files and in-memory session state while preserving unrelated assets and metadata.; Visible/returned assertion: assert the returned project/error variant, exact post-operation files and decoded state, and save-then-reopen equality or the specified fail-closed no-write result.; Evidence required: after the deterministic test passes, record code:<tracked-file>#<declared-symbol> and test:<tracked-test-file>#<exact-test-name>; proposed concrete evidence is test:crates/opentake-project/tests/completion_ee5f3f6b2ccb9cc6.rs#completion_ee5f3f6b2ccb9cc6_use_the_ffmpeg_ffprobe_sidecar_boundary_rather_t. | active-gap:media-render-playback-export: docs/modules/opentake-media/OVERVIEW.md:55 lacks strict implementation and automated-test proof | | media-render-playback-export | `doc-ef91c43e6cb92a53` | docs/architecture/PLAYBACK-ENGINE.md:117 | Playback/export is release-ready across packaged macOS and Windows with all declared capabilities rendered or explicitly rejected. | Complete installed-app export UI artifact and packaged FFmpeg/sidecar validation.; Close Windows WebView2/CSP/sidecar and signing/notarization acceptance.; Implement or fail closed for Lottie, polygon masks, unsupported effects, composited reverse/speed, and complete ProRes/A/V device probes. | open-gap:acceptance-criteria-required | | media-render-playback-export | `doc-f06463b86285d17a` | docs/specs/media/2-ffmpeg.md:1 | FFmpeg is resolved as a verified bundled sidecar in packaged macOS and Windows builds. | Package target-specific FFmpeg/ffprobe binaries and verify checksum/version.; Resolve packaged sidecar paths without relying on developer PATH.; Run installed-app probe/decode/encode smoke tests on macOS and Windows. | open-gap:acceptance-criteria-required | | media-render-playback-export | `doc-f2992815147bed14` | docs/modules/opentake-project/OVERVIEW.md:110 | At docs/modules/opentake-project/OVERVIEW.md:110 under “移植铁律(本模块重点)” (gap-marker), the source “1. **serde 向后兼容是第一铁律**:所有序列化模型加 `#[serde(default)]` + `Option<T>`,保证**读旧工程不破坏**。新增字段必须有缺省值;缺失键降级而非报错。`media.json` 的 `version` 缺省按 1(结构体默认 2,但缺省回退 1),`generation-log.json` 的 `version` 默认与回退**都是 1**——二者不同,别混。” requires this exact behavior: Decode persisted project/domain models compatibly, defaulting absent optional fields and preserving explicit version migrations. | Source binding: docs/modules/opentake-project/OVERVIEW.md:110; signal=gap-marker; heading=移植铁律(本模块重点); candidate=1. **serde 向后兼容是第一铁律**:所有序列化模型加 `#[serde(default)]` + `Option<T>`,保证**读旧工程不破坏**。新增字段必须有缺省值;缺失键降级而非报错。`media.json` 的 `version` 缺省按 1(结构体默认 2,但缺省回退 1),`generation-log.json` 的 `version` 默认与回退**都是 1**——二者不同,别混。; Expected behavior: Decode persisted project/domain models compatibly, defaulting absent optional fields and preserving explicit version migrations. This closes only the promise expressed by “1. **serde 向后兼容是第一铁律**:所有序列化模型加 `#[serde(default)]` + `Option<T>`,保证**读旧工程不破坏**。新增字段必须有缺省值;缺失键降级而非报错。`media.json` 的 `version` 缺省按 1(结构体默认 2,但缺省回退 1),`generation-log.json` 的 `version` 默认与回退**都是 1**——二者不同,别混。” in “移植铁律(本模块重点)”; adjacent headings or similarly named features remain independently adjudicated.; Deterministic test: exercise “1. **serde 向后兼容是第一铁律**:所有序列化模型加 `#[serde(default)]` + `Option<T>`,保证**读旧工程不破坏**。新增字段必须有缺省值;缺失键降级而非报错。`media.json` 的 `version` 缺省按 1(结构体默认 2,但缺省回退 1),`generation-log.json` 的 `version` 默认与回退**都是 1**——二者不同,别混。” with the scenario below and register test:crates/opentake-agent/tests/completion_f2992815147bed14.rs#completion_f2992815147bed14_decode_persisted_project_domain_models_compatibl; Initial state/input/event: create an isolated temporary project fixture representing the source state in “1. **serde 向后兼容是第一铁律**:所有序列化模型加 `#[serde(default)]` + `Option<T>`,保证**读旧工程不破坏**。新增字段必须有缺省值;缺失键降级而非报错。`media.json` 的 `version` 缺省按 1(结构体默认 2,但缺省回退 1),`generation-log.json` 的 `version` 默认与回退**都是 1**——二者不同,别混。”, perform the named open/edit/save/reopen event, and include its absent, malformed, and existing-file boundary where applicable.; Code/store/API/Rust effect: at the Rust Agent/MCP dispatch, typed argument boundary, and any named Tauri/provider bridge, apply “Decode persisted project/domain models compatibly, defaulting absent optional fields and preserving explicit version migrations.” atomically in the Rust project/core layer, changing only the named project files and in-memory session state while preserving unrelated assets and metadata.; Visible/returned assertion: assert the returned project/error variant, exact post-operation files and decoded state, and save-then-reopen equality or the specified fail-closed no-write result.; Evidence required: after the deterministic test passes, record code:<tracked-file>#<declared-symbol> and test:<tracked-test-file>#<exact-test-name>; proposed concrete evidence is test:crates/opentake-agent/tests/completion_f2992815147bed14.rs#completion_f2992815147bed14_decode_persisted_project_domain_models_compatibl. | active-gap:media-render-playback-export: docs/modules/opentake-project/OVERVIEW.md:110 lacks strict implementation and automated-test proof | diff --git a/docs/audit/2026-07-14/implementation-plans/agent-settings-generation-design.md b/docs/audit/2026-07-14/implementation-plans/agent-settings-generation-design.md index cc6fa6ed..e39199aa 100644 --- a/docs/audit/2026-07-14/implementation-plans/agent-settings-generation-design.md +++ b/docs/audit/2026-07-14/implementation-plans/agent-settings-generation-design.md @@ -901,8 +901,8 @@ Close each record as the smallest end-to-end vertical slice while preserving Rus - `crates/opentake-agent/src/tools/short_id.rs#expand_unique_prefix_resolves_to_full` (existing-owned): Exact named test already exists in the reviewed owning runner and records current boundary behavior. - `crates/opentake-agent/src/tools/short_id.rs#shared_prefix_extends_until_unique` (existing-owned): Exact named test already exists in the reviewed owning runner and records current boundary behavior. - `crates/opentake-agent/src/tools/short_id.rs#unique_id_shortens_to_floor` (existing-owned): Exact named test already exists in the reviewed owning runner and records current boundary behavior. -- Expected behavior: At docs/modules/opentake-agent/prompt.md:32 under “base.rs:分段 base 提示” (gap-marker), the source “- 短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](dispatch-tools.md) 契约失效)” requires this exact behavior: Tell the model to pass shortened IDs back exactly without padding or guessing. -- Acceptance criteria: Source binding: docs/modules/opentake-agent/prompt.md:32; signal=gap-marker; heading=base.rs:分段 base 提示; candidate=- 短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](dispatch-tools.md) 契约失效) Expected behavior: Tell the model to pass shortened IDs back exactly without padding or guessing. This closes only the promise expressed by “短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](dispatch-tools.md) 契约失效)” in “base.rs:分段 base 提示”; adjacent headings or similarly named features remain independently adjudicated. Deterministic test: exercise “短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](dispatch-tools.md) 契约失效)” with the scenario below and register test:crates/opentake-agent/tests/completion_9bc7c9cdadaaa081.rs#completion_9bc7c9cdadaaa081_tell_the_model_to_pass_shortened_ids_back_exactl Initial state/input/event: create an isolated temporary project fixture representing the source state in “短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](dispatch-tools.md) 契约失效)”, perform the named open/edit/save/reopen event, and include its absent, malformed, and existing-file boundary where applicable. Code/store/API/Rust effect: at the Rust Agent/MCP dispatch, typed argument boundary, and any named Tauri/provider bridge, apply “Tell the model to pass shortened IDs back exactly without padding or guessing.” atomically in the Rust project/core layer, changing only the named project files and in-memory session state while preserving unrelated assets and metadata. Visible/returned assertion: assert the returned project/error variant, exact post-operation files and decoded state, and save-then-reopen equality or the specified fail-closed no-write result. Evidence required: after the deterministic test passes, record code:# and test:#; proposed concrete evidence is test:crates/opentake-agent/tests/completion_9bc7c9cdadaaa081.rs#completion_9bc7c9cdadaaa081_tell_the_model_to_pass_shortened_ids_back_exactl. +- Expected behavior: At docs/modules/opentake-agent/prompt.md:32 under “base.rs:分段 base 提示” (gap-marker), the source “- 短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](../../../modules/opentake-agent/dispatch-tools.md) 契约失效)” requires this exact behavior: Tell the model to pass shortened IDs back exactly without padding or guessing. +- Acceptance criteria: Source binding: docs/modules/opentake-agent/prompt.md:32; signal=gap-marker; heading=base.rs:分段 base 提示; candidate=- 短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](../../../modules/opentake-agent/dispatch-tools.md) 契约失效) Expected behavior: Tell the model to pass shortened IDs back exactly without padding or guessing. This closes only the promise expressed by “短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](../../../modules/opentake-agent/dispatch-tools.md) 契约失效)” in “base.rs:分段 base 提示”; adjacent headings or similarly named features remain independently adjudicated. Deterministic test: exercise “短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](../../../modules/opentake-agent/dispatch-tools.md) 契约失效)” with the scenario below and register test:crates/opentake-agent/tests/completion_9bc7c9cdadaaa081.rs#completion_9bc7c9cdadaaa081_tell_the_model_to_pass_shortened_ids_back_exactl Initial state/input/event: create an isolated temporary project fixture representing the source state in “短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](../../../modules/opentake-agent/dispatch-tools.md) 契约失效)”, perform the named open/edit/save/reopen event, and include its absent, malformed, and existing-file boundary where applicable. Code/store/API/Rust effect: at the Rust Agent/MCP dispatch, typed argument boundary, and any named Tauri/provider bridge, apply “Tell the model to pass shortened IDs back exactly without padding or guessing.” atomically in the Rust project/core layer, changing only the named project files and in-memory session state while preserving unrelated assets and metadata. Visible/returned assertion: assert the returned project/error variant, exact post-operation files and decoded state, and save-then-reopen equality or the specified fail-closed no-write result. Evidence required: after the deterministic test passes, record code:# and test:#; proposed concrete evidence is test:crates/opentake-agent/tests/completion_9bc7c9cdadaaa081.rs#completion_9bc7c9cdadaaa081_tell_the_model_to_pass_shortened_ids_back_exactl. ### requirement-58cdfb5cd8505898 diff --git a/docs/audit/2026-07-14/implementation-plans/agent-settings-generation-implementation.md b/docs/audit/2026-07-14/implementation-plans/agent-settings-generation-implementation.md index 81f8aca3..0a26d127 100644 --- a/docs/audit/2026-07-14/implementation-plans/agent-settings-generation-implementation.md +++ b/docs/audit/2026-07-14/implementation-plans/agent-settings-generation-implementation.md @@ -1881,13 +1881,13 @@ and frame-sequence sources remain separately scoped post-Beta work. #### requirement-b3c9690870310f25 - Candidate/source: `doc-9bc7c9cdadaaa081` at `docs/modules/opentake-agent/prompt.md:32` (requirement) -- Expected behavior: At docs/modules/opentake-agent/prompt.md:32 under “base.rs:分段 base 提示” (gap-marker), the source “- 短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](dispatch-tools.md) 契约失效)” requires this exact behavior: Tell the model to pass shortened IDs back exactly without padding or guessing. +- Expected behavior: At docs/modules/opentake-agent/prompt.md:32 under “base.rs:分段 base 提示” (gap-marker), the source “- 短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](../../../modules/opentake-agent/dispatch-tools.md) 契约失效)” requires this exact behavior: Tell the model to pass shortened IDs back exactly without padding or guessing. - Resolution: `validated-ledger-evidence:AG-short-id-prompt-contract` — Both slices cover one no-padding/no-guessing short-ID prompt and resolver contract with the same implementation and focused tests. - Exact acceptance contract: - - Source binding: docs/modules/opentake-agent/prompt.md:32; signal=gap-marker; heading=base.rs:分段 base 提示; candidate=- 短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](dispatch-tools.md) 契约失效) - - Expected behavior: Tell the model to pass shortened IDs back exactly without padding or guessing. This closes only the promise expressed by “短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](dispatch-tools.md) 契约失效)” in “base.rs:分段 base 提示”; adjacent headings or similarly named features remain independently adjudicated. - - Deterministic test: exercise “短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](dispatch-tools.md) 契约失效)” with the scenario below and register test:crates/opentake-agent/tests/completion_9bc7c9cdadaaa081.rs#completion_9bc7c9cdadaaa081_tell_the_model_to_pass_shortened_ids_back_exactl - - Initial state/input/event: create an isolated temporary project fixture representing the source state in “短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](dispatch-tools.md) 契约失效)”, perform the named open/edit/save/reopen event, and include its absent, malformed, and existing-file boundary where applicable. + - Source binding: docs/modules/opentake-agent/prompt.md:32; signal=gap-marker; heading=base.rs:分段 base 提示; candidate=- 短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](../../../modules/opentake-agent/dispatch-tools.md) 契约失效) + - Expected behavior: Tell the model to pass shortened IDs back exactly without padding or guessing. This closes only the promise expressed by “短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](../../../modules/opentake-agent/dispatch-tools.md) 契约失效)” in “base.rs:分段 base 提示”; adjacent headings or similarly named features remain independently adjudicated. + - Deterministic test: exercise “短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](../../../modules/opentake-agent/dispatch-tools.md) 契约失效)” with the scenario below and register test:crates/opentake-agent/tests/completion_9bc7c9cdadaaa081.rs#completion_9bc7c9cdadaaa081_tell_the_model_to_pass_shortened_ids_back_exactl + - Initial state/input/event: create an isolated temporary project fixture representing the source state in “短 id:`Pass them back exactly as given — never pad, complete, or guess a longer form.`(缺失则 [short_id](../../../modules/opentake-agent/dispatch-tools.md) 契约失效)”, perform the named open/edit/save/reopen event, and include its absent, malformed, and existing-file boundary where applicable. - Code/store/API/Rust effect: at the Rust Agent/MCP dispatch, typed argument boundary, and any named Tauri/provider bridge, apply “Tell the model to pass shortened IDs back exactly without padding or guessing.” atomically in the Rust project/core layer, changing only the named project files and in-memory session state while preserving unrelated assets and metadata. - Visible/returned assertion: assert the returned project/error variant, exact post-operation files and decoded state, and save-then-reopen equality or the specified fail-closed no-write result. - Evidence required: after the deterministic test passes, record code:# and test:#; proposed concrete evidence is test:crates/opentake-agent/tests/completion_9bc7c9cdadaaa081.rs#completion_9bc7c9cdadaaa081_tell_the_model_to_pass_shortened_ids_back_exactl. diff --git a/docs/audit/2026-07-14/implementation-plans/media-render-playback-export-design.md b/docs/audit/2026-07-14/implementation-plans/media-render-playback-export-design.md index d2b00671..eb083065 100644 --- a/docs/audit/2026-07-14/implementation-plans/media-render-playback-export-design.md +++ b/docs/audit/2026-07-14/implementation-plans/media-render-playback-export-design.md @@ -427,8 +427,8 @@ Close each record as the smallest end-to-end vertical slice while preserving Rus - Test ownership: - `crates/opentake-media/src/ff.rs#env_override_is_respected_for_ffmpeg` (existing-owned): Exact named test already exists in the reviewed owning runner and records current boundary behavior. - `crates/opentake-media/src/ff.rs#default_ffprobe_is_ffprobe` (existing-owned): Exact named test already exists in the reviewed owning runner and records current boundary behavior. -- Expected behavior: At docs/modules/opentake-media/OVERVIEW.md:55 under “FFmpeg sidecar 编解码(与 SPEC 的关键实现偏差)” (gap-marker), the source “**实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](probe-ff.md)。” requires this exact behavior: Use the ffmpeg/ffprobe sidecar boundary rather than a libav ABI binding. -- Acceptance criteria: Source binding: docs/modules/opentake-media/OVERVIEW.md:55; signal=gap-marker; heading=FFmpeg sidecar 编解码(与 SPEC 的关键实现偏差); candidate=**实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](probe-ff.md)。 Expected behavior: Use the ffmpeg/ffprobe sidecar boundary rather than a libav ABI binding. This closes only the promise expressed by “*实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](probe-ff.md)。” in “FFmpeg sidecar 编解码(与 SPEC 的关键实现偏差)”; adjacent headings or similarly named features remain independently adjudicated. Deterministic test: exercise “*实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](probe-ff.md)。” with the scenario below and register test:crates/opentake-project/tests/completion_ee5f3f6b2ccb9cc6.rs#completion_ee5f3f6b2ccb9cc6_use_the_ffmpeg_ffprobe_sidecar_boundary_rather_t Initial state/input/event: create an isolated temporary project fixture representing the source state in “*实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](probe-ff.md)。”, perform the named open/edit/save/reopen event, and include its absent, malformed, and existing-file boundary where applicable. Code/store/API/Rust effect: at the Rust media/render/playback pipeline and its typed preview/export adapter, apply “Use the ffmpeg/ffprobe sidecar boundary rather than a libav ABI binding.” atomically in the Rust project/core layer, changing only the named project files and in-memory session state while preserving unrelated assets and metadata. Visible/returned assertion: assert the returned project/error variant, exact post-operation files and decoded state, and save-then-reopen equality or the specified fail-closed no-write result. Evidence required: after the deterministic test passes, record code:# and test:#; proposed concrete evidence is test:crates/opentake-project/tests/completion_ee5f3f6b2ccb9cc6.rs#completion_ee5f3f6b2ccb9cc6_use_the_ffmpeg_ffprobe_sidecar_boundary_rather_t. +- Expected behavior: At docs/modules/opentake-media/OVERVIEW.md:55 under “FFmpeg sidecar 编解码(与 SPEC 的关键实现偏差)” (gap-marker), the source “**实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](../../../modules/opentake-media/SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](../../../modules/opentake-media/probe-ff.md)。” requires this exact behavior: Use the ffmpeg/ffprobe sidecar boundary rather than a libav ABI binding. +- Acceptance criteria: Source binding: docs/modules/opentake-media/OVERVIEW.md:55; signal=gap-marker; heading=FFmpeg sidecar 编解码(与 SPEC 的关键实现偏差); candidate=**实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](../../../modules/opentake-media/SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](../../../modules/opentake-media/probe-ff.md)。 Expected behavior: Use the ffmpeg/ffprobe sidecar boundary rather than a libav ABI binding. This closes only the promise expressed by “*实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](../../../modules/opentake-media/SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](../../../modules/opentake-media/probe-ff.md)。” in “FFmpeg sidecar 编解码(与 SPEC 的关键实现偏差)”; adjacent headings or similarly named features remain independently adjudicated. Deterministic test: exercise “*实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](../../../modules/opentake-media/SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](../../../modules/opentake-media/probe-ff.md)。” with the scenario below and register test:crates/opentake-project/tests/completion_ee5f3f6b2ccb9cc6.rs#completion_ee5f3f6b2ccb9cc6_use_the_ffmpeg_ffprobe_sidecar_boundary_rather_t Initial state/input/event: create an isolated temporary project fixture representing the source state in “*实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](../../../modules/opentake-media/SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](../../../modules/opentake-media/probe-ff.md)。”, perform the named open/edit/save/reopen event, and include its absent, malformed, and existing-file boundary where applicable. Code/store/API/Rust effect: at the Rust media/render/playback pipeline and its typed preview/export adapter, apply “Use the ffmpeg/ffprobe sidecar boundary rather than a libav ABI binding.” atomically in the Rust project/core layer, changing only the named project files and in-memory session state while preserving unrelated assets and metadata. Visible/returned assertion: assert the returned project/error variant, exact post-operation files and decoded state, and save-then-reopen equality or the specified fail-closed no-write result. Evidence required: after the deterministic test passes, record code:# and test:#; proposed concrete evidence is test:crates/opentake-project/tests/completion_ee5f3f6b2ccb9cc6.rs#completion_ee5f3f6b2ccb9cc6_use_the_ffmpeg_ffprobe_sidecar_boundary_rather_t. ### requirement-d7bd977b01924877 @@ -561,8 +561,8 @@ Close each record as the smallest end-to-end vertical slice while preserving Rus - Test ownership: - `crates/opentake-motion/src/sandbox.rs#document_size_ceiling_enforced` (existing-owned): Exact named test already exists in the reviewed owning runner and records current boundary behavior. - `crates/opentake-motion/src/renderer.rs#chromium_applies_sandbox_size_before_unavailable` (existing-owned): Exact named test already exists in the reviewed owning runner and records current boundary behavior. -- Expected behavior: At docs/modules/opentake-motion/sandbox.md:67 under “谁在调用” (gap-marker), the source “- `StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](renderer.md))。” requires this exact behavior: Apply sandbox document-size checks before both stub rendering and the unavailable Chromium path. -- Acceptance criteria: Source binding: docs/modules/opentake-motion/sandbox.md:67; signal=gap-marker; heading=谁在调用; candidate=- `StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](renderer.md))。 Expected behavior: Apply sandbox document-size checks before both stub rendering and the unavailable Chromium path. This closes only the promise expressed by “`StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](renderer.md))。” in “谁在调用”; adjacent headings or similarly named features remain independently adjudicated. Deterministic test: exercise “`StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](renderer.md))。” with the scenario below and register test:crates/opentake-project/tests/completion_3a71829a7b489aae.rs#completion_3a71829a7b489aae_apply_sandbox_document_size_checks_before_both_s Initial state/input/event: create an isolated temporary project fixture representing the source state in “`StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](renderer.md))。”, perform the named open/edit/save/reopen event, and include its absent, malformed, and existing-file boundary where applicable. Code/store/API/Rust effect: at the Rust media/render/playback pipeline and its typed preview/export adapter, apply “Apply sandbox document-size checks before both stub rendering and the unavailable Chromium path.” atomically in the Rust project/core layer, changing only the named project files and in-memory session state while preserving unrelated assets and metadata. Visible/returned assertion: assert the returned project/error variant, exact post-operation files and decoded state, and save-then-reopen equality or the specified fail-closed no-write result. Evidence required: after the deterministic test passes, record code:# and test:#; proposed concrete evidence is test:crates/opentake-project/tests/completion_3a71829a7b489aae.rs#completion_3a71829a7b489aae_apply_sandbox_document_size_checks_before_both_s. +- Expected behavior: At docs/modules/opentake-motion/sandbox.md:67 under “谁在调用” (gap-marker), the source “- `StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](../../../modules/opentake-motion/renderer.md))。” requires this exact behavior: Apply sandbox document-size checks before both stub rendering and the unavailable Chromium path. +- Acceptance criteria: Source binding: docs/modules/opentake-motion/sandbox.md:67; signal=gap-marker; heading=谁在调用; candidate=- `StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](../../../modules/opentake-motion/renderer.md))。 Expected behavior: Apply sandbox document-size checks before both stub rendering and the unavailable Chromium path. This closes only the promise expressed by “`StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](../../../modules/opentake-motion/renderer.md))。” in “谁在调用”; adjacent headings or similarly named features remain independently adjudicated. Deterministic test: exercise “`StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](../../../modules/opentake-motion/renderer.md))。” with the scenario below and register test:crates/opentake-project/tests/completion_3a71829a7b489aae.rs#completion_3a71829a7b489aae_apply_sandbox_document_size_checks_before_both_s Initial state/input/event: create an isolated temporary project fixture representing the source state in “`StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](../../../modules/opentake-motion/renderer.md))。”, perform the named open/edit/save/reopen event, and include its absent, malformed, and existing-file boundary where applicable. Code/store/API/Rust effect: at the Rust media/render/playback pipeline and its typed preview/export adapter, apply “Apply sandbox document-size checks before both stub rendering and the unavailable Chromium path.” atomically in the Rust project/core layer, changing only the named project files and in-memory session state while preserving unrelated assets and metadata. Visible/returned assertion: assert the returned project/error variant, exact post-operation files and decoded state, and save-then-reopen equality or the specified fail-closed no-write result. Evidence required: after the deterministic test passes, record code:# and test:#; proposed concrete evidence is test:crates/opentake-project/tests/completion_3a71829a7b489aae.rs#completion_3a71829a7b489aae_apply_sandbox_document_size_checks_before_both_s. ### requirement-3c0728a9184da517 diff --git a/docs/audit/2026-07-14/implementation-plans/media-render-playback-export-implementation.md b/docs/audit/2026-07-14/implementation-plans/media-render-playback-export-implementation.md index 5bddd852..d2c2e1e4 100644 --- a/docs/audit/2026-07-14/implementation-plans/media-render-playback-export-implementation.md +++ b/docs/audit/2026-07-14/implementation-plans/media-render-playback-export-implementation.md @@ -1341,13 +1341,13 @@ preceding unchanged-code workspace gate passed in Task16. #### requirement-e71abff7d9b41127 - Candidate/source: `doc-ee5f3f6b2ccb9cc6` at `docs/modules/opentake-media/OVERVIEW.md:55` (requirement) -- Expected behavior: At docs/modules/opentake-media/OVERVIEW.md:55 under “FFmpeg sidecar 编解码(与 SPEC 的关键实现偏差)” (gap-marker), the source “**实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](probe-ff.md)。” requires this exact behavior: Use the ffmpeg/ffprobe sidecar boundary rather than a libav ABI binding. +- Expected behavior: At docs/modules/opentake-media/OVERVIEW.md:55 under “FFmpeg sidecar 编解码(与 SPEC 的关键实现偏差)” (gap-marker), the source “**实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](../../../modules/opentake-media/SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](../../../modules/opentake-media/probe-ff.md)。” requires this exact behavior: Use the ffmpeg/ffprobe sidecar boundary rather than a libav ABI binding. - Resolution: `reviewed-mapping-report:MR-cli-sidecar-boundary-complete` — The media crate deliberately uses ffmpeg-sidecar and does not link a libav ABI binding. - Exact acceptance contract: - - Source binding: docs/modules/opentake-media/OVERVIEW.md:55; signal=gap-marker; heading=FFmpeg sidecar 编解码(与 SPEC 的关键实现偏差); candidate=**实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](probe-ff.md)。 - - Expected behavior: Use the ffmpeg/ffprobe sidecar boundary rather than a libav ABI binding. This closes only the promise expressed by “*实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](probe-ff.md)。” in “FFmpeg sidecar 编解码(与 SPEC 的关键实现偏差)”; adjacent headings or similarly named features remain independently adjudicated. - - Deterministic test: exercise “*实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](probe-ff.md)。” with the scenario below and register test:crates/opentake-project/tests/completion_ee5f3f6b2ccb9cc6.rs#completion_ee5f3f6b2ccb9cc6_use_the_ffmpeg_ffprobe_sidecar_boundary_rather_t - - Initial state/input/event: create an isolated temporary project fixture representing the source state in “*实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](probe-ff.md)。”, perform the named open/edit/save/reopen event, and include its absent, malformed, and existing-file boundary where applicable. + - Source binding: docs/modules/opentake-media/OVERVIEW.md:55; signal=gap-marker; heading=FFmpeg sidecar 编解码(与 SPEC 的关键实现偏差); candidate=**实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](../../../modules/opentake-media/SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](../../../modules/opentake-media/probe-ff.md)。 + - Expected behavior: Use the ffmpeg/ffprobe sidecar boundary rather than a libav ABI binding. This closes only the promise expressed by “*实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](../../../modules/opentake-media/SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](../../../modules/opentake-media/probe-ff.md)。” in “FFmpeg sidecar 编解码(与 SPEC 的关键实现偏差)”; adjacent headings or similarly named features remain independently adjudicated. + - Deterministic test: exercise “*实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](../../../modules/opentake-media/SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](../../../modules/opentake-media/probe-ff.md)。” with the scenario below and register test:crates/opentake-project/tests/completion_ee5f3f6b2ccb9cc6.rs#completion_ee5f3f6b2ccb9cc6_use_the_ffmpeg_ffprobe_sidecar_boundary_rather_t + - Initial state/input/event: create an isolated temporary project fixture representing the source state in “*实现走 `ffmpeg` / `ffprobe` 命令行二进制(ffmpeg-sidecar),不链接 `libav*`。** 原因:本机工具链为 ffmpeg 8.1(libavcodec 62),C 绑定 crate(`ffmpeg-next` / `ffmpeg-the-third`)不支持,且 `pkg-config` 缺失。`ff.rs` 封装二进制发现与一次性 ffprobe JSON 查询,上层解码模块用裸 stdin/stdout 管道交换原始像素/PCM。这与多处架构文档([ARCHITECTURE.md](../../../architecture/ARCHITECTURE.md) §1、[ROADMAP.md](../../../architecture/ROADMAP.md) Phase 2、本模块 [SPEC.md](../../../modules/opentake-media/SPEC.md) §1.2 仍写 `ffmpeg-next`)存在偏差——以代码为准,详见 [probe-ff.md](../../../modules/opentake-media/probe-ff.md)。”, perform the named open/edit/save/reopen event, and include its absent, malformed, and existing-file boundary where applicable. - Code/store/API/Rust effect: at the Rust media/render/playback pipeline and its typed preview/export adapter, apply “Use the ffmpeg/ffprobe sidecar boundary rather than a libav ABI binding.” atomically in the Rust project/core layer, changing only the named project files and in-memory session state while preserving unrelated assets and metadata. - Visible/returned assertion: assert the returned project/error variant, exact post-operation files and decoded state, and save-then-reopen equality or the specified fail-closed no-write result. - Evidence required: after the deterministic test passes, record code:# and test:#; proposed concrete evidence is test:crates/opentake-project/tests/completion_ee5f3f6b2ccb9cc6.rs#completion_ee5f3f6b2ccb9cc6_use_the_ffmpeg_ffprobe_sidecar_boundary_rather_t. @@ -1691,13 +1691,13 @@ not the separately owned desktop motion/Lottie materialization. #### requirement-69723d1c0bd77577 - Candidate/source: `doc-3a71829a7b489aae` at `docs/modules/opentake-motion/sandbox.md:67` (requirement) -- Expected behavior: At docs/modules/opentake-motion/sandbox.md:67 under “谁在调用” (gap-marker), the source “- `StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](renderer.md))。” requires this exact behavior: Apply sandbox document-size checks before both stub rendering and the unavailable Chromium path. +- Expected behavior: At docs/modules/opentake-motion/sandbox.md:67 under “谁在调用” (gap-marker), the source “- `StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](../../../modules/opentake-motion/renderer.md))。” requires this exact behavior: Apply sandbox document-size checks before both stub rendering and the unavailable Chromium path. - Resolution: `reviewed-mapping-report:MR-motion-sandbox-complete` — Both stub and unavailable Chromium paths enforce document size before rendering or returning unavailable. - Exact acceptance contract: - - Source binding: docs/modules/opentake-motion/sandbox.md:67; signal=gap-marker; heading=谁在调用; candidate=- `StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](renderer.md))。 - - Expected behavior: Apply sandbox document-size checks before both stub rendering and the unavailable Chromium path. This closes only the promise expressed by “`StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](renderer.md))。” in “谁在调用”; adjacent headings or similarly named features remain independently adjudicated. - - Deterministic test: exercise “`StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](renderer.md))。” with the scenario below and register test:crates/opentake-project/tests/completion_3a71829a7b489aae.rs#completion_3a71829a7b489aae_apply_sandbox_document_size_checks_before_both_s - - Initial state/input/event: create an isolated temporary project fixture representing the source state in “`StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](renderer.md))。”, perform the named open/edit/save/reopen event, and include its absent, malformed, and existing-file boundary where applicable. + - Source binding: docs/modules/opentake-motion/sandbox.md:67; signal=gap-marker; heading=谁在调用; candidate=- `StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](../../../modules/opentake-motion/renderer.md))。 + - Expected behavior: Apply sandbox document-size checks before both stub rendering and the unavailable Chromium path. This closes only the promise expressed by “`StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](../../../modules/opentake-motion/renderer.md))。” in “谁在调用”; adjacent headings or similarly named features remain independently adjudicated. + - Deterministic test: exercise “`StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](../../../modules/opentake-motion/renderer.md))。” with the scenario below and register test:crates/opentake-project/tests/completion_3a71829a7b489aae.rs#completion_3a71829a7b489aae_apply_sandbox_document_size_checks_before_both_s + - Initial state/input/event: create an isolated temporary project fixture representing the source state in “`StubRenderer` 与 `HeadlessChromiumRenderer` 都在 `render()` 里对 `MotionSource::Code` 调 `check_document_size`——连 stub 与 "renderer unavailable" 路径都不放过(见 [renderer.md](../../../modules/opentake-motion/renderer.md))。”, perform the named open/edit/save/reopen event, and include its absent, malformed, and existing-file boundary where applicable. - Code/store/API/Rust effect: at the Rust media/render/playback pipeline and its typed preview/export adapter, apply “Apply sandbox document-size checks before both stub rendering and the unavailable Chromium path.” atomically in the Rust project/core layer, changing only the named project files and in-memory session state while preserving unrelated assets and metadata. - Visible/returned assertion: assert the returned project/error variant, exact post-operation files and decoded state, and save-then-reopen equality or the specified fail-closed no-write result. - Evidence required: after the deterministic test passes, record code:# and test:#; proposed concrete evidence is test:crates/opentake-project/tests/completion_3a71829a7b489aae.rs#completion_3a71829a7b489aae_apply_sandbox_document_size_checks_before_both_s. diff --git a/docs/audit/2026-09-06/public-beta-validation.md b/docs/audit/2026-09-06/public-beta-validation.md new file mode 100644 index 00000000..d1abe5e8 --- /dev/null +++ b/docs/audit/2026-09-06/public-beta-validation.md @@ -0,0 +1,43 @@ +--- +status: draft +content_stage: partial-implementation +updated: 2026-09-06 +--- + +# Public Beta 候选验证记录 + +本记录只报告本轮实际执行结果。候选基线为 `33ee8e2` 加本任务改动,分支 `release/v1.0.0-beta.6`,最终 commit 尚未产生。 + +## 环境 + +macOS ARM64;Node 26.7.0、pnpm 10.33.4。默认 `/usr/bin/python3` 缺少 `tomllib`,脚本改用 Codex bundled Python。初始 `web/node_modules` 缺失,已按锁文件安装。Rust工具链与构建日志保存在 `output/public-beta-2026-09-06/`。 + +## 当前结果 + +| Gate | 本轮结果 | +|---|---| +| Rust fmt | 通过 | +| Rust workspace clippy | 修复 constant chunks/manual contains 后通过,exit 0;第三方 `block 0.1.6` 有 future-incompat notice | +| Rust workspace tests | 本轮首个完整结果:80组、2885通过、0失败、10个显式ignored;之后的搜索恢复及有界排队修复18项定向测试通过,最终汇总待更新 | +| Rust minimal clippy | 无默认feature构建检查通过 | +| Web tests/build | 最终整合后153files/1462tests通过,生产构建通过;保留既有chunk/dynamic-import warnings | +| 发布/Windows workflow结构 | Beta 6 identity 更新后通过;只修改version/WiX/notes入口及对应现有合同摘要,历史Beta4恢复链不变 | +| 脚本单测 | Beta6更新后209/209通过 | +| sidecar provisioner 单测 | 7/7 通过 | +| C1B/打包sidecar | CI/evidence Ruby合同及macOS内置FFmpeg实际执行通过 | +| Motion Canvas | 2/2 测试、123项许可证记录、构建通过;runner.html与锁定输出一致 | +| Motion dependency audit | xmldom升级后0项漏洞 | +| Web dependency audit | nanoid升级后0项已知漏洞 | +| 真实媒体集成 | FFmpeg导出、Motion命令/合成、播放传输全通过;严格真实音频探针 frames=83、playhead=90、clock=audio、非黑比例1.000、neon-green=0.000 | +| 真实Keychain/MCP | 2项通过:鉴权、Host/Origin、重启/撤销、端口冲突、禁用/退出清理;敏感token未进入日志 | +| 新安装包GUI | macOS release app构建中,原生GUI待执行 | +| 宣传视频 | 独立GPT-6交付60s/1080p/30fps/1800帧H.264+AAC,主代理ffprobe、封面/联系表及全片解码通过;当前为注明日期的历史实拍剪辑,不算新包GUI证据 | +| 远端CI/Release | 未执行、未发布 | + +## 缺口与证据边界 + +- 语义搜索manifest/模型链路已完成并通过实际Rust推理;模型v2索引过滤、同尺寸损坏修复路径和真实下载大小正在独立复核后的收尾。Windows ort-tract真实运行发现线程配置未实现及动态shape推理错误,正在修复,尚未达发布条件。 +- 既有安装包可读取AX但窗口截图全黑;系统`IOConsoleLocked=No`,Raise及窗口zoom后仍黑。尚不能区分应用显示与窗口采集问题,已向用户异步询问可见状态;不能把AX按钮存在当成视觉通过。 +- 历史 GUI 记录不自动继承为当前候选的稳定性证明。 +- 用户已有 audit 工程、截图删除和视频素材改动均保留;本轮使用独立输出目录。 +- [执行计划](../../plans/active/2026-09-06-public-beta.md)与[候选说明](../../releases/1.0.0-beta.6.md)持续写回。 diff --git a/docs/audit/2026-09-06/search-model-recovery.md b/docs/audit/2026-09-06/search-model-recovery.md new file mode 100644 index 00000000..1a942512 --- /dev/null +++ b/docs/audit/2026-09-06/search-model-recovery.md @@ -0,0 +1,69 @@ +# 语义搜索模型恢复与下载大小提示 + +> 状态:generated · 阶段:implementation-backed · 日期:2026-09-06 +> 工作树:OpenTake-generation · 分支:release/v1.0.0-beta.6 +> 范围:Web 搜索界面与 IPC 类型接线;原生模型、下载与 worker 验证由主线负责。 + +## 目标与契约 + +本切片修复首次模型下载大小占位、损坏模型缺少修复入口,以及修复成功后当前查询不刷新的问题。保留现有搜索 hook、共享操作 token、Rust 只读镜像与 Files/Spoken 独立结果组。 + +主线确认的契约: + +- `SearchResults.visualError?: string | null` 向后兼容;未安装模型继续空 Moments 且无 visualError。 +- 模型验证损坏使用稳定 marker `SEARCH_MODEL_REPAIR_REQUIRED:`。查询返回直接前缀,索引 rejection 可能由 WorkerError 包装为 `model error: SEARCH_MODEL_REPAIR_REQUIRED: ...`,因此 Web 在整个错误消息中查找 marker。 +- 普通推理/索引错误不带该 marker,不能误导用户重新下载模型。 +- `searchModelStatus().bytes` 是 manifest 下载文件合计;成功 DTO 与现有 `downloadSearchModel`、`searchIndexStart(epoch, path)`、`searchQuery(query)` 调用约定不变。 +- Tauri 查询/索引等待移出 UI 主线程、复用 bounded ORT worker 属于主线 Rust 工作,不在本切片修改。 + +## 实现 + +1. 读取既有 `searchModelStatus()`,下载 tooltip 使用实际合计按十进制 MB/GB 展示。1535824768 bytes 显示 `1.54 GB`。等待状态、零值、无效数值或状态读取失败时显示“暂时无法确定下载大小”,移除 380 MB 默认值。 +2. 索引操作结算事件保留错误文字。含修复 marker 的错误导向“修复并重新下载模型”并调用 `downloadSearchModel`;普通索引失败显示实际错误,使用索引重试说明并继续调用 `searchIndexStart`。 +3. 消费可选 visualError。即使所有素材已经建立索引,损坏查询也显示修复说明及入口;普通视觉推理错误显示失败提示。Files 和 Spoken 继续渲染,不把视觉失败显示为无匹配结果。 +4. 同工程下载/索引成功后沿现有刷新通道读取最新索引状态,并递增查询 revision,重跑此时的查询文字;新查询仍使用 250ms debounce、请求序列与取消保护,成功后移除旧视觉错误。下载完成后若仍需建立索引,继续显示原“建立索引”按钮,不新增自动索引流程。 +5. 保留共享下载/索引 operation token,重复修复点击不会重复下载。同工程卸载/重挂载可继续显示同一 pending 操作并接收完成通知;卸载后无订阅者时不发起刷新/查询。旧工程操作完成可使当前工程重新读取权威状态(原有行为),但不会重跑新工程查询、套用旧失败或自动建立索引。 +6. manifest 大小返回、查询返回和操作后的查询重跑均检查现有生命周期/工程身份。修复时的新下载错误不会被旧 visualError 遮盖,网络失败可见且能重试下载。 + +## Read Set 与写集 + +已核对根 AGENTS.md、`docs/project/conventions.md`、既有 Web 路由与 MediaSearch hook/测试、SearchResults/SearchModelStatus/SearchIndexStatus 类型,以及 api.ts 中对应只读契约。只读参考上游 `MediaPanel/MediaTab/MediaTab+IndexStatus.swift`;核对主线新增 Rust visual_error DTO,仅阅读未修改。 + +本切片实际写入: + +- `web/src/components/media/MediaSearch.tsx` +- `web/src/components/media/MediaSearch.test.ts` +- `web/src/lib/types.ts`(仅 SearchResults 的可选字段与说明) +- `web/src/i18n/dict.ts`(搜索恢复所需中英文文案,保留既有贴纸内容) +- 本记录 + +Sticker 组件及其测试保持冻结。没有修改 Rust、API 调用参数、依赖、测试配置、版本或其他 MD,没有 commit/push。 + +## 验证证据 + +TDD 过程: + +- 最初新增 9 项用例均在旧实现失败,包括真实大小/未知大小、损坏索引修复、普通索引重试、完整索引下的 query 损坏与结果保留、操作完成重查及工程边界;最小接线后通过。 +- 增补卸载/重挂载、卸载后无刷新、迟到旧查询与下载失败可见性用例;“旧 query 损坏错误遮盖新下载失败”先失败,修正显示优先级后通过。 +- 收到主线 WorkerError 包装补充后,增加包装 marker 用例,先确认 startsWith 判断失败,再改为 includes 后通过。 + +最终执行: + +```bash +pnpm --dir web test --run src/components/media/MediaSearch.test.ts src/components/media/MediaPanel.test.tsx +pnpm --dir web build +git diff --check -- web/src/components/media/MediaSearch.tsx web/src/components/media/MediaSearch.test.ts web/src/lib/types.ts web/src/i18n/dict.ts +``` + +结果:2 个测试文件 / 76 项通过;其中 MediaSearch 29 项(原 15 项 + 新增 14 项)。TypeScript 与 Vite 构建通过。构建仍有动态/静态 import 无法分块和大 chunk 的既有告警。本任务没有运行全量测试或 Rust 测试。 + +测试使用真实 React 搜索面板与 Zustand,替换外部媒体 API 和下载/索引 promise,以验证用户动作、显示、请求次数及异步隔离。没有下载真实 1.5GB 模型,也没有运行原生 GUI,不能据此声明主线 worker 或真模型恢复验收通过。 + +## 主线原生待验收 + +1. 模型未安装时下载提示来自 manifest 合计,下载/索引期间 UI 响应正常。 +2. 同尺寸损坏模型:索引 worker 包装错误与完整索引下 query 校验错误均显示修复;Files/Spoken 保留。 +3. 修复下载成功后当前查询自动重跑;仍缺索引时按现有按钮建立索引,再自动重查;恢复后的画面结果正确,错误提示消失。 +4. 普通推理错误不触发重新下载;网络失败给出实际错误和下载重试;快速重复点击、跨项目以及切走再回的操作不会重复提交或显示旧工程错误。 + +这些 GUI/真实模型场景仍待主线验收;本记录不表示 Beta 6 已发布。 diff --git a/docs/audit/2026-09-06/semantic-search-model.md b/docs/audit/2026-09-06/semantic-search-model.md new file mode 100644 index 00000000..62abd7d8 --- /dev/null +++ b/docs/audit/2026-09-06/semantic-search-model.md @@ -0,0 +1,194 @@ +--- +status: canonical +stage: final-verified +updated_at: 2026-09-06 +--- + +# 语义搜索模型首次安装修复 + +## 目标与写入范围 + +完成真实可下载、固定版本校验、离线安装、真实图文 embedding 与语义排名。保留主线已有 `embedder.rs` 的 `as_chunks` 修改。不提交、推送、发布,不修改其他 Markdown、marketing 或 output。 + +代码改动:`crates/opentake-media/src/search/{config,model_download,tokenizer,ort_embedder}.rs`、`src-tauri/src/search.rs`。其中 `ort_embedder.rs` 是调查后提前告知的必要新增写入范围。知识来源仅写 [模型知识记录](../../knowledge/2026-09-06-semantic-search-model.md),详细模型 SHA/长度事实统一在那里维护。 + +读取范围:工作区和项目 AGENTS、docs/INDEX、opentake-media 和 src-tauri 的 OVERVIEW/INDEX、semantic-search 模块页、对应 Rust 文件,以及只读上游 `Search/SearchIndexConfig.swift`、`Search/Models/*.swift`、`Search/Query/VisualSearch.swift`。未修改上游。 + +## 根因与修复 + +1. 原 URL 指向未托管的 OpenTake ONNX 仓库,3 个 SHA-256 为空、长度为零。改用可信 ONNX Community 对应 Google 原模型的固定 revision FP32 资产,3 个文件共 1,535,824,768 字节,匿名下载后逐一验证通过。 +2. 真实资产使用 `onnx/` 子目录和原始 `tokenizer.json`;旧下载器只支持根路径及 `tokenizer.zip`。现支持相对路径、原始 JSON,保留旧 zip 兼容性,不增加依赖。 +3. 默认 I/O 名仍是 CoreML 的 `image/tokens/embedding`。改为真实图的 `pixel_values/input_ids/pooler_output`。 +4. 原归一化分支为空操作;真实池化输出未归一化。现校验维度、有限值、非零范数,并按 `spec.normalized == false` 做 L2 归一化。 +5. 原 tokenizer 编码后硬截断会丢失长查询末尾 EOS。现让 tokenizer 在特殊 token 后处理前执行长度限制,再按 0 右填到 64。 +6. 原 `installed` 只判断三个路径存在,错误或部分文件也能被当作已安装。现要求与当前 manifest 相同的安装回执和准确尺寸;Tauri 加载前重新验证实际 SHA-256。安装目录与索引版本升至 2,使 v1 索引自动失效。 + +安装使用模型目录内的临时目录,下载结束逐个验证长度和 SHA-256,所有文件准备好后再发布整个目录;临时目录 RAII 清理覆盖请求失败和取消。替换旧目录失败时尝试恢复原目录。进度只在安装成功后到 1.0,超出 manifest 预期大小即拒绝写入更多内容。 + +## 实际外部证据 + +- 获取时间:2026-09-06;固定 revision 与逐个校验值见知识记录。Python 使用公开 HTTPS 请求,不读取 Hugging Face 用户配置或凭据。 +- Python ONNX Runtime 1.23.2 CPU 加载两个真实图成功,不需要外部 `.onnx_data` 文件。 +- 视觉输入 `(1,3,256,256)`,文本输入 `(1,64)`,二者 `pooler_output` 均为 `(1,768)`。 +- 未归一化输出范数:全零像素张量约 14.08375,示例文本约 24.39208。归一化后再排名,不能将裸点积当余弦。 +- 图片来源:[猫图](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/coco_sample.png)、[鹦鹉图](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/hub/parrots.png),只放任务临时目录,不加入仓库。 + +Python 真实图文余弦分数: + +| 查询 | 猫图 | 鹦鹉图 | 预期 | +|---|---:|---:|---| +| a photo of two cats on a couch | 0.155847 | -0.012872 | 猫图第一 | +| a photo of colorful parrots | -0.004647 | 0.133400 | 鹦鹉图第一 | +| 沙发上的两只猫 | 0.115520 | 0.013697 | 猫图第一 | +| 彩色鹦鹉 | -0.043940 | 0.115521 | 鹦鹉图第一 | +| a photo of an airplane | -0.018885 | 0.006155 | 均低于 0.05 门槛 | + +## Rust 验证与复现 + +配置修复前,针对公开 API 数据的 manifest 回归检查失败,原因是缺少固定 revision。修复后通过。Tokenizer 长序列回归在修改前实际失败:`[2,2,2,2]`,期望 `[2,2,2,1]`;修改后通过。 + +为避免争用主线 Cargo,先使用 `/tmp` 中的 `rustc --test` harness 引用实际模块并只读复用已编译依赖。最初 harness 误选 reqwest 0.13(项目用 0.12)导致 TLS provider 初始化失败;按项目 0.12 重新链接后 34 项通过。这是测试装配错误,没有为其修改产品依赖或添加 TLS 绕过。 + +主线随后明确释放 Cargo 锁,执行正式命令: + +```sh +cargo test -p opentake-media --features model-download,ort-backend --lib search:: -- --test-threads=1 +``` + +最终结果:84 passed、0 failed、1 ignored(真实资产测试默认 opt-in);包括模型下载、HTTP 404/截断/超长/错误 hash、离线安装、回执、实际字节损坏拒绝、tokenizer 与排名/存储现有回归。 + +真实模型 Cargo 复现命令(本轮真实模型实际使用下述独立 Rust harness 执行;此命令供标准 Cargo 重跑,无下载、无凭据): + +```sh +OPENTAKE_SEARCH_MODEL_TEST_DIR=/tmp/opentake-semantic-model \ + cargo test -p opentake-media --features model-download,ort-backend --lib \ + real_model_offline_install_embeddings_and_ranking -- --ignored --nocapture --test-threads=1 +``` + +该目录应包含 `onnx/vision_model.onnx`、`onnx/text_model.onnx`、`tokenizer.json`、`cats.png`、`parrots.png`。测试从固定 manifest 校验离线来源,安装至临时模型目录,以产品 OrtEmbedder 加载,运行实际图片与中英文查询,断言 768 维/有限值/单位范数,将图像 embedding 经 PALMEMB1 f16 编码解码,再运行产品 ranker 并检查五组结果。 + +真实 Rust 模块回归已完成:`/tmp/opentake-semantic-model/rust_tests.py` 用 `rustc --test` 引用本工作树实际 `config/embedder/tokenizer/model_download/ort_embedder/embed_store/ranker` 及依赖模块,启用 `model-download` 与 `ort-backend`,连接项目已编译的 `ort 2.0.0-rc.11` 原生运行库。macOS 初始化函数与库内一致为空函数。结果 **72 passed、0 failed、0 ignored**,包含真实模型 opt-in 测试,耗时 301.84 秒(debug 构建对模型源、暂存副本、安装文件的重复 SHA-256 校验占主要时间)。没有以 Python 推理代替 Rust 实测,也没有为测试关闭 checksum。 + +产品 Rust 路径经历真实图像预处理、768 维向量归一化、f16 索引编码解码和 ranker 门槛后的结果: + +| 查询 | 第一名 | 分数 | +|---|---|---:| +| a photo of two cats on a couch | cats | 0.15572338 | +| a photo of colorful parrots | parrots | 0.13344961 | +| 沙发上的两只猫 | cats | 0.11564892 | +| 彩色鹦鹉 | parrots | 0.11663653 | +| a photo of an airplane | 无结果 | 所有候选低于门槛 | + +Tauri 正式验证: + +```sh +cargo test -p opentake-tauri --lib search:: --jobs 1 -- --test-threads=1 +``` + +结果 **14 passed、0 failed**;跨 crate 的 `verify_installed` 已正常编译链接。原主线编译中间态错误不再出现。仅有已有依赖 `block 0.1.6` 的未来 Rust 兼容性提示。 + +最后执行受限文件 `rustfmt --check` 与 `git diff --check`,均通过。主线原有 `embedder.rs` 的 `as_chunks` 改动保留,本任务未再编辑该文件。Cargo 本轮完成后已明确通知主线继续。 + +本地证据保留在 `/tmp/opentake-semantic-model/`:`api.json`、三个原始模型文件、两张公开图片、`python-evidence.json`、`rust-red.log`、`rust-unit.log`、`rust-real.log`、`cargo-search.log`、`tauri-tests.log` 及复现脚本。它们不加入 Git;真实测试生成的安装副本已随 tempfile 清理。 + +## 离线使用方式 + +- 用户可在可联网机器完成一次应用内模型下载,然后将完整 `siglip2-base-patch16-256-v2/` 目录复制至离线机器的应用 `Models` 目录(`MediaEngine::models_dir()`)。目录包含两个 ONNX、`tokenizer/tokenizer.json`、`spec.json`、`manifest.json`。应用识别回执和尺寸,并在推理加载前重新验 hash;不需要联网或账号。 +- 对预先下载的原始资产,调用公开 Rust 函数 `model_download::install_from_directory(models_dir, &config::manifest(), source_dir)`。它读取上述三个 repository-relative 文件,验证源和暂存副本,再创建同一安装回执。真实模型 opt-in 测试覆盖此路径。 +- 没有增加离线导入 UI 或额外 Tauri 命令;也没有新增前端类型或注册项。不可只放几个未校验的空文件伪造“已安装”。 + +## 剩余边界 + +- 此记录验证本任务纵向链路,不宣称 workspace、完整 UI、发布包、Windows ort-tract 已通过;主线仍负责公开 Beta 发布门槛。 +- Hugging Face 在受限网络可能不可达,应用返回请求错误,离线复制/导入保留相同校验。 +- 模型净资产约 1.43 GiB;离线安装测试需要源与安装目录同时存在,逻辑峰值约 2.86 GiB,临时安装目录测试结束后清理。未下载整个模型仓库。 +- 加载前重新计算两个大 ONNX 的 SHA-256 有 CPU/IO 成本;快速状态查询只读回执和尺寸。原有查询构造新 embedder 的行为未在本任务扩为跨请求缓存。 + +## 2026-09-06 独立 ort-tract 资格验证:失败,阻塞当前 Windows 语义搜索 + +获取/执行时间:2026-09-06T06:58:44Z 起完成汇总。范围仅为临时 harness 和本节审计;冻结产品代码没有在本轮修改,主 workspace target 未使用。主线另行处理查询缓存版本过滤、模型失败恢复与下载大小显示,这些修改不属于本次后端结论。 + +### 环境与可重复性 + +- 实际宿主:macOS ARM64,16 GiB RAM,`rustc 1.97.1 (8bab26f4f 2026-07-14)`。 +- 锁定后端:`ort 2.0.0-rc.11`、`ort-sys 2.0.0-rc.11`、`ort-tract 0.2.0+0.22`、`tract-onnx/tract-core 0.22.3`。 +- 临时 Cargo.lock 的所有 registry 包版本及 checksum 均与主 workspace 当时锁文件一致,差异列表为空,证据 `lock-comparison.json`。本地原先没有 tract 源码,只获取构建该后端所需的锁定 Rust 依赖。 +- Cargo 实际 feature:`ort = [alternative-backend, ndarray, std]`;`ort-sys = [disable-linking, std]`;没有启用 native ONNX Runtime 的 download-binaries、copy-dylibs 或 TLS feature。测试中 `ort::set_api(ort_tract::api())` 断言成功。 +- 复制产品相关模块到 `/tmp/opentake-tract-qualification/src/imported/` 作为稳定快照,复用实际 `OrtEmbedder`、`Embedder` trait、预处理、tokenizer、f16 index 与 ranker。初始化函数逐句使用产品 Windows 分支的 `Once + ort::set_api`,没有人为设置 `cfg windows`。 +- ort-tract 可以在 macOS 原生编译。其 `api.rs:66-72` 的 Windows cfg 只分支文件路径 UTF-16 解码;后续模型解析、类型推导和执行走共同代码。本次结果是相同后端的兼容性预检,**不是 Windows OS/MSVC 实机通过证据**。 +- 模型与两张图继续使用 `/tmp/opentake-semantic-model/` 中前轮已校验的同一批文件。未额外下载或重写模型、未固化 ONNX 维度、未关闭 shape/finite/dim/normalization/ranking 断言。 + +临时构建: + +```sh +CARGO_TARGET_DIR=/tmp/opentake-tract-qualification/target \ + cargo build --manifest-path /tmp/opentake-tract-qualification/Cargo.toml \ + --locked --offline --jobs 1 +``` + +最终构建成功。临时 harness 的初版日志曾误用 ort Session 私有字段,改为锁定 API 的 `inputs()` / `outputs()` 后通过;修正仅发生在 `/tmp`。为节省资源使用 `debug=0`、`incremental=false`、`opt-level=1`、单编译任务。所有临时构建/日志约 731 MiB;本轮开始可用磁盘约 25 GiB,结束约 23 GiB。前轮模型目录仍约 1.7 GiB,没有模型复制增量。 + +### 三个真实运行阻塞 + +| 路径 | 实际失败阶段及结果 | 判定 | +|---|---|---| +| 原样产品 OrtEmbedder → Windows init → tract | `model install: ort threads: Unimplemented`;图像、文本独立入口也均返回 `Unimplemented`,进程退出码 1 | 模型尚未加载即失败,当前 Windows 产品路径必需先处理 | +| 临时诊断模式仅省略线程数量性能选项,加载原图像图 | `Failed to parse model: Failed analyse for node #232 "/vision_model/embeddings/patch_embedding/Conv" ConvHir`,退出码 1 | 即使处理线程选项,原图像图仍无法加载 | +| 同一临时诊断模式加载原文本图并用真实 tokenizer 输入 `(1,64)` | Session 加载成功,执行失败:`Failed to run session: Evaluating #11 "/text_model/embeddings/Slice" StridedSlice`,退出码 1 | 原文本图无法输出 embedding | + +线程选项的具体源码链: + +- 产品 `crates/opentake-media/src/search/ort_embedder.rs::build_session` 调用 `.with_intra_threads(...)`。 +- 锁定 crate `ort-tract-0.2.0+0.22/api.rs:531-590` 构造 API 表,没有覆盖 `SetIntraOpNumThreads`,其余字段来自 `..ort_sys::stub::api()`。 +- `ort-sys-2.0.0-rc.11/src/stub.rs:194` 的 `SetIntraOpNumThreads` 明确返回 `OrtErrorCode::ORT_NOT_IMPLEMENTED`、`"Unimplemented"`。 + +为了得到 ort-tract API 错误包装省略的完整原因,临时诊断又直接调用了**同版本 tract 的相同解析/into_typed/into_runnable/run 流程**,没有修改输入事实或模型: + +```text +Vision: +Failed analyse for node #232 "/vision_model/embeddings/patch_embedding/Conv" ConvHir: +Infering facts: +Applying rule inputs[0].shape[1] == 1*{inputs[1].shape[1]}: +Impossible to unify Sym(num_channels) with Val(3). + +Text (typed graph 成功,实际执行失败): +Evaluating #11 "/text_model/embeddings/Slice" StridedSlice: +Running legacy eval: +Evaluating #5 "adhoc" Slice: +Undetermined symbol in expression: +``` + +`ort-tract/session.rs:57-64` 在加载时 `model_for_proto_model` 后立即 `into_typed()`(或启用优化时 `into_optimized()`);`session.rs:35-43` 的执行计划构造只设置 input names,没有根据运行时 tensor 绑定 input shape。该代码与上述错误相互印证。这里只记录已观察到的动态 shape 失败,不将它泛称为整个 Conv/Slice 算子不受支持,也不预先声称某一种修复方案已可行。 + +### 内存与验收结果 + +使用 `/usr/bin/time -l` 测量,每个图在独立进程顺序运行,没有 OOM 或进程被杀: + +| 诊断 | 最大 RSS | 结果 | +|---|---:|---| +| ort-tract 图像加载(省略线程选项) | 1,500,954,624 bytes(约 1.40 GiB) | shape 推导失败 | +| ort-tract 文本加载 + `(1,64)` 执行(省略线程选项) | 2,383,986,688 bytes(约 2.22 GiB) | Slice 动态符号失败 | +| 直接 tract 文本完整原因链诊断 | 2,925,232,128 bytes(约 2.72 GiB) | 同一 Slice 失败;time 另报 peak memory footprint 4,105,866,744 bytes | + +**没有产生有效 tract 图像/文本 embedding 或排名结果。** 原样产品排名入口实际运行并在 `OrtEmbedder::new` 返回线程设置错误,因此不能以先前 native-ORT 的正确猫/鹦鹉排名替代本后端验收。由于执行提前失败,也不能据此宣称完整模型推理的内存上限已通过。 + +结论:**阻塞当前 Windows 语义搜索发布资格。** 同一个锁定 tract 后端的共享代码已在宿主预检稳定复现三个阻塞;不能仅修复 `.with_intra_threads` 就宣布 Windows 支持。修复需要主线协调产品后端/API 或 shape 适配策略;本轮没有修改 `ort_embedder.rs`、`model_download.rs` 或模型文件。 + +### 最小 Windows 原生 qualification + +在真实 `windows-2022`/Windows x64 runner、VS C/C++ Build Tools + Windows SDK、项目 stable Rust MSVC 工具链上运行。无需 Python ONNX Runtime、native onnxruntime.dll、FFmpeg、Whisper 或付费服务。fixture 目录预先准备前文三个固定 SHA 模型文件以及 cats.png/parrots.png,测试本身不联网下载;需保留足够空间供源文件与临时安装副本并存。 + +```powershell +$env:OPENTAKE_SEARCH_MODEL_TEST_DIR = Join-Path $env:RUNNER_TEMP 'semantic-model-fixtures' +$env:CARGO_TARGET_DIR = Join-Path $env:RUNNER_TEMP 'semantic-qualification-target' +cargo test --locked -p opentake-media ` + --target x86_64-pc-windows-msvc ` + --features model-download,ort-backend --lib ` + search::ort_embedder::tests::real_model_offline_install_embeddings_and_ranking ` + -- --exact --ignored --nocapture --test-threads=1 +if ($LASTEXITCODE -ne 0) { throw 'Windows semantic model qualification failed' } +``` + +该命令自然选中产品 Windows `ort-tract` 依赖和初始化分支,覆盖实际 hash/安装、预处理、768 维单位向量、f16 索引和五条查询。需在协调修复后真正通过,并保留资源峰值及日志,才能解除 Windows 资格阻塞。这里提供命令,未提交/运行 CI 或改工作流。 + +本轮原始证据:`/tmp/opentake-tract-qualification/{Cargo.toml,Cargo.lock,metadata.json,lock-comparison.json,build.log,vision.log,text.log,ranking.log,diagnostic-vision.log,diagnostic-text.log,direct-vision.log,direct-text.log}`;可执行文件为 `target/debug/opentake-tract-qualification`,模式分别是 `vision`、`text`、`ranking`、`diagnostic-vision`、`diagnostic-text`、`direct-vision`、`direct-text`,运行时设置 `OPENTAKE_SEARCH_MODEL_TEST_DIR=/tmp/opentake-semantic-model`。 diff --git a/docs/audit/2026-09-06/sticker-panel.md b/docs/audit/2026-09-06/sticker-panel.md new file mode 100644 index 00000000..23d543b3 --- /dev/null +++ b/docs/audit/2026-09-06/sticker-panel.md @@ -0,0 +1,79 @@ +# Sticker 面板纵切记录 + +> 状态:generated · 阶段:implementation-backed · 日期:2026-09-06 +> 工作树:OpenTake-generation · 分支:release/v1.0.0-beta.6 +> 范围:本地贴纸面板接线与定向 Web 验证;不是原生安装包或发布验收。 + +## 实现结果 + +- Sticker 主标签已启用,在 MediaPanel 内提供独立贴纸内容区。 +- 「导入贴纸」打开有贴纸格式筛选的原生多选文件对话框。接受 PNG/JPG/JPEG/TIFF/HEIC/WebP、Lottie JSON、`.lottie`;扩展名集合与既有 Rust `ClipType::from_file_extension` 一致,JSON/Lottie 内容校验继续由 Rust 负责。 +- 面板从现有 `useMediaStore` 镜像筛选整个工程的 `image` / `lottie`,包括子文件夹素材。不新增贴纸模型或分类持久化字段,不引入商店、网络请求、生成素材或依赖。 +- 复用 `MediaCard` 的缩略图、单击/键盘选中及源预览、拖拽 MIME、收藏和重新链接。增加可键盘访问的「将选中贴纸加入时间线」按钮;双击经同一受控回调调用既有 `addMediaToTimeline`,拖拽继续交由现有时间线接收。 +- 落轨继续经既有 `placeMedia` 单事务处理,前端没有新增 Undo 栈或领域编辑逻辑。定向测试核对入口与既有事务行为;真实桌面 Undo 仍待主代理验收。 +- 空态及状态使用 `role=status`,失败使用 `role=alert`,按钮通过 `aria-describedby` 关联提示。无工程、只读工程、素材离线、生成/下载中及生成失败/取消时给出原因并禁用相应操作。 +- 整个导入手势(包括文件选择器)复用 `beginMediaImport` / `endMediaImport` token。重复点击被拦截;工程 epoch/path 变化后的选择结果、刷新和错误不会写回新工程,旧操作结束不会清除新工程的导入状态。 +- StickerPanel 同工程跨标签保持挂载,切走时仅卸载卡片视图,保留 pending/ref/错误;贴纸局部状态以 epoch/path 为 React key 重建,旧工程落轨失败不进入新面板。共享方向键导航尊重卡片 `aria-disabled`,不会通过方向键预览不可用贴纸。 + +## Read Set 与修改范围 + +已阅读根 AGENTS.md、`docs/project/conventions.md`、Web OVERVIEW/INDEX 路由,以及现有 MediaPanel、MediaTabBar、mediaActions/mediaStore/editActions、类型、对话框/API 与 Rust 图片类型/导入实现。 + +只读上游参考: + +- `../palmier-pro-upstream/Sources/PalmierPro/MediaPanel/MediaPanelView.swift` +- `../palmier-pro-upstream/Sources/PalmierPro/MediaPanel/MediaTab/AssetThumbnailView.swift` +- `../palmier-pro-upstream/Sources/PalmierPro/Editor/ViewModel/EditorViewModel+MediaLibrary.swift` + +实际修改仅以下文件: + +1. `web/src/components/media/MediaPanel.tsx`:贴纸内容、导入/落轨协调;为共享卡片增加可选回调与禁用原因。 +2. `web/src/components/media/MediaTabBar.tsx`:启用标签。 +3. `web/src/components/media/MediaTabBar.test.tsx`:新增可导航标签后的转场导航步数。 +4. `web/src/components/media/StickerPanel.test.tsx`:新增真实 MediaPanel/MediaCard DOM 交互测试。 +5. `web/src/i18n/dict.ts`:贴纸中英文文案。 +6. 本记录。 + +没有修改测试配置、版本、Rust、其他文档;没有 commit/push/release。现有并行未提交改动保留。 + +## 验证证据 + +TDD:先写 11 项 Sticker 交互用例,旧面板下 11 项失败(置灰、缺少卡片和动作入口);实现后通过。补键盘导航/工程失败隔离等边界至 16 项;方向键用例先复现失败,再补网格导航容器与禁用预览判断后通过。另补用例复现旧导入错误遮盖新落轨错误,改为同时展示后通过。 + +最终执行: + +```bash +pnpm --dir web test --run src/components/media/StickerPanel.test.tsx src/components/media/MediaTabBar.test.tsx src/components/media/MediaPanel.test.tsx src/store/editActions.test.ts +pnpm --dir web build +git diff --check -- web/src/components/media/MediaPanel.tsx web/src/components/media/MediaTabBar.tsx web/src/components/media/MediaTabBar.test.tsx web/src/i18n/dict.ts +``` + +- P2 修复后的最终结果:4 个测试文件 / 117 项通过,其中 Sticker 19 项(原 16 项 + 跨标签/工程隔离 3 项)。 +- `tsc -b && vite build` 成功。仍有动态/静态 import 不能分块和大于 500 kB chunk 的构建告警;本切片未做打包重构。 +- 最终 diff 已检查。未重复全量测试,留给主代理统一验证。 +- 测试使用真实 React 卡片、Zustand 镜像和对话框/媒体 API 边界替身;Sticker DOM 用例中落轨 action 被 mock 来检验手势次数/失败门控,另运行既有真实 editActions 定向测试。不能把这些测试写成真实原生解码/撤销验收。 + +## 冻结 patch 审查 P2 修复 + +审查指出并经本地 DOM 用例复现:原 `active ? : null` 在切换标签时卸载组件,重置 `placing` / `placementPending`,旧落轨尚未完成时返回已可再次提交;旧实例 `alive=false` 也会丢弃失败反馈。 + +最小修复:MediaPanel 对 StickerPanel 保持稳定挂载,传入 `active`;StickerPanel 完成所有 hooks 后在非活动状态返回 null,仅卸载卡片视图,不丢弃该工程的操作状态。父级继续用 epoch/path key 隔离工程,不新增全局 store。 + +本次新增 3 项回归: + +- 可见期间失败和隐藏期间失败两种情形:落轨中切走再返回,导入/落轨按钮仍禁用,双击不重复提交;失败返回后可见;点击重试只新增一次调用,成功后解除 pending。 +- 旧工程落轨在隐藏状态切换到新工程,新工程可独立提交;旧 promise 失败不显示到新工程,也不清除新工程的 pending。 + +TDD 证据:两个跨标签用例先在“返回后按钮仍禁用”断言失败;修复后原定向集合及新增用例全部通过(117 项),`pnpm --dir web build` 成功,原动态 import/大 chunk 告警仍在。测试 setup 对落轨 mock 使用 `mockReset`,防止失败用例未消费的 once promise 污染后续测试。 + +本次追加修改只有 `MediaPanel.tsx`、`StickerPanel.test.tsx` 和本记录,未改 Rust、配置、其他 MD;未运行全量测试。 + +## 待主代理候选包 GUI 验收 + +1. 在新 Beta 6 候选原生应用通过「导入贴纸」各导入透明 PNG、有效 Lottie JSON、`.lottie`,核对名称、缩略图/源预览、透明效果与动画播放;确认取消和无效 JSON 提示。 +2. 单击、方向键、Tab 操作与选中按钮落轨;双击与拖拽各落轨一次,核对轨道/时长/合成画面;每次落轨执行一次 Undo、Redo,确认完整撤销/恢复本次操作。 +3. 嵌套文件夹素材可见;将素材移走后确认离线状态及重新链接恢复。检查只读项目、生成/下载中及失败卡片提示。 +4. 落轨 pending 时切走/返回,确认禁用持续、失败可见及重试一次;导入进行中切换面板/工程,核对旧结果/错误不覆盖新工程,连续点击不重复提交;检查中文/英文、窄面板滚动和屏幕阅读器导航。 +5. 保存重开工程,确认贴纸仍来自同一媒体镜像,落轨片段及资源引用正常。 + +本任务未运行原生 GUI;不据此声明 Beta 6 已发布或全平台已验收。 diff --git a/docs/capabilities/CAPABILITY-LEDGER.md b/docs/capabilities/CAPABILITY-LEDGER.md index 24c3d4b3..b26bcdc6 100644 --- a/docs/capabilities/CAPABILITY-LEDGER.md +++ b/docs/capabilities/CAPABILITY-LEDGER.md @@ -18,7 +18,7 @@ skip_when: - 仅查看单个内部函数 priority: must freshness_class: project -last_verified: 2026-08-22T01:03:46+08:00 +last_verified: 2026-09-06 owners: - OpenTake-generation source_of_truth: @@ -36,7 +36,16 @@ tags: - evidence --- -# 首轮状态 +# Beta 6 当前状态 + +本轮规范来源仍为 [`requirements.json`](./requirements.json),实际执行证据见 [2026-09-06 验证记录](../audit/2026-09-06/public-beta-validation.md)。下方八月记录保留历史属性。 + +- 新增贴纸面板:图片/Lottie 导入、预览、选择、落轨及跨标签请求状态保留,Web 交互验证通过;新包原生 GUI 待验收。 +- 语义搜索:固定公开模型资产与校验清单,真实 macOS 模型推理、图文检索和旧索引隔离已验证;损坏模型提供修复入口,文件名/口播结果不受视觉失败影响。Windows 同版本 Tract 后端发现不支持线程设置和动态形状的问题,修复与实际推理复验进行中。 +- 真实媒体集成、原生音频时钟播放探针、真实 Keychain/MCP 生命周期验证已通过;这些证据各自有范围,不能代替安装包 GUI 或全平台验证。 +- 测试现在使用按测试文件隔离的 Storage;无须历史 NODE_OPTIONS localStorage 文件参数。 + +# 2026-08-21–22 历史状态 这不是最终完成声明,而是 2026-08-21 的可恢复基线。机器可读条目见 [`requirements.json`](./requirements.json);安装版 UI 场景见 [`full-desktop-functional-matrix.md`](../audit/2026-08-21/full-desktop-functional-matrix.md)。首轮上游 agent 按实际代码和两条定向测试落锚,未把历史 handoff 当成当前证据。 @@ -48,7 +57,7 @@ tags: - Motion Studio transparent alpha:Rust/Web 纵切已接通并通过全量自动化;最新包已完成透明开关、发布进度、时间线落轨和保存重开屏幕验收;导出面板已新增透明 ProRes 4444 codec,真实 GPU/FFmpeg integration 已复核 `yuva444` alpha;透明导出屏幕仍待解锁后复验。 - Agent/MCP linked move parity:`move_clips` 现在和上游约定一致,显式 `toFrame` 会把 frame delta 传播到 linked A/V partner,track-only move 不传播;新增两条 dispatcher 回归测试。最新安装版还验证了 Agent 面板标签生命周期、无通道时发送禁用和快捷键收起布局。 - 上游收敛新切片:`create_folder.entries` 与 `move_to_folder.entries` 已接入单一 EditCommand/Undo 事务;普通媒体导入现在接受有效 `.json` / `.lottie`,用 Velato 校验并写入 Lottie metadata,坏文档在批量入口跳过,MCP 单文件入口返回明确错误。 -- OpenTake 扩展:MediaPanel 的 Text 标签已接入既有 `addTextClip()`/Inspector Text 工作流并保持单步 Undo;Sticker/Effect 仍是明确置灰占位,不计入已完成能力。 +- OpenTake 扩展:MediaPanel 的 Text 标签已接入既有 `addTextClip()`/Inspector Text 工作流并保持单步 Undo;此处是当时的 Sticker/Effect 占位状态;当前 Sticker 已实现,Effect 预设已接链接选区的视频片段及单步撤销,见本轮候选说明。 ## 首轮明确缺口 diff --git a/docs/capabilities/requirements.json b/docs/capabilities/requirements.json index fc7d0096..8430c48d 100644 --- a/docs/capabilities/requirements.json +++ b/docs/capabilities/requirements.json @@ -1,8 +1,8 @@ { "schema_version": 1, - "catalog_status": "initial-triage", - "last_verified": "2026-08-22T12:22:40+08:00", - "scope": "首轮模块级能力清单;完整 UP-* 行为级拆分由长期计划 Task 1 继续展开。", + "catalog_status": "beta6-candidate", + "last_verified": "2026-09-06", + "scope": "Beta 6 候选代码与实际证据同步;既有 verified 条目保留其历史验收日期,不能自动作为当前安装包或全平台证明。", "capabilities": [ {"id":"UP-MODELS-TIMELINE","area":"models","title":"Timeline/Track/Clip/Keyframe/Transform/TextStyle","status":"implemented","priority":"P1","source":"palmier-pro-upstream/Sources/PalmierPro/Models/","opentake":"crates/opentake-domain/src/","tests":["cargo test -p opentake-domain"]}, {"id":"UP-EDITOR-COMMAND-TRANSACTION","area":"editor","title":"Single EditCommand entry and undo/redo","status":"implemented","priority":"P0","source":"palmier-pro-upstream/Sources/PalmierPro/Editor/EditorViewModel.swift","opentake":"crates/opentake-ops/src/command.rs;crates/opentake-core/src/session.rs","tests":["cargo test -p opentake-ops","cargo test -p opentake-core"]}, @@ -23,6 +23,8 @@ {"id":"UP-ACCOUNT-CLOUD-BOUNDARY","area":"account-generation","title":"Palmier account/subscription/cloud generation contract","status":"blocked","priority":"P0","source":"palmier-pro-upstream/Sources/PalmierPro/Account/;Generation/","opentake":"crates/opentake-gen/src/;src-tauri/src/advanced.rs;src-tauri/src/secret.rs","tests":[]}, {"id":"UP-SETTINGS-TAXONOMY","area":"settings","title":"Account/general/models/agent/storage settings taxonomy","status":"partial","priority":"P2","source":"palmier-pro-upstream/Sources/PalmierPro/Settings/","opentake":"web/src/components/settings/","tests":["web/src/components/settings/SettingsView.interaction.test.tsx"]}, {"id":"OT-WINDOW-SMALL-SCREEN","area":"shell","title":"Window size and responsive shell on small screens","status":"verified","priority":"P0","source":"User goal 2026-08-21","opentake":"src-tauri/tauri.conf.json;web/src/store/settingsStore.ts;web/src/components/shell/;web/src/components/home/","tests":["web/src/store/settingsStore.test.ts","docs/audit/2026-08-21/full-desktop-functional-matrix.md"]}, + {"id":"OT-MEDIA-STICKERS","area":"media","title":"Project image/Lottie sticker import, preview and timeline insertion","status":"implemented","priority":"P1","source":"OpenTake requirement","opentake":"web/src/components/media/MediaPanel.tsx","tests":["web/src/components/media/StickerPanel.test.tsx","docs/audit/2026-09-06/sticker-panel.md"]}, + {"id":"UP-SEMANTIC-SEARCH-MODEL","area":"intelligence","title":"Verified SigLIP2 assets, offline install, text/image inference and recoverable search","status":"partial","priority":"P1","source":"palmier-pro-upstream/Sources/PalmierPro/Search/","opentake":"crates/opentake-media/src/search/;src-tauri/src/search.rs;web/src/components/media/MediaSearch.tsx","tests":["docs/audit/2026-09-06/semantic-search-model.md","docs/audit/2026-09-06/search-model-recovery.md","cargo test -p opentake-tauri --lib search::tests"]}, {"id":"OT-DESKTOP-FUNCTIONAL-QA","area":"qa","title":"Installed-app modular functional verification","status":"partial","priority":"P0","source":"User goal 2026-08-21","opentake":"docs/audit/2026-08-21/full-desktop-functional-matrix.md","tests":["Computer Use @oai/sky"]} ] } diff --git a/docs/documentation-sync-2026-09-06.md b/docs/documentation-sync-2026-09-06.md new file mode 100644 index 00000000..9a399012 --- /dev/null +++ b/docs/documentation-sync-2026-09-06.md @@ -0,0 +1,494 @@ +# 全项目 Markdown 同步报告 — 2026-09-06 + +> 状态:generated · 阶段:implementation-backed · 文档审计日期:2026-09-06。 +> 本报告记录文档同步与静态源码核对,不代替候选功能、GUI、安装包或远端发布验收。 + +工作树:`OpenTake-generation/`。起点为 `release/v1.0.0-beta.5` / `33ee8e2`,origin 为 `appergb/OpenTake`。工作期间主代理切到 `release/v1.0.0-beta.6` 并更新版本清单;Beta 1–5 已发布,Beta 6 仍是未发布候选。本文未执行 commit、push、tag 或 release。 + +当前入口:[活动计划](plans/active/2026-09-06-public-beta.md) · [Beta 6 候选](releases/1.0.0-beta.6.md) · [当日验证](audit/2026-09-06/public-beta-validation.md) · [文档总目录](INDEX.md) · [开发规范](project/conventions.md)。 + +## 覆盖方式与边界 + +`scripts/check_docs.py` 通过 Git 枚举 tracked + 非忽略 untracked Markdown(含 `.MD` 与隐藏的 `.superpowers` 历史报告),逐文件扫描可点击的 Markdown 内联链接、引用定义和 HTML href/src 的本地文件目标,检查自动入口命名。代码块/行内代码中的历史命令和路径不当作链接。完整机器清单可运行 `python3 scripts/check_docs.py --json` 获取。 + +覆盖包括维护文档、设计来源、日期化历史、运行时 Markdown 资源和第三方归属记录。排除 `marketing/`、`output/`、`.playwright-cli/`、构建/依赖目录;其中视频切片、媒体输出由其他所有者维护。主代理指定的计划、Beta 6 发布说明、能力账本及 `docs/audit/2026-09-06/` 只读进入清单。 + +“逐文件覆盖”是路径/链接与状态分类审计,不代表全部历史正文重新逐句验真。正文同步重点为项目入口、README 三语、CHANGELOG、CONTRIBUTING、架构、各模块 OVERVIEW/INDEX、已变化子系统。历史测试日志、上游映射和旧设计保持原时点含义,不批量改成 canonical/final-verified。外部 URL、Markdown 标题 fragment、代码块中的机器绝对路径不由本脚本验证。 + +## 主要同步结果 + +- 根 `AGENTS.md` 压缩为项目摘要、技术栈、意图、风险和路由;有效开发规则迁入 `docs/project/conventions.md`,补 `docs/project/index.md` 与 `intent.md`。当前活动工作树、origin 和候选版本已纠正。 +- `CLAUDE.md` 明确 historical,索引不再把它当作当前交接。`docs/port-map/agent.md` 改名 `agent-port-map.md`,有效上游映射保留、所有 Markdown 引用更新;当前仓库只有根 `AGENTS.md` 自动入口。 +- 三语 README 与 CHANGELOG 区分已发布 Beta 1–5、Beta 6 候选和源码新增。平台表不再把跨平台目标等同于已验证 Windows/Linux 发行;语义搜索改为模型安装及 macOS 真实 Rust 链路已验证,保留首次安装要求与 Windows/新包 UI 边界;外部 MCP 配对不再写“未来才开放”。 +- 模块路由补缺失的 `opentake-process-tree`:Cargo 有 10 个库 crate + Tauri 壳,共 11 个成员。OVERVIEW/INDEX 区分源码和候选验收,早期规格保留设计状态。 +- 播放架构更新 temporal compositor、Lottie 与能力路由;导出文档更新 H.264/H.265/ProRes 422/4444、alpha、进度、取消和失败清理。媒体 Text/Effect 和 Agent 面板不再写未接线;预览 tab、媒体视图有当前路由。 +- 修复旧审计/实现计划中从模块目录复制后失效的 `dispatch-tools.md`、`renderer.md`、`probe-ff.md`、SPEC 和 architecture 相对链接,保留日期和原始证据正文。 + +## 74 个提交的源码核对 + +执行 `git rev-list --count aae0ae6..33ee8e2` 得到 74;`git log` 逐条核对该范围(包含文档与测试提交,不能称 74 项新功能,也不将该范围直接等同完整 Beta 5 tag diff)。 + +| 领域 | 提交依据 | 定向源码与结论 | +|---|---|---| +| 多预览 tab | `bd06d62`、`4c8b761` | `web/src/store/uiStore.ts`、Preview 组件;多 tab、旧状态归一存在 | +| 媒体视图/导入 | `745a190`、`5b1f45d`、`0bd7c6a` | MediaPanel 与导入动作;folder/flat/grouped、悬空 folder 归一、原生导入修复 | +| temporal compositor | `a833764`、`1013355`、`dafd5eb`、`3e124dc` | `web/src/components/preview/playbackRoute.ts` 与 native transport;合成时间线倒放/速度路由和发布流重置 | +| 透明 Motion | `e106bc3`、`ba85202`、`5e3b5b7`、`6ce80fe` | `src-tauri/src/motion.rs`、Motion UI;透明发布、编辑 alpha 保留与 Inspector 开关 | +| ProRes 4444 | `cca9327` | `src-tauri/src/export.rs` 与 media encode;透明 `.mov` 编码路径 | +| 时间线与链接行为 | `a7a372e`、`478e1b4`、`f05bac8`、`3b8ba9b`、`76e700b` | ops / editActions / timeline 手势;范围选择、选区驱动修剪/分割、链接移动、Option trim | +| 导出与播放失败 | `0bae80e`、`a7d98d6`、`eb1cc1a`、`741ff07` | 导出清理/取消/身份与缺失素材拒绝路径 | +| Lottie | `c1db732`、`2ed8a61` | Lottie 素材和原生播放接入;不能继续列为路由必然 unsupported | +| 文本/特效面板 | `17bcb8c`、`d87b74f`、`33ee8e2` | TextTab 调 addTextClip;EffectTab 向单个视觉选区追加预设,保留既有 effects,链接音轨不增加视觉计数 | + +## 实际代码与旧文档冲突 + +| 旧说法 | 当前依据与处理 | +|---|---| +| domain 零依赖 | Cargo.toml 实际依赖 serde;改为无 I/O 叶子 | +| Swift/Rust round 中点向偶 | 两者默认就近中点远离零;纠正规范,secondsToFrame 截断语义另列 | +| web 有 lint script | `web/package.json` 只有 dev/build/preview/test;CONTRIBUTING 改用真实脚本,build 包含 TypeScript 检查 | +| 全工作区 9 crate / 11 模块 | Cargo 实际 11 成员(10 库 + shell),加 web 为 12 个文档模块 | +| Beta 2 当前入口、Beta 5 未发布候选 | 当前 Beta 6 未发布候选;旧 Beta 日期/发布文件留存为历史 | +| 透明 Motion/ProRes 4444 未做 | 本轮之前的 74 提交中已有实现;更新当前总览与导出说明,旧设计注明时点 | +| Text/Effect/Agent 面板占位 | HEAD 已有接线;源码、浏览器测试、原生 GUI 验收分别记账 | +| MCP OAuth 元数据为空等于无需认证 | 生产桌面有 Bearer 与工程 gate;空 authorization_servers 不表示无认证 | +| 语义搜索模型仍为占位/修复中 | 原空 hash/bytes=0 已修复;约 1.5 GB 固定 revision 资产校验、macOS 真实 Rust 离线安装/图文 embedding/排名均通过。使用前需安装模型;Windows ort-tract 真实图及新包 UI 待验收 | +| Sticker 仍为占位 | 核对起点是禁用占位;Banach 已完成图片/Lottie 展示、导入、预览、拖拽/落轨和工程身份隔离,4 files / 114 tests 与 build 通过;新包原生 GUI 待主线验收 | + +未遇到必须由文档切片决定的新增产品意图冲突。Sticker 已由实现切片完成接线并通过定向 Web 检查,原生 GUI 仍待验收;语义搜索模型实现与 macOS 真实 Rust 链路已验证,Windows ort-tract 真实图及新包 UI 仍待。文档分别记录实现和验收状态。 + +Sticker 的实现与定向验证见[专项审计](audit/2026-09-06/sticker-panel.md),该记录由实现切片维护。 + +语义模型状态补记:实现切片的[最终专项审计](audit/2026-09-06/semantic-search-model.md)记录固定 revision 的约 1.5 GB 资产逐项校验通过;正式 Cargo media search **84 passed / 1 ignored**、Tauri search **14 passed**,引用实际产品模块的真实模型 Rust harness **72 passed / 0 ignored**。这些是不同且有重叠的验证集合,不能求和;真实 Rust 链路已覆盖离线安装、图文 embedding 和排名。Windows ort-tract 真实图及新包 UI 仍待主线验收,不据此宣称全平台、整仓或发布包通过。 + +## 验证记录与遗留 + +文档切片执行:`python3 scripts/check_docs.py`、脚本针对链接/代码块/分类的内存检查、Markdown 范围 `git diff --check`,并审阅本切片最终 diff。无新增依赖,无 Rust/TS/package/lock 修改;未重跑与此文档切片无关的构建/GUI/付费 provider。 + +主代理提供的较早一轮状态(非本切片执行,未包含随后新增的 Sticker 用例):Web 152 files / 1429 tests 通过,使用每文件 happy-dom Storage 隔离,命令 `pnpm -C web exec vitest run --maxWorkers=1`,无需 NODE_OPTIONS 或残留 localStorage 文件;workspace Clippy exit 0;脚本 209/209、sidecar 7/7 通过。Carver 跨 crate 中间态已由正式 Tauri search 14 项验证解决;主线整仓统一测试/审查仍在执行,不记整仓最终通过。依赖安全、运行验证和发布状态以当日审计后续更新为准。 + +用户预先删除的 5 张截图保持删除,未恢复:`09-dual-track-playback-frame-239.png`、`11-dual-track-seek-pause-frame-115.png`、`14-gui-export-completed.png`、`17-relaunch-editor-thumbnails-composite.png`、`18-relaunch-source-preview.png`(均在 `docs/audit/2026-08-07/editor-core-after-fix-assets/`)。已有工程 JSON、视频、日志、诊断素材保持原状。历史正文中的已删除证据文件名保留,不当作本轮可用证据。 + +后续由主代理在源码切片完成后统一复验并更新候选/当日审计,审阅文档 diff 后再决定发布。本报告不会把这些待执行门槛写成已经通过。 + +## 全部 Markdown 覆盖清单 + + + +本次快照覆盖 **402** 份 Markdown、**1891** 个本地文件链接;普通断链 **0**,待生成链接 **0**。本切片已同步/新增 **71** 份 Markdown(含本报告);历史保留类别 **188** 份,其中可能仅修复迁移链接。分类数量与变更数量为不同维度,不可直接相加。协作过程中新增文件可能改变下次审计计数。 + +| 分类 | 数量 | +|---|---:| +| 主代理维护 | 7 | +| 历史保留 | 188 | +| 生成的同步报告 | 1 | +| 维护文档 | 120 | +| 设计与来源参考 | 83 | +| 运行资源或归属记录 | 3 | + +逐文件状态:`同步` = 本轮文档 diff 或新增(不含主线切片文件);`保留` = 内容未更改但已完成链接/分类扫描;`主代理` = 主线及实现切片只读覆盖。历史/设计文件的状态解释由分类给出,不会把路径审计提升成完整功能验证。 + +| 路径 | 分类 | 本轮处理 | 本地文件链接数 | +|---|---|---|---:| +| `.superpowers/sdd/2026-08-13-beta5-agent-conversation/task-1-report.md` | 历史保留 | 保留 | 0 | +| `.superpowers/sdd/2026-08-13-beta5-agent-conversation/task-2-report.md` | 历史保留 | 保留 | 0 | +| `.superpowers/sdd/2026-08-13-beta5-agent-conversation/task-3-report.md` | 历史保留 | 保留 | 0 | +| `.superpowers/sdd/2026-08-13-beta5-agent-conversation/task-4-report.md` | 历史保留 | 保留 | 0 | +| `.superpowers/sdd/2026-08-13-beta5-external-mcp/task-6-report.md` | 历史保留 | 保留 | 0 | +| `.superpowers/sdd/2026-08-13-beta5-interface-polish/task-2-report.md` | 历史保留 | 保留 | 0 | +| `.superpowers/sdd/2026-08-13-beta5-interface-polish/task-3-report.md` | 历史保留 | 保留 | 0 | +| `.superpowers/sdd/2026-08-13-beta5-interface-polish/task-4-report.md` | 历史保留 | 保留 | 0 | +| `.superpowers/sdd/2026-08-13-beta5-interface-polish/task-5-report.md` | 历史保留 | 保留 | 0 | +| `.superpowers/sdd/2026-08-13-beta5-interface-polish/task-6-report.md` | 历史保留 | 保留 | 0 | +| `.superpowers/sdd/2026-08-13-beta5-motion-studio/task-1-report.md` | 历史保留 | 保留 | 0 | +| `.superpowers/sdd/2026-08-13-beta5-motion-studio/task-2-report.md` | 历史保留 | 保留 | 0 | +| `.superpowers/sdd/2026-08-13-beta5-motion-studio/task-3-report.md` | 历史保留 | 保留 | 0 | +| `.superpowers/sdd/2026-08-13-beta5-motion-studio/task-4-report.md` | 历史保留 | 保留 | 0 | +| `.superpowers/sdd/2026-08-13-beta5-motion-studio/task-5-report.md` | 历史保留 | 保留 | 0 | +| `.superpowers/sdd/2026-08-13-beta5-motion-studio/task-6-report.md` | 历史保留 | 保留 | 0 | +| `.superpowers/sdd/2026-08-13-beta5-motion-studio/task-7-report.md` | 历史保留 | 保留 | 0 | +| `.superpowers/sdd/2026-08-21-preview-temporal-remap-parity/task-2-report.md` | 历史保留 | 保留 | 0 | +| `.superpowers/sdd/2026-08-21-preview-temporal-remap-parity/task-3-report.md` | 历史保留 | 保留 | 0 | +| `.superpowers/sdd/task-11-red-replay.md` | 历史保留 | 保留 | 0 | +| `.superpowers/sdd/task-11-report.md` | 历史保留 | 保留 | 1 | +| `.superpowers/sdd/task-5-agent-chat-report.md` | 历史保留 | 保留 | 0 | +| `.superpowers/sdd/task-5-export-freeze-report.md` | 历史保留 | 保留 | 0 | +| `.superpowers/sdd/task-5-media-library-report.md` | 历史保留 | 保留 | 0 | +| `.superpowers/sdd/task-5-render-report.md` | 历史保留 | 保留 | 0 | +| `AGENTS.md` | 维护文档 | 同步 | 8 | +| `CHANGELOG.md` | 维护文档 | 同步 | 4 | +| `CLAUDE.md` | 历史保留 | 同步 | 2 | +| `CONTRIBUTING.md` | 维护文档 | 同步 | 5 | +| `DECISIONS.md` | 维护文档 | 保留 | 0 | +| `README.ja.md` | 维护文档 | 同步 | 24 | +| `README.md` | 维护文档 | 同步 | 25 | +| `README.zh-CN.md` | 维护文档 | 同步 | 25 | +| `THIRD_PARTY_NOTICES.md` | 运行资源或归属记录 | 保留 | 0 | +| `crates/opentake-agent/src/plugin/builtin/audio-first/instructions.md` | 运行资源或归属记录 | 保留 | 0 | +| `docs/INDEX.md` | 维护文档 | 同步 | 54 | +| `docs/architecture/ADVANCED-FEATURES.md` | 维护文档 | 同步 | 2 | +| `docs/architecture/ARCHITECTURE.md` | 维护文档 | 同步 | 4 | +| `docs/architecture/BUGS.md` | 历史保留 | 同步 | 6 | +| `docs/architecture/CAPCUT-GAP.md` | 历史保留 | 同步 | 2 | +| `docs/architecture/EDITING-ENGINE-PLAN.md` | 维护文档 | 同步 | 4 | +| `docs/architecture/FULL_PROJECT_SCAN_REPORT.md` | 历史保留 | 同步 | 13 | +| `docs/architecture/HANDOFF-2026-07.md` | 历史保留 | 同步 | 9 | +| `docs/architecture/INDEX.md` | 维护文档 | 同步 | 22 | +| `docs/architecture/MODULE-PORT-MAP.md` | 维护文档 | 同步 | 2 | +| `docs/architecture/PLAYBACK-ENGINE.md` | 维护文档 | 同步 | 4 | +| `docs/architecture/PORT-1TO1-GAP.md` | 历史保留 | 同步 | 2 | +| `docs/architecture/ROADMAP.md` | 历史保留 | 同步 | 7 | +| `docs/architecture/STEM-SEPARATION.md` | 维护文档 | 同步 | 2 | +| `docs/architecture/UPDATER.md` | 维护文档 | 同步 | 2 | +| `docs/architecture/editing-automation/EDITING-AUTOMATION-DOS.md` | 维护文档 | 保留 | 13 | +| `docs/architecture/editing-automation/EDITING-AUTOMATION/acceptance-tests.md` | 维护文档 | 保留 | 9 | +| `docs/architecture/editing-automation/EDITING-AUTOMATION/agent-editing-suggestions.md` | 维护文档 | 保留 | 8 | +| `docs/architecture/editing-automation/EDITING-AUTOMATION/auto-crop-smart-reframe.md` | 维护文档 | 保留 | 8 | +| `docs/architecture/editing-automation/EDITING-AUTOMATION/beat-sync-auto-cut.md` | 维护文档 | 保留 | 8 | +| `docs/architecture/editing-automation/EDITING-AUTOMATION/workflow-plugin-recipes.md` | 维护文档 | 保留 | 7 | +| `docs/architecture/editing-automation/README.md` | 维护文档 | 保留 | 19 | +| `docs/audit/2026-07-14/beta-1-convergence-2026-08-01.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/completion-report.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/document-reconciliation.md` | 历史保留 | 同步 | 30 | +| `docs/audit/2026-07-14/implementation-plan-index.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/implementation-plans/accessibility-polish-design.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/implementation-plans/accessibility-polish-implementation.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/implementation-plans/agent-settings-generation-design.md` | 历史保留 | 同步 | 5 | +| `docs/audit/2026-07-14/implementation-plans/agent-settings-generation-implementation.md` | 历史保留 | 同步 | 5 | +| `docs/audit/2026-07-14/implementation-plans/command-contracts-design.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/implementation-plans/command-contracts-implementation.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/implementation-plans/data-safety-design.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/implementation-plans/data-safety-implementation.md` | 历史保留 | 保留 | 6 | +| `docs/audit/2026-07-14/implementation-plans/documentation-design.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/implementation-plans/documentation-implementation.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/implementation-plans/home-shell-design.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/implementation-plans/home-shell-implementation.md` | 历史保留 | 保留 | 5 | +| `docs/audit/2026-07-14/implementation-plans/inspector-text-keyframes-design.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/implementation-plans/inspector-text-keyframes-implementation.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/implementation-plans/media-library-design.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/implementation-plans/media-library-implementation.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/implementation-plans/media-render-playback-export-design.md` | 历史保留 | 同步 | 25 | +| `docs/audit/2026-07-14/implementation-plans/media-render-playback-export-implementation.md` | 历史保留 | 同步 | 31 | +| `docs/audit/2026-07-14/implementation-plans/preview-timeline-design.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/implementation-plans/preview-timeline-implementation.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/interface-traces.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/agent-lottie-inspect-real-device-2026-08-01.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/ai-matting-vertical-2026-08-01.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/avatar-voice-clone-vertical-2026-08-01.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/beta-1-sequential-validation-2026-08-01.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/bounded-audio-streaming-real-device-2026-08-01.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/caption-translation-vertical-2026-08-01.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/cli-sidecar-boundary-real-device-2026-08-01.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/color-match-vertical-2026-08-01.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/data-safety-cache-identity-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/data-safety-cross-cutting-security-partial-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/data-safety-generation-seed-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/data-safety-legacy-default-matrix-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/data-safety-manifest-corruption-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/data-safety-mcp-redaction-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/data-safety-mcp-tool-import-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/data-safety-mcp-transport-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/data-safety-project-open-composite-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/data-safety-shared-core-command-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/data-safety-windows-safe-fs-native-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/denoise-real-device-2026-08-01.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/ffmpeg-license-replacement-2026-08-01.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/five-panel-layout-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/generation-finalization-2026-07-29.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/generic-effects-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/hdr-proxy-account-real-device-2026-08-01.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/headless-chromium-real-device-2026-08-01.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/home-autosave-metadata-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/home-component-mapping-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/home-new-project-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/home-open-project-real-device-2026-07-30.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/home-project-lifecycle-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/home-sample-project-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/home-secondary-controls-real-device-2026-07-30.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/home-upstream-composite-real-device-2026-07-31.md` | 历史保留 | 保留 | 5 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/hsl-secondary-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/interchange-export-real-device-2026-07-30.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/lgg-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/linked-audio-real-device-2026-08-01.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/loudness-real-device-2026-08-01.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/lut-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/mask-rendering-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/media-render-packaged-ffmpeg-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/motion-tracking-agent-backend-2026-08-01.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/nested-timeline-compound-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/object-removal-vertical-2026-08-01.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/optical-flow-24-to-60-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/playback-route-lifecycle-real-device-2026-08-01.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/recent-project-card-real-device-2026-07-30.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/schema-safe-persistence-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/script-to-video-vertical-2026-08-01.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/stabilization-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/stem-separation-agent-vertical-2026-08-01.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/stems-real-device-2026-08-01.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/subtitle-export-real-device-2026-07-30.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/talking-head-cleanup-2026-07-29.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/titlebar-controls-real-device-2026-07-30.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/transition-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/view-menu-contract-real-device-2026-07-31.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/runtime-artifacts/automated/view-menu-real-device-2026-07-30.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/task8-core-slice-validation.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/task8-ledger-slice-validation.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/task8-ui-slice-validation.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-07-14/upstream-downstream.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-02/beta-functional-verification.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-07/editor-core-after-fix-assets/README.md` | 历史保留 | 保留 | 1 | +| `docs/audit/2026-08-07/editor-core-after-fix-assets/installation-ledger.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-07/editor-core-after-fix-assets/playback-av-real-device.md` | 历史保留 | 保留 | 3 | +| `docs/audit/2026-08-07/editor-core-remediation-assets/p0-asset-scope-import.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-07/editor-core-remediation-assets/proxy-regression.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-07/editor-core-remediation-assets/resource-scheduler.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-07/editor-core-remediation-assets/safe-asset-authority.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-07/editor-core-remediation-assets/ui-semantics.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-07/editor-core-remediation-matrix.md` | 历史保留 | 保留 | 3 | +| `docs/audit/2026-08-07/editor-core-validation-assets/README.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-07/editor-core-validation-assets/competitor-official-sources.md` | 历史保留 | 保留 | 23 | +| `docs/audit/2026-08-07/editor-core-validation-assets/fresh-gui-ledger.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-07/editor-core-validation-assets/resource-ui-static.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-07/editor-core-validation-evidence-b/README.md` | 历史保留 | 保留 | 1 | +| `docs/audit/2026-08-07/editor-core-validation-evidence-b/command-ledger.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-07/editor-core-validation.md` | 历史保留 | 保留 | 35 | +| `docs/audit/2026-08-08/release-module-tests/competitor-gap.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-08/release-module-tests/compositor-render.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-08/release-module-tests/import-export.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-08/release-module-tests/motion-canvas-supply-chain.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-08/release-module-tests/playback-fallback-fix.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-08/release-module-tests/playback.md` | 历史保留 | 保留 | 6 | +| `docs/audit/2026-08-08/release-module-tests/project-media-scope-hardening.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-08/release-module-tests/release-workflow.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-08/release-module-tests/resource-loading.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-08/release-module-tests/ui-blockers-fix.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-08/release-module-tests/ui.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-08/release-module-tests/windows-ci-hardening-fix.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-08/release-module-tests/windows-ci-retest.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-08/release-module-tests/windows-ci.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-10/final-module-validation.md` | 历史保留 | 保留 | 1 | +| `docs/audit/2026-08-13/beta5-external-mcp.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-13/beta5-motion-studio.md` | 历史保留 | 保留 | 2 | +| `docs/audit/2026-08-13/beta5-release-candidate.md` | 历史保留 | 保留 | 0 | +| `docs/audit/2026-08-21/full-desktop-functional-matrix.md` | 历史保留 | 保留 | 3 | +| `docs/audit/2026-09-06/public-beta-validation.md` | 主代理维护 | 主代理 | 2 | +| `docs/audit/2026-09-06/semantic-search-model.md` | 主代理维护 | 主代理 | 1 | +| `docs/audit/2026-09-06/sticker-panel.md` | 主代理维护 | 主代理 | 0 | +| `docs/capabilities/CAPABILITY-LEDGER.md` | 主代理维护 | 主代理 | 6 | +| `docs/capabilities/README.md` | 维护文档 | 保留 | 3 | +| `docs/documentation-sync-2026-09-06.md` | 生成的同步报告 | 同步 | 6 | +| `docs/knowledge/2026-09-06-beta-dependencies.md` | 维护文档 | 同步 | 0 | +| `docs/knowledge/2026-09-06-semantic-search-model.md` | 主代理维护 | 主代理 | 1 | +| `docs/modules/INDEX.md` | 维护文档 | 同步 | 34 | +| `docs/modules/opentake-agent/AGENT-CONTEXT-SIGNAL.md` | 维护文档 | 保留 | 5 | +| `docs/modules/opentake-agent/INDEX.md` | 维护文档 | 同步 | 31 | +| `docs/modules/opentake-agent/OVERVIEW.md` | 维护文档 | 同步 | 15 | +| `docs/modules/opentake-agent/SPEC.md` | 设计与来源参考 | 同步 | 2 | +| `docs/modules/opentake-agent/WORKFLOW-PLUGIN-SYSTEM.md` | 维护文档 | 保留 | 4 | +| `docs/modules/opentake-agent/context-signal.md` | 维护文档 | 保留 | 12 | +| `docs/modules/opentake-agent/core-handle-convert.md` | 维护文档 | 保留 | 15 | +| `docs/modules/opentake-agent/dispatch-tools.md` | 维护文档 | 保留 | 14 | +| `docs/modules/opentake-agent/mcp-server.md` | 维护文档 | 同步 | 16 | +| `docs/modules/opentake-agent/plugin-system.md` | 维护文档 | 保留 | 14 | +| `docs/modules/opentake-agent/prompt.md` | 维护文档 | 保留 | 12 | +| `docs/modules/opentake-core/INDEX.md` | 维护文档 | 同步 | 22 | +| `docs/modules/opentake-core/OVERVIEW.md` | 维护文档 | 同步 | 27 | +| `docs/modules/opentake-core/SPEC.md` | 设计与来源参考 | 同步 | 5 | +| `docs/modules/opentake-core/core-router.md` | 维护文档 | 保留 | 17 | +| `docs/modules/opentake-core/deps-di.md` | 维护文档 | 保留 | 13 | +| `docs/modules/opentake-core/dto.md` | 维护文档 | 保留 | 19 | +| `docs/modules/opentake-core/events-bus.md` | 维护文档 | 保留 | 17 | +| `docs/modules/opentake-core/session.md` | 维护文档 | 保留 | 17 | +| `docs/modules/opentake-domain/INDEX.md` | 维护文档 | 同步 | 33 | +| `docs/modules/opentake-domain/OVERVIEW.md` | 维护文档 | 同步 | 11 | +| `docs/modules/opentake-domain/keyframe-transform.md` | 维护文档 | 保留 | 11 | +| `docs/modules/opentake-domain/media-signal.md` | 维护文档 | 保留 | 7 | +| `docs/modules/opentake-domain/split-subtitle.md` | 维护文档 | 保留 | 13 | +| `docs/modules/opentake-domain/text-grade.md` | 维护文档 | 保留 | 10 | +| `docs/modules/opentake-domain/timeline-model.md` | 维护文档 | 保留 | 15 | +| `docs/modules/opentake-gen/INDEX.md` | 维护文档 | 同步 | 57 | +| `docs/modules/opentake-gen/OVERVIEW.md` | 维护文档 | 同步 | 28 | +| `docs/modules/opentake-gen/SPEC.md` | 设计与来源参考 | 同步 | 2 | +| `docs/modules/opentake-gen/catalog.md` | 维护文档 | 保留 | 14 | +| `docs/modules/opentake-gen/client-transport.md` | 维护文档 | 保留 | 18 | +| `docs/modules/opentake-gen/keys-byok.md` | 维护文档 | 保留 | 13 | +| `docs/modules/opentake-gen/params.md` | 维护文档 | 保留 | 15 | +| `docs/modules/opentake-gen/providers.md` | 维护文档 | 保留 | 21 | +| `docs/modules/opentake-media/INDEX.md` | 维护文档 | 同步 | 28 | +| `docs/modules/opentake-media/OVERVIEW.md` | 维护文档 | 同步 | 21 | +| `docs/modules/opentake-media/SPEC.md` | 设计与来源参考 | 同步 | 2 | +| `docs/modules/opentake-media/analysis.md` | 维护文档 | 保留 | 13 | +| `docs/modules/opentake-media/decode.md` | 维护文档 | 保留 | 20 | +| `docs/modules/opentake-media/encode.md` | 维护文档 | 保留 | 14 | +| `docs/modules/opentake-media/library-index.md` | 维护文档 | 保留 | 14 | +| `docs/modules/opentake-media/probe-ff.md` | 维护文档 | 保留 | 11 | +| `docs/modules/opentake-media/semantic-search.md` | 维护文档 | 同步 | 19 | +| `docs/modules/opentake-media/thumbnail.md` | 维护文档 | 保留 | 13 | +| `docs/modules/opentake-media/transcribe.md` | 维护文档 | 保留 | 13 | +| `docs/modules/opentake-media/waveform.md` | 维护文档 | 保留 | 14 | +| `docs/modules/opentake-motion/INDEX.md` | 维护文档 | 同步 | 21 | +| `docs/modules/opentake-motion/MOTION-GRAPHICS-PLUGIN.md` | 维护文档 | 同步 | 1 | +| `docs/modules/opentake-motion/OVERVIEW.md` | 维护文档 | 同步 | 16 | +| `docs/modules/opentake-motion/cache.md` | 维护文档 | 保留 | 10 | +| `docs/modules/opentake-motion/integration.md` | 维护文档 | 保留 | 11 | +| `docs/modules/opentake-motion/manifest-source.md` | 维护文档 | 保留 | 12 | +| `docs/modules/opentake-motion/renderer.md` | 维护文档 | 保留 | 10 | +| `docs/modules/opentake-motion/sandbox.md` | 维护文档 | 保留 | 10 | +| `docs/modules/opentake-ops/INDEX.md` | 维护文档 | 同步 | 17 | +| `docs/modules/opentake-ops/OVERVIEW.md` | 维护文档 | 同步 | 15 | +| `docs/modules/opentake-ops/command-apply.md` | 维护文档 | 保留 | 8 | +| `docs/modules/opentake-ops/engines.md` | 维护文档 | 保留 | 5 | +| `docs/modules/opentake-ops/intent-id.md` | 维护文档 | 保留 | 7 | +| `docs/modules/opentake-ops/ops-algorithms.md` | 维护文档 | 保留 | 8 | +| `docs/modules/opentake-process-tree/INDEX.md` | 维护文档 | 同步 | 5 | +| `docs/modules/opentake-process-tree/OVERVIEW.md` | 维护文档 | 同步 | 5 | +| `docs/modules/opentake-project/INDEX.md` | 维护文档 | 同步 | 17 | +| `docs/modules/opentake-project/OVERVIEW.md` | 维护文档 | 同步 | 18 | +| `docs/modules/opentake-project/bundle-archive.md` | 维护文档 | 保留 | 9 | +| `docs/modules/opentake-project/fcpxml-export.md` | 维护文档 | 保留 | 4 | +| `docs/modules/opentake-project/gen-log.md` | 维护文档 | 保留 | 6 | +| `docs/modules/opentake-project/layout.md` | 维护文档 | 保留 | 10 | +| `docs/modules/opentake-render/INDEX.md` | 维护文档 | 同步 | 20 | +| `docs/modules/opentake-render/OVERVIEW.md` | 维护文档 | 同步 | 16 | +| `docs/modules/opentake-render/SPEC.md` | 设计与来源参考 | 同步 | 2 | +| `docs/modules/opentake-render/gpu-compositor.md` | 维护文档 | 保留 | 12 | +| `docs/modules/opentake-render/render-plan.md` | 维护文档 | 保留 | 10 | +| `docs/modules/opentake-render/source-size.md` | 维护文档 | 保留 | 8 | +| `docs/modules/opentake-render/text-rasterizer.md` | 维护文档 | 保留 | 7 | +| `docs/modules/src-tauri/INDEX.md` | 维护文档 | 同步 | 27 | +| `docs/modules/src-tauri/OVERVIEW.md` | 维护文档 | 同步 | 20 | +| `docs/modules/src-tauri/commands-ipc.md` | 维护文档 | 保留 | 15 | +| `docs/modules/src-tauri/export.md` | 维护文档 | 同步 | 9 | +| `docs/modules/src-tauri/library-media.md` | 维护文档 | 保留 | 16 | +| `docs/modules/src-tauri/mcp.md` | 维护文档 | 保留 | 11 | +| `docs/modules/src-tauri/render.md` | 维护文档 | 保留 | 13 | +| `docs/modules/src-tauri/secret.md` | 维护文档 | 保留 | 10 | +| `docs/modules/src-tauri/setup-lib.md` | 维护文档 | 保留 | 24 | +| `docs/modules/web/INDEX.md` | 维护文档 | 同步 | 28 | +| `docs/modules/web/OVERVIEW.md` | 维护文档 | 同步 | 9 | +| `docs/modules/web/SPEC.md` | 设计与来源参考 | 同步 | 2 | +| `docs/modules/web/hooks-i18n-theme.md` | 维护文档 | 保留 | 9 | +| `docs/modules/web/ipc-api.md` | 维护文档 | 保留 | 11 | +| `docs/modules/web/panels-ui.md` | 维护文档 | 同步 | 13 | +| `docs/modules/web/preview-ui.md` | 维护文档 | 同步 | 13 | +| `docs/modules/web/state-stores.md` | 维护文档 | 保留 | 11 | +| `docs/modules/web/timeline-ui.md` | 维护文档 | 保留 | 11 | +| `docs/plans/active/2026-09-06-public-beta.md` | 主代理维护 | 主代理 | 0 | +| `docs/port-map/account.md` | 设计与来源参考 | 保留 | 0 | +| `docs/port-map/agent-port-map.md` | 设计与来源参考 | 同步 | 1 | +| `docs/port-map/app.md` | 设计与来源参考 | 保留 | 0 | +| `docs/port-map/editor.md` | 设计与来源参考 | 保留 | 0 | +| `docs/port-map/export.md` | 设计与来源参考 | 保留 | 0 | +| `docs/port-map/generation.md` | 设计与来源参考 | 保留 | 0 | +| `docs/port-map/help.md` | 设计与来源参考 | 保留 | 0 | +| `docs/port-map/inspector.md` | 设计与来源参考 | 保留 | 0 | +| `docs/port-map/mediapanel.md` | 设计与来源参考 | 保留 | 0 | +| `docs/port-map/models.md` | 设计与来源参考 | 保留 | 0 | +| `docs/port-map/preview.md` | 设计与来源参考 | 保留 | 0 | +| `docs/port-map/project.md` | 设计与来源参考 | 保留 | 0 | +| `docs/port-map/search.md` | 设计与来源参考 | 保留 | 0 | +| `docs/port-map/settings.md` | 设计与来源参考 | 保留 | 0 | +| `docs/port-map/telemetry.md` | 设计与来源参考 | 保留 | 0 | +| `docs/port-map/timeline.md` | 设计与来源参考 | 保留 | 0 | +| `docs/port-map/toolbar.md` | 设计与来源参考 | 保留 | 0 | +| `docs/port-map/transcription.md` | 设计与来源参考 | 保留 | 0 | +| `docs/port-map/ui.md` | 设计与来源参考 | 保留 | 0 | +| `docs/port-map/utilities.md` | 设计与来源参考 | 保留 | 0 | +| `docs/project/conventions.md` | 维护文档 | 同步 | 2 | +| `docs/project/index.md` | 维护文档 | 同步 | 5 | +| `docs/project/intent.md` | 维护文档 | 同步 | 2 | +| `docs/releases/1.0.0-beta.1.md` | 历史保留 | 保留 | 0 | +| `docs/releases/1.0.0-beta.2.md` | 历史保留 | 保留 | 0 | +| `docs/releases/1.0.0-beta.3.md` | 历史保留 | 保留 | 0 | +| `docs/releases/1.0.0-beta.4.md` | 历史保留 | 保留 | 1 | +| `docs/releases/1.0.0-beta.5.md` | 历史保留 | 保留 | 2 | +| `docs/releases/1.0.0-beta.6.md` | 主代理维护 | 主代理 | 4 | +| `docs/specs/INDEX.md` | 设计与来源参考 | 保留 | 46 | +| `docs/specs/agent/0-evidence-index.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/agent/1-mcp-server.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/agent/10-implementation.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/agent/2-tools.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/agent/3-short-id.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/agent/4-execution-shell.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/agent/5-chat.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/agent/6-context-signal.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/agent/7-system-prompt.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/agent/8-core-dispatch.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/agent/9-telemetry.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/core/0-design-baseline.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/core/1-editor-state.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/core/2-command-routing.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/core/3-event-bus.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/core/4-frontend-sync.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/core/5-assembly.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/core/6-tauri-commands.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/core/7-security.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/core/8-implementation.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/frontend/0-principles.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/frontend/1-design-tokens.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/frontend/10-state.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/frontend/11-tauri.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/frontend/12-data-models.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/frontend/13-implementation.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/frontend/2-layout.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/frontend/3-components.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/frontend/4-toolbar.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/frontend/5-timeline.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/frontend/6-inspector.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/frontend/7-media-panel.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/frontend/8-preview.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/frontend/9-interactions.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/media/0-principles.md` | 设计与来源参考 | 保留 | 1 | +| `docs/specs/media/1-structure.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/media/10-acceptance.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/media/11-implementation.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/media/2-ffmpeg.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/media/3-thumbnails.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/media/4-waveform.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/media/5-search.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/media/6-transcribe.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/media/7-ort-worker.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/media/8-coordinator.md` | 设计与来源参考 | 保留 | 0 | +| `docs/specs/media/9-domain-contract.md` | 设计与来源参考 | 保留 | 0 | +| `docs/superpowers/archive/2026-07-08-branch-integration-register.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/archive/2026-07-08-editing-automation-dos-session.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/archive/2026-07-08-verification-report.md` | 历史保留 | 保留 | 1 | +| `docs/superpowers/archive/2026-07-10-playback-cache-installed-app-qa.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/archive/2026-07-10-wave1a-baseline-disposition.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/archive/2026-07-13-cloud-integration-release-report.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/plans/2026-07-08-opentake-recovery-integration.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/plans/2026-07-10-opentake-wave-1a-integration-baseline.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/plans/2026-07-11-opentake-wave-1b-schema-compatibility.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/plans/2026-07-12-opentake-wave-1b-c1a-fail-closed-export.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/plans/2026-07-12-opentake-wave-1b-c1b-safe-filesystem.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/plans/2026-07-14-opentake-completion-audit.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/plans/2026-07-18-long-media-playback-handoff.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/plans/2026-08-10-opentake-beta4-release.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/plans/2026-08-13-beta5-agent-conversation.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/plans/2026-08-13-beta5-external-mcp.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/plans/2026-08-13-beta5-interface-polish.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/plans/2026-08-13-beta5-motion-studio.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/plans/2026-08-13-beta5-release.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/plans/2026-08-21-file-import-dialog.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/plans/2026-08-21-media-view-modes.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/plans/2026-08-21-opentake-full-ui-and-upstream-convergence.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/plans/2026-08-21-preview-tabs-parity.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/plans/2026-08-21-preview-temporal-remap-parity.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/plans/c1b/2026-07-12-c1b-common-unix-normative.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/plans/c1b/2026-07-12-c1b-windows-ci-normative.md` | 历史保留 | 保留 | 0 | +| `docs/superpowers/specs/2026-07-08-opentake-recovery-integration-design.md` | 设计与来源参考 | 保留 | 0 | +| `docs/superpowers/specs/2026-07-10-opentake-full-convergence-design.md` | 设计与来源参考 | 保留 | 0 | +| `docs/superpowers/specs/2026-07-12-opentake-wave-1b-c-filesystem-security-design.md` | 设计与来源参考 | 保留 | 0 | +| `docs/superpowers/specs/2026-07-14-opentake-completion-audit-design.md` | 设计与来源参考 | 保留 | 0 | +| `docs/superpowers/specs/2026-08-13-opentake-beta5-design.md` | 设计与来源参考 | 保留 | 0 | +| `docs/upstream-analysis/01-架构与数据流.md` | 设计与来源参考 | 保留 | 0 | +| `docs/upstream-analysis/02-苹果框架可移植性.md` | 设计与来源参考 | 保留 | 0 | +| `docs/upstream-analysis/03-闭源云边界.md` | 设计与来源参考 | 保留 | 0 | +| `docs/upstream-analysis/04-MCP与Agent工具.md` | 设计与来源参考 | 保留 | 0 | +| `docs/upstream-analysis/README.md` | 设计与来源参考 | 保留 | 0 | +| `docs/需求与问题汇总.md` | 维护文档 | 保留 | 0 | +| `plugins/motion-canvas-studio/README.md` | 维护文档 | 保留 | 0 | +| `plugins/motion-canvas-studio/THIRD_PARTY_NOTICES.md` | 运行资源或归属记录 | 保留 | 0 | +| `src-tauri/README.md` | 维护文档 | 同步 | 3 | +| `src-tauri/resources/ffmpeg/SOURCE.md` | 维护文档 | 保留 | 0 | +| `update-summary/2026-06-27-ui-refresh.md` | 历史保留 | 保留 | 0 | diff --git a/docs/knowledge/2026-09-06-beta-dependencies.md b/docs/knowledge/2026-09-06-beta-dependencies.md new file mode 100644 index 00000000..cb97c412 --- /dev/null +++ b/docs/knowledge/2026-09-06-beta-dependencies.md @@ -0,0 +1,19 @@ +--- +status: canonical +content_stage: implementation-backed +retrieved: 2026-09-06 +freshness_class: rapid +valid_until: 2026-09-07 +confidence: high +--- + +# Beta 候选依赖修复来源 + +发布前需实时审计,本记录不代替发布安全门禁。 + +| 依赖 | 项目原版本 | 目标版本 | 来源与结论 | +|---|---|---|---| +| Web/PostCSS → nanoid | 3.3.16 | 3.3.18 | [维护者发布](https://github.com/ai/nanoid/releases/tag/3.3.18)及[GitHub公告](https://github.com/advisories/GHSA-2v37-7h3g-55p8);审计要求3.x分支至少3.3.18,避免零长度自定义生成器的无限循环。锁文件仅更新此传递依赖。 | +| Motion Canvas → speech-rule-engine → @xmldom/xmldom | 0.9.10 | 0.9.12 | [维护者安全公告](https://github.com/xmldom/xmldom/security/advisories/GHSA-6gmq-8vp8-gcm6)说明无效 EntityReference 名称可注入序列化XML。speech-rule-engine 4.1.4 精确依赖旧版,使用 npm override 保持其4.x及Motion Canvas3.17.2,锁定修复版本。 | + +本轮实际 `pnpm -C web audit --audit-level moderate` 与 Motion Canvas `npm audit --audit-level=moderate` 均为0项;Motion测试、许可证和runner构建通过。此结论限定本次解析出的依赖图;应用自身未直接使用公告中的危险API,升级不代表已验证每条第三方执行路径。 diff --git a/docs/knowledge/2026-09-06-semantic-search-model.md b/docs/knowledge/2026-09-06-semantic-search-model.md new file mode 100644 index 00000000..1cc93fcd --- /dev/null +++ b/docs/knowledge/2026-09-06-semantic-search-model.md @@ -0,0 +1,50 @@ +--- +status: canonical +stage: implementation-backed +retrieved_at: 2026-09-06T06:34:20Z +freshness: normal +valid_until: 2026-09-16 +confidence: high +--- + +# SigLIP2 公开 ONNX 模型资产 + +本记录是 2026-09-06 搜索模型缺口修复的外部知识来源。固定模型事实长期有效;远端可达性按 10 天重新验证。不是对上游最新版本的声明。 + +## 来源与版本 + +- [Google 模型卡](https://huggingface.co/google/siglip2-base-patch16-256):原始模型 `google/siglip2-base-patch16-256`,Apache-2.0,支持图文检索。 +- [ONNX Community 固定发布](https://huggingface.co/onnx-community/siglip2-base-patch16-256-ONNX/tree/d1114256522a37ffa257a0a58017348ab0058db2):由 Hugging Face 的 Xenova 发布,模型卡明确来源为上述 Google 模型;不是 OpenTake 自行托管或同名替代模型。 +- [固定 revision 文件 API](https://huggingface.co/api/models/onnx-community/siglip2-base-patch16-256-ONNX/revision/d1114256522a37ffa257a0a58017348ab0058db2?blobs=true):提供 LFS SHA-256 和准确长度。必须使用 `lfs.sha256`,不能将 Xet 存储 hash 或 Git blobId 当文件 SHA-256。 +- [固定预处理配置](https://huggingface.co/onnx-community/siglip2-base-patch16-256-ONNX/blob/d1114256522a37ffa257a0a58017348ab0058db2/preprocessor_config.json):256×256、双线性缩放、RGB mean/std 均为 0.5。 +- [固定 tokenizer 配置](https://huggingface.co/onnx-community/siglip2-base-patch16-256-ONNX/blob/d1114256522a37ffa257a0a58017348ab0058db2/tokenizer_config.json):Gemma tokenizer,无 BOS、有 EOS,右填 `` id 0,长度 64。 +- [Transformers 4.50.3 GemmaTokenizerFast](https://github.com/huggingface/transformers/blob/v4.50.3/src/transformers/models/gemma/tokenization_gemma_fast.py):特殊 token 后处理;截断应在加入 EOS 前完成。所下载 tokenizer.json 包含完整词表、后处理和空格正规化,不需要 tokenizer.model 或联网加载配置。 + +项目使用 `ort = 2.0.0-rc.11`、`tokenizers = 0.21.4`(锁文件实际解析版本),ONNX 配置记载导出工具 `transformers 4.50.0.dev0`;这几者不代表上游最新版本。本次没有改依赖。 + +## 选用资产 + +基础地址:`https://huggingface.co/onnx-community/siglip2-base-patch16-256-ONNX/resolve/d1114256522a37ffa257a0a58017348ab0058db2`。 + +| 相对路径 | 准确字节数 | 文件 SHA-256 | +|---|---:|---| +| `onnx/vision_model.onnx` | 371992072 | `f5cb16728a704703f05516ded628397e11dbca4de2eb5db04b0c0bcee988aa7a` | +| `onnx/text_model.onnx` | 1129469657 | `d3de4a6bbbfcb429b6615ac496790353cf4a4fc0f19fbbe7179e523ae60daaef` | +| `tokenizer.json` | 34363039 | `cb9140fae3ac5122c972d37adf83e1248471a38147ad76f8215c8872c6fd8322` | + +合计 **1,535,824,768 字节**(约 1.43 GiB)。以上值已与实际匿名公开下载重新计算的 SHA-256、文件长度逐一比对。只下载 FP32 两个独立编码器,不下载整个 11.4 GB 模型仓库、不使用量化变体、不生成修改后的 ONNX。 + +## 实际推理契约 + +| 图 | 输入 | 使用的输出 | +|---|---|---| +| Vision | `pixel_values`, float32 `(1,3,256,256)` | `pooler_output`, float32 `(1,768)` | +| Text | `input_ids`, int64 `(1,64)` | `pooler_output`, float32 `(1,768)` | + +图内还暴露 `last_hidden_state`,不能拿它当最终检索向量。两个池化输出没有 L2 归一化;零图像与示例文本测得范数约 14.08 和 24.39,必须图外归一化后再交给现有余弦点积排名。短文本右填 0,长文本截断保留 EOS。沿用 Rust 的黑底 alpha 合成、双线性正方形缩放。完整真实图像/文本和 Rust 验证见[审计记录](../audit/2026-09-06/semantic-search-model.md)。 + +旧文档曾要求等待 OpenTake 自托管资产,并假定图内归一化;这属于旧占位实现。本次选用公开 ONNX 分离编码器,因此明确更正接口契约;配置中的安装/索引版本升至 2,旧 v1 索引需重新生成。没有改模型名称、维度、分辨率或上下文长度。 + +## 有效性边界 + +已验证 macOS ARM64 CPU 原生 ONNX Runtime 与 Python ONNX Runtime。Windows 使用项目现有 ort-tract 后端,其对这两个实际图的兼容性需要 Windows 运行证据;不能由 macOS 验证推断已通过。公开 Hugging Face 链路在受限网络可能不可达,离线安装可使用相同字节与校验值。 diff --git a/docs/knowledge/2026-09-06-tauri-search-scheduling.md b/docs/knowledge/2026-09-06-tauri-search-scheduling.md new file mode 100644 index 00000000..c2f54580 --- /dev/null +++ b/docs/knowledge/2026-09-06-tauri-search-scheduling.md @@ -0,0 +1,18 @@ +--- +status: canonical +content_stage: implementation-backed +retrieved: 2026-09-06 +freshness_class: normal +valid_until: 2026-09-16 +confidence: high +--- + +# Tauri 搜索任务调度 + +项目使用 Tauri 2(Cargo.lock 解析到 2.11.3)。[官方命令文档](https://v2.tauri.app/develop/calling-rust/#async-commands)说明:默认同步命令在主线程执行;async命令在异步任务执行,适合避免重任务卡住界面。 + +本次真实SigLIP2模型接通后,模型字节校验、初始化和推理是实际重任务。原`search_query`同步完成这些工作,`search_index_start`虽然提交后台worker,但同步等待结果,都会占住调用线程。 + +本轮将两个命令改为async,并通过`spawn_blocking`等待CPU任务。视觉查询复用现有有界`OrtWorker`,优先级为Interactive,索引为Background;两者共享已校验模型的registry,避免并发加载多份大型模型。没有新增调度依赖或另一套模型缓存。`visualError`是向后兼容的可选结果字段,文件名与字幕关键词结果仍保留。 + +此文记录官方执行规则和代码改动;真实安装包的响应性以当前候选验收为准。 diff --git a/docs/modules/INDEX.md b/docs/modules/INDEX.md index 0cb78dbf..0bf1dc45 100644 --- a/docs/modules/INDEX.md +++ b/docs/modules/INDEX.md @@ -31,6 +31,10 @@ web React/TS 前端(只读镜像) - **[opentake-domain](opentake-domain/INDEX.md)** — Timeline/Track/Clip/Keyframe/Transform/Text/Grade 纯值语义;序列化模型。叶子 crate,禁止 I/O。 [总览](opentake-domain/OVERVIEW.md) +### 平台基础 +- **[opentake-process-tree](opentake-process-tree/INDEX.md)** — Unix 进程组 / Windows Job Object 生命周期与取消支持。 + [总览](opentake-process-tree/OVERVIEW.md) + ### 引擎层 - **[opentake-ops](opentake-ops/INDEX.md)** — Overwrite/Ripple/Snap 纯引擎、`EditCommand` 枚举、`apply()` 事务、撤销/重做栈、各 ops 算法(trim/move/split/ripple/link…)。 [总览](opentake-ops/OVERVIEW.md) @@ -38,13 +42,13 @@ web React/TS 前端(只读镜像) ### 能力层 - **[opentake-project](opentake-project/INDEX.md)** — 工程持久化、bundle/archive、布局、FCPXML(XMEML) 导出、生成日志。 [总览](opentake-project/OVERVIEW.md) -- **[opentake-render](opentake-render/INDEX.md)** — RenderPlan(纯函数 Timeline→每帧属性)、wgpu 合成器、文本栅格化;预览与导出像素一致。 +- **[opentake-render](opentake-render/INDEX.md)** — RenderPlan(纯函数 Timeline→每帧属性)、wgpu 合成器、文本栅格化;预览/导出共享语义,像素一致性按场景验收。 [总览](opentake-render/OVERVIEW.md) · [规格 SPEC](opentake-render/SPEC.md) - **[opentake-media](opentake-media/INDEX.md)** — FFmpeg 编解码、缩略图/雪碧图、波形、转写(whisper)、语义搜索(SigLIP2+ort)、节拍/静音/自动裁剪分析。 [总览](opentake-media/OVERVIEW.md) · [规格 SPEC](opentake-media/SPEC.md) - **[opentake-motion](opentake-motion/INDEX.md)** — Lottie / web 动态图形渲染、沙箱、缓存、与渲染管线集成。 [总览](opentake-motion/OVERVIEW.md) · [Motion Graphics 插件设计](opentake-motion/MOTION-GRAPHICS-PLUGIN.md) -- **[opentake-agent](opentake-agent/INDEX.md)** — MCP/Agent(当前最多 44 个可发布工具:38 基础 + 4 生成 + 2 动效,能力门控后按能力发布;KNOWN wire 名共 54)、工具派发、Context Signal、工作流插件、内置 Agent 聊天(Codex/ChatGPT 登录)。 +- **[opentake-agent](opentake-agent/INDEX.md)** — MCP/Agent(工具按基础集合、生成/Motion 桥与凭据动态发布;完整集合以源码和运行期发现为准)、工具派发、Context Signal、工作流插件、内置 Agent 聊天(Codex/ChatGPT 登录)。 [总览](opentake-agent/OVERVIEW.md) · [规格 SPEC](opentake-agent/SPEC.md) · [Context Signal](opentake-agent/AGENT-CONTEXT-SIGNAL.md) · [工作流插件](opentake-agent/WORKFLOW-PLUGIN-SYSTEM.md) - **[opentake-gen](opentake-gen/INDEX.md)** — 生成式 AI 客户端(fal.ai/Replicate/OpenAI/ElevenLabs)、模型目录、生成参数、BYOK 密钥(无后端)。 [总览](opentake-gen/OVERVIEW.md) · [规格 SPEC](opentake-gen/SPEC.md) diff --git a/docs/modules/opentake-agent/INDEX.md b/docs/modules/opentake-agent/INDEX.md index 8938d44c..4268f3db 100644 --- a/docs/modules/opentake-agent/INDEX.md +++ b/docs/modules/opentake-agent/INDEX.md @@ -1,8 +1,11 @@ # opentake-agent — 模块目录 +> 状态:draft · 阶段:implementation-backed · 源码同步:2026-09-06。 +> 当前模块状态见 [OVERVIEW.md](OVERVIEW.md),候选验收见[当日审计](../../audit/2026-09-06/public-beta-validation.md)。规格文档保留设计语义,不作为全功能完成证明。 + > 上级:[模块文档树](../INDEX.md) · [docs 总目录](../../INDEX.md) > -> `opentake-agent` = 工具层(= 上游 `ToolExecutor`,**44 个可发布工具:38 基础 + 4 生成 + 2 动效,能力门控后按能力发布;KNOWN wire 名共 54**)+ MCP server(rmcp Streamable-HTTP,`127.0.0.1:19789/mcp`)+ Context Signal + 工作流插件 + 内置 Agent 提示。能力层:依赖 `domain / ops / core / media / gen`,被 `src-tauri` 集成启动。 +> `opentake-agent` = 工具层(= 上游 `ToolExecutor`,**44 个可发布工具:基础 + 生成 + 动效,能力门控后按能力发布;KNOWN wire 名共 54**)+ MCP server(rmcp Streamable-HTTP,`127.0.0.1:19789/mcp`)+ Context Signal + 工作流插件 + 内置 Agent 提示。能力层:依赖 `domain / ops / core / media / gen`,被 `src-tauri` 集成启动。 --- diff --git a/docs/modules/opentake-agent/OVERVIEW.md b/docs/modules/opentake-agent/OVERVIEW.md index 415f1cf2..c2884680 100644 --- a/docs/modules/opentake-agent/OVERVIEW.md +++ b/docs/modules/opentake-agent/OVERVIEW.md @@ -1,5 +1,10 @@ # opentake-agent — 总览 +> 状态:draft · 阶段:implementation-backed · 源码同步:2026-09-06。 +> 本次定向来源:`crates/opentake-agent/src/tools/`、`mcp/`、`src-tauri/src/external_mcp.rs`。 +> 工具集合按实际主机能力和凭据动态发布,勿混用上游 31、兼容 wire 名和实际可见工具数。Beta 5 已引入外部 MCP 认证配对;透明 Motion 文档工具、链接移动修复在后续源码中存在。 +> 当前验收见[公开 Beta 审计](../../audit/2026-09-06/public-beta-validation.md);下文历史里程碑和测试记录保留其原时点边界。 + > 上级:[模块目录](INDEX.md) · [模块文档树](../INDEX.md) · [docs 总目录](../../INDEX.md) --- @@ -30,7 +35,7 @@ src-tauri 桌面壳:build_registry + server::serve 起 MCP(src-tauri **做什么** -- 定义 **44 个可发布工具**(38 基础 + 4 生成 + 2 动效,能力门控后按能力发布;另有 9 个高级 AI 工具仅 schema 已知不主动发布,KNOWN wire 名共 54)的名称 / 描述 / JSON Schema / 类型化参数(= 上游工具单一事实源)。 +- 定义 **44 个可发布工具**(基础 + 生成 + 动效,能力门控后按能力发布;另有 9 个高级 AI 工具仅 schema 已知不主动发布,KNOWN wire 名共 54)的名称 / 描述 / JSON Schema / 类型化参数(= 上游工具单一事实源)。 - 一条统一派发管线包裹**每个**工具:解析名 → 快照 → 展开短 id → 解码(精确路径错误)→ 跑 body → 附 Context Signal → 缩短 id。 - 把编辑类工具归一到 `opentake-ops::EditCommand`,经 `CoreHandle` 应用到权威 `AppCore`。 - 提供 rmcp transport 组装能力;Beta 2 由官方 Codex 每轮创建带 Bearer 的临时回环 server。 @@ -112,13 +117,12 @@ MCP 客户端 → /mcp (loopback 守卫) → McpServer::call_tool ### 已实现 -- **44 个可发布工具的名称 / 描述 / Schema / 类型化参数**(`tools/`:38 基础 + 4 生成 + 2 动效,能力门控后按能力发布;未就绪能力按主机会话 fail-closed 隐藏)。 +- **可发布工具的名称 / 描述 / Schema / 类型化参数**(`tools/`:基础 + 生成 + 动效,能力门控后按能力发布;未就绪能力按主机会话 fail-closed 隐藏)。 - **统一派发管线**全链路(解析 → 快照 → 短 id 展开 → 解码 → body → 信号 → 短 id 缩短)。 - 编辑类工具接线到 `EditCommand`:`add_clips` / `insert_clips` / `move_clips` / `remove_clips` / `remove_tracks` / `split_clip` / `set_keyframes` / `ripple_delete_ranges` / `add_texts` / `set_clip_properties` / `create_folder` / `move_to_folder` / `rename_media` / `rename_folder` / `delete_media` / `delete_folder` / `undo`,以及 A-tier 效果 `set_color_grade` / `chroma_key` / `set_mask` / `apply_effect`。 - 读类工具:`get_timeline`(紧凑编码)/ `get_media` / `list_folders` / `list_models`(读 `opentake-gen` 静态目录,纯本地)。 - 分析驱动工具:`detect_beats` / `auto_cut_to_beats` / `tighten_silences` / `remove_filler_words`(PCM 或词级转写分析,**返回预览/建议,不直接落地**——`applied:false`,由模型审阅后再调编辑工具落地)。 -- **MCP server**:rmcp Streamable-HTTP + loopback/Origin 守卫 + Bearer gate;Beta 2 由 - `src-tauri/src/codex.rs` 按官方 Codex 轮次临时启动,固定外部 listener 不启动。 +- **MCP server**:rmcp Streamable-HTTP + loopback/Origin 守卫 + Bearer gate;官方 Codex 轮次由 `src-tauri/src/codex.rs` 临时启动;外部客户端由 `src-tauri/src/external_mcp.rs` 管理显式配对与认证 listener。 - **Context Signal**:视频类型自动判定 / 轨道角色检测 + 逐轨建议 / 剪辑阶段推断 + 阶段指引 / 内置规则告警 + 插件规则。 - **工作流插件**:JSON 模型 + 注册表(扫描/校验/激活)+ 内置 `audio-first` + 三个工作流工具(`list_workflows` / `activate_workflow` / `deactivate_workflow`)。 - **系统提示**:分段 base + 插件围栏注入。 @@ -133,7 +137,7 @@ MCP 客户端 → /mcp (loopback 守卫) → McpServer::call_tool - `create_folder` / `move_to_folder` 的批量 `entries` 形式未接线(仅单条形式)。 - **应用内聊天客户端**(`AgentService` 等价的 SSE 工具循环、BYOK 直连)已随 Beta 1 交付内置 Agent 聊天面板(`chat/` 模块 + `ChatLoop` / `ChatTurnGate`),并接入官方 Codex CLI / ChatGPT 登录(逐轮临时 loopback MCP,Beta 2)。剩余:ChatGPT 凭据由官方 CLI 独占,应用内不持久化。 -## 工具总数:**最多 44 个可发布工具(当前 38 基础 + 4 生成 + 2 动效,能力门控后按能力发布);KNOWN wire 名共 54(含 9 个高级 AI 工具,仅 schema 已知、不主动发布)** +## 工具总数:**最多 44 个可发布工具(当前 基础 + 生成 + 动效,能力门控后按能力发布);KNOWN wire 名共 54(含 9 个高级 AI 工具,仅 schema 已知、不主动发布)** 源:`crates/opentake-agent/src/tools/names.rs` 的 `ALL`(38)/ `GENERATION`(4)/ `MOTION`(2)/ `KNOWN`(54)/ `UPSTREAM`(31)常量。 diff --git a/docs/modules/opentake-agent/SPEC.md b/docs/modules/opentake-agent/SPEC.md index 4ff0ed6a..8096a8eb 100644 --- a/docs/modules/opentake-agent/SPEC.md +++ b/docs/modules/opentake-agent/SPEC.md @@ -1,5 +1,8 @@ # opentake-agent 实现就绪规格(Issue #9) +> 状态:draft · 阶段:partial-implementation · 设计与早期实现来源保留;原文日期、行号和“待做”属于设计时点。2026-09-06 当前实现见本模块 [OVERVIEW.md](OVERVIEW.md),当前验收见[公开 Beta 记录](../../audit/2026-09-06/public-beta-validation.md)。 + + > **Beta 2 安全裁决(2026-08-03):**本文保留上游固定端口方案作为设计来源与测试合同, > 但其“默认启动、未认证 `127.0.0.1:19789`、外部客户端直连”不再是产品运行合同。 > 当前产品仅由官方 Codex / ChatGPT 每轮创建随机 loopback 端口、256-bit Bearer、工程绑定的 @@ -9,7 +12,7 @@ > 短 ID 系统 + 统一执行壳 + 面向 LLM 的精确路径错误 + 应用内 chat + > **Agent Context Signal 注入** + **Workflow Plugin 系统**。 > -> 设计来源(已逐行核读):上游 `palmier-pro-upstream/Sources/PalmierPro/Agent/`(29 文件),以及 OpenTake `docs/AGENT-CONTEXT-SIGNAL.md`、`docs/WORKFLOW-PLUGIN-SYSTEM.md`、`docs/ARCHITECTURE.md §7/§9`、`docs/MODULE-PORT-MAP.md`「Agent」、`docs/_analysis/04-MCP与Agent工具.md`、`docs/ROADMAP.md` Phase 7/S/W。 +> 设计来源(已逐行核读):上游 `palmier-pro-upstream/Sources/PalmierPro/Agent/`(29 文件),以及 OpenTake `docs/AGENT-CONTEXT-SIGNAL.md`、`docs/WORKFLOW-PLUGIN-SYSTEM.md`、`docs/architecture/ARCHITECTURE.md §7/§9`、`docs/architecture/MODULE-PORT-MAP.md`「Agent」、`docs/_analysis/04-MCP与Agent工具.md`、`docs/architecture/ROADMAP.md` Phase 7/S/W。 > > 核心架构原则(上游验证,OpenTake 照搬):**编辑能力只有一处真实定义**(`opentake-core` 的 `EditCommand` 路由 → `opentake-ops`),**MCP server 与应用内 chat 是它的两个对等前端**,不写两套。Agent 层「非常薄」——31 个工具是 `opentake-core` 命令的薄包装;真正的编辑算法在 `opentake-ops`/`opentake-domain`(不在本 crate)。 > @@ -34,10 +37,10 @@ | 后端代理客户端(计费通道,OpenTake 替换) | `…/Agent/Clients/PalmierClient.swift` | `endpoint v1/agent/stream :35`;错误信封 `:80-101` | | get_timeline 编码(轨道/clip 结构 + 压缩规则,Context Signal 检测依据) | `…/Agent/Tools/ToolExecutor+Timeline.swift` | `getTimeline :17`;`trackDefaults :60`(`muted/hidden/syncLocked`);`clipDefaults :62`;`compactTrack :73`;`compactClip :112` | | add_clips 行为(覆写、自动建轨、linked audio、全有或全无) | `…/Agent/Tools/ToolExecutor+Clips.swift` | `AddClipsInput :5`;`addClips :13`;`Mixed trackIndex :171-174`;`insertTrack :194/:199`;`clearRegion+placeClip :225-226` | -| OpenTake 目标 crate 边界 + §7 MCP 设计 | `/Users/lvbaiqing/TRUE 开发/PRIMARY-CN/OpenTake/docs/ARCHITECTURE.md` | crate 布局 `:64-87`;`EditCommand/EditResult :105-116`;§7 `:148-154`;§9 目录 `:165-177` | +| OpenTake 目标 crate 边界 + §7 MCP 设计 | `/Users/lvbaiqing/TRUE 开发/PRIMARY-CN/OpenTake/docs/architecture/ARCHITECTURE.md` | crate 布局 `:64-87`;`EditCommand/EditResult :105-116`;§7 `:148-154`;§9 目录 `:165-177` | | Context Signal 全设计 | `/Users/lvbaiqing/TRUE 开发/PRIMARY-CN/OpenTake/docs/AGENT-CONTEXT-SIGNAL.md` | 发射时机表 `:37-47`;数据结构 `:50-83`;插件叠加 `:88-98`;类型检测 `:104-140`;轨道角色 `:148-173`;规则 `:177-203` | | Workflow Plugin 全设计 | `/Users/lvbaiqing/TRUE 开发/PRIMARY-CN/OpenTake/docs/WORKFLOW-PLUGIN-SYSTEM.md` | 目录 `:18-24`;plugin.json schema `:28-96`;激活 `:100-104`;影响 Agent `:108-118`;与 Core 关系 `:136-141` | -| Phase 7 / S / W 验证标准 | `/Users/lvbaiqing/TRUE 开发/PRIMARY-CN/OpenTake/docs/ROADMAP.md` | Phase 7 `:52-59`;Phase S `:99-110`;Phase W `:113-119` | +| Phase 7 / S / W 验证标准 | `/Users/lvbaiqing/TRUE 开发/PRIMARY-CN/OpenTake/docs/architecture/ROADMAP.md` | Phase 7 `:52-59`;Phase S `:99-110`;Phase W `:113-119` | --- diff --git a/docs/modules/opentake-agent/mcp-server.md b/docs/modules/opentake-agent/mcp-server.md index cb55d53e..7d2b84ec 100644 --- a/docs/modules/opentake-agent/mcp-server.md +++ b/docs/modules/opentake-agent/mcp-server.md @@ -1,5 +1,8 @@ # mcp-server — rmcp MCP server 网络面 +> 状态:draft · 阶段:implementation-backed · 源码同步:2026-09-06。 +> 实际桌面认证与候选验证见[当日审计](../../audit/2026-09-06/public-beta-validation.md)。 + > 上级:[模块目录](INDEX.md) · [总览](OVERVIEW.md) · [docs 总目录](../../INDEX.md) > > 源码:[`../../../crates/opentake-agent/src/mcp/server.rs`](../../../crates/opentake-agent/src/mcp/server.rs) @@ -17,7 +20,7 @@ `McpServer` 实现 rmcp 的 `ServerHandler`,持有一个 `Arc`(自带会话级 agent-undo 栈)+ 构造时快照的系统提示 `instructions`。 - **`get_info`** — 广告 `instructions`(base 提示 + 激活插件,构造时由 [`assemble_system_prompt`](prompt.md) 生成)与 tools 能力;`server_info.name = "opentake"`,版本取 `CARGO_PKG_VERSION`。 -- **`list_tools`** — 从最多 39 个基础工具(`ToolName::ALL`)按当前主机的媒体桥能力过滤,在授权可用时追加 4 个生成工具,并在桌面 Motion 渲染桥可用时追加 add/edit 两工具;描述/Schema 来自 [`tools::descriptions`](dispatch-tools.md)。 +- **`list_tools`** — 从基础工具集合(`ToolName::ALL`)按当前主机的媒体桥能力过滤,在授权可用时追加 4 个生成工具,并在桌面 Motion 渲染桥可用时追加 add/edit 两工具;描述/Schema 来自 [`tools::descriptions`](dispatch-tools.md)。 - **`call_tool`** — 把工具调用交给 `Dispatcher::dispatch`。因为所有已接线工具是同步的,用 `tokio::task::spawn_blocking` 在阻塞线程池跑,避免堵住 async 运行时;结果经 [`convert::to_call_tool_result`](core-handle-convert.md) 转成 rmcp `CallToolResult`。 - **`call`** — 与 `call_tool` 等价的同步入口,单独拆出以便**不构造传输 `RequestContext`** 就能单测一次工具派发。 @@ -26,19 +29,19 @@ `build_router` 组装 axum 路由: - `nest_service("/mcp", StreamableHttpService::new(...))` —— 每次会话用 `McpServer::new(handle, registry)` 新建(`LocalSessionManager` 管理会话)。 -- `GET /.well-known/oauth-protected-resource` —— 返回 `{ resource: "opentake", authorization_servers: [] }`,让探测客户端得到明确的"无需鉴权"回答(服务仅回环,故不挂任何授权服务器)。 +- `GET /.well-known/oauth-protected-resource` —— 返回 `{ resource: "opentake", authorization_servers: [] }`,该发现信息不列出 OAuth authorization server;不能由空数组推导为桌面端无需 Bearer 认证。 - 整条路由外层 `from_fn(localhost_guard)`。 -`serve(addr, handle, registry)` 绑定回环 `TcpListener` 并 `axum::serve` 到进程退出。`DEFAULT_ADDR = "127.0.0.1:19789"`(端口沿用上游)。 +`build_router*` 是库层装配面;桌面生产路径使用 gated/authorized transport。官方 Codex 逐轮临时启动认证 listener,Beta 5 外部 MCP 使用显式配对和可撤销凭据。`DEFAULT_ADDR = "127.0.0.1:19789"` 是默认常量,不能等同于默认启动的未认证服务。生命周期见 `src-tauri/src/codex.rs` 与 `src-tauri/src/external_mcp.rs`。 ### 回环 Origin/Host 守卫(DNS-rebinding 防御) `localhost_guard` 中间件检查请求头: -- `Host` 与 `Origin` **若存在**必须指向回环;**缺省即放行**(原生 MCP 客户端常不带 `Origin`)。 +- Host/Origin 的缺省、重复值、authority 与端口按 `localhost_guard`、`host_is_local` 和 `origin_is_local` 的实际规则校验;认证由外层 Bearer authorizer 与工程/请求 gate 完成。 - 存在但非回环 → `403 "non-local Origin/Host rejected"`。 -`host_is_local` 解析规则:剥协议(`http://host:port` 形式)→ 剥路径/查询 → 剥端口(IPv6 括号形式 `[::1]:port` 单独处理)→ 匹配 `localhost` / `127.0.0.1` / `::1`。这是防 DNS-rebinding 把本地回环服务暴露给 LAN/网页的关键(对应上游 `NWParameters.requiredLocalEndpoint` 锁回环)。 +`host_is_local` 解析规则:剥协议(`http://host:port` 形式)→ 剥路径/查询 → 剥端口(IPv6 括号形式 `[::1]:port` 单独处理)→ 匹配 `localhost` / `127.0.0.1` / `::1`。这是回环请求校验的一部分,不能替代 Bearer、工程身份和请求边界检查(对应上游 `NWParameters.requiredLocalEndpoint` 锁回环)。 ## 数据流 diff --git a/docs/modules/opentake-core/INDEX.md b/docs/modules/opentake-core/INDEX.md index 5add89c6..928207fe 100644 --- a/docs/modules/opentake-core/INDEX.md +++ b/docs/modules/opentake-core/INDEX.md @@ -1,5 +1,8 @@ # opentake-core — 模块目录 +> 状态:draft · 阶段:implementation-backed · 源码同步:2026-09-06。 +> 当前模块状态见 [OVERVIEW.md](OVERVIEW.md),候选验收见[当日审计](../../audit/2026-09-06/public-beta-validation.md)。规格文档保留设计语义,不作为全功能完成证明。 + > 上级:[模块文档树](../INDEX.md) · [docs 总目录](../../INDEX.md) > > `opentake-core` = **装配层(命令路由层)**:把 `opentake-{domain,ops,project}` + 注入式能力句柄装配成一个权威可观测的会话 `EditorSession`,对 UI / Agent / MCP 三客户端暴露唯一编辑入口 `AppCore::apply`,经版本号 + 事件广播驱动前端只读镜像。依赖只向下:依赖 `domain` / `ops` / `project`,被 `src-tauri` 调用(`opentake-agent` 也作为客户端持其句柄)。 diff --git a/docs/modules/opentake-core/OVERVIEW.md b/docs/modules/opentake-core/OVERVIEW.md index 7de65571..6f1f79ea 100644 --- a/docs/modules/opentake-core/OVERVIEW.md +++ b/docs/modules/opentake-core/OVERVIEW.md @@ -1,5 +1,10 @@ # opentake-core 总览 +> 状态:draft · 阶段:implementation-backed · 源码同步:2026-09-06。 +> 本次定向来源:`crates/opentake-core/src/`。 +> 继续持有工程/会话、命令编排和能力桥;本轮 Motion 媒体目录保留及链接行为修复沿用工程身份与事务边界。下文较早 API 列表是结构参考,完整注册/变体以源码为准。 +> 当前验收见[公开 Beta 审计](../../audit/2026-09-06/public-beta-validation.md);下文历史里程碑和测试记录保留其原时点边界。 + > 上级:[模块文档树](../INDEX.md) · [docs 总目录](../../INDEX.md) · 本模块目录:[INDEX.md](INDEX.md) ## 一句话定位 diff --git a/docs/modules/opentake-core/SPEC.md b/docs/modules/opentake-core/SPEC.md index 4b679f70..002236a0 100644 --- a/docs/modules/opentake-core/SPEC.md +++ b/docs/modules/opentake-core/SPEC.md @@ -1,5 +1,8 @@ # opentake-core 历史合订规格(Issue #11) +> 状态:draft · 阶段:partial-implementation · 设计与早期实现来源保留;原文日期、行号和“待做”属于设计时点。2026-09-06 当前实现见本模块 [OVERVIEW.md](OVERVIEW.md),当前验收见[公开 Beta 记录](../../audit/2026-09-06/public-beta-validation.md)。 + + > **状态:历史快照,不是当前实现的权威来源。** 本文件保留 Issue #11 时的原始合订设计,部分类型名、字段草案和 Undo/Redo 形态已与代码不同。当前可执行规格以 [`docs/specs/core/`](../../specs/core/) 的拆分文件为准;共享编辑状态与命令路由请直接阅读 [`1-editor-state.md`](../../specs/core/1-editor-state.md) 和 [`2-command-routing.md`](../../specs/core/2-command-routing.md)。审计与验收不得用本历史快照覆盖拆分规格或当前代码证据。 > **范围**:`crates/opentake-core/` —— EditorState 组装、命令路由(= 上游单一能力层)、事件总线、Tauri 边界契约。 @@ -17,7 +20,7 @@ > - `Utilities/Constants.swift:105-115`(工程目录包文件名常量) > - `Export/ExportService.swift` + `Export/ExportView.swift:13-26`(导出表面) > - `Agent/Tools/ToolExecutor+Clips.swift:129-209`(写工具样板:decode→validate→withUndoGroup→mutate) -> 以及 `docs/ARCHITECTURE.md` §2/§5/§7 与 `docs/ROADMAP.md` Phase 6/7。 +> 以及 `docs/architecture/ARCHITECTURE.md` §2/§5/§7 与 `docs/architecture/ROADMAP.md` Phase 6/7。 --- @@ -371,7 +374,7 @@ impl EditorCore { ## 5. 与 ops / project / render / agent 的装配关系 -`opentake-core` 是**装配中枢**(ARCHITECTURE §3:「`opentake-core/` # 组装:EditorState…、command 路由、事件总线」)。依赖法则(ARCHITECTURE §3 末「依赖法则」):`domain` 零依赖叶子;`ops` 只依赖 `domain`;`command` 是唯一编辑入口;UI/Agent/MCP 是三个对等客户端。 +`opentake-core` 是**装配中枢**(ARCHITECTURE §3:「`opentake-core/` # 组装:EditorState…、command 路由、事件总线」)。依赖法则(ARCHITECTURE §3 末「依赖法则」):`domain` 无 I/O 叶子;`ops` 只依赖 `domain`;`command` 是唯一编辑入口;UI/Agent/MCP 是三个对等客户端。 ### 5.1 依赖方向(谁依赖谁) diff --git a/docs/modules/opentake-domain/INDEX.md b/docs/modules/opentake-domain/INDEX.md index 35c0caca..b4d1ec5f 100644 --- a/docs/modules/opentake-domain/INDEX.md +++ b/docs/modules/opentake-domain/INDEX.md @@ -1,5 +1,8 @@ # opentake-domain — 模块目录 +> 状态:draft · 阶段:implementation-backed · 源码同步:2026-09-06。 +> 当前模块状态见 [OVERVIEW.md](OVERVIEW.md),候选验收见[当日审计](../../audit/2026-09-06/public-beta-validation.md)。规格文档保留设计语义,不作为全功能完成证明。 + > 上级:[模块文档树](../INDEX.md) · [docs 总目录](../../INDEX.md) > > 值语义叶子层(依赖只有 `serde`,禁 `std::fs` / 网络)。被 `opentake-ops` 及其上所有层依赖。 diff --git a/docs/modules/opentake-domain/OVERVIEW.md b/docs/modules/opentake-domain/OVERVIEW.md index 878ec1d6..36c7ee54 100644 --- a/docs/modules/opentake-domain/OVERVIEW.md +++ b/docs/modules/opentake-domain/OVERVIEW.md @@ -1,5 +1,11 @@ # opentake-domain — 总览 +> 状态:draft · 阶段:implementation-backed · 源码同步:2026-09-06。 +> 本次定向来源:`crates/opentake-domain/Cargo.toml`、`src/clip.rs`、`src/timeline.rs`。 +> 领域 crate 无 I/O、仅依赖 serde(开发依赖另计);它不是零依赖 crate。帧/关键帧与兼容模型是值层契约,不承担 GUI 与发布验收。 +> 当前验收见[公开 Beta 审计](../../audit/2026-09-06/public-beta-validation.md);下文历史里程碑和测试记录保留其原时点边界。 + + > 模块目录:[INDEX.md](INDEX.md) · 上级:[模块文档树](../INDEX.md) · [docs 总目录](../../INDEX.md) --- diff --git a/docs/modules/opentake-gen/INDEX.md b/docs/modules/opentake-gen/INDEX.md index 05a9a624..66ca4c7a 100644 --- a/docs/modules/opentake-gen/INDEX.md +++ b/docs/modules/opentake-gen/INDEX.md @@ -1,5 +1,8 @@ # opentake-gen — 模块目录 +> 状态:draft · 阶段:implementation-backed · 源码同步:2026-09-06。 +> 当前模块状态见 [OVERVIEW.md](OVERVIEW.md),候选验收见[当日审计](../../audit/2026-09-06/public-beta-validation.md)。规格文档保留设计语义,不作为全功能完成证明。 + > 上级:[模块文档树](../INDEX.md) · [docs 总目录](../../INDEX.md) > > 生成式 AI 客户端(**BYOK 无后端**:自带 key 直连 fal.ai/Replicate/OpenAI/ElevenLabs + 内置静态模型目录;可选托管 proxy)。能力层叶子 crate,仅依赖 `opentake-domain`,被 `opentake-agent` / `src-tauri` 调用。 diff --git a/docs/modules/opentake-gen/OVERVIEW.md b/docs/modules/opentake-gen/OVERVIEW.md index df3ba682..b03e5245 100644 --- a/docs/modules/opentake-gen/OVERVIEW.md +++ b/docs/modules/opentake-gen/OVERVIEW.md @@ -1,5 +1,10 @@ # opentake-gen — 模块总览 +> 状态:draft · 阶段:implementation-backed · 源码同步:2026-09-06。 +> 本次定向来源:`crates/opentake-gen/src/`、`src-tauri/src/generation.rs`。 +> provider adapter、能力/凭据检查与生成作业实现和真实付费 provider 验收分开记录;无需因本轮文档同步重跑付费任务。源代码中的 provider 接入不能写成全部账号开箱即用。 +> 当前验收见[公开 Beta 审计](../../audit/2026-09-06/public-beta-validation.md);下文历史里程碑和测试记录保留其原时点边界。 + > 上级:[opentake-gen 目录](INDEX.md) · [模块文档树](../INDEX.md) · [docs 总目录](../../INDEX.md) > > 模块/子系统级总览(不逐函数)。完整规格见 [SPEC.md](SPEC.md)(只读,本总览只链接、不复述)。 @@ -94,7 +99,7 @@ GenerationInput(持久化) + uploaded URLs - `HttpTransport` + `ReqwestTransport` / `MockTransport`(全套测试零 socket)。 - **已接线**:`list_models` 工具已从存根接到内置静态目录(agent `mcp/gen_catalog.rs`,ROADMAP #111);BYOK 钥匙串 save/load/delete Tauri 命令(聊天 LLM key)。 -**计划中 / 后续版本(Beta 1/2 已交付之外):** +**集成边界与后续核对:** - **`generate_*` / `upscale_media` 已接线**:agent `dispatch.rs` 的四个生成工具经 `GenerationBridge`(异步 job + BYOK/托管授权 + 成本确认 + 进度/取消/重试/恢复 + 产物安全下载落库)接入;存在兼容凭据时动态进入 MCP/Chat 发现面,无凭据 fail-closed 隐藏(ROADMAP Phase 9 / Phase 7 进度注记)。 - **托管 proxy `opentake-gen-proxy` 未实现**(Phase 9 自建后端:`/v1/models`、`/v1/generations`、`/v1/uploads/sign`、SSE stream、对象存储预签名、可选积分计费;SPEC §3)。客户端侧已就绪,等服务端;Beta 阶段以 BYOK 直连为准。 diff --git a/docs/modules/opentake-gen/SPEC.md b/docs/modules/opentake-gen/SPEC.md index 91e83442..ba3de284 100644 --- a/docs/modules/opentake-gen/SPEC.md +++ b/docs/modules/opentake-gen/SPEC.md @@ -1,5 +1,8 @@ # gen-SPEC — opentake-gen 实现就绪规格(Issue #10) +> 状态:draft · 阶段:partial-implementation · 设计与早期实现来源保留;原文日期、行号和“待做”属于设计时点。2026-09-06 当前实现见本模块 [OVERVIEW.md](OVERVIEW.md),当前验收见[公开 Beta 记录](../../audit/2026-09-06/public-beta-validation.md)。 + + > 状态:实现就绪(implementation-ready)。本规格基于上游 Swift 真实源码逐字段复刻,每个契约点给出 `文件:行号` 证据。 > 范围:`crates/opentake-gen`(BYOK 生成客户端 + 静态 catalog + provider adapters)与可选的 `opentake-gen-proxy`(axum 托管模式后端)。 > 约束遵循 OpenTake `AGENTS.md`:内部错误用 `anyhow::Error`,边界层转 `Err(String)`;所有 serde 模型加 `#[serde(default)]` + `Option` 以读旧数据不破坏;注释最小化。 @@ -57,7 +60,7 @@ bytes = "1" opentake-domain = { workspace = true } # GenerationInput 等共享类型(见 §5) ``` -> 说明:`opentake-domain` 是零依赖叶子 crate(`crates/opentake-domain/src/lib.rs:1-8`),不允许网络/FS。因此 **`GenerationInput` 放 domain,`GenerationParams`/`GenClient`/adapters 放 opentake-gen**,方向是 gen → domain。详见 §5.1。 +> 说明:`opentake-domain` 是无 I/O 叶子 crate(`crates/opentake-domain/src/lib.rs:1-8`),不允许网络/FS。因此 **`GenerationInput` 放 domain,`GenerationParams`/`GenClient`/adapters 放 opentake-gen**,方向是 gen → domain。详见 §5.1。 ### 1.1 顶层客户端 @@ -739,7 +742,7 @@ pub struct UpscaleCaps { ### 5.1 GenerationInput 落在 opentake-domain(逐字段复刻 `MediaManifest.swift:36-63`) -`GenerationInput` 是**持久化到工程文件**的领域类型(上游存于 `MediaManifestEntry.generationInput` `MediaManifest.swift:26`),故放零依赖 `opentake-domain`,按 `AGENTS.md:62` 全字段 `#[serde(default)]` + `Option`: +`GenerationInput` 是**持久化到工程文件**的领域类型(上游存于 `MediaManifestEntry.generationInput` `MediaManifest.swift:26`),故放无 I/O 的 `opentake-domain`,按 `AGENTS.md:62` 全字段 `#[serde(default)]` + `Option`: ```rust // crates/opentake-domain/src/generation.rs(新增;domain 当前无此文件) @@ -888,6 +891,6 @@ pub struct GenerationInput { - `…/OpenTake/Cargo.toml` — workspace 含 `opentake-gen`/`opentake-domain`/`opentake-agent`(`members`);`serde`/`serde_json` 在 `[workspace.dependencies]` - `…/OpenTake/crates/opentake-gen/{Cargo.toml,src/lib.rs}` — 当前为 Phase 0 空脚手架(`[dependencies]` 为空 / lib 仅 `crate_compiles` 测试) -- `…/OpenTake/crates/opentake-domain/{Cargo.toml,src/lib.rs}` — 零依赖叶子 crate(仅依赖 serde;声明“Zero IO”) +- `…/OpenTake/crates/opentake-domain/{Cargo.toml,src/lib.rs}` — 无 I/O 叶子 crate(仅依赖 serde;声明“Zero IO”) - `…/OpenTake/crates/opentake-agent/src/lib.rs` — 工具层 + MCP(rmcp) + chat 客户端(peer clients) - `…/OpenTake/AGENTS.md` — Rust 风格铁律(`anyhow`→边界 `Err(String)`;domain 零网络/FS;serde `default`+`Option`;≥80% 覆盖) diff --git a/docs/modules/opentake-media/INDEX.md b/docs/modules/opentake-media/INDEX.md index 83648d28..4035ff92 100644 --- a/docs/modules/opentake-media/INDEX.md +++ b/docs/modules/opentake-media/INDEX.md @@ -1,5 +1,8 @@ # opentake-media — 模块目录 +> 状态:draft · 阶段:implementation-backed · 源码同步:2026-09-06。 +> 当前模块状态见 [OVERVIEW.md](OVERVIEW.md),候选验收见[当日审计](../../audit/2026-09-06/public-beta-validation.md)。规格文档保留设计语义,不作为全功能完成证明。 + > 上级:[模块文档树](../INDEX.md) · [docs 总目录](../../INDEX.md) > > `opentake-media` = 媒体读取与离线分析层:ffmpeg sidecar 探测/解码/编码、缩略图/雪碧图、波形、转写(whisper)、SigLIP2 语义搜索、节拍/静音/自动裁剪分析、全局素材库。依赖只向下:仅依赖 `opentake-domain`,被 `opentake-core` / `src-tauri` / `opentake-agent` / `opentake-render` 调用。运行期需 **FFmpeg ≥ 6.0 在 PATH**。 diff --git a/docs/modules/opentake-media/OVERVIEW.md b/docs/modules/opentake-media/OVERVIEW.md index 98543566..1f4be234 100644 --- a/docs/modules/opentake-media/OVERVIEW.md +++ b/docs/modules/opentake-media/OVERVIEW.md @@ -1,5 +1,10 @@ # opentake-media — 模块总览 +> 状态:draft · 阶段:implementation-backed · 源码同步:2026-09-06。 +> 本次定向来源:`crates/opentake-media/src/search/config.rs`、`encode/`、`ffmpeg/`。 +> 包含 ProRes 4444、完整轨道 PCM 抽取和媒体索引。语义搜索模型安装修复已完成:约 1.5 GB 固定 revision 资产校验、macOS 真实 Rust 离线安装/图文 embedding/排名已验证。使用前需安装模型;Windows ort-tract 真实图及新包 UI 待验收,见[专项审计](../../audit/2026-09-06/semantic-search-model.md)。 +> 当前验收见[公开 Beta 审计](../../audit/2026-09-06/public-beta-validation.md);下文历史里程碑和测试记录保留其原时点边界。 + > 上级:[模块目录 INDEX.md](INDEX.md) · [模块文档树](../INDEX.md) · [docs 总目录](../../INDEX.md) --- @@ -101,15 +106,15 @@ opentake-core / src-tauri / opentake-agent / opentake-render 调用方 - 探测 / 解码帧 / 抽 PCM / ffmpeg sidecar 封装(含旋转校正、零声道音轨防幻影链接)。 - 视频缩略图序列 + JPEG 雪碧图磁盘缓存(与上游 key/meta 互读)、图片单缩略图。 - 波形(ffmpeg PCM → RMS → 归一化 + `.waveform` 缓存)。 -- 编码器 + 预设表(H.264/H.265/ProRes)+ 线性音频混音(逐 clip 偏移 + 增益 + 硬限幅,第二趟 ffmpeg mux AAC / `-shortest`,mux 失败回退视频-only)。 +- 编码器 + 预设表(H.264/H.265/ProRes)+ 线性音频混音(逐 clip 偏移 + 增益 + 硬限幅,第二趟 ffmpeg mux AAC / `-shortest`,mux 失败按错误/清理路径返回,不静默交付无音轨结果)。 - 转写数据模型 + locale 匹配 + 双层缓存 + 转写内关键词搜索(纯逻辑全测);whisper 后端在 `whisper-backend` feature 后。 - 语义搜索全链路纯函数(预处理 / tokenize / 视觉去重抽帧 / 索引累积 / `PALMEMB1` 存储 / 排名 / 模型下载校验);ort 后端在 `ort-backend` feature 后;默认 build 用 mock 离线可测。 - 节拍检测、静音检测、自动裁剪(黑边)。 - 全局素材库(内容寻址去重 + 原子 manifest,#104;Tauri 命令层在 src-tauri,#106)。 - 后台索引调度内核(`work_needed` / `visual_share` / `ExportPause`)。 -**计划中 / 后续版本(Beta 1/2 已交付之外):** -- **whisper / ort 后端真实接线**:whisper-rs 本地转写(word/segment 时间戳)已交付;SigLIP2 + ort 语义搜索的推理/索引/排名链路已接入产品路径(Captions / Moments / `search_media`)。⚠️ **SigLIP2 ONNX 模型资产待外部托管**:`search/config.rs` 的 `Manifest` sha256/bytes 仍为空占位(`MODEL_DOWNLOAD_BASE_URL` 指向 `huggingface.co/opentake/siglip2-base-patch16-256-onnx`),在 ONNX 资产正式托管并填实校验值前,`download_search_model`(Tauri 命令)/ `model_download::install` 无法通过 SHA-256 校验完成真实下载——语义搜索**不是开箱即用**,模型首次下载需联网。 +**集成边界与后续核对:** +- **whisper / ort 后端真实接线**:whisper-rs 本地转写(word/segment 时间戳)已交付;SigLIP2 + ort 语义搜索的推理/索引/排名链路已接入产品路径(Captions / Moments / `search_media`)。**模型安装与真实 Rust 链路已验证**:固定 revision 资产逐文件校验通过;Cargo media search 84 passed / 1 ignored、Tauri search 14 passed,引用实际产品模块的真实模型 Rust harness 72 passed。各组覆盖有重叠,不合计为独立测试数。Windows ort-tract 真实图及新包 UI 尚待验收,证据见[专项审计](../../audit/2026-09-06/semantic-search-model.md)。 - **自动裁剪升级**:当前 `autocrop` 仅做黑边/透明区扫描,**未集成人脸/显著性 ML**(SPEC 的 `smart_reframe` 完整语义为后续版本)。 - **编码导出**:H.264/H.265/ProRes + 进度/取消已随 Beta 1 交付(`src-tauri/export.rs`,预览与导出共享 RenderPlan);音频重采样曲线 / pan / 立体声 / 动态处理为后续。 - **进阶 AI 推理(ADVANCED-FEATURES B 层,复用 `ort_worker`)**:Beta 1 已交付 RVM 抠像、防抖、补帧、智能擦除、参考色彩匹配与可视化运动追踪;超分 / 真实显著性自动裁剪为后续。 diff --git a/docs/modules/opentake-media/SPEC.md b/docs/modules/opentake-media/SPEC.md index 36b6f8b7..2fa588f1 100644 --- a/docs/modules/opentake-media/SPEC.md +++ b/docs/modules/opentake-media/SPEC.md @@ -1,5 +1,8 @@ # opentake-media 实现就绪规格 (Issue #8) +> 状态:draft · 阶段:partial-implementation · 设计与早期实现来源保留;原文日期、行号和“待做”属于设计时点。2026-09-06 当前实现见本模块 [OVERVIEW.md](OVERVIEW.md),当前验收见[公开 Beta 记录](../../audit/2026-09-06/public-beta-validation.md)。 + + > 范围:`crates/opentake-media`。把上游基于 AVFoundation / DSWaveformImage / macOS 26 Speech / CoreML 的媒体读取层,移植为跨平台 Rust:**ffmpeg-next 解码/编码/缩略图/抽 PCM、Symphonia 波形、whisper-rs 转写、candle/ort + SigLIP2 语义搜索、ort 通用推理 worker**。本 crate 是媒体**读取与离线分析**层,不含 wgpu 帧合成器(那在 `opentake-render`,见 §9 边界)。 > > 状态:设计规格。对应 ROADMAP **Phase 2**(缩略图/波形,易)与 **Phase 8**(转写/语义搜索/进阶 AI worker)。本 crate 在 workspace 已有空壳 `crates/opentake-media/{Cargo.toml,src/lib.rs}`(`crate_compiles` 占位测试)。 @@ -8,8 +11,8 @@ > - 解码/编码/缩略图/PCM:`palmier-pro-upstream/Sources/PalmierPro/Preview/{ImageVideoGenerator,AlphaVideoNormalizer,TimelineRenderer}.swift`、`Transcription/Transcription.swift`(`extractAudioTrack`)、`Timeline/MediaVisualCache.swift`(缩略图 sprite + 波形 + 磁盘缓存)。 > - 转写:`Transcription/{Transcription,TranscriptCache,TranscriptSearch}.swift`。 > - 语义搜索:`Search/{SearchIndexConfig,SearchIndexCoordinator}.swift`、`Search/Models/{VisualEmbedder,VisualModelLoader,ModelDownloader,TextTokenizer}.swift`、`Search/Indexing/{FrameSampler,VisualIndexer,EmbeddingStore}.swift`、`Search/Query/VisualSearch.swift`。 -> - 横切分析:`docs/_analysis/02-苹果框架可移植性.md`、`docs/_analysis/01-架构与数据流.md`、`docs/MODULE-PORT-MAP.md`(行级算法笔记 L833–883、L923–940、L1211)。 -> - 架构/相位:`docs/ARCHITECTURE.md` §1/§6/§7、`docs/ROADMAP.md` Phase 2/8、`docs/ADVANCED-FEATURES.md`(ort worker 复用方)。 +> - 横切分析:`docs/_analysis/02-苹果框架可移植性.md`、`docs/_analysis/01-架构与数据流.md`、`docs/architecture/MODULE-PORT-MAP.md`(行级算法笔记 L833–883、L923–940、L1211)。 +> - 架构/相位:`docs/architecture/ARCHITECTURE.md` §1/§6/§7、`docs/architecture/ROADMAP.md` Phase 2/8、`docs/architecture/ADVANCED-FEATURES.md`(ort worker 复用方)。 > - 领域契约:`crates/opentake-domain/src/{media,clip_type,timeline,clip}.rs`(本 crate 消费方,不可改)。 --- @@ -21,7 +24,7 @@ 1. **时间单位分层**:本 crate 一律用**秒(`f64`)**与**源采样位置**作 IO 边界量;帧↔秒换算(`Int(s*fps)` 截断)留在 `opentake-domain`/调用层,本 crate**不做** fps 折算。证据:上游 `Transcription`/`MediaVisualCache`/`FrameSampler` 全用 `seconds`,`secondsToFrame` 在 `MediaTab`(上层)。 2. **零硬编码常量**:所有阈值(promoteDiff=12、coverageFloor=8.0、imageSize=256、dim=768、relativeCutoff=0.85、cosineFloor=0.05、波形 count 公式 150/帧 与 20000 上限、缩略图 maximumSize=120×68、sprite 列数=50 …)以 `pub const` / `Options` 结构体集中声明,值**逐字照搬**上游。 3. **缓存键与磁盘格式逐字节复刻**:嵌入/转写使用 `SHA256("path|mtime_unix_f64|size")`,缩略图/波形使用 `SHA256("path|size|mtime_unix_f64")`,均保留前 16 字节为 32 hex;同时复刻 `PALMEMB1` 二进制布局、`.waveform`/`.thumbs.jpg`+`.thumbs.json` sidecar、转写 JSON。理由:让 OpenTake 与上游/旧工程的缓存目录**可互读**(同机迁移),并保证幂等判定一致。 -4. **错误用 `thiserror` 定义本 crate 错误,内部传播用 `anyhow`,边界返回 `Result`**;`opentake-domain` 零依赖,本 crate 是第一层允许 IO 的 crate。 +4. **错误用 `thiserror` 定义本 crate 错误,内部传播用 `anyhow`,边界返回 `Result`**;`opentake-domain` 无 I/O(依赖 serde),本 crate 是第一层允许 IO 的 crate。 5. **不可变 / 纯函数优先**:排名(`VisualSearch`)、波形降采样、采样判定、转写过滤等都是无副作用纯函数,可全单测;有状态的只有索引调度器(§7.7)与模型加载器(§5.6)。 6. **后端推理可插拔**:`Embedder` / `Transcriber` / `OrtWorker` 定义为 trait,默认实现走 ort 或 candle;测试注入 mock(协议化 DI)。 7. **导出期让路**:任何后台任务(索引/缩略图/波形)在导出活跃时暂停。证据:上游 `ExportService.isExporting.didSet → SearchIndexCoordinator.exportDidBegin/End`(`MODULE-PORT-MAP` L457)、`SearchIndexCoordinator.waitWhileExportActive`(`SearchIndexCoordinator.swift:49`)。 @@ -759,7 +762,7 @@ pub fn best_supported_locale(supported: &[&str]) -> Option; // 系统首 ## 7. ort 推理 worker 通用接口(供进阶 AI 特性复用) -上游无此抽象(CoreML 直接在 `VisualEmbedder`)。`docs/ROADMAP.md` Phase 8 与 `docs/ADVANCED-FEATURES.md` B/C/D 层要求「统一 ort worker」承载:超分(Real-ESRGAN/SeedVR)、AI 抠像(RVM/BiRefNet)、运动追踪(CoTracker)、人声分离(Demucs)等。SigLIP2 的 `OrtEmbedder`(§5.7)是它的第一个使用者。 +上游无此抽象(CoreML 直接在 `VisualEmbedder`)。`docs/architecture/ROADMAP.md` Phase 8 与 `docs/architecture/ADVANCED-FEATURES.md` B/C/D 层要求「统一 ort worker」承载:超分(Real-ESRGAN/SeedVR)、AI 抠像(RVM/BiRefNet)、运动追踪(CoTracker)、人声分离(Demucs)等。SigLIP2 的 `OrtEmbedder`(§5.7)是它的第一个使用者。 ### 7.1 通用模型抽象 @@ -858,14 +861,14 @@ impl IndexCoordinator { - `encode::{VideoEncoder, ExportPreset}`(导出后端把合成 RGBA 帧序列 + 混音编码成容器)。 - `MediaProbe`(渲染尺寸/源 fps 决策)。 -**职责切分**(`docs/ARCHITECTURE.md` §1/§6): +**职责切分**(`docs/architecture/ARCHITECTURE.md` §1/§6): - `opentake-media` = **读取/编码 + 离线分析**(解码到 RGBA、抽 PCM、缩略图、波形、转写、语义索引/搜索、ort worker)。 - `opentake-render` = **合成 + 调度**(RenderPlan 纯函数、wgpu 逐帧合成、媒体物化为纹理、预览/导出后端、A/V 同步)。`renderSize` 偶数化、BT.709 instruction、关键帧 ramp **全在 render**。 - 二者通过 **`RgbaFrame` / `PcmBuffer`** 这两个朴素值类型交换帧/样本,无 wgpu/ffmpeg 类型泄漏到边界。 ### 8.3 媒体物化(图片/Lottie → 纹理)的归属 -上游用 `ImageVideoGenerator`(图片烧静止视频)、`LottieVideoGenerator`(Lottie 烧 ProRes)、`AlphaVideoNormalizer`(直 alpha 预乘)绕开 AVPlayer 限制。`docs/_analysis/02` 表 L74/L75/L81 与 `docs/ARCHITECTURE.md` §6 L130:**自建 wgpu 合成器后,这三类 hack 整类消失**——图片/Lottie 在合成前**物化为纹理**(content-hash 缓存),由 `opentake-render` 负责。 +上游用 `ImageVideoGenerator`(图片烧静止视频)、`LottieVideoGenerator`(Lottie 烧 ProRes)、`AlphaVideoNormalizer`(直 alpha 预乘)绕开 AVPlayer 限制。`docs/_analysis/02` 表 L74/L75/L81 与 `docs/architecture/ARCHITECTURE.md` §6 L130:**自建 wgpu 合成器后,这三类 hack 整类消失**——图片/Lottie 在合成前**物化为纹理**(content-hash 缓存),由 `opentake-render` 负责。 - 本 crate **提供**:图片解码 → `RgbaFrame`(§3.2 / `image` crate);(可选)Lottie 解码用 `rlottie` FFI 或 `velato`(`docs/_analysis/02` 表 L81),渲成 `RgbaFrame` 序列。**建议** Lottie 放 render 的物化层或独立 `opentake-motion` fallback。Motion Canvas v1 输出 mp4 后走普通视频 import,不归本 crate。 - 本 crate **不提供**:静止视频烧制、ProRes 烧制、alpha 预乘转码(整类删除)。 @@ -966,4 +969,4 @@ impl MediaEngine { - 转写:`Transcription/Transcription.swift:5-39`(模型/`offsetting:26-38`)、`:72-90`(locale)、`:284-322`(`decodeResults` segment/word);`Transcription/TranscriptCache.swift:12-88`(缓存/filter/key);`Transcription/TranscriptSearch.swift:12-36`(terms/matches)。 - 语义搜索:`Search/SearchIndexConfig.swift:4-45`(阈值/manifest);`Search/Models/VisualEmbedder.swift:7-87`(Spec/encode/预处理 squash 黑底 `:81-85`/输出断言 `:53-61`);`Search/Models/TextTokenizer.swift:16-23`(截断 64 右填 0);`Search/Models/ModelDownloader.swift:46-172`(安装/校验/解压);`Search/Models/VisualModelLoader.swift:86-110`(load + warm-up);`Search/Indexing/FrameSampler.swift:40-117`(采样/LumaGrid `:94-117`);`Search/Indexing/VisualIndexer.swift:15-86`(累积/幂等);`Search/Indexing/EmbeddingStore.swift:30-115`(PALMEMB1);`Search/Query/VisualSearch.swift:16-56`(sgemv 排名);`Search/SearchIndexCoordinator.swift:37-257`(调度/导出暂停/查询)。 - 领域契约:`OpenTake/crates/opentake-domain/src/media.rs:226-440`(MediaResolver/MediaAsset)、`clip_type.rs:781-832`(ClipType)、`timeline.rs:931-1031`、`clip.rs`。 -- 横切:`OpenTake/docs/_analysis/02-苹果框架可移植性.md`(能力→栈映射表 L66-83、攻坚清单 L98-136);`OpenTake/docs/MODULE-PORT-MAP.md`(L833-883 搜索、L923-940 存储、L1211 转写);`OpenTake/docs/ARCHITECTURE.md` §1/§6/§7;`OpenTake/docs/ROADMAP.md` Phase 2/8。 +- 横切:`OpenTake/docs/_analysis/02-苹果框架可移植性.md`(能力→栈映射表 L66-83、攻坚清单 L98-136);`OpenTake/docs/architecture/MODULE-PORT-MAP.md`(L833-883 搜索、L923-940 存储、L1211 转写);`OpenTake/docs/architecture/ARCHITECTURE.md` §1/§6/§7;`OpenTake/docs/architecture/ROADMAP.md` Phase 2/8。 diff --git a/docs/modules/opentake-media/semantic-search.md b/docs/modules/opentake-media/semantic-search.md index 4a4e730b..75b31382 100644 --- a/docs/modules/opentake-media/semantic-search.md +++ b/docs/modules/opentake-media/semantic-search.md @@ -167,7 +167,7 @@ pub async fn install(models_dir, m, base_url, on_progress) -> Result<...>; // f ``` - 幂等下载 image/text encoder + tokenizer → 逐个 **SHA-256 流式校验**(1MiB 块)→ tokenizer.zip 解压单顶层目录 → 原子 rename 到最终位置 → 写 spec.json。`installed` 按三件 + `tokenizer/tokenizer.json` 存在性判定。 - 相比上游去掉了 `MLModel.compileModel`(ONNX 无需编译)。 -- ⚠️ **占位待填**:`Manifest` 的 sha256/bytes 当前为空字符串/0,待实际 ONNX 资产托管后填实([ROADMAP.md](../../architecture/ROADMAP.md) Phase 8)。 +- **模型安装修复已完成并验证(2026-09-06)**:原空 sha256/bytes 占位已替换为固定 revision 资产清单,约 1.5 GB 文件已逐一校验。macOS 真实 Rust 路径已验证离线安装、图文 embedding、归一化、PALMEMB1 f16 往返和排名;Windows ort-tract 真实图及新包 UI 仍待验收。详见[模型专项审计](../../audit/2026-09-06/semantic-search-model.md),完整 revision/校验值由审计链接的知识记录统一维护。 --- @@ -176,7 +176,7 @@ pub async fn install(models_dir, m, base_url, on_progress) -> Result<...>; // f ort-backend = ["ort", "ndarray"] # 默认 build 不含;启用后真实 SigLIP2 推理 model-download = ["reqwest", "zip", ...] # 启用后下载 ``` -全链路纯函数 + mock **已实现并全测**;真实 ort 推理 + 模型托管属 Phase 8 计划中。改任何烧印常量(promoteDiff/coverageFloor/dim/imageSize…)须两侧同步。 +模型安装与真实后端纵向链路已实现并验证:正式 Cargo media search 为 84 passed / 1 ignored,Tauri search 为 14 passed;引用实际产品模块并运行真实模型的 Rust harness 为 72 passed / 0 ignored,包含离线安装、图文 embedding 与排名。后者不是 Python 推理替代,也不是已执行正式 Cargo opt-in 命令;复现方式与证据见[专项审计](../../audit/2026-09-06/semantic-search-model.md)。三组测试覆盖有重叠,不相加为独立测试数。Windows ort-tract 真实图、新包 UI 与整仓发布门槛仍由主线验收。改任何烧印常量(promoteDiff/coverageFloor/dim/imageSize…)须两侧同步。 ## 测试 预处理(黑→-1/白→+1/squash/alpha 合成)、pad_or_truncate、候选时间(stride/回退/零 duration)、luma_grid(黑/白/Rec.601)、ShotDetector 状态机(首帧/去重/镜头切/覆盖下限/grid 总更新)、accumulate_rows(首镜头归零/链接/幂等)、PALMEMB1 往返(f16 量化/版本校验/多字节拒绝)、排名(点积排序/best-per-shot/limit-then-floor/空索引)、install_dir/installed/SHA256、ort_worker 张量互转;端到端 `index_then_rank_finds_brightest_match`(mock 流)。 diff --git a/docs/modules/opentake-motion/INDEX.md b/docs/modules/opentake-motion/INDEX.md index 618b2ef5..caa46ef1 100644 --- a/docs/modules/opentake-motion/INDEX.md +++ b/docs/modules/opentake-motion/INDEX.md @@ -1,10 +1,13 @@ # opentake-motion — 模块目录 +> 状态:draft · 阶段:implementation-backed · 源码同步:2026-09-06。 +> 当前模块状态见 [OVERVIEW.md](OVERVIEW.md),候选验收见[当日审计](../../audit/2026-09-06/public-beta-validation.md)。规格文档保留设计语义,不作为全功能完成证明。 + > 上级:[模块文档树](../INDEX.md) · [docs 总目录](../../INDEX.md) > > `opentake-motion` = **原生 web 动态图形 fallback 渲染原语层**:把内联 HTML/CSS/JS(或模板 + 参数)确定性逐帧栅格化为磁盘 RGBA PNG 帧序列、内容寻址缓存、安全沙箱,并适配成 `opentake-render` 的 clip source。 > **Beta v1 已接入桌面**:`plugins/motion-canvas-studio/` 锁定 Motion Canvas 3.17.2,`title-card` 通过官方逐帧 renderer 产帧并编码 `mp4`;本 crate 的离线 Chromium 宿主也承载本地 HTML/CSS fallback。两条路径经 Tauri/Core/Agent/UI 共用的原子导入落轨流程进入普通视频预览与导出。**不是 Lottie 渲染器**(Lottie 在 [opentake-render](../opentake-render/INDEX.md))。 -> 透明 alpha、任意 TSX 与 frame-sequence source 仍是后续能力;当前 Beta 对它们显式报不支持。 +> 透明 alpha 发布已进入候选源码;任意 TSX 与通用 frame-sequence source 仍属后续范围,不能混为同一未实现项。 --- diff --git a/docs/modules/opentake-motion/MOTION-GRAPHICS-PLUGIN.md b/docs/modules/opentake-motion/MOTION-GRAPHICS-PLUGIN.md index 9238dd35..8b4fb5a5 100644 --- a/docs/modules/opentake-motion/MOTION-GRAPHICS-PLUGIN.md +++ b/docs/modules/opentake-motion/MOTION-GRAPHICS-PLUGIN.md @@ -1,6 +1,6 @@ # Motion Canvas 动效 / AI Video 插件设计与 Beta v1 实施记录 -> 状态:**Beta v1 已实现并通过代码/原生集成验收(2026-08-01)**。固定 `title-card` 使用 Motion Canvas 3.17.2;HTML/CSS 路径保留为受限 fallback,透明/frame-sequence 属后续版本。 +> 状态:**Beta v1 已实现并通过代码/原生集成验收(2026-08-01)**。固定 `title-card` 使用 Motion Canvas 3.17.2;HTML/CSS 路径保留为受限 fallback,这是 2026-08-01 的历史范围。2026-09-06 候选已有透明 Motion/ProRes 4444 路径,通用 frame-sequence 仍为后续目标;当前状态见[模块总览](OVERVIEW.md)。 > Issue trail:#34 motion dispatch / motion graphics;已认领实现切片。 > 决策:先 fork / vendor Motion Canvas(MIT)做独立外部模块或内置插件,让它产出可导入的视频文件;OpenTake 负责一站式导入、落轨、预览和导出。 diff --git a/docs/modules/opentake-motion/OVERVIEW.md b/docs/modules/opentake-motion/OVERVIEW.md index fbf8d6dd..838ef961 100644 --- a/docs/modules/opentake-motion/OVERVIEW.md +++ b/docs/modules/opentake-motion/OVERVIEW.md @@ -1,5 +1,10 @@ # opentake-motion — 模块总览 +> 状态:draft · 阶段:implementation-backed · 源码同步:2026-09-06。 +> 本次定向来源:`crates/opentake-motion/src/`、`src-tauri/src/motion.rs`。 +> 受限 HTML/CSS Motion Studio 与固定 Motion Canvas runner 已接入。透明发布与编辑保留 alpha 已有源码;ProRes 4444 编码/输出由 media/Tauri 层承担。任意 TSX 工程宿主仍不在当前开放范围。 +> 当前验收见[公开 Beta 审计](../../audit/2026-09-06/public-beta-validation.md);下文历史里程碑和测试记录保留其原时点边界。 + > 上级:[模块目录 INDEX.md](INDEX.md) · [模块文档树](../INDEX.md) · [docs 总目录](../../INDEX.md) > > **Beta v1 已接入桌面时间线。** `plugins/motion-canvas-studio/` 提供锁定的 Motion Canvas 3.17.2 `title-card` runner;本 crate 提供离线 Chromium 帧宿主、缓存与 HTML/CSS fallback。Tauri/Core/Agent/Motion Panel 共用同一原子 Render → Import → Place/Replace 流程。完整设计与验收见 [Motion Graphics 插件设计](MOTION-GRAPHICS-PLUGIN.md)。 @@ -46,7 +51,7 @@ opentake-core / src-tauri / opentake-agent 已接 v1 调用方(渲染、验 - **Motion Canvas 模板是 v1 主模板路径**。本 crate 的 Chromium 宿主负责逐帧执行其官方 renderer;HTML/CSS/JS 仍是兼容 fallback。二者产 `mp4` 后复用普通视频导入/预览/导出链路。 - **默认 build 不启动浏览器**。真实 CDP 后端必须显式启用 `chromium` feature;默认路径仍 fail-closed 返回 `RendererUnavailable`。 - **不持 UI 状态 / 不直接做时间线落轨**。导入 + 落轨的单事务(Render → Import Media → Place Clip)仍属 `src-tauri`/`opentake-core`,本 crate 负责安全、确定性的帧生产。 -- **不定义 `ClipType::Motion`**。透明动效与新 clip 类型 / frame sequence source 是后续目标。 +- **不定义独立 `ClipType::Motion`**。透明发布已使用媒体导入/编码路径;直接 frame sequence source 仍是独立后续目标。 - **不做帧↔秒折算的真理**。本 crate 内部 `t = frame / fps` 仅用于渲染时间网格;时间线帧↔秒的真理在 domain / 调用层(移植铁律,见 §6)。 --- @@ -128,13 +133,13 @@ MotionSource (Code 内联文档 | Template id + params) - 集成桥:`MotionClipSource` 实现 `SourceMetrics` + `FrameProvider`,解码器注入、过末端钳位、缺帧返回 `None`。 **Beta v1 已实现(集成验收):** -- `plugins/motion-canvas-studio/`:Motion Canvas 3.17.2 lockfile、MIT LICENSE/notice、typed job、`title-card.tsx`、确定性离线 runner 与可复现 bundle;npm audit 0,锁定依赖许可证门禁通过。 +- `plugins/motion-canvas-studio/`:Motion Canvas 3.17.2 lockfile、MIT LICENSE/notice、typed job、`title-card.tsx`、确定性离线 runner 与可复现 bundle;当时的 npm audit 与许可证门禁记录保留;当前依赖审计见当日候选记录。 - Tauri/Core:受控临时目录、离线 Chromium、进度/取消、FFmpeg `output.mp4`、`motion-result.json` 精确校验、项目 media capability 发布、单事务注册/落轨或替换、一步撤销与保存重开。 - 产品入口:独立 Motion Panel 与动态发布的 Agent add/edit 工具共享生产桥;普通视频 preview/export 无需识别 Motion Canvas。 - 自动化:两次固定模板渲染像素/哈希一致;失败、取消、遍历、符号链接、畸形/篡改元数据无 manifest/timeline 变更;`composite_frame` 与 `export_video` 均验证包含生成片段。 **计划中 / 待做(明确未实现):** -- `MotionClipSource` 直接接入 `TextureSource::FrameSequence`、PNG sequence、透明 alpha overlay/ProRes4444——属 v2。 +- `MotionClipSource` 直接接入通用 frame sequence/PNG sequence 仍待规划;透明 alpha 发布与 ProRes 4444 已有候选源码,不再列作未实现。 - 用户提供的任意 TS/TSX 编译和通用模板工程宿主——Beta 仅开放固定 `title-card`;其他输入显式拒绝或走受限 HTML fallback。 - 独立结构化 `motion_metadata`/长期保留 job 文件;Beta 使用 `generation_input` 保存可编辑来源,并在命令结果返回经验证的 output metadata。 @@ -142,7 +147,7 @@ MotionSource (Code 内联文档 | Template id + params) ## 6. 移植铁律(本模块必须遵守) -来自 [AGENTS.md](../../../AGENTS.md) 移植铁律、[MOTION-GRAPHICS-PLUGIN.md](MOTION-GRAPHICS-PLUGIN.md) 与代码现况: +来自[开发规范](../../project/conventions.md)的移植规则、[MOTION-GRAPHICS-PLUGIN.md](MOTION-GRAPHICS-PLUGIN.md) 与代码现况: 1. **一切以整数帧为单位**:渲染时间网格 `t = i / fps`(`i ∈ 0..duration_frames`);`RenderedClip` / 缓存 / source 全以帧索引寻址。时间线帧↔秒的真理留在 domain / 调用层(`secondsToFrame` 用截断 `Int(s*fps)`,不四舍五入)。 2. **确定性可复现(预览 == 导出)**:渲染器必须对同一 `MotionRenderRequest` 产出**字节一致**的帧。这是内容寻址缓存与"预览/导出像素一致"的硬前提;任何引入非确定性(墙钟、随机、未冻结的页面时钟)的实现都违规。 diff --git a/docs/modules/opentake-ops/INDEX.md b/docs/modules/opentake-ops/INDEX.md index 163c2d67..a5cc8aeb 100644 --- a/docs/modules/opentake-ops/INDEX.md +++ b/docs/modules/opentake-ops/INDEX.md @@ -1,5 +1,8 @@ # opentake-ops — 模块目录 +> 状态:draft · 阶段:implementation-backed · 源码同步:2026-09-06。 +> 当前模块状态见 [OVERVIEW.md](OVERVIEW.md),候选验收见[当日审计](../../audit/2026-09-06/public-beta-validation.md)。规格文档保留设计语义,不作为全功能完成证明。 + > 上级:[模块文档树](../INDEX.md) · [docs 总目录](../../INDEX.md) > > `opentake-ops` = 纯编辑引擎 + 唯一编辑入口 `EditCommand` + `apply()` 事务 + 整树快照撤销/重做栈。依赖只向下:仅依赖 `opentake-domain`,被 `opentake-core` 调用。 diff --git a/docs/modules/opentake-ops/OVERVIEW.md b/docs/modules/opentake-ops/OVERVIEW.md index 5dc05dca..c1c5f2a0 100644 --- a/docs/modules/opentake-ops/OVERVIEW.md +++ b/docs/modules/opentake-ops/OVERVIEW.md @@ -1,5 +1,10 @@ # opentake-ops 总览 +> 状态:draft · 阶段:implementation-backed · 源码同步:2026-09-06。 +> 本次定向来源:`crates/opentake-ops/src/ops/`、`web/src/store/editActions.ts`。 +> 编辑仍经过 Rust 命令与撤销事务;本轮选区驱动 split/trim、链接移动、Option trim 和范围选择的接线进展见同步报告。旧 GAP 文档不是当前未实现列表。 +> 当前验收见[公开 Beta 审计](../../audit/2026-09-06/public-beta-validation.md);下文历史里程碑和测试记录保留其原时点边界。 + > 上级:[模块文档树](../INDEX.md) · [docs 总目录](../../INDEX.md) · 本模块目录:[INDEX.md](INDEX.md) ## 一句话定位 @@ -95,7 +100,7 @@ UI 手势 / Agent / MCP 工具 - 命令层属性类操作:`SetClipProperties` / 关键帧族(`SetKeyframes` / `StampKeyframe` / `RemoveKeyframe` / `MoveKeyframe` / `SetKeyframeInterpolation`)/ `SetColorGrade` / `SetChromaKey` / `SetMasks` / `SetEffects` / `SetTrackProps` / `SwapMedia`。 - `intent.rs` 高层意图预检:自动建轨放置、卡点放置、修剪到播放头、单区间波纹删除、smart-reframe。 -**计划中(仅 ROADMAP / GAP 规划,本 crate 代码尚未落地):** +**设计与后续核对(旧 ROADMAP / GAP 仅作来源,不能直接判定未实现):** - 与上游 1:1 的若干接线层 / 模型扩展缺口主要在**前端与 domain**,不在本 crate(见 [EDITING-ENGINE-PLAN.md](../../architecture/EDITING-ENGINE-PLAN.md) §3):如 fade knee 拖拽态、隐藏轨 hitTest 过滤、`Clip.isSoloed` 字段(需前后端 DTO 扩展)、轨间插入阈值 `insertThreshold`、Snap 容差按 DPI 缩放。 - 曲线变速(speed 升级为关键帧轨)、复合片段嵌套等属 ROADMAP 后期能力,本 crate 当前无对应命令。 diff --git a/docs/modules/opentake-process-tree/INDEX.md b/docs/modules/opentake-process-tree/INDEX.md new file mode 100644 index 00000000..399f613b --- /dev/null +++ b/docs/modules/opentake-process-tree/INDEX.md @@ -0,0 +1,8 @@ +# opentake-process-tree — 目录 + +> 状态:draft · 阶段:implementation-backed · 同步日期:2026-09-06 + +- [总览](OVERVIEW.md):职责、平台差异与验证边界。 +- [源码](../../../crates/opentake-process-tree/src/lib.rs):启动配置、绑定、终止与等待。 +- [依赖清单](../../../crates/opentake-process-tree/Cargo.toml)。 +- [开发规范](../../project/conventions.md) · [模块总目录](../INDEX.md)。 diff --git a/docs/modules/opentake-process-tree/OVERVIEW.md b/docs/modules/opentake-process-tree/OVERVIEW.md new file mode 100644 index 00000000..12cb0fca --- /dev/null +++ b/docs/modules/opentake-process-tree/OVERVIEW.md @@ -0,0 +1,9 @@ +# opentake-process-tree — 总览 + +> 状态:draft · 阶段:implementation-backed · 同步日期:2026-09-06 + +进程树生命周期支持 crate,位于 [Cargo workspace](../../../Cargo.toml);实现见 [lib.rs](../../../crates/opentake-process-tree/src/lib.rs)。`configure_command` 配置平台进程启动,`ProcessTree::attach` 绑定子进程,`terminate` / `terminate_and_wait` / `wait_for_exit` 提供取消与退出等待。 + +Unix 使用进程组,Windows 使用 Job Object 相关实现。它为调用方清理媒体或工具进程提供基础能力,不持有 Timeline,也不负责 UI。源码存在不代表 Windows 原生进程树与安装器已在此候选验收;证据见[当日验证](../../audit/2026-09-06/public-beta-validation.md)。 + +[模块目录](INDEX.md) · [模块总目录](../INDEX.md) diff --git a/docs/modules/opentake-project/INDEX.md b/docs/modules/opentake-project/INDEX.md index 0a97eb74..2652dfe9 100644 --- a/docs/modules/opentake-project/INDEX.md +++ b/docs/modules/opentake-project/INDEX.md @@ -1,5 +1,8 @@ # opentake-project — 模块目录 +> 状态:draft · 阶段:implementation-backed · 源码同步:2026-09-06。 +> 当前模块状态见 [OVERVIEW.md](OVERVIEW.md),候选验收见[当日审计](../../audit/2026-09-06/public-beta-validation.md)。规格文档保留设计语义,不作为全功能完成证明。 + > 上级:[模块文档树](../INDEX.md) · [docs 总目录](../../INDEX.md) > > `opentake-project` = 工程持久化层:`.opentake` 目录包读写 + 自包含归档 + XMEML(FCP7 XML) 时间线导出 + 生成日志。依赖只向下:仅依赖 `opentake-domain`,被 `opentake-core` / `src-tauri` 调用。 diff --git a/docs/modules/opentake-project/OVERVIEW.md b/docs/modules/opentake-project/OVERVIEW.md index 501607e0..bcf21acc 100644 --- a/docs/modules/opentake-project/OVERVIEW.md +++ b/docs/modules/opentake-project/OVERVIEW.md @@ -1,5 +1,10 @@ # opentake-project 总览 +> 状态:draft · 阶段:implementation-backed · 源码同步:2026-09-06。 +> 本次定向来源:`crates/opentake-project/src/`。 +> 工程保存、自包含归档、布局和 XMEML 交换格式为本模块范围;视频编码位于 media/Tauri。日期化测试和旧交接不自动代表 Beta 6 候选已验收。 +> 当前验收见[公开 Beta 审计](../../audit/2026-09-06/public-beta-validation.md);下文历史里程碑和测试记录保留其原时点边界。 + > 上级:[模块文档树](../INDEX.md) · [docs 总目录](../../INDEX.md) · 本模块目录:[INDEX.md](INDEX.md) ## 一句话定位 diff --git a/docs/modules/opentake-render/INDEX.md b/docs/modules/opentake-render/INDEX.md index d695db3e..4e5cf1e5 100644 --- a/docs/modules/opentake-render/INDEX.md +++ b/docs/modules/opentake-render/INDEX.md @@ -1,5 +1,8 @@ # opentake-render — 模块目录 +> 状态:draft · 阶段:implementation-backed · 源码同步:2026-09-06。 +> 当前模块状态见 [OVERVIEW.md](OVERVIEW.md),候选验收见[当日审计](../../audit/2026-09-06/public-beta-validation.md)。规格文档保留设计语义,不作为全功能完成证明。 + > 上级:[模块文档树](../INDEX.md) · [docs 总目录](../../INDEX.md) > > `opentake-render` = RenderPlan(纯函数 `Timeline → 每帧属性`)+ wgpu 帧合成器 + 文本栅格化。**预览与导出共用同一条 RenderPlan + 同一个合成器,保证像素一致**。依赖只向下:仅依赖 `opentake-domain`,被 `opentake-core` / `src-tauri` 的预览与导出后端调用。 diff --git a/docs/modules/opentake-render/OVERVIEW.md b/docs/modules/opentake-render/OVERVIEW.md index 1718408d..56977f05 100644 --- a/docs/modules/opentake-render/OVERVIEW.md +++ b/docs/modules/opentake-render/OVERVIEW.md @@ -1,5 +1,10 @@ # opentake-render 总览 +> 状态:draft · 阶段:implementation-backed · 源码同步:2026-09-06。 +> 本次定向来源:`crates/opentake-render/src/plan/`、`gpu/`、`src-tauri/src/export.rs`。 +> RenderPlan、源帧时间映射、透明合成与导出协作已经实现;temporal 播放调度由桌面壳/预览路由负责。共享合成语义不等于所有预览/导出组合已通过像素验收。 +> 当前验收见[公开 Beta 审计](../../audit/2026-09-06/public-beta-validation.md);下文历史里程碑和测试记录保留其原时点边界。 + > 上级:[模块文档树](../INDEX.md) · [docs 总目录](../../INDEX.md) · 本模块目录:[INDEX.md](INDEX.md) · 完整规格:[SPEC.md](SPEC.md) ## 一句话定位 @@ -98,7 +103,7 @@ Compositor::render_to_rgba(device, queue, size, &frame_plan, resolver) -> Decode **计划中(仅 SPEC / ROADMAP / GAP 规划,本 crate 代码尚未落地或仅占位):** - **线性光混合**:当前合成在 **sRGB 非线性域**直接混合以最贴近 AVFoundation(`RT_FORMAT = Rgba8Unorm`,`color.rs` 的 sRGB↔linear 已备但合成 over 未切线性);线性光(RGBA16F)为质量增强项,仅在像素 diff 通过后切换(SPEC §3.7)。 - **转场 transitions**(相邻 clip 重叠区 pass):ADVANCED-FEATURES A 层 p0;Beta 1 已交付 cross-dissolve 全链(含时间线标记 / Inspector 选型),扩库为后续版本项。 -- **透明 alpha / PNG sequence / 任意 TSX 动效**:Motion Canvas 透明输出与 frame-sequence 明确属于后续版本(v1 走普通视频导入/预览/导出链路)。 +- **通用 PNG sequence / 任意 TSX 动效**:仍属后续范围;透明 Motion 与 ProRes 4444 已通过普通媒体/编码路径进入候选源码。 > **2026-08-03 更新:**「运行期预览接线」「真实播放引擎」「图片/Lottie 物化」三项 > 已随 Beta 1 落地(`composite_frame`/播放引擎在 `src-tauri/playback` 与 `web`,像素仍走本 crate diff --git a/docs/modules/opentake-render/SPEC.md b/docs/modules/opentake-render/SPEC.md index 87d3b658..4ff06a85 100644 --- a/docs/modules/opentake-render/SPEC.md +++ b/docs/modules/opentake-render/SPEC.md @@ -1,5 +1,8 @@ # opentake-render 实现就绪规格(Issue #7:wgpu 帧合成器 + RenderPlan) +> 状态:draft · 阶段:partial-implementation · 设计与早期实现来源保留;原文日期、行号和“待做”属于设计时点。2026-09-06 当前实现见本模块 [OVERVIEW.md](OVERVIEW.md),当前验收见[公开 Beta 记录](../../audit/2026-09-06/public-beta-validation.md)。 + + > 状态:实现就绪(implementation-ready)。本文是 `crates/opentake-render/` 的逐项施工图。 > 范围:① 纯函数 `Timeline → RenderPlan`(Rust 数据结构 + 算法,逐条对应上游公式);② wgpu render graph;③ 预览/导出共享 RenderPlan;④ 图片/文字/Lottie 物化为纹理;⑤ 与 `opentake-domain` / `opentake-media` 的接口契约;⑥ PoC 验收(与上游 `inspect_timeline` 像素 diff)+ 分步实施清单。 > 定位:这是**全项目命门**(ARCHITECTURE.md §1、ROADMAP Phase 3)。上游所有像素级合成都委托给 AVFoundation 黑盒(`AVVideoComposition` + layer instructions + ramps),无 Metal/CoreImage/手写 shader;OpenTake 必须自建 wgpu 合成器把这块从零补回。 @@ -19,7 +22,7 @@ | 文字渲染(预览+导出) | `…/Preview/TextLayerController.swift`(224 行) | `applyStyle` L152、`isGeometryFlipped` L13、`applyOpacityAnimation` discrete L191、`buildForExport` L75、`visibleTextClips` L122、`referenceCanvasHeight=1080` L150 | | 文字样式/测量 | `…/Models/TextStyle.swift`、`…/Models/TextLayout.swift` | `attributes` L138、`naturalSize` L9、shadowPadding=12 L6 | | 领域模型(已 1:1 移植到 Rust) | `…/Models/Timeline.swift`、`…/Models/Keyframe.swift`、`…/Models/ClipType.swift` | `Transform` L364、`Crop` L501、`affineTransform` 输入语义见下 | -| 架构/路线 | `/Users/lvbaiqing/TRUE 开发/PRIMARY-CN/OpenTake/docs/ARCHITECTURE.md` §1/§6 | `…/docs/_analysis/02-苹果框架可移植性.md`、`…/docs/ROADMAP.md` Phase 3 L25-34 | +| 架构/路线 | `/Users/lvbaiqing/TRUE 开发/PRIMARY-CN/OpenTake/docs/architecture/ARCHITECTURE.md` §1/§6 | `…/docs/_analysis/02-苹果框架可移植性.md`、`…/docs/architecture/ROADMAP.md` Phase 3 L25-34 | **已就位的 Rust 依赖(本 crate 不得重写,只能调用):** `crates/opentake-domain/` 已逐行移植并单测覆盖: - `Clip::transform_at / size_at / top_left_at / rotation_at`(`clip.rs` L214-246) @@ -83,7 +86,7 @@ return placed ∘ translate(-cx,-cy) ∘ rotate(rotation*π/180) ∘ translate(c ### 1.5 多轨混合(对应 instruction.layerInstructions 顺序 L405-449) - `layerInstructions` 顺序 = `trackMappings.filter(\.isVideo)` 顺序 = **轨道枚举顺序 + 黑底最后追加**(L194/L206)。AVFoundation 按 layer instruction 数组顺序混合,**后者在上**。 -- 因此 OpenTake 混合顺序:**黑底(最底)→ track[0] → track[1] → … → track[n-1](最顶)**。注意上游黑底 mapping 是最后 append 到 trackMappings 的(L209-215),但它的 opacity 在自身区间是 1、其余轨道盖在它上面——等价于"黑底铺底,视频轨从下标 0 到 n-1 依次叠加"。OpenTake 直接按此顺序 alpha-over 合成(见 §3.6)。 +- 因此 OpenTake 混合顺序:**黑底(最底)→ track[0] → track[1] → … → `track[n-1]`(最顶)**。注意上游黑底 mapping 是最后 append 到 trackMappings 的(L209-215),但它的 opacity 在自身区间是 1、其余轨道盖在它上面——等价于"黑底铺底,视频轨从下标 0 到 n-1 依次叠加"。OpenTake 直接按此顺序 alpha-over 合成(见 §3.6)。 - `track.hidden` ⇒ 整轨不渲染(L419);`track.muted` ⇒ 音频不出声(audio 路径 L391,本 crate 视频侧只需关心 hidden)。 ### 1.6 色彩空间(对应 vcConfig L456-458 + ImageVideoGenerator L169-173) diff --git a/docs/modules/src-tauri/INDEX.md b/docs/modules/src-tauri/INDEX.md index 1fb35f2c..318d0f02 100644 --- a/docs/modules/src-tauri/INDEX.md +++ b/docs/modules/src-tauri/INDEX.md @@ -1,5 +1,8 @@ # src-tauri — 模块目录 +> 状态:draft · 阶段:implementation-backed · 源码同步:2026-09-06。 +> 当前模块状态见 [OVERVIEW.md](OVERVIEW.md),候选验收见[当日审计](../../audit/2026-09-06/public-beta-validation.md)。规格文档保留设计语义,不作为全功能完成证明。 + > 上级:[模块文档树](../INDEX.md) · [docs 总目录](../../INDEX.md) > > **Tauri 2 桌面壳 + 命令边界层**:装配所有 crate 成原生进程,持有权威 `AppCore`,对前端暴露薄 `#[tauri::command]` 接口,并把 core 事件桥回 WebView。它是 workspace member,但不在 `crates/` 下。 diff --git a/docs/modules/src-tauri/OVERVIEW.md b/docs/modules/src-tauri/OVERVIEW.md index fb1a36d1..435bce48 100644 --- a/docs/modules/src-tauri/OVERVIEW.md +++ b/docs/modules/src-tauri/OVERVIEW.md @@ -1,5 +1,10 @@ # src-tauri — 总览 +> 状态:draft · 阶段:implementation-backed · 源码同步:2026-09-06。 +> 本次定向来源:`src-tauri/src/export.rs`、`motion.rs`、`external_mcp.rs`、`lib.rs`。 +> 导出支持 H.264、H.265、ProRes 422/4444,进度/取消与失败清理有实现。透明 Motion、Lottie 原生播放、外部 MCP 配对已接入;本次安装包与 GUI 验收另见当日审计。 +> 当前验收见[公开 Beta 审计](../../audit/2026-09-06/public-beta-validation.md);下文历史里程碑和测试记录保留其原时点边界。 + > 上级:[本模块目录](INDEX.md) · [模块文档树](../INDEX.md) · [docs 总目录](../../INDEX.md) ## 一句话定位 @@ -116,15 +121,15 @@ pub enum EditRequest { … } | 子系统 | 状态 | 说明 | |---|---|---| -| 命令边界 / `EditRequest` 映射 | ✅ 已实现 | 30 个命令注册;`EditRequest` 覆盖前端 v1 全部编辑变体,带回归测试 | +| 命令边界 / `EditRequest` 映射 | ✅ 已实现 | 命令集中注册;`EditRequest` 覆盖前端 v1 全部编辑变体,带回归测试 | | 事件桥 | ✅ 已实现 | 4 类 CoreEvent 全部转发 | | 启动 / 窗口 / FFmpeg 解析 | ✅ 已实现 | 关窗隐藏 + 关窗前 flush 存盘;`RunEvent::Reopen` **仅 macOS** | | 单帧预览合成 `composite_frame` | ✅ 已实现 | 视频 + 图片 + 文本 + Lottie 层;预览与导出共享 RenderPlan 像素路径 | -| 整片导出 `export_video` | ✅ 已实现 | **H.264 / .mp4、H.265 / .mp4、ProRes 422 / .mov** + 线性音频混音;带进度回调 / 取消(`cancel_export`) | +| 整片导出 `export_video` | ✅ 已实现 | **H.264 / .mp4、H.265 / .mp4、ProRes 422 / 4444 / .mov** + 线性音频混音;带进度回调 / 取消(`cancel_export`) | | 媒体导入 / relink / 波形 / 缩略图 | ✅ 已实现 | 导入白名单 + 首帧海报缩略图 + 波形 + 缺失素材重链接 | | 全局素材库(7 命令) | ✅ 已实现 | copy-on-favorite,跨工程 | | 密钥(BYOK) | ✅ 已实现 | keyring 存储 + 白名单账户(含生成侧 provider) | -| MCP / Agent 通道 | ✅ 已实现 | 官方 Codex/ChatGPT 逐轮临时 loopback MCP(256-bit Bearer、工程绑定);固定端口外部 MCP 在 Beta 2 默认关闭 | +| MCP / Agent 通道 | ✅ 已实现 | 官方 Codex/ChatGPT 逐轮临时 loopback MCP(256-bit Bearer、工程绑定);外部 MCP 从 Beta 5 起采用显式认证配对,旧未认证入口仍关闭 | | 跨平台窗口重显 | 🟡 部分 | `RunEvent::Reopen` 仅 macOS;其它平台靠托盘 / OS 重现是后续项 | | FFmpeg 随包分发 | ✅ 已实现 | sidecar 随包打包(macOS/Windows);Windows CI 校验 exact-SHA | diff --git a/docs/modules/src-tauri/export.md b/docs/modules/src-tauri/export.md index 3b8e667d..6909032f 100644 --- a/docs/modules/src-tauri/export.md +++ b/docs/modules/src-tauri/export.md @@ -1,87 +1,23 @@ -# export — 整条时间线视频导出 +# export — 时间线视频导出 -> 上级:[本模块目录](INDEX.md) · [总览](OVERVIEW.md) · [模块文档树](../INDEX.md) -> -> 源码:[`../../../src-tauri/src/export.rs`](../../../src-tauri/src/export.rs) +> 状态:draft · 阶段:implementation-backed · 同步日期:2026-09-06 +> [模块总览](OVERVIEW.md) · [模块目录](INDEX.md) · [源码 export.rs](../../../src-tauri/src/export.rs) -## 定位 +`export_video` / `run_export` 将时间线采样成 RenderPlan,通过合成器生成 RGBA,再由 media 编码层写入目标容器。格式和扩展名在 `resolve_preset` 等入口校验。 -`export_video` 命令:把当前时间线的**每一帧**在 GPU 上合成(wgpu 合成器 `opentake-render`),把 RGBA 帧喂给系统 ffmpeg 编码器(`opentake_media::VideoEncoder`),产出磁盘上真实的 `.mp4`。它是单帧预览路径 [render.md](render.md) 的「整片」对应物。 - -## 完成状态(首版切片,SPEC §2.4 / §8.2) - -| 维度 | 状态 | +| 路径 | 当前源码行为 | |---|---| -| 视频编码 | 🟡 **仅 H.264 / .mp4**。编码器本身已支持 H.265 / ProRes preset,但本命令未接线 | -| 音频 | ✅ **线性混音**:每个含音频 clip 的源窗解码成 mono f32 @ 混音采样率,按帧推导的样本偏移落位,乘 `volume_at` 包络,求和、硬限幅,由编码器 mux 入(AAC) | -| 分辨率 | ✅ 全量导出分辨率(`export_render_size`),非预览降采样上限 | -| 进度 / 取消 | ❌ 未实现——编排器在 GPU 锁下逐帧跑到完 | -| 文本层 | ✅ 支持(`CosmicTextRasterizer`) | -| Lottie 层 | ❌ resolver 返回 `None`(跳过) | - -H.265 / ProRes 在 `resolve_preset` 里**显式报错**(`"H.265 export is not wired yet (TODO)"` / ProRes 同理),而非默默失败。 - -## IPC 入参(ExportRequest) - -```rust -#[derive(Clone, Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ExportRequest { - pub out_path: String, // 必须 .mp4(H.264 路径) - #[serde(default)] pub codec: ExportCodec, // 默认 H264 - #[serde(default)] pub quality: ExportQuality, // 默认 1080p -} -``` - -- `ExportCodec`(`rename_all = "lowercase"`):`h264`(默认)/ `h265`(留位)/ `prores`(留位)。 -- `ExportQuality`:`720p` / `1080p`(默认)/ `4k`——每档同时映射到 render-crate 分辨率与 encode-crate 分辨率两个选择器。 -- `#[serde(default)]` 让 bare 载荷 `{ "outPath": "…" }` 即导出 H.264 / 1080p。 - -返回 `ExportSummary { outPath, width, height, fps, frameCount }`(camelCase)。 - -## 编排流程(run_export) - -`export_video` 命令只做「快照 live 会话 → 委派」,真正逻辑在 `run_export()`(与 Tauri / `AppCore` 解耦,便于 ffmpeg-gated 集成测试 `tests/export_integration.rs` 直接用手搭的 timeline + manifest 驱动): - -``` -resolve_preset(codec, quality, out) // 校验扩展名匹配容器;拒未接线 codec -project_text(timeline) // 文本 clip → {content, style, box} 按 clip id -project_media(manifest, project_dir) // manifest → (sizes, media 路径);解析 Project 相对路径 -export_render_size((w,h), quality) // 全量导出尺寸(偶数化) -build_render_plan(timeline, size, metrics) -RenderDevice::try_new() // 本地一次性 GPU 上下文(不复用预览的缓存上下文) -VideoEncoder::new(out, w, h, fps, preset) -for f in 0..total_frames { - plan.frame(timeline, f) → MediaResolver(每帧新建,cache cap=64) - compositor.render_to_rgba(...) → encoder.push_frame(RgbaFrame) -} -mix_timeline_audio(timeline, media) → encoder.push_audio(pcm) // 无音频则视频-only -encoder.finish() -``` - -要点: -- **GPU 上下文本地一次性**:导出是一次性批处理,不复用预览缓存的上下文,避免与预览锁竞争。 -- 空时间线仍产出合法(可能零帧)文件;越界帧合成为不透明黑(正确的 clear color,非错误)。 -- GPU 获取 / 解码 / 编码失败均转 `Err(String)`(Tauri 边界约定)。 - -## 纹理解析(MediaResolver,导出版) - -`TextureResolver` 实现:video 按源帧 key、image 一次 key、text 栅格化其 box、Lottie 返回 `None`。与预览版相比,导出版 `FrameRequest.tolerance_secs = 0.0`(精确落帧,质量优先;预览用 0.1s 宽容差换 scrub 速度)。这份 resolver / metrics / 投影逻辑是预览路径逻辑的**自包含拷贝**(有意留在本模块,不动 `render.rs`);待两条路径稳定后再把共享投影上提为 `pub(crate)` 辅助。 - -## 音频混音(mix_timeline_audio) - -- 解码规格 `AUDIO_DECODE_SPEC`:mono / f32 / `MIX_SAMPLE_RATE`——在混音率上解码,使混音成为样本对齐的纯加法(本切片不做逐 clip 重采样)。 -- 仅 `Audio` / `Video` 类型 clip 贡献声音(text/image/lottie 无声);**muted 轨被跳过**。 -- 每个 clip 经 `project_clip_audio`:解码可见源窗 → 落到帧推导的起始样本 → 按 `volume_at` 逐样本建增益包络(全 unity 则塌缩为空包络)。 -- clip 指向无音轨的视频 → `MediaError::NoTrack` 被吞为「贡献静音」,**不是导出失败**;其它解码错误才上抛。 -- 全部 clip 无音频 → 返回 `None` → 保持视频-only 输出。 +| H.264 / H.265 | `.mp4`,带时间线音频混音 | +| ProRes 422 | `.mov`,不承载透明输出 | +| ProRes 4444 | `.mov`,承载 alpha;输入直 alpha 与合成预乘边界显式处理 | +| 文本与 Lottie | 经渲染/源解析器进入帧合成,不再是早期跳过 Lottie 的切片 | +| 进度与取消 | `ExportControl` 绑定导出代次;`cancel_export` 请求取消,逐帧与收尾继续校验 | +| 失败与输出身份 | 失败/取消清理不完整结果,拒绝不安全或冲突的输出身份;不会静默交付失败的无音轨输出 | -## 与 `export_fcpxml` 的区别 +`ExportRequest`、`ExportCodec`、`ExportQuality` 与前端[导出面板](../../../web/src/components/shell/ExportDialog.tsx)共同定义选项。参数字段以类型定义为准,文档不复制易过时的完整 DTO。 -`export_video`(本文件)产出**像素级渲染的视频文件**;`export_fcpxml`([commands-ipc.md](commands-ipc.md))产出 **XMEML 工程交换 XML**(给 Premiere/DaVinci/FCP)。两者无关。 +导出尺寸采用目标导出配置,区别于低分辨率预览。时间重映射、音频窗口、透明输出和缺失素材均有专门代码路径;这些路径的存在不代表所有组合和平台已经实机验收。 ---- +`aae0ae6..33ee8e2` 中的候选新增包括 ProRes 4444、失败清理、取消传播和输出身份检查。[本次同步报告](../../documentation-sync-2026-09-06.md)记录源码依据,[当前验证](../../audit/2026-09-06/public-beta-validation.md)记录实际运行结果。早期 H.264-only、无进度/取消的描述已被当前实现替代。 -> 相关:[render.md](render.md)(共享逻辑的单帧版)· [commands-ipc.md](commands-ipc.md)(`export_fcpxml` 对比)· 跨模块 [opentake-render](../opentake-render/INDEX.md)(合成器 / RenderPlan)· [opentake-media](../opentake-media/INDEX.md)(VideoEncoder / PCM) -> -> 导航:[本模块目录](INDEX.md) · [模块文档树](../INDEX.md) · [docs 总目录](../../INDEX.md) +关联:[媒体编码](../opentake-media/encode.md) · [RenderPlan](../opentake-render/render-plan.md) · [单帧渲染](render.md)。 diff --git a/docs/modules/web/INDEX.md b/docs/modules/web/INDEX.md index f349b2cb..8e6c521f 100644 --- a/docs/modules/web/INDEX.md +++ b/docs/modules/web/INDEX.md @@ -1,5 +1,8 @@ # web — 模块目录 +> 状态:draft · 阶段:implementation-backed · 源码同步:2026-09-06。 +> 当前模块状态见 [OVERVIEW.md](OVERVIEW.md),候选验收见[当日审计](../../audit/2026-09-06/public-beta-validation.md)。规格文档保留设计语义,不作为全功能完成证明。 + > 上级:[模块文档树](../INDEX.md) · [docs 总目录](../../INDEX.md) > > `web/` = OpenTake 的 **React/TypeScript + Vite + Zustand** 前端(包管理器 pnpm,测试 vitest)。它是架构最上层的**纯消费者**:只持后端 `Timeline` 只读镜像 + 版本号,**不做撤销、不持领域逻辑**;编辑经 `edit_apply` 发往 Rust,由 `timeline_changed` 事件回流刷新。像素↔帧换算放前端,帧↔秒换算放 Rust。非 Tauri 下 `isTauri=false`,命令落内存 fallback。 @@ -10,13 +13,15 @@ - **[OVERVIEW.md](OVERVIEW.md)** — 一句话定位与架构位置、职责边界(做/不做)、关键概念与数据流(手势→editActions→`editApply`→`edit_apply`→`timeline_changed`→`get_timeline`;camelCase 契约;单表面单时钟预览;非 Tauri 降级)、完成状态、代码风格。 +当前新增:[Sticker 专项验证](../../audit/2026-09-06/sticker-panel.md)(图片/Lottie 既有媒体路径已接线,新包 GUI 待验收)。 + ## 子系统文档 - **[state-stores.md](state-stores.md)** — `store/`:Zustand 各 store + actions + 镜像同步。`projectStore`(只读镜像 + 版本 + canUndo/canRedo,**无撤销栈**)、`uiStore`/`settingsStore`/`clipboardStore`/`recentStore`(纯 UI 态)、`mediaStore`/`libraryStore`(后端镜像)、`editActions`(手势→`EditRequest`,删除健壮化、媒体落轨串行化、复制/剪切/粘贴)、`mediaActions`/`projectActions`(对话框驱动)、`sync.ts`(镜像更新唯一入口 + `forceRefresh`)。 - **[ipc-api.md](ipc-api.md)** — `lib/` 对接面:`api.ts`(IPC + `isTauri`,`editApply`/`getTimeline`/`getWaveform` try/catch/`compositeFrame`/`secret_*` 事件)、`types.ts`(领域镜像 + `EditRequest` 全变体 + **camelCase 对齐铁律**)、`asset.ts`(`convertFileSrc` 资产协议)、`libraryApi.ts`(全局库通道)、`dialog.ts`(对话框懒加载)、`fallback.ts`(浏览器内存 demo 子集)。 - **[timeline-ui.md](timeline-ui.md)** — `components/timeline/` + `lib/geometry.ts`/`snap.ts`/`ruler.ts`/`zones.ts`/`clip.ts`:像素↔帧(前端、截断)、Canvas 绘制(`timelineCanvas`/`clipRenderer`/`rulerCanvas`)、吸附/多探针、命中测试、刮擦/缩放/平移/移动/修剪/切割与触控板手势、轨道头、右键菜单、媒体交换。 - **[preview-ui.md](preview-ui.md)** — `components/preview/`:单表面 + 单时钟模型,`previewEngine`(rAF 三态 PLAY/SCRUB/PAUSE)、`timelinePlayback`(纯逻辑)、`TimelinePlaybackLayer`(被动 DOM 注册)、`previewLayerStyles`(样式采样)、`Preview`(单素材/合成两模式 + 运输控制)。 -- **[panels-ui.md](panels-ui.md)** — `components/` 其余:inspector(检查器 + 关键帧面板 + 可拖拽数值 + 文本)、media(媒体面板 + 全局库页 + 星标)、toolbar、home(启动器)、settings(含 BYOK keychain)、agent(占位)、shell(五面板布局 + 分割条 + 标题栏)、ui(lucide `Icon` / `HoverButton` / `Dropdown` / `PanelShell`)。 +- **[panels-ui.md](panels-ui.md)** — `components/` 其余:inspector(检查器 + 关键帧面板 + 可拖拽数值 + 文本)、media(媒体面板 + 全局库页 + 星标)、toolbar、home(启动器)、settings(含 BYOK keychain)、agent(有序对话与工具结果)、shell(五面板布局 + 分割条 + 标题栏)、ui(lucide `Icon` / `HoverButton` / `Dropdown` / `PanelShell`)。 - **[hooks-i18n-theme.md](hooks-i18n-theme.md)** — `hooks/`(`useAutosave` 防抖保存、`useKeyboardShortcuts` 快捷键)+ `i18n/`(zh-CN 默认 / en)+ `lib/theme.ts`(`AppTheme` 数值常量单一源)+ `styles/`(`tokens.css` CSS 变量、`global.css` 全局基础)。 ## 规格 @@ -84,7 +89,7 @@ web/src/ │ ├── toolbar/ 顶部工具栏 │ ├── home/ 启动器 │ ├── settings/ 设置(含 BYOK) -│ ├── agent/ Agent 面板(占位) +│ ├── agent/ Agent 面板(有序对话与工具结果) │ ├── shell/ 五面板布局 + 分割条 + 标题栏 + 视图菜单 │ └── ui/ 通用原始件(lucide Icon / HoverButton / Dropdown / PanelShell) ├── hooks/ useAutosave / useKeyboardShortcuts diff --git a/docs/modules/web/OVERVIEW.md b/docs/modules/web/OVERVIEW.md index ccd0e0fe..f82f6f74 100644 --- a/docs/modules/web/OVERVIEW.md +++ b/docs/modules/web/OVERVIEW.md @@ -1,5 +1,10 @@ # web — 总览 +> 状态:draft · 阶段:implementation-backed · 源码同步:2026-09-06。 +> 本次定向来源:`web/src/components/media/MediaPanel.tsx`、`MediaTabBar.tsx`、`components/preview/playbackRoute.ts`、`store/uiStore.ts`。 +> Text/Effect/Sticker 已接线;Sticker 使用项目图片/Lottie 与既有导入、预览、落轨流程。Transition/Captions/Smart Pack 有对应面板。多预览 tab、媒体三种视图和 temporal Rust compositor 已接入。当前测试结果统一引用当日审计,旧测试数仅为历史。 +> 当前验收见[公开 Beta 审计](../../audit/2026-09-06/public-beta-validation.md);下文历史里程碑和测试记录保留其原时点边界。 + > 上级:[本模块目录](INDEX.md) · [模块文档树](../INDEX.md) · [docs 总目录](../../INDEX.md) ## 一句话定位 @@ -64,10 +69,10 @@ UI 手势 / 快捷键 / 拖放 - 检查器(Video/Audio/Text、现场采样、关键帧面板与钻石键、可拖拽数值、Source 检查器、截帧入库)。 - 媒体面板(导入/拖放/双击/星标/萃取音频/Relink/搜索/文件夹)、全局库页、工具栏、主页启动器、设置(BYOK keychain、Codex/ChatGPT 官方登录、外观、导入目录)。 - 复制/剪切/粘贴、删除健壮化、自动保存、双语(zh-CN/en)、AppTheme 全令牌、可访问性(focus trap、24px 命中、reduced-motion、760×480 布局)。 -- 全量回归:129 个测试文件 / 1072 项测试通过(Vitest),`pnpm build` 通过。 +- 历史 Beta 1/2 回归记录为 129 文件 / 1072 测试;本候选结果见当日审计,不沿用旧计数。 **计划中 / 后续版本:** -- 媒体面板部分标签(Text/Sticker/Effect/Transition/Captions/Smart Wrap)在剪映式顶栏中为置灰占位,接相应功能面板后逐个点亮。 +- Sticker 已实现项目图片/Lottie 展示、导入、预览、拖拽/落轨及工程身份隔离;[定向测试](../../audit/2026-09-06/sticker-panel.md)通过,当前新包 GUI 验收仍待执行。 - 高负载流式预览引擎进一步优化、库→时间线拖拽接线(#37 尾巴)。 - 与上游逐项 1:1 截图/行为/几何对拍验收(见 [SPEC.md](SPEC.md))。 diff --git a/docs/modules/web/SPEC.md b/docs/modules/web/SPEC.md index 75d25e6c..9a75381b 100644 --- a/docs/modules/web/SPEC.md +++ b/docs/modules/web/SPEC.md @@ -1,5 +1,8 @@ # 前端 React UI —— 1:1 复刻上游 实现就绪规格 (Issue #12) +> 状态:draft · 阶段:partial-implementation · 设计与早期实现来源保留;原文日期、行号和“待做”属于设计时点。2026-09-06 当前实现见本模块 [OVERVIEW.md](OVERVIEW.md),当前验收见[公开 Beta 记录](../../audit/2026-09-06/public-beta-validation.md)。 + + > **状态**:v1 硬要求规格。**已有功能的 UI 与交互必须 1:1 复刻上游 palmier-pro。** > **范围**:`web/`(React + TypeScript + Vite + Zustand)的全部可视层与交互层。**不含** Rust core / Tauri command 的内部实现(只定义对接契约)。 > **证据基准**:全部数值/行为均引自 `palmier-pro-upstream/Sources/PalmierPro/`,并标注 `文件:行号`。凡本规格与上游源码冲突,**以上游源码为准**。 @@ -1360,4 +1363,4 @@ Tauri envelope;Rust `every_frontend_edit_request_deserializes_to_intended_comm | Preview | `Preview/PreviewContainerView.swift` + `PreviewTab.swift` + overlays | | 数据模型 | `Models/Timeline.swift` / `Keyframe.swift` / `ClipType.swift` | | 小组件 | `UI/HoverHighlight.swift` / `CapsuleButton.swift` | -| 目标架构(Tauri/React/Zustand/命令-事件) | `OpenTake/docs/ARCHITECTURE.md` | +| 目标架构(Tauri/React/Zustand/命令-事件) | `OpenTake/docs/architecture/ARCHITECTURE.md` | diff --git a/docs/modules/web/panels-ui.md b/docs/modules/web/panels-ui.md index 190e60d5..a2b1b169 100644 --- a/docs/modules/web/panels-ui.md +++ b/docs/modules/web/panels-ui.md @@ -22,15 +22,21 @@ ## inspector(检查器) -- `Inspector.tsx`:属性检查器主体(SPEC §6)。四态:多选摘要 / 单选 clip 检查(Video·Audio·Text 标签)/ 无选时显工程元数据 / 媒体资产检查(占位)。**现场采样**:每次 render 从 `activeFrame` 取 clip 的动画值,故数值字段总显示播放头处当前值;已有关键帧轨的属性显示为只读「(animated)」并把编辑转到关键帧面板。所有编辑经 `editActions.setClipProperties()`。 +- `Inspector.tsx`:属性检查器主体(SPEC §6)。四态:多选摘要 / 单选 clip 检查(Video·Audio·Text 标签)/ 无选时显工程元数据 / 媒体资产 Source 检查器。**现场采样**:每次 render 从 `activeFrame` 取 clip 的动画值,故数值字段总显示播放头处当前值;已有关键帧轨的属性显示为只读「(animated)」并把编辑转到关键帧面板。所有编辑经 `editActions.setClipProperties()`。 - `KeyframesPanel.tsx` + `KeyframesLaneRow.tsx`:关键帧面板(SPEC §6.4)。单选 clip 下每个可动画属性一行(视频 position/scale/rotation/opacity/crop;音频 volume),顶部刻度尺 + 面板级红色播放头叠加,行内可拖拽菱形标记(→ stamp/move/remove/insertation 关键帧动作)。 - `ScrubbableNumberField.tsx`:可拖拽数值控件(SPEC §6.6)。水平拖拽改值(Shift×10 / Cmd×0.1),单击切文本输入(Enter/失焦提交、ESC 取消),动画属性时只读;`onCommit` 触发命令。 -- `TextTab.tsx`:编辑 `Clip.textContent`(草稿本地态,失焦提交;字号/颜色/对齐等样式延后)。 +- `TextTab.tsx`:编辑 `Clip.textContent`及 TextStyle(字体、字号、颜色、对齐等),经命令提交。 - `SwapMediaSection.tsx`:检查器内的「替换素材」入口(→ `swapMedia`)。 +## 2026-09-06 媒体与预览增量 + +`MediaPanel.tsx` 的 TextTab 调用 `addTextClip`;EffectTab 仅对单个选中视觉片段追加预设并保留既有 effects,链接音轨不作为额外视觉选中项。Sticker 已从 `33ee8e2` 的禁用占位接为可用主标签,筛选当前项目(含子目录)的 image/Lottie,复用 MediaCard 与导入/落轨事务;空态、处理中、错误和工程身份隔离有对应处理。转场、字幕与 Smart Pack 均有独立内容组件。媒体视图支持 folder/flat/grouped,预览区支持多个素材 tab 与旧存储状态归一。 + +本段记录源码接线;[当日审计](../../audit/2026-09-06/public-beta-validation.md)记录 GUI 与自动化结果。 + ## media(媒体面板与全局库) -- `MediaPanel.tsx`:媒体库容器。顶部主标签(Material/Audio/Text/Sticker/Effect/Transition/Captions/Smart Wrap,仅 Material·Audio 可用、余者置灰占位,仿剪映);二级标签 Import/Mine(Mine=星标收藏,localStorage)。过滤管线不可变(audio 标签仅显纯音频,Mine 仅显收藏)。卡片 HTML5-draggable(`MEDIA_DND_TYPE`),单击预览、双击 `addMediaToTimeline`、星标切换、视频可「萃取音频」(`extractAudio` + 保存对话框);离线素材红覆盖 + Relink。订阅 `uiStore.mediaTab/mediaSubTab`,消费 `mediaStore`。 +- `MediaPanel.tsx`:媒体库容器。顶部主标签(Material/Audio/Music/Text/Sticker/Effect/Transition/Captions/Smart Pack;全部已有对应内容入口);二级标签 Import/Mine(Mine=星标收藏,localStorage)。过滤管线不可变(audio 标签仅显纯音频,Mine 仅显收藏)。卡片 HTML5-draggable(`MEDIA_DND_TYPE`),单击预览、双击 `addMediaToTimeline`、星标切换、视频可「萃取音频」(`extractAudio` + 保存对话框);离线素材红覆盖 + Relink。订阅 `uiStore.mediaTab/mediaSubTab`,消费 `mediaStore`。 - `LibraryView.tsx`:全局库整页视图(`view === "library"`,跨项目永久库),消费 `libraryStore`,支持分类/搜索/排序与「导入到项目」。 - `MediaTabBar.tsx`:主/次标签按钮组。 - `favorites.ts`:星标收藏 store(localStorage)。 @@ -43,7 +49,7 @@ - `home/HomeView.tsx`:启动器。最近项目列表 / 空态 + 新建/打开/设置入口。消费 `recentStore`,调 `newProjectAndEnter`/`openProjectViaDialog`/`openProjectPath`。 - `settings/SettingsView.tsx`:模态设置。标签式:General(语言) / Appearance(主题) / Import(默认目录) / AI(BYOK 密钥) / About(版本·许可)。**BYOK 明文密钥存 OS keychain**(`secret_*`),不进 localStorage。消费 `settingsStore` 与 i18n。 -- `agent/AgentPanel.tsx`:内置 Agent 聊天面板(当前占位,SPEC §2.1,后续接通)。 +- `agent/AgentPanel.tsx`:内置 Agent 聊天面板(有序文本/tool use/tool result、历史恢复与 provider 对话接线)。 ## ui(通用原始件) @@ -57,7 +63,7 @@ ## 完成状态 - **已实现**:三种布局 + 最大化、检查器(Video/Audio/Text + 现场采样 + 关键帧面板 + 可拖拽数值)、媒体面板(导入/拖放/双击/星标/萃取音频/Relink)、全局库页、工具栏、主页启动器、设置(含 BYOK keychain)、lucide 图标体系。 -- **计划中/占位**:Agent 面板真实对话;媒体面板的 Text/Sticker/Effect/Transition/Captions/Smart Wrap 标签为置灰占位;检查器文本样式(字号/颜色/对齐)与媒体资产检查待补。 +- **待原生验收**:Sticker 主标签及图片/Lottie 导入、预览、拖拽/落轨已经实现,[专项记录](../../audit/2026-09-06/sticker-panel.md)包含定向测试与构建;其他主标签、文本样式与 Source 检查器亦已有实现,新包原生交互按候选验收。 ## 相关文档 diff --git a/docs/modules/web/preview-ui.md b/docs/modules/web/preview-ui.md index 224df2ae..26a3a86e 100644 --- a/docs/modules/web/preview-ui.md +++ b/docs/modules/web/preview-ui.md @@ -1,5 +1,8 @@ # preview-ui — 预览与播放(components/preview) +> 状态:draft · 阶段:implementation-backed · 源码同步:2026-09-06。 +> Preview 已支持多个素材 tab、激活历史、关闭/恢复与旧状态归一(`uiStore.ts`);temporal compositor 支持合成时间线倒放/变速,Lottie 走 Rust 原生路由。下文早期单素材描述表示显示模式,不是只能打开一个素材 tab。最新路由见[播放架构](../../architecture/PLAYBACK-ENGINE.md),测试/GUI 边界见[当日审计](../../audit/2026-09-06/public-beta-validation.md)。 + > 上级:[本模块目录](INDEX.md) · [模块文档树](../INDEX.md) · [docs 总目录](../../INDEX.md) > > 覆盖 `web/src/components/preview/`。预览面板按**单表面 + 单时钟**模型工作:播放/暂停/拖拽共用同一组 `