fix(daemon): 移除資料夾要真的把雲端資料收回(arcrun-rag#46)
leo 2026-08-16 實撞:掛資料夾、同步完成、從清單按「移除」之後—— 「我去把 Logseq plugin 刪掉以後,**採集的 wiki 沒消失**」。 內容一筆都沒少,照樣搜得到、照樣 is_embedded=1、AI 照樣拿它回答。 真兇(源碼):app.go 的 RemoveFolder 全文只做三件事——從 WatchFolders 拿掉、 saveCfg、restartWatch,**一次都沒碰撤除**。而撤除機制本身是好的(在部署白名單裡、 有測試、direct.go 真的會觸發它),只是那兩個觸發點都在「還在監看的資料夾裡某個檔 被刪掉」的差異偵測迴圈裡。⇒ 刪一個檔會撤除 ✅/移除整個資料夾不會 ❌。 **在使用者眼裡是同一件事,在程式裡是兩條完全不同的路,只有一條接上了撤除。** 這不只是少一個功能:產品說明卡寫著「確保資料所有權完全屬於使用者而非 SaaS 供應商」,而使用者唯一看得到的收回動作不收回任何東西 ⇒ 知情同意的問題。 修法(沿用既有那條撤除路,不另寫一份): - drainPendingTakedowns:把既有的「待下架清單逐筆送出」抽成共用函式 - retireRootOnce:資料夾進 retiring_folders 後,把帳本裡真的上傳過的檔排進 既有的 PendingTakedowns、走同一條撤除路;撤乾淨才刪帳本 - 進度與失敗真因寫進 status.json(level-triggered),App 看到 done 才清設定 - App 是 config.json 的唯一寫入者(兩個行程都寫=互相蓋掉對方的設定) 邊界(本票最危險的地方):path 是相對於被監看資料夾的路徑 ⇒ 兩個資料夾各有 notes.md 時 page_name 與 path 完全相同,撤除一個會連坐另一個。撤除 payload 帶 library(逐根導出,與 ingest 同一個函式算的),workflow 兩個比對節點加「library 相符才殺」。只在兩邊都有 library 時才收緊 ⇒ 舊 daemon 不送/舊卡沒有都退回原行為。 畫面:舊文案「已經上傳的知識卡不會被刪除」技術上是對的,但它替使用者決定了他要的 是「只停止同步」。改成兩個選項各寫一行後果讓他選(預設待 leo 裁)。 測試:go test ./collector/... ./collector/cmd/arcrun-app/... 全綠; 新增 direct_retire_test.go(7)/remove_folder_takedown_test.go(4)/ workflows/tests/takedown-scope.test.mjs(8)。UI 用真 dist + headless Chrome 複驗, check-cis.sh/check-render.sh 全過。 ◐ 未做:真實例端到端(不可逆且 leo 正在該機器上工作,步驟已寫成清單等總管確認)/ workflow 要重新部署才生效/未重打 bundle、未出貨。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -268,7 +268,14 @@ function pageLib(s, idx) {
|
||||
<button class="primary" data-addto="${idx}">加入資料夾</button>
|
||||
</div>
|
||||
<div class="kbver">${kbVersionLine(a)}</div>
|
||||
${(a.folders || []).map((f) => `
|
||||
${(a.folders || []).map((f) => f.retiring ? `
|
||||
<div class="folder">
|
||||
<span class="path" title="${esc(f.path)}">${esc(f.path)}</span>
|
||||
<span class="tag retiring">${f.retireError
|
||||
? '收回時出錯,會自動再試'
|
||||
: `正在從雲端收回…${f.retireRemaining ? `還有 ${f.retireRemaining} 份` : ''}`}</span>
|
||||
</div>
|
||||
${f.retireError ? `<div class="d folder-note">${esc(f.retireError)}</div>` : ''}` : `
|
||||
<div class="folder">
|
||||
<span class="path" title="${esc(f.path)}">${esc(f.path)}</span>
|
||||
<span class="tag">自動同步中</span>
|
||||
@@ -467,15 +474,30 @@ async function addFolder(accIdx) {
|
||||
state = await go.GetState(); renderNav(); renderPage();
|
||||
}
|
||||
|
||||
// 移除資料夾=兩個後果完全不同的動作,所以給兩顆按鈕,不給一顆猜。
|
||||
//
|
||||
// 🔴 arcrun-rag#46(leo 2026-08-16 實撞):「我去把 Logseq plugin 刪掉以後,
|
||||
// **採集的 wiki 沒消失**。」舊文案寫的是「已經上傳的知識卡不會被刪除」——
|
||||
// 那句話**在技術上是對的**,但它預設使用者要的是「只停止同步」,
|
||||
// 而他要的是「我不要這份資料了」。⇒ 病不在少一句說明,在**替他決定了**。
|
||||
// 現在兩個選擇都擺出來、後果各寫一行,由他挑。
|
||||
function confirmRemove(accIdx, path) {
|
||||
openSheet(`
|
||||
<h2>移除這個資料夾?</h2>
|
||||
<p>「${esc(path)}」不再自動同步。<br/>已經上傳的知識卡不會被刪除。</p>
|
||||
<div class="acts"><button id="c1">取消</button><button class="primary" id="c2">移除</button></div>`,
|
||||
<p>「${esc(path)}」要怎麼處理?<b>你電腦裡的原始檔案不會被動到</b>,兩個選擇的差別只在雲端。</p>
|
||||
<label class="radio"><input type="radio" name="rmMode" value="takedown" checked/>
|
||||
<span><b>連同雲端的知識一起收回</b><br/>
|
||||
<span class="d">這個資料夾整理出來的知識會從知識庫刪除,之後搜尋找不到、AI 也不會再拿它回答。<b>刪掉就要不回來</b>。</span></span></label>
|
||||
<label class="radio"><input type="radio" name="rmMode" value="unwatch"/>
|
||||
<span><b>只停止同步,雲端的知識保留</b><br/>
|
||||
<span class="d">以後這個資料夾有變動不會再上傳,但之前整理好的知識留在知識庫裡,搜尋和 AI 照樣找得到。</span></span></label>
|
||||
<div class="acts"><button id="c1">取消</button><button class="primary" id="c2">確定</button></div>`,
|
||||
() => {
|
||||
$('c1').onclick = closeSheet;
|
||||
$('c2').onclick = async () => {
|
||||
await go.RemoveFolder(accIdx, path); closeSheet();
|
||||
const mode = document.querySelector('input[name="rmMode"]:checked');
|
||||
const takedown = !mode || mode.value === 'takedown';
|
||||
await go.RemoveFolder(accIdx, path, takedown); closeSheet();
|
||||
state = await go.GetState(); renderNav(); renderPage();
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user