fix: preserve history coverage during tail refresh

Keeps an existing complete cursor/coverage after refreshing the latest messages
Prevents tail refreshes from reintroducing an incorrect "load older" state
Adds a regression test for complete history coverage across refreshes
This commit is contained in:
Bohdan Triapitsyn
2026-07-29 17:53:55 +03:00
parent 84f651d9c8
commit 931e20d814
2 changed files with 31 additions and 2 deletions
@@ -89,6 +89,28 @@ describe("SessionMessageLoader", () => {
childStores.disposeAll()
})
test("preserves complete history coverage across a tail refresh", async () => {
let calls = 0
const { childStores, loader } = createLoader(async ({ sessionID }) => {
calls += 1
return calls === 1
? response([createRecord(sessionID, "msg_1")])
: response([createRecord(sessionID, "msg_2")], "stale-tail-cursor")
})
const target = { directory: "/repo", sessionID: "session-a" }
await loader.ensure(target)
expect(loader.getSnapshot(target).complete).toBe(true)
expect(loader.getSnapshot(target).cursor).toBe(undefined)
await loader.refreshTail(target, 2)
expect(loader.getSnapshot(target).complete).toBe(true)
expect(loader.getSnapshot(target).cursor).toBe(undefined)
loader.dispose()
childStores.disposeAll()
})
test("does not deduplicate identical session IDs across directories", async () => {
const calls: string[] = []
const { childStores, loader } = createLoader(async ({ directory, sessionID }) => {
@@ -265,18 +265,25 @@ export class SessionMessageLoader {
const store = this.childStores.ensureChild(normalized.directory, { bootstrap: false })
this.bumpGeneration(entry)
return this.startLoad(normalized, entry, store, "refresh", async (isCurrent) => {
const previousCoverage = entry.snapshot.resolved
? { cursor: entry.snapshot.cursor, complete: entry.snapshot.complete }
: null
const page = await this.fetchPage(normalized, Math.max(1, limit))
if (!isCurrent()) return
const committed = this.commitPage(normalized, entry, store, page, "merge", isCurrent)
if (!committed || !isCurrent()) return
const coverage = previousCoverage ?? page
this.patchEntry(entry, {
status: "ready",
loadingKind: null,
error: null,
resolved: true,
limit: Math.max(entry.snapshot.limit, committed.messages.length),
cursor: page.cursor,
complete: page.complete,
// A tail refresh uses a deliberately small window. Its cursor only
// describes that window, so it must not replace the established
// history coverage and spuriously expose "load older".
cursor: coverage.cursor,
complete: coverage.complete,
updatedAt: Date.now(),
})
this.persistCoverage(normalized, entry.snapshot)