Skip to content

perf(layout): override explicitAlignment to stop O(n×depth) cascade in custom Layout wrappers (LUM-1167)#28691

Merged
vex-assistant-bot[bot] merged 1 commit into
mainfrom
do/lum-1167-fix-explicitAlignment-cascade
Apr 28, 2026
Merged

perf(layout): override explicitAlignment to stop O(n×depth) cascade in custom Layout wrappers (LUM-1167)#28691
vex-assistant-bot[bot] merged 1 commit into
mainfrom
do/lum-1167-fix-explicitAlignment-cascade

Conversation

@ashleeradka
Copy link
Copy Markdown
Contributor

@ashleeradka ashleeradka commented Apr 28, 2026

Overrides Layout.explicitAlignment on BottomAlignedMinHeightLayout and WidthCapLayout to return nil, eliminating the O(n×depth) alignment-guide cascade that caused 2000ms+ main-thread hangs (Sentry MACOS-M9, MACOS-JG). Both layouts were introduced to replace _FlexFrameLayout, but neither overrode explicitAlignment — the default protocol extension merges all subviews' guides, which is the exact same recursive cascade _FlexFrameLayout performs. Returning nil is safe because both layouts position children via placeSubviews, not alignment guides.

Root cause analysis

  1. How did the code get into this state? PR fix: replace FlexFrame with Layout protocol to eliminate main-thread hang (LUM-944) #26053 (LUM-944) created BottomAlignedMinHeightLayout to replace .frame(minHeight:alignment:.bottom) and avoid _FlexFrameLayout. The implementation correctly used placeSubviews for positioning but missed the explicitAlignment override. PR perf: eliminate remaining FlexFrame anti-patterns from LazyVStack cell views (Batch 3) #26007/PR perf: cap ForEach item count and eliminate cell-level FlexFrames (LUM-945) #26092 created WidthCapLayout with the same omission.
  2. What was missed? The Layout protocol has TWO ways to cascade: (a) _FlexFrameLayout specifically, and (b) the default explicitAlignment protocol extension. PR fix: replace FlexFrame with Layout protocol to eliminate main-thread hang (LUM-944) #26053 only addressed (a). The Apple docs state: "If you don't implement the method, the protocol's default implementation merges the subviews' guides."
  3. Warning signs: The prior investigation (LUM-946) identified double-measure as the issue, but the actual root cause is the alignment cascade — SwiftUI caches LayoutSubview.sizeThatFits() results internally, so the re-measure in placeSubviews hits the cache and is cheap. The Sentry stack showing explicitAlignment in the call chain was the real signal.
  4. Prevention: Any custom Layout wrapper that exists to avoid _FlexFrameLayout MUST override explicitAlignment to return nil — otherwise the default extension re-introduces the same cascade. This should be documented in AGENTS.md for future Layout implementations.

Prompt / plan

LUM-1167: Fix BottomAlignedMinHeightLayout double-measure hang. Deep investigation revealed the actual root cause is the missing explicitAlignment override, not the double-measure (which is SwiftUI-cached and cheap).

Test plan

  • Lint checks pass (check-flexframe.sh: OK)
  • Pre-commit hooks pass (design token guard, generic-examples, secrets check)
  • No behavioral change: explicitAlignment returning nil means "no explicit guide — use default positioning," identical to any view without custom alignment. Placement logic in sizeThatFits/placeSubviews is unchanged.
  • CI does not include macOS builds — user must verify Xcode build locally.


Open in Devin Review

…n custom Layout wrappers (LUM-1167)

BottomAlignedMinHeightLayout and WidthCapLayout were introduced to replace
_FlexFrameLayout, but neither overrode Layout.explicitAlignment. The default
protocol extension merges all subviews' alignment guides — the exact same
O(n×depth) recursive cascade that _FlexFrameLayout performs. When
BottomAlignedMinHeightLayout wraps the entire LazyVStack scroll content,
this cascade walks every visible cell on each layout pass, producing
2000ms+ main-thread hangs (Sentry MACOS-M9, MACOS-JG).

Override both explicitAlignment(of: HorizontalAlignment) and
explicitAlignment(of: VerticalAlignment) to return nil on both layouts.
Returning nil means 'no explicit guide — use default positioning', which
is correct because both layouts position children via placeSubviews, not
alignment guides.

Closes LUM-1167

Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>
@devin-ai-integration
Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@linear
Copy link
Copy Markdown

linear Bot commented Apr 28, 2026

LUM-1167 App Hanging: App hanging for at least 2000 ms.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 1 additional finding.

Open in Devin Review

Copy link
Copy Markdown
Contributor

@vex-assistant-bot vex-assistant-bot Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vex review ✦

Clean and correct. The explicitAlignment override returning nil is exactly the right fix — the default Layout protocol extension was silently re-introducing the same O(n×depth) cascade that BottomAlignedMinHeightLayout and WidthCapLayout were originally created to avoid.

A few notes:

  • Both overrides (horizontal + vertical) are present — correct, since the default implementation merges both guide axes
  • placeSubviews handles all positioning — nil here has no behavioral side effect
  • The root cause analysis is well documented and accurately identifies the prior investigation miss (double-measure vs cascade)
  • PR description correctly calls out AGENTS.md as needing an update for future custom Layout implementations — should be followed up

Devin ✅ + Codex 👍 already in. CI passing. Merge criteria met.

@vex-assistant-bot vex-assistant-bot Bot merged commit 8f2d2d9 into main Apr 28, 2026
8 checks passed
@vex-assistant-bot vex-assistant-bot Bot deleted the do/lum-1167-fix-explicitAlignment-cascade branch April 28, 2026 23:52
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