import { expect, test } from "../support/fixtures"; import { expectSameOlderHistoryLoadingOperation, expectStableHistoryStartGutter, expectTimelineAtHistoryStart, expectTimelinePromptCentered, expectTimelinePromptNotMounted, expectTimelinePromptPositionPreserved, expectTimelinePromptVisible, holdBootstrapTimelinePage, holdDaemonHydration, holdOlderHistoryPages, makeLoadedTimelineFitViewport, openAgentTimeline, rememberOlderHistoryLoadingOperation, rememberTimelineViewport, rememberTimelinePromptPosition, reloadAgentTimelineFromPersistedReplica, scrollTimelineUntilOlderHistoryIsReachable, scrollTimelineToOldestLoadedEdge, scrollTimelineToNewestLoadedEdge, seedLongMockAgentTimeline, scrollThroughOlderHistoryPages, seedRealisticOlderTimeline, sendLiveTurnBeforeHydration, expectTimelineViewportAnchoredAfterPrepend, userNavigatesTimelineToHistoryStartWithKeyboard, userScrollsTimelineToHistoryStart, waitForPersistedCanonicalTimelineRange, } from "../support/helpers/timeline-pagination"; import { trackAgentTimelineRequests } from "../support/helpers/agent-timeline-gate"; import { seedMockAgentWorkspace } from "../support/helpers/mock-agent"; test.describe("Agent timeline pagination", () => { test("resumes a persisted canonical timeline after its exact end", async ({ page }) => { const prompt = "timeline persisted range resumes without tail replay"; const agent = await seedMockAgentWorkspace({ repoPrefix: "timeline-persisted-range-", title: "Persisted timeline range", initialPrompt: prompt, }); try { await agent.client.waitForFinish(agent.agentId, 15_000); await openAgentTimeline(page, agent); await expectTimelinePromptVisible(page, prompt); const range = await waitForPersistedCanonicalTimelineRange(page, agent.agentId); const tracker = await trackAgentTimelineRequests(page, agent.agentId); await page.reload(); await expectTimelinePromptVisible(page, prompt); const request = await tracker.nextRequest(); expect(request).toEqual({ direction: "after", cursor: { epoch: range.epoch, seq: range.endSeq }, }); await tracker.waitForResponse(); expect(tracker.requests().some((entry) => entry.direction === "tail")).toBe(false); } finally { await agent.cleanup(); } }); test("keeps a fixed history-start gutter before, during, and after pagination", async ({ page, }) => { const agent = await seedLongMockAgentTimeline({ turns: 40 }); try { const history = await holdOlderHistoryPages(page, agent); await openAgentTimeline(page, agent); await expectStableHistoryStartGutter(page); await userScrollsTimelineToHistoryStart(page); await history.expectRequestedPages(1); await expectStableHistoryStartGutter(page); history.releasePage(1); await history.expectSettledWithRequestedPages(1); await expectStableHistoryStartGutter(page); } finally { await agent.cleanup(); } }); test("loads one page each time the user returns to history start", async ({ page }) => { test.setTimeout(120_000); const agent = await seedLongMockAgentTimeline({ turns: 80 }); try { const history = await holdOlderHistoryPages(page, agent); await openAgentTimeline(page, agent); await expectTimelinePromptVisible(page, agent.newestPrompt); await expectTimelinePromptCentered(page, agent.newestPrompt); await expectTimelinePromptNotMounted(page, agent.oldestPrompt); await expectTimelinePromptNotMounted(page, agent.initialTailOldestPrompt); await userScrollsTimelineToHistoryStart(page); await expectTimelinePromptVisible(page, agent.initialTailOldestPrompt); await history.expectRequestedPages(1); history.releasePage(1); await history.expectSettledWithRequestedPages(1); await userScrollsTimelineToHistoryStart(page); await expectTimelinePromptCentered(page, agent.firstOlderPagePrompt); await history.expectRequestedPages(2); } finally { await agent.cleanup(); } }); test("loads older history when the user navigates to history start with the keyboard", async ({ page, }) => { const agent = await seedLongMockAgentTimeline({ turns: 40 }); try { const history = await holdOlderHistoryPages(page, agent); await openAgentTimeline(page, agent); await userNavigatesTimelineToHistoryStartWithKeyboard(page); await history.expectRequestedPages(1); } finally { await agent.cleanup(); } }); test("does not repeat an assistant block that spans older history pages", async ({ page }) => { test.setTimeout(120_000); const agent = await seedRealisticOlderTimeline(); try { const history = await holdOlderHistoryPages(page, agent); await openAgentTimeline(page, agent); await expectTimelinePromptVisible(page, agent.newestPrompt); await scrollThroughOlderHistoryPages(page, 3, history); history.expectNoRepeatedEntries(); } finally { await agent.cleanup(); } }); test("keeps the visible timeline position anchored while prepending a page", async ({ page }) => { test.setTimeout(120_000); const agent = await seedLongMockAgentTimeline({ turns: 80 }); try { const history = await holdOlderHistoryPages(page, agent); await openAgentTimeline(page, agent); await userScrollsTimelineToHistoryStart(page); await history.expectRequestedPages(1); const viewport = await rememberTimelineViewport(page); history.releasePage(1); await expectTimelineViewportAnchoredAfterPrepend(page, viewport); } finally { await agent.cleanup(); } }); test("keeps visible history anchored when live output grows during a prepend", async ({ page, }) => { test.setTimeout(120_000); const agent = await seedLongMockAgentTimeline({ turns: 80 }); try { const history = await holdOlderHistoryPages(page, agent); await openAgentTimeline(page, agent); await userScrollsTimelineToHistoryStart(page); await history.expectRequestedPages(1); const position = await rememberTimelinePromptPosition(page, agent.initialTailOldestPrompt); await agent.client.sendAgentMessage( agent.agentId, "timeline live during held older page: emit 20 coalesced agent stream updates", ); await agent.client.waitForFinish(agent.agentId, 15_000); history.releasePage(1); await expectTimelinePromptPositionPreserved(page, position); } finally { await agent.cleanup(); } }); test("finishes loading an older page while live output continues", async ({ page }) => { test.setTimeout(120_000); const agent = await seedLongMockAgentTimeline({ turns: 40 }); try { const history = await holdOlderHistoryPages(page, agent); await openAgentTimeline(page, agent); await userScrollsTimelineToHistoryStart(page); await history.expectRequestedPages(1); await agent.client.sendAgentMessage(agent.agentId, "keep streaming while history settles"); await agent.client.waitForAgentUpsert( agent.agentId, (snapshot) => snapshot.status === "running", ); history.releasePage(1); await expect(page.getByTestId("load-older-history-spinner")).toBeHidden({ timeout: 5_000 }); const running = await agent.client.fetchAgents({ scope: "active" }); expect(running.entries.find((entry) => entry.agent.id === agent.agentId)?.agent.status).toBe( "running", ); } finally { await agent.cleanup(); } }); test("keeps the visible timeline anchored when the final page finishes", async ({ page }) => { test.setTimeout(120_000); const agent = await seedLongMockAgentTimeline({ turns: 40 }); try { const history = await holdOlderHistoryPages(page, agent); await openAgentTimeline(page, agent); await userScrollsTimelineToHistoryStart(page); await history.expectRequestedPages(1); const position = await rememberTimelinePromptPosition(page, agent.initialTailOldestPrompt); history.releasePage(1); await expectTimelinePromptPositionPreserved(page, position); await history.expectSettledWithRequestedPages(1); } finally { await agent.cleanup(); } }); test("continues one loading operation while older pages still leave history start exposed", async ({ page, }) => { test.setTimeout(120_000); const agent = await seedLongMockAgentTimeline({ turns: 80 }); try { await makeLoadedTimelineFitViewport(page); const history = await holdOlderHistoryPages(page, agent); await openAgentTimeline(page, agent); await scrollTimelineToOldestLoadedEdge(page); await history.expectRequestedPages(1); const loading = await rememberOlderHistoryLoadingOperation(page); history.releasePage(1); await history.expectRequestedPages(2); await expectTimelineAtHistoryStart(page); await expectSameOlderHistoryLoadingOperation(page, loading); } finally { await agent.cleanup(); } }); test("keeps complete loaded history reachable after reload", async ({ page }) => { test.setTimeout(120_000); const agent = await seedLongMockAgentTimeline({ turns: 80 }); try { await openAgentTimeline(page, agent); await scrollTimelineUntilOlderHistoryIsReachable(page, agent.oldestPrompt); await expectTimelinePromptVisible(page, agent.oldestPrompt); const hydration = await holdDaemonHydration(page); await reloadAgentTimelineFromPersistedReplica(page, agent); hydration.release(); await scrollTimelineUntilOlderHistoryIsReachable(page, agent.oldestPrompt); await expectTimelinePromptVisible(page, agent.oldestPrompt); } finally { await agent.cleanup(); } }); test("preserves a live row received before replica hydration", async ({ page }) => { test.setTimeout(120_000); const agent = await seedLongMockAgentTimeline({ turns: 80 }); try { await openAgentTimeline(page, agent); await scrollTimelineUntilOlderHistoryIsReachable(page, agent.oldestPrompt); const hydration = await holdBootstrapTimelinePage(page, agent); await reloadAgentTimelineFromPersistedReplica(page, agent); await hydration.waitForDelayedResponse(); const livePrompt = await sendLiveTurnBeforeHydration(agent); await expectTimelinePromptVisible(page, livePrompt); hydration.release(); await hydration.waitForDelayedCatchUp(); await expectTimelinePromptVisible(page, livePrompt); hydration.releaseCatchUp(); await scrollTimelineUntilOlderHistoryIsReachable(page, agent.oldestPrompt); await expectTimelinePromptVisible(page, agent.oldestPrompt); await scrollTimelineToNewestLoadedEdge(page); await expectTimelinePromptVisible(page, livePrompt); } finally { await agent.cleanup(); } }); });