Merge branch 'master' into fix/compact-before-model-switch

This commit is contained in:
Dudu-0223 authored and GitHub committed 2026-10-16 13:30:07 +08:00
commit f9761a140c
7 files changed
+92 -42

No files matched your search

+1 -3
View File
@@ -166,7 +166,6 @@ describe('web e2e: Markdown image rendering', () => {
let browser: Browser
let page: Page
let tripwire: ReturnType<typeof watchConsole>
let unpinBrowserClock: (() => void) | undefined
const mediaResponses = new Map<string, number>()
beforeAll(async () => {
@@ -191,7 +190,7 @@ describe('web e2e: Markdown image rendering', () => {
{ createdAt: WEB_FIXTURE_TIME - 60_000 })
browser = await chromium.launch()
page = await newEnglishPage(browser)
unpinBrowserClock = await pinBrowserClock(page)
await pinBrowserClock(page)
tripwire = watchConsole(page)
page.on('response', (response) => {
const url = new URL(response.url())
@@ -204,7 +203,6 @@ describe('web e2e: Markdown image rendering', () => {
}, 120_000)
afterAll(async () => {
unpinBrowserClock?.()
await browser?.close()
await scaffold?.close()
if (imageOrigin !== undefined) await stopServer(imageOrigin.server)
+1 -3
View File
@@ -191,7 +191,6 @@ describe('web e2e: message IconActions and clocks on settled history', () => {
let browser: Browser
let page: Page
let tripwire: ReturnType<typeof watchConsole>
let unpinBrowserClock: (() => void) | undefined
beforeAll(async () => {
scaffold = await launchWebScaffold()
@@ -212,14 +211,13 @@ describe('web e2e: message IconActions and clocks on settled history', () => {
await seedSession(scaffold, raw, SEED_ID, undefined, { createdAt: WEB_FIXTURE_TIME - 90_000 })
browser = await chromium.launch()
page = await newEnglishPage(browser)
unpinBrowserClock = await pinBrowserClock(page)
await pinBrowserClock(page)
tripwire = watchConsole(page)
await page.goto(scaffold.authenticatedUrl, { waitUntil: 'load' })
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
}, 120_000)
afterAll(async () => {
unpinBrowserClock?.()
await browser?.close()
await scaffold?.close()
})
+60
View File
@@ -0,0 +1,60 @@
/** Fixture-day clock pins keep page scheduling native at every real-to-fixture distance. */
import { chromium, type Browser } from 'playwright'
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'
import { newEnglishPage, pinBrowserClock, WEB_FIXTURE_TIME } from './support.ts'
// Host clock shifts spread over one 2^32 ms period of the real-to-fixture
// distance; Playwright's page clock stopped page timers in half of that period.
const DISTANCE_PERIOD_MS = 2 ** 32
const HOST_SHIFTS_MS = Array.from({ length: 8 }, (_, index) => index * DISTANCE_PERIOD_MS / 8)
describe('web e2e: pinned fixture clocks', () => {
let browser: Browser
beforeAll(async () => {
browser = await chromium.launch()
}, 120_000)
afterAll(async () => {
await browser?.close()
})
it.each(HOST_SHIFTS_MS)('runs page timers and reads the fixture day with the Host clock %d ms behind it', async (shift) => {
const hostStartedAt = performance.now()
const hostClock = vi.spyOn(Date, 'now').mockImplementation(() =>
WEB_FIXTURE_TIME - shift + Math.floor(performance.now() - hostStartedAt))
const page = await newEnglishPage(browser)
try {
await pinBrowserClock(page)
for (const navigation of ['initial', 'replacement']) {
await page.goto(`data:text/html,<body>${navigation}</body>`)
const pageStartedAt = await page.evaluate(() => {
requestAnimationFrame(() => { document.body.dataset.frame = 'done' })
setTimeout(() => { document.body.dataset.timer = 'done' }, 20)
return Date.now()
})
await expect.poll(() => page.evaluate(() => ({
frame: document.body.dataset.frame,
timer: document.body.dataset.timer,
})), { timeout: 5_000 }).toEqual({ frame: 'done', timer: 'done' })
const dates = await page.evaluate(() => ({
now: Date.now(),
day: new Date().toISOString().slice(0, 10),
explicit: new Date(0).getTime(),
parsed: Date.parse('2000-01-01T00:00:00Z'),
}))
expect(pageStartedAt).toBeGreaterThanOrEqual(WEB_FIXTURE_TIME)
expect(dates.now).toBeGreaterThan(pageStartedAt)
expect(dates.day).toBe(new Date(WEB_FIXTURE_TIME).toISOString().slice(0, 10))
expect(dates.explicit).toBe(0)
expect(dates.parsed).toBe(946_684_800_000)
}
} finally {
try {
await page.close()
} finally {
hostClock.mockRestore()
}
}
}, 60_000)
})
+1 -3
View File
@@ -142,7 +142,6 @@ describe.skipIf(MODE === 'record')('web e2e: file and session references through
let browser: Browser
let page: Page
let tripwire: ReturnType<typeof watchConsole>
let unpinBrowserClock: (() => void) | undefined
beforeAll(async () => {
scaffold = await launchWebScaffold({})
@@ -151,7 +150,7 @@ describe.skipIf(MODE === 'record')('web e2e: file and session references through
await seedSession(scaffold, targetSessionFixture(), TARGET_SESSION_ID, undefined, { createdAt: targetCreatedAt })
browser = await chromium.launch()
page = await newEnglishPage(browser)
unpinBrowserClock = await pinBrowserClock(page)
await pinBrowserClock(page)
tripwire = watchConsole(page)
// Fixture files land before the workspace connects so the Host's file
// index never races their creation (the connect helper mkdirs the same
@@ -171,7 +170,6 @@ describe.skipIf(MODE === 'record')('web e2e: file and session references through
}, 120_000)
afterAll(async () => {
unpinBrowserClock?.()
await browser?.close()
await scaffold?.close()
})
+1 -3
View File
@@ -230,7 +230,6 @@ describe('web e2e: seeded history renders through cold resume', () => {
let seededThroughSeq = -1
let openingWindow: unknown
let hostClock: ReturnType<typeof pinHostClock> | undefined
let unpinBrowserClock: (() => void) | undefined
beforeAll(async () => {
// Seeded and later command timestamps share the renderer's fixture day across midnight.
@@ -271,7 +270,7 @@ describe('web e2e: seeded history renders through cold resume', () => {
}
browser = await chromium.launch()
page = await newEnglishPage(browser)
if (MODE !== 'record') unpinBrowserClock = await pinBrowserClock(page)
if (MODE !== 'record') await pinBrowserClock(page)
tripwire = watchConsole(page)
if (MODE !== 'record') {
// A one-message tail makes this short recording exercise the real Load earlier path.
@@ -303,7 +302,6 @@ describe('web e2e: seeded history renders through cold resume', () => {
await browser?.close()
await scaffold?.close()
} finally {
unpinBrowserClock?.()
hostClock?.mockRestore()
}
})
+3 -7
View File
@@ -73,7 +73,6 @@ describe('web e2e: mid-turn steering lands durably and visibly', () => {
let page: Page
let tripwire: ReturnType<typeof watchConsole>
let hostClock: MockInstance<typeof Date.now> | undefined
let unpinBrowserClock: (() => void) | undefined
const sessionEvents: SessionEvent[] = []
beforeAll(async () => {
@@ -87,7 +86,7 @@ describe('web e2e: mid-turn steering lands durably and visibly', () => {
scaffold.ctx.on('session/event', (_session, event) => { sessionEvents.push(event) })
browser = await chromium.launch()
page = await newEnglishPage(browser)
unpinBrowserClock = await pinBrowserClock(page)
await pinBrowserClock(page)
tripwire = watchConsole(page)
await page.goto(scaffold.authenticatedUrl, { waitUntil: 'load' })
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
@@ -101,8 +100,7 @@ describe('web e2e: mid-turn steering lands durably and visibly', () => {
await scaffold?.close()
} finally {
// A failing close (unconsumed fixture, mismatched snapshot) must not
// leave the pinned clocks running for the suites after this one.
unpinBrowserClock?.()
// leave the pinned Host clock running for the suites after this one.
hostClock?.mockRestore()
}
})
@@ -396,7 +394,6 @@ describe('web e2e: empty-draft Cmd+Enter steers the whole queue', () => {
let page: Page
let tripwire: ReturnType<typeof watchConsole>
let hostClock: MockInstance<typeof Date.now> | undefined
let unpinBrowserClock: (() => void) | undefined
const sessionEvents: SessionEvent[] = []
beforeAll(async () => {
@@ -416,7 +413,7 @@ describe('web e2e: empty-draft Cmd+Enter steers the whole queue', () => {
scaffold.ctx.on('session/event', (_session, event) => { sessionEvents.push(event) })
browser = await chromium.launch()
page = await newEnglishPage(browser)
unpinBrowserClock = await pinBrowserClock(page)
await pinBrowserClock(page)
tripwire = watchConsole(page)
await page.goto(scaffold.authenticatedUrl, { waitUntil: 'load' })
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
@@ -431,7 +428,6 @@ describe('web e2e: empty-draft Cmd+Enter steers the whole queue', () => {
await browser?.close()
await scaffold?.close()
} finally {
unpinBrowserClock?.()
hostClock?.mockRestore()
}
})
+25 -23
View File
@@ -50,9 +50,6 @@ export const WEB_FIXTURE_TIME = Date.parse('2026-01-15T12:00:00+08:00')
*/
export const PLUGIN_TOGGLE_SETTLE_MS = 10_000
/** How often a pinned browser clock advances, in milliseconds. */
const PINNED_CLOCK_STEP_MS = 250
/**
* Pin the Host clock to the shared fixture day for one scenario suite.
*
@@ -74,31 +71,36 @@ export function pinHostClock(): MockInstance<typeof Date.now> {
}
/**
* Pin a scenario page's clock to the shared fixture day, advancing with real
* Pin a scenario page's calendar to the shared fixture day, advancing with real
* time. Pair it with {@link pinHostClock} for a scenario that stamps its own
* message times, and with `seedSession`'s `createdAt` for a seeded one.
*
* The frozen instant is re-applied from the anchor on an interval rather than
* left at one value, because product logic reads two `Date.now()` values to
* decide a minimum display hold or a deadline window; a permanently frozen
* clock stops supplying elapsed time, so a title that owes its golden settled
* text keeps rendering its previous one. Both clocks therefore advance at the
* real rate and stay within one step of each other.
* @param page - page whose clock is pinned.
* @returns the disposer that stops re-pinning; call it in teardown.
* Only the page's `Date` reads the fixture day; native timers and
* `requestAnimationFrame` keep their real scheduling. Playwright's
* `page.clock` is not used: its server records clock state with the Vitest
* worker's `Date.now`, which {@link pinHostClock} replaces, so after a
* navigation the page clock replays the gap between the real and fixture days
* and hands the resulting negative delay to native `setTimeout`. The browser
* truncates that delay to a 32-bit integer, and in about half of the dates the
* result is a positive delay of weeks that stops every page timer. The `Date`
* override is installed for every later document and on the current one, and
* stays until the page closes.
* @param page - page whose calendar is pinned.
*/
export async function pinBrowserClock(page: Page): Promise<() => void> {
const startedAt = Date.now()
const step = async (): Promise<void> => {
try {
await page.clock.setFixedTime(WEB_FIXTURE_TIME + (Date.now() - startedAt))
} catch {
// The page closed under the interval; teardown owns the last word.
}
export async function pinBrowserClock(page: Page): Promise<void> {
// The worker's `new Date()` keeps the real clock even when `Date.now` is pinned.
const offset = WEB_FIXTURE_TIME - new Date().getTime()
const install = (offset: number): void => {
const NativeDate = Date
const now = (): number => NativeDate.now() + offset
globalThis.Date = new Proxy(NativeDate, {
construct: (target, args, newTarget): Date => Reflect.construct(target, args.length === 0 ? [now()] : args, newTarget) as Date,
apply: () => new NativeDate(now()).toString(),
get: (target, property, receiver): unknown => property === 'now' ? now : Reflect.get(target, property, receiver),
})
}
await step()
const timer = setInterval(() => { void step() }, PINNED_CLOCK_STEP_MS)
return () => { clearInterval(timer) }
await page.addInitScript(install, offset)
await page.evaluate(install, offset)
}
/**