# v2/code-reuse-v2.md — 沿用 vs 改寫 vs 新寫 > 所屬:TDD v2 §2.8 > 目的:以 v1 (M1-M7 已完成)為基準,量化 v2 refactor 的沿用率 > 承接:`architect-analysis-round2-refactor.md` §D > 結論:**整體沿用率 85-95%**(三方共識 #1),**不是丟掉重做**,是**擴充 + 砍功能** --- ## 1. 模組沿用率總表 | 模組 | 現況大小 | 改動量 | 沿用率 | 關鍵影響 | |------|---------|-------|-------|---------| | `server/` Go 後端 | ~15k LoC | ~400 行修改 + 新增 boot-id endpoint | **~96%** | 砍 yt-dlp + Mock + 新增 CORS whitelist + boot-id;預置模型 / inference / camera pipeline / Python bridge / WebSocket / static serving 全部不動 | | `server/web/out/` go:embed 的 Next.js static | ~20k LoC | ~100 行修改 + ~250 行新增(offline overlay + boot-id watcher + i18n) | **~88%** | 砍 URL tab + Mock UI;新增 Offline Overlay + boot-id watcher;業務頁面全部不動 | | `visiona-local/app.go` Wails 殼 | 1584 行 | ~350 行新增 / ~30 行刪改 | **~85%** | 新增 ServerController + LogBuffer + Preferences;watchServer 改為 Error state;`mockMode` 欄位與 VISIONA_MOCK 砍除;核心 startup / shutdown / single-instance / driver installer 全部不動 | | `visiona-local/frontend/` Wails 內嵌 UI | 211 行(html + js + css) | 三檔全部改寫 + 新增 12 檔 | **程式面砍掉重寫,概念承襲** | 沿用 ES module + wailsjs 串接模式,但內容是全新控制台 UI | | `installer/` 打包腳本 | macOS dmgbuild / Windows iss / Linux AppImage | 各 ~3 行修改 | **~98%** | 只改 ffmpeg/ffprobe/LGPL licnese 的 copy,其餘不變 | | `Makefile` | 570 行 | ~60 行修改 + ~80 行新增(macOS build) | **~85%** | 砍 yt-dlp targets;改 Windows/Linux ffmpeg URL;新增 macOS 自 build | | `scripts/bootstrap-*.sh/.ps1` | 中等 | 各 ~5 行修改 | **~99%** | 只砍 yt-dlp 參照 | | `vendor/` | 現有依賴 | 砍 `yt-dlp/`;改 `ffmpeg/`;新增 `ffmpeg/macos/` 進 git | N/A | 內容差異顯著 | **總計**:整個 repo 程式碼層 ~85-95% 沿用(依粗估方式不同,但結論是「絕大多數 M1-M7 投入仍有效」)。 --- ## 2. 逐目錄細節 ### 2.1 `server/` — 96% 沿用 **完全不動**(> 99%): - `server/internal/api/ws/` — WebSocket hub / handlers(除 CheckOrigin 那個 helper 是 M8-8 新增) - `server/internal/camera/` — 除 `video_source.go` 的 yt-dlp 函式(§2.2)+ `mock_camera.go`(整檔刪) - `server/internal/device/` — 除 `manager.go` 的 mockMode 條件 + `driver/mock/` 目錄 - `server/internal/inference/` — 推論 service 邏輯 - `server/internal/model/` — 模型 repository / store / upload - `server/internal/flash/` — flash service(Kneron 韌體燒錄,v1 已保留) - `server/pkg/logger/` — logger + broadcaster - `server/scripts/kneron_bridge.py` — Python KneronPLUS bridge - `server/data/` — 預置 .nef 模型 **修改**(< 5% 程式碼): - `server/main.go` 第 86-87 行(log message)、第 89-95 行(註解)、第 142 行(NewManager 簽名)、第 147 行(NewManager 簽名) - `server/internal/api/router.go` 第 83 行(刪 `/media/url`)+ 新增 `/api/system/boot-id` - `server/internal/api/handlers/camera_handler.go` 第 251 行(擴充副檔名)+ 第 341-497 行(刪 yt-dlp 相關) - `server/internal/api/handlers/system_handler.go` 新增 `BootID` method + constructor 新增 bootID 參數 - `server/internal/api/middleware.go` 整檔覆寫(CORS 白名單) - `server/internal/config/config.go` 刪 MockMode / MockCamera / MockDeviceCount - `server/internal/deps/checker.go` 刪 yt-dlp entry + 註解更新 - `server/internal/camera/video_source.go` 砍 `ResolveWithYTDLP` / `friendlyYTDLPError`;可能砍 `NewVideoSourceFromURL`(視 grep 結果) - `server/internal/camera/manager.go` 砍 mockMode / mockCamera - `server/internal/device/manager.go` 砍 mockMode / mockCount **新增**: - `server/internal/api/ws/origin.go` — CheckOrigin helper(~30 行) - `server/internal/api/middleware_test.go` — TestIsAllowedOrigin(~50 行) ### 2.2 `server/web/out/`(go:embed 的 Next.js SPA,實際源碼在 `frontend/`)— 88% 沿用 **完全不動**: - `frontend/src/app/dashboard/` / `devices/` / `models/` / `workspace/` — 業務頁面 - `frontend/src/components/device/` / `model/` / `inference/` — 業務元件 - `frontend/src/lib/api/` — API client - `frontend/src/stores/` 除 `camera-store.ts` 的 `startFromUrl` **修改**: - `frontend/src/components/camera/source-selector.tsx` — 砍 URL tab,擴充副檔名 - `frontend/src/stores/camera-store.ts` — 砍 `startFromUrl` - `frontend/src/app/settings/page.tsx` — 砍 Mock 模式切換 - `frontend/src/app/layout.tsx` — 掛 `` + `` - `frontend/src/lib/i18n/{types,zh-TW,en}.ts` — 砍 4 個 yt-dlp keys + 4 個 Mock keys + `noDevices` 文案 + 新增 `serverOffline` 區塊 **新增**: - `frontend/src/stores/system-store.ts`(~40 行) - `frontend/src/hooks/use-boot-id-watcher.ts`(~70 行) - `frontend/src/components/server-offline-overlay.tsx`(~90 行) - `frontend/src/components/boot-id-watcher-mount.tsx`(~10 行) ### 2.3 `visiona-local/app.go` — 85% 沿用 **完全不動**的 v1 邏輯: - `startup()` 的 data dir migration / single-instance lock / IPC server / seed user data dir - `shutdown()` 的 watchCancel / ipcListener.Close / releaseLock - `reportFatal()` + `showNativeError()` 三平台原生對話框(保留給 startup 致命錯誤) - `ensurePythonRuntime()` / `findSystemPython()` / `ensureBundledPython()` 完整 Python 雙策略 - `ensureDriverInstalled()` / `markDriverInstalled()` / `InstallKneronDriver()` — Kneron WinUSB driver 安裝 - `locateServerBinary()` / `pickPort()` / `waitHealthy()` / `writeIPCPort()` — 這些 helper 不動 - `seedUserDataDir()` — 首次啟動的資料 seed - `configureSysProcAttr()` / `openBrowser()` 跨平台封裝 **修改**: - 第 83 行:砍 `mockMode` 欄位;新增 `ctrl` / `logBuf` / `prefs` / `startupPipeline` / `pipelineCancelFn` 欄位(R5-D3 規則:每次 Start 都開瀏覽器,不需 per-session 的 flag;詳見 `server-lifecycle.md` §11.5) - 第 119-120 行:砍 VISIONA_MOCK env 讀取 - 第 313-326 行:砍 `GetBootstrapStatus` / `setBootstrapStatus` / `bootstrapStatus` - 第 425-564 行:`startServer()` 改名為 `startServerV2()`,內容改為 - 用 `StdoutPipe / StderrPipe` 而非 file - 啟動兩個 `logPump` goroutine - mockMode 相關條件全砍 - `--mock` arg 拿掉 - 第 571-610 行:`watchServer()` 的 `reportFatal + os.Exit` 改為 `ctrl.setState(Error)` + event emit **新增 bindings**(13 個 method,見 `v2/control-panel.md` §4.2): - `StartServer` / `StopServer` / `RestartServer` - `GetServerStatus`(改版)/ `GetRecentLogs` / `ClearLogs` - `GetSystemInfo` - `OpenInBrowser` / `RevealLogsFolder` / `ExportLog` - `GetPreferences` / `SetPreferences` - `RestartStartupSequence`(v2.1:Startup Error state 的 Retry 按鈕;見 `startup-pipeline.md` §9) ### 2.4 `visiona-local/` 新增檔案 - `visiona-local/server_control.go`(~250 行)— ServerController + startServerV2 + logPump - `visiona-local/log_buffer.go`(~120 行)— LogBuffer ring buffer - `visiona-local/preferences.go`(~60 行)— JSON load/save ### 2.5 `visiona-local/main.go` — 修改 < 5 行 新增 `OnBeforeClose` handler;其餘不動。 ### 2.6 `visiona-local/frontend/` — 改寫 原本(M7-B 後):`index.html` 25 行 + `app.js` 74 行 + `style.css` 112 行 = 211 行 splash。 v2: - `index.html` ~80 行(控制台 layout) - `app.js` ~130 行(init + binding 呼叫 + event 訂閱) - `style.css` ~300 行(控制台樣式 + dark/light tokens) - 新增 components/×4 + i18n/×3 + icons/×6 + wailsjs/(自動生成) **概念沿用**:ES module + `import` wailsjs + `EventsOn` 訂閱模式——v1 M7-B splash 就是這樣寫的,v2 延續這個模式,只是元件更多。 **程式碼沿用**:0。三個檔案的內容全部重寫。但這三個檔案加起來才 211 行,重寫 2 天是合理的。 ### 2.7 `installer/` — 98% 沿用 - `installer/windows/visiona-local.iss` — 改 1 行(砍 yt-dlp)+ 加 2 行(ffprobe + license) - `installer/linux/build-appimage.sh` — 改 1 行(砍 yt-dlp 從 for loop) - macOS `dmgbuild` 路徑完全不動(dmg 是從 `visiona-local/build/bin/visiona-local.app` 直接建,ffmpeg/ffprobe 已透過 payload-macos 置入) ### 2.8 `Makefile` — 85% 沿用 **新增 target**:`vendor-ffmpeg-macos-build`(~50 行) **修改**: - `vendor-ffmpeg` 改為驗證(~10 行) - `vendor-ffmpeg-windows` URL + Python zipfile 提取清單擴充為 ffmpeg + ffprobe + LICENSE - `vendor-ffmpeg-linux` URL + 解壓邏輯 + 加抓 ffprobe - `payload-macos / payload-windows / payload-linux` 全部改為 `cp ffmpeg + ffprobe`,砍 `cp yt-dlp` **刪除**: - `vendor-ytdlp` / `vendor-ytdlp-windows` / `vendor-ytdlp-linux` 三個 target - 三個 `YTDLP_URL_*` 變數 - `vendor-sync` 中的 `vendor-ytdlp` 依賴 ### 2.9 `scripts/bootstrap-*.sh/.ps1` — 99% 沿用 只改刪 yt-dlp 參照,其他完全不動。 ### 2.10 `vendor/` 目錄 | 子目錄 | v1 | v2 | 備註 | |-------|----|----|------| | `vendor/python/` | 進 vendor(不進 git)| 不變 | - | | `vendor/wheels/` | 進 vendor | 不變 | - | | `vendor/ffmpeg/darwin/` | 進 vendor(不進 git,GPL build)| **目錄改名** `vendor/ffmpeg/macos/` | 進 git,LGPL build | | `vendor/ffmpeg/windows/` | 進 vendor | 同路徑,LGPL build | - | | `vendor/ffmpeg/linux/` | 進 vendor | 同路徑,LGPL build | - | | `vendor/yt-dlp/` | 進 vendor | **整個刪除** | R5-7 | --- ## 3. 文字統計估算 | 類別 | 行數 | |------|------| | v2 新增 Go 程式碼 | ~600 行(server_control.go + log_buffer.go + preferences.go + boot-id handler + CORS middleware + origin.go + middleware_test.go) | | v2 新增 JS/TS 程式碼 | ~700 行(Wails 控制台 5 JS 檔 + 6 SVG + 2 JSON + frontend Offline Overlay 4 檔) | | v2 修改 Go 程式碼 | ~400 行 diff | | v2 修改 TS 程式碼 | ~200 行 diff | | v2 修改 Makefile / scripts / installer | ~100 行 diff | | v2 刪除 Go 程式碼 | ~500 行(mock_driver.go 183 + mock_camera.go 95 + camera_handler.go yt-dlp 段 160 + video_source.go yt-dlp 段 50) | | v2 刪除 TS 程式碼 | ~80 行(source-selector URL tab 70 + camera-store startFromUrl 30 - 扣除同 section 重複計算) | | v2 刪除 Makefile / scripts | ~60 行 | **總 diff 量**:~2600 行(新增 + 修改 + 刪除) **總 repo 規模**:~40k LoC **diff 比例**:~6.5% → 換算成**淨沿用率**:~93%,落在三方共識的「85-95%」區間上半部。 --- ## 4. 為什麼沿用率這麼高 M1-M7 的投資集中在「打包」這件事上(vendor 機制、dmg/iss/AppImage、Python runtime 雙策略、single-instance、data dir migration、Kneron driver installer),這些**完全不動**。 v2 的 refactor 集中在: 1. 把業務 UI 從 Wails WebView 搬到瀏覽器(純移動,Next.js 源碼不動) 2. Wails 視窗內容從 splash 換成控制台(全新 UI,但只有 ~700 行程式碼) 3. 砍兩個功能(yt-dlp, Mock)— 都是局部刪除,不觸及核心架構 4. 切 ffmpeg 授權(純 vendor 層,不動執行邏輯) **等於說 v2 是在 v1 基礎上做「加工」,不是推倒重來**。原本這個 refactor 有可能變成「丟掉重做」的惡夢,但 M7-B 已經把 Next.js UI 從 Wails binding 強耦合解開變成純 HTTP,少了這個前置條件 v2 真的會很痛。 --- ## 5. 被保留但可能失去商業意義的部分 這些 v2 不砍,但隨 R5 決策的不同方向它們的重要性變低了: - **Python runtime 雙策略 + PBS 內嵌(M3 work)** — 仍然需要,Kneron inference 必須有 Python。保留。 - **驗證 driver installer(M4 Windows WinUSB)** — 仍然需要。保留。 - **預置 .nef 模型**(8 個,73 MB) — 仍然需要。保留。 - **Kneron KL520/KL720 韌體檔案** — 仍然需要(最近才補上)。保留。 - **splash 進度訊息 `setBootstrapStatus`** — 砍掉(R5 下 Wails 視窗是控制台,沒有「loading」階段,Start 是 async 不阻塞 UI)。 --- **結論**:v2 refactor 的沿用率接近 93%,剩下的 7% diff 主要是新增控制台 UI 與整合 boot-id 機制,總工時 ~10 人天是合理估算。