hermes-agent/apps/desktop/scripts/diag-overlay-churn.mjs
2026-07-28 19:03:17 -05:00

161 lines
5.4 KiB
JavaScript

#!/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)
})