From 92bb959ee1580ed05a45b80af599b53d78f611bb Mon Sep 17 00:00:00 2001 From: Tom Brow Date: Sun, 12 Jul 2026 08:14:54 -0700 Subject: [PATCH] fix(mobile): highlight full multi-word mentions (#1762) Signed-off-by: npub1shglkdhngx3hrnhf4gf8vhpqdrmeludctechdvpwd3988zzs7ncq2cmtxu <85d1fb36f341a371cee9aa12765c2068f79ff1b85e7176b02e6c4a738850f4f0@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub1shglkdhngx3hrnhf4gf8vhpqdrmeludctechdvpwd3988zzs7ncq2cmtxu <85d1fb36f341a371cee9aa12765c2068f79ff1b85e7176b02e6c4a738850f4f0@sprout-oss.stage.blox.sqprod.co> --- .../lib/features/channels/message_content.dart | 8 +++++--- .../features/channels/message_content_test.dart | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/mobile/lib/features/channels/message_content.dart b/mobile/lib/features/channels/message_content.dart index 143e6b9f3..4ac6b4339 100644 --- a/mobile/lib/features/channels/message_content.dart +++ b/mobile/lib/features/channels/message_content.dart @@ -112,10 +112,10 @@ class MessageContent extends HookConsumerWidget { var segment = mentionParts[i]; for (final name in mentionNames.values) { if (name.contains(' ')) { - final nbspName = name.replaceAll(' ', '\u00A0'); + final normalizedName = _markdownMentionName(name); segment = segment.replaceAllMapped( RegExp('@${RegExp.escape(name)}', caseSensitive: false), - (m) => '@$nbspName', + (m) => '@$normalizedName', ); } } @@ -716,11 +716,13 @@ RegExp _buildPrefixPattern({ ); } +String _markdownMentionName(String name) => name.replaceAll(' ', '\u00A0'); + Iterable _mentionAliases(Iterable mentionNames) sync* { for (final name in mentionNames) { final trimmed = name.trim(); if (trimmed.isEmpty) continue; - yield trimmed; + yield _markdownMentionName(trimmed); final firstName = trimmed.split(RegExp(r'\s+')).first; if (firstName.isNotEmpty) { yield firstName; diff --git a/mobile/test/features/channels/message_content_test.dart b/mobile/test/features/channels/message_content_test.dart index 947c57e1e..e22225476 100644 --- a/mobile/test/features/channels/message_content_test.dart +++ b/mobile/test/features/channels/message_content_test.dart @@ -732,6 +732,23 @@ void main() { expect(find.text('@Alice'), findsOneWidget); }); + testWidgets('highlights an entire multi-word display name', ( + tester, + ) async { + await tester.pumpWidget( + _testable( + const MessageContent( + content: 'Hey @Kenny Lopez can you review this?', + mentionNames: {'pk1': 'Kenny Lopez'}, + ), + ), + ); + + expect(find.text('@Kenny Lopez'), findsOneWidget); + expect(find.text('@Kenny'), findsNothing); + expect(_allRichText(tester), isNot(contains('Lopez Lopez'))); + }); + testWidgets('renders unknown @mention as-is', (tester) async { await tester.pumpWidget( _testable(