Automerge backed Markdown editor for the Atmosphere
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467// Run from a Nix dev shell while the Classic dev server is up on :7002.// PLAYWRIGHT_CHROMIUM_BIN="$(command -v chromium)" node scripts/benchmark-mixed-vault.mjs// An isolated browser context owns the fixture; this never changes the user's vault.import { chromium } from 'playwright'
const fixture = { notes: Number(process.env.REAMS_BENCH_NOTES ?? 120), images: Number(process.env.REAMS_BENCH_IMAGES ?? 20), typedNotes: Number(process.env.REAMS_BENCH_TYPED_NOTES ?? 30), tabs: Number(process.env.REAMS_BENCH_TABS ?? 30), bases: process.env.REAMS_BENCH_BASES === '1',}const cpuThrottle = Number(process.env.REAMS_BENCH_CPU_THROTTLE ?? (process.env.REAMS_BENCH_MOBILE === '1' ? 2 : 1))if (!Number.isInteger(fixture.notes) || !Number.isInteger(fixture.images) || !Number.isInteger(fixture.typedNotes) || !Number.isInteger(fixture.tabs) || !Number.isFinite(cpuThrottle) || cpuThrottle < 1 || fixture.notes < 30 || fixture.images < 4 || fixture.typedNotes < 26 || fixture.typedNotes > fixture.notes || fixture.tabs < 7 || fixture.tabs > fixture.notes + 5 || fixture.typedNotes <= Math.max(25, fixture.tabs - 6 - Number(fixture.bases))) { throw new Error('Benchmark requires at least 30 notes, 4 images, 26 typed notes and 7 feasible tabs')}
const browser = await chromium.launch({ executablePath: process.env.PLAYWRIGHT_CHROMIUM_BIN || undefined, headless: true, args: ['--no-sandbox'],})const mobile = process.env.REAMS_BENCH_MOBILE === '1'const context = await browser.newContext({ viewport: mobile ? { width: 390, height: 844 } : { width: 1280, height: 720 } })
try { const page = await context.newPage() page.on('console', message => { if (message.text().startsWith('[mixed vault]')) console.log(message.text()) if (message.type() === 'error') console.error('[benchmark browser]', message.text()) }) page.on('pageerror', error => console.error('[benchmark browser]', error)) await page.goto(process.env.REAMS_BENCH_URL || 'http://localhost:7002/') // A fresh isolated profile can race Vite's first module transform and // repository initialization. A failed provisioning boot is recorded and // retried once; it is never counted as a measured reload. for (let attempt = 0; attempt < 2; attempt++) { const result = await Promise.race([ page.locator('.tab-bar .tab').first().waitFor({ timeout: 60_000 }).then(() => 'ready'), page.locator('#root > main[role="alert"]').waitFor({ timeout: 60_000 }).then(() => 'failed'), ]) if (result === 'ready') break const reason = await page.locator('#root > main[role="alert"]').innerText() if (attempt === 1) throw new Error(`Fixture boot failed twice: ${reason}`) console.warn('[mixed vault] fixture boot retry:', reason) await page.reload() } const production = process.env.REAMS_BENCH_PRODUCTION === '1' if (production) { // Production assets have no /src/*.ts import endpoint. Enable the real // vault preference through the same settings control a user operates. const originalSidebarLabel = await page.locator('.header-toggle-btn').first().getAttribute('aria-label') if (await page.locator('.ribbon').getAttribute('data-hidden') === 'true') await page.locator('.header-toggle-btn').first().click() await page.getByRole('button', { name: 'Open settings' }).click() await page.locator('#settings-tab-types-records').waitFor({ state: 'attached', timeout: 5000 }).catch(async () => { throw new Error(`Types settings missing after opening Settings: ${(await page.locator('body').innerText()).slice(0, 1500)}`) }) await page.locator('#settings-tab-types-records').click() const typesSwitch = page.getByRole('switch', { name: 'Enable Types and Records' }) if (!await typesSwitch.isChecked()) await page.locator('#settings-panel-types-records .settings-toggle').first().click() await page.keyboard.press('Escape') for (let i = 0; i < 3 && await page.locator('.header-toggle-btn').first().getAttribute('aria-label') !== originalSidebarLabel; i++) { await page.locator('.header-toggle-btn').first().click() } }
const created = await page.evaluate(async ({ notes: noteCount, images: imageCount, typedNotes: typedCount, tabs: tabCount, bases, production: productionBuild }) => { const app = window.__reamsApp const started = performance.now() // Fixture construction is not the measured operation. Stop live indexing // while writing hundreds of documents; the reload below starts it fresh // and measures exactly the cold-start path we care about. await app.reamsBackgroundIndexer.stop() const schemas = { task: { version: 1, name: 'Task', fields: { title: { key: 'title', name: 'Title', kind: 'text' }, done: { key: 'done', name: 'Done', kind: 'boolean', default: false }, status: { key: 'status', name: 'Status', kind: 'select', options: ['todo', 'doing', 'done'] }, }, fieldOrder: ['title', 'done', 'status'], NodeTitle: 'title', TaskListItem: 'done', }, project: { version: 1, name: 'Project', fields: { title: { key: 'title', name: 'Title', kind: 'text' }, status: { key: 'status', name: 'Status', kind: 'select', options: ['planned', 'active', 'shipped'] }, }, fieldOrder: ['title', 'status'], NodeTitle: 'title', }, meeting: { version: 1, name: 'Meeting', fields: { title: { key: 'title', name: 'Title', kind: 'text' }, date: { key: 'date', name: 'Date', kind: 'date' }, }, fieldOrder: ['title', 'date'], NodeTitle: 'title', }, } for (const [name, schema] of Object.entries(schemas)) { await app.vault.create(`types/${name}.json`, JSON.stringify(schema)) }
const smallPng = new Uint8Array(await (await fetch('/icon-512.png')).arrayBuffer()) const canvas = document.createElement('canvas') canvas.width = canvas.height = 512 const ctx = canvas.getContext('2d') if (!ctx) throw new Error('Canvas 2D is unavailable') const image = ctx.createImageData(512, 512) let seed = 0x51c0ffee const nextByte = () => { seed ^= seed << 13; seed ^= seed >>> 17; seed ^= seed << 5; return seed & 255 } for (let i = 0; i < image.data.length; i += 4) { image.data[i] = nextByte() image.data[i + 1] = nextByte() image.data[i + 2] = nextByte() image.data[i + 3] = 255 } ctx.putImageData(image, 0, 0) const largePng = new Uint8Array(await new Promise((resolve, reject) => { canvas.toBlob(async blob => blob ? resolve(await blob.arrayBuffer()) : reject(new Error('PNG encoding failed')), 'image/png') })) const smallImageCount = Math.floor(imageCount * 0.8) for (let i = 0; i < imageCount; i++) { await app.vault.createBinary(`media/Photo ${String(i).padStart(2, '0')}.png`, i < smallImageCount ? smallPng : largePng, 'image/png') if (i % 10 === 9 || i + 1 === imageCount) console.log(`[mixed vault] ${i + 1}/${imageCount} images in ${Math.round(performance.now() - started)} ms`) }
const base = bases ? await app.vault.create('Tasks.base', 'views:\n - name: Tasks\n type: table\n filters: node.records["task"] != null\n order: [file.name]\n') : null const notes = [] for (let i = 0; i < noteCount; i++) { const name = `Note ${String(i).padStart(3, '0')}` const type = i < typedCount ? ['task', 'project', 'meeting'][i % 3] : null const length = i % 20 === 0 ? 40_000 : i % 4 === 0 ? 5_000 : 800 const prose = 'Markdown prose with links and ongoing work. '.repeat(Math.ceil(length / 44)).slice(0, length) const links = Array.from({ length: 3 }, (_, j) => `[[Note ${String((i * 37 + j * 83 + 11) % noteCount).padStart(3, '0')}]]`).join(' ') const records = type ? '\n## Records\n' + Array.from({ length: 8 }, (_, j) => `- [${j % 3 ? ' ' : 'x'}] Item ${j} in ${name} #${type}`).join('\n') + '\n' : '' const embed = i % 5 === 0 ? `\n![[media/Photo ${String(i % imageCount).padStart(2, '0')}.png]]\n` : '' notes.push(await app.vault.create(`notes/${name}.md`, `---\ntitle: Project ${String(noteCount - i).padStart(3, '0')}\ncategory: ${type || 'journal'}\n---\n\n# ${name}\n\n${prose}\n${records}${embed}\n${links}\n`)) if (i % 25 === 24 || i + 1 === noteCount) console.log(`[mixed vault] ${i + 1}/${noteCount} notes in ${Math.round(performance.now() - started)} ms`) }
if (!productionBuild) { const { setTypesRecordsEnabled } = await import('/src/types-records/feature-setting.ts') setTypesRecordsEnabled(true) } for (const file of notes.slice(0, tabCount - 6 - (base ? 1 : 0))) app.workspace.openFile(file) for (let i = 0; i < 4; i++) { const imageFile = app.vault.getFileByPath(`media/Photo ${String(i).padStart(2, '0')}.png`) if (!imageFile) throw new Error('Created image is missing from vault') app.workspace.openFile(imageFile) } if (base) app.workspace.openFile(base) const activeNoteIndex = Math.max(25, tabCount - 6 - Number(bases)) app.workspace.openFile(notes[activeNoteIndex]) // active tab is a typed Markdown note return { creationMs: performance.now() - started, files: app.vault.getFiles().length, images: imageCount, typedNotes: typedCount, schemas: 3, bases: Number(bases), tabs: tabCount, active: `Note ${String(activeNoteIndex).padStart(3, '0')}`, imageBytes: smallPng.length * smallImageCount + largePng.length * (imageCount - smallImageCount), } }, { ...fixture, production })
await page.waitForFunction(count => document.querySelectorAll('.tab-bar .tab').length === count, fixture.tabs, { timeout: 90000 }) await page.waitForTimeout(900) // allow the debounced local tab-layout save to settle const beforeReload = await page.evaluate(() => ({ files: window.__reamsApp.vault.getFiles().length, tabs: document.querySelectorAll('.tab-bar .tab').length, savedTabs: JSON.parse(localStorage.getItem('reams:workspace-tabs') ?? '{}').tabs?.length, active: document.querySelector('.tab[aria-selected=true]')?.getAttribute('aria-label'), activePath: window.__reamsApp.workspace.getActiveFile()?.path, hasBase: Boolean(window.__reamsApp.vault.getFileByPath('Tasks.base')), leafPaths: (() => { const paths = []; window.__reamsApp.workspace.iterateRootLeaves(leaf => paths.push(leaf.file?.path)); return paths })(), })) if (beforeReload.files !== fixture.notes + fixture.images + 5 + Number(fixture.bases) || beforeReload.savedTabs !== fixture.tabs || beforeReload.activePath !== `notes/${created.active}.md` || beforeReload.hasBase !== fixture.bases) { throw new Error(`Fixture is incomplete: ${JSON.stringify(beforeReload)}`) }
// Throttle only the measured reload, not fixture creation. const cdp = await context.newCDPSession(page) if (cpuThrottle > 1) await cdp.send('Emulation.setCPUThrottlingRate', { rate: cpuThrottle }) const profiling = process.env.REAMS_BENCH_PROFILE === '1' if (profiling) { await cdp.send('Profiler.enable'); await cdp.send('Profiler.start') }
await page.addInitScript(() => { window.__mixedBoot = [] window.__mixedLongTasks = [] new PerformanceObserver(list => window.__mixedLongTasks.push(...list.getEntries().map(entry => ({ at: entry.startTime, ms: entry.duration })))).observe({ type: 'longtask', buffered: true }) window.__tabBoot = { opens: [], lookups: [] } if (window.location.search.includes('benchBreakdown=1')) { let app Object.defineProperty(window, 'app', { configurable: true, get: () => app, set: value => { app = value const workspace = value.workspace const originalOpenLeaf = workspace._openLeaf workspace._openLeaf = async function (leaf, file) { const started = performance.now() try { return await originalOpenLeaf.call(this, leaf, file) } finally { window.__tabBoot.opens.push({ path: file.path, ms: performance.now() - started }) } } const originalSetDocuments = value.setDocumentManager value.setDocumentManager = function (...args) { const result = originalSetDocuments.apply(this, args) const documents = value.reamsDocumentManager if (!documents.__benchWrapped) { documents.__benchWrapped = true const originalGetFile = documents.getFile documents.getFile = async function (...lookupArgs) { const started = performance.now() try { return await originalGetFile.apply(this, lookupArgs) } finally { window.__tabBoot.lookups.push(performance.now() - started) } } } return result } for (const [object, name] of [[value.vault, 'init'], [value.workspace, 'restoreLayout']]) { const original = object[name] object[name] = async function (...args) { window.__mixedBoot.push({ stage: `${name} start`, ms: performance.now() }) try { return await original.apply(this, args) } finally { window.__mixedBoot.push({ stage: `${name} end`, ms: performance.now() }) } } } const originalSetIndexer = value.setBackgroundIndexer value.setBackgroundIndexer = function (indexer) { if (indexer && !indexer.__benchWrapped) { indexer.__benchWrapped = true if (location.search.includes('benchPauseIndexer=1')) { const schedule = indexer.scheduleIndexing let resumed = false indexer.scheduleIndexing = function (...args) { if (resumed) return schedule.apply(this, args) } window.__benchResumeIndexer = () => { resumed = true schedule.call(indexer) } } const start = indexer.start indexer.start = async function (...args) { window.__mixedBoot.push({ stage: 'indexer start', ms: performance.now() }) try { return await start.apply(this, args) } finally { window.__mixedBoot.push({ stage: 'indexer end', ms: performance.now() }) } } } return originalSetIndexer.call(this, indexer) } }, }) } let previous = '' const timer = setInterval(() => { const stage = document.querySelector('[data-boot-stage]')?.textContent ?? (document.querySelector('.tab-bar') ? 'ready' : '') if (stage && stage !== previous) { window.__mixedBoot.push({ stage, ms: performance.now() }) previous = stage } if (stage === 'ready') clearInterval(timer) }, 10) }) if (process.env.REAMS_BENCH_BREAKDOWN === '1' || process.env.REAMS_BENCH_SKIP_FILE_STATS === '1' || process.env.REAMS_BENCH_PAUSE_INDEXER === '1') { const params = new URLSearchParams() if (process.env.REAMS_BENCH_BREAKDOWN === '1') params.set('benchBreakdown', '1') if (process.env.REAMS_BENCH_SKIP_FILE_STATS === '1') params.set('benchSkipFileStats', '1') if (process.env.REAMS_BENCH_PAUSE_INDEXER === '1') params.set('benchPauseIndexer', '1') await page.goto(`${page.url().split('?')[0].split('#')[0]}?${params}`) } else { await page.reload() } await page.waitForFunction(count => !document.querySelector('[data-boot-stage]') && document.querySelectorAll('.tab-bar .tab').length === count, fixture.tabs, { timeout: 180000 }) let hotFunctions = null let hotStacks = null if (profiling) { const { profile } = await cdp.send('Profiler.stop') const nodes = new Map(profile.nodes.map(node => [node.id, node.callFrame])) const parents = new Map(profile.nodes.flatMap(node => (node.children ?? []).map(child => [child, node.id]))) const counts = new Map() for (const id of profile.samples ?? []) counts.set(id, (counts.get(id) ?? 0) + 1) hotFunctions = [...counts].map(([id, samples]) => ({ samples, function: nodes.get(id)?.functionName, url: nodes.get(id)?.url, line: nodes.get(id)?.lineNumber, })).sort((a, b) => b.samples - a.samples).slice(0, 35) hotStacks = [...counts].sort((a, b) => b[1] - a[1]).filter(([id]) => nodes.get(id)?.url.includes('chunk-VWIVH32Q')).slice(0, 8) .map(([id, samples]) => { const stack = [] for (let current = id; current && stack.length < 15; current = parents.get(current)) { const frame = nodes.get(current) stack.push(`${frame?.functionName}@${frame?.url?.split('/').pop()}:${frame?.lineNumber}`) } return { samples, stack } }) } const startup = await page.evaluate(async productionBuild => { const typesEnabled = productionBuild ? true : (await import('/src/types-records/feature-setting.ts')).isTypesRecordsEnabled() const typed = window.__reamsApp.hasTypedNodeCatalog() ? window.__reamsApp.reamsTypedNodes.getSnapshot() : null return { stages: window.__mixedBoot, steps: performance.getEntriesByType('mark') .filter(mark => mark.name.startsWith('reams:boot:')) .map(mark => ({ step: mark.name.slice('reams:boot:'.length), ms: mark.startTime })), longTasks: [...window.__mixedLongTasks].sort((a, b) => b.ms - a.ms).slice(0, 12), tabOpens: { count: window.__tabBoot.opens.length, totalMs: window.__tabBoot.opens.reduce((sum, item) => sum + item.ms, 0), slowest: [...window.__tabBoot.opens].sort((a, b) => b.ms - a.ms).slice(0, 5), lookups: window.__tabBoot.lookups.length, lookupMs: window.__tabBoot.lookups.reduce((sum, ms) => sum + ms, 0), }, files: window.__reamsApp.vault.getFiles().length, hydratedStatsAtReady: window.__reamsApp.vault.getFiles().filter(file => file.stat.mtime > 0).length, tabs: document.querySelectorAll('.tab-bar .tab').length, active: document.querySelector('.tab[aria-selected=true]')?.getAttribute('aria-label'), activePath: window.__reamsApp.workspace.getActiveFile()?.path, activeView: document.querySelector('.main-area-leaf')?.getAttribute('data-reams-view'), editorEditable: document.querySelector('.main-area-leaf .cm-content')?.getAttribute('contenteditable'), editorHasContent: document.querySelector('.main-area-leaf .cm-content')?.textContent?.includes(`# ${window.__reamsApp.workspace.getActiveFile()?.basename}`) ?? false, leafViews: (() => { const views = []; window.__reamsApp.workspace.iterateRootLeaves(leaf => views.push({ path: leaf.file?.path, view: leaf.view?.getViewType() })); return views })(), deferredTabsAtReady: (() => { let count = 0; window.__reamsApp.workspace.iterateRootLeaves(leaf => { if (leaf.isDeferred) count++ }); return count })(), typesRecordsEnabled: typesEnabled, typedIndexStatusAtReady: typed?.status, typedRowsAtReady: typed?.rows.length, } }, production) if (process.env.REAMS_BENCH_PAUSE_INDEXER === '1') await page.evaluate(() => window.__benchResumeIndexer?.()) if (startup.files !== beforeReload.files || startup.tabs !== fixture.tabs || startup.activePath !== beforeReload.activePath || startup.activeView !== 'markdown' || startup.editorEditable !== 'true' || !startup.editorHasContent || JSON.stringify(startup.leafViews.map(leaf => leaf.path)) !== JSON.stringify(beforeReload.leafPaths) || (fixture.bases && !startup.leafViews.some(leaf => leaf.path === 'Tasks.base' && (leaf.view === 'reams-base' || leaf.view === 'reams-deferred-file')))) { throw new Error(`Startup did not restore the expected active tab: ${JSON.stringify(startup)}`) } let deferredSwitch = null if (process.env.REAMS_BENCH_CHECK_DEFERRED === '1') { const leaves = await page.evaluate(() => { const result = [] window.__reamsApp.workspace.iterateRootLeaves(leaf => result.push({ id: leaf.id, path: leaf.file?.path, deferred: leaf.isDeferred })) return result }) const target = leaves.find(leaf => leaf.path === 'notes/Note 010.md') const original = leaves.find(leaf => leaf.path === beforeReload.activePath) if (!target?.deferred || !original) throw new Error('Deferred switch fixture lost the target tab') await page.locator(`.tab[data-leaf-id="${target.id}"]`).click() await page.waitForFunction(() => window.__reamsApp.workspace.getActiveFile()?.path === 'notes/Note 010.md' && document.querySelector('.main-area-leaf .cm-content')?.textContent?.includes('# Note 010'), null, { timeout: 15000 }) await page.locator(`.tab[data-leaf-id="${original.id}"]`).click() await page.waitForFunction(path => window.__reamsApp.workspace.getActiveFile()?.path === path && document.querySelector('.main-area-leaf .cm-content')?.getAttribute('contenteditable') === 'true', beforeReload.activePath, { timeout: 15000 }) deferredSwitch = await page.evaluate(() => ({ ms: performance.now(), tabs: document.querySelectorAll('.tab-bar .tab').length })) if (deferredSwitch.tabs !== fixture.tabs) throw new Error('Switching a deferred tab duplicated or removed a tab') } let restoredTabs = null if (process.env.REAMS_BENCH_WAIT_TABS === '1') { await page.waitForFunction(() => { let deferred = 0 window.__reamsApp.workspace.iterateRootLeaves(leaf => { if (leaf.isDeferred || leaf.view?.getViewType() === 'reams-deferred-file') deferred++ }) return deferred === 0 }, null, { timeout: 180000 }) restoredTabs = await page.evaluate(() => ({ ms: performance.now(), views: (() => { const views = []; window.__reamsApp.workspace.iterateRootLeaves(leaf => views.push({ path: leaf.file?.path, view: leaf.view?.getViewType() })); return views })(), })) if (JSON.stringify(restoredTabs.views.map(leaf => leaf.path)) !== JSON.stringify(beforeReload.leafPaths) || !restoredTabs.views.some(leaf => leaf.path === 'Tasks.base' && leaf.view === 'reams-base') || restoredTabs.views.filter(leaf => leaf.path?.endsWith('.png') && leaf.view === 'image-viewer').length !== 4) { throw new Error(`Deferred views did not hydrate truthfully: ${JSON.stringify(restoredTabs)}`) } } let stats = null if (process.env.REAMS_BENCH_WAIT_STATS === '1') { let timedOut = false try { await page.waitForFunction(expected => window.__reamsApp.vault.getFiles() .filter(file => file.stat.mtime > 0).length === expected, startup.files, { timeout: Number(process.env.REAMS_BENCH_WAIT_STATS_MS ?? 120000) }) } catch { timedOut = true } stats = await page.evaluate(() => ({ ms: performance.now(), hydrated: window.__reamsApp.vault.getFiles().filter(file => file.stat.mtime > 0).length, queue: (() => { const index = window.__reamsApp.vault.fileIndex return { pending: index.pendingInitialStats.length, active: index.activeInitialStats, started: index.initialStatsStarted, next: index.pendingInitialStats[0]?.file?.path } })(), })) stats.timedOut = timedOut } let indexing = null if (process.env.REAMS_BENCH_WAIT_INDEX === '1') { let timedOut = false try { await page.waitForFunction(() => window.__reamsApp.reamsTypedNodes.getSnapshot().status === 'ready', null, { timeout: 120000 }) } catch { timedOut = true } indexing = await page.evaluate(() => ({ ms: performance.now(), status: window.__reamsApp.reamsTypedNodes.getSnapshot().status, rows: window.__reamsApp.reamsTypedNodes.getSnapshot().rows.length, metadataFiles: window.__reamsApp.metadataCache.getIndexedDocuments().length, fileStatsHydrated: window.__reamsApp.vault.getFiles().filter(file => file.stat.mtime > 0).length, work: window.__reamsApp.reamsBackgroundIndexer.getIndexingStatus(), title: window.__reamsApp.metadataCache.getDisplayTitle(window.__reamsApp.vault.getFileByPath('notes/Note 000.md')), links: window.__reamsApp.metadataCache.getFileCache(window.__reamsApp.vault.getFileByPath('notes/Note 000.md'))?.links?.length ?? 0, })) indexing.timedOut = timedOut } let sidebar = null if (process.env.REAMS_BENCH_CHECK_SIDEBAR === '1') { for (let i = 0; i < 3 && await page.locator('.app-shell-sidebar').getAttribute('data-hidden') !== 'false'; i++) { await page.locator('.header-toggle-btn').first().click() } const rows = page.locator('.quick-access-row') await rows.first().waitFor({ timeout: 15000 }) await page.waitForFunction(() => Boolean(document.querySelector('.quick-access-row .quick-access-preview')), null, { timeout: 15000 }) await rows.last().scrollIntoViewIfNeeded() await page.waitForFunction(() => Boolean(document.querySelector('.quick-access-row:last-child .quick-access-preview')), null, { timeout: 15000 }) sidebar = { rows: await rows.count(), first: await rows.first().locator('.quick-access-preview').innerText(), last: await rows.last().locator('.quick-access-preview').innerText() } } const repeatedReloads = [] const reloads = Number(process.env.REAMS_BENCH_RELOADS ?? 1) if (!Number.isInteger(reloads) || reloads < 1 || reloads > 10) throw new Error('REAMS_BENCH_RELOADS must be 1–10') for (let run = 1; run < reloads; run++) { await page.reload({ waitUntil: 'domcontentloaded' }) await page.waitForFunction(count => !document.querySelector('[data-boot-stage]') && document.querySelectorAll('.tab-bar .tab').length === count, fixture.tabs, { timeout: 180000 }) const sample = await page.evaluate(() => ({ firstReadyMs: performance.getEntriesByName('reams:boot:visible').at(-1)?.startTime ?? window.__mixedBoot.find(stage => stage.stage === 'ready')?.ms, visibleMs: performance.getEntriesByName('reams:boot:visible').at(-1)?.startTime, path: window.__reamsApp.workspace.getActiveFile()?.path, view: document.querySelector('.main-area-leaf')?.getAttribute('data-reams-view'), editable: document.querySelector('.main-area-leaf .cm-content')?.getAttribute('contenteditable'), content: document.querySelector('.main-area-leaf .cm-content')?.textContent?.includes(`# ${window.__reamsApp.workspace.getActiveFile()?.basename}`) ?? false, deferred: (() => { let count = 0; window.__reamsApp.workspace.iterateRootLeaves(leaf => { if (leaf.isDeferred) count++ }); return count })(), })) if (sample.path !== beforeReload.activePath || sample.view !== 'markdown' || sample.editable !== 'true' || !sample.content) throw new Error(`Repeat ${run} lost the active editor: ${JSON.stringify(sample)}`) repeatedReloads.push(sample) } console.log(JSON.stringify({ created, beforeReload, startup, deferredSwitch, repeatedReloads, restoredTabs, sidebar, hotFunctions, hotStacks, stats, indexing, mobile, cpuThrottle, production, probeSkippedFileStats: process.env.REAMS_BENCH_SKIP_FILE_STATS === '1', probePausedIndexer: process.env.REAMS_BENCH_PAUSE_INDEXER === '1' }, null, 2)) if (stats?.timedOut || indexing?.timedOut || (indexing && ( indexing.rows < fixture.typedNotes * 8 || indexing.metadataFiles < fixture.notes || indexing.title !== `Project ${String(fixture.notes).padStart(3, '0')}` || indexing.links < 3 ))) process.exitCode = 1} finally { await context.close() await browser.close()}