Web UI Performance Testing
One-command performance test for the agent-worker web UI.
Usage
Run the full pipeline (build → restart → Lighthouse → report):
bash ~/.claude/skills/perf/scripts/run-perf.sh
Options
| Flag | Effect |
|---|---|
| (none) | Full pipeline: bump version, build, restart daemon, run Lighthouse |
--no-build |
Skip build + version bump (test current deployed code) |
--no-restart |
Skip daemon restart |
--quick |
Skip Lighthouse, do a fast agent-browser DOM check instead |
Quick check (no Lighthouse, ~3s)
bash ~/.claude/skills/perf/scripts/run-perf.sh --quick
Returns DOM node count, style element count, list items, buttons — useful for verifying a change didn't break rendering without waiting for a full Lighthouse run.
What the metrics mean
| Metric | Target | Why it matters |
|---|---|---|
| TBT (Total Blocking Time) | <200ms | Time the main thread is blocked — directly causes "can't click" |
| TTI (Time to Interactive) | <1s | When the page first becomes fully interactive |
| FCP (First Contentful Paint) | <0.5s | When the user first sees content |
| LCP (Largest Contentful Paint) | <1.5s | When the main content is visible |
| Style & Layout | <100ms | CSS style recalculation + layout — the main bottleneck in this app |
| Long Tasks | 0 >500ms | Tasks that block the event loop — causes UI freezes |
Comparing runs
The script automatically saves the previous Lighthouse report and shows deltas:
TBT ✗ 4,290 ms (-2000ms) ← improved by 2 seconds
Style & Layout: 2000ms (-6000ms) ← big improvement
Common performance issues in this project
semajsx/style CSS injection — Each
rule()call injects CSS. IfinjectStyles()modifies<style>.textContentN times synchronously, it causes N CSSOM rebuilds. Fix: batch into buffer, flush once per microtask.Signal-driven DOM replacement —
computedsignal updates callrenderValueToNodewhich creates an entire new DOM subtree and replaces the old one. No diffing. Large lists (50+ channel messages, event lists) cause heavy layout work.Channel message history —
loadChannelHistoryfetches all messages and sets them at once on the signal, triggering synchronous DOM creation for every message. Fix: limit initial fetch, implement virtual scrolling, or defer rendering.Build cache —
semajsx/styleresolves to the umbrella package's pre-builtdist/. After changing semajsx source, must rebuild bothpackages/styleANDpackages/semajsx:cd vendor/semajsx/packages/style && bun run build cd vendor/semajsx/packages/semajsx && bun run build
Workflow
Typical performance debugging cycle:
bash ~/.claude/skills/perf/scripts/run-perf.sh— baseline- Make a change
bash ~/.claude/skills/perf/scripts/run-perf.sh— compare- Read the delta — did TBT/Style&Layout improve?
- Repeat