# 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 人天是合理估算。