Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
cd811e1
refactor(desktop): give the Renderer the transcript window
Astro-Han Sep 10, 2026
bba473c
test(desktop): measure the reader at transcript range boundaries
Astro-Han Sep 10, 2026
aedc843
fix(desktop): scope transcript window invalidation to what each read …
Astro-Han Sep 10, 2026
e8450a1
feat(desktop)!: drop the transcript range boundary notice
Astro-Han Sep 10, 2026
b61d08d
chore: drop the retired transcript history pending state from the hoo…
Astro-Han Sep 11, 2026
649b835
chore(desktop): refresh the renderer architecture ledger
Astro-Han Sep 11, 2026
327d96a
refactor(desktop): let the subscription's lifetime say when a transcr…
Astro-Han Sep 11, 2026
684c34c
fix(desktop): keep a navigation answerable across a trim of the windo…
Astro-Han Sep 11, 2026
5b8679e
fix(desktop): release the delivery budget a stranded page answer holds
Astro-Han Sep 11, 2026
c99ac2c
fix(desktop): give the transcript window one lifecycle and two invari…
Astro-Han Sep 11, 2026
693dc10
test(desktop): tell the displacement probe when the reader's hand is …
Astro-Han Sep 11, 2026
ec1682c
chore(desktop): keep hand-written e2e drivers out of the tree
Astro-Han Sep 11, 2026
15894f8
fix(desktop): drop the retired fill answer from the app-shell stories
Astro-Han Sep 11, 2026
32c1974
refactor(desktop)!: make the transcript window an anchored immutable …
Astro-Han Sep 11, 2026
770b271
chore(desktop): refresh the renderer architecture ledger
Astro-Han Sep 11, 2026
02fbd2e
fix(desktop): move the tail watermark with every catch-up page
Astro-Han Sep 11, 2026
84b1f05
fix(desktop): bring the app-shell stories onto the Renderer-owned window
Astro-Han Sep 11, 2026
2567918
test(ui): scroll the rail reading-position probe as a reader
Astro-Han Sep 11, 2026
16191d2
fix(ui): re-check the transcript band when the viewport resizes
Astro-Han Sep 11, 2026
bf3b56f
fix(desktop): mark the tail read only when the Renderer window reache…
Astro-Han Sep 11, 2026
5ca9127
fix(desktop): read the tail back before answering return-to-latest
Astro-Han Sep 11, 2026
368ac3d
fix(desktop): keep the reader's place across a Host epoch change
Astro-Han Sep 11, 2026
b239a9b
fix(desktop): give the WorkHub transcript the pixel band
Astro-Han Sep 11, 2026
ae1a52d
refactor(desktop): drop transcript window state nothing reads
Astro-Han Sep 11, 2026
e319d7c
refactor(desktop): publish WorkHub transcript snapshots from the stor…
Astro-Han Sep 11, 2026
1731276
refactor(desktop): let the read-marker gate stand alone
Astro-Han Sep 11, 2026
34853e5
refactor(desktop): name the transcript epoch-changed rejection once
Astro-Han Sep 11, 2026
16dfa83
refactor(ui): report reader scroll by phase only
Astro-Han Sep 11, 2026
93fc963
Merge remote-tracking branch 'origin/main' into refactor/transcript-r…
Astro-Han Sep 11, 2026
93e72a6
test(desktop): give the teardown fakes the transcript window contract
Astro-Han Sep 11, 2026
1c1c3ff
fix(desktop): release a transcript registration whose source is unava…
Astro-Han Sep 11, 2026
4bce9ec
refactor(desktop): drop the navigation wrapper and the surface's pass…
Astro-Han Sep 11, 2026
0a881c4
fix(desktop): acknowledge the transcript tail only for a visible reader
Astro-Han Sep 11, 2026
4ee5597
chore: trim comments and test scaffolding the transcript window PR added
Astro-Han Sep 11, 2026
0eb377c
Merge remote-tracking branch 'origin/main' into refactor/transcript-r…
Astro-Han Sep 11, 2026
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
4 changes: 4 additions & 0 deletions apps/desktop/.gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Playwright E2E run artifacts: traces, videos, screenshots, last-run state.
test-results/
# Throwaway drivers written beside the suite while investigating a scenario by
# hand. They resolve `@playwright/test` from here, which is why they live in the
# tree at all, and they are nobody's to keep.
e2e/.scratch/
resources/workers/
.maka-dev/
.maka-dev.staging/
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/e2e-budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@
"electron": "observation seeding, reconnect and settle are Host subscriptions surviving a renderer remount"
},
"transcript-scroll-cost.spec.ts": {
"tests": 3,
"electron": "the perf budget is measured from CDP wheel input and the browser's own render skipping"
"tests": 4,
"electron": "the perf budget is measured from CDP wheel input and the browser's own render skipping, and reader displacement only exists against a real layout"
},
"workhub-layout.spec.ts": {
"tests": 2,
Expand Down
65 changes: 25 additions & 40 deletions apps/desktop/e2e/partial-history-notice.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,66 +19,51 @@

import { expect, test } from './fixtures';

const GAP = '.maka-transcript-gap-row';
const TURN = '.maka-transcript-turn';
/** Turns the partial-history fixture seeds. */
const PARTIAL_HISTORY_TURN_COUNT = 18;

test('bounded transcript ranges expose only their truthful boundary gaps', async ({
test('a bounded transcript range reaches its whole history without a control to ask', async ({
partialHistoryWindow: page,
}) => {
await page.setViewportSize({ width: 1_400, height: 800 });

const olderGap = page.locator('[data-transcript-gap="older"]');
const newerGap = page.locator('[data-transcript-gap="newer"]');
await expect(olderGap).toBeVisible();
await expect(olderGap.getByRole('button', {
name: /^(?:加载较早消息|Load earlier messages)$/,
})).toBeVisible();
await expect(newerGap).toHaveCount(0);
await expect(page.locator('.maka-transcript-history-controls')).toHaveCount(0);
await expect(page.locator(TURN).first()).toBeVisible();
expect(await page.locator(TURN).count()).toBeLessThan(PARTIAL_HISTORY_TURN_COUNT);

const oldestPrompt = page.locator(
'.maka-prompt-rail-tick[data-prompt-turn-id="turn-partial-history-1"]',
);
await expect(oldestPrompt).toBeVisible();
await oldestPrompt.click();

const firstTurn = page.locator('[data-turn-id="turn-partial-history-1"]');
await expect(firstTurn).toBeVisible();
await expect(firstTurn).toHaveAttribute('data-search-highlight', 'true');
await expect(olderGap).toHaveCount(0);
await expect(newerGap).toBeVisible();
await expect(newerGap.getByRole('button', {
name: /^(?:加载较新消息|Load newer messages)$/,
})).toBeVisible();
await expect(page.locator(GAP)).toHaveCount(1);
expect(await page.locator(TURN).count()).toBeLessThanOrEqual(10);
await expect(page.locator('[data-turn-id="turn-partial-history-1"]')).toBeVisible();
// Where the jump landed, read from the reading position rather than from
// `data-search-highlight`: that highlight clears itself 2.2s after the
// command lands, so waiting for the Turn to mount and then asserting it
// fails whenever loading the page around it takes longer than the flash —
// measured here as a 3s pass turning into an 18s timeout under load.
await expect(oldestPrompt).toHaveAttribute('data-active', 'true');
// A jump lands on its own page, not on the whole history.
expect(await page.locator(TURN).count()).toBeLessThan(PARTIAL_HISTORY_TURN_COUNT);

const loadNewer = newerGap.getByRole('button', {
name: /^(?:加载较新消息|Load newer messages)$/,
});
await loadNewer.click();
await expect(page.locator('[data-turn-id="turn-partial-history-2"]')).toBeVisible();
await expect(olderGap).toHaveCount(0);
await expect(newerGap).toBeVisible();
await expect(loadNewer).toBeEnabled();

await loadNewer.click();
await expect(page.locator('[data-turn-id="turn-partial-history-3"]')).toBeVisible();
await expect(olderGap).toBeVisible();
await expect(newerGap).toBeVisible();
await expect(loadNewer).toBeEnabled();
await expect(oldestPrompt).toBeVisible();
await expect(page.locator(GAP)).toHaveCount(2);
expect(await page.locator(TURN).count()).toBeLessThanOrEqual(10);
// The newer side of the jump fills on its own as the reader moves into it.
await page.mouse.move(700, 400);
await expect(async () => {
await page.mouse.wheel(0, 400);
await expect(page.locator('[data-turn-id="turn-partial-history-3"]')).toBeVisible();
}).toPass({ timeout: 30_000 });

const returnToLatest = page.getByRole('button', {
name: /^(?:滚动主对话到底部|Scroll main conversation to bottom)$/,
});
await expect(returnToLatest).toBeVisible();
await returnToLatest.click();

await expect(page.locator('[data-turn-id="turn-partial-history-18"]')).toBeVisible();
await expect(newerGap).toHaveCount(0);
// Reading the tail page and rebuilding the window around it is slower than
// the paging above, and measured past the suite's 10s expect timeout here.
await expect(page.locator(`[data-turn-id="turn-partial-history-${PARTIAL_HISTORY_TURN_COUNT}"]`))
.toBeVisible({ timeout: 30_000 });
await expect(oldestPrompt).toBeVisible();
expect(await page.locator(TURN).count()).toBeLessThanOrEqual(10);
expect(await page.locator(TURN).count()).toBeLessThan(PARTIAL_HISTORY_TURN_COUNT);
});
Loading