Check a performance change
Compare equivalent flows and reject weak or incomplete evidence.
Use the browser setup in Check optimization evidence before running these commands.
Account for instrumentation
Genie's active render analysis adds synchronous work to development commits. react_profile_stop
pauses detailed collection while the hook remains installed. Keep collector settings and coverage
equivalent across both cohorts. For an overhead report, also measure the same flow with Genie
disabled; a faster instrumented run alone does not measure the cost seen by an uninstrumented app.
The repository's runtime overhead report documents a 500-row development fixture and its limits. Its timings do not establish overhead in your application or in production.
Quick same-session diff
Start and run the before flow:
npx @genie-react/cli call react_profile_start '{}'
agent-browser --session docs-case-proof click '[data-testid="counter"]'
agent-browser --session docs-case-proof get text '[data-testid="counter"]'
npx @genie-react/cli call react_profile_snapshot '{"label":"before"}'Make the change, start a clean window, and drive the same flow:
npx @genie-react/cli call react_profile_start '{}'
agent-browser --session docs-case-proof click '[data-testid="counter"]'
agent-browser --session docs-case-proof get text '[data-testid="counter"]'
npx @genie-react/cli call react_renders_diff \
'{"baseline":"before","thresholdMs":0.5}' --jsonStrong repeated comparison
Run the same flow six times before and six times after. The comparison removes one warm-up and needs five usable runs. Collect the baseline IDs:
retention="$(npx @genie-react/cli call devtools_capture_list '{}' --json)"
jq -e '(.maxRetained - .pinned) >= 12' <<<"$retention" >/dev/null
baseline_ids=()
candidate_ids=()
all_capture_ids=()
cleanup_captures() {
local retained_id
for retained_id in "${all_capture_ids[@]}"; do
npx @genie-react/cli call devtools_capture_pin \
"$(jq -cn --arg captureId "$retained_id" \
'{captureId:$captureId,pinned:false}')" --json >/dev/null || true
done
}
trap cleanup_captures EXIT
for run in 0 1 2 3 4 5; do
agent-browser --session docs-case-proof reload
npx @genie-react/cli call devtools_wait \
'{"condition":"ready","timeoutMs":10000}'
npx @genie-react/cli call react_clear_renders '{}'
agent-browser --session docs-case-proof click '[data-testid="counter"]'
agent-browser --session docs-case-proof get text '[data-testid="counter"]' | \
rg -x 'Count: 1'
capture_args="$(jq -cn --arg name "baseline-$run" \
'{name:$name,include:["react","effects"]}')"
capture="$(npx @genie-react/cli call devtools_capture_create \
"$capture_args" --json)"
capture_id="$(printf '%s\n' "$capture" | jq -er '.captureId')"
baseline_ids+=("$capture_id")
all_capture_ids+=("$capture_id")
npx @genie-react/cli call devtools_capture_pin \
"$(jq -cn --arg captureId "$capture_id" \
'{captureId:$captureId,pinned:true}')" --json
doneMake the source change, then collect the candidate IDs with the exact same flow:
for run in 0 1 2 3 4 5; do
agent-browser --session docs-case-proof reload
npx @genie-react/cli call devtools_wait \
'{"condition":"ready","timeoutMs":10000}'
npx @genie-react/cli call react_clear_renders '{}'
agent-browser --session docs-case-proof click '[data-testid="counter"]'
agent-browser --session docs-case-proof get text '[data-testid="counter"]' | \
rg -x 'Count: 1'
capture_args="$(jq -cn --arg name "candidate-$run" \
'{name:$name,include:["react","effects"]}')"
capture="$(npx @genie-react/cli call devtools_capture_create \
"$capture_args" --json)"
capture_id="$(printf '%s\n' "$capture" | jq -er '.captureId')"
candidate_ids+=("$capture_id")
all_capture_ids+=("$capture_id")
npx @genie-react/cli call devtools_capture_pin \
"$(jq -cn --arg captureId "$capture_id" \
'{captureId:$captureId,pinned:true}')" --json
doneBuild the comparison input from the returned IDs:
baseline_json="$(printf '%s\n' "${baseline_ids[@]}" | jq -Rsc 'split("\n")[:-1]')"
candidate_json="$(printf '%s\n' "${candidate_ids[@]}" | jq -Rsc 'split("\n")[:-1]')"
comparison_args="$(jq -cn \
--argjson baselineCaptureIds "$baseline_json" \
--argjson candidateCaptureIds "$candidate_json" \
'{
baselineCaptureIds:$baselineCaptureIds,
candidateCaptureIds:$candidateCaptureIds,
metrics:["react.renders"],
minimumRuns:5,
warmupRuns:1,
budgets:[{metric:"react.renders",maxRegressionPct:0}]
}')"
npx @genie-react/cli call devtools_capture_compare \
"$comparison_args" --jsonA recorded Router demo run produced the rejection below. These selected fields show how to read an incomplete comparison; your current run may have different coverage:
{
"kind": "capture-comparison",
"minimumRuns": 5,
"policy": {
"warmupRuns": 1,
"confidenceLevel": 0.95,
"minimumEffectPct": 5
},
"overall": "not-comparable",
"metrics": [
{
"metric": "react.renders",
"direction": "lower-is-better",
"baseline": { "samples": 0, "median": null },
"candidate": { "samples": 0, "median": null },
"comparable": false,
"notComparableReasons": ["render-props-not-enumerated"],
"verdict": "not-comparable"
}
],
"violations": []
}Fix the coverage gap or narrow the claim, then repeat both cohorts.
pass means Genie found no budget violation. To claim an improvement, also require
comparable:true, the expected direction, confidence.significant:true, and an effect large enough
to matter. not-comparable, insufficient-data, and inconclusive are not passes.
Unpin retained evidence after export or review. The exit trap runs the same cleanup after any earlier failure:
cleanup_captures
trap - EXIT
unset all_capture_ids baseline_ids candidate_ids baseline_json candidate_json
unset capture capture_args capture_id comparison_args retention
unset -f cleanup_capturesReturn the comparison verdict, usable sample counts, rejected evidence, budget results, and the visible behavior check. Claim a performance improvement only for metrics that meet the comparison criteria above.