mirror of
https://github.com/NeuralNomadsAI/CodeNomad.git
synced 2026-10-26 16:36:02 +00:00
## Summary I reduced two independently demonstrated sources of redundant work in the UI shared by Electron and Tauri: - I reuse the timeline projection when the outline snapshot, resident marker identities and translated labels have not changed, preserving reactive locale updates and structural marker changes. - I discard superseded or disposed Markdown invocations before parsing and before publishing results, including same-key language retries and cache hits. Related to #851, **not a confirmed fix for its Linux Electron freeze**. I have not reproduced the reporter's environment, and this PR intentionally does not close the issue. Native events, transcript authority and OpenCode service lifecycle are unchanged. ## Evidence and validation - Real native-dispatcher/SessionView fixture with 10k outline entries: 64 sub-bucket deltas trigger **64 full-history projections before / zero after**; changed text buckets, locale and final text still update. - Real Markdown/parser instrumentation: cold burst **33 parses before / one after**, warm burst **32 before / one after**. Disposal, same-key retries, cached results, theme/highlight, wrap and full-source copy are covered. - 100 browser regressions passed (70 renderer/parser/outline + 6 full-source-copy + 24 history-navigation/reasoning), plus 3 outline unit tests; UI typecheck passed. - Independent gatekeeper: **zero actionable findings**, with 60 browser and 6 unit tests passed independently. - Windows Tauri `build --no-bundle`, packaged-resource smoke and isolated launch/capture/close passed. I did not replace the installed app or restart OpenCode. No Electron desktop build/test was run; Chromium fixture tests do not establish Linux Electron host behavior. - The initial concurrent history/reasoning regression run hit a shared Vite cache `EPERM` on Windows and timed out; all 24 affected checks passed when rerun sequentially. The broad synthetic stream profile still shows pauses and does **not** demonstrate an overall responsiveness win. I recorded the measured work reductions and remaining limits in `dev-docs/STREAMING_RESPONSIVENESS.md`, with reproducible profiling scripts.
89 lines
5.4 KiB
JavaScript
89 lines
5.4 KiB
JavaScript
// Synthetic real-renderer workload; not a reproduction of the reporter's host.
|
|
import { mkdir, mkdtemp, writeFile } from "node:fs/promises"
|
|
import path from "node:path"
|
|
import os from "node:os"
|
|
import { fileURLToPath } from "node:url"
|
|
import { chromium } from "playwright"
|
|
import { createServer } from "vite"
|
|
import solid from "vite-plugin-solid"
|
|
|
|
const repo = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
|
|
const args = Object.fromEntries(process.argv.slice(2).map(arg => arg.replace(/^--/, "").split("=")))
|
|
const chunkSize = Number(args.chunk ?? 1024), deliveriesCount = Number(args.deliveries ?? 96)
|
|
const temporaryParent = path.join(os.tmpdir(), "opencode")
|
|
await mkdir(temporaryParent, { recursive: true })
|
|
const evidence = await mkdtemp(path.join(temporaryParent, "issue851-profile-"))
|
|
const server = await createServer({
|
|
configFile: false, root: path.join(repo, "packages/ui"), logLevel: "error",
|
|
cacheDir: "node_modules/.vite-streaming-profile", plugins: [solid(), {
|
|
name: "streaming-profile", configureServer(server) {
|
|
server.middlewares.use("/fixture", async (_req, res) => {
|
|
res.setHeader("Content-Type", "text/html")
|
|
res.end(await server.transformIndexHtml("/fixture", '<html><body><div id="root" style="display:flex;height:850px;width:1200px"></div><script type="module" src="/tests/browser/fixtures/streaming-motion.tsx"></script></body></html>'))
|
|
})
|
|
},
|
|
}], resolve: { dedupe: ["solid-js"] }, optimizeDeps: { exclude: ["lucide-solid"] },
|
|
server: { host: "127.0.0.1", port: 0, hmr: false, watch: null },
|
|
})
|
|
let browser
|
|
const results = []
|
|
try {
|
|
await server.listen()
|
|
browser = await chromium.launch({ executablePath: process.env.CODENOMAD_BROWSER_PATH || undefined })
|
|
const url = `http://127.0.0.1:${server.httpServer.address().port}/fixture`
|
|
for (const kind of args.kind ? [args.kind] : ["prose", "code", "burst"]) {
|
|
const page = await browser.newPage({ viewport: { width: 1200, height: 850 } })
|
|
const errors = []
|
|
page.on("pageerror", error => errors.push(error.message))
|
|
page.setDefaultTimeout(60000)
|
|
await page.route("**/api/**", route => route.fulfill({ contentType: route.request().url().includes("events") ? "text/event-stream" : "application/json", body: "" }))
|
|
await page.goto(url, { waitUntil: "domcontentloaded" })
|
|
await page.waitForFunction(() => Boolean(window.motionFixture))
|
|
await page.evaluate(async () => { await window.motionFixture.seed(); window.motionFixture.start(); window.motionFixture.text() })
|
|
await page.waitForTimeout(300)
|
|
const cdp = await page.context().newCDPSession(page)
|
|
await cdp.send("Profiler.enable")
|
|
await cdp.send("Profiler.start")
|
|
const measurements = await page.evaluate(async ({ kind, chunkSize, deliveriesCount }) => {
|
|
const tasks = [], gaps = [], deliveries = []
|
|
const observer = new PerformanceObserver(list => { for (const entry of list.getEntries()) tasks.push(entry.duration) })
|
|
observer.observe({ type: "longtask", buffered: false })
|
|
let previous = performance.now()
|
|
const timer = setInterval(() => { const now = performance.now(); gaps.push(now - previous); previous = now }, 10)
|
|
let text = kind === "prose" ? "" : "```typescript\n"
|
|
const line = kind === "prose" ? "A streamed paragraph with **important details** and a [reference](https://example.com).\n\n" : 'export const example = { name: "sample", enabled: true, count: 123 };\n'
|
|
const chunk = line.repeat(Math.ceil(chunkSize / line.length))
|
|
const started = performance.now()
|
|
for (let i = 0; i < deliveriesCount; i++) {
|
|
const delta = (i === 0 ? text : "") + chunk
|
|
text += chunk
|
|
const before = performance.now()
|
|
window.motionFixture.delta(delta)
|
|
deliveries.push(performance.now() - before)
|
|
if (kind !== "burst") await new Promise(resolve => setTimeout(resolve, 20))
|
|
else if (i % 16 === 15) await new Promise(resolve => setTimeout(resolve, 0))
|
|
}
|
|
await new Promise(resolve => setTimeout(resolve, 250))
|
|
const streamingMs = performance.now() - started
|
|
window.motionFixture.finish(text)
|
|
await new Promise(resolve => setTimeout(resolve, 250))
|
|
clearInterval(timer)
|
|
observer.disconnect()
|
|
return { characters: text.length, streamingMs, longestDeliveryMs: Math.max(...deliveries), longTasks: tasks.length,
|
|
totalLongTaskMs: tasks.reduce((sum, value) => sum + value, 0), longestTaskMs: Math.max(0, ...tasks),
|
|
longestHeartbeatGapMs: Math.max(0, ...gaps),
|
|
mountedRows: document.querySelectorAll("[data-virtual-follow-key]").length }
|
|
}, { kind, chunkSize, deliveriesCount })
|
|
const { profile } = await cdp.send("Profiler.stop")
|
|
const counts = new Map()
|
|
for (const id of profile.samples ?? []) counts.set(id, (counts.get(id) ?? 0) + 1)
|
|
const top = profile.nodes.map(node => ({ function: node.callFrame.functionName, url: node.callFrame.url.replace(url, "fixture"),
|
|
line: node.callFrame.lineNumber + 1, samples: counts.get(node.id) ?? 0 })).sort((a, b) => b.samples - a.samples).slice(0, 20)
|
|
await writeFile(path.join(evidence, `${kind}.cpuprofile`), JSON.stringify(profile))
|
|
await page.screenshot({ path: path.join(evidence, `${kind}.png`) })
|
|
results.push({ kind, ...measurements, errors, top })
|
|
await page.close()
|
|
}
|
|
await writeFile(path.join(evidence, "results.json"), JSON.stringify(results, null, 2))
|
|
console.log(JSON.stringify({ evidence, results }, null, 2))
|
|
} finally { await browser?.close(); await server.close() }
|