Skip to content

Commit 1560a67

Browse files
authored
fix(poll): restore vote payload delivery (#150)
Restore native poll vote delivery on macOS 26.4 by applying the Polls balloon and payload across the associated message and backing item. Add focused regression coverage and document the affected release boundary. Co-authored-by: Omar Shahine <[email protected]>
1 parent 9452489 commit 1560a67

4 files changed

Lines changed: 32 additions & 21 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## 0.12.2 - Unreleased
44

5+
### Native Polls
6+
- fix: restore native poll vote delivery on macOS 26.4 by persisting the Polls balloon and payload across the responding message objects (#150, thanks @omarshahine).
7+
58
## 0.12.1 - 2026-07-02
69

710
### Packaging

Sources/IMsgHelper/IMsgInjected.m

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2815,15 +2815,9 @@ static id buildPollVoteIMMessage(NSAttributedString *body,
28152815
Class messageClass = NSClassFromString(@"IMMessage");
28162816
if (!messageClass) return nil;
28172817

2818-
// No macOS 26 IMMessage initializer carries balloon + payload AND
2819-
// association together (verified by selector probe). Reactions prove the
2820-
// associated-message initializer persists association atomically, and
2821-
// balloon/payload set on the backing IMMessageItem persist (verified: an
2822-
// earlier item-first vote landed with the balloon intact, only the
2823-
// association — set via the wrap — was lost). So: init with association
2824-
// atomically, then stamp balloonBundleID + payloadData onto the message's
2825-
// own backing item (a real item from a direct init, not the transient one
2826-
// the messageFromIMMessageItem: wrap returns).
2818+
// No macOS 26 initializer carries the payload and association together.
2819+
// Build the association atomically, then stamp payload metadata across the
2820+
// message and backing item; macOS 26.4 splits the setters between them.
28272821
SEL sel = @selector(initWithSender:time:text:messageSubject:fileTransferGUIDs:flags:error:guid:subject:associatedMessageGUID:associatedMessageType:associatedMessageRange:messageSummaryInfo:);
28282822
if (![messageClass instancesRespondToSelector:sel]) return nil;
28292823

@@ -2857,8 +2851,6 @@ static id buildPollVoteIMMessage(NSAttributedString *body,
28572851
id result = invokeReturningObject(inv);
28582852
if (!result) return nil;
28592853

2860-
// Both values must land on one object. A partial stamp can emit an
2861-
// associated message that Messages cannot decode as a poll vote.
28622854
NSString *balloonID = pollsBalloonBundleIdentifier();
28632855
NSArray<id> *targets = @[result];
28642856
SEL itemSel = NSSelectorFromString(@"_imMessageItem");
@@ -2868,14 +2860,19 @@ static id buildPollVoteIMMessage(NSAttributedString *body,
28682860
if (item) targets = @[item, result];
28692861
} @catch (__unused NSException *e) {}
28702862
}
2863+
BOOL balloonStamped = NO;
2864+
BOOL payloadStamped = NO;
28712865
for (id target in targets) {
2872-
if (![target respondsToSelector:@selector(setBalloonBundleID:)]
2873-
|| ![target respondsToSelector:@selector(setPayloadData:)]) continue;
2874-
[target performSelector:@selector(setBalloonBundleID:) withObject:balloonID];
2875-
[target performSelector:@selector(setPayloadData:) withObject:payloadData];
2876-
return result;
2866+
if ([target respondsToSelector:@selector(setBalloonBundleID:)]) {
2867+
[target performSelector:@selector(setBalloonBundleID:) withObject:balloonID];
2868+
balloonStamped = YES;
2869+
}
2870+
if ([target respondsToSelector:@selector(setPayloadData:)]) {
2871+
[target performSelector:@selector(setPayloadData:) withObject:payloadData];
2872+
payloadStamped = YES;
2873+
}
28772874
}
2878-
return nil;
2875+
return (balloonStamped && payloadStamped) ? result : nil;
28792876
}
28802877

28812878
/// `send-poll-vote`: cast a vote on an existing poll. Builds a Polls balloon

Tests/imsgTests/BridgeCommandRegistrationTests.swift

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ func injectedHelperWiresNativePollSend() throws {
150150
}
151151

152152
@Test
153-
func injectedHelperWiresFailClosedNativePollVote() throws {
153+
func injectedHelperBroadcastsFailClosedNativePollVoteMetadata() throws {
154154
let testFile = URL(fileURLWithPath: #filePath)
155155
let repoRoot =
156156
testFile
@@ -168,9 +168,17 @@ func injectedHelperWiresFailClosedNativePollVote() throws {
168168
#expect(sendVoteBody.contains("pollVoteMessageInitializerAvailable()"))
169169
#expect(!sendVoteBody.contains("pollPayloadMessageInitializerAvailable()"))
170170
#expect(voteBody.contains("associatedMessageType"))
171-
#expect(voteBody.contains("setBalloonBundleID:"))
172-
#expect(voteBody.contains("setPayloadData:"))
173-
#expect(voteBody.contains("return nil;"))
171+
#expect(voteBody.contains("BOOL balloonStamped = NO;"))
172+
#expect(voteBody.contains("BOOL payloadStamped = NO;"))
173+
#expect(
174+
voteBody.contains("if ([target respondsToSelector:@selector(setBalloonBundleID:)])")
175+
)
176+
#expect(voteBody.contains("if ([target respondsToSelector:@selector(setPayloadData:)])"))
177+
#expect(voteBody.contains("return (balloonStamped && payloadStamped) ? result : nil;"))
178+
#expect(
179+
!voteBody.contains(
180+
"|| ![target respondsToSelector:@selector(setPayloadData:)]"
181+
))
174182
}
175183

176184
@Test

docs/history.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ poll's stable option identifier before sending:
9292
imsg poll vote --chat-id <id> --poll <poll-guid> --option-index 2
9393
```
9494

95+
On macOS 26.4, use imsg 0.12.2 or later. Earlier builds could create a local
96+
vote row without the Polls payload, so the recipient's poll did not update.
97+
9598
### Manual native poll test plan
9699

97100
1. Create a native poll in Messages from an iPhone or Mac.

0 commit comments

Comments
 (0)