@buoy-gg/mcp 7.0.52 → 7.0.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{ALL_DEVICES as e,BROKER_PORT as o,BROKER_URL as r,DASHBOARD_NAME as t,DEFAULT_ACTION_TIMEOUT_MS as s,DEFAULT_SNAPSHOT_TIMEOUT_MS as i,PACKAGE_NAME as T,SyncClient as n,VERSION as a,buildAutomationConfig as g,compareReportStats as l,createMcpServer as E,estimateBatchMs as R,formatRankingMarkdown as A,getUpdateNotice as S,rankBatchFromIndex as _,registerConsoleTools as c,registerEventsTools as M,registerGenericTools as m,registerJotaiTools as O,registerPerfMonitorTools as p,registerReactQueryTools as u,registerReduxTools as I,registerRoutesTools as d,registerScreenshotTools as C,registerStorageTools as N,registerZustandTools as U,runInit as B}from"./chunk-JULJYHPG.mjs";import"./chunk-RYFN5J56.mjs";import"./chunk-EKM5ZZKU.mjs";export{e as ALL_DEVICES,o as BROKER_PORT,r as BROKER_URL,t as DASHBOARD_NAME,s as DEFAULT_ACTION_TIMEOUT_MS,i as DEFAULT_SNAPSHOT_TIMEOUT_MS,T as PACKAGE_NAME,n as SyncClient,a as VERSION,g as buildAutomationConfig,l as compareReportStats,E as createMcpServer,R as estimateBatchMs,A as formatRankingMarkdown,S as getUpdateNotice,_ as rankBatchFromIndex,c as registerConsoleTools,M as registerEventsTools,m as registerGenericTools,O as registerJotaiTools,p as registerPerfMonitorTools,u as registerReactQueryTools,I as registerReduxTools,d as registerRoutesTools,C as registerScreenshotTools,N as registerStorageTools,U as registerZustandTools,B as runInit};
1
+ import{ALL_DEVICES as e,BROKER_PORT as o,BROKER_URL as r,DASHBOARD_NAME as t,DEFAULT_ACTION_TIMEOUT_MS as s,DEFAULT_SNAPSHOT_TIMEOUT_MS as i,PACKAGE_NAME as T,SyncClient as n,VERSION as a,buildAutomationConfig as g,compareReportStats as l,createMcpServer as E,estimateBatchMs as R,formatRankingMarkdown as A,getUpdateNotice as S,rankBatchFromIndex as _,registerConsoleTools as c,registerEventsTools as M,registerGenericTools as m,registerJotaiTools as O,registerPerfMonitorTools as p,registerReactQueryTools as u,registerReduxTools as I,registerRoutesTools as d,registerScreenshotTools as C,registerStorageTools as N,registerZustandTools as U,runInit as B}from"./chunk-27P7ATNT.mjs";import"./chunk-RYFN5J56.mjs";import"./chunk-ALXRBSW6.mjs";export{e as ALL_DEVICES,o as BROKER_PORT,r as BROKER_URL,t as DASHBOARD_NAME,s as DEFAULT_ACTION_TIMEOUT_MS,i as DEFAULT_SNAPSHOT_TIMEOUT_MS,T as PACKAGE_NAME,n as SyncClient,a as VERSION,g as buildAutomationConfig,l as compareReportStats,E as createMcpServer,R as estimateBatchMs,A as formatRankingMarkdown,S as getUpdateNotice,_ as rankBatchFromIndex,c as registerConsoleTools,M as registerEventsTools,m as registerGenericTools,O as registerJotaiTools,p as registerPerfMonitorTools,u as registerReactQueryTools,I as registerReduxTools,d as registerRoutesTools,C as registerScreenshotTools,N as registerStorageTools,U as registerZustandTools,B as runInit};
@@ -0,0 +1 @@
1
+ import{GPU_RENDERER_JS as e,PLAYWRIGHT_MISSING as a,aggregateCase as t,caseUrl as r,cdpDeltas as s,checkBudgets as o,closeCall as i,closeWithin as c,expectFailureFrom as n,formatWebBenchMarkdown as l,isSoftwareGpu as m,keyMatches as h,loadChromium as p,locateStepTargets as d,mergeInteractions as u,missedInput as g,oneLine as f,percentile as k,runWebBench as R,shuffled as b,snapRefreshRate as w,summarizeFrameTrace as B,summarizeProbe as F,takeHostLock as I,tiedPairs as S,validateWebBenchOptions as W,waitForHostLock as G,webBenchFlags as L,withDeadline as P}from"./chunk-ALXRBSW6.mjs";export{e as GPU_RENDERER_JS,a as PLAYWRIGHT_MISSING,t as aggregateCase,r as caseUrl,s as cdpDeltas,o as checkBudgets,i as closeCall,c as closeWithin,n as expectFailureFrom,l as formatWebBenchMarkdown,m as isSoftwareGpu,h as keyMatches,p as loadChromium,d as locateStepTargets,u as mergeInteractions,g as missedInput,f as oneLine,k as percentile,R as runWebBench,b as shuffled,w as snapRefreshRate,B as summarizeFrameTrace,F as summarizeProbe,I as takeHostLock,S as tiedPairs,W as validateWebBenchOptions,G as waitForHostLock,L as webBenchFlags,P as withDeadline};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@buoy-gg/mcp",
3
3
  "mcpName": "io.github.Buoy-gg/mcp",
4
- "version": "7.0.52",
4
+ "version": "7.0.53",
5
5
  "buoy": {
6
6
  "dashboardAuthVersion": 1
7
7
  },
@@ -35,8 +35,8 @@
35
35
  "socket.io-client": "^4.8.1",
36
36
  "zod": "^3.25.0",
37
37
  "ws": "^8.18.0",
38
- "@buoy-gg/sync-broker": "^7.0.52",
39
- "@buoy-gg/license": "^7.0.52"
38
+ "@buoy-gg/sync-broker": "^7.0.53",
39
+ "@buoy-gg/license": "^7.0.53"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/node": "^22.0.0",
@@ -0,0 +1,100 @@
1
+ # Measuring with Buoy
2
+
3
+ How to measure in the Buoy Optimize loop. The loop itself is in [SKILL.md](SKILL.md).
4
+
5
+ ## Fair comparisons
6
+
7
+ - Keep the browser mode, build, window size, pixel ratio, load and settings fixed all session.
8
+ - Measure one thing at a time. Only one Buoy web benchmark runs per machine; a second one waits for the first.
9
+ - Mix up the order, and keep the baseline and current best in every batch as anchors. If the anchors move between batches, the machine drifted. Measure again.
10
+ - Rank on a production build when you can. Dev builds can pick a different winner. If you ranked on a dev build, say so.
11
+ - Keep DevTools closed while ranking.
12
+ - A failed or partial run is not a result. A missing number can't pass a limit.
13
+
14
+ ## Web
15
+
16
+ All web tools open the app in their own Chromium and ask it to use the GPU. They need Playwright in the project, but no Buoy setup in the app. Each report names the GPU that drew. If it says software, the numbers mean little for drawing-heavy pages.
17
+
18
+ ### `find_web_ceiling`: the score
19
+
20
+ The most load each variant holds at a target frame rate. It times main-thread `requestAnimationFrame` callbacks, the same way most in-app fps meters do. Use it as the score when your contract's score is that kind of frame rate. If the app has its own judge with a different rule, the app's judge wins.
21
+
22
+ Before you trust a ceiling, make sure the page really is ready and drawing the requested load: pass a `readySelector` that appears only after the first real frame, and check the screenshots. A canvas that stopped updating can still show a high rate. Never lower the target to suit the test setup.
23
+
24
+ ```json
25
+ {
26
+ "url": "http://localhost:3000",
27
+ "cases": [
28
+ { "name": "baseline", "params": { "variant": "baseline" } },
29
+ { "name": "atlas", "params": { "variant": "atlas" } }
30
+ ],
31
+ "param": "count",
32
+ "targetFps": 55,
33
+ "readySelector": "canvas",
34
+ "viewport": { "width": 1440, "height": 900, "deviceScaleFactor": 2 }
35
+ }
36
+ ```
37
+
38
+ - It loads each step fresh with `param` set, settles 1 s, times frames for 2 s, and raises the load ~30% until a real sample misses. It repeats that miss once to rule out noise, then bisects until the pass and the miss are within 4% (`precisionPct`). If the first load misses, it searches down.
39
+ - A page error or a failed measurement marks the round **invalid**. It never counts as a miss. Fix the page and run again.
40
+ - "At least" means the round hit `max` (default 1,000,000,000) or the time limit before any miss. Raise `max` and run again.
41
+ - `rounds: 3` gives a median when two variants are close. Ceilings within about 10% are a tie until more rounds agree. Two-second samples are for screening; confirm your final pick with `rounds: 3`.
42
+ - `cpuSlowdown: 4` repeats it with the CPU slowed. It's a CPU stress test. It doesn't slow the GPU, memory or heat, so it isn't a phone.
43
+ - The report shows the browser's idle frame rate. Headless Chrome is often 60 Hz. If your target is close to it, use `headed: true` on a faster screen.
44
+
45
+ ### `screenshot_web_variants`: the looks
46
+
47
+ ```json
48
+ { "url": "http://localhost:3000", "cases": [ ... same cases, same count ... ], "atMs": [500, 1500, 3000], "outDir": "shots/round-2", "clipSelector": "canvas" }
49
+ ```
50
+
51
+ Put the baseline first in `cases`. Each call saves into a new folder, with the real capture time of each shot, and reports how much each variant differs from the baseline at the same moment. A variant flagged as an outlier differs far more than the rest: lights not drawn, wrong colors or frozen motion. Open it first, and never let a flagged variant win until you've seen it's right. Open the PNGs and compare each variant with the baseline at about the same moment. Animated scenes won't match pixel for pixel, so compare shapes, colors, glow and layout, and use several moments to catch frozen motion.
52
+
53
+ ### `run_web_benchmark`: smoothness and cost at one load
54
+
55
+ Use it to see why a variant wins: Dropped % (Chrome's own count of frames that hurt smoothness), frame gaps, long tasks, the scripts behind slow frames, main thread %, script, style and layout time, DOM size and JS heap.
56
+
57
+ - Compare only variants doing the same work at the same load.
58
+ - Dropped % ranges that overlap, or middles within 1.5 points, are a tie. `extraRoundsWhenClose: 2` adds runs for the top two.
59
+ - To test taps or typing, pass `steps` and end them with an `expect` step, so a run that skipped the work fails.
60
+ - Pass `budgets` for hard limits, like `{"droppedPct": 2, "inputMs": 200}`. A limit that wasn't measured counts as not met.
61
+ - Mark a case that does different work (like an empty page) with `"control": true`. It's shown, never ranked.
62
+ - `traceDir` saves a Chrome trace per case, for a deep look.
63
+ - `outFile` keeps the full result.
64
+
65
+ ### Web traps
66
+
67
+ - On react-native-web, `Animated` with `useNativeDriver` and Reanimated worklets still run on the main thread.
68
+ - Skia on web loads a large WASM file. Count its load time.
69
+ - A pixel ratio of 2 draws 4 times the pixels of 1. Keep it fixed.
70
+
71
+ ### Phones, when they're in scope
72
+
73
+ Open the dev server's network URL on a phone on the same Wi-Fi (for Vite, `vite --host`). In development, the page finds the Buoy broker on port 42831, so the phone shows in `list_devices`. Then use the app's own Bench with `run_benchmark_batch`. Keep the phone awake and the tab in front. Say it was a dev build.
74
+
75
+ ## React Native
76
+
77
+ - `list_devices`: pick the device with `perf-monitor`. Benchmarking needs Buoy Pro. Don't work around account checks.
78
+ - `get_benchmark_settings` with `pages` = the number of cases: read back the settings and time estimate. Don't call `set_benchmark_settings` unless the user asked to save settings.
79
+ - `run_benchmark_batch` with `targetRoute`, `deviceId` and `cases`. The first case is the baseline. The route reads each case's `params`. Pass `captureRenders: false` while ranking; render capture adds work that differs by variant.
80
+ - If a batch times out, check `get_automation_status` before starting another.
81
+ - Read UI FPS, JS FPS, CPU, memory and jank together. A UI-thread flag is close to disqualifying. `balanced` means no flag fired; missing data can also fire no flag.
82
+ - `get_batch_report` takes a `batchId`. `compare_reports` takes report IDs. They're different IDs.
83
+ - `measure_renders` with `steps` counts renders in one interaction. Compare with `compareToPrevious: true`.
84
+ - A remount doesn't reset module state or storage. If you use `reloadBetweenCases`, make sure `AutomationResumer` is mounted.
85
+ - A simulator result holds for that simulator. Confirm on real hardware before claiming phone speed.
86
+
87
+ ## Tool list
88
+
89
+ | Tool | Use |
90
+ | --- | --- |
91
+ | `start_optimize_session` | Only when the user gives a time limit: every Buoy result then shows the time left. |
92
+ | `find_web_ceiling` | The most load each web variant holds at a target fps. The score. |
93
+ | `screenshot_web_variants` | Screenshots of each web variant at the same moments. |
94
+ | `run_web_benchmark` | Smoothness and cost of web variants at one load. |
95
+ | `list_devices` | Find the connected app and its tools. |
96
+ | `get_benchmark_settings` / `set_benchmark_settings` | Read settings and the time estimate, or save them. |
97
+ | `run_benchmark_batch` / `get_automation_status` | Measure variants in the app with Bench. |
98
+ | `get_batch_report` / `compare_reports` | Read or compare saved runs. |
99
+ | `measure_renders` | Count renders in one interaction. |
100
+ | `navigate` | Open a route in the app. |
@@ -1,149 +1,151 @@
1
1
  ---
2
2
  name: buoy-optimize
3
- description: Use when the user asks to investigate a slow React Native or web screen or compare rendering approaches with a connected Buoy MCP server. Measure a baseline and selected variants, check visual behavior, and explain the observed performance trade-offs. Triggers include "buoy optimize", "optimize this screen", "find the fastest way to render X", and "why is this laggy".
3
+ description: Use when the user wants a slow screen made faster, or the most performant way to build something heavy (lists, animations, light grids, canvases, maps) in a React Native or web app, with the Buoy MCP server connected. Runs a search loop that keeps going until it hits a wall - brainstorm many ideas, build the most promising side by side, measure them all, research, fork the winners, combine them, and repeat. Triggers include "buoy optimize", "optimize this screen", "find the fastest way to render X", "why is this laggy".
4
4
  ---
5
5
 
6
- # Optimize React Native rendering with Buoy
6
+ # Buoy Optimize
7
7
 
8
- Use repeatable measurements to investigate the user's workload. Keep a working baseline, compare a small set of plausible changes, and report which devices and conditions the results cover.
8
+ You run a performance search. The goal is the best result you can find in the time you have, not a small fix.
9
9
 
10
- ## Check setup and scope
10
+ Say a screen draws Christmas lights with CSS and it's slow. Don't just tune the CSS. Ask: what are all the ways to draw this? Canvas, WebGL, Skia, a shader, a sprite sheet, a video, a worker, and more. Build the most promising ones. Measure them all the same way. Keep what wins. Go again. Keep going until your time runs out.
11
11
 
12
- If the target is a web app, you can start with `run_web_benchmark`. It opens the app in its own browser and needs no connected app (see "When the target is a browser"). Otherwise, call `list_devices` and select the intended device. Benchmarking requires a connected app exposing `perf-monitor`, configured account access and Buoy Pro. If a requirement is missing, explain the setup needed. Do not bypass account checks.
12
+ Measuring details and tool tips are in [MEASURING.md](MEASURING.md). Read it before your first measurement.
13
13
 
14
- Infer the target screen, existing harness, rendering libraries and workload from the project and conversation. Ask only for missing details that would change the experiment: workload size, required visual behavior, target devices, performance limits or a time budget. Continue work already authorized without asking the user to approve each routine prune or iteration.
14
+ ## The loop
15
15
 
16
- Start on the user's target platform and available device. Record whether it is physical hardware or a simulator, the build type and available device information. Keep comparisons on the same setup. Simulator results apply to that environment; confirm relevant candidates on target hardware before making hardware claims. Do not assume one operating system or device class is faster, or require platforms outside the user's scope.
16
+ ```
17
+ 1 contract → 2 baseline → 3 idea bank → 4 pick a wide round → 5 build (time-boxed)
18
+ → 6 check looks, then measure → 7 find each ceiling → 8 record + research
19
+ → 9 next round (forks, combos, new families, old ideas) → back to 4
20
+ → 10 at the wall: confirm and install the best checked version, then report
21
+ ```
17
22
 
18
- For a specific interaction that causes excess renders, start with `measure_renders` and the relevant `steps`. This may answer the question without a new test route. Compare the same steps after editing with `compareToPrevious: true`. Treat reported render reasons and wasted-render counts as diagnostics to investigate, not proof that a render is unnecessary.
23
+ ### 1. Write the contract
19
24
 
20
- ## Choose a baseline and candidates
25
+ Write these down before you build anything:
21
26
 
22
- Use the current implementation with the representative workload as the baseline for a fix. An empty screen can be a separate control for harness overhead; it does not do equivalent work and cannot establish an improvement to the feature.
27
+ - **The score.** One number with an exact pass rule. For "as many lights as possible", the score is the most lights that hold the target frame rate (say 55 fps) in the main setup.
28
+ - **The load knob.** The param that sets the work, like `?count=`. The score comes from turning it up until the frame rate drops.
29
+ - **What must stay the same.** The look, the motion, the timing, every pattern or mode, the interactions. A variant that changes these can't win.
30
+ - **The setup.** Browser or device, build type, window size and pixel ratio. Keep it fixed all session.
31
+ - **No time limit unless the user gives one.** You keep going until you hit the wall (step 9). If the user does give a time limit, call `start_optimize_session` with it so every Buoy tool result shows the time left.
23
32
 
24
- When comparing approaches needs a harness, use a route whose parameters select isolated variants. Keep workload, visual output and interactions comparable. Include a way to open each variant for inspection. Follow the project's development-only route and build conventions; `__DEV__` guards alone do not prove a route or dependency is absent from the shipped bundle.
33
+ **An app's own test cap is not the ceiling.** If the app's test stops at 50,000, the score has no cap. Measure past it (see step 7). Keep the app's own test as a second check, and confirm both agree below its cap.
25
34
 
26
- Check that the target and bounce routes exist and navigation resets the intended state. A remount does not reset module state or persistent storage. If using `reloadBetweenCases`, confirm that `AutomationResumer` is mounted and that the app can resume the batch after reload.
35
+ This holds even when the user says "your score is the app's test". Maxing out a capped test means you have reached the cap, not the wall. Reaching the cap is when the real search starts: find the uncapped ceiling and keep raising it.
27
36
 
28
- Start with a few candidates supported by the observed problem. Add variants when a result suggests a useful next experiment. Keep the original implementation available for comparison.
37
+ ### 2. Measure the baseline
29
38
 
30
- ## Read settings and run the batch
39
+ - Save the current code as the `baseline` variant.
40
+ - Make one test page where a URL param picks the variant, like `?variant=atlas&count=50000`. Every idea becomes a variant here. **Never edit the current best in place.** Old variants stay, so you can compare and combine them.
41
+ - Check that the load knob really changes the work.
42
+ - Find the baseline's ceiling. Profile it near its limit to see what is slow.
43
+ - Estimate the **hardware limit**: roughly how much work the GPU and CPU could do per frame (pixels drawn, draw calls, bytes sent, math per item). Far below that limit means bold ideas are worth it. Near it means small gains.
31
44
 
32
- Call `get_benchmark_settings` with `pages` equal to the total case count, including the baseline, and the selected `deviceId`. Summarize the returned settings and time estimate. The tool reports either a saved device profile or defaults; do not describe defaults as the user's chosen settings.
45
+ ### 3. Build the idea bank
33
46
 
34
- Use per-run overrides for a temporary change. Call `set_benchmark_settings` only when the user has asked to persist settings. Explain relevant measurement conditions, including warmup, repeats, cooldown, case order and render instrumentation. Consistent settings help comparisons but do not eliminate measurement noise or thermal effects.
47
+ Make `OPTIMIZE_IDEAS.md` in the project. It is your memory. **Read it before every round,** and after any break.
35
48
 
36
- Call `run_benchmark_batch` with `targetRoute`, `deviceId` and `cases`. Each case has a `name` and optional `params`; the first case is the baseline for deltas. The route must interpret those parameters. The call waits for completion or its wait cap, which can take minutes.
49
+ Write down at least 20 different ideas in about 10 minutes. Go wide on purpose, across families:
37
50
 
38
- Read the returned warnings, failures and partial-result note before comparing rows. A timed-out wait can leave runs saving or automation active; inspect `get_automation_status` before starting another batch. Do not count failed or missing cases as successful tests. Check the current screen afterward instead of assuming navigation restored it.
51
+ - **Other technology.** For drawing: DOM, CSS animations, Canvas 2D, WebGL or WebGPU, Skia (`drawAtlas`, `drawVertices`, runtime shaders, pictures), one big shader, a sprite sheet, a video, a worker with OffscreenCanvas, native views. For lists: windowing, recycling, flattening.
52
+ - **Doing less work.** Draw once and reuse. Cache. Batch into one draw call. Move math to the GPU. Compute once per item instead of once per pixel. Skip work nobody can see.
53
+ - **Hacks.** Draw blur at low resolution and scale it up. Bake glows into textures. Lookup tables. Pack data into textures. Split still and moving layers. Spatial grids.
54
+ - **The runtime.** No React work per frame. No allocations per frame. No layout reads after writes. Better blend modes and texture formats.
39
55
 
40
- ## Check behavior and interpret measurements
56
+ Give each idea one row: ID, family, parent ideas, what it is, why it might win, the bottleneck it attacks, rough build time, status, score range, report IDs and the next test. Statuses: `untried`, `building`, `valid`, `invalid` (looks or acts wrong), `failed-build`, `deferred` (with what would make it worth another try), `blocked`.
41
57
 
42
- Inspect visual behavior and the relevant interactions using available screenshots, UI tools and project checks. Ask the user when the required appearance is subjective or cannot be checked with available tools. Record unverified behavior explicitly. Exclude a visibly incorrect variant from performance recommendations, while retaining its result as evidence of what was tested.
58
+ Keep a **current best** line at the top. Also keep the best variant of each family, not just the overall leader. A family that loses now can win after a fork.
43
59
 
44
- Compare the available UI/JS FPS, CPU, memory and jank measurements together. Check missing values before interpreting a low value or an absent warning. The report's per-metric leaders and `balanced` list are summaries of recorded data. `balanced` means no configured risk flag was triggered; missing measurements can also produce no flag. It does not establish that a case meets every performance requirement.
60
+ **Never delete an idea.** A loss at one load, or a bug, doesn't prove an idea is bad. Write what happened and move on.
45
61
 
46
- Use `get_batch_report` with a `batchId` to read a saved batch. Use `compare_reports` with `reportIdA` and `reportIdB` to compare two saved runs; report IDs are different from batch IDs. Inspect available averages, p95 values and render data in the context of the same workload. A lower wasted-render count alone does not prove a fix or an overall speed improvement.
62
+ ### 4. Pick a wide round
47
63
 
48
- Explain trade-offs against the user's requirements. For example, a candidate may use less memory while producing more frame drops. Repeat close or noisy comparisons before choosing between them. Do not translate CPU measurements into battery-life claims without separate evidence.
64
+ - **First round:** about 6 ideas from at least 3 families. Include at least one bold idea that changes the technology.
65
+ - **Every round tests several ideas,** never just one tweak. At least one must be new, untried or deferred, from outside the current best's family.
66
+ - **Later rounds:** spend about half the effort on forks and combinations of what's winning. Spend the other half on new families, ideas from research and deferred ideas. Don't let one family eat all your time.
67
+ - Keep one variant that scales differently when the early ranking isn't clear. It may win at a higher load.
68
+ - Keep the baseline and the current best in every comparison.
69
+ - Write one line on why each pick is worth its build time.
49
70
 
50
- ## Iterate and report
71
+ ### 5. Build each one, time-boxed
51
72
 
52
- Keep candidates that meet the behavior requirements and offer a useful measured trade-off. Refine a specific hypothesis or combine compatible changes, then rerun the same workload. Stay within the agreed scope and time budget; seek direction when a decision would materially change either.
73
+ - Give each idea a build budget, about 5 to 8 minutes. A bold idea can get more. If a build blows its budget, mark it `deferred` with what went wrong and move on.
74
+ - Smoke-test each variant as soon as it runs.
75
+ - **Every picked idea gets an outcome:** measured, `invalid`, `failed-build`, `deferred` or `blocked`. Never drop one silently.
76
+ - Inactive variants must not run work in the background.
53
77
 
54
- Stop when the requested issue is resolved with appropriate checks, the agreed budget is reached, or further measurements require unavailable hardware or input. Report the selected implementation, baseline, workload, device/build conditions, settings, observed differences, failures and remaining uncertainty. Distinguish completed checks from proposed follow-up tests. Validation on one device does not establish results for all devices on that platform.
78
+ ### 6. Check looks, then measure
55
79
 
56
- ## When the target is a browser
80
+ **A fast variant that looks wrong is worthless.** Check before you trust any number.
57
81
 
58
- `list_devices` shows `platform: "web"` for a browser tab. There, Bench measures the page's own main thread. The report uses a browser table with different columns.
82
+ - If the user is around, ask them to look.
83
+ - If not, check it yourself with `screenshot_web_variants` (web) or screenshots (app). Put the baseline first; the tool flags variants that differ from it far more than the rest. Use the same count and settings for every case. Take several moments, so frozen or broken motion shows. Check a sparse scene, where each item is visible, and a dense one. Compare each variant with the baseline side by side: shapes, colors, glow, layout, motion. Watch it run live too. A still image can't prove motion.
84
+ - Mark wrong ones `invalid` and say what was wrong.
59
85
 
60
- Read it this way:
86
+ Then measure every valid variant the same way. Screen them at a few loads (one low, one near the current best's ceiling, one above), because a variant can lose small and win big. Before you prune, raise the load so the top ones don't all tie at the frame cap.
61
87
 
62
- - FPS is the main thread's frame rate from `requestAnimationFrame`. It can't see frames the GPU or compositor drops later. It shows once, because a page has one main thread.
63
- - p95 frame gap, worst gap and long frames come from the time between frames in that run. A gap over 1.5 frames missed at least one frame.
64
- - Long tasks % is the share of the run the main thread spent in tasks over 50 ms. It is not CPU use, and it misses many shorter tasks.
65
- - A metric the browser can't measure shows as —. Safari and Firefox have no long tasks or JS heap. A — can't win or lose.
66
- - "Why frames were slow" names the scripts behind slow frames, plus style, layout and forced layout time. Start your next idea there. It only sees frames over 50 ms, and it can't see work in workers.
67
- - "DOM grew every run" is a hint of a leak, not proof. Confirm it with a heap snapshot after the screen unmounts.
68
- - "Too close to call" means the top cases overlap across runs. Run more repeats before you pick.
69
- - A page load table shows up only when each case ran on its own fresh page load (the batch reloads between cases with `reloadBetweenCases`). Use it for load-time work.
88
+ ### 7. Find each ceiling
70
89
 
71
- Set up the run:
90
+ The ceiling is the score.
72
91
 
73
- - Keep the tab in front and the window the same size. A run with a hidden tab or a resize is saved as a failure.
74
- - Rank on a production build when you can. Dev React is slower and can pick a different winner. Put the test page behind its own bench flag, not just `__DEV__`. Keep it out of public builds. If you ranked on a dev build, say so in the report.
75
- - Keep DevTools closed while ranking. Record the screen size and pixel ratio. A pixel ratio of 3 draws 9 times the canvas pixels of 1.
76
- - Pass `captureRenders: false` to `run_benchmark_batch` while ranking. Render capture adds work, and the cost differs by variant. Turn it on in a separate pass to explain a slow case.
92
+ - On web, use `find_web_ceiling`. It raises the load about 30% a step until the frame rate misses the target, then narrows the gap to a few percent. It has no cap by default (up to 1,000,000,000).
93
+ - On React Native, ramp the count with `run_benchmark_batch` cases.
94
+ - Record the highest pass and the lowest miss. A single miss can be noise; run the boundary again before you call it.
95
+ - If nothing missed, the score is "at least" that load. Raise the max and run again.
96
+ - **Rank on the score.** Dropped frames, frame times, CPU, memory and profiles explain why a variant won. They only rank when the contract says so.
77
97
 
78
- Two tools can run a web batch:
98
+ ### 8. Record and research
79
99
 
80
- - `run_benchmark_batch` runs inside the app's own tab through Bench. Use it when the app has Bench and the user's browser is open.
81
- - `run_web_benchmark` opens the app in its own headless Chromium. It needs Playwright in the project, but no Buoy setup in the app. Each run gets a fresh browser context, and the tab is always in front. It adds Chrome's own main-thread, script, style and layout time. Pass a `readySelector` the page adds once it has drawn.
100
+ - Write every outcome into the bank: the score range, what the profile showed, and **why** it won or lost. Keep what you saw separate from what you guess.
101
+ - Research for about 3 minutes after **every** round, even one where every build failed. Search the web, docs, source code and issues for the bottleneck you just saw. Examples: "WebGL draw 1 million sprites", "Skia drawAtlas performance", "CanvasKit flush cost". Add new ideas and their sources to the bank.
102
+ - Keep a research log in the bank: each round's search, the sources, what you learned and the experiment it suggests.
103
+ - After a big win, profile again. The bottleneck moves.
82
104
 
83
- In `run_web_benchmark`, rank on Dropped % first, and only across variants doing the same work. It counts frames Chrome dropped or only partly made that hurt smoothness, from its own count. Deltas are in percentage points (pp). Headless Chrome shows nothing on a screen, so these are frames Chrome produced, not frames a person saw. Shown FPS only counts frames with new content, so an animation that changes 16 times a second needs just 16. Don't compare Shown FPS across variants that update at different rates. Main-thread FPS can be low while nothing drops. That happens when the compositor runs a CSS animation and JavaScript is slow. The animation looks smooth, but taps will feel slow.
105
+ ### 9. Next round
84
106
 
85
- To test taps, typing or scrolling, pass `steps`. For example: `[{"wait": 300}, {"click": "#add"}, {"type": {"selector": "#search", "text": "lights"}}]`. The report then shows input response for each case. Under 200 ms is good. Click targets must be on screen when the run starts and must not move. Leave at least 300 ms after the last step (a `wait`), so its paint lands inside the run. If not, the report says the input timing is incomplete and won't pass an input budget.
107
+ Build the next round from forks of the winners, combinations of tricks that worked, new ideas from research and untried or deferred ideas from the bank. Then go back to step 4.
86
108
 
87
- End every step list with an `expect` step, like `{"expect": {"selector": "#cart-count", "text": "1"}}`. A run that skipped the work then fails instead of ranking as fast.
109
+ - If every idea in a round lost, pick the next best ideas from the bank. Write more ideas if the bank runs thin.
110
+ - **A plateau means change direction, not stop.** Try another family, a deferred idea with new evidence, or attack the current bottleneck another way.
111
+ - Keep going until you hit the wall. Stop sooner only if the user says so, their time limit runs out, or something outside your control blocks all useful work.
88
112
 
89
- When the user gave limits, pass them as `budgets`. For "almost no dropped frames, taps under 200 ms", use `{"droppedPct": 2, "inputMs": 200}`. The report says which cases meet every limit. Input and freeze limits use the worst run, and an input limit needs a click, key or typing step. A limit that wasn't measured counts as not met.
113
+ Tell the user where you are each round in a line or two: round number, what you tried, the new best score.
90
114
 
91
- Mark a case that does different work as a control: `{"name": "empty", "control": true}`. It shows in the table, but it is never the baseline and never a candidate.
115
+ **The wall.** You have hit it only when all of these are true:
92
116
 
93
- If the two best cases come out too close to call, pass `extraRoundsWhenClose: 2`. It adds runs only for those two. Pass `outFile` to save the whole result, so you can read it again later.
117
+ 1. Your score is an uncapped ceiling with a real miss, not "at least" and not the app's own cap.
118
+ 2. The last 3 rounds each raised it by less than 3%.
119
+ 3. Those 3 rounds tried at least 3 different families of ideas, not 3 tweaks of one.
120
+ 4. You researched after each of those rounds and wrote what you found.
121
+ 5. Every idea still in the bank has a written reason it won't help. "It changes the look" or "too slow" only counts if you measured it. A guess doesn't.
94
122
 
95
- Test in this order, and tell the user which step you are on:
123
+ If any one is false, you are not at the wall. Start another round. Hitting a good number, writing `RESULTS.md` or running long are never reasons to stop.
96
124
 
97
- 1. Desktop Chrome, with `run_web_benchmark` and `cpuSlowdown: [1]`.
98
- 2. The same cases with `cpuSlowdown: [4, 6]`. This slows the CPU only, not the GPU, memory or heat.
99
- 3. A real iPhone in Safari.
100
- 4. A real mid-range Android phone in Chrome.
125
+ Work in one long session. Don't end your turn to wait for a long command. Wait for it, then keep going.
101
126
 
102
- For steps 3 and 4, use the app's own Bench and `run_benchmark_batch`. Start the dev server so other devices can reach it (for Vite, `vite --host`). Open its network URL on the phone, on the same Wi-Fi as this computer. In development, the page finds the broker at its own host on port 42831, so the phone shows up in `list_devices`. Keep the phone awake and the tab in front, or runs fail as hidden. This is a dev build, so say so in the report. A production build on a phone needs `externalSync` settings; see the web install guide.
103
- 5. Desktop Firefox and Safari, if the user supports them.
127
+ ### 10. At the wall: lock in the best
104
128
 
105
- Take a quick look in Safari early, so you don't spend rounds on an idea that breaks there.
129
+ When you hit the wall:
106
130
 
107
- Ideas to try for heavy drawing on web:
131
+ - Measure the top 2 or 3 again with fresh runs, and confirm the ceilings.
132
+ - Put the winner on the real screen. Check its looks and interactions again there.
133
+ - Run the app's own test too, if it has one.
134
+ - Leave the best checked version in place, never an untested experiment.
108
135
 
109
- - Canvas2D with pre-drawn sprites.
110
- - WebGL with one draw call for many items.
111
- - An OffscreenCanvas in a worker.
112
- - CSS or Web Animations API transforms. The compositor can run these.
113
- - List windowing, and `content-visibility` for things off screen.
136
+ Writing a report or `RESULTS.md` along the way is fine. Update it after each win. It doesn't end the session; the wall does.
114
137
 
115
- Picking a winner on web:
138
+ Then report:
116
139
 
117
- Don't pick a winner when any of these is true:
140
+ - Baseline and final score, with the range, and the exact setup.
141
+ - The winning approach and the tricks that mattered most.
142
+ - What each family scored, and the ideas still untried.
143
+ - How you checked the looks.
144
+ - Why you stopped: show the wall check (the last 3 rounds, their families, the research, and why each idea left in the bank won't help). Say "best found", not "the maximum possible".
118
145
 
119
- - You can't show both cases do the same work, or you don't know which build was served.
120
- - Runs failed, frame data is missing, or a case has fewer than 2 good runs.
121
- - The Dropped % ranges touch or overlap, or the middle values are within 1.5 points.
122
- - An input limit matters and its timing was incomplete or unknown. A missed or unknown budget can't back a "meets the goal" pick.
146
+ ## Rules
123
147
 
124
- When you do pick, say all of this with it:
125
-
126
- - It's a smoothness pick for this build on this desktop setup. Give the Chrome mode, screen size, scale, CPU slowdown, run count, recorded time, Dropped % for each case, their ranges and the gap.
127
- - Headless Chrome made the frames but showed nothing on a screen. This doesn't prove phone speed.
128
- - Input times come from the scripted steps, not real users. A missing time is unknown. The tie rule is a guide, not proof.
129
-
130
- Traps on react-native-web:
131
-
132
- - `Animated` with `useNativeDriver` still runs on the main thread.
133
- - Reanimated worklets run as plain JS on the main thread.
134
- - Skia on web loads CanvasKit, a large WASM file. Count its load time and first draw.
135
-
136
- ## Tool reference
137
-
138
- | Tool | Use |
139
- | --- | --- |
140
- | `list_devices` | Select the connected device and inspect its tools. |
141
- | `measure_renders` | Measure a supported interaction before and after a change. |
142
- | `get_benchmark_settings` / `set_benchmark_settings` | Read the profile and estimate, or explicitly persist settings. |
143
- | `run_benchmark_batch` / `get_automation_status` | Run the case matrix and inspect automation status. |
144
- | `run_web_benchmark` | Run web cases in a fresh headless Chromium, with optional CPU slowdown. |
145
- | `get_batch_report` | Read a saved batch using its `batchId`. |
146
- | `compare_reports` | Compare saved runs using `reportIdA` and `reportIdB`. |
147
- | `navigate` | Open a supported app route using `path`. |
148
-
149
- Keep summaries focused on the decision and its evidence. Include report IDs so results can be revisited without pasting every sample into the conversation.
148
+ - Every variant does the same work: same items, same look, same behavior. Drawing fewer items, skipping frames, or anything that makes the meter read high without the user seeing it, is cheating and can't win.
149
+ - Don't change the app's own test, its clock or its frame counter.
150
+ - Don't ask the user to approve each round. Ask only when a choice changes the scope.
151
+ - Stay on the user's platform. Test other platforms only when they're in scope.