feat(t101): 托盤刪除語意取代看守語意——leo 實測裁定

「沒有看守這件事,只要列入就是看守,不想同步就刪掉」:
子選單「停止看守這個資料夾」→「刪除這個知識庫」(按下從列表剔除、冪等);
「+ 建立新資料夾並看守…」整項刪除。tray go test 綠(總管親跑)。
(實作=子 CC;驗證+commit=總管。此案原被誤擱置害 v0.13 漏裝=mistakes c947e5a)
This commit is contained in:
2026-07-28 16:05:23 +08:00
parent 77aa4c5195
commit ef74c69023
2 changed files with 40 additions and 49 deletions
+2 -49
View File
@@ -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)
}
// t17fyne 內建 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
))
// 開機即看守(若已連上知識庫)
+38
View File
@@ -246,6 +246,44 @@ func TestSyncNowSignalPath(t *testing.T) {
}
}
// ── t101removeWatchFolder 刪除邏輯 ─────────────────────────────────────────
// 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)
}
}
// TestDirectConfigPreservesClaudeBinconfig JSON round-trip 保留 claude_bin 欄位(t92 回寫驗收)。
func TestDirectConfigPreservesClaudeBin(t *testing.T) {
c := &directConfig{