Skip to content

Add a dependency-free shared-WebGL feasibility harness - #417

Open
Alek99 wants to merge 3 commits into
mainfrom
agent/shared-webgl-spike
Open

Add a dependency-free shared-WebGL feasibility harness#417
Alek99 wants to merge 3 commits into
mainfrom
agent/shared-webgl-spike

Conversation

@Alek99

@Alek99 Alek99 commented Jul 31, 2026

Copy link
Copy Markdown
Member

Summary

Adds a standalone, dependency-free A/B harness for the blit-mode shared-context design proposed in #407.

The shared path uses one detached WebGL2 context to render synthetic chart clients sequentially, then synchronously presents each completed frame into that chart's visible 2D canvas. The native baseline renders the same workload with one WebGL2 context per canvas.

This is a feasibility experiment only. It does not modify xy's production renderer and does not close #407.

What the harness exercises

  • 50 continuously rendered chart clients
  • explicit WebGL state reset and deliberately poisoned state between draws
  • per-chart geometry and lazy pick targets
  • asymmetric pixel canaries for stale, flipped, cropped, or cross-chart frames
  • encoded-ID picking checks
  • grow-only host backing dimensions and non-zero source cropping
  • forced shared-context loss and resource reconstruction
  • shared and native A/B modes
  • no packages, imports, or network requests

Exploratory result

One run in Chrome 150.0.0.0 using ANGLE's Metal renderer on an Apple M5 Pro produced:

Shared host Native contexts
Requested clients 50 50
Live clients 50 16
Live WebGL contexts 1 16
Canary checks 50 / 50 16 / 16 surviving
Exact-ID pick checks 150 / 150 48 / 48 surviving
Full-set result pass expected failure: 34 unavailable

The shared context was deliberately lost and restored. All 50 synthetic client resources were rebuilt and the complete checks passed again.

These numbers are environment-specific. Timing fields measure JavaScript submission cost, not completed GPU work, and should not be interpreted as a shared-vs-native speedup because the native arm rendered only its 16 surviving clients.

Scope and limitations

This does not yet validate the design against xy's production ChartView.

In particular, it does not cover:

  • xy marks, LOD, kernel replies, exports, selection, or renderer caches
  • settled pan/zoom preservation during context recovery
  • shared geometry/data caches
  • visibility-aware or dirty-client scheduling
  • the single full-page-canvas compositing mode
  • the complete WebGL state surface used by xy
  • 50 cards fitting in the viewport at once

The exact-ID assertions isolate one requested vertex. The normal dense hover pass renders all points and returns the last covered sample, so these assertions do not prove production interaction semantics.

The repository's benchmark-harness test suite validates metadata/report infrastructure; this standalone browser experiment is functionally verified through its own browser controls and automation surface, not that Python test suite.

Validation

  • node --check benchmarks/shared_webgl_spike/experiment.js
  • JSON parse and git diff --check
  • make check-benchmark-harness — 108 passed
  • uv run --with pre-commit pre-commit run --all-files
  • uv run ruff check .
  • uv run ruff format --check .
  • shared browser mode: 50 clients through one WebGL2 context; 50 canaries and 150 picks passed
  • native browser mode: 16/50 contexts survived; 16 canaries and 48 picks passed on survivors
  • shared context loss/restoration followed by a complete verification rerun
  • no browser console warnings or errors during the verification run

Next step

Use this result to inform the phased production work in #407:

  1. Add an all-visible xy dashboard baseline.
  2. Introduce a GLHost abstraction without changing behavior.
  3. Make render and pick targets explicit.
  4. Attach multiple ChartView instances to one host.

Refs #407.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added an interactive browser-based benchmark comparing shared and per-chart WebGL rendering.
    • Added configurable chart counts and rendering modes, live performance metrics, validation checks, context recovery testing, and benchmark snapshots.
    • Added responsive controls, status reporting, visual chart grids, and reduced-motion support.
  • Documentation

    • Documented setup, controls, automation, test coverage, result interpretation, limitations, and architectural scope.
    • Included captured benchmark results and clarified that they provide architectural evidence, not production performance claims.
  • Tests

    • Added validation coverage for shared WebGL benchmark reports, profiles, metrics, correctness, recovery, and environment metadata.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Added a dependency-free WebGL2 benchmark harness. It compares shared and native contexts across rendering, picking, recovery, and performance checks. It includes a browser interface, automation APIs, report validation, captured Chromium results, and production-scope documentation.

Changes

Shared WebGL experiment

Layer / File(s) Summary
Rendering primitives and chart views
benchmarks/shared_webgl_spike/experiment.js
Added WebGL shaders, resource management, picking targets, canary rendering, animated chart views, resizing, and tooltip handling.
Shared and native rendering backends
benchmarks/shared_webgl_spike/experiment.js
Added shared-context and per-chart native backends with rendering, statistics, context cycling, restoration, and cleanup.
Experiment controls and verification
benchmarks/shared_webgl_spike/experiment.js, benchmarks/shared_webgl_spike/index.html
Added application controls, render scheduling, metrics, deterministic verification, benchmarking, snapshots, context cycling, and the window.__sharedWebglExperiment API.
Report validation and benchmark evidence
scripts/verify_benchmark_report.py, tests/test_verify_benchmark_report.py, benchmarks/shared_webgl_spike/results/*
Added shared-webgl-spike report validation, validation tests, and Chromium results for shared and native profiles.
Interface and scope documentation
benchmarks/shared_webgl_spike/styles.css, benchmarks/shared_webgl_spike/README.md, benchmarks/shared_webgl_spike/RESULTS.md, benchmarks/README.md, spec/design-dossier.md, spec/process/contributing.md
Added responsive styling, experiment documentation, result interpretation, benchmark-kind documentation, and production integration boundaries.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ExperimentApp
  participant ChartView
  participant SharedBackend
  participant WebGL2Context
  participant Canvas2D
  ExperimentApp->>ChartView: schedule chart updates
  ChartView->>SharedBackend: render chart and process picking
  SharedBackend->>WebGL2Context: submit shared WebGL2 draws
  SharedBackend->>Canvas2D: copy presentation crops
  WebGL2Context-->>SharedBackend: report context loss or restoration
  SharedBackend-->>ExperimentApp: expose statistics and recovery state
Loading
🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR adds only a feasibility harness and does not implement the production GLHost and renderer changes required by [#407]. Implement the production shared-WebGL host and chart integration, or link this PR to a feasibility-spike issue with suitable acceptance criteria.
Out of Scope Changes check ⚠️ Warning The shared-WebGL harness changes are in scope, but adding workflow-native to contributor documentation appears unrelated to [#407]. Remove the unrelated workflow-native documentation change or provide a direct requirement linking it to the shared-WebGL feasibility harness.
Docstring Coverage ⚠️ Warning Docstring coverage is 4.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: a dependency-free shared-WebGL feasibility harness.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/shared-webgl-spike

Comment @coderabbitai help to get the list of available commands.

@codspeed-hq

codspeed-hq Bot commented Jul 31, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 109 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing agent/shared-webgl-spike (276cbf3) with main (a2f2976)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@Alek99
Alek99 marked this pull request as ready for review July 31, 2026 20:19

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (5)
benchmarks/shared_webgl_spike/experiment.js (5)

1313-1316: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The third coverageComplete condition is always true.

Line 1316 asserts testCharts.length * indices.length === stats.requestedCharts * indices.length. Line 1315 already asserts testCharts.length === stats.requestedCharts. The third comparison is the second one multiplied by a constant, so it adds no coverage. Remove it.

♻️ Proposed simplification
       const coverageComplete =
-        rendered === stats.requestedCharts &&
-        testCharts.length === stats.requestedCharts &&
-        testCharts.length * indices.length === stats.requestedCharts * indices.length;
+        rendered === stats.requestedCharts && testCharts.length === stats.requestedCharts;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@benchmarks/shared_webgl_spike/experiment.js` around lines 1313 - 1316, Remove
the redundant third comparison from the coverageComplete expression in the
surrounding benchmark logic; retain the rendered count check and the
testCharts.length === stats.requestedCharts check unchanged.

837-855: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

cycleResolve(true) reports success even when the rebuild fails.

The catch block sets the chart state to "error" and logs the failure. Line 852 then resolves the cycle promise with true. ExperimentApp.cycleContext treats a truthy result as a successful restore. SharedBackend._handleContextRestored resolves with fullyRebuilt instead.

The subsequent verify() call still fails, so the reported outcome stays correct. Resolve with the actual rebuild result for symmetry.

♻️ Proposed change
     chart.canvas.addEventListener("webglcontextrestored", () => {
       if (record.destroying) return;
+      let rebuilt = false;
       try {
         record.programs = createProgramSet(gl);
         record.lossExtension = gl.getExtension("WEBGL_lose_context");
         createChartGpu(gl, chart);
         chart.pickTarget = null;
         chart.setState("live");
         this.app.needsDraw = true;
+        rebuilt = true;
         this.app.onContextRestored(`Native context ${chart.index + 1} restored.`);
       } catch (error) {
         chart.setState("error", "restore failed");
         this.app.log(`Native chart ${chart.index + 1} restore failed: ${error.message}`);
       }
       if (record.cycleResolve) {
-        record.cycleResolve(true);
+        record.cycleResolve(rebuilt);
         record.cycleResolve = null;
       }
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@benchmarks/shared_webgl_spike/experiment.js` around lines 837 - 855, Update
the context-restoration handler’s cycleResolve outcome to reflect whether the
rebuild succeeded: track a success flag initialized false, set it true only
after the try block completes, and resolve cycleResolve with that flag instead
of always true. Preserve the existing error state and logging in the catch
block.

174-223: 🎯 Functional Correctness | 🔵 Trivial | 🏗️ Heavy lift

The state-stress check is a closed loop, so it cannot detect a missing reset.

poisonState dirties exactly the state that beginPass restores: BLEND, DEPTH_TEST, CULL_FACE, colorMask, viewport, scissor, activeTexture, the VAO binding, and the current program. The canary therefore always passes, even if beginPass omits a state that a real client would leak.

Several persistent WebGL2 states are not reset here: frontFace, depthFunc, blendColor, stencilFunc, polygonOffset, readBuffer, drawBuffers, the transform-feedback binding, UNIFORM_BUFFER bindings, and UNPACK_FLIP_Y_WEBGL / UNPACK_PREMULTIPLY_ALPHA_WEBGL. The production GLHost in #407 will need those, so add them to poisonState to make the harness result transferable.

♻️ Suggested extension of the poison set
 function poisonState(gl, programs, width, height) {
   gl.disable(gl.BLEND);
   gl.enable(gl.DEPTH_TEST);
   gl.enable(gl.CULL_FACE);
   gl.colorMask(false, false, false, false);
+  gl.frontFace(gl.CW);
+  gl.depthFunc(gl.GREATER);
+  gl.blendColor(1, 0, 1, 0.5);
+  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
+  gl.pixelStorei(gl.PACK_ALIGNMENT, 1);
   gl.viewport(1, 2, Math.max(1, width - 3), Math.max(1, height - 4));

Add the matching resets to beginPass.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@benchmarks/shared_webgl_spike/experiment.js` around lines 174 - 223, Extend
poisonState with persistent WebGL2 state that beginPass does not currently
reset, including frontFace, depthFunc, blendColor, stencilFunc, polygonOffset,
readBuffer, drawBuffers, transform-feedback binding, UNIFORM_BUFFER bindings,
and UNPACK_FLIP_Y_WEBGL/UNPACK_PREMULTIPLY_ALPHA_WEBGL. Add corresponding
restoration calls to beginPass so the canary detects omissions while each pass
still starts from the expected baseline.

1367-1382: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

benchmark and cycleContext do not restore control state when an error occurs. Both methods disable a button and mutate UI state, then perform asynchronous work without a finally block. If any awaited call or getStats() raises, the button stays disabled for the rest of the session. verify() at lines 1274-1365 already uses try/finally for exactly this reason; apply the same pattern.

  • benchmarks/shared_webgl_spike/experiment.js#L1367-L1382: wrap the body after const start = performance.now(); in try, and move this.ui.streaming.checked = wasStreaming;, this.ui.benchmark.disabled = false;, and this.needsDraw = true; into a finally block.
  • benchmarks/shared_webgl_spike/experiment.js#L1457-L1468: wrap the body after this.ui.cycle.disabled = true; in try, and move this.ui.cycle.disabled = false; into a finally block.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@benchmarks/shared_webgl_spike/experiment.js` around lines 1367 - 1382, Update
benchmark and cycleContext in benchmarks/shared_webgl_spike/experiment.js at
lines 1367-1382 and 1457-1468: wrap each asynchronous body in try/finally so
benchmark always restores streaming state, re-enables the benchmark button, and
sets needsDraw, while cycleContext always re-enables its button; preserve the
existing success behavior and ensure cleanup runs when awaited calls or
getStats() throw.

965-995: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Wire or remove backend disposal.

No code path calls SharedBackend.dispose or NativeBackend.dispose. navigate reloads the document, and the automation API does not expose disposal. Add disposal before navigation, expose it for automation, or remove the dead methods. NativeBackend.cycleContext also does not retain its timers for cancellation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@benchmarks/shared_webgl_spike/experiment.js` around lines 965 - 995, Address
the unused backend disposal paths by either invoking SharedBackend.dispose and
NativeBackend.dispose before navigate, exposing disposal through the automation
API, or removing the dead methods. Also update NativeBackend.cycleContext to
retain its restore and timeout timer handles so dispose can cancel them safely.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@benchmarks/shared_webgl_spike/experiment.js`:
- Around line 394-396: Update sampleMatches so the frame-counter channel encoded
from chart.frameByte is compared exactly, while retaining the existing tolerance
for color channels affected by drawImage conversion drift. Ensure stale frames
differing by 1–3 renders cannot pass the canary.
- Around line 619-627: Update prepareVerification to size capacity using the
tallest chart in this.charts, including the existing non-zero source-Y padding,
before calculating cropOffsetPixels. Ensure the returned offset is based on the
final capacityHeight that render will use for every chart, while preserving the
current early return behavior.

In `@benchmarks/shared_webgl_spike/styles.css`:
- Around line 17-23: Update the affected CSS declarations in the stylesheet,
including the repeated locations, to comply with the configured Stylelint rules
while preserving the existing visual behavior and values. Verify the stylesheet
lint command passes with no violations.

---

Nitpick comments:
In `@benchmarks/shared_webgl_spike/experiment.js`:
- Around line 1313-1316: Remove the redundant third comparison from the
coverageComplete expression in the surrounding benchmark logic; retain the
rendered count check and the testCharts.length === stats.requestedCharts check
unchanged.
- Around line 837-855: Update the context-restoration handler’s cycleResolve
outcome to reflect whether the rebuild succeeded: track a success flag
initialized false, set it true only after the try block completes, and resolve
cycleResolve with that flag instead of always true. Preserve the existing error
state and logging in the catch block.
- Around line 174-223: Extend poisonState with persistent WebGL2 state that
beginPass does not currently reset, including frontFace, depthFunc, blendColor,
stencilFunc, polygonOffset, readBuffer, drawBuffers, transform-feedback binding,
UNIFORM_BUFFER bindings, and UNPACK_FLIP_Y_WEBGL/UNPACK_PREMULTIPLY_ALPHA_WEBGL.
Add corresponding restoration calls to beginPass so the canary detects omissions
while each pass still starts from the expected baseline.
- Around line 1367-1382: Update benchmark and cycleContext in
benchmarks/shared_webgl_spike/experiment.js at lines 1367-1382 and 1457-1468:
wrap each asynchronous body in try/finally so benchmark always restores
streaming state, re-enables the benchmark button, and sets needsDraw, while
cycleContext always re-enables its button; preserve the existing success
behavior and ensure cleanup runs when awaited calls or getStats() throw.
- Around line 965-995: Address the unused backend disposal paths by either
invoking SharedBackend.dispose and NativeBackend.dispose before navigate,
exposing disposal through the automation API, or removing the dead methods. Also
update NativeBackend.cycleContext to retain its restore and timeout timer
handles so dispose can cancel them safely.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: de17eb6c-11a5-4568-8172-76fd9cc6d52c

📥 Commits

Reviewing files that changed from the base of the PR and between d505ef5 and d635400.

📒 Files selected for processing (8)
  • benchmarks/README.md
  • benchmarks/shared_webgl_spike/README.md
  • benchmarks/shared_webgl_spike/RESULTS.md
  • benchmarks/shared_webgl_spike/experiment.js
  • benchmarks/shared_webgl_spike/index.html
  • benchmarks/shared_webgl_spike/results/chromium-2026-07-31.json
  • benchmarks/shared_webgl_spike/styles.css
  • spec/design-dossier.md

Comment thread benchmarks/shared_webgl_spike/experiment.js
Comment thread benchmarks/shared_webgl_spike/experiment.js
Comment thread benchmarks/shared_webgl_spike/styles.css Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 8 files

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread benchmarks/shared_webgl_spike/RESULTS.md Outdated
Comment thread benchmarks/shared_webgl_spike/experiment.js
Comment thread benchmarks/shared_webgl_spike/styles.css
Comment thread benchmarks/shared_webgl_spike/results/chromium-2026-07-31.json
Comment thread benchmarks/shared_webgl_spike/experiment.js Outdated
Comment thread benchmarks/shared_webgl_spike/experiment.js Outdated
Comment thread benchmarks/shared_webgl_spike/styles.css Outdated
Comment thread benchmarks/shared_webgl_spike/index.html Outdated
Comment thread benchmarks/shared_webgl_spike/results/chromium-2026-07-31.json Outdated
Comment thread benchmarks/shared_webgl_spike/index.html Outdated
@Alek99
Alek99 marked this pull request as draft July 31, 2026 21:01
@Alek99
Alek99 marked this pull request as ready for review July 31, 2026 21:07

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 8 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="benchmarks/shared_webgl_spike/styles.css">

<violation number="1" location="benchmarks/shared_webgl_spike/styles.css:619">
P3: The dense chart grid overflows the page at viewport widths 941–952px because eight 108px minimum columns plus gaps need 913px. Allowing these eight tracks to shrink removes the narrow breakpoint gap.</violation>
</file>

<file name="benchmarks/shared_webgl_spike/results/chromium-2026-07-31.json">

<violation number="1" location="benchmarks/shared_webgl_spike/results/chromium-2026-07-31.json:2">
P2: This published machine-readable result cannot pass the repository's required benchmark-artifact verifier: schema 1, the unregistered `shared-webgl-spike` kind, and incomplete standard environment metadata are rejected. Adding validator support for this result shape and emitting the required schema/environment fields would keep the evidence artifact independently verifiable.</violation>
</file>

<file name="benchmarks/shared_webgl_spike/index.html">

<violation number="1" location="benchmarks/shared_webgl_spike/index.html:120">
P3: Context-loss progress and renderer failure messages are not announced because the dynamically updated `#status` element is not a live region. Consider exposing it as a status region and deduplicating the periodic identical updates in `setStatus()` to avoid repeated announcements.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

@@ -0,0 +1,112 @@
{
"schema_version": 1,

@cubic-dev-ai cubic-dev-ai Bot Jul 31, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This published machine-readable result cannot pass the repository's required benchmark-artifact verifier: schema 1, the unregistered shared-webgl-spike kind, and incomplete standard environment metadata are rejected. Adding validator support for this result shape and emitting the required schema/environment fields would keep the evidence artifact independently verifiable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At benchmarks/shared_webgl_spike/results/chromium-2026-07-31.json, line 2:

<comment>This published machine-readable result cannot pass the repository's required benchmark-artifact verifier: schema 1, the unregistered `shared-webgl-spike` kind, and incomplete standard environment metadata are rejected. Adding validator support for this result shape and emitting the required schema/environment fields would keep the evidence artifact independently verifiable.</comment>

<file context>
@@ -0,0 +1,112 @@
+{
+  "schema_version": 1,
+  "kind": "shared-webgl-spike",
+  "source_issue": "https://github.com/reflex-dev/xy/issues/407",
</file context>
Fix with cubic

Comment thread benchmarks/shared_webgl_spike/experiment.js
Comment thread benchmarks/shared_webgl_spike/experiment.js Outdated
Comment thread benchmarks/shared_webgl_spike/experiment.js Outdated
Comment thread benchmarks/shared_webgl_spike/experiment.js
}

.chart-grid--dense {
grid-template-columns: repeat(8, minmax(108px, 1fr));

@cubic-dev-ai cubic-dev-ai Bot Jul 31, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The dense chart grid overflows the page at viewport widths 941–952px because eight 108px minimum columns plus gaps need 913px. Allowing these eight tracks to shrink removes the narrow breakpoint gap.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At benchmarks/shared_webgl_spike/styles.css, line 619:

<comment>The dense chart grid overflows the page at viewport widths 941–952px because eight 108px minimum columns plus gaps need 913px. Allowing these eight tracks to shrink removes the narrow breakpoint gap.</comment>

<file context>
@@ -0,0 +1,738 @@
+  }
+
+  .chart-grid--dense {
+    grid-template-columns: repeat(8, minmax(108px, 1fr));
+  }
+}
</file context>
Fix with cubic

Comment thread benchmarks/shared_webgl_spike/styles.css Outdated

<section class="result-strip">
<div id="health-dot" class="health-dot" data-state="working"></div>
<p id="status">Building the shared renderer…</p>

@cubic-dev-ai cubic-dev-ai Bot Jul 31, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Context-loss progress and renderer failure messages are not announced because the dynamically updated #status element is not a live region. Consider exposing it as a status region and deduplicating the periodic identical updates in setStatus() to avoid repeated announcements.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At benchmarks/shared_webgl_spike/index.html, line 120:

<comment>Context-loss progress and renderer failure messages are not announced because the dynamically updated `#status` element is not a live region. Consider exposing it as a status region and deduplicating the periodic identical updates in `setStatus()` to avoid repeated announcements.</comment>

<file context>
@@ -0,0 +1,149 @@
+
+      <section class="result-strip">
+        <div id="health-dot" class="health-dot" data-state="working"></div>
+        <p id="status">Building the shared renderer…</p>
+        <p id="last-check" class="result-strip__aside" role="status">Checks not run yet</p>
+      </section>
</file context>
Fix with cubic

Comment thread benchmarks/shared_webgl_spike/experiment.js
Comment thread benchmarks/shared_webgl_spike/experiment.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (5)
scripts/verify_benchmark_report.py (2)

2034-2049: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Consider adding recovery invariants.

The recovery block validates types only. Two relations are cheap to enforce and catch transcription errors:

  • context_restores <= context_losses
  • live_charts_after_restore <= live_charts
♻️ Proposed additional checks
             for key in ("context_losses", "context_restores", "live_charts_after_restore"):
                 _require_nonnegative_integer(recovery, key, recovery_path, errors)
+            losses = recovery.get("context_losses")
+            restores = recovery.get("context_restores")
+            if _is_number(losses) and _is_number(restores) and restores > losses:
+                errors.append(f"{recovery_path}.context_restores must be <= context_losses")
+            after = recovery.get("live_charts_after_restore")
+            if _is_number(after) and isinstance(live, int) and not isinstance(live, bool) and after > live:
+                errors.append(f"{recovery_path}.live_charts_after_restore must be <= live_charts")
             _require_boolean(recovery, "correctness_after_restore", recovery_path, errors)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/verify_benchmark_report.py` around lines 2034 - 2049, Extend the
shared-profile recovery validation in the existing recovery block to enforce
that context_restores does not exceed context_losses and
live_charts_after_restore does not exceed live_charts. Reuse the established
error-reporting pattern and live_charts value already available in the
surrounding profile validation, while preserving the current type and
nonnegative-integer checks.

1886-1899: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add a report conversion step or document the manual mapping.

The harness emits camelCase fields and canvasPixels ranges, while the report uses snake_case fields and canvas_pixels.width/height. The README documents capture but not this transformation. Add the mapping to benchmarks/shared_webgl_spike/README.md or provide a converter script.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/verify_benchmark_report.py` around lines 1886 - 1899, Document the
harness-to-report field transformation in the shared WebGL benchmark README,
covering camelCase-to-snake_case names and conversion of canvasPixels ranges
into canvas_pixels.width and canvas_pixels.height. Anchor the documentation near
the existing capture instructions and describe the manual mapping unless an
existing conversion utility should be extended.
benchmarks/shared_webgl_spike/results/chromium-2026-07-31.json (1)

127-143: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Consider recording native recovery data.

The native profile lost 34 of 50 contexts, but it records no recovery block. The shared profile records one. The comparison therefore reports recovery behavior for one backend only, while context loss is the failure mode the native backend actually exhibited.

Recording native loss and restore counts would make the A/B comparison symmetric. If the harness cannot restore evicted native contexts, state that in benchmarks/shared_webgl_spike/RESULTS.md.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@benchmarks/shared_webgl_spike/results/chromium-2026-07-31.json` around lines
127 - 143, Add a native recovery block to the benchmark result data, recording
context-loss and restore counts consistently with the shared profile; if native
contexts cannot be restored, represent that explicitly rather than omitting the
block. Update the benchmark documentation in RESULTS.md to state the native
restoration limitation when applicable, preserving symmetric A/B recovery
reporting.
tests/test_verify_benchmark_report.py (2)

854-925: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the remaining new invariants.

The new tests cover workloads, metrics, correctness coverage, state stress, and environment. Three new branches in _validate_shared_webgl_profile and _validate_shared_webgl_quantiles have no test:

  • the shared invariant live_contexts must be 1 while shared charts are live
  • the native invariant live_contexts must equal live_charts
  • the quantile ordering rule quantiles must be nondecreasing

The first invariant encodes the central claim of the experiment. Add a negative test for each.

💚 Proposed tests
def test_shared_webgl_spike_rejects_multiple_shared_contexts(tmp_path: Path) -> None:
    payload = _shared_webgl_spike_report()
    payload["profiles"]["shared"]["live_contexts"] = 2
    path = _write_report(tmp_path, payload)

    errors = verify_benchmark_report.validate_report(path, kind="shared-webgl-spike")

    assert any("live_contexts must be 1" in error for error in errors)


def test_shared_webgl_spike_rejects_unordered_quantiles(tmp_path: Path) -> None:
    payload = _shared_webgl_spike_report()
    payload["profiles"]["shared"]["benchmark"]["frame_ms"]["p99"] = 0.0
    path = _write_report(tmp_path, payload)

    errors = verify_benchmark_report.validate_report(path, kind="shared-webgl-spike")

    assert any("quantiles must be nondecreasing" in error for error in errors)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_verify_benchmark_report.py` around lines 854 - 925, Add negative
tests covering the remaining validation branches in
_validate_shared_webgl_profile and _validate_shared_webgl_quantiles: verify
shared profiles reject live_contexts other than 1 when charts are live, native
profiles reject live_contexts values that differ from live_charts, and benchmark
frame_ms quantiles reject decreasing ordering. Use _shared_webgl_spike_report,
_write_report, and validate_report, asserting the corresponding validation
messages.

67-75: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider resolving the results file by glob.

The helper hardcodes chromium-2026-07-31.json. A future capture adds a new date-stamped file, and these tests keep validating only the old one. Renaming or removing the file breaks seven tests with a FileNotFoundError instead of a clear message.

♻️ Proposed change to validate every captured result
 def _shared_webgl_spike_report() -> dict:
-    path = (
-        Path(__file__).resolve().parents[1]
-        / "benchmarks"
-        / "shared_webgl_spike"
-        / "results"
-        / "chromium-2026-07-31.json"
-    )
-    return json.loads(path.read_text(encoding="utf-8"))
+    results = (
+        Path(__file__).resolve().parents[1] / "benchmarks" / "shared_webgl_spike" / "results"
+    )
+    paths = sorted(results.glob("*.json"))
+    assert paths, f"no shared WebGL spike results in {results}"
+    return json.loads(paths[-1].read_text(encoding="utf-8"))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_verify_benchmark_report.py` around lines 67 - 75, Update
_shared_webgl_spike_report to discover the date-stamped Chromium results file
via glob rather than hardcoding chromium-2026-07-31.json, validate that the
expected result is found, and load the resolved file so tests follow future
captures and fail clearly when no result exists.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@benchmarks/shared_webgl_spike/results/chromium-2026-07-31.json`:
- Around line 54-58: Update benchmarks/shared_webgl_spike/RESULTS.md next to the
existing environment-specificity caveat to state that this capture is not
reproducible because environment.git.dirty is true and its commit differs from
base_commit. Include the relevant commit identifiers and explain that the exact
benchmark code cannot be reconstructed from them.
- Line 91: Update the results narrative in RESULTS.md to document both timed-run
limitations: state isolation was validated only by correctness while timed
state-reset work was omitted from both profiles, and recovery measured client
restoration without checking visible frames during the loss window. Explicitly
note that the shared throughput and ratio are not production estimates, while
preserving the feasibility-spike qualification.

---

Nitpick comments:
In `@benchmarks/shared_webgl_spike/results/chromium-2026-07-31.json`:
- Around line 127-143: Add a native recovery block to the benchmark result data,
recording context-loss and restore counts consistently with the shared profile;
if native contexts cannot be restored, represent that explicitly rather than
omitting the block. Update the benchmark documentation in RESULTS.md to state
the native restoration limitation when applicable, preserving symmetric A/B
recovery reporting.

In `@scripts/verify_benchmark_report.py`:
- Around line 2034-2049: Extend the shared-profile recovery validation in the
existing recovery block to enforce that context_restores does not exceed
context_losses and live_charts_after_restore does not exceed live_charts. Reuse
the established error-reporting pattern and live_charts value already available
in the surrounding profile validation, while preserving the current type and
nonnegative-integer checks.
- Around line 1886-1899: Document the harness-to-report field transformation in
the shared WebGL benchmark README, covering camelCase-to-snake_case names and
conversion of canvasPixels ranges into canvas_pixels.width and
canvas_pixels.height. Anchor the documentation near the existing capture
instructions and describe the manual mapping unless an existing conversion
utility should be extended.

In `@tests/test_verify_benchmark_report.py`:
- Around line 854-925: Add negative tests covering the remaining validation
branches in _validate_shared_webgl_profile and _validate_shared_webgl_quantiles:
verify shared profiles reject live_contexts other than 1 when charts are live,
native profiles reject live_contexts values that differ from live_charts, and
benchmark frame_ms quantiles reject decreasing ordering. Use
_shared_webgl_spike_report, _write_report, and validate_report, asserting the
corresponding validation messages.
- Around line 67-75: Update _shared_webgl_spike_report to discover the
date-stamped Chromium results file via glob rather than hardcoding
chromium-2026-07-31.json, validate that the expected result is found, and load
the resolved file so tests follow future captures and fail clearly when no
result exists.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 84426a88-4521-4448-b6e3-2004c1c27064

📥 Commits

Reviewing files that changed from the base of the PR and between d635400 and 276cbf3.

📒 Files selected for processing (9)
  • benchmarks/README.md
  • benchmarks/shared_webgl_spike/RESULTS.md
  • benchmarks/shared_webgl_spike/experiment.js
  • benchmarks/shared_webgl_spike/index.html
  • benchmarks/shared_webgl_spike/results/chromium-2026-07-31.json
  • benchmarks/shared_webgl_spike/styles.css
  • scripts/verify_benchmark_report.py
  • spec/process/contributing.md
  • tests/test_verify_benchmark_report.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • benchmarks/README.md
  • benchmarks/shared_webgl_spike/RESULTS.md
  • benchmarks/shared_webgl_spike/index.html
  • benchmarks/shared_webgl_spike/styles.css
  • benchmarks/shared_webgl_spike/experiment.js

Comment on lines +54 to +58
"git": {
"commit": "2e169101f16cfdbe4881a704e3fe095379dc7b9e",
"branch": "agent/shared-webgl-spike",
"dirty": true
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Record that the capture is not reproducible.

environment.git.dirty is true, and environment.git.commit (2e169101…) differs from base_commit (d505ef57, line 5). A reader cannot reconstruct the exact code that produced these numbers from either identifier.

State this limitation in benchmarks/shared_webgl_spike/RESULTS.md, next to the existing environment-specificity caveat.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@benchmarks/shared_webgl_spike/results/chromium-2026-07-31.json` around lines
54 - 58, Update benchmarks/shared_webgl_spike/RESULTS.md next to the existing
environment-specificity caveat to state that this capture is not reproducible
because environment.git.dirty is true and its commit differs from base_commit.
Include the relevant commit identifiers and explain that the exact benchmark
code cannot be reconstructed from them.

"pick_checks": 150,
"pick_failures": 0,
"crop_offset_pixels": 17,
"state_stress": true,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Document two gaps in the timed run.

Two recorded flags limit what the headline throughput numbers support:

  1. correctness.state_stress is true, but benchmark.state_stress is false in both profiles. Correctness validated state isolation. The timed run did not include the state-reset work. Shared-context compositing requires an explicit GL state reset per chart, so the shared profile's 2448.9 presentations/s excludes a cost that the production design in #407 must pay. State this in benchmarks/shared_webgl_spike/RESULTS.md so the shared-versus-native ratio is not read as a production estimate.

  2. recovery.visible_frames_during_loss_checked is false. The recovery result therefore covers restoration of clients, not the visual state that a user sees during the loss window.

Both are acceptable for a feasibility spike. Both should be explicit in the results narrative.

Also applies to: 100-100, 122-122

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@benchmarks/shared_webgl_spike/results/chromium-2026-07-31.json` at line 91,
Update the results narrative in RESULTS.md to document both timed-run
limitations: state isolation was validated only by correctness while timed
state-reset work was omitted from both profiles, and recovery measured client
restoration without checking visible frames during the loss window. Explicitly
note that the shared throughput and ratio are not production estimates, while
preserving the feasibility-spike qualification.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6 issues found across 8 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="benchmarks/README.md">

<violation number="1" location="benchmarks/README.md:280">
P2: The validation command never runs when this code block is followed sequentially because the preceding `python3 -m http.server` process blocks. Label the `make` invocation as a second-terminal command (or explicitly background the server) so the documented reproduction flow is executable.</violation>
</file>

<file name="scripts/verify_benchmark_report.py">

<violation number="1" location="scripts/verify_benchmark_report.py:1941">
P2: Native artifacts can claim more live contexts than were created, or more created contexts than requested, and still pass verification. Validate `live_charts <= created_contexts <= requested_charts`.</violation>

<violation number="2" location="scripts/verify_benchmark_report.py:1980">
P3: Malformed verification timestamps ending in `Z` are accepted as ISO UTC evidence. Parse the value as a datetime and require a UTC offset instead of relying only on `endswith`.</violation>

<violation number="3" location="scripts/verify_benchmark_report.py:2048">
P1: An artifact can claim successful recovery without a loss, with more restores than losses, or with more restored charts than requested. Cross-check the recovery counters and require a successful result to represent a completed loss/restore with all requested shared charts live.</violation>

<violation number="4" location="scripts/verify_benchmark_report.py:2076">
P1: Internally contradictory throughput metrics can pass and be published as benchmark evidence. Recompute and compare the generated rates, expected batches, and dropped intervals from `duration_ms`, `target_fps`, and the corresponding counters, using a small float tolerance for rates.</violation>

<violation number="5" location="scripts/verify_benchmark_report.py:2101">
P1: A/B artifacts with different chart counts or state-stress modes are accepted as matching workloads because the comparison tuple omits both fields. Include `requested_charts` and `benchmark.state_stress` in the workload identity.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic
Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

return None
for key in ("duration_ms", "target_fps"):
_require_positive_number(benchmark, key, benchmark_path, errors)
for key in ("observed_fps", "chart_presentations_per_second"):

@cubic-dev-ai cubic-dev-ai Bot Jul 31, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Internally contradictory throughput metrics can pass and be published as benchmark evidence. Recompute and compare the generated rates, expected batches, and dropped intervals from duration_ms, target_fps, and the corresponding counters, using a small float tolerance for rates.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/verify_benchmark_report.py, line 2076:

<comment>Internally contradictory throughput metrics can pass and be published as benchmark evidence. Recompute and compare the generated rates, expected batches, and dropped intervals from `duration_ms`, `target_fps`, and the corresponding counters, using a small float tolerance for rates.</comment>

<file context>
@@ -1840,6 +1846,300 @@ def _validate_dashboard_telemetry(row: dict[str, Any], path: str, errors: list[s
+        return None
+    for key in ("duration_ms", "target_fps"):
+        _require_positive_number(benchmark, key, benchmark_path, errors)
+    for key in ("observed_fps", "chart_presentations_per_second"):
+        _require_nonnegative_number(benchmark, key, benchmark_path, errors)
+    _require_positive_integer(benchmark, "points_per_chart", benchmark_path, errors)
</file context>
Fix with cubic

Comment on lines +2048 to +2049
_require_boolean(recovery, "correctness_after_restore", recovery_path, errors)
_require_boolean(recovery, "visible_frames_during_loss_checked", recovery_path, errors)

@cubic-dev-ai cubic-dev-ai Bot Jul 31, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: An artifact can claim successful recovery without a loss, with more restores than losses, or with more restored charts than requested. Cross-check the recovery counters and require a successful result to represent a completed loss/restore with all requested shared charts live.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/verify_benchmark_report.py, line 2048:

<comment>An artifact can claim successful recovery without a loss, with more restores than losses, or with more restored charts than requested. Cross-check the recovery counters and require a successful result to represent a completed loss/restore with all requested shared charts live.</comment>

<file context>
@@ -1840,6 +1846,300 @@ def _validate_dashboard_telemetry(row: dict[str, Any], path: str, errors: list[s
+        if isinstance(recovery, dict):
+            for key in ("context_losses", "context_restores", "live_charts_after_restore"):
+                _require_nonnegative_integer(recovery, key, recovery_path, errors)
+            _require_boolean(recovery, "correctness_after_restore", recovery_path, errors)
+            _require_boolean(recovery, "visible_frames_during_loss_checked", recovery_path, errors)
+
</file context>
Suggested change
_require_boolean(recovery, "correctness_after_restore", recovery_path, errors)
_require_boolean(recovery, "visible_frames_during_loss_checked", recovery_path, errors)
_require_boolean(recovery, "correctness_after_restore", recovery_path, errors)
_require_boolean(recovery, "visible_frames_during_loss_checked", recovery_path, errors)
losses = recovery.get("context_losses")
restores = recovery.get("context_restores")
live_after_restore = recovery.get("live_charts_after_restore")
if (
isinstance(losses, int)
and not isinstance(losses, bool)
and isinstance(restores, int)
and not isinstance(restores, bool)
and restores > losses
):
errors.append(f"{recovery_path}.context_restores must be <= context_losses")
if (
isinstance(live_after_restore, int)
and not isinstance(live_after_restore, bool)
and isinstance(requested, int)
and not isinstance(requested, bool)
and live_after_restore > requested
):
errors.append(
f"{recovery_path}.live_charts_after_restore must be <= requested_charts"
)
if (
recovery.get("correctness_after_restore") is True
and isinstance(losses, int)
and not isinstance(losses, bool)
and isinstance(restores, int)
and not isinstance(restores, bool)
and isinstance(live_after_restore, int)
and not isinstance(live_after_restore, bool)
and isinstance(requested, int)
and not isinstance(requested, bool)
and (losses == 0 or restores != losses or live_after_restore != requested)
):
errors.append(
f"{recovery_path}.correctness_after_restore is inconsistent with recovery telemetry"
)
Fix with cubic

_validate_shared_webgl_quantiles(
present, f"{benchmark_path}.present_ms_per_chart", ("p50", "p95"), errors
)
return (

@cubic-dev-ai cubic-dev-ai Bot Jul 31, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: A/B artifacts with different chart counts or state-stress modes are accepted as matching workloads because the comparison tuple omits both fields. Include requested_charts and benchmark.state_stress in the workload identity.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/verify_benchmark_report.py, line 2101:

<comment>A/B artifacts with different chart counts or state-stress modes are accepted as matching workloads because the comparison tuple omits both fields. Include `requested_charts` and `benchmark.state_stress` in the workload identity.</comment>

<file context>
@@ -1840,6 +1846,300 @@ def _validate_dashboard_telemetry(row: dict[str, Any], path: str, errors: list[s
+        _validate_shared_webgl_quantiles(
+            present, f"{benchmark_path}.present_ms_per_chart", ("p50", "p95"), errors
+        )
+    return (
+        benchmark.get("points_per_chart"),
+        benchmark.get("dense"),
</file context>
Fix with cubic

Comment thread benchmarks/README.md
Comment on lines +280 to +282
make check-benchmark-report \
BENCHMARK_JSON=benchmarks/shared_webgl_spike/results/chromium-2026-07-31.json \
BENCHMARK_KIND=shared-webgl-spike

@cubic-dev-ai cubic-dev-ai Bot Jul 31, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The validation command never runs when this code block is followed sequentially because the preceding python3 -m http.server process blocks. Label the make invocation as a second-terminal command (or explicitly background the server) so the documented reproduction flow is executable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At benchmarks/README.md, line 280:

<comment>The validation command never runs when this code block is followed sequentially because the preceding `python3 -m http.server` process blocks. Label the `make` invocation as a second-terminal command (or explicitly background the server) so the documented reproduction flow is executable.</comment>

<file context>
@@ -277,6 +277,9 @@ exercises state isolation, crop/orientation canaries, picking, and context resto
 
 ```bash
 python3 -m http.server 4173 --directory benchmarks/shared_webgl_spike
+make check-benchmark-report \
+  BENCHMARK_JSON=benchmarks/shared_webgl_spike/results/chromium-2026-07-31.json \
+  BENCHMARK_KIND=shared-webgl-spike
</file context>
Suggested change
make check-benchmark-report \
BENCHMARK_JSON=benchmarks/shared_webgl_spike/results/chromium-2026-07-31.json \
BENCHMARK_KIND=shared-webgl-spike
# In another terminal:
make check-benchmark-report \
BENCHMARK_JSON=benchmarks/shared_webgl_spike/results/chromium-2026-07-31.json \
BENCHMARK_KIND=shared-webgl-spike
Fix with cubic

if profile == "shared" and isinstance(live, int) and live > 0 and contexts != 1:
errors.append(f"{path}.live_contexts must be 1 while shared charts are live")
if profile == "native":
_require_nonnegative_integer(profile_value, "created_contexts", path, errors)

@cubic-dev-ai cubic-dev-ai Bot Jul 31, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Native artifacts can claim more live contexts than were created, or more created contexts than requested, and still pass verification. Validate live_charts <= created_contexts <= requested_charts.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/verify_benchmark_report.py, line 1941:

<comment>Native artifacts can claim more live contexts than were created, or more created contexts than requested, and still pass verification. Validate `live_charts <= created_contexts <= requested_charts`.</comment>

<file context>
@@ -1840,6 +1846,300 @@ def _validate_dashboard_telemetry(row: dict[str, Any], path: str, errors: list[s
+    if profile == "shared" and isinstance(live, int) and live > 0 and contexts != 1:
+        errors.append(f"{path}.live_contexts must be 1 while shared charts are live")
+    if profile == "native":
+        _require_nonnegative_integer(profile_value, "created_contexts", path, errors)
+        if isinstance(live, int) and not isinstance(live, bool) and contexts != live:
+            errors.append(f"{path}.live_contexts must equal live_charts")
</file context>
Suggested change
_require_nonnegative_integer(profile_value, "created_contexts", path, errors)
_require_nonnegative_integer(profile_value, "created_contexts", path, errors)
created = profile_value.get("created_contexts")
if (
isinstance(created, int)
and not isinstance(created, bool)
and isinstance(requested, int)
and not isinstance(requested, bool)
and isinstance(live, int)
and not isinstance(live, bool)
and not (live <= created <= requested)
):
errors.append(
f"{path}.created_contexts must be between live_charts and requested_charts"
)
Fix with cubic

f"{correctness_path}.state_stress_method must be {expected_stress_method!r}"
)
verified_at_utc = correctness.get("verified_at_utc")
if not isinstance(verified_at_utc, str) or not verified_at_utc.endswith("Z"):

@cubic-dev-ai cubic-dev-ai Bot Jul 31, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Malformed verification timestamps ending in Z are accepted as ISO UTC evidence. Parse the value as a datetime and require a UTC offset instead of relying only on endswith.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/verify_benchmark_report.py, line 1980:

<comment>Malformed verification timestamps ending in `Z` are accepted as ISO UTC evidence. Parse the value as a datetime and require a UTC offset instead of relying only on `endswith`.</comment>

<file context>
@@ -1840,6 +1846,300 @@ def _validate_dashboard_telemetry(row: dict[str, Any], path: str, errors: list[s
+                f"{correctness_path}.state_stress_method must be {expected_stress_method!r}"
+            )
+        verified_at_utc = correctness.get("verified_at_utc")
+        if not isinstance(verified_at_utc, str) or not verified_at_utc.endswith("Z"):
+            errors.append(
+                f"{correctness_path}.verified_at_utc must be an ISO UTC string ending in Z"
</file context>
Fix with cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Shared-context compositing: remove the ~12 live-chart ceiling (one GPU context per page)

1 participant