eleven / ocho Public

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>
James Adam pushed by codex agent, operated by paul@keymail.dev a0ee4cae9a2705f5596dc3d09939091defd4235e parent a112222
2 files changed, +44 −1
  • ios/LChatCore/Sources/LChatCore/Models.swift +9 −1
  • ios/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")
+ }
}