Skip to content

Frame-level responsive detection so grouping works at scale (#247)#272

Merged
chubes4 merged 1 commit into
trunkfrom
cook/figma-scale-detection
Jun 28, 2026
Merged

Frame-level responsive detection so grouping works at scale (#247)#272
chubes4 merged 1 commit into
trunkfrom
cook/figma-scale-detection

Conversation

@chubes4

@chubes4 chubes4 commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

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_bounded fired 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 via ScenegraphFrameInspector::inspect() with frame_inspection_limit => max(1, $nodeCount), which built a full ScenegraphIndex over every node — a second whole-scenegraph index on top of the one the planner already builds. That second full ScenegraphIndex::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.

  • Added ScenegraphFrameInspector::detectResponsiveFrames(): a pure path that runs the existing heuristics (deviceHint, name normalization/similarity, sibling grouping) over a small frame-candidate set and builds no ScenegraphIndex.
  • ScenegraphPagePlanner now 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.
  • The detection output contract is unchanged (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

  • The RESPONSIVE_DETECTION_NODE_LIMIT (25,000) and the responsive_detection_node_limit option are retired — total node count no longer drives detection memory.
  • Replaced with RESPONSIVE_DETECTION_FRAME_LIMIT (5,000) / responsive_detection_frame_limit, a sane bound on the number of frame candidates (which is small).
  • responsive_detection_bounded now reports frame_candidate_count / frame_candidate_limit and 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_formed diagnostics are untouched — and now actually fire on large designs because detection runs instead of being skipped.

Tests

cd figma-transformer && php tests/contract/run.phpFigma Transformer contract tests passed.

New/updated contract coverage:

  • Scale: 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 responsive group with no responsive_detection_bounded skip (page-plan-scale-*).
  • figma-transformer: harden responsive grouping (false-positive guard + memory-safe detection + diagnostics) #265 guard: same-width / same-name "For Hosts" drafts still stay separate pages with duplicate_draft_frames (existing page-plan-duplicate-drafts-*).
  • Frame-level primitive: detectResponsiveFrames() produces the full contract from frame-level records alone — no source, no index (frame-level-detection-*).
  • Frame-candidate bound: responsive_detection_bounded now fires only when frame candidates exceed the frame limit (page-plan-bounded-detection-frame-*).

Note: the giant ~/Downloads/*.fig files were not run locally (too heavy). Memory-safety is proven here with a synthetic large-node fixture; real 293MB end-to-end validation should be offloaded to the Homeboy fixture-matrix rig.

AI assistance

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>
@chubes4 chubes4 merged commit 4b3c4ee into trunk Jun 28, 2026
@chubes4 chubes4 deleted the cook/figma-scale-detection branch June 28, 2026 12:03
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.

1 participant