From ee18c9976e00ebf0162fe1dcc47b209e0507b3e5 Mon Sep 17 00:00:00 2001
From: Adam <2363879+adamdotdevin@users.noreply.github.com>
Date: Mon, 9 Mar 2026 15:57:19 -0500
Subject: chore(app): dev stats
---
packages/app/src/components/debug-bar.tsx | 432 ++++++++++++++++++++++++++++++
1 file changed, 432 insertions(+)
create mode 100644 packages/app/src/components/debug-bar.tsx
(limited to 'packages/app/src/components')
diff --git a/packages/app/src/components/debug-bar.tsx b/packages/app/src/components/debug-bar.tsx
new file mode 100644
index 000000000..93a4654f8
--- /dev/null
+++ b/packages/app/src/components/debug-bar.tsx
@@ -0,0 +1,432 @@
+import { useIsRouting, useLocation } from "@solidjs/router"
+import { batch, createEffect, onCleanup, onMount } from "solid-js"
+import { createStore } from "solid-js/store"
+import { Tooltip } from "@opencode-ai/ui/tooltip"
+
+type Mem = Performance & {
+ memory?: {
+ usedJSHeapSize: number
+ jsHeapSizeLimit: number
+ }
+}
+
+type Evt = PerformanceEntry & {
+ interactionId?: number
+ processingStart?: number
+}
+
+type Shift = PerformanceEntry & {
+ hadRecentInput: boolean
+ value: number
+}
+
+type Obs = PerformanceObserverInit & {
+ durationThreshold?: number
+}
+
+const span = 5000
+
+const ms = (n?: number, d = 0) => {
+ if (n === undefined || Number.isNaN(n)) return "n/a"
+ return `${n.toFixed(d)}ms`
+}
+
+const time = (n?: number) => {
+ if (n === undefined || Number.isNaN(n)) return "n/a"
+ return `${Math.round(n)}`
+}
+
+const mb = (n?: number) => {
+ if (n === undefined || Number.isNaN(n)) return "n/a"
+ const v = n / 1024 / 1024
+ return `${v >= 1024 ? v.toFixed(0) : v.toFixed(1)}MB`
+}
+
+const bad = (n: number | undefined, limit: number, low = false) => {
+ if (n === undefined || Number.isNaN(n)) return false
+ return low ? n < limit : n > limit
+}
+
+const session = (path: string) => path.includes("/session")
+
+function Cell(props: { bad?: boolean; dim?: boolean; label: string; tip: string; value: string }) {
+ return (
+
+
+
{props.label}
+
+ {props.value}
+
+
+
+ )
+}
+
+export function DebugBar() {
+ const location = useLocation()
+ const routing = useIsRouting()
+ const [state, setState] = createStore({
+ cls: undefined as number | undefined,
+ delay: undefined as number | undefined,
+ fps: undefined as number | undefined,
+ gap: undefined as number | undefined,
+ heap: {
+ limit: undefined as number | undefined,
+ used: undefined as number | undefined,
+ },
+ inp: undefined as number | undefined,
+ jank: undefined as number | undefined,
+ long: {
+ block: undefined as number | undefined,
+ count: undefined as number | undefined,
+ max: undefined as number | undefined,
+ },
+ nav: {
+ dur: undefined as number | undefined,
+ pending: false,
+ },
+ })
+
+ const heap = () => (state.heap.limit ? (state.heap.used ?? 0) / state.heap.limit : undefined)
+ const heapv = () => {
+ const value = heap()
+ if (value === undefined) return "n/a"
+ return `${Math.round(value * 100)}%`
+ }
+ const longv = () => (state.long.count === undefined ? "n/a" : `${time(state.long.block)}/${state.long.count}`)
+ const navv = () => (state.nav.pending ? "..." : time(state.nav.dur))
+
+ let prev = ""
+ let start = 0
+ let init = false
+ let one = 0
+ let two = 0
+
+ createEffect(() => {
+ const busy = routing()
+ const next = `${location.pathname}${location.search}`
+
+ if (!init) {
+ init = true
+ prev = next
+ return
+ }
+
+ if (busy) {
+ if (one !== 0) cancelAnimationFrame(one)
+ if (two !== 0) cancelAnimationFrame(two)
+ one = 0
+ two = 0
+ if (start !== 0) return
+ start = performance.now()
+ if (session(prev)) setState("nav", { dur: undefined, pending: true })
+ return
+ }
+
+ if (start === 0) {
+ prev = next
+ return
+ }
+
+ const at = start
+ const from = prev
+ start = 0
+ prev = next
+
+ if (!(session(from) || session(next))) return
+
+ if (one !== 0) cancelAnimationFrame(one)
+ if (two !== 0) cancelAnimationFrame(two)
+ one = requestAnimationFrame(() => {
+ one = 0
+ two = requestAnimationFrame(() => {
+ two = 0
+ setState("nav", { dur: performance.now() - at, pending: false })
+ })
+ })
+ })
+
+ onMount(() => {
+ const obs: PerformanceObserver[] = []
+ const fps: Array<{ at: number; dur: number }> = []
+ const long: Array<{ at: number; dur: number }> = []
+ const seen = new Map()
+ let hasLong = false
+ let poll: number | undefined
+ let raf = 0
+ let last = 0
+ let snap = 0
+
+ const trim = (list: Array<{ at: number; dur: number }>, span: number, at: number) => {
+ while (list[0] && at - list[0].at > span) list.shift()
+ }
+
+ const syncFrame = (at: number) => {
+ trim(fps, span, at)
+ const total = fps.reduce((sum, entry) => sum + entry.dur, 0)
+ const gap = fps.reduce((max, entry) => Math.max(max, entry.dur), 0)
+ const jank = fps.filter((entry) => entry.dur > 32).length
+ batch(() => {
+ setState("fps", total > 0 ? (fps.length * 1000) / total : undefined)
+ setState("gap", gap > 0 ? gap : undefined)
+ setState("jank", jank)
+ })
+ }
+
+ const syncLong = (at = performance.now()) => {
+ if (!hasLong) return
+ trim(long, span, at)
+ const block = long.reduce((sum, entry) => sum + Math.max(0, entry.dur - 50), 0)
+ const max = long.reduce((hi, entry) => Math.max(hi, entry.dur), 0)
+ setState("long", { block, count: long.length, max })
+ }
+
+ const syncInp = (at = performance.now()) => {
+ for (const [key, entry] of seen) {
+ if (at - entry.at > span) seen.delete(key)
+ }
+ let delay = 0
+ let inp = 0
+ for (const entry of seen.values()) {
+ delay = Math.max(delay, entry.delay)
+ inp = Math.max(inp, entry.dur)
+ }
+ batch(() => {
+ setState("delay", delay > 0 ? delay : undefined)
+ setState("inp", inp > 0 ? inp : undefined)
+ })
+ }
+
+ const syncHeap = () => {
+ const mem = (performance as Mem).memory
+ if (!mem) return
+ setState("heap", { limit: mem.jsHeapSizeLimit, used: mem.usedJSHeapSize })
+ }
+
+ const reset = () => {
+ fps.length = 0
+ long.length = 0
+ seen.clear()
+ last = 0
+ snap = 0
+ batch(() => {
+ setState("fps", undefined)
+ setState("gap", undefined)
+ setState("jank", undefined)
+ setState("delay", undefined)
+ setState("inp", undefined)
+ if (hasLong) setState("long", { block: 0, count: 0, max: 0 })
+ })
+ }
+
+ const watch = (type: string, init: Obs, fn: (entries: PerformanceEntry[]) => void) => {
+ if (typeof PerformanceObserver === "undefined") return false
+ if (!(PerformanceObserver.supportedEntryTypes ?? []).includes(type)) return false
+ const ob = new PerformanceObserver((list) => fn(list.getEntries()))
+ try {
+ ob.observe(init)
+ obs.push(ob)
+ return true
+ } catch {
+ ob.disconnect()
+ return false
+ }
+ }
+
+ if (
+ watch("layout-shift", { buffered: true, type: "layout-shift" }, (entries) => {
+ const add = entries.reduce((sum, entry) => {
+ const item = entry as Shift
+ if (item.hadRecentInput) return sum
+ return sum + item.value
+ }, 0)
+ if (add === 0) return
+ setState("cls", (value) => (value ?? 0) + add)
+ })
+ ) {
+ setState("cls", 0)
+ }
+
+ if (
+ watch("longtask", { buffered: true, type: "longtask" }, (entries) => {
+ const at = performance.now()
+ long.push(...entries.map((entry) => ({ at: entry.startTime, dur: entry.duration })))
+ syncLong(at)
+ })
+ ) {
+ hasLong = true
+ setState("long", { block: 0, count: 0, max: 0 })
+ }
+
+ watch("event", { buffered: true, durationThreshold: 16, type: "event" }, (entries) => {
+ for (const raw of entries) {
+ const entry = raw as Evt
+ if (entry.duration < 16) continue
+ const key =
+ entry.interactionId && entry.interactionId > 0
+ ? entry.interactionId
+ : `${entry.name}:${Math.round(entry.startTime)}`
+ const prev = seen.get(key)
+ const delay = Math.max(0, (entry.processingStart ?? entry.startTime) - entry.startTime)
+ seen.set(key, {
+ at: entry.startTime,
+ delay: Math.max(prev?.delay ?? 0, delay),
+ dur: Math.max(prev?.dur ?? 0, entry.duration),
+ })
+ if (seen.size <= 200) continue
+ const first = seen.keys().next().value
+ if (first !== undefined) seen.delete(first)
+ }
+ syncInp()
+ })
+
+ const loop = (at: number) => {
+ if (document.visibilityState !== "visible") {
+ raf = 0
+ return
+ }
+
+ if (last === 0) {
+ last = at
+ raf = requestAnimationFrame(loop)
+ return
+ }
+
+ fps.push({ at, dur: at - last })
+ last = at
+
+ if (at - snap >= 250) {
+ snap = at
+ syncFrame(at)
+ }
+
+ raf = requestAnimationFrame(loop)
+ }
+
+ const stop = () => {
+ if (raf !== 0) cancelAnimationFrame(raf)
+ raf = 0
+ if (poll === undefined) return
+ clearInterval(poll)
+ poll = undefined
+ }
+
+ const start = () => {
+ if (document.visibilityState !== "visible") return
+ if (poll === undefined) {
+ poll = window.setInterval(() => {
+ syncLong()
+ syncInp()
+ syncHeap()
+ }, 1000)
+ }
+ if (raf !== 0) return
+ raf = requestAnimationFrame(loop)
+ }
+
+ const vis = () => {
+ if (document.visibilityState !== "visible") {
+ stop()
+ return
+ }
+ reset()
+ start()
+ }
+
+ syncHeap()
+ start()
+ document.addEventListener("visibilitychange", vis)
+
+ onCleanup(() => {
+ if (one !== 0) cancelAnimationFrame(one)
+ if (two !== 0) cancelAnimationFrame(two)
+ stop()
+ document.removeEventListener("visibilitychange", vis)
+ for (const ob of obs) ob.disconnect()
+ })
+ })
+
+ return (
+
+ )
+}
--
cgit v1.2.3