Fix tab-switch rendering latency and optimize SVG processing - #46
Merged
Merged
Conversation
- Move SVG processing (boostSvgForLargeGrid) to a Web Worker so
DOMParser/XMLSerializer work doesn't block the main thread
- Only apply highlights and voltage range filter to the active tab;
defer inactive tabs and catch up on tab switch
- Cache the DOM id-map (querySelectorAll('[id]')) per container with
WeakMap, invalidated when SVG content changes, avoiding repeated
O(n) scans on every highlight/filter application
https://claude.ai/code/session_018qY95WeqFBdiVQeT541YcT
- Port id-map caching (getIdMap/invalidateIdMapCache) to standalone_interface.html - Port deferred highlights and voltage filter (active-tab-only updates with stale tracking and catch-up on tab switch) to standalone_interface.html - Add 16 new tests: getIdMap caching and invalidation, applyOverloadedHighlights, applyDeltaVisuals (delta class application, text replacement, restoration), and processSvgAsync worker client https://claude.ai/code/session_018qY95WeqFBdiVQeT541YcT
…llel fetch Three performance improvements for large grids (11K+ lines): 1. Deferred tab-switch decorations: Highlights and voltage filter now use requestAnimationFrame when triggered by a tab switch, letting the browser paint the new tab instantly before doing DOM decoration work. Data changes still apply synchronously. Removed the separate catch-up effect to avoid redundant double-application. 2. boostSvgForLargeGrid cache: DOMParser→mutate→XMLSerializer is expensive on large SVGs. Results are now cached by (length + first 200 chars + vlCount) so revisiting a tab with the same diagram skips the boost entirely. 3. Parallel manual simulation: handleAddManualAction now fires the diagram fetch in parallel with the simulation API call, instead of sequentially. When simulation completes, the pre-fetched diagram is used directly. https://claude.ai/code/session_018qY95WeqFBdiVQeT541YcT
Root cause analysis revealed 5 bottlenecks in the post-paint decoration pipeline on large grids (11K+ lines). Fixes applied to both standalone interface and React frontend: 1. useLayoutEffect in usePanZoom had NO dependency array, running 3x per render and BLOCKING paint. Added [initialViewBox] dep so it only runs when a new diagram loads. 2. Voltage filter iterated 33,675 style.display writes even when the kV range covered all voltages (default case). Added early-return check. 3. applyDeltaVisuals ran 4 querySelectorAll cleanup scans on the entire SVG even when no deltas had been applied. Added data-deltas-applied flag to skip cleanup when unnecessary. 4. getScreenCTM() on the background layer forced layout recalculation on every highlight call. Cached the bgCTM per highlight pass. 5. applyOverloadedHighlights and highlight functions used querySelector for element lookups. Switched to getIdMap() for O(1) cached lookups. 6. Deferred highlights and voltage filter to requestAnimationFrame on tab switch (vs synchronous on data change) in both standalone and React. Also saved the optimization analysis as docs/rendering-optimization-plan.md. https://claude.ai/code/session_018qY95WeqFBdiVQeT541YcT
…useLayoutEffect - Fix A: Remove global vector-effect: non-scaling-stroke CSS (caused thick lines at zoom-out and per-element screen-coordinate paint cost). Kept only for .nad-action-target halo circles/rects. - Fix B: Move applyViewBox() from useEffect to useLayoutEffect([active]) in usePanZoom so the viewBox is correct before the browser paints on tab switch. - Fix C: Change zoom sync from useEffect to useLayoutEffect([activeTab]) in both standalone_interface.html and App.tsx so target tab renders with the correct viewport on the first frame. https://claude.ai/code/session_018qY95WeqFBdiVQeT541YcT
handleDisplayPrioritizedActions was replacing the entire result with pendingAnalysisResult, losing pdf_url (set by a separate streaming event). Fix: spread prev first (...prev, ...pendingAnalysisResult) so existing fields like pdf_url are preserved when pendingAnalysisResult doesn't include them. Also adds tests for: - usePanZoom hook (viewBox sync, active/inactive transitions, useLayoutEffect) - mergeAnalysisResult logic (pdf_url preservation, manual action merging) - VisualizationPanel overflow tab visibility across result updates https://claude.ai/code/session_018qY95WeqFBdiVQeT541YcT
Restores `vector-effect: non-scaling-stroke` on SVG paths/lines/polylines/rects that was removed in 6d03b24. Without it: - Lines become subpixel at full zoom-out, making native pypowsybl colors invisible - Zooming in makes strokes extremely thick, causing expensive rendering and lag - Tab switches trigger heavy repaint with variable-width strokes Also adds `contain: layout style paint` to standalone .svg-container to match the React frontend, telling the browser the SVG subtree is independent. https://claude.ai/code/session_018qY95WeqFBdiVQeT541YcT
New tests (23 new, 136 total): - cssRegression.test.ts: Verifies critical CSS rules (non-scaling-stroke, CSS containment, text-hidden, highlight styles, delta styles) are present in both App.css and standalone_interface.html. Also checks usePanZoom useLayoutEffect deps, voltage filter early-return, deferred highlights, and boost cache. - usePanZoom.test.tsx: Adds text visibility toggle tests (hidden at zoom-out, visible at zoom-in, no toggle on non-large grids) and rapid tab switching regression test. Documentation: - docs/rendering-optimization-plan.md: Comprehensive documentation of all rendering features (non-scaling-stroke, CSS containment, text culling, usePanZoom architecture, tab-switch optimization, highlight caching, voltage filter short-circuit, SVG boost cache) with rationale, regression risks, and do's/don'ts. Also excludes test files from tsconfig.app.json build to allow Node API imports in test files. https://claude.ai/code/session_018qY95WeqFBdiVQeT541YcT
marota
pushed a commit
that referenced
this pull request
Apr 30, 2026
Rewrites Phase 1 from the abstract CHANGELOG summary into a six-week chronology grounded in PR descriptions: - 1.1 Bootstrap (pre-#1) — scaffolding pushed direct to main - 1.2 End-to-end loop (PRs #1–#15) — NAD scaling, multi-tab pan/zoom, manual actions, action variants, action scoring, svgUtils + usePanZoom - 1.3 UX maturation (PRs #16–#24) — settings panel, OverloadPanel, monitoring factor, action filters, state-switching SLDs - 1.4 Robustness + CI (PRs #25–#46) — backend/frontend test suites, CircleCI + GitHub Actions, lazy loading, divergence UI - 1.5 Persistence + complex actions (PRs #47–#56) — save/reload sessions, combined actions w/ persistence, islanding MW, App.tsx Phase 1 hooks - 1.6 Two-step + replay logging + rebrand (PRs #57–#65) — split analysis, config.json persistence, load shedding, MW Start, SLD highlights, InteractionLogger, ExpertAssist → Co-Study4Grid Sections 2–4 left untouched (will be reconciled in a follow-up). https://claude.ai/code/session_01Pg7fuCUG2edfm5PyHS6SbN
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.
Summary
This PR addresses critical rendering performance issues when switching between diagram tabs on large networks (11k+ lines). The main problems were a blocking
useLayoutEffectinusePanZoomthat prevented the browser from painting, and inefficient DOM queries during highlight application. The fix includes dependency array corrections, DOM caching, Web Worker offloading for SVG processing, and several micro-optimizations.Key Changes
Critical Performance Fixes
useLayoutEffectinusePanZoom: Added[initialViewBox]dependency array so the hook only runs when a new diagram loads, not on every render. This was preventing the browser from painting the new tab until all layout effects completed.useLayoutEffectfor viewBox sync on tab activation: Ensures the SVG displays the correct zoom state before the first paint when switching tabs.processSvgAsync, preventing UI blocking during diagram load.DOM Query Optimizations
getIdMap/invalidateIdMapCache): Replaces repeatedquerySelectorAll('[id]')calls with a WeakMap-backed cache that rebuilds only when SVG content changes. Eliminates ~3-7ms of scanning per highlight operation.getScreenCTM()calls: Within a single highlight pass, reuse the background layer's CTM instead of recalculating it for each element.Tab Synchronization & State Management
useLayoutEffect: Ensures viewBox is applied before paint, preventing visual glitches when switching tabs.pdf_urland other fields when merging analysis results by spreading previous state first (...prev, ...pending).Code Quality & Testing
getIdMap,applyOverloadedHighlights,applyDeltaVisuals,usePanZoomhook behavior, and merge logic.Implementation Details
contain: layout style paintto.svg-containerto tell the browser this subtree's layout is independent, reducing work during viewBox changes.vector-effect="non-scaling-stroke"to remain visible at all zoom levels without breaking pypowsybl native colors.processSvgAsyncgracefully falls back to synchronous processing if workers are unavailable.useEffecthooks on diagram state changes.Performance Impact
On a France network (11,225 lines, 500+ voltage levels):
https://claude.ai/code/session_018qY95WeqFBdiVQeT541YcT