fix(composer): match @mentions across Unicode NFC/NFD forms

Stored nicknames are NFC after #1502, but typed mentions can be NFD;
exact token contains() dropped them from the outbound mentions list.
This commit is contained in:
Taksh 2026-08-03 17:27:27 +05:30
parent 1f59e814f9
commit 89b449a8a1
2 changed files with 23 additions and 5 deletions

View File

@ -125,16 +125,22 @@ final class ChatComposerCoordinator {
)
let peerNicknames = context.meshPeerNicknames()
var validTokens = Set(peerNicknames.values)
validTokens.insert(context.nickname)
validTokens.insert(context.nickname + "#" + String(context.myPeerID.id.prefix(4)))
// Key by NFC so a typed combining-accent form still resolves to the
// stored (precomposed) nickname token used on the wire.
var tokensByCanonical = [String: String]()
for token in peerNicknames.values {
tokensByCanonical[token.normalizedNickname] = token
}
tokensByCanonical[context.nickname.normalizedNickname] = context.nickname
let selfSuffix = context.nickname + "#" + String(context.myPeerID.id.prefix(4))
tokensByCanonical[selfSuffix.normalizedNickname] = selfSuffix
var mentions: [String] = []
for match in matches {
guard let range = Range(match.range(at: 1), in: content) else { continue }
let mentionedName = String(content[range])
if validTokens.contains(mentionedName) {
mentions.append(mentionedName)
if let canonical = tokensByCanonical[mentionedName.normalizedNickname] {
mentions.append(canonical)
}
}

View File

@ -192,4 +192,16 @@ struct ChatComposerCoordinatorContextTests {
)
#expect(Set(mentions) == ["alice", "me", "me#0011"])
}
@Test @MainActor
func parseMentions_matchesUnicodeEquivalentNicknames() {
let context = MockChatComposerContext()
let coordinator = ChatComposerCoordinator(context: context)
let precomposed = "caf\u{00E9}"
let decomposed = "cafe\u{0301}"
context.meshNicknamesByPeerID = [PeerID(str: "1111111111111111"): precomposed]
let mentions = coordinator.parseMentions(from: "hi @\(decomposed)")
#expect(mentions == [precomposed])
}
}