Resume cached timelines without replaying their tail (#3329)

* perf(timeline): resume cached history from its exact range

Warm launches were repainting cached history and then downloading the same bounded tail to rediscover that nothing changed. Persist synchronization authority only when the complete current canonical window can be encoded losslessly; otherwise retain the existing display-only fallback.

* test(timeline): hide replica setup behind E2E helper

* fix(timeline): bound persisted resume overflow

* test(timeline): cover workspace eviction catch-up order

* test(timeline): read persisted range from IndexedDB
This commit is contained in:
Mohamed Boudra
2026-08-14 12:57:44 +02:00
committed by GitHub
parent 30195d3bd1
commit f0bd2c8483
18 changed files with 707 additions and 75 deletions
@@ -5,6 +5,7 @@ import {
isTimelineResumeSnapshotAuthoritative,
planTimelineOlderFetch,
planTimelinePromptJump,
planTimelineResumeFetch,
planTimelineTailFetch,
} from "./timeline-sync-plan";
@@ -19,6 +20,15 @@ describe("timeline sync planning", () => {
});
});
test("a restored canonical range catches up after its exact end", () => {
expect(planTimelineResumeFetch({ epoch: "epoch-1", endSeq: 49 })).toEqual({
direction: "after",
cursor: { epoch: "epoch-1", seq: 49 },
limit: TIMELINE_FETCH_PAGE_SIZE,
projection: "projected",
});
});
test("older history loads one bounded page before the start cursor", () => {
const plan = planTimelineOlderFetch({ epoch: "epoch-1", seq: 25 });
@@ -51,6 +51,14 @@ export function planTimelineTailFetch() {
} as const;
}
export function planTimelineResumeFetch(
range: { epoch: string; endSeq: number } | null | undefined,
): ProjectedTimelineForwardFetchPlan {
return range
? planTimelineCatchUpAfter({ epoch: range.epoch, seq: range.endSeq })
: planTimelineTailFetch();
}
export function planTimelineOlderFetch(cursor: TimelineSyncCursor) {
return {
direction: "before",
@@ -33,6 +33,7 @@ interface TimelineFetch {
class TimelineWorld {
readonly errors: string[] = [];
readonly cursors = new Map<string, { epoch: string; endSeq: number }>();
readonly sync = createViewedTimelineSync({
initialDeliveryMode: "selective",
setSubscription: async (agentIds) => {
@@ -45,6 +46,7 @@ class TimelineWorld {
this.releaseMembershipWaiter();
return result.promise;
},
readCursor: (agentId) => this.cursors.get(agentId),
fetchPage: async (agentId, request) => {
const result = deferred<{
hasNewer: boolean;
@@ -63,6 +65,12 @@ class TimelineWorld {
this.releaseFetchWaiters();
return result.promise;
},
fetchLatestTail: (agentId) =>
this.fetchTimeline(agentId, {
direction: "tail",
limit: 40,
projection: "projected",
}),
reportError: (error) => {
this.errors.push(error instanceof Error ? error.message : String(error));
const waiter = this.errorWaiters.shift();
@@ -94,6 +102,25 @@ class TimelineWorld {
private readonly scheduled: Array<{ task: () => void; delayMs: number }> = [];
private readonly retryWaiters: Array<(retry: () => void) => void> = [];
private fetchTimeline(
agentId: string,
request: ProjectedTimelineForwardFetchPlan,
): Promise<{ hasNewer: boolean; endCursor: { epoch: string; seq: number } | null }> {
const result = deferred<{
hasNewer: boolean;
endCursor: { epoch: string; seq: number } | null;
}>();
this.fetches.push({
agentId,
request,
respond: ({ hasNewer, seq = 1 }) =>
result.resolve({ hasNewer, endCursor: { epoch: `epoch-${agentId}`, seq } }),
fail: (message) => result.reject(new Error(message)),
});
this.releaseFetchWaiters();
return result.promise;
}
nextMembership(): Promise<MembershipRequest> {
const request = this.memberships.shift();
if (request) return Promise.resolve(request);
@@ -166,6 +193,41 @@ test("uses a tail fetch when an agent becomes visible", async () => {
fetch.respond({ hasNewer: false });
});
test("catches up after the restored cursor when an agent becomes visible", async () => {
const world = new TimelineWorld();
world.cursors.set("agent-a", { epoch: "epoch-agent-a", endSeq: 42 });
world.sync.setConnected(true);
world.sync.replaceVisibleAgentIds("workspace", ["agent-a"]);
const membership = await world.nextMembership();
membership.succeed();
const fetch = await world.nextFetch("agent-a");
expect(fetch.request).toEqual({
direction: "after",
cursor: { epoch: "epoch-agent-a", seq: 42 },
limit: 40,
projection: "projected",
});
fetch.respond({ hasNewer: false });
});
test("falls back to the latest tail when a restored cursor has more than one catch-up page", async () => {
const world = new TimelineWorld();
world.cursors.set("agent-a", { epoch: "epoch-agent-a", endSeq: 42 });
world.sync.setConnected(true);
world.sync.replaceVisibleAgentIds("workspace", ["agent-a"]);
const membership = await world.nextMembership();
membership.succeed();
const probe = await world.nextFetch("agent-a");
expect(probe.request.direction).toBe("after");
probe.respond({ hasNewer: true, seq: 82 });
const fallback = await world.nextFetch("agent-a");
expect(fallback.request).toEqual({ direction: "tail", limit: 40, projection: "projected" });
fallback.respond({ hasNewer: false });
});
test("a gap absorbed by a running tail is recovered after the tail completes", async () => {
const world = new TimelineWorld();
world.sync.setConnected(true);
@@ -1,6 +1,6 @@
import {
planTimelineCatchUpAfter,
planTimelineTailFetch,
planTimelineResumeFetch,
type ProjectedTimelineForwardFetchPlan,
} from "./timeline-sync-plan";
@@ -12,10 +12,12 @@ interface TimelinePageResult {
interface ViewedTimelineSyncPorts {
initialDeliveryMode: TimelineDeliveryMode;
setSubscription(agentIds: string[]): Promise<void>;
readCursor(agentId: string): { epoch: string; endSeq: number } | undefined;
fetchPage(
agentId: string,
request: ProjectedTimelineForwardFetchPlan,
): Promise<TimelinePageResult>;
fetchLatestTail(agentId: string): Promise<TimelinePageResult>;
reportError(error: unknown): void;
schedule(task: () => void, delayMs: number): () => void;
}
@@ -136,6 +138,12 @@ export function createViewedTimelineSync(ports: ViewedTimelineSyncPorts): Viewed
const isAcknowledged = (agentId: string) => acknowledged.includes(agentId);
const isDesired = (agentId: string) => desired.includes(agentId);
const ownsCatchUp = (agentId: string, generation: number) =>
!disposed &&
connected &&
isDesired(agentId) &&
isAcknowledged(agentId) &&
catchUps.get(agentId)?.generation === generation;
const notifyListeners = () => {
for (const listener of listeners) listener();
@@ -168,30 +176,26 @@ export function createViewedTimelineSync(ports: ViewedTimelineSyncPorts): Viewed
agentId: string,
generation: number,
request: ProjectedTimelineForwardFetchPlan,
fallbackToLatestTailOnOverflow: boolean,
): Promise<void> => {
if (
disposed ||
!connected ||
!isDesired(agentId) ||
!isAcknowledged(agentId) ||
catchUps.get(agentId)?.generation !== generation
) {
return;
}
if (!ownsCatchUp(agentId, generation)) return;
try {
const page = await ports.fetchPage(agentId, request);
if (
disposed ||
!connected ||
!isDesired(agentId) ||
!isAcknowledged(agentId) ||
catchUps.get(agentId)?.generation !== generation
) {
return;
}
if (!ownsCatchUp(agentId, generation)) return;
if (page.hasNewer && page.endCursor) {
await fetchUntilCurrent(agentId, generation, planTimelineCatchUpAfter(page.endCursor));
if (fallbackToLatestTailOnOverflow) {
await ports.fetchLatestTail(agentId);
catchUps.set(agentId, { generation, status: "complete" });
setVisibilityCatchUpReady(agentId);
return;
}
await fetchUntilCurrent(
agentId,
generation,
planTimelineCatchUpAfter(page.endCursor),
false,
);
return;
}
if (page.hasNewer) {
@@ -230,7 +234,7 @@ export function createViewedTimelineSync(ports: ViewedTimelineSyncPorts): Viewed
if (request) pendingCatchUps.set(agentId, request);
return;
}
const nextRequest = request ?? planTimelineTailFetch();
const nextRequest = request ?? planTimelineResumeFetch(ports.readCursor(agentId));
const current = catchUps.get(agentId);
const decision = decideCatchUp({ current, request: nextRequest, supersede });
if (decision === "keep-and-park") {
@@ -245,7 +249,12 @@ export function createViewedTimelineSync(ports: ViewedTimelineSyncPorts): Viewed
catchUpGenerations.set(agentId, generation);
catchUps.set(agentId, { generation, status: "running", request: nextRequest });
pendingCatchUps.delete(agentId);
void fetchUntilCurrent(agentId, generation, nextRequest);
void fetchUntilCurrent(
agentId,
generation,
nextRequest,
request === undefined && nextRequest.direction === "after",
);
};
const startAcknowledgedCatchUps = () => {