native: the sidebar's group prefix resolves the sender by uid (#1420)
The last piece of the same parity gap as #1418's reaction line: a group row's "Ada: hello" prefix used the payload's `name` snapshot alone, where the web resolves by uid through nicknames + roster (previewText in shell.js resolves `who` once and uses it for both). Three consequences, all now fixed: the row quoted whatever name was frozen at send time (so a rename never reached old rows), it showed the sender's published name rather than MY name for them (#577), and a message from a device with no screen name rendered as a bare ": hello". Also skips the prefix on action lines, like the web — "Ada waves" already names its actor, so prefixing doubled it up.
Co-authored-by: Claude <noreply@anthropic.com>
2 files changed,
+44
−1
ios/LChatCore/Sources/LChatCore/Models.swift+9 −1ios/LChatCore/Tests/LChatCoreTests/PreviewTextTests.swift+35 −0
diff --git a/ios/LChatCore/Sources/LChatCore/Models.swift b/ios/LChatCore/Sources/LChatCore/Models.swift| index 8a9c32d..ce2ab8f 100644 |
| --- a/ios/LChatCore/Sources/LChatCore/Models.swift |
| +++ b/ios/LChatCore/Sources/LChatCore/Models.swift |
| @@ -442,6 +442,14 @@ public func previewText(kind: String, payload: MessagePayload, mine: Bool = fals |
| body = n > 1 ? "📎 \(n) files" : "📎 " + (a.first?.name ?? "File") |
| } |
| } |
| - if kind == "group", let n = payload.name, !n.isEmpty { return "\(n): \(body)" } |
| + // The group prefix takes the same by-uid resolution as the reaction line |
| + // above (web twin: previewText resolves `who` once, for both). So a group |
| + // row names the sender by MY name for them and survives a rename, instead |
| + // of quoting whatever the payload froze at send time — and a message from |
| + // a device with no screen name stops rendering as a bare ": hello". |
| + // An action line ("Ada waves") already names its actor: no prefix, or it |
| + // doubles up. |
| + let who = actor.isEmpty ? (payload.name ?? "") : actor |
| + if kind == "group", !who.isEmpty, payload.action != true { return "\(who): \(body)" } |
| return body |
| } |
diff --git a/ios/LChatCore/Tests/LChatCoreTests/PreviewTextTests.swift b/ios/LChatCore/Tests/LChatCoreTests/PreviewTextTests.swift| index fe7120f..191d434 100644 |
| --- a/ios/LChatCore/Tests/LChatCoreTests/PreviewTextTests.swift |
| +++ b/ios/LChatCore/Tests/LChatCoreTests/PreviewTextTests.swift |
| @@ -136,4 +136,39 @@ final class PreviewTextTests: XCTestCase { |
| p.name = nil |
| XCTAssertEqual(previewText(kind: "dm", payload: p), "Someone reacted 👍 to “buy milk”") |
| } |
| + |
| + // The group prefix takes the same by-uid resolution as the reaction line: |
| + // my name for them, so the row survives a rename and never renders a bare |
| + // ": hello" for a sender whose device stamped no screen name. Web twin: |
| + // previewText in shell.js resolves `who` once and uses it for both. |
| + func testGroupPrefixUsesTheResolvedActor() { |
| + var p = MessagePayload() |
| + p.name = "Old Name" |
| + p.text = "hello" |
| + XCTAssertEqual(previewText(kind: "group", payload: p, actor: "My Name For Them"), |
| + "My Name For Them: hello") |
| + } |
| + |
| + func testGroupPrefixFallsBackToThePayloadName() { |
| + var p = MessagePayload() |
| + p.name = "Ada" |
| + p.text = "hello" |
| + XCTAssertEqual(previewText(kind: "group", payload: p), "Ada: hello") |
| + } |
| + |
| + // Nothing to resolve and nothing stamped: no stray colon. |
| + func testGroupPrefixIsSkippedWithNoNameAtAll() { |
| + var p = MessagePayload() |
| + p.text = "hello" |
| + XCTAssertEqual(previewText(kind: "group", payload: p), "hello") |
| + } |
| + |
| + // An action line names its own actor — prefixing it doubles the name up. |
| + func testActionLineTakesNoGroupPrefix() { |
| + var p = MessagePayload() |
| + p.name = "Ada" |
| + p.text = "waves" |
| + p.action = true |
| + XCTAssertEqual(previewText(kind: "group", payload: p, actor: "Ada"), "waves") |
| + } |
| } |