修首頁狀態說謊+DMG 補上拖曳版面(leo 08-05 封測回報 ①②)

## ② 首頁狀態是壞的
leo:「拖新的檔案進資料夾,完成本地萃取、上傳,但自始至終 daemon 的首頁都顯示
『有變動就自動開始』『等待中』,都顯示綠燈沒動,實際上已經做完了。」

兩個獨立的真兇:

**A. 「同步中」判斷用錯依據(回歸)**
t191 早就做好機制:collector 開工印 phase:"start"、跑完印 "done",
supervisor 據此維護 StateSyncing。**換 Wails 時 App 沒接這條線**,改看 sync-now 訊號檔:
  ① 訊號檔只有手動按「立刻同步」才產生 ⇒ 拖檔進資料夾(自動觸發)整輪不亮燈
  ② 就算手動按,collector 是**先刪檔再跑**(consumeSyncNowSignal)⇒ 真正在跑時檔早沒了
⇒ 改讀 sup.Status().State == StateSyncing。**接回既有機制,不發明第三種判斷法。**

**B. 做完的證據被下一輪抹掉**
ExtractedOK/ExtractFailed 是**本輪**計數、每輪覆寫整份 status.json
⇒ 有產出那輪寫下 N,十幾秒後空轉的一輪把它蓋成 0
⇒ 「上一輪 N 份」永遠空白、四步時間軸的綠燈只靠「跑過任何一輪」判斷=與實際進度脫鉤。
⇒ 新增 last_activity_{at,ok,failed}:有產出記本輪、空轉沿用上一輪(CarryForwardActivity)。
   首頁改顯示「幾點整理了幾份」,綠燈只在真的整理過東西時才亮。

## ① Mac DMG 少了「看得出要拖」的版面
leo:「要跳出虛擬隨身碟,打開一個 finder 的視窗,**顯示 Arcrun 和 Application 的捷徑**,
用戶把 App 拖進 Application」。
原本 DMG 內容是對的(Arcrun.app+Applications 捷徑),但**沒設版面**
⇒ 預設清單視圖、位置隨機,看不出「要往右拖」。
⇒ build-dmg.sh 改走 UDRW → Finder AppleScript 設大圖示/視窗大小/左右並排 → 轉 UDZO。
   best-effort:無桌面工作階段時只警告不中止(版面是加分,不該讓打包掛掉)。

⚠️ 真正讓 leo 拿到 zip 的原因不在這裡——是**線上 portal 還沒出貨**(見下)。

## 驗(實測輸出)
· collector 全測綠;新增 TestCarryForwardActivity 四子測(含「空轉不該抹掉上次成果」)
· 掛載 dist/Arcrun-v0.18.5.dmg 實查:
    內容剛好兩項(Arcrun.app + Applications→/Applications 捷徑)
    .DS_Store 6148 bytes(版面已存進去)
    Contents/MacOS/ 有 arcrun-app **和 arcrun-collector**
    CFBundleShortVersionString = 0.18.5(不是 1.0.0)/LSUIElement = true/codesign -v 通過
· 三支機械閘 check-cis/check-render/check-tray 全 PASS

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 18:01:17 +08:00
parent 1a1478260b
commit 6bf0daa786
6 changed files with 185 additions and 21 deletions
+43
View File
@@ -225,3 +225,46 @@ func TestRunDirectOnceWritesStatusExtractorFail(t *testing.T) {
t.Error("ExtractorError 應有白話說明")
}
}
// TestCarryForwardActivity 鎖住「做完的證據不會被下一輪抹掉」(2026-08-05 leo 實撞的 status 假象)。
//
// 症狀:拖檔進資料夾 → 萃取上傳都跑完了,但首頁自始至終顯示「等待中」、綠燈沒動。
// 真兇:ExtractedOK 是本輪計數,每輪覆寫整份 status.json ⇒ 有產出那輪寫下 N,
// 十幾秒後沒事做的那輪把它蓋成 0,使用者永遠看不到自己剛做完的事。
func TestCarryForwardActivity(t *testing.T) {
t.Run("本輪有產出→記本輪", func(t *testing.T) {
st := SyncStatus{LastSync: "2026-08-05T17:18:34+08:00", ExtractedOK: 3, ExtractFailed: 1}
CarryForwardActivity(SyncStatus{}, &st)
if st.LastActivityAt != st.LastSync || st.LastActivityOK != 3 || st.LastActivityFailed != 1 {
t.Fatalf("有產出的一輪應記成 last_activity,得到 %+v", st)
}
})
t.Run("本輪沒事做→沿用上一輪,不歸零", func(t *testing.T) {
prev := SyncStatus{LastActivityAt: "2026-08-05T17:18:34+08:00", LastActivityOK: 3, LastActivityFailed: 1}
st := SyncStatus{LastSync: "2026-08-05T17:36:38+08:00"} // 空轉的一輪
CarryForwardActivity(prev, &st)
if st.LastActivityOK != 3 || st.LastActivityFailed != 1 {
t.Fatalf("空轉的一輪不該抹掉上次成果,得到 OK=%d Failed=%d", st.LastActivityOK, st.LastActivityFailed)
}
if st.LastActivityAt != prev.LastActivityAt {
t.Fatalf("時間也要沿用上一輪:want %q got %q", prev.LastActivityAt, st.LastActivityAt)
}
})
t.Run("只有失敗也算有做事", func(t *testing.T) {
st := SyncStatus{LastSync: "2026-08-05T18:00:00+08:00", ExtractFailed: 2}
CarryForwardActivity(SyncStatus{}, &st)
if st.LastActivityFailed != 2 || st.LastActivityAt == "" {
t.Fatalf("全失敗的一輪也要留痕(否則使用者不知道剛剛出過事),得到 %+v", st)
}
})
t.Run("從沒跑過→全空,不編造", func(t *testing.T) {
st := SyncStatus{LastSync: "2026-08-05T18:00:00+08:00"}
CarryForwardActivity(SyncStatus{}, &st)
if st.LastActivityAt != "" || st.LastActivityOK != 0 || st.LastActivityFailed != 0 {
t.Fatalf("沒有任何歷史時應保持空值,得到 %+v", st)
}
})
}