t191/t192:daemon 主視窗(Google Drive 風格)+「同步中…」進行中狀態

解 issue #18+#17(leo 指定併做)。

## #17 進行中狀態——真兇是「工作時間對用戶隱形」
leo:「按『立刻同步』後很快就回到『看守中』,看起來好像就做完了,
      這時使用者一看沒做完啊,就覺得是 bug」。
真兇:collector **只在一輪跑完後**才印 JSON ⇒ 托盤無從得知「正在跑」。
修:開工前先印 phase:"start"、跑完印 phase:"done";
    supervisor 新增 StateSyncing;托盤/主視窗顯示「同步中… 正在讀檔並整理成知識卡」。
形狀相容:兩筆都有 at,舊版托盤只會多算一次 round,不會壞。

實測(真的跑一輪 --once):
  第1筆 phase='start' at=21:31:26
  第2筆 phase='done'  at=21:31:27

## #18 主視窗(新檔 mainwindow.go)
leo:「daemon 設定項越來越多,不可能統統塞在下拉選單」
     「參考 Google Drive 設定畫面:點擊 daemon 就開一個視窗,佔螢幕約 1/2」
     「資料夾清單要可捲動——我每個 gitea 專案都要同步,那就是幾十個,根本塞不下」
- 920x620 視窗,關窗=隱藏(daemon 續跑)
- 上:大字狀態+副標(上次同步/已整理幾份/失敗幾份;有錯誤優先顯示)
- 中:widget.List 虛擬捲動的資料夾清單(帳號標題+其下資料夾攤平成單層)
- 下:白話動作列(加入資料夾/新增知識庫帳號/AI 設定/檢查更新)
- 每秒自動刷新 ⇒ 同步中看得到在動
- 無帳號時引導去連線(onboarding),不是丟錯誤或空面板
- **所有動作複用既有 handler**,不在視窗那邊重寫一份邏輯

托盤選單第一項加「開啟 Arcrun…」——leo 已點破兩次
「能力做出來了,入口沒出現在用戶會看的地方」(docs 沒連結/MCP 零處提及),
這次做完就讓它看得見。

測試:t192_test.go 八則(狀態文案四態/同步中要說明在做什麼/
副標優先顯示錯誤/副標不可空白/清單攤平且 accIdx 正確/60 個資料夾/無帳號空清單)全過。
collector 與 supervisor 全套綠。

⚠️ 未驗:fyne GUI 的實際外觀需真機開窗(無螢幕環境驗不到),
   CIS 視覺套用待 leo 看過畫面再調。
This commit is contained in:
2026-08-04 21:32:31 +08:00
parent 8c7b50d9a9
commit cc6534dd39
5 changed files with 500 additions and 2 deletions
+13 -1
View File
@@ -30,7 +30,12 @@ const (
StateStopped State = "stopped" // 未啟動或已停止
StateStarting State = "starting" // 行程剛拉起、尚無第一輪
StateWatching State = "watching" // 正常看守中(已有掃描輪)
StateError State = "error" // 行程非預期退出、等待重起
// 🔴 t191leo 08-04issue #17):「按『立刻同步』後很快就回到『看守中』,
// 看起來好像就做完了⋯⋯使用者一看沒做完啊,就覺得是 bug」。
// collector 開工前會印 phase:"start"、跑完印 phase:"done"
// ⇒ 這中間就是本狀態,讓托盤顯示「同步中…」,工作時間不再對用戶隱形。
StateSyncing State = "syncing" // 正在掃描/萃取(收到 start、還沒收到 done)
StateError State = "error" // 行程非預期退出、等待重起
)
// Status 是托盤要顯示的即時狀態(值型別、複製安全)。
@@ -46,6 +51,8 @@ type Status struct {
// round 對應 collector direct 每輪印到 stdout 的 JSON(見 direct.go runOne)。
type round struct {
At string `json:"at"`
Phase string `json:"phase"` // t191"start"(開工)/"done"(跑完);舊版沒有此欄=空
Folder string `json:"folder"`
Results []json.RawMessage `json:"results"`
}
@@ -298,6 +305,11 @@ func (s *Supervisor) runOnce(ctx context.Context) error {
break
}
at := parseAt(r.At)
if r.Phase == "start" {
// t191:開工 → 顯示「同步中…」。不累加 Rounds(那是「完成幾輪」)。
s.setState(func(st *Status) { st.State = StateSyncing })
continue
}
s.setState(func(st *Status) {
st.State = StateWatching
st.Rounds++