fix: clear stale accepted commit marker after deleting local IndexedDB caches

When users delete local BME IndexedDB via UI actions (delete current/all
IDB), the chat metadata's st_bme_commit_marker was not cleared. This left
an accepted high-revision promise with no local DB backing, causing
persist-mismatch:indexeddb-behind-commit-marker and blocking graph load
indefinitely.

- index.js: add clearCurrentChatCommitMarker() helper and expose via runtime
- ui-actions-controller.js: call clearCurrentChatCommitMarker before
  syncGraphLoadFromLiveContext after IDB deletion
- p0-regressions.mjs: regression test asserting marker is cleared before
  reload after current-IDB deletion
This commit is contained in:
Youzini-afk
2026-04-12 20:06:51 +08:00
parent 4643e0ad75
commit 05083ef5f0
3 changed files with 134 additions and 0 deletions

View File

@@ -67,6 +67,7 @@ import {
shouldRunRecallForTransaction,
} from "../ui/ui-status.js";
import {
onDeleteCurrentIdbController,
onManualCompressController,
onManualEvolveController,
onManualSleepController,
@@ -2036,6 +2037,93 @@ async function testBackendVectorQueryFailureMarksStateDirty() {
}
}
async function testDeleteCurrentIdbClearsCommitMarkerBeforeReload() {
const originalIndexedDb = globalThis.indexedDB;
const callLog = [];
globalThis.indexedDB = {
deleteDatabase(name) {
callLog.push(["delete-db", String(name || "")]);
const request = {
onsuccess: null,
onerror: null,
onblocked: null,
};
queueMicrotask(() => {
request.onsuccess?.();
});
return request;
},
};
try {
const runtime = {
confirm() {
return true;
},
getCurrentChatId() {
return "chat-delete-idb";
},
buildBmeDbName(chatId) {
return `STBME_${chatId}`;
},
buildRestoreSafetyDbName(chatId) {
return `STBME___restore__${chatId}`;
},
async closeBmeDb(chatId) {
callLog.push(["close-db", chatId]);
},
clearCachedIndexedDbSnapshot(chatId) {
callLog.push(["clear-indexeddb-cache", chatId]);
},
clearCurrentChatCommitMarker(options = {}) {
callLog.push([
"clear-commit-marker",
String(options.reason || ""),
options.immediate === true,
]);
},
syncGraphLoadFromLiveContext(options = {}) {
callLog.push([
"sync-graph-load",
String(options.source || ""),
options.force === true,
]);
},
refreshPanelLiveState() {
callLog.push(["refresh-panel"]);
},
toastr: {
success(message) {
callLog.push(["toast-success", String(message || "")]);
},
warning(message) {
callLog.push(["toast-warning", String(message || "")]);
},
error(message) {
callLog.push(["toast-error", String(message || "")]);
},
},
};
const result = await onDeleteCurrentIdbController(runtime);
assert.equal(result?.handledToast, true);
const clearMarkerIndex = callLog.findIndex(
(entry) => entry[0] === "clear-commit-marker",
);
const syncLoadIndex = callLog.findIndex(
(entry) => entry[0] === "sync-graph-load",
);
assert.ok(clearMarkerIndex >= 0, "删除当前 IDB 后应清理 commit marker");
assert.ok(syncLoadIndex >= 0, "删除当前 IDB 后应重新同步图谱加载状态");
assert.ok(
clearMarkerIndex < syncLoadIndex,
"应先清理 commit marker再触发图谱重探测",
);
} finally {
globalThis.indexedDB = originalIndexedDb;
}
}
async function testCompressTypeAcceptsTopLevelFieldsResult() {
const graph = createEmptyGraph();
const typeDef = {
@@ -6778,6 +6866,7 @@ async function testManualSleepExplainsThatItIsLocalOnlyWhenNothingChanges() {
await testCompressorMigratesEdgesToCompressedNode();
await testVectorIndexKeepsDirtyOnDirectPartialEmbeddingFailure();
await testBackendVectorQueryFailureMarksStateDirty();
await testDeleteCurrentIdbClearsCommitMarkerBeforeReload();
await testCompressTypeAcceptsTopLevelFieldsResult();
await testExtractorFailsOnUnknownOperation();
await testExtractorNormalizesFlatCreateOperation();