feat(console): 分流台勾掉/還原(雙向銷案:console 勾掉=終局 checked_via:console,萃取端絕不復活)
leo 2026-07-08 拍板,PR#41 分流台的後續:
- lib/console-triage-model.ts:applyTriageCheck 純函式(check→status:done+
checked_via:console+checked_at;restore→status:new+移除 checked_via/checked_at;
content 非 JSON 殘資料包成 {"text":原文} 不丟字)。雙向銷案語意寫死在註解:
console 勾掉=終局,即使 Logseq 原文還是 TODO 萃取端也絕不復活;Logseq 改 DONE
由萃取端 PATCH(checked_via:logseq);console 只忠實顯示非 done 項。
- console-dashboard.ts:POST /console/triage-check(沿 triage-data 同款 session 驗證;
瀏覽器沒 KBDB token 故 server 端做 PATCH——先 GET 原 entry 整串回寫防蓋掉別的欄位;
守 owner_id=CONSOLE_TENANT 與 entry_type∈{todo,inbox} 兩道邊界)。
- console.ts:每筆待辦 44px 圓形勾掉鈕(手機好按)+done 清單「還原」鈕(誤勾救濟);
後端回寫成功才改本地狀態移出三欄(done 預設隱藏既有機制),失敗誠實 toast 可重試。
- 測試:applyTriageCheck 7 例(先紅後綠)+ route 層 8 例(fetchMock 假 host
kbdb.test + disableNetConnect,絕不外連;驗 session 閘/整串回寫/邊界/restore)。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -112,6 +112,40 @@ export function inboxToTriageItem(e: KbdbEntry): TriageItem {
|
||||
};
|
||||
}
|
||||
|
||||
export type TriageCheckAction = 'check' | 'restore';
|
||||
|
||||
/**
|
||||
* console 勾掉/還原 → 新 content JSON 字串(純函式;route 先 GET 原 entry 再整串回寫,
|
||||
* 防 PATCH 蓋掉別的欄位)。
|
||||
*
|
||||
* ── 雙向銷案語意(leo 2026-07-08 拍板,死循環防呆——萃取端會配合這套規約)──
|
||||
* 1. console 勾掉=**終局**:status:done + checked_via:"console" + checked_at。
|
||||
* 即使 Logseq 原文還是 TODO,萃取端(Arcrun#8 ingest 線)看到 checked_via:console
|
||||
* 也**絕不復活它**(不改回 new、不重灌覆蓋)——否則「console 勾掉→下輪萃取又出現」
|
||||
* 的死循環。
|
||||
* 2. Logseq 那邊改 DONE 的,萃取端來 PATCH status:done + checked_via:"logseq"——
|
||||
* 同一個 status slot、兩個來源靠 checked_via 溯源,console 只需忠實顯示非 done 項。
|
||||
* 3. 還原(誤勾救濟)=status 回 new、**移除 checked_via/checked_at**(回到「未銷案」
|
||||
* 的乾淨狀態,讓兩邊規約重新適用;不留殘欄位讓萃取端誤讀成已銷案)。
|
||||
*
|
||||
* 防禦:content 非 JSON(不合契約殘資料,與 todoToTriageItem 同一情境)→ 包成
|
||||
* {"text":原文} 再上狀態欄——原文不丟、從此畢業成契約 JSON(status 才有地方放)。
|
||||
*/
|
||||
export function applyTriageCheck(content: string | null | undefined, action: TriageCheckAction, nowIso: string): string {
|
||||
let obj: Record<string, unknown>;
|
||||
try {
|
||||
const v = JSON.parse(content ?? '');
|
||||
obj = v && typeof v === 'object' && !Array.isArray(v) ? (v as Record<string, unknown>) : { text: content ?? '' };
|
||||
} catch {
|
||||
obj = { text: content ?? '' };
|
||||
}
|
||||
if (action === 'restore') {
|
||||
const { checked_via: _via, checked_at: _at, ...rest } = obj;
|
||||
return JSON.stringify({ ...rest, status: 'new' });
|
||||
}
|
||||
return JSON.stringify({ ...obj, status: 'done', checked_via: 'console', checked_at: nowIso });
|
||||
}
|
||||
|
||||
/** 二源合一 → 分流台模型。排序=created_at 新→舊(時間不明沉底);分欄計數只算未完成。 */
|
||||
export function buildTriageModel(todoEntries: KbdbEntry[], inboxEntries: KbdbEntry[]): TriageModel {
|
||||
const items = [
|
||||
|
||||
Reference in New Issue
Block a user