Skip to content

fix: message parts - use partIds and helpers instead of unstable indices (1/7) - #3152

Open
DeveloperBlue wants to merge 8 commits into
BlueBubblesApp:masterfrom
DeveloperBlue:developerblue/fix/messageParts
Open

fix: message parts - use partIds and helpers instead of unstable indices (1/7)#3152
DeveloperBlue wants to merge 8 commits into
BlueBubblesApp:masterfrom
DeveloperBlue:developerblue/fix/messageParts

Conversation

@DeveloperBlue

@DeveloperBlue DeveloperBlue commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Message-parts APIs currently incorrectly treat parts list indices as if they were iMessage part IDs. After edits, unsends, or gallery collapse, anything that keys off those values (replies, threads, swipe-to-reply, bubble chrome, and reactions) can target the wrong part or break.

This PR adds helpers to look up parts by id (partById, coversPartId), attachmentGuid for replies to a specific gallery attachment, and leading/trailing helpers (isLeadingMessagePart / isTrailingMessagePart) so collapsed galleries still get the correct tails, connect styling, and reaction/sticker placement.

Bugs Addressed:

  • Tapbacks/stickers on a photo in a collapsed gallery applied to / rendered on the wrong attachment (or didn’t appear)
  • Popover Reply on a gallery image replied to the wrong part and previewed the wrong attachment
  • Reply-thread open / reply-bubble render crashed or showed the whole parent message when the target part was missing
  • Bubble effects, tails, avatars, and Material multi-bubble connectors broke on collapsed galleries because leading/trailing used list index instead of the gallery’s covered part ids
  • “Reply recent” could target the wrong part when part ids don’t match parts.length - 1
  • Reply-thread line decoration (and originator thread open via a gallery part) missed replies whose thread part id sat inside a collapsed gallery span; bubble-level “X Replies” on galleries is suppressed in favor of per-attachment reply UX

Each architecture change is commit-by-commit, so you can review each commit individually for the scoped changes:
partById implementation → partById call sites → attachmentGuid for reply contexts→ null guards for missing messageParts→ leading/trailing part helpers → per-part/per-attachment reaction and sticker matching → threadsForParts so reply threads match any part id in a collapsed gallery span.

see file changes

1. add partById to resolve message parts by id instead of unstable list index

Adds MessageState.partById and switches reply/thread UI from parts[index] to lookup by MessagePart.part, so targeting survives edits, unsends, and gallery collapse.

  • lib/app/state/message_state.dart — Add partById (lookup by MessagePart.part)
  • lib/app/layouts/conversation_view/widgets/message/message_holder.dart — Resolve reply-target parts by id
  • lib/app/layouts/conversation_view/widgets/message/reply/reply_bubble.dart — Load the replied-to part by id
  • lib/app/layouts/conversation_view/widgets/text_field/reply_holder.dart — Reply composer preview uses id lookup
  • lib/app/layouts/conversation_view/widgets/message/popup/actions/navigation_actions.dart — Thread originator resolved by id

2. fix: use message-part ids for swipe-to-reply, bubble effects, reply-recent, and gallery cards

Propagates part ids through the remaining interaction surfaces, and records original part ids per attachment when consecutive media parts collapse into a gallery.

  • lib/database/global/message_part.dartpartIndexForAttachment / attachmentPartIndices for collapsed galleries
  • lib/app/layouts/conversation_view/widgets/message/attachment/message_image_gallery.dart — Per-card swipe/reply uses each attachment’s original part id
  • lib/app/layouts/conversation_view/widgets/message/message_holder.dart — Swipe-to-reply / bubble effects use part ids
  • lib/app/layouts/conversation_view/widgets/message/misc/message_part_content.dart — Pass part id through content wrapper
  • lib/services/backend_ui_interop/intents.dart — Reply Recent targets parts.last.part, not parts.length - 1

3. fix: plumb attachmentGuid through reply context to resolve specific attachments

Extends reply context so a reply can target a specific attachment inside a multi-attachment part/gallery, not just the part id.

  • lib/models/message_reply_context.dart — Optional attachmentGuid on reply context
  • lib/app/layouts/conversation_view/widgets/message/misc/swipe_to_reply_wrapper.dart — Carry attachmentGuid into MessageReplyContext
  • lib/app/layouts/conversation_view/widgets/message/popup/actions/navigation_actions.dart — Popover reply sets guid when the part has a single attachment
  • lib/app/layouts/conversation_view/widgets/text_field/reply_holder.dart — Preview resolves by guid first, then falls back to partById

4. fix: guard reply/thread paths when a reply targets a missing message part

Makes reply/thread paths null-safe when partById misses (deleted, unsent, or out-of-sync parts) instead of force-unwrapping.

  • lib/app/layouts/conversation_view/widgets/message/message_holder.dart — Null-safe matched part when rendering a reply target
  • lib/app/layouts/conversation_view/widgets/message/reply/reply_bubble.dart — Nullable part; skip/fallback instead of !
  • lib/app/layouts/conversation_view/widgets/message/popup/actions/navigation_actions.dart — Guard thread open when originator part is missing
  • lib/app/layouts/conversation_view/widgets/message/misc/swipe_to_reply_wrapper.dart — Align with safer reply-context construction
  • lib/models/message_reply_context.dart — Clarify partIndex as message-part id

5. fix: add isLeading/isTrailingMessagePart helpers for multipart layouts

Replaces list-position leading/trailing checks with span-aware helpers so bubble tails, connect styling, and chrome stay correct for collapsed galleries.

  • lib/database/global/message_part.dartcoversPartId for single parts and gallery spans
  • lib/app/state/message_state.dartisLeadingMessagePart / isTrailingMessagePart via coversPartId
  • lib/app/layouts/conversation_view/widgets/message/message_holder.dart — Stickers, effects, connectUpper, tails use leading/trailing helpers
  • lib/app/layouts/conversation_view/widgets/message/parts/message_part_wrapper.dart — Same for bubble wrapper chrome
  • lib/app/layouts/conversation_view/widgets/message/attachment/attachment_holder.dart — Tail only on trailing part
  • lib/app/layouts/conversation_view/widgets/message/message_holder/message_holder_timestamps.dart — Edit-history bubble chrome matches leading/trailing

6. fix: target correct message parts for bubble reactions/stickers for collapsed galleries via coversPartId

Switches reaction/sticker matching to coversPartId so associations against any id in a collapsed gallery span still render on that bubble.

  • lib/app/layouts/conversation_view/widgets/message/message_holder.dartreactionsForPart takes MessagePart and filters via coversPartId
  • lib/app/layouts/conversation_view/widgets/message/message_holder/message_holder_reactions.dart — Reactions, stickers, and spacing use span-aware matching
  • lib/app/layouts/conversation_view/widgets/message/message_holder/message_holder_timestamps.dart — Samsung timestamp padding uses the updated callback
  • lib/app/layouts/conversation_view/widgets/message/message_holder/message_reactions.dart — Reaction widget callback signature matches

7. fix: resolve reply threads by message-part id across collapsed gallery part spans

Adds a multi-part thread lookup so replies keyed to any id covered by a collapsed gallery are found together, and keeps gallery reply UX per-attachment (native iMessage).

  • lib/database/global/chat_messages.dartthreadsForParts matches normalizedThreadPart against any originator part id in the set
  • lib/app/layouts/conversation_view/widgets/message/message_holder.dart — Reply line decoration uses threadsForParts with attachmentPartIndices
  • lib/app/layouts/conversation_view/widgets/message/misc/message_properties.dart — Skip aggregate reply count on media galleries; otherwise use threadsForParts
  • lib/app/layouts/conversation_view/widgets/message/reply/reply_thread_popup.dart — Opening a thread from a gallery originator unions replies across covered part ids

Groundwork for addressing #3122, #2605, and #2812

@DeveloperBlue

DeveloperBlue commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

First chunk of #3147 for #3122

Each commit is specifically scoped, so hopefully reviewing should be smooth.

@DeveloperBlue
DeveloperBlue marked this pull request as ready for review August 1, 2026 06:06
@DeveloperBlue

Copy link
Copy Markdown
Contributor Author

Ready to go, let me know if the PR changes are a good size

@DeveloperBlue
DeveloperBlue force-pushed the developerblue/fix/messageParts branch from db171a5 to fe09630 Compare August 8, 2026 05:48
@DeveloperBlue DeveloperBlue changed the title fix: message parts - use partIds and helpers instead of unstable indices fix: message parts - use partIds and helpers instead of unstable indices (1/6) Aug 8, 2026
@DeveloperBlue
DeveloperBlue force-pushed the developerblue/fix/messageParts branch from 8e9712e to 1b690c8 Compare August 10, 2026 04:27
@DeveloperBlue
DeveloperBlue force-pushed the developerblue/fix/messageParts branch from 1b690c8 to f948391 Compare August 10, 2026 04:49
@DeveloperBlue DeveloperBlue changed the title fix: message parts - use partIds and helpers instead of unstable indices (1/6) fix: message parts - use partIds and helpers instead of unstable indices (1/7) Aug 10, 2026
@DeveloperBlue
DeveloperBlue force-pushed the developerblue/fix/messageParts branch 2 times, most recently from 9571988 to f948391 Compare August 15, 2026 08:14
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