From ef74c69023af4ca69862b20c186be4a21fa65b98 Mon Sep 17 00:00:00 2001 From: richblack Date: Tue, 28 Jul 2026 16:05:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(t101):=20=E6=89=98=E7=9B=A4=E5=88=AA?= =?UTF-8?q?=E9=99=A4=E8=AA=9E=E6=84=8F=E5=8F=96=E4=BB=A3=E7=9C=8B=E5=AE=88?= =?UTF-8?q?=E8=AA=9E=E6=84=8F=E2=80=94=E2=80=94leo=20=E5=AF=A6=E6=B8=AC?= =?UTF-8?q?=E8=A3=81=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 「沒有看守這件事,只要列入就是看守,不想同步就刪掉」: 子選單「停止看守這個資料夾」→「刪除這個知識庫」(按下從列表剔除、冪等); 「+ 建立新資料夾並看守…」整項刪除。tray go test 綠(總管親跑)。 (實作=子 CC;驗證+commit=總管。此案原被誤擱置害 v0.13 漏裝=mistakes c947e5a) --- cmd/arcrun-tray/main.go | 51 ++---------------------------------- cmd/arcrun-tray/main_test.go | 38 +++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 49 deletions(-) diff --git a/cmd/arcrun-tray/main.go b/cmd/arcrun-tray/main.go index 9a4d5f1..985f787 100644 --- a/cmd/arcrun-tray/main.go +++ b/cmd/arcrun-tray/main.go @@ -494,7 +494,7 @@ func main() { desk, hasTray := a.(desktop.App) // rebuildTray 依現況重建整個 tray 選單(daemon-beta t7 多資料夾): - // 每個看守中的資料夾一列(點=在 Finder 打開)+各自的「停止看守」項; + // 每個列入的資料夾一列(點=在 Finder 打開)+各自的「刪除這個知識庫」項; // 重設選單=刷新(比 (*Menu).Refresh() 跨版本更穩)。 var rebuildTray func() @@ -599,51 +599,6 @@ func main() { }, win) } - // t17:fyne 內建 folder picker 無法輸入名稱建新資料夾(leo 07-24 親測)—— - // 補一條「建立新資料夾並看守…」:文字輸入+確定 → 在 ~/ 底下 mkdir(已存在則沿用)→ 看守。 - newFolderAction := func() { - win.Show() - win.RequestFocus() - entry := widget.NewEntry() - entry.SetPlaceHolder("例如:我的知識庫") - content := container.NewVBox( - widget.NewLabel("輸入新資料夾名稱(會建立在你的家目錄底下):"), - entry, - ) - dialog.ShowCustomConfirm("建立新資料夾並看守", "確定", "取消", content, func(ok bool) { - if !ok { - return - } - name := strings.TrimSpace(entry.Text) - if name == "" { - return - } - // 只收「單層名稱」:擋路徑分隔與 ..(避免建到家目錄之外) - if strings.ContainsAny(name, `/\`) || name == "." || name == ".." { - dialog.ShowError(errors.New("名稱不能包含 / 或 \\,也不能是 . 或 .."), win) - return - } - home, err := os.UserHomeDir() - if err != nil { - dialog.ShowError(err, win) - return - } - p := filepath.Join(home, name) - if err := os.MkdirAll(p, 0o755); err != nil { // 已存在=直接沿用 - dialog.ShowError(err, win) - return - } - addWatchFolder(cfg, p) - if err := saveConfig(cfg); err != nil { - dialog.ShowError(err, win) - return - } - restartWatch() - rebuildTray() - win.Hide() - }, win) - } - // t98:立刻同步——寫訊號檔觸發 collector 立即跑一輪,無需等下一個定時週期。 // syncNowItem 宣告在 rebuildTray 外,確保 click handler 的 goroutine 能還原標籤後重建選單。 var syncNowItem *fyne.MenuItem @@ -719,7 +674,7 @@ func main() { } }) it.ChildMenu = fyne.NewMenu("", - fyne.NewMenuItem("停止看守這個資料夾", func() { + fyne.NewMenuItem("刪除這個知識庫", func() { removeWatchFolder(cfg, folder) if err := saveConfig(cfg); err != nil { dialog.ShowError(err, win) @@ -733,7 +688,6 @@ func main() { } items = append(items, fyne.NewMenuItem("+ 新增知識資料夾…", addAction), - fyne.NewMenuItem("+ 建立新資料夾並看守…", newFolderAction), // t17 fyne.NewMenuItem("🔗 連上知識庫…(換帳號/重新連線)", func() { showConnectWizard() }), // t54 fyne.NewMenuItemSeparator(), syncNowItem, // t98:立刻同步 @@ -774,7 +728,6 @@ func main() { widget.NewLabelWithStyle("Arcrun RAG 知識同步", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), widget.NewLabel("把檔案丟進你選的資料夾,就會自動進你的知識庫。\n這個程式會待在選單列/系統匣,關掉視窗它仍在背景看守。"), widget.NewButton("新增知識資料夾…", func() { addAction() }), - widget.NewButton("建立新資料夾並看守…", func() { newFolderAction() }), // t17 )) // 開機即看守(若已連上知識庫) diff --git a/cmd/arcrun-tray/main_test.go b/cmd/arcrun-tray/main_test.go index a4061cb..9caca7b 100644 --- a/cmd/arcrun-tray/main_test.go +++ b/cmd/arcrun-tray/main_test.go @@ -246,6 +246,44 @@ func TestSyncNowSignalPath(t *testing.T) { } } +// ── t101:removeWatchFolder 刪除邏輯 ───────────────────────────────────────── + +// TestRemoveWatchFolder_removesEntry:移除後 WatchFolders 不含該路徑,WatchFolder 同步更新。 +func TestRemoveWatchFolder_removesEntry(t *testing.T) { + cfg := &directConfig{ + WatchFolder: "/a", + WatchFolders: []string{"/a", "/b", "/c"}, + } + removeWatchFolder(cfg, "/b") + for _, f := range cfg.WatchFolders { + if f == "/b" { + t.Errorf("移除後 WatchFolders 仍含 /b:%v", cfg.WatchFolders) + } + } + if cfg.WatchFolder == "/b" { + t.Errorf("移除後 WatchFolder 不應是 /b") + } + // 移除中間項不影響剩餘項 + if len(cfg.WatchFolders) != 2 { + t.Errorf("移除一項後應剩 2 項,got %d:%v", len(cfg.WatchFolders), cfg.WatchFolders) + } +} + +// TestRemoveWatchFolder_idempotent:對不在清單中的路徑呼叫,清單保持不變(冪等)。 +func TestRemoveWatchFolder_idempotent(t *testing.T) { + cfg := &directConfig{ + WatchFolder: "/a", + WatchFolders: []string{"/a", "/b"}, + } + removeWatchFolder(cfg, "/nonexistent") + if len(cfg.WatchFolders) != 2 { + t.Errorf("重複刪除不存在路徑後清單應不變,got %v", cfg.WatchFolders) + } + if cfg.WatchFolder != "/a" { + t.Errorf("WatchFolder 不應改變,got %q", cfg.WatchFolder) + } +} + // TestDirectConfigPreservesClaudeBin:config JSON round-trip 保留 claude_bin 欄位(t92 回寫驗收)。 func TestDirectConfigPreservesClaudeBin(t *testing.T) { c := &directConfig{