P0:路徑約定與程式碼不一致

routes/data_routes.pyagent_tools/workspace.py 兩處都把 交接文件路徑組成 <project>/project_context.md(根目錄版本), 但 HANDOFF.md 第 36 行和 UI 說明文字都寫 <project>/memory/project_context.md。 掃過 程式倉庫/ 所有 repo,沒有一個根目錄版本,只有 nomad-dashboard 自己有 memory/ 版本。

結果是:選任何專案,GET 回傳 0/0 字——文件不存在、內容全空。這還不是最糟糕的。 POST 用同一組路徑,所以使用者按儲存,會在 repo 根目錄新建孤兒檔。Agent 的 read_handoff_context 工具同樣中招,永遠讀不到使用者儲存的內容, 而 UI 會跳「已成功儲存並同步」。

P1 與 P2:其餘兩層缺陷

P2(沒有更新機制): fetchHandoff() 只在切分頁、按重新載入、換專案時觸發。對照其他分頁: ports 有每 5 秒 setInterval、agent 有 5 秒輪詢、healing logs 甚至有 SSE。 handoff 是唯一的例外——使用者看到的「不即時」問題就來自這裡, 但這其實是三個問題裡最輕的一個。

加輪詢還有隱藏的資料遺失路徑:textarea 直接綁值,「使用者正在打字 → Agent 背景更新 → 使用者按儲存」會用舊快照覆蓋 Agent 寫入的內容,且無任何提示。 光加輪詢不加鎖反而會製造新的問題。

P1(ZH 欄位指向不存在的檔案): 全域中文欄固定指向一個路徑,但實際上不存在,所以中文欄常態空白,也沒有任何說明。

修法:一個服務層解決路徑散亂

新增 services/handoff.py 作為路徑的單一真相來源。邏輯是: 優先找 memory/ 目錄下的文件;若根目錄有舊檔則相容回退; 兩處皆無時,新建仍落在 memory/。EN/ZH 一律成對取自同一目錄, 不讓它們因為新舊佈局混合而拆散在不同層級。

即時性加 10 秒輪詢,比對 mtime:textarea 未被編輯就自動吸收 Agent 的更新;已有修改則顯示橫幅讓使用者決定。POST 帶 base_mtime 樂觀鎖,磁碟版本較新時回 409 而非靜默覆寫。P1 改為回傳 en_path/zh_path/en_exists/zh_exists, 前端顯示實際解析路徑與「尚未建立,儲存後將自動建檔」,取代無聲空白。

驗證三條路徑

tests/test_handoff.py 13 個 case 涵蓋舊佈局回退與 EN/ZH 同目錄 不拆散,已掛進 make test。live API:GET nomad-dashboard 由 0/0 字 變 22011/12255 字,路徑落在 memory/,根目錄確認無孤兒檔。

瀏覽器實測:改背景檔後 13 秒自動同步顯示橫幅;先弄髒 textarea 再改背景檔, 編輯內容原封不動轉為警告橫幅;此時按儲存被 409 擋下,grep 確認磁碟檔案未被舊版本覆寫。

關鍵教訓

「不即時」的抱怨底下可能藏著「根本讀不到」:先驗資料是否到得了前端,再談刷新頻率。刷新頻率是表象,資料流通才是前提。

說明文字與程式碼路徑衝突時,說明文字通常才是對的:文件寫的是人類約定的意圖,程式碼可能只是沒對齊的實作。HANDOFF.md 與 UI 文案兩處獨立佐證 memory/,程式碼是孤例。

同一份路徑組法散在多處等於遲早分岔:route 與 agent tool 各寫一份、一起錯。抽成 service 層後兩邊共用,下次改路徑只改一處。

可編輯欄位加背景寫入者必須有樂觀鎖:只加輪詢會製造新的資料遺失路徑,髒值偵測與 mtime 基準要一起上,不能只解其一。

孤兒檔比空白更危險:空白讓使用者知道有問題,孤兒檔加上「儲存成功」的 toast 讓問題完全隱形,直到 Agent 無論如何讀不到才被發現。

來源:個人開發日誌 2026-07-25 · Nomad Dashboard · commit db28657 · 13 test cases · GET 0→22011 字