Skip to content

perf(dash-apps): lightweight jsdom regression gate - #183

Merged
wpmed92 merged 1 commit into
mainfrom
feat/dashapp-perf-gate-100
Jul 29, 2026
Merged

perf(dash-apps): lightweight jsdom regression gate#183
wpmed92 merged 1 commit into
mainfrom
feat/dashapp-perf-gate-100

Conversation

@dkneeland

@dkneeland dkneeland commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

What

A CI gate that replays a 6 s drive (150 frames @ 25 Hz) in jsdom and trips when a dashboard's onCarStateUpdate mutates too many child DOM elements per frame. Targets the actual web-retro regression class: per-child update loops (cells.forEach(c => c.className = next)).

Feedback

FAIL retro-bug — may cause dashboard lag (driver sees stale speed/blinkers)
  .steer-strip: 22 child updates per frame (max 6)
  Fix: update .steer-strip once instead of each child

Reach

what where
gate (114 lines) dash-apps/_perf-gate/gate.js
CI workflow (matrix is source of truth) .github/workflows/dashapp-perf.yml
matrix apps web-retro, web-analog, web-ambient
not gated web-vanilla (WASM/canvas invisible to jsdom), web-expo (RN bridge, refuses window.onCarStateUpdate contract)

Verified

npm test 3/3. All 3 matrix apps pass. Historic 0a32362 regression trips on .steer-strip 22/tick.

Tuning

All knobs (budget, streak, scenario, matrix) documented in dash-apps/README.md tuning map.

@dkneeland
dkneeland marked this pull request as draft July 29, 2026 01:29
@dkneeland
dkneeland force-pushed the feat/dashapp-perf-gate-100 branch 3 times, most recently from e5baf1c to b7bd724 Compare July 29, 2026 02:10
A CI gate that runs on every PR touching dash-apps/** and trips when a
dashboard's onCarStateUpdate does too much DOM work per frame.

Gate replays a deterministic 6 s drive (150 frames at 25 Hz) in jsdom.
Per-tick MutationObserver records are grouped by target.parentNode (rendered
as #id, .class, or tagname). Any single parent that sees >= 6 child updates
per frame for 3 consecutive frames (after a 50-frame warmup) fails the gate.

Verdict targets the failure mode that actually occurred: per-child update
loops rewriting attributes on every sibling, every tick (the web-retro
regression at 0a32362).

Design:
- 114-line gate.js — Proxy NativeCarState with convention-based key
  derivation (getEgoSpeed-egoSpeed), no getter list to sync with Kotlin
- Script-inliner: no HTTP server, reads <script src> from disk
- 3-in-a-row streak instead of p95 — tighter on real regressions, ignores
  isolated CI-runner stalls
- matchMedia shim for jsdom
- 46-line test, 2 fixtures (fast-app PASS, churn-app FAIL), 32-line workflow
- 1 dep (jsdom ^29.1.1)
- CI matrix is the single source of truth for which apps are gated (no
  manifest perf field duplication)

Author-facing message leads with impact, names the offending element, and
gives a concrete fix instruction:
  FAIL app — may cause dashboard lag (driver sees stale speed/blinkers)
    .steer-strip: 22 child updates per frame (max 6)
    Fix: update .steer-strip once instead of each child

Matrix: web-retro, web-analog, web-ambient. Not gated: web-vanilla
(WASM/canvas invisible to jsdom tripwire), web-expo (React Native bridge
does not match the window.onCarStateUpdate contract the gate replays).

Verified: 3/3 tests pass; web-retro, web-analog, web-ambient pass; historic
0a32362 regression trips cleanly on .steer-strip at 22/tick.
@dkneeland
dkneeland force-pushed the feat/dashapp-perf-gate-100 branch from b7bd724 to bd09a37 Compare July 29, 2026 03:56
@dkneeland
dkneeland marked this pull request as ready for review July 29, 2026 03:58
@wpmed92
wpmed92 merged commit 1a4e2f7 into main Jul 29, 2026
5 checks passed
@wpmed92
wpmed92 deleted the feat/dashapp-perf-gate-100 branch July 29, 2026 20:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants