Skip to content

Fix: hide repeated avatar and username for grouped messages#82

Draft
beezly wants to merge 3 commits intoviktorstrate:mainfrom
beezly:fix/group-sender-messages
Draft

Fix: hide repeated avatar and username for grouped messages#82
beezly wants to merge 3 commits intoviktorstrate:mainfrom
beezly:fix/group-sender-messages

Conversation

@beezly
Copy link
Contributor

@beezly beezly commented Mar 14, 2026

Summary

When multiple consecutive messages are from the same sender, the avatar and username are now only shown on the first message in the group. Timestamps remain visible on every message.

Before

preview-before

After

preview-after

Changes

  • Added shouldIncludeProfileHeader(at:) helper to TimelineViewController that checks whether the previous visible item is a message from the same sender
  • TimelineItemRowView now accepts an includeProfileHeader parameter (defaults to true)
  • Both the data source and row height measurement use the same grouping logic

@viktorstrate
Copy link
Owner

Nice fix, I've thought about doing it as well but haven't come around to it yet. When testing it though I can see that the hover actions are cut off for the row that it's inside. This is not a problem when the display name is shown since it is a part of the same row. I don't know if there is an option to allow drawing outside the rect of the row. 🤔

Screenshot 2026-03-14 at 16 36 01 Screenshot 2026-03-14 at 16 36 06

@beezly
Copy link
Contributor Author

beezly commented Mar 15, 2026

Nice find! I think the only good fix for this is going to be to float a popover. I'll work on it. Marking this as a draft for now.

@beezly beezly marked this pull request as draft March 15, 2026 22:56
beezly added 2 commits March 17, 2026 11:19
NSHostingView inside NSTableView with usesAutomaticRowHeights enters an
infinite layout loop: layout() flushes SwiftUI transactions, which
invalidate constraints, which re-enter layout. AppKit detects this and
crashes with NSGenericException.

Replace the entire auto-sizing mechanism with SelfSizingHostingView:

- sizingOptions = [] prevents the hosting view from participating in
  Auto Layout constraint solving.

- invalidateIntrinsicContentSize() is overridden to NOT call super
  (which would re-enter the constraint system) but instead schedule
  a coalesced async height update via DispatchQueue.main.async.

- measureHeight() uses a temporary NSHostingController.sizeThatFits()
  to properly measure content at the cell width, including text wrapping.

- heightOfRow returns cached heights (default 60px). noteHeightOfRows
  is called with animation duration 0 to avoid visible row resizing.

Also removes the old measurementHostingView and handleTableResize.
…same sender

Only show the profile header (avatar + username) on the first message
in a consecutive group from the same sender. Timestamps remain visible
on every message.
@beezly beezly force-pushed the fix/group-sender-messages branch 3 times, most recently from 51b7298 to 0a392fa Compare March 17, 2026 19:59
Floating panel with quick reactions (👍🎉❤️), reply, reply in thread,
and pin actions. Panel is dismissed when scrolling or resizing, and
hidden when the row top is outside the visible timeline area.
@beezly beezly force-pushed the fix/group-sender-messages branch from 0a392fa to d239d3d Compare March 17, 2026 20:06
@beezly
Copy link
Contributor Author

beezly commented Mar 17, 2026

Rebased onto #85. Hover actions are now a floating NSPanel (HoverActionsPanel) instead of the inline ZStack
overlay — this avoids the clipping issues with NSTableView row views. The panel is dismissed on scroll/resize and
hidden when the row top is outside the visible timeline. The inline hover actions code has been removed from
MessageEventView and the reactions/receipts layout simplified.

Copy link
Owner

@viktorstrate viktorstrate left a comment

Choose a reason for hiding this comment

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

I have left some comments that are specific to this PR and not related to #85.

Generally I like this approach and the implementation generally feels solid. I mostly just want to get rid of the timers.

Comment on lines +23 to +37
public var sendReplyTo: MatrixRustSDK.EventTimelineItem? {
get { _sendReplyTo }
set {
let id = newValue?.eventOrTransactionId.id ?? "nil"
let msg = "sendReplyTo changed to \(id)"
if newValue == nil && _sendReplyTo != nil {
let stack = Thread.callStackSymbols.prefix(15).joined(separator: "\n ")
Self.debugLog("\(msg) — CLEARED\n \(stack)")
} else {
Self.debugLog(msg)
}
_sendReplyTo = newValue
}
}
private var _sendReplyTo: MatrixRustSDK.EventTimelineItem?
Copy link
Owner

Choose a reason for hiding this comment

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

You can use willSet to simplify this :)

Comment on lines +79 to +80
.stroke(Color(NSColor.separatorColor), lineWidth: 1)
.shadow(color: .black.opacity(0.1), radius: 4)
Copy link
Owner

Choose a reason for hiding this comment

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

This shadow gets clipped by the NSPanel. I think we can remove this shadow and enable hasShadow on the NSPanel.

Also the stroke is clipped, but we can fix that by adding a .padding(1) modifier after the background modifier.

Comment on lines +301 to +307
// Delay hiding so mouse can move from row to panel
hideTimer = Timer.scheduledTimer(withTimeInterval: 0.15, repeats: false) { [weak self] _ in
guard let self else { return }
if !self.hoverPanel.isMouseInside {
self.dismissHoverPanel()
}
}
Copy link
Owner

Choose a reason for hiding this comment

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

I think we can find a solution to this problem that doesn't require a timer hack. This solution doesn't feel responsive since the hover effect activates and deactivates some time after the mouse enters the area.

If you can not make it work without a timer, I can have a crack at it if you'd like?

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.

2 participants