diff --git a/apps/desktop/scripts/diag-overlay-churn.mjs b/apps/desktop/scripts/diag-overlay-churn.mjs new file mode 100644 index 00000000000..41a2379093f --- /dev/null +++ b/apps/desktop/scripts/diag-overlay-churn.mjs @@ -0,0 +1,161 @@ +#!/usr/bin/env node +// CDP probe: measure render churn on overlay surfaces (cmdk, settings, command-center, skills) +// against the user's live instance on :9222. Read-only — never closes or navigates away. +import WebSocket from 'ws' + +const CDP_URL = 'ws://127.0.0.1:9222' +const TARGET_GLOB = '/devtools/page/' + +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) + if (msg.error) reject(new Error(JSON.stringify(msg.error))) + else resolve(msg.result) + } + } + ws.on('message', handler) + ws.send(JSON.stringify({ id, method, params })) + }) +} + +async function getRendererTarget() { + const resp = await fetch('http://127.0.0.1:9222/json/list') + const targets = await resp.json() + // Find the renderer target (not devtools) + return targets.find(t => t.url?.startsWith('http://127.0.0.1:5174') || t.url?.includes('5174')) + ?? targets.find(t => t.type === 'page' && !t.url.startsWith('devtools')) +} + +async function evalInPage(ws, expr) { + const result = await send(ws, 'Runtime.evaluate', { + expression: expr, + returnByValue: true, + awaitPromise: true, + }) + return result?.result?.value +} + +async function main() { + const target = await getRendererTarget() + if (!target) { + console.error('No renderer target found') + process.exit(1) + } + console.log(`Target: ${target.url}`) + + const ws = new WebSocket(target.webSocketDebuggerUrl) + await new Promise((resolve, reject) => { + ws.on('open', resolve) + ws.on('error', reject) + }) + + await send(ws, 'Runtime.enable') + + // Check if __RENDER_COUNTS__ is available + const hasCounter = await evalInPage(ws, 'typeof __RENDER_COUNTS__') + console.log(`__RENDER_COUNTS__ available: ${hasCounter}`) + + if (hasCounter !== 'object') { + console.log('Render counter not loaded — checking perf-live...') + const hasPerfLive = await evalInPage(ws, 'typeof __PERF_LIVE__') + console.log(`__PERF_LIVE__ available: ${hasPerfLive}`) + } + + // 1. Baseline: open cmdk, type a few chars, measure + console.log('\n=== CmdK Palette ===') + await evalInPage(ws, ` + window.__renderBaseline = {} + if (typeof __RENDER_COUNTS__ === 'object' && __RENDER_COUNTS__) { + __RENDER_COUNTS__.reset() + } + // Open cmdk via keyboard shortcut + const evt = new KeyboardEvent('keydown', { key: 'k', metaKey: true, bubbles: true }) + document.dispatchEvent(evt) + true + `) + await new Promise(r => setTimeout(r, 500)) + + // Type some characters + for (const ch of ['s', 'e', 't', 't', 'i', 'n', 'g']) { + await evalInPage(ws, ` + const el = document.querySelector('[cmdk-input]') || document.querySelector('input[placeholder]') + if (el) { + el.focus() + el.value = el.value + '${ch}' + el.dispatchEvent(new Event('input', { bubbles: true })) + } + true + `) + await new Promise(r => setTimeout(r, 80)) + } + + await new Promise(r => setTimeout(r, 300)) + const cmdkCounts = await evalInPage(ws, ` + __RENDER_COUNTS__?.snapshot?.() ?? __RENDER_COUNTS__?.counts ?? 'no counter' + `) + console.log('CmdK render counts after typing "setting":', JSON.stringify(cmdkCounts, null, 2)) + + // Close cmdk + await evalInPage(ws, `new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }); document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })); true`) + await new Promise(r => setTimeout(r, 300)) + + // 2. Command Center — measure renders while on System tab + console.log('\n=== Command Center (System tab) ===') + await evalInPage(ws, ` + if (typeof __RENDER_COUNTS__ === 'object' && __RENDER_COUNTS__) { + __RENDER_COUNTS__.reset() + } + // Navigate to command center system section + const link = Array.from(document.querySelectorAll('a, button')).find(el => el.textContent?.includes('System') || el.textContent?.includes('Command Center')) + if (link) link.click() + true + `) + await new Promise(r => setTimeout(r, 1000)) + + const ccCounts = await evalInPage(ws, ` + __RENDER_COUNTS__?.snapshot?.() ?? __RENDER_COUNTS__?.counts ?? 'no counter' + `) + console.log('Command Center render counts (after 1s on System tab):', JSON.stringify(ccCounts, null, 2)) + + // 3. Settings page — measure renders on nav + console.log('\n=== Settings Page ===') + await evalInPage(ws, ` + if (typeof __RENDER_COUNTS__ === 'object' && __RENDER_COUNTS__) { + __RENDER_COUNTS__.reset() + } + // Navigate to settings + const link = Array.from(document.querySelectorAll('a, button')).find(el => el.textContent?.includes('Settings')) + if (link) link.click() + true + `) + await new Promise(r => setTimeout(r, 1000)) + + // Click through a few nav items + for (const label of ['Appearance', 'Gateway', 'Keys', 'About']) { + await evalInPage(ws, ` + const el = Array.from(document.querySelectorAll('button')).find(b => b.textContent?.trim() === '${label}') + if (el) el.click() + true + `) + await new Promise(r => setTimeout(r, 150)) + } + + const settingsCounts = await evalInPage(ws, ` + __RENDER_COUNTS__?.snapshot?.() ?? __RENDER_COUNTS__?.counts ?? 'no counter' + `) + console.log('Settings render counts (after clicking 4 nav items):', JSON.stringify(settingsCounts, null, 2)) + + ws.close() + console.log('\nDone.') +} + +main().catch(err => { + console.error('Error:', err) + process.exit(1) +})