# v2/deletions.md — 刪檔 / 刪程式碼清單 > 所屬:TDD v2 §2.5 > 版本:v2.1(2026-04-14 吸收 PM Minor 5 grep 精準化 + Architect Q1 互審結論) > 決策依據:R5-5a(Mock 模式完全砍除)、R5-7 前置(yt-dlp 全套砍除) > 對應 milestone:M8-1(砍 yt-dlp)、M8-2(砍 Mock) > 給工程師:這是按表操作的清單,依序砍完就能送 Reviewer **操作規則**: - 每個「刪」動作後必須 `go build ./server/...` 和 `pnpm --dir frontend build` 確認還可 compile - 每個「改」動作後同樣 - 全部砍完後 `git grep -i 'yt-dlp\|ytdlp\|YTDLP\|mock\|Mock\|MOCK\|VISIONA_MOCK'` 應該只剩註解(如「原本這裡有 Mock 模式,已在 v2 砍除」)或明確是別的 mock(例如 gomock 測試框架) --- ## 1. 後端 Go — yt-dlp 全套砍除 ### 1.1 `server/internal/camera/video_source.go` **刪除區塊**:第 91-140 行 ```go // ResolveWithYTDLP uses yt-dlp to extract the direct video stream URL // from platforms like YouTube, Vimeo, etc. // Returns the resolved direct URL or an error. func ResolveWithYTDLP(rawURL string) (string, error) { ... 整個 func ... } // friendlyYTDLPError 把 yt-dlp 的技術性錯誤訊息轉成使用者能理解的提示。 func friendlyYTDLPError(stderr string) string { ... 整個 func ... } ``` 同時檢查 import:若原本因為 `ResolveWithYTDLP` 引入了 `strings` / `os/exec` / `fmt`,確認這些 import 在檔案其他地方還有用(視情況保留或移除)。 **`NewVideoSourceFromURL` / `NewVideoSourceFromURLWithSeek` 的砍除**(v2.1 Minor 5 精準化): v2.0 原本寫「可能仍然有其他路徑呼叫,grep 再決定」— 這是模糊的。Architect Q1 互審時已實際 grep 確認結論: ``` grep -rn 'NewVideoSourceFromURL' /Users/jimchen/visionA/local-tool/server/ ``` **實測結果**(2026-04-14 Architect 互審): - `server/internal/api/handlers/camera_handler.go:435`(`StartFromURL` 內部呼叫)— 即將砍 - `server/internal/api/handlers/camera_handler.go:731`(`handleVideoSeek` 的 `videoIsURL` guard 下的 seek 分支)— dead code,連同 `videoIsURL` field 一起砍 **沒有其他呼叫者**。所以: - 連同 `NewVideoSourceFromURL` 整個 function 砍 - 連同 `NewVideoSourceFromURLWithSeek`(若有)整個 function 砍 - 連同 `newVideoSource(..., isURL=true, ...)` 的 `isURL` 參數分支砍(簡化內部函式簽名) - `camera_handler.go:731` 的 `handleVideoSeek` 中 `if h.videoIsURL { ... }` 整段 seek URL 分支砍(dead code,因為 `videoIsURL` 將不可能為 true) - `CameraHandler` struct 的 `videoIsURL bool` field 砍 **驗收**:砍完後以下 grep 應全部無輸出: ```bash grep -rn 'NewVideoSourceFromURL\|videoIsURL' /Users/jimchen/visionA/local-tool/server/ ``` ### 1.2 `server/internal/api/handlers/camera_handler.go` **刪除區塊**:第 341-497 行 ```go // ytdlpHosts lists hostnames where yt-dlp should be used to resolve the actual var ytdlpHosts = map[string]bool{ ... } type urlKind int const ( urlDirect urlKind = iota urlYTDLP urlBad ) // classifyVideoURL determines how to handle the given URL. func classifyVideoURL(rawURL string) (urlKind, string) { ... } // StartFromURL handles video/stream inference from a URL (HTTP, HTTPS, RTSP). func (h *CameraHandler) StartFromURL(c *gin.Context) { ... } ``` → 整個 `ytdlpHosts` / `urlKind` / `classifyVideoURL` / `StartFromURL` 函式全砍。 **`videoIsURL` field 的處置**(v2.1 Minor 5 定案): Architect Q1 互審時已 grep:`videoIsURL` 只在 `camera_handler.go:731` 的 `handleVideoSeek` URL 分支被讀取,沒有 stopActivePipeline 的特殊 cleanup。因此: - 砍 `CameraHandler.videoIsURL bool` field - 砍 `handleVideoSeek` 內 `if h.videoIsURL { ... }` 整個 URL seek 分支(dead code — 砍 `StartFromURL` 後永遠不會是 true) - 砍 `startVideoInference` 設 `h.videoIsURL = true` 的行(若有) - 結論:**不保留為 always-false flag**,直接刪乾淨(v2.0 原本留餘地「可能保留」,v2.1 明確決定刪) ### 1.3 `server/internal/api/router.go` **刪除**:第 83 行 ```go api.POST("/media/url", cameraHandler.StartFromURL) ``` ### 1.4 `server/internal/deps/checker.go` **刪除**:第 30-32 行 ```go check("yt-dlp", false, "macOS: brew install yt-dlp | Windows: winget install yt-dlp", "--version"), ``` **同時修改**:第 69-70 行的註解提及 yt-dlp 冷啟動 20 秒的內容,更新為: ```go // 效能:bundle 內的 binary 冷啟動可能較慢(尤其 PyInstaller), // bundle binary 已知良好,跳過 version 查詢以加速啟動。 ``` ### 1.5 `server/main.go` **修改**:第 89-95 行的 PATH 注入註解。原文: ```go // 把 VISIONA_BUNDLE_BIN_DIR 加到 PATH,讓 exec.Command("yt-dlp") / exec.Command("ffmpeg") // 能透過 LookPath 找到 bundle 內的 binary(Go 1.19+ Windows 不再搜 cwd)。 ``` 改為: ```go // 把 VISIONA_BUNDLE_BIN_DIR 加到 PATH,讓 exec.Command("ffmpeg") / exec.Command("ffprobe") // 能透過 LookPath 找到 bundle 內的 binary(Go 1.19+ Windows 不再搜 cwd)。 ``` **PATH 注入邏輯本身不動**(ffmpeg 仍需要)。 --- ## 2. 後端 Go — Mock 模式全套砍除(R5-5a) ### 2.1 整檔刪除 | 檔案 | 行數 | 說明 | |------|------|------| | `server/internal/driver/mock/mock_driver.go` | 183 | `MockDriver` struct 與所有 method | | `server/internal/camera/mock_camera.go` | 95 | `MockCamera` struct 與所有 method | **動作**: ``` rm server/internal/driver/mock/mock_driver.go rmdir server/internal/driver/mock # 該目錄若空就刪 rm server/internal/camera/mock_camera.go ``` ### 2.2 修改 `server/internal/device/manager.go` **現況**: ```go import ( ... mockdriver "visiona-local/server/internal/driver/mock" ... ) type Manager struct { ... mockMode bool ... } func NewManager(registry *DriverRegistry, mockMode bool, mockCount int, scriptPath string) *Manager { ... if mockMode { ... mockdriver.NewMockDriver(...) ... } } func (m *Manager) Scan() error { if m.mockMode { ... } ... } func (m *Manager) someMethod() { if m.mockMode { ... } ... } ``` **改法**: - 刪 `mockdriver` import - 刪 `mockMode` / `mockCount` 欄位 - 修改 `NewManager` 簽名:`func NewManager(registry *DriverRegistry, scriptPath string) *Manager` - 砍掉所有 `if m.mockMode { ... }` 分支,只留 real path ### 2.3 修改 `server/internal/camera/manager.go` **現況**: ```go type Manager struct { mockMode bool mockCamera *MockCamera ... } func NewManager(mockMode bool) *Manager { return &Manager{mockMode: mockMode} } func (m *Manager) Start(...) { if m.mockMode { m.mockCamera = NewMockCamera(width, height) ... } ... } ``` **改法**: - 刪 `mockMode` 欄位、`mockCamera` 欄位 - 改簽名:`func NewManager() *Manager` - 砍所有 `if m.mockMode { ... }` ### 2.4 修改 `server/internal/config/config.go` **現況**:第 21-23 行 ```go MockMode bool MockCamera bool MockDeviceCount int ``` 第 39-41 行: ```go flag.BoolVar(&cfg.MockMode, "mock", false, "Enable mock device driver") flag.BoolVar(&cfg.MockCamera, "mock-camera", false, "Enable mock camera") flag.IntVar(&cfg.MockDeviceCount, "mock-devices", 1, "Number of mock devices") ``` **改法**:整組刪(三個欄位 + 三個 flag)。 ### 2.5 修改 `server/main.go` **現況**: ```go // 第 86-87 行 logger.Info("Mock mode: %v, Mock camera: %v, Dev mode: %v, Python mode: %s", cfg.MockMode, cfg.MockCamera, cfg.DevMode, cfg.PythonMode) // 第 142 行 deviceMgr := device.NewManager(registry, cfg.MockMode, cfg.MockDeviceCount, bridgeScript) // 第 147 行 cameraMgr := camera.NewManager(cfg.MockCamera) ``` **改法**: ```go logger.Info("Dev mode: %v, Python mode: %s", cfg.DevMode, cfg.PythonMode) deviceMgr := device.NewManager(registry, bridgeScript) cameraMgr := camera.NewManager() ``` ### 2.6 修改 `server/internal/device/manager_test.go` 如果測試檔案裡有 mockMode 相關 case → 整段刪。若整個測試檔都是 mock-based → 整檔刪。 **動作**:`cat server/internal/device/manager_test.go` 確認後決定。 ### 2.7 修改 `server/internal/api/api_e2e_test.go` 如果 e2e test 開啟 mock mode 跑 → 改為 skip(沒硬體時 skip),或改寫成不依賴 mock。 **動作**:`grep -n 'Mock\|mock' server/internal/api/api_e2e_test.go` 查看,M8-2 執行者決定保留哪些測試。 --- ## 3. Wails app.go — Mock 模式砍除 ### 3.1 `visiona-local/app.go` 修改 **現況**: ```go // 第 83 行 type App struct { ... mockMode bool ... } // 第 119-120 行 // M7:預設真實硬體模式(使用者決策 Q8) // 若要強制 mock 模式(無 Kneron 裝置環境下 debug),設環境變數 VISIONA_MOCK=1 mock := os.Getenv("VISIONA_MOCK") == "1" return &App{ pythonMode: mode, mockMode: mock, } // 第 429 行(在 startServer 中) if err != nil && !a.mockMode { ... } // 第 441 行 if !a.mockMode && pyBin != "" { if err := a.ensureDriverInstalled(pyBin); err != nil { ... } } // 第 472-479 行(組 args) if a.mockMode { args = append(args, "--mock") } else { args = append(args, "--python-mode", string(pyMode)) if pyBin != "" { args = append(args, "--python", pyBin) } } // 第 502 行 if !a.mockMode && pyBin != "" { env = append(env, "VISIONA_PYTHON="+pyBin) ... } ``` **改法**: - 刪 `mockMode` struct field - 刪 `NewApp()` 的 env 讀取 + 初始化 - 刪所有 `!a.mockMode` / `a.mockMode` 條件 - 組 args 的邏輯簡化:一定走 real path,`--python-mode` 與 `--python` 一定帶 - `err != nil && !a.mockMode` → 直接 `err != nil`(失敗就 return 錯誤) ### 3.2 `visiona-local/frontend/app.js` 若有任何提及 mock 的內容(M7-B 後應該沒有) → 刪 --- ## 4. 打包流程砍除 ### 4.1 `Makefile` — 砍 yt-dlp **刪除 targets**: | 行數 | 內容 | |------|------| | ~22 | `vendor-sync vendor-python vendor-wheels vendor-ffmpeg vendor-ytdlp \` 中的 `vendor-ytdlp` | | ~23 | `vendor-python-windows ... vendor-ytdlp-windows` 同上 | | ~24 | `vendor-python-linux ... vendor-ytdlp-linux` 同上 | | 39 | help 文字中 `/yt-dlp` | | 70-71 | `YTDLP_URL_DARWIN := ...` | | 73 | `vendor-sync` 依賴移除 `vendor-ytdlp` | | 134-144 | `vendor-ytdlp:` target 整段 | | 182 | `payload-macos` 依賴移除 `vendor-ytdlp` | | 188-189 | `cp vendor/yt-dlp/darwin/yt-dlp payload/darwin/bin/` + `chmod +x payload/darwin/bin/yt-dlp` | | 194 | help 文字 `+ yt-dlp` | | 219 | `YTDLP_URL_WINDOWS := ...` | | 288-296 | `vendor-ytdlp-windows:` target 整段 | | 298 | `payload-windows` 依賴移除 `vendor-ytdlp-windows` | | 299 | help 文字 `+ yt-dlp` | | 307 | `cp vendor/yt-dlp/windows/yt-dlp.exe payload/windows/bin/` | | 332 | `YTDLP_URL_LINUX := ...` | | 381-390 | `vendor-ytdlp-linux:` target 整段 | | 392 | `payload-linux` 依賴移除 `vendor-ytdlp-linux` | | 393 | help 文字 `+ yt-dlp` | | 401 | `cp vendor/yt-dlp/linux/yt-dlp ...` 整行 | **同時**:刪 `/vendor/yt-dlp/` 整個目錄(若 Makefile 已經產出過) ``` rm -rf vendor/yt-dlp/ ``` ### 4.2 `installer/windows/visiona-local.iss` **刪除**:第 74 行的區段標題 `; ── ffmpeg + yt-dlp ───` 改為 `; ── ffmpeg + ffprobe ───`。 第 76 行: ``` Source: "..\..\payload\windows\bin\yt-dlp.exe"; DestDir: "{app}\bin"; Flags: ignoreversion ``` **刪除此行**。同時新增 ffprobe 和 LGPL license 行(見 `v2/ffmpeg-lgpl.md` §8)。 ### 4.3 `installer/linux/build-appimage.sh` **修改**:第 61-62 行 ```bash # ffmpeg / yt-dlp for tool in ffmpeg yt-dlp; do ``` 改為: ```bash # ffmpeg / ffprobe for tool in ffmpeg ffprobe; do ``` ### 4.4 `scripts/bootstrap-linux.sh` **修改**:第 61 行 ```bash make vendor-python-linux vendor-wheels-linux vendor-ffmpeg-linux vendor-ytdlp-linux ``` 改為: ```bash make vendor-python-linux vendor-wheels-linux vendor-ffmpeg-linux ``` ### 4.5 `scripts/bootstrap-windows.ps1` **修改**: - 第 191 行註解:`保留 vendor/ 快取(Python runtime / wheels / ffmpeg / yt-dlp)以免重下 200MB` → `... 200MB)` 改為 `... (Python runtime / wheels / ffmpeg)` - 第 202 行註解:同上 - 第 207 行:`'make vendor-python-windows vendor-wheels-windows vendor-ffmpeg-windows vendor-ytdlp-windows'` 移除 `vendor-ytdlp-windows` --- ## 5. 前端 TypeScript / React — yt-dlp URL tab 砍除 ### 5.1 `frontend/src/components/camera/source-selector.tsx` **現況**:260 行,使用 `videoMode` state + `pasteUrl` / `urlPlaceholder` / `urlHelpText` i18n、`handleUrlSubmit` / `startFromUrl` 呼叫。 **改動**: 1. 刪 import:`startFromUrl` 從 `useCameraStore` destructure 中移除(第 28 行附近) 2. 刪 state:第 51 行 `const [videoMode, setVideoMode] = useState<'file' | 'url'>('file');` 整行刪 3. 刪 state:同一段若有 `const [videoUrl, setVideoUrl] = useState('');` 也刪 4. 刪函式:第 94-96 行 `handleUrlSubmit` 整個函式刪 5. 刪 UI:第 185-253 行的 video tab 內的 mode toggle + `videoMode === 'file' ? ... : ...` 整塊改為「只剩 file 選擇」: ```tsx {activeTab === 'video' && (
{t('camera.mp4AviMovMpeg')}
)} ``` 砍掉「上傳檔案 / 貼上連結」兩個 mode Button + URL input + help text + loading text。 ### 5.2 `frontend/src/stores/camera-store.ts` **刪除**: - 第 32 行 type 定義:`startFromUrl: (url: string, deviceId: string) => Promise;` - 第 167-196 行:`startFromUrl` 函式的整個實作(30 行) ### 5.3 `frontend/src/lib/i18n/types.ts` **刪除**: - 第 210-212 行:`pasteUrl`, `urlPlaceholder`, `urlHelpText` 三個 key 的 type 定義 - 第 422 行:`cannotOpenVideoUrl` key 的 type 定義 **新增**: - `mp4AviMovMpeg: string;`(取代 v1 的 `mp4AviMov` 或並存)— 決定改不改 key:**建議改名成 `videoFormats`**(通用化),以便未來擴充時不用再改型別名。 ### 5.4 `frontend/src/lib/i18n/zh-TW.ts` **刪除/修改**: - 第 212 行 `pasteUrl: '貼上連結',` 刪 - 第 213 行 `urlPlaceholder: 'https://example.com/video.mp4',` 刪 - 第 214 行 `urlHelpText: '支援 YouTube、直接影片 URL(.mp4 等)及 RTSP 串流。',` 刪 - 第 217 行 `mp4AviMov: 'MP4, AVI, MOV',` → **改為** `videoFormats: 'MP4 / AVI / MOV / MPEG / MPG',` - 第 424 行 `cannotOpenVideoUrl: '無法開啟影片連結',` 刪(若 store 沒有呼叫處) ### 5.5 `frontend/src/lib/i18n/en.ts` 同 §5.4,英文版對應修改: - 刪 `pasteUrl`, `urlPlaceholder`, `urlHelpText`, `cannotOpenVideoUrl` - `mp4AviMov: 'MP4, AVI, MOV',` → `videoFormats: 'MP4 / AVI / MOV / MPEG / MPG',` --- ## 6. 前端 TypeScript / React — Mock 模式砍除 ### 6.1 `frontend/src/app/settings/page.tsx` **刪除區塊**:第 140-156 行 ```tsx
{/* TODO: 連接 backend GET /api/system/config ... */}

{t('settings.hardware.runtimeModeHint')}

``` → 整段 `
` 與其後的 `` 一起刪(Separator 是用來分隔 runtimeMode 與 pythonMode 的)。 ### 6.2 `frontend/src/lib/i18n/types.ts` **刪除**:第 272-275 行 ```typescript runtimeMode: string; runtimeModeMock: string; runtimeModeReal: string; runtimeModeHint: string; ``` ### 6.3 `frontend/src/lib/i18n/zh-TW.ts` **刪除**:第 274-277 行 4 個 key。 ### 6.4 `frontend/src/lib/i18n/en.ts` **刪除**:同 §6.3,第 274-277 行英文版。 ### 6.5 `frontend/src/lib/i18n/zh-TW.ts` + `en.ts` 的 `noDevices` 文字 **現況**: ```typescript // zh-TW.ts:70 noDevices: '未偵測到裝置。請確認已啟用 Mock 模式或連接裝置。', // en.ts:70 noDevices: 'No devices detected. Make sure mock mode is enabled or connect a device.', ``` **改為**(對應 R-v2-7 的 empty state 建議): ```typescript // zh-TW noDevices: '未偵測到 Kneron 裝置。請連接 KL520 / KL720 後按「掃描」。', // en noDevices: 'No Kneron devices detected. Please connect a KL520 / KL720 device and click "Scan".', ``` --- ## 7. Wails 控制台(改寫而非刪) `visiona-local/frontend/index.html` / `app.js` / `style.css` — **改寫**而非刪,詳見 `v2/control-panel.md` §7「檔案系統變化」。 **提醒**:改寫步驟屬於 M8-5,不在本文件的 M8-1 / M8-2 範圍。 --- ## 8. 驗收 grep M8-1 + M8-2 全部完成後,執行以下 grep 應該全部 clean(只剩註解或非相關的 match): ```bash # yt-dlp grep -rn 'yt-dlp\|ytdlp\|YTDLP\|ResolveWithYTDLP\|friendlyYTDLPError\|ytdlpHosts\|urlYTDLP\|classifyVideoURL\|StartFromURL\|cannotOpenVideoUrl\|pasteUrl\|urlPlaceholder\|urlHelpText' \ /Users/jimchen/visionA/local-tool/server/ \ /Users/jimchen/visionA/local-tool/frontend/ \ /Users/jimchen/visionA/local-tool/visiona-local/ \ /Users/jimchen/visionA/local-tool/Makefile \ /Users/jimchen/visionA/local-tool/installer/ \ /Users/jimchen/visionA/local-tool/scripts/ # Mock grep -rn 'MockMode\|mockMode\|MockCamera\|MockDriver\|NewMockDriver\|NewMockCamera\|VISIONA_MOCK\|runtimeModeMock' \ /Users/jimchen/visionA/local-tool/server/ \ /Users/jimchen/visionA/local-tool/frontend/ \ /Users/jimchen/visionA/local-tool/visiona-local/ ``` **預期**: - `yt-dlp` grep:**完全無輸出**(或只有 v1 文件 `.autoflow/` 內的歷史紀錄,忽略) - `Mock` grep:**完全無輸出** 若有殘留 → 補刀刪除或保留但註記原因。 --- ## 9. 砍除後的 compile 確認 ```bash # Go server cd /Users/jimchen/visionA/local-tool/server && go build ./... cd /Users/jimchen/visionA/local-tool/server && go test ./... # Wails app cd /Users/jimchen/visionA/local-tool/visiona-local && go build . # 前端 cd /Users/jimchen/visionA/local-tool/frontend && pnpm install && pnpm build ``` 四個都要綠才算 M8-1 / M8-2 完成,可送 Reviewer。