Fix crash: infinite layout loop in timeline with media/reactions#84
Merged
viktorstrate merged 4 commits intoviktorstrate:mainfrom Mar 17, 2026
Merged
Conversation
dba5ab2 to
9fc07da
Compare
Contributor
Author
|
I've based this commit off #80 as it includes changes to similar code. It should show the same two commits as 80 until that branch is merged, and then should just have the last four. |
Owner
Contributor
Author
|
No problem! I'll make take 4 out of this PR and put it's on it's own and
see if that fixes the problems you found.
…On Tue, 17 Mar 2026, 08:17 Viktor Strate Kløvedal, ***@***.***> wrote:
*viktorstrate* left a comment (viktorstrate/mactrix#84)
<#84 (comment)>
Bugs found when testing it
-
When I clicked play on a video, the video player became very small.
Screenshot.2026-03-17.at.09.00.07.png (view on web)
<https://github.com/user-attachments/assets/f28d1d70-c56e-45ab-9cba-089997bbdeff>
-
It doesn't seem to handle wrapping properly. Even after loading a room
from scratch (no resizing after timeline load) can this happen.
Screenshot.2026-03-17.at.09.05.52.png (view on web)
<https://github.com/user-attachments/assets/c3958a2a-d5af-4b49-843b-bd0f85fa037d>
-
By resizing the window to a small width, I managed to hit an infinite
loop, when pausing the debugger, I placed in the sizeThatFits
function. I'm not sure this is related to this PR and it might as well be
an unrelated old bug.
Screenshot.2026-03-17.at.09.05.05.png (view on web)
<https://github.com/user-attachments/assets/f07a526a-0abf-4a3d-868f-4a91555a2f00>
Comments
I think the layout of the timeline is very complicated, so I think we need
to approach this problem very carefully. I am suspecting that commit 4 is
the one causing the rows to have the wrong height, since it's the only one
that touches the AppKit level layouting. And that commit 1-3 could be
standalone improvements independent of 4.
If it's easier we can split this PR up into 1-3, make sure they fully work
and merge them, and then we can focus on improving the AppKit layout in a
separate PR. What do you think?
—
Reply to this email directly, view it on GitHub
<#84 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AACYYG7BVSZATJSX3OKO65L4RECZZAVCNFSM6AAAAACWT35DZSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHM2DANZTGE2DAMJUGQ>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
9fc07da to
5c18560
Compare
Contributor
Author
|
I ran a test build of this and wrapping is correct. |
Prevent division by zero when image metadata reports zero or missing dimensions. Without this guard, aspectRatio computes infinity or NaN, which causes SwiftUI layout issues inside NSTableView cells.
…llback - Guard against zero width/height in aspect ratio calculation (same as image fix). - Reorder modifiers: .aspectRatio() before .frame(maxHeight:) so the aspect ratio is applied before the height constraint, matching the intended sizing behaviour. - Add minHeight when thumbnailSource is nil. Without a thumbnail, MatrixImageView renders a Rectangle with no intrinsic size, causing .aspectRatio(.fit) to collapse the video to zero height.
Replace the conditional if/else if blocks for reactions and read receipts with an always-present HStack that uses frame(height: 0) and .clipped() when empty. Conditional view branches (if !reactions.isEmpty / else if) change the SwiftUI view tree structure when data loads asynchronously. Inside an NSHostingView table cell, this triggers invalidateIntrinsicContentSize during layout, contributing to an infinite constraint update loop that crashes the app. Keeping the view tree stable avoids this trigger.
5c18560 to
1be2bbb
Compare
beezly
added a commit
to beezly/mactrix
that referenced
this pull request
Mar 17, 2026
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.



Problem
Opening a room containing video/image messages with reactions or read receipts causes an infinite layout loop that crashes the app with
NSGenericException:The crash is timing-dependent — it reproduces when launched normally but not always under a debugger.
Root Cause
NSHostingViewinsideNSTableViewwithusesAutomaticRowHeightsenters an infinite constraint update cycle. During layout,NSHostingView.layout()flushes SwiftUI transactions, which can invalidate constraints (especially when the SwiftUI view tree structure changes), which re-enters layout — exceeding AppKit's re-entrancy limit.The crash was latent since the NSTableView rewrite (#47). Adding view complexity (reactions, read receipts, video playback) increased the likelihood of triggering the loop.
Fix
Four commits, each addressing a contributing factor:
1. Guard against zero width/height in image aspect ratio
Prevent division by zero when image metadata reports zero or missing dimensions, which produces infinity/NaN aspect ratios.
2. Video sizing fixes
.aspectRatio()before.frame(maxHeight:)minHeightfor videos without thumbnails (otherwiseMatrixImageViewrenders a zero-intrinsic-size rectangle that collapses under.aspectRatio(.fit))3. Stabilise view tree in MessageEventBodyView
Replace conditional
if/else ifblocks for reactions and read receipts with an always-presentHStackthat usesframe(height: 0)+.clipped()when empty. Conditional view branches change the SwiftUI view tree structure when data loads asynchronously, triggeringinvalidateIntrinsicContentSizeduring layout.4. Replace usesAutomaticRowHeights with deferred self-sizing
SelfSizingHostingViewsubclass that:sizingOptions = []to opt out of Auto Layout constraint solvinginvalidateIntrinsicContentSize()to schedule a coalesced async height update instead of calling super (which would re-enter the constraint system)measureHeight()temporarily re-enables sizing, pins a width constraint, readsfittingSize, then resets — all outside the layout passnoteHeightOfRowswith animation disabledAlso removes the old
measurementHostingView(separateNSHostingControllerper height query) andhandleTableResizeobserver.