perf(web): instant dashboard load, default to today, fast-fail offline peers (#573)
Some checks failed
CI / semgrep (push) Has been cancelled

The web server is long-lived, so cache what the per-invocation CLI cannot:

- Cache the parsed local payload in-memory (single-flight, 180s TTL matched to
  the parser session cache, expired entries pruned on write). /api/usage and the
  local half of /api/devices now return from a Map hit after the first parse.
- Prewarm today at startup and inline that payload into index.html as a
  bootstrap, so the SPA paints today's numbers with no round-trip. Only the
  local device is embedded; '<' is escaped so a name cannot break the script
  tag; served no-store; the seeded view refetches at once so live peers appear.
- Default the web command and dashboard to today.
- Cap the peer connect phase at 3s. req.setTimeout does not abort a stalled TCP
  connect, so an offline paired device hung ~75s on the OS timeout; it now
  degrades to an unreachable row in ~3s. The cap clears on TCP connect, so the
  TLS handshake and the 65s pairing-approval wait are unaffected.

Measured: /api/usage 0.0007s warm (was ~0.22s), /api/devices ~3s with an offline
peer (was ~75s), first paint instant.
This commit is contained in:
Resham Joshi 2026-06-29 04:21:36 +02:00 committed by GitHub
parent 805d2ffa49
commit 22d5fc1743
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 94 additions and 23 deletions

View file

@ -342,7 +342,7 @@ function CombinedView({ devices, unit }: { devices: DeviceUsage[]; unit: Unit })
}
export function App() {
const [period, setPeriod] = useState<Period>('month')
const [period, setPeriod] = useState<Period>('today')
const [provider, setProvider] = useState('all')
const [view, setView] = useState<string>('all')
const [unit, setUnit] = useState<Unit>('cost')
@ -354,6 +354,10 @@ export function App() {
const { data, isError, error, refetch } = useQuery({
queryKey: ['devices', period, provider],
queryFn: () => fetchDevices(period, provider),
initialData: () => (period === 'today' && provider === 'all' ? window.__CODEBURN_BOOTSTRAP__ : undefined),
// Bootstrap paints instantly but is stale by definition, so refetch at once
// (the default 30s staleTime would otherwise hide a live peer until then).
initialDataUpdatedAt: 0,
// When devices are paired, re-pull periodically so a device that briefly
// dropped (asleep/network blip) reappears on its own instead of staying
// gone until you switch tabs.

View file

@ -63,6 +63,12 @@ export type DeviceUsage = {
error?: string
}
declare global {
interface Window {
__CODEBURN_BOOTSTRAP__?: { devices: DeviceUsage[] }
}
}
// A device may run a different CodeBurn version and send a payload missing
// fields we treat as required. Fill safe defaults at the boundary so the UI
// can iterate them without crashing (the alternative is a white screen for an

View file

@ -614,7 +614,7 @@ program
program
.command('web')
.description('Open the local web dashboard in your browser')
.option('-p, --period <period>', 'Initial period: today, week, 30days, month, all', 'month')
.option('-p, --period <period>', 'Initial period: today, week, 30days, month, all', 'today')
.option('--from <date>', 'Start date (YYYY-MM-DD)')
.option('--to <date>', 'End date (YYYY-MM-DD)')
.option('--provider <provider>', 'Filter by provider (e.g. claude, codex, copilot)', 'all')

View file

@ -5,6 +5,13 @@ import { certFingerprint } from './pairing.js'
import type { Identity } from './identity.js'
import type { UsageQuery } from './share-server.js'
// req.setTimeout only arms once connected; it does not abort a stalled TCP
// connect, so an unreachable peer would otherwise ride the OS connect timeout
// (~75s on macOS). Cap the TCP-connect phase separately; it clears the instant
// the socket connects, so the TLS handshake and the read/approval timeouts
// (req.setTimeout) are unaffected even over a slow VPN link.
const CONNECT_TIMEOUT_MS = 3000
export type PeerEndpoint = {
identity: Identity // our own identity (we present our cert so the peer can bind a token to us)
host: string
@ -59,6 +66,14 @@ function call(
)
req.on('error', reject)
req.setTimeout(timeoutMs, () => req.destroy(new Error('peer timed out')))
const connectTimer = setTimeout(() => req.destroy(new Error('peer unreachable')), CONNECT_TIMEOUT_MS)
connectTimer.unref()
req.once('socket', (socket) => {
const clear = () => clearTimeout(connectTimer)
socket.once('connect', clear)
socket.once('secureConnect', clear)
})
req.once('close', () => clearTimeout(connectTimer))
if (body) req.write(body)
req.end()
})

View file

@ -10,6 +10,7 @@ import { hostname } from 'os'
import { loadPricing } from './models.js'
import { buildMenubarPayloadForRange } from './usage-aggregator.js'
import type { MenubarPayload } from './menubar-json.js'
import { periodInfoFromQuery, UsageQueryError } from './cli-date.js'
import { pullDevices, linkRemote } from './sharing/host.js'
import { browse } from './sharing/discovery.js'
@ -36,6 +37,10 @@ function writeJsonError(res: import('http').ServerResponse, status: number, erro
res.end(JSON.stringify({ error }))
}
// Cap on the cached local payload, matched to the parser's own session cache
// (parser.ts) so the assembled payload is never staler than its source data.
const LOCAL_PAYLOAD_TTL_MS = 180_000
const HERE = dirname(fileURLToPath(import.meta.url))
// Locate the built React dashboard (dist/dash). Works both when running from a
@ -105,6 +110,37 @@ export async function runWebDashboard(opts: {
const share = new ShareController(shareGetUsage)
if (await loadShareAlways()) await share.start(true).catch(() => {})
// The server is long-lived, so cache this machine's parsed payload (the CLI
// process cannot). Store the promise before any await so concurrent identical
// requests collapse into one parse instead of racing.
const localPayloadCache = new Map<string, { at: number; payload: Promise<MenubarPayload> }>()
const getLocalPayload = (period: string, provider: string, from?: string, to?: string): Promise<MenubarPayload> => {
const key = `${period}|${provider}|${from ?? ''}|${to ?? ''}`
const hit = localPayloadCache.get(key)
if (hit && Date.now() - hit.at < LOCAL_PAYLOAD_TTL_MS) return hit.payload
const periodInfo = periodInfoFromQuery({ period, from, to }, opts.period)
const payload = buildMenubarPayloadForRange(periodInfo, { provider, project: opts.project, exclude: opts.exclude, optimize: false })
const now = Date.now()
localPayloadCache.set(key, { at: now, payload })
for (const [k, v] of localPayloadCache) if (now - v.at >= LOCAL_PAYLOAD_TTL_MS) localPayloadCache.delete(k)
void payload.catch(() => localPayloadCache.delete(key))
return payload
}
// Embed this machine's prewarmed payload in index.html for an instant first
// paint with no data round-trip. Only the local device is inlined: no remote
// network wait, and paired devices stream in via the live fetch right after.
const serveIndexHtml = async (res: import('http').ServerResponse, filePath: string): Promise<void> => {
const html = await readFile(filePath, 'utf8')
const payload = await getLocalPayload(opts.period, opts.provider, opts.from, opts.to)
const devices = [{ id: 'local', name: hostname(), local: true, payload }]
// Escape every '<' so a device/model/project name can't close the <script>.
const json = JSON.stringify({ devices }).replace(/</g, String.fromCharCode(92) + 'u003c')
const injected = html.replace('<script type="module"', `<script>window.__CODEBURN_BOOTSTRAP__=${json}</script>\n <script type="module"`)
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store' })
res.end(injected)
}
const server = createServer(async (req, res) => {
try {
const url = new URL(req.url ?? '/', 'http://localhost')
@ -128,20 +164,14 @@ export async function runWebDashboard(opts: {
const provider = url.searchParams.get('provider') ?? opts.provider
const from = url.searchParams.get('from') ?? opts.from
const to = url.searchParams.get('to') ?? opts.to
let periodInfo
let payload
try {
periodInfo = periodInfoFromQuery({ period, from, to }, opts.period)
payload = await getLocalPayload(period, provider, from, to)
} catch (err) {
if (!(err instanceof UsageQueryError)) throw err
writeJsonError(res, 400, err instanceof Error ? err.message : String(err))
writeJsonError(res, 400, err.message)
return
}
const payload = await buildMenubarPayloadForRange(periodInfo, {
provider,
project: opts.project,
exclude: opts.exclude,
optimize: false,
})
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' })
res.end(JSON.stringify(payload))
return
@ -154,17 +184,14 @@ export async function runWebDashboard(opts: {
const provider = url.searchParams.get('provider') ?? opts.provider
const from = url.searchParams.get('from') ?? opts.from
const to = url.searchParams.get('to') ?? opts.to
let periodInfo
let results
try {
periodInfo = periodInfoFromQuery({ period, from, to }, opts.period)
results = await pullDevices(() => getLocalPayload(period, provider, from, to), { period, from, to }, hostname(), {})
} catch (err) {
if (!(err instanceof UsageQueryError)) throw err
writeJsonError(res, 400, err instanceof Error ? err.message : String(err))
writeJsonError(res, 400, err.message)
return
}
const localGetUsage = async () =>
buildMenubarPayloadForRange(periodInfo, { provider, project: opts.project, exclude: opts.exclude, optimize: false })
const results = await pullDevices(localGetUsage, { period, from, to }, hostname(), {})
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' })
res.end(JSON.stringify({ devices: results }))
return
@ -271,14 +298,16 @@ export async function runWebDashboard(opts: {
return
}
try {
const buf = await readFile(filePath)
res.writeHead(200, { 'content-type': CONTENT_TYPES[extname(filePath)] ?? 'application/octet-stream' })
res.end(buf)
if (extname(filePath) === '.html') {
await serveIndexHtml(res, filePath)
} else {
const buf = await readFile(filePath)
res.writeHead(200, { 'content-type': CONTENT_TYPES[extname(filePath)] ?? 'application/octet-stream' })
res.end(buf)
}
} catch {
// Unknown path: serve index.html so the SPA can route it.
const buf = await readFile(join(dashDir, 'index.html'))
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' })
res.end(buf)
await serveIndexHtml(res, join(dashDir, 'index.html'))
}
} catch (err) {
res.writeHead(500, { 'content-type': 'application/json' })
@ -299,6 +328,8 @@ export async function runWebDashboard(opts: {
// Durable handler so a post-bind socket error never crashes the process.
server.on('error', () => {})
void Promise.resolve().then(() => getLocalPayload(opts.period, opts.provider, opts.from, opts.to)).catch(() => {})
const url = `http://127.0.0.1:${port}`
if (!dashDir) {
process.stdout.write(`\n Dashboard UI is not built. Run: cd dash && npm install && npm run build\n`)

View file

@ -0,0 +1,15 @@
import { describe, it, expect } from 'vitest'
import { generateIdentity } from '../../src/sharing/identity.js'
import { hello } from '../../src/sharing/client.js'
describe('peer connect timeout', () => {
it('fails fast when a peer is unreachable instead of riding the OS connect timeout', async () => {
const id = await generateIdentity('Test')
// RFC5737 TEST-NET-1 is reserved and black-holed: the SYN gets no answer,
// so without the connect-phase cap this would hang ~75s on macOS.
const start = Date.now()
await expect(hello({ identity: id, host: '192.0.2.1', port: 7777 })).rejects.toThrow()
expect(Date.now() - start).toBeLessThan(6000)
}, 15000)
})