Dashboard context governor: visibility-driven WebGL context recovery - #4
Merged
Conversation
Browsers cap live WebGL contexts per page (~16 in Chrome) and LRU-evict the oldest on overflow, permanently blanking the earliest charts of a 20/50-chart dashboard (measured: 16-of-50 usable, zero webglcontextrestored events). The render client now governs itself instead of letting the browser evict: - Context budget (default 12, window.FASTCHARTS_CONTEXT_BUDGET override). Before a view acquires a context at budget, the least-recently-visible off-screen view releases its own via WEBGL_lose_context — a controlled loss the existing (lifecycle-gated) restore machinery undoes. - IntersectionObserver visibility feed (25% rootMargin hysteresis) tracks least-recently-visible order and re-acquires on scroll-into-view; releases are demand-driven only, so fast scrolling never thrashes. - Real browser evictions (over-budget all-visible layouts) recover too: the dead canvas is swapped for a fresh clone (listeners retargeted) and rebuilt from the retained spec+payload. - Observable per §28: data-fc-ctx on the canvas reads live/released/lost. - Under the budget nothing ever releases — small pages are unaffected. Dashboard benchmark upgrades to match: per-visit settle-wait scroll pass (reports recovery-latency p95), governed-vs-evicted event classification, a 'governed' health tier (fully usable above the budget), and a visible_stable_chart_ceiling; verifier + fixtures enforce the new schema. Measured (Chrome/macOS, 10/20/50 sweep): visible-stable ceiling 16 -> 50; 20/20 and 50/50 charts nonblank when visited (was 16 permanently); recovery p95 8.1 ms at 50 charts; 10-chart dashboards stay 'complete' with zero losses; render times, redraw-submission p95, and JS heap unchanged vs baseline. Render/lifecycle/interaction smokes green (visual smoke's histogram dark-pixel probe fails identically on unmodified main on this machine — pre-existing, environmental).
FarhanAliRaza
added a commit
that referenced
this pull request
Jul 15, 2026
- Legend now (re)freezes options and swatch scaling via _attach() when added to an axes, so a legend constructed against one axes but attached to another picks up the host's dpi/rcParams (review #1). - Legend.__init__ warns on handles/labels length mismatch and on handles without a legend entry instead of dropping them silently (review #4). - JS tracks every legend box (not just the primary) so _resize refreshes max-height on extra legends too (review #2). - JS line swatch uses ?? so an explicit lw=0 draws nothing instead of falling back to 1.5px (review #3). - Drop unreachable return in _best_legend_loc (review #5).
FarhanAliRaza
added a commit
that referenced
this pull request
Jul 15, 2026
* Improve pyplot legend and dash parity * Fix extra legend rendering regressions * Address review: legend attach recompute, resize tracking, lw=0 swatch - Legend now (re)freezes options and swatch scaling via _attach() when added to an axes, so a legend constructed against one axes but attached to another picks up the host's dpi/rcParams (review #1). - Legend.__init__ warns on handles/labels length mismatch and on handles without a legend entry instead of dropping them silently (review #4). - JS tracks every legend box (not just the primary) so _resize refreshes max-height on extra legends too (review #2). - JS line swatch uses ?? so an explicit lw=0 draws nothing instead of falling back to 1.5px (review #3). - Drop unreachable return in _best_legend_loc (review #5). --------- Co-authored-by: Alek <alek@reflex.dev>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Browsers cap live WebGL contexts per page (~16 in Chrome) and LRU-evict the oldest on overflow. Measured on main with the dashboard telemetry harness: a 20-chart page permanently blanks its 4 earliest charts, a 50-chart page blanks 34 — all evicted during the
createphase, with zerowebglcontextrestoredevents, and scrolling back never recovers them.Fix
The render client governs itself instead of letting the browser evict:
window.FASTCHARTS_CONTEXT_BUDGEToverride). Before a view acquires a context at budget, the least-recently-visible off-screen view releases its own viaWEBGL_lose_context— a controlled loss the existing, lifecycle-gated restore machinery undoes.data-fc-ctxon the canvas readslive/released/lost; views count releases/recoveries.The dashboard benchmark upgrades to measure this honestly: per-visit settle-wait scroll pass (reports recovery-latency p95), governed-release vs browser-eviction event classification, a
governedhealth tier (fully usable above the budget), and avisible_stable_chart_ceiling. Verifier + fixtures enforce the new schema, including a test that an ungoverned eviction demotes a row topartial.Perf: no degradation (measured, Chrome/macOS, same machine, 10/20/50 sweep)
ea589ac)governedgovernedGates: 883 pytest, ruff, ty,
check-benchmark-harness, artifact verification, render smoke (probe values byte-identical), real-Figure smoke, Reflex lifecycle smoke (18 charts × 2 loads, iframe remount/reload/hidden-boot-reveal, forced context-restore), interaction stress smoke (all gesture p95 budgets met, zero blank frames). The visual-regression smoke's histogram dark-pixel probe fails identically on unmodified main on this machine (local font AA) — pre-existing/environmental; CI should arbitrate.Docs: CHANGELOG, benchmark.md notes, production-readiness "Not yet safe" reworded to the governed reality (don't claim more than ~budget simultaneously visible live charts).
Known limits: per-JS-realm governance (many same-origin iframes each budget independently); a layout keeping more than the budget visible at once can still hit browser eviction — now recovered on the next visibility pass instead of staying blank.
🤖 Generated with Claude Code