Frame-level responsive detection so grouping works at scale (#247)#272
Merged
Conversation
Responsive grouping is a frame-level question — it only needs the small set of page-candidate FRAMEs and a few light attributes (name, width, height, device hint, page/section/parent ids), not an index of every descendant node. The prior path re-inspected the source inside detectionById(), forcing a SECOND full ScenegraphIndex build over every node on top of the planner's existing one. On the 293MB "WP.Cloud 2.0" .fig that second index OOMed, so #265 skipped detection above a 25,000-node ceiling and responsive grouping silently switched off at scale (responsive_detection_bounded fired 3x on the fixture matrix). Detection now reuses the frame-level data the planner already holds in memory after its single index build and runs the same heuristics (deviceHint, name normalization/similarity, sibling grouping) via a new pure ScenegraphFrameInspector::detectResponsiveFrames() that builds NO ScenegraphIndex. Total node count no longer drives detection memory, so the 25k node ceiling is retired; the only remaining bound guards the pathological case of an absurd number of FRAME candidates (RESPONSIVE_DETECTION_FRAME_LIMIT / responsive_detection_frame_limit). The responsive_detection_bounded diagnostic now reports frame_candidate_count / frame_candidate_limit and only fires in that pathological case, not on ordinary large designs. The detection output contract (device_hint, sibling_group_key, responsive_siblings) is unchanged, so the page planner, breakpoint variants, and #265's duplicate-draft false-positive guard (distinct-device-hint / material-width-spread, duplicate_draft_frames / responsive_group_formed) keep working — and now actually fire on large designs because detection runs. Contract tests: a synthetic design with 26,000 descendants under a single frame (above the retired 25k ceiling) but only 3 FRAME candidates still forms a real desktop/tablet/mobile group with NO bounded skip; the duplicate-draft guard still keeps same-width same-name drafts separate; and detectResponsiveFrames() produces the full contract from frame-level records alone with no source/index. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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
Makes responsive sibling detection memory-efficient so responsive grouping stays ON for large designs instead of auto-disabling. Refs #247.
The problem (verified on the 293MB "WP.Cloud 2.0" .fig)
PR #265 stopped the planner OOM by bounding responsive detection with a 25,000-node ceiling — above it, detection was SKIPPED entirely (
responsive_detection_boundedfired 3× on the fixture matrix). The side effect: at Automattic scale the marquee responsive feature auto-disabled and degraded to one-page-per-frame.Root cause:
ScenegraphPagePlanner::detectionById()re-inspected the source viaScenegraphFrameInspector::inspect()withframe_inspection_limit => max(1, $nodeCount), which built a fullScenegraphIndexover every node — a second whole-scenegraph index on top of the one the planner already builds. That second fullScenegraphIndex::build()over all nodes is what OOMed.The fix
Responsive grouping is a frame-level question: it only needs the small set of page-candidate FRAMEs and a few light attributes (name, width, height, device hint, page/section/parent ids) — NOT an index of every descendant node.
ScenegraphFrameInspector::detectResponsiveFrames(): a pure path that runs the existing heuristics (deviceHint, name normalization/similarity, sibling grouping) over a small frame-candidate set and builds noScenegraphIndex.ScenegraphPagePlannernow extracts those frame-level records from the data it already holds in memory after its single index build (FRAME candidates + node/parent indexes) and feeds them to the inspector. No second index, no source re-walk.device_hint,sibling_group_key,responsive_siblings), so the planner, breakpoint variants, and figma-transformer: harden responsive grouping (false-positive guard + memory-safe detection + diagnostics) #265's grouping guard consume it exactly as before.What happened to the 25k ceiling and the bounded diagnostic
RESPONSIVE_DETECTION_NODE_LIMIT(25,000) and theresponsive_detection_node_limitoption are retired — total node count no longer drives detection memory.RESPONSIVE_DETECTION_FRAME_LIMIT(5,000) /responsive_detection_frame_limit, a sane bound on the number of frame candidates (which is small).responsive_detection_boundednow reportsframe_candidate_count/frame_candidate_limitand only fires in genuinely pathological cases (absurd frame-candidate counts), NOT on ordinary large designs.#265 guard preserved
The duplicate-draft false-positive guard (distinct device-hint / material width spread) and the
duplicate_draft_frames/responsive_group_formeddiagnostics are untouched — and now actually fire on large designs because detection runs instead of being skipped.Tests
cd figma-transformer && php tests/contract/run.php→ Figma Transformer contract tests passed.New/updated contract coverage:
responsive_detection_boundedskip (page-plan-scale-*).duplicate_draft_frames(existingpage-plan-duplicate-drafts-*).detectResponsiveFrames()produces the full contract from frame-level records alone — no source, no index (frame-level-detection-*).responsive_detection_boundednow fires only when frame candidates exceed the frame limit (page-plan-bounded-detection-frame-*).AI assistance