diff --git a/packages/app/e2e/performance/timeline/first-navigation-metrics.ts b/packages/app/e2e/performance/timeline/first-navigation-metrics.ts index 7c4a7d1f1..db3867804 100644 --- a/packages/app/e2e/performance/timeline/first-navigation-metrics.ts +++ b/packages/app/e2e/performance/timeline/first-navigation-metrics.ts @@ -18,9 +18,7 @@ export function summarizeFirstNavigation(samples: FirstNavigationSample[]) { const categories = samples.map(category) const stable = categories.findIndex( (value, index) => - value === "destination" && - categories[index + 1] === "destination" && - categories[index + 2] === "destination", + value === "destination" && categories[index + 1] === "destination" && categories[index + 2] === "destination", ) return { samples: samples.length, diff --git a/packages/app/e2e/performance/timeline/first-navigation-probe.ts b/packages/app/e2e/performance/timeline/first-navigation-probe.ts index 0fa3af6d8..0e7ef45f2 100644 --- a/packages/app/e2e/performance/timeline/first-navigation-probe.ts +++ b/packages/app/e2e/performance/timeline/first-navigation-probe.ts @@ -72,7 +72,8 @@ export async function measureFirstNavigation( ) await input.navigate() await page.waitForFunction(() => { - const samples = (window as Window & { __firstNavigationProbe?: FirstNavigationProbe }).__firstNavigationProbe?.samples + const samples = (window as Window & { __firstNavigationProbe?: FirstNavigationProbe }).__firstNavigationProbe + ?.samples if (!samples) return false return samples.length >= 3 && samples.slice(-3).every((sample) => sample.destination && !sample.source) }) diff --git a/packages/app/e2e/performance/timeline/navigation-milestones.ts b/packages/app/e2e/performance/timeline/navigation-milestones.ts index 6f003d244..b8ec858e8 100644 --- a/packages/app/e2e/performance/timeline/navigation-milestones.ts +++ b/packages/app/e2e/performance/timeline/navigation-milestones.ts @@ -20,7 +20,9 @@ export function summarizeNavigationMilestones(samples: NavigationMilestoneSample } return { samples: samples.length, - milestones: Object.fromEntries(names.map((name) => [name, summarize((sample) => sample.milestones[name] === true)])), + milestones: Object.fromEntries( + names.map((name) => [name, summarize((sample) => sample.milestones[name] === true)]), + ), all: summarize((sample) => names.every((name) => sample.milestones[name] === true)), } } @@ -38,75 +40,78 @@ export async function measureNavigationMilestones( navigate: () => Promise }, ) { - await page.evaluate(({ triggerSelector, milestones }) => { - const samples: NavigationMilestoneSample[] = [] - const streaks = new Map() - const marked = new Set() - let started: number | undefined - let running = true - const visible = (selector: string) => - [...document.querySelectorAll(selector)].some((element) => { - const rect = element.getBoundingClientRect() - const style = getComputedStyle(element) - return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none" - }) - const sample = () => { - if (!running || started === undefined) return - requestAnimationFrame(() => { - setTimeout(() => { - if (!running || started === undefined) return - const current = Object.fromEntries( - Object.entries(milestones).map(([name, milestone]) => [ - name, - milestone.visible === false ? !document.querySelector(milestone.selector) : visible(milestone.selector), - ]), - ) - samples.push({ - observedAtMs: performance.now() - started, - milestones: current, - }) - Object.entries(current).forEach(([name, value]) => { - if (!value) { - streaks.set(name, 0) - return + await page.evaluate( + ({ triggerSelector, milestones }) => { + const samples: NavigationMilestoneSample[] = [] + const streaks = new Map() + const marked = new Set() + let started: number | undefined + let running = true + const visible = (selector: string) => + [...document.querySelectorAll(selector)].some((element) => { + const rect = element.getBoundingClientRect() + const style = getComputedStyle(element) + return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none" + }) + const sample = () => { + if (!running || started === undefined) return + requestAnimationFrame(() => { + setTimeout(() => { + if (!running || started === undefined) return + const current = Object.fromEntries( + Object.entries(milestones).map(([name, milestone]) => [ + name, + milestone.visible === false ? !document.querySelector(milestone.selector) : visible(milestone.selector), + ]), + ) + samples.push({ + observedAtMs: performance.now() - started, + milestones: current, + }) + Object.entries(current).forEach(([name, value]) => { + if (!value) { + streaks.set(name, 0) + return + } + if (!marked.has(`${name}.first`)) { + performance.mark(`opencode.navigation.${name}.first`) + marked.add(`${name}.first`) + } + const streak = (streaks.get(name) ?? 0) + 1 + streaks.set(name, streak) + if (streak === 3) performance.mark(`opencode.navigation.${name}.stable`) + }) + const all = Object.values(current).every(Boolean) + const allStreak = all ? (streaks.get("all") ?? 0) + 1 : 0 + streaks.set("all", allStreak) + if (all && !marked.has("all.first")) { + performance.mark("opencode.navigation.all.first") + marked.add("all.first") } - if (!marked.has(`${name}.first`)) { - performance.mark(`opencode.navigation.${name}.first`) - marked.add(`${name}.first`) - } - const streak = (streaks.get(name) ?? 0) + 1 - streaks.set(name, streak) - if (streak === 3) performance.mark(`opencode.navigation.${name}.stable`) - }) - const all = Object.values(current).every(Boolean) - const allStreak = all ? (streaks.get("all") ?? 0) + 1 : 0 - streaks.set("all", allStreak) - if (all && !marked.has("all.first")) { - performance.mark("opencode.navigation.all.first") - marked.add("all.first") - } - if (allStreak === 3) performance.mark("opencode.navigation.all.stable") + if (allStreak === 3) performance.mark("opencode.navigation.all.stable") + sample() + }, 0) + }) + } + document.addEventListener( + "click", + (event) => { + if (!(event.target instanceof Element) || !event.target.closest(triggerSelector)) return + started = performance.now() + performance.mark("opencode.navigation.click") sample() - }, 0) - }) - } - document.addEventListener( - "click", - (event) => { - if (!(event.target instanceof Element) || !event.target.closest(triggerSelector)) return - started = performance.now() - performance.mark("opencode.navigation.click") - sample() - }, - { capture: true, once: true }, - ) - ;(window as Window & { __navigationMilestones?: NavigationMilestoneProbe }).__navigationMilestones = { - samples, - stop: () => { - running = false - }, - } - }, { triggerSelector: input.triggerSelector, milestones: input.milestones }) + }, + { capture: true, once: true }, + ) + ;(window as Window & { __navigationMilestones?: NavigationMilestoneProbe }).__navigationMilestones = { + samples, + stop: () => { + running = false + }, + } + }, + { triggerSelector: input.triggerSelector, milestones: input.milestones }, + ) await input.navigate() await page.waitForFunction(() => { const samples = (window as Window & { __navigationMilestones?: NavigationMilestoneProbe }).__navigationMilestones diff --git a/packages/app/e2e/performance/timeline/session-timeline-stress.fixture.ts b/packages/app/e2e/performance/timeline/session-timeline-stress.fixture.ts index 7017752f8..529081a1d 100644 --- a/packages/app/e2e/performance/timeline/session-timeline-stress.fixture.ts +++ b/packages/app/e2e/performance/timeline/session-timeline-stress.fixture.ts @@ -344,9 +344,7 @@ export const fixture = { targetMessageIDs: targetMessages .filter((message) => message.info.role === "user") .map((message) => message.info.id), - childMessageIDs: childMessages - .filter((message) => message.info.role === "user") - .map((message) => message.info.id), + childMessageIDs: childMessages.filter((message) => message.info.role === "user").map((message) => message.info.id), targetPartIDs: targetMessages.flatMap((message) => orderedParts(message) .filter(renderable) diff --git a/packages/app/e2e/performance/timeline/timeline-test-helpers.ts b/packages/app/e2e/performance/timeline/timeline-test-helpers.ts index b66a6f2b2..1535dd2c7 100644 --- a/packages/app/e2e/performance/timeline/timeline-test-helpers.ts +++ b/packages/app/e2e/performance/timeline/timeline-test-helpers.ts @@ -47,17 +47,15 @@ export async function installStressSessionTabs(page: Page, input?: { draftID?: s ) localStorage.setItem( "opencode.global.dat:tabs", - JSON.stringify( - [ - ...sessionIDs.map((sessionId) => ({ - type: "session", - server, - dirBase64, - sessionId, - })), - ...(draftID ? [{ type: "draft", draftID, server, directory }] : []), - ], - ), + JSON.stringify([ + ...sessionIDs.map((sessionId) => ({ + type: "session", + server, + dirBase64, + sessionId, + })), + ...(draftID ? [{ type: "draft", draftID, server, directory }] : []), + ]), ) }, { diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 0e26a7e65..7528534e5 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1149,7 +1149,8 @@ export default function Page() { }) } const onHistoryScroll = () => { - if (historyRequest || historyLoading() || !autoScroll.userScrolled() || !scroller || scroller.scrollTop >= 200) return + if (historyRequest || historyLoading() || !autoScroll.userScrolled() || !scroller || scroller.scrollTop >= 200) + return void loadOlder() }