From efe76378c2133a06a86a63addfa46d8ec64f72c0 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Tue, 28 Jul 2026 20:04:21 -0500 Subject: [PATCH] chore: full overlay render-churn measurement probe --- apps/desktop/scripts/diag-overlay-full.mjs | 201 +++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 apps/desktop/scripts/diag-overlay-full.mjs diff --git a/apps/desktop/scripts/diag-overlay-full.mjs b/apps/desktop/scripts/diag-overlay-full.mjs new file mode 100644 index 000000000000..c851faa96439 --- /dev/null +++ b/apps/desktop/scripts/diag-overlay-full.mjs @@ -0,0 +1,201 @@ +#!/usr/bin/env node +// Comprehensive overlay render-churn measurement on the live instance. +// Measures: cmdk root + submenus, settings, command center, capabilities, system overlays. +// Read-only — never closes the app, only navigates and clicks. +import WebSocket from 'ws' + +const WS_URL = 'ws://127.0.0.1:9222/devtools/page/6E095DBE024BD280C674D00023C01201' +let msgId = 1 + +function send(ws, method, params = {}) { + const id = msgId++ + return new Promise((resolve, reject) => { + const handler = (data) => { + const msg = JSON.parse(data.toString()) + if (msg.id === id) { + ws.off('message', handler) + msg.error ? reject(new Error(JSON.stringify(msg.error))) : resolve(msg.result) + } + } + ws.on('message', handler) + ws.send(JSON.stringify({ id, method, params })) + }) +} + +async function eval_(ws, expr) { + const r = await send(ws, 'Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }) + return r?.result?.value +} + +async function sleep(ms) { return new Promise(r => setTimeout(r, ms)) } + +async function measure(ws, label, fn, settleMs = 400) { + await eval_(ws, `__RENDER_COUNTS__.start(); true`) + await fn(ws) + await sleep(settleMs) + const report = await eval_(ws, `JSON.stringify(__RENDER_COUNTS__.report())`) + const parsed = JSON.parse(report) + const totalRenders = parsed.reduce((s, c) => s + c.renders, 0) + const totalWasted = parsed.reduce((s, c) => s + c.wasted, 0) + const totalMs = parsed.reduce((s, c) => s + c.totalMs, 0) + const top5 = parsed.filter(c => c.wasted > 0).sort((a,b) => b.wasted - a.wasted).slice(0, 5) + console.log(`\n${label}`) + console.log(` total: ${totalRenders} renders, ${totalWasted} wasted, ${totalMs.toFixed(1)}ms`) + if (top5.length > 0) { + for (const c of top5) { + console.log(` ${c.name}: ${c.renders} renders, ${c.wasted} wasted, ${c.totalMs.toFixed(1)}ms`) + } + } else { + console.log(` (no wasted renders)`) + } + return { label, totalRenders, totalWasted, totalMs, top: top5 } +} + +async function clickByText(ws, text, tag = 'button') { + return eval_(ws, ` + const el = Array.from(document.querySelectorAll('${tag}')).find(b => b.textContent?.trim() === '${text}') + if (el) { el.click(); true } else false + `) +} + +async function clickByHref(ws, partial) { + return eval_(ws, ` + const el = Array.from(document.querySelectorAll('a,button')).find(e => e.getAttribute('href')?.includes('${partial}')) + if (el) { el.click(); true } else false + `) +} + +async function typeInInput(ws, text) { + for (const ch of text) { + await eval_(ws, ` + const el = document.querySelector('[cmdk-input]') || document.querySelector('input[type="text"]') || document.querySelector('input') + if (el) { el.focus(); el.value = el.value + '${ch}'; el.dispatchEvent(new Event('input', {bubbles:true})) } + true + `) + await sleep(50) + } +} + +async function pressKey(ws, key, mods = {}) { + await eval_(ws, ` + document.dispatchEvent(new KeyboardEvent('keydown', { key: '${key}', ${Object.entries(mods).map(([k,v]) => `${k}:${v}`).join(', ')}, bubbles: true })) + true + `) +} + +async function main() { + const ws = new WebSocket(WS_URL) + await new Promise((resolve, reject) => { + ws.on('open', resolve) + ws.on('error', reject) + }) + await send(ws, 'Runtime.enable') + + const avail = await eval_(ws, `typeof __RENDER_COUNTS__`) + console.log(`__RENDER_COUNTS__: ${avail}`) + if (avail !== 'object') { + console.error('Render counter not loaded. Reload the page.') + process.exit(1) + } + + const results = [] + + // === 1. CmdK root: open, type "setting", close === + results.push(await measure(ws, '1. CmdK root (type "setting")', async (ws) => { + await pressKey(ws, 'k', { metaKey: true }) + await sleep(300) + await typeInInput(ws, 'setting') + })) + await pressKey(ws, 'Escape') + await sleep(300) + + // === 2. CmdK submenu: open, navigate to theme picker, click a theme, back === + results.push(await measure(ws, '2. CmdK theme submenu', async (ws) => { + await pressKey(ws, 'k', { metaKey: true }) + await sleep(300) + await typeInInput(ws, 'theme') + await sleep(200) + // Click first theme item + await eval_(ws, ` + const item = document.querySelector('[cmdk-item]') + if (item) item.click() + true + `) + await sleep(300) + })) + await pressKey(ws, 'Escape') + await sleep(300) + + // === 3. CmdK color-mode submenu === + results.push(await measure(ws, '3. CmdK color-mode submenu', async (ws) => { + await pressKey(ws, 'k', { metaKey: true }) + await sleep(300) + await typeInInput(ws, 'color mode') + await sleep(200) + await eval_(ws, `const item = document.querySelector('[cmdk-item]'); if (item) item.click(); true`) + await sleep(300) + })) + await pressKey(ws, 'Escape') + await sleep(300) + + // === 4. Settings: open, click through nav items === + results.push(await measure(ws, '4. Settings (5 nav clicks)', async (ws) => { + await clickByHref(ws, 'settings') + await sleep(500) + for (const label of ['Appearance', 'Gateway', 'Keys', 'Notifications', 'About']) { + await clickByText(ws, label) + await sleep(100) + } + })) + + // === 5. Command Center: open to System tab, idle 2s === + results.push(await measure(ws, '5. Command Center (System tab, 2s idle)', async (ws) => { + await clickByHref(ws, 'command-center') + await sleep(500) + await clickByText(ws, 'System') + await sleep(2000) + }, 100)) + + // === 6. Command Center: Sessions tab === + results.push(await measure(ws, '6. Command Center (Sessions tab, 1s)', async (ws) => { + await clickByText(ws, 'Sessions') + await sleep(1000) + }, 100)) + + // === 7. Capabilities/Skills page === + results.push(await measure(ws, '7. Capabilities (Skills tab, 1s)', async (ws) => { + await clickByHref(ws, 'skills') + await sleep(800) + // Click Skills tab if not already + await clickByText(ws, 'Skills') + await sleep(1000) + }, 100)) + + // === 8. Capabilities Toolsets tab === + results.push(await measure(ws, '8. Capabilities (Toolsets tab, 1s)', async (ws) => { + await clickByText(ws, 'Tools') + await sleep(1000) + }, 100)) + + // === 9. Capabilities MCP tab === + results.push(await measure(ws, '9. Capabilities (MCP tab, 1s)', async (ws) => { + await clickByText(ws, 'MCP') + await sleep(1000) + }, 100)) + + // Navigate back to chat + await eval_(ws, `window.location.hash = '#/'; true`) + await sleep(300) + + // Summary table + console.log('\n\n=== SUMMARY ===') + console.log('Surface'.padEnd(45) + 'Renders'.padStart(10) + 'Wasted'.padStart(10) + 'ms'.padStart(10)) + console.log('-'.repeat(75)) + for (const r of results) { + console.log(r.label.padEnd(45) + String(r.totalRenders).padStart(10) + String(r.totalWasted).padStart(10) + r.totalMs.toFixed(1).padStart(10)) + } + + ws.close() +} + +main().catch(err => { console.error(err); process.exit(1) })