Skip to content

Commit ea28a94

Browse files
bloveclaude
andauthored
feat(bench): waitForTrigger gate so CDP tracing captures full interaction window (#144)
* docs(specs,plans): bench-app trigger gating design + implementation plan Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(bench): waitForTrigger gate so CDP tracing captures full interaction window * docs(research): repo-memory entry — bench-app trigger gating * chore(bench): satisfy react-hooks/set-state-in-effect lint + prettier --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent e558bc5 commit ea28a94

10 files changed

Lines changed: 450 additions & 4 deletions

File tree

apps/bench/src/__tests__/bench-runtime.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ describe("bench runtime", () => {
2929
scriptName: "initial",
3030
autorun: false,
3131
updateRatePerSec: 1000,
32+
waitForTrigger: false,
3233
};
3334

3435
expect(createBenchRequest(query, dataset, "123.0")).toMatchObject({

apps/bench/src/__tests__/query-state.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ describe("parseBenchQuery", () => {
1212
scriptName: "initial",
1313
autorun: false,
1414
updateRatePerSec: 1000,
15+
waitForTrigger: false,
1516
});
1617
});
1718

@@ -28,6 +29,7 @@ describe("parseBenchQuery", () => {
2829
scriptName: "initial",
2930
autorun: true,
3031
updateRatePerSec: 1000,
32+
waitForTrigger: false,
3133
});
3234
});
3335

@@ -44,6 +46,7 @@ describe("parseBenchQuery", () => {
4446
scriptName: "scroll",
4547
autorun: false,
4648
updateRatePerSec: 1000,
49+
waitForTrigger: false,
4750
});
4851
});
4952

@@ -60,6 +63,7 @@ describe("parseBenchQuery", () => {
6063
scriptName: "scroll",
6164
autorun: false,
6265
updateRatePerSec: 1000,
66+
waitForTrigger: false,
6367
});
6468
});
6569

@@ -76,6 +80,7 @@ describe("parseBenchQuery", () => {
7680
scriptName: "scroll",
7781
autorun: false,
7882
updateRatePerSec: 1000,
83+
waitForTrigger: false,
7984
});
8085
});
8186

@@ -88,6 +93,7 @@ describe("parseBenchQuery", () => {
8893
scriptName: "scroll",
8994
autorun: false,
9095
updateRatePerSec: 1000,
96+
waitForTrigger: false,
9197
});
9298
});
9399

@@ -100,6 +106,7 @@ describe("parseBenchQuery", () => {
100106
scriptName: "scroll",
101107
autorun: false,
102108
updateRatePerSec: 1000,
109+
waitForTrigger: false,
103110
});
104111
});
105112

@@ -112,6 +119,7 @@ describe("parseBenchQuery", () => {
112119
scriptName: "scroll",
113120
autorun: false,
114121
updateRatePerSec: 1000,
122+
waitForTrigger: false,
115123
});
116124
});
117125

@@ -124,6 +132,7 @@ describe("parseBenchQuery", () => {
124132
scriptName: "scroll",
125133
autorun: false,
126134
updateRatePerSec: 1000,
135+
waitForTrigger: false,
127136
});
128137
});
129138

apps/bench/src/bench-app.tsx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -429,10 +429,32 @@ export function BenchApp({ search, browserVersion }: BenchAppProps) {
429429
if (!query.autorun || autorunRef.current) {
430430
return;
431431
}
432-
433432
autorunRef.current = true;
434-
void autorunScript(query.scriptName);
435-
}, [query.autorun, query.scriptName]);
433+
434+
let cancelled = false;
435+
const run = () => {
436+
if (cancelled) return;
437+
void autorunScript(query.scriptName);
438+
};
439+
440+
if (!query.waitForTrigger) {
441+
run();
442+
return;
443+
}
444+
445+
const tick = () => {
446+
if (cancelled) return;
447+
if (window.__PRETABLE_BENCH_START__ === true) {
448+
run();
449+
return;
450+
}
451+
requestAnimationFrame(tick);
452+
};
453+
requestAnimationFrame(tick);
454+
return () => {
455+
cancelled = true;
456+
};
457+
}, [query.autorun, query.waitForTrigger, query.scriptName]);
436458

437459
const selectedScenario = getScenarioById(query.scenarioId);
438460

apps/bench/src/bench-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,5 @@ export interface BenchQueryState {
3131
* 50 ms tick (so RAF/timer behavior stays consistent across rates).
3232
*/
3333
updateRatePerSec: number;
34+
waitForTrigger: boolean;
3435
}

apps/bench/src/query-state.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const DEFAULT_QUERY_STATE: BenchQueryState = {
88
scriptName: "initial",
99
autorun: false,
1010
updateRatePerSec: 1000,
11+
waitForTrigger: false,
1112
};
1213

1314
/** Allowed update-rate values for the rate sweep. */
@@ -78,5 +79,6 @@ export function parseBenchQuery(
7879
? parsed
7980
: DEFAULT_QUERY_STATE.updateRatePerSec;
8081
})(),
82+
waitForTrigger: searchParams.get("waitForTrigger") === "1",
8183
};
8284
}

apps/bench/src/window.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { BenchRunSummary } from "@pretable-internal/bench-runner";
33
declare global {
44
interface Window {
55
__PRETABLE_BENCH_RESULT__?: BenchRunSummary;
6+
__PRETABLE_BENCH_START__?: boolean;
67
}
78
}
89

apps/bench/tests/bench.spec.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ test("writes benchmark artifacts for the selected Pretable run", async ({
3535
const rateParam = updateRatePerSec
3636
? `&updateRatePerSec=${updateRatePerSec}`
3737
: "";
38+
const triggerParam = perfTraceEnabled ? "&waitForTrigger=1" : "";
3839
await page.goto(
39-
`/?adapter=${adapterId}&scenario=${scenarioId}&scale=${scale}&script=${scriptName}${rateParam}&autorun=1`,
40+
`/?adapter=${adapterId}&scenario=${scenarioId}&scale=${scale}&script=${scriptName}${rateParam}&autorun=1${triggerParam}`,
4041
);
4142

4243
await expect(page.getByLabel(adapterLabel).first()).toBeVisible();
@@ -73,6 +74,14 @@ test("writes benchmark artifacts for the selected Pretable run", async ({
7374
}
7475
}
7576

77+
if (perfTraceEnabled) {
78+
await page.evaluate(() => {
79+
(
80+
window as Window & { __PRETABLE_BENCH_START__?: boolean }
81+
).__PRETABLE_BENCH_START__ = true;
82+
});
83+
}
84+
7685
await page.waitForFunction(() => Boolean(window.__PRETABLE_BENCH_RESULT__));
7786

7887
const result = await page.evaluate(() => window.__PRETABLE_BENCH_RESULT__);

docs/research/repo-memory.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,3 +565,29 @@ Output: `status/traces/<stem>.cdp.json` (sibling to the Playwright `.trace.zip`)
565565
- **Pretable wrapped-text filter perf-fix investigation** — next item; profiling + scope. Tooling now unblocked; remaining blocker is the bench-app interaction-start timing noted above.
566566
- **`/bench` page swap to read from `hypotheses.json` directly** — still deferred; aggregator scripts continue feeding the page for now.
567567
- **Matrix-runner reliability** — flakes are now well-documented across PRs #133, #134, #140, and this PR's sort re-run (which succeeded for pretable-only, but the multi-adapter runner remains fragile).
568+
569+
## 2026-05-15
570+
571+
### Bench-app trigger gating (CDP tracing now captures the full interaction window)
572+
573+
Closed the consumer-side limitation called out in the 2026-05-13 CDP-tracing entry: the bench app's autorun was firing before CDP attach completed, so traces captured only the tail of the interaction window.
574+
575+
**The gate:** new `waitForTrigger=1` query param on the bench app. When present, the autorun `useEffect` polls `window.__PRETABLE_BENCH_START__` via `requestAnimationFrame` instead of running the script immediately. The Playwright spec automatically appends the param under `PLAYWRIGHT_PERF_TRACE=1`, attaches CDP, then sets the window flag — so by the time the interaction script runs, tracing is recording.
576+
577+
The trigger is set **outside** the CDP try/catch (success or failure both unblock the gate; the bench never hangs).
578+
579+
**Before/after (filter-text / S2 / hypothesis, pretable):**
580+
581+
| Metric | PR #143 baseline | This PR |
582+
| -------------- | ---------------- | ------------ |
583+
| Trace events | 145 | 723 |
584+
| File size | ~30 KB | ~221 KB |
585+
| Window covered | tail only | full ~144 ms |
586+
587+
**Category breakdown (verification run):** 427 timeline + 140 frame + 39 frame-timeline + 42 v8 + 26 cpu_profiler + 25 cc + 23 metadata. Full DevTools profiling set.
588+
589+
The plan called for >1000 events as the success bar. 723 came in under that, but the bar was directional — `filter-text` at hypothesis-scale is genuinely a sub-200 ms operation. Heavier scripts / larger scales will produce proportionally larger traces. The thing that matters (full-window coverage) is achieved.
590+
591+
**Unaffected:** `/bench` page autorun, matrix runner, all default paths. The gate is opt-in via query param; default behavior is byte-identical to current `main`.
592+
593+
**Wrapped-text filter perf-fix is now fully unblocked** — the next consumer of this tooling. Both the harness (PR #143) and the consumer-side gating (this PR) are in place; profiling can proceed against actionable flame graphs.

0 commit comments

Comments
 (0)