Manual performance measurements

Refs #4677 and #5038. These two independent workflows collect baselines; they have no schedule or timing merge gate. Run Performance frontend or Performance protocol from Actions after the workflow exists on the default branch, selecting the desired ref. Download the performance-*-<sha> artifact and read the JSON alongside the job summary. Compare explicit commits with identical fixtures and environment; a shared-runner result alone is not a regression verdict.

Both lanes use the existing free ubuntu-24.04 GitHub-hosted x64 runner, one measurement process at a time, Node 24.18.1 and npm ci. Electron is pinned by the repository (43.4.1 at introduction); Playwright/Chromium comes from the lockfile and browser install. JSON records actual OS, CPU, memory, Node and image version; browser results record the browser version. Runner hardware is not dedicated and its image can change. Reject comparisons across different environments and repeat suspicious results.

Reproduce

From a clean checkout, run npm ci. For protocol, build core, storage, mcp, runtime and runtime-host in that order, then node scripts/perf/protocol.mjs. For frontend, run npm --workspace @maka/desktop run build:workspace-deps, npm --workspace @maka/desktop run build, npm --workspace @maka/desktop run build-storybook and npx playwright install --with-deps chromium. From apps/desktop, run MAKA_PERF_OUTPUT="$PWD/../../perf-results" xvfb-run -a npx playwright test --config ../../scripts/perf/playwright.config.ts. From the repository root run xvfb-run -a node scripts/perf/storybook.mjs. Use CI for standard measurements; local runs are for harness debugging.

Reports retain every raw sample. Median averages the two middle observations for even counts; p95 is nearest rank ceil(0.95*n), so ten samples yield the maximum. Counts and one-off diagnostics have n=1 and are not latency distributions. Protocol timings use one discarded warmup and ten measured repetitions; encoding instrumentation is a separate pass and restores JSON.stringify in finally. It counts actual UTF-8 JSON output, including normalization/revision/decoder work, rather than estimated object size. The negative control calls the same production page builder an extra time and must increase measured encoding work. UTF-8 byte caps, decoded values and concatenated page completeness are asserted. This first lane covers Runtime Resource projection through final wire encoding; it does not measure SQLite, transport send, Memory/Plan/Artifact/catalog coordinators, Shell persistence, or Usage. Their implementations are unchanged.

Protocol warm-projection-codec-ms retains production wire decoding but excludes the byte-cap, equality and ordering assertions already run in the untimed baseline and negative-control passes. The cursor progress guard stays in the loop to fail on non-progress instead of hanging. This metric replaces warm-ms (includes validation); do not compare their timings as a product speedup. Existing published reports retain their original metric and values.

Frontend coverage and limits

  • Electron with the real Host: existing 120-Turn fixture switching and older/latest navigation; normal fake backend output (9-character deltas every 45ms) while editing, two background Tasks emitting the same fixed response, then mid-stream Stop; DOM/heap samples after repeated navigation and an idle CPU profile. Each input sample asserts both background streams are still active; concatenated deltas and final output must be complete. Background-read drafts contain 680 characters; submitting the retained final draft proves the input reached application state. Stop must leave a proper output prefix and no subsequent delta.
  • Storybook in Chromium: existing oversized-Turn generator extended to 45 tools, verified expansion and collapse, relative upward scrolling with explicit reading-anchor displacement. This is a simulated shell, not Electron/Host coverage.
  • Every operation checks its consumer state: selected session, target Turn, draft focus/text, output markers or disclosed tool result. Failures fail the job; an empty run is not accepted. DOM click/input events and the existing preload admission seam drive actions. Native mouse/keyboard replay is not used. Programmatic scrolling does not establish native wheel or accessibility acceptance.
  • DOM-ready latency includes Playwright polling/IPC. Stream lag starts at renderer subscription delivery and ends at DOM mutation; neither is proof of pixels presented. These are laboratory measurements, not whole-page INP. Long tasks and CPU TaskDuration do not substitute for anchor geometry, energy watts or OS wakeups.
  • Repeated actions use a warm process. First-action samples are labelled separately, not called cold application startup. Storybook reloads for cold-scroll trials, then repeats expansion and closure in the same mounted story. Heap readings include garbage awaiting collection and do not establish a leak by themselves. Idle retains ten approximately one-second CPU/heap samples and the CPU profile; task duration is normalized by the actual CDP timestamp interval. Browser-context traces retain DOM snapshots, and their overhead is included in the laboratory timings. Long-task/LoAF rows labelled case-total include every operation after observer installation in that case. Actual disk-cold startup, RTT injection, physical presentation, wakeups and long-duration leak acceptance remain outside this first harness.

#5184 production layout measurements

The frontend lane measures the current production layout. geometry-navigation.spec.ts reloads three renderer documents in one Desktop

  • Host process. It measures renderer mount, older history and return to latest separately, including CDP task/layout time and long tasks. The long-task observer has a deliberate busy-task control after measurement. Mount is a warm-process document reload, not disk-cold startup; history uses the existing prompt rail, not wheel-triggered fill/trim.

node scripts/perf/geometry-ablation.mjs runs the three fixed-range stories (mixed 24 turns, 45 tools, 1200-line code), with three repetitions each. Its mount CPU/layout counters are recorded before the first upward sweep, so deferred work cannot disappear from the comparison. Scroll metrics cover only that cold upward sweep. Older reports included two return sweeps and are not equivalent timing workloads. Unasserted per-step anchors, LoAF and heap diagnostics are no longer collected by this driver. The script emits the shared report format and full per-frame geometry JSON. Both probes run sequentially in the existing frontend job and upload through its existing artifact step. No additional workflow or production switch exists.

Reports are frontend-geometry-navigation.{json,md} and frontend-geometry-ablation.{json,md}, plus geometry-ablation.json. Compare production reports from the baseline and candidate commits, including every raw sample; three samples do not establish a robust p95. CI success means the scenarios and measurements worked, not that a timing budget or product geometry contract passed. The separate --assert-stable flag remains an explicit geometry assertion, not a hidden performance threshold.

Validating a new workflow before merge

GitHub cannot dispatch a new workflow absent from the default branch. For this PR only, temporarily add push: { branches: [<this PR branch>] } to these workflows, push and inspect both jobs/artifacts. Remove that trigger after verification. Do not merge to obtain a run, add pull_request_target, or leave a permanent automatic trigger. Record both the measured SHA and final SHA in the PR when the sole subsequent change removes the validation trigger.