Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 39 additions & 11 deletions apps/desktop/e2e/transcript-scroll.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -429,8 +429,12 @@ test('a gesture a nested scroller consumed does not release the tail', async ({
await page.setViewportSize({ width: 900, height: 700 });
await sendPrompt(page, LONG_PROMPT);
await expect(answeredTurns(page)).toHaveCount(1, { timeout: 30_000 });
const settled = await scrollMetrics(page);
expect(settled.distance, JSON.stringify(settled)).toBeLessThanOrEqual(4);
// The turn's arrival and the tail-follow write are two steps, so one sample
// races the follow on a loaded runner. Poll until the reader has provably
// been carried back to the tail.
await expect.poll(async () => (await scrollMetrics(page)).distance, {
message: 'the transcript follows the landed answer to the tail',
}).toBeLessThanOrEqual(4);

// A real scroller inside the transcript, standing in for a tool-output box
// (`.maka-tool-output-body`, `max-height: 256px; overflow-y: auto`) or a pty
Expand Down Expand Up @@ -500,9 +504,23 @@ test('a nested scroller near the history boundary does not request an earlier ra
}) => {
await page.setViewportSize({ width: 900, height: 1500 });
await waitForPaintedFrames(page, 6);
const metrics = await scrollMetrics(page);
expect(metrics.scrollTop).toBeLessThanOrEqual(Math.max(640, metrics.clientHeight * 2));
expect(metrics.distance).toBeLessThanOrEqual(4);
// The fixture is ready when the transcript exists, before its initial tail
// positioning necessarily completes. Poll one geometry sample so the pin has
// provably settled inside the load band and at the tail before the nested
// scroller exercises it.
await expect.poll(async () => {
const metrics = await scrollMetrics(page);
return {
insideLoadBand: metrics.scrollTop <= Math.max(640, metrics.clientHeight * 2),
settledAtTail: metrics.distance <= 4,
metrics,
};
}, {
message: 'the initial transcript tail positioning settles',
}).toMatchObject({
insideLoadBand: true,
settledAtTail: true,
});

const nestedBefore = await page.evaluate((selector) => {
const root = document.querySelector<HTMLElement>(selector);
Expand Down Expand Up @@ -706,12 +724,22 @@ test('following the tail does not ask for the history above it', async ({
await page.setViewportSize({ width: 900, height: 1500 });
await waitForPaintedFrames(page, 6);

const settled = await scrollMetrics(page);
expect(settled.distance, JSON.stringify(settled)).toBeLessThanOrEqual(4);
expect(
settled.scrollTop,
`the tail must be inside the load band for this test to mean anything: ${JSON.stringify(settled)}`,
).toBeLessThanOrEqual(Math.max(640, settled.clientHeight * 2));
// Same as above: the fixture being ready does not mean the initial tail
// positioning has completed. Poll until the pin has provably settled at the
// tail and inside the load band this test's history claim rests on.
await expect.poll(async () => {
const settled = await scrollMetrics(page);
return {
insideLoadBand: settled.scrollTop <= Math.max(640, settled.clientHeight * 2),
settledAtTail: settled.distance <= 4,
settled,
};
}, {
message: 'the initial transcript tail positioning settles',
}).toMatchObject({
insideLoadBand: true,
settledAtTail: true,
});

// Nothing arrived that the reader did not ask for.
await waitForPaintedFrames(page, 12);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ test("settings usage stats use the canonical model-call total and load every act
cacheHitRequests: 10,
cacheCreateRequests: 5,
errorRequests: 2,
totalDurationMs: 0,
},
provenance: provenance(),
} satisfies UsageQueryResult;
Expand Down Expand Up @@ -176,6 +177,7 @@ test("settings usage stats reject a non-advancing activity page", async () => {
cacheHitRequests: 0,
cacheCreateRequests: 0,
errorRequests: 0,
totalDurationMs: 0,
},
provenance: provenance(),
} satisfies UsageQueryResult;
Expand Down Expand Up @@ -243,6 +245,7 @@ test("settings usage stats degrade instead of erroring when logs disagree with t
cacheHitRequests: 0,
cacheCreateRequests: 0,
errorRequests: 0,
totalDurationMs: 0,
},
provenance: provenance(),
} satisfies UsageQueryResult;
Expand Down Expand Up @@ -316,6 +319,7 @@ test("settings usage stats group the provider breakdown by connection", async ()
cacheHitRequests: 0,
cacheCreateRequests: 0,
errorRequests: 0,
totalDurationMs: 0,
},
provenance: provenance(),
} satisfies UsageQueryResult;
Expand Down Expand Up @@ -394,6 +398,7 @@ test("settings usage stats truncate the activity log at the cap instead of error
cacheHitRequests: 0,
cacheCreateRequests: 0,
errorRequests: 0,
totalDurationMs: 0,
},
provenance: provenance(),
} satisfies UsageQueryResult;
Expand Down Expand Up @@ -467,6 +472,7 @@ test("settings usage stats name each row from the Host-resolved session title",
cacheHitRequests: 0,
cacheCreateRequests: 0,
errorRequests: 0,
totalDurationMs: 0,
},
provenance: provenance(),
} satisfies UsageQueryResult;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ test('does not render legacy zero cost as a known free Session', () => {
cacheHitRequests: 0,
cacheCreateRequests: 0,
errorRequests: 0,
totalDurationMs: 0,
provenance: {
coverage: {
attempts: 0,
Expand Down Expand Up @@ -85,6 +86,7 @@ test('reports incomplete provenance as unavailable regardless of recorded reques
cacheHitRequests: 0,
cacheCreateRequests: 0,
errorRequests: 0,
totalDurationMs: 0,
provenance: {
coverage: {
attempts: 0,
Expand Down Expand Up @@ -121,6 +123,7 @@ test('does not estimate a cache-hit ratio from partial usage', () => {
cacheHitRequests: 1,
cacheCreateRequests: 0,
errorRequests: 0,
totalDurationMs: 0,
provenance: {
coverage: {
attempts: 1,
Expand Down
Loading