VIEW-2: fix camera fit on the live graph (poll until SVG+sim ready)#103
Merged
Conversation
…G+sim ready The once-per-graph framing worked in dev but left the graph pinned top-left on the live site (offMag ~0.81, ~6% usage on the guest graph). Root cause: the effect ran when `hasNodes` flipped true but `svgRef.current` was still null (the <svg> mounts a render later), so it hit the `!svgRef.current` early-return and — because its deps don't change again — never retried once the SVG mounted. In dev, React StrictMode double-invokes effects so a second run caught the mounted SVG and masked the bug; production runs effects once, so the fit never applied and the origin-centred layout rendered in the top-left corner. Fix: move the readiness checks INTO the poll loop. The effect now sets up the poll whenever hasNodes && currentGraphId, and each attempt retries (250ms, up to ~15s) until svgRef + zoomBehaviorRef + the simulation's positioned nodes are all available, then fits/restores ONCE. fitViewToNodes returns a success boolean so the poll knows when it actually applied. Verified in a production build pointed at the live cloud Worker (the exact failing scenario): 7/7 nodes framed at 1440/1280/1920/768 (was 3/7). Local camera.spec 4/4, balance gate 3/3 (offMag ~0.05). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🧪 Comprehensive Test Suite
Full-stack smoke gate runs in the CI workflow. |
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.
The bug
The once-per-graph framing (VIEW-1) worked in dev but left the live graph pinned in the top-left corner — the OpenCV live audit measured offMag ~0.81 / ~6% usage on the guest graph, and the screenshot confirmed the (origin-centred) nodes clustered top-left with no camera transform applied.
Root cause
The effect ran when
hasNodesflipped true butsvgRef.currentwas still null — the<svg>mounts a render later. It hit the!svgRef.currentearly-return and, since its deps ([hasNodes, currentGraphId]) don't change again, never retried once the SVG mounted. In dev, React StrictMode double-invokes effects, so a second run caught the mounted SVG and masked the bug. Production runs effects once → the fit never applied.Fix
Move the readiness checks into the poll loop: the effect arms the poll whenever
hasNodes && currentGraphId, and each attempt retries (250ms, up to ~15s) untilsvgRef+zoomBehaviorRef+ the simulation's positioned nodes are all available, then fits/restores once.fitViewToNodesnow returns a success boolean so the poll knows when it actually applied.Verification
GraphDone-Cloud/scripts/verify-camera-fix.mjs): 7/7 nodes framed at 1440/1280/1920/768 (was 3/7, zoom=none).camera.spec4/4, OpenCV balance gate 3/3 (offMag ~0.05), typecheck clean.Note: dev-mode E2E can't reproduce this (StrictMode masks it); the prod-build script is the real guard. Will re-run the live audit after deploy to confirm offMag drops on the live site.
🤖 Generated with Claude Code