packages/app/perf/timeline.bench.ts
Rama dev · réplica de lectura del repositorio original.
// One long session (120 turns of markdown, code fences, tool calls and diffs) at normal speed.
import { bench, web } from "@hona/strobe"
import { editTools, reviewDiffs } from "./fixtures/diffs"
import { dragDivider, measureDivider, type Divider } from "./fixtures/drag"
import { cached } from "./fixtures/mock"
import { openReview } from "./fixtures/review"
import { composer, mockTimelineSession, oldestPrompt, timeline } from "./fixtures/timeline"
const find = '[data-component="timeline-search-bar"] input'
const count = '[data-slot="timeline-search-count"]'
// The first of four edits of Core's tool sources (edit.ts), listed one per file and collapsed.
const edit = '[data-scope="apply-patch"] [data-slot="accordion-trigger"]'
const diff = '[data-component="apply-patch-file-diff"] diffs-container >>> [data-line]'
const drag = "drag the review's edge"
// The divider between the session and the side panel, read once the review is open. The session panel keeps at least
// 400px, the side panel 320px and the 8px gap.
const handle = '[data-slot="session-chat-panel"] > div > [data-component="resize-handle"]'
let divider: Divider | undefined
export default bench({
// The session a case's launch loads: the edits alone, the history beside the review, or the history. Cases with the
// same one share a launch.
setupKey: (ctx) => (ctx.case === "expand an edit" ? "edits" : ctx.case === drag ? "review" : "history"),
targets: {
web: web({
expect: { title: "OpenCode" },
viewport: { width: 1366, height: 768 },
prepare: async (ctx) =>
mockTimelineSession(cached(ctx), {
server: new URL(ctx.url).origin,
// The edits alone, so the case measures the diff, not the history around it.
historyTurns: ctx.setupKey === "edits" ? 0 : 120,
collapsedEdits: ctx.setupKey === "edits",
tools: ctx.setupKey === "edits" ? await editTools() : undefined,
vcsDiff: ctx.setupKey === "review" ? reviewDiffs() : undefined,
}),
url: ({ prepared }) => prepared.path,
}),
},
async setup({ setupKey, page }) {
await page.waitFor(timeline, { timeout: 60_000 })
await page.waitFor(composer)
if (setupKey === "review") {
await openReview(page)
await page.idle()
divider = await page.eval(measureDivider, {
panel: '[data-slot="session-chat-panel"]',
row: "[data-pane-row]",
min: 400,
rest: 328,
})
}
await page.idle()
},
cases: {
"scroll history": {
primary: "main.busy",
async run({ page, phase }) {
await phase("up", () => page.scroll(timeline, { by: -4000, speed: 3000 }))
await phase("down", () => page.scroll(timeline, { by: 4000, speed: 3000 }))
},
reset: ({ page }) => page.idle(),
},
"type in composer": {
primary: "interaction.p95",
run: ({ page }) => page.type(composer, "Refactor the timeline so rows mount lazily", { delay: 80 }),
async reset({ page }) {
await page.press("ControlOrMeta+a")
await page.press("Backspace")
await page.idle()
},
},
// 120 prompts match; the first is the oldest, far above the viewport and not mounted until Find reveals it.
"find in session": {
primary: "main.work",
metrics: ["run.duration", "interaction.p95"],
async run({ page }) {
await page.press("ControlOrMeta+f", { until: find })
await page.type(find, "Historical prompt", { delay: 50 })
await page.waitFor(count, { text: "1/120" })
await page.waitFor(oldestPrompt)
},
// A fresh document: once revealed, the top rows stay measured and a second search would be cheaper.
async reset({ page, prepared }) {
await page.goto(prepared.path, { until: timeline, timeout: 60_000 })
await page.idle()
},
},
// A full-file diff of edit.ts (395 lines) mounts and highlights in the timeline, which does not virtualize it.
"expand an edit": {
primary: "main.work",
metrics: ["run.duration"],
async run({ page }) {
await page.click(edit, { until: diff })
await page.idle()
},
async reset({ page }) {
await page.click(edit)
await page.idle()
},
},
// The divider beside the review (72 changed files) held for five seconds and dragged as a hand does, a pointer
// move every display frame: a slow stretch toward the session, jiggles, a fast throw toward the review and a slow
// pull back (fixtures/drag.ts). The timeline narrows and widens on every frame, so the rows in view rewrap; each
// frame should keep up with the display.
[drag]: {
primary: "frames.dropped.pct",
metrics: ["main.work", "main.layout", "main.style", "main.script", "frames.interval.p95"],
run: ({ page }) => dragDivider(page, handle, divider!),
reset: ({ page }) => page.idle(),
},
},
})