tree: 7e8b32e973d1db743e763c6d3137dc3294cc0831
  1. cdp-client.mjs
  2. CI.md
  3. frontend.spec.ts
  4. playwright.config.ts
  5. protocol.mjs
  6. react-commit-probe.js
  7. README.md
  8. report.mjs
  9. session-switch-busy-js.mjs
  10. session-switch-commits.mjs
  11. storybook.mjs
scripts/perf/README.md

Renderer performance probes

For the two manual CI lanes, raw reports, fixtures and their coverage limits, see CI.md. Those lanes verify DOM events and consumer state; they do not use the legacy native-input helpers below.

Measuring what a session switch costs in the running Desktop app, over CDP. The findings in #4109 were produced with these; they live here so the next measurement is a command rather than a rebuild.

They are ad-hoc tools, not a benchmark suite: they attach to a dev app you started, and they answer “how much” only for the interaction they drive.

Running

Start the dev app with the debugger open:

npm run dev -w @maka/desktop -- --remote-debugging-port=9334

Then, with a populated sidebar (about 30 rows is what the #4109 numbers used):

node scripts/perf/session-switch-commits.mjs before
node scripts/perf/session-switch-busy-js.mjs
  • session-switch-commits.mjs — React commits per switch, how many are full-tree renders, and how many fibers actually re-rendered. Reloads the page first, so react-commit-probe.js is in place before React boots.
  • session-switch-busy-js.mjs — renderer busy JS per switch, with the top self-time entries. It reports the running build, one configuration. A before/after needs both configurations in one instance; see below.
  • react-commit-probe.js — the in-page half: a minimal __REACT_DEVTOOLS_GLOBAL_HOOK__ plus a Function.prototype.bind wrapper that catches React creating a dispatchSetState, so a commit can be attributed to the setState that caused it.
  • cdp-client.mjs — the protocol client and the row-clicking helper.

The one rule

Never compare numbers from two app launches. Restarting the app shifts these metrics by orders of magnitude, while the spread inside a single running instance is small. An A/B means: one instance, a globalThis switch to select the configuration, at least three repetitions per configuration, alternating, compared pairwise. Two runs of “before” and “after” against two launches will produce a confident number that means nothing.

The switch is a throwaway, not a shipped affordance. Branch a worktree, put a globalThis flag in the one place that reads it — one that restores the old behaviour when set, so the regressed configuration is reachable without rebuilding the defect by hand — alternate the flag inside the running instance, then delete the worktree. Measurement discipline is ours; product code carries none of it. A flag that reaches main is a branch nothing executes and a defect replica that has to be maintained against the code it replicates.

Two smaller ones that cost time to rediscover:

  • Clicks must be Input.dispatchMouseEvent. A synthesised element.click() does not activate an Astryx SideNavItem, so the app does nothing and the measurement describes it.
  • Disarm the commit probe (__MAKA_PROBE__.arm = false) before sampling. Its fiber walk otherwise shows up in the profile as the app's own work; session-switch-busy-js.mjs does this for you.

What these do not tell you

The commit count is unweighted, and busy JS on this workload is spread across about a thousand cheap fibers rather than concentrated in one component. A change that lowers the commit count has not necessarily lowered the time, and a change that moves the top self-time entry has not necessarily lowered anything. Read both, and read the medians.