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:
@@ -11,7 +11,7 @@
|
||||
* Logseq marker 詞彙(TODO/DOING/LATER/NOW),非憑空編造。
|
||||
*/
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { todoToTriageItem, inboxToTriageItem, buildTriageModel } from '../src/lib/console-triage-model';
|
||||
import { todoToTriageItem, inboxToTriageItem, buildTriageModel, applyTriageCheck } from '../src/lib/console-triage-model';
|
||||
import type { KbdbEntry } from '../src/lib/console-dashboard-model';
|
||||
|
||||
const SEC = (iso: string) => Math.floor(Date.parse(iso) / 1000);
|
||||
@@ -132,3 +132,81 @@ describe('buildTriageModel — 二源合一 / 三欄計數 / per-project', () =>
|
||||
expect(m.counts).toEqual({ ai: 0, collab: 0, leo: 0, unclassified: 0, done: 0, total: 0 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyTriageCheck — console 勾掉/還原(雙向銷案語意,leo 2026-07-08 拍板)', () => {
|
||||
const NOW = '2026-07-08T12:00:00.000Z';
|
||||
|
||||
it('check:status→done + checked_via:console + checked_at,其餘欄位原樣保留', () => {
|
||||
const before = JSON.stringify({
|
||||
text: '整理 sprint 板',
|
||||
marker: 'TODO',
|
||||
owner_tier: 'leo',
|
||||
project: 'Arcrun',
|
||||
source: 'notes',
|
||||
status: 'new',
|
||||
});
|
||||
const after = JSON.parse(applyTriageCheck(before, 'check', NOW));
|
||||
expect(after).toEqual({
|
||||
text: '整理 sprint 板',
|
||||
marker: 'TODO',
|
||||
owner_tier: 'leo',
|
||||
project: 'Arcrun',
|
||||
source: 'notes',
|
||||
status: 'done',
|
||||
checked_via: 'console',
|
||||
checked_at: NOW,
|
||||
});
|
||||
});
|
||||
|
||||
it('check:inbox 契約 {"text","from","status"} 同樣適用,不掉 from', () => {
|
||||
const after = JSON.parse(applyTriageCheck(JSON.stringify({ text: '買咖啡豆', from: 'leo', status: 'new' }), 'check', NOW));
|
||||
expect(after.from).toBe('leo');
|
||||
expect(after.status).toBe('done');
|
||||
expect(after.checked_via).toBe('console');
|
||||
});
|
||||
|
||||
it('check:content 非 JSON(不合契約殘資料)→ 包成 {"text":原文},原文不丟', () => {
|
||||
const after = JSON.parse(applyTriageCheck('裸字串待辦', 'check', NOW));
|
||||
expect(after).toEqual({ text: '裸字串待辦', status: 'done', checked_via: 'console', checked_at: NOW });
|
||||
});
|
||||
|
||||
it('check:已 done 的再勾=冪等(仍 done,checked_via 蓋成 console)', () => {
|
||||
const before = JSON.stringify({ text: 'x', status: 'done', checked_via: 'logseq' });
|
||||
const after = JSON.parse(applyTriageCheck(before, 'check', NOW));
|
||||
expect(after.status).toBe('done');
|
||||
expect(after.checked_via).toBe('console');
|
||||
});
|
||||
|
||||
it('restore:status→new、移除 checked_via/checked_at,其餘欄位原樣保留', () => {
|
||||
const before = JSON.stringify({
|
||||
text: '誤勾的事',
|
||||
marker: 'TODO',
|
||||
owner_tier: 'ai',
|
||||
project: 'mira',
|
||||
source: 'notes',
|
||||
status: 'done',
|
||||
checked_via: 'console',
|
||||
checked_at: '2026-07-08T11:00:00.000Z',
|
||||
});
|
||||
const after = JSON.parse(applyTriageCheck(before, 'restore', NOW));
|
||||
expect(after).toEqual({
|
||||
text: '誤勾的事',
|
||||
marker: 'TODO',
|
||||
owner_tier: 'ai',
|
||||
project: 'mira',
|
||||
source: 'notes',
|
||||
status: 'new',
|
||||
});
|
||||
});
|
||||
|
||||
it('restore:content 非 JSON → 包成 {"text":原文,"status":"new"}', () => {
|
||||
const after = JSON.parse(applyTriageCheck('裸字串', 'restore', NOW));
|
||||
expect(after).toEqual({ text: '裸字串', status: 'new' });
|
||||
});
|
||||
|
||||
it('null/空 content 防禦:不炸,text 誠實為空字串', () => {
|
||||
const after = JSON.parse(applyTriageCheck(null, 'check', NOW));
|
||||
expect(after.text).toBe('');
|
||||
expect(after.status).toBe('done');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user