fix(mobile): recover historical namesake chip identity

Signed-off-by: Logan Johnson <loganj@squareup.com>
This commit is contained in:
Logan Johnson
2026-09-09 12:55:11 -04:00
parent 39afd73b0a
commit acd841354a
2 changed files with 56 additions and 12 deletions

View File

@@ -90,23 +90,25 @@ Map<String, Set<String>> renderedMentionBindings(
key: key,
));
add(label, key);
bindings.putIfAbsent(match.group(1)!.toLowerCase(), () => <String>{});
}
final winning = mentionOccurrences(
content,
bindings.keys,
).map((range) => range.label).toSet();
for (final entry in bindings.entries) {
if (entry.value.length < 2) continue;
entry.value.removeAll(
qualified
.where(
(q) =>
q.base == entry.key &&
winning.contains(q.label) &&
bindings[q.label]?.length == 1,
)
.map((q) => q.key),
);
final claimed = qualified.where((q) => winning.contains(q.label)).toList();
final bases = claimed.map((q) => q.base).where(winning.contains).toSet();
final remaining = keys.difference(claimed.map((q) => q.key).toSet());
// Ordinary sibling mentions claim their own identities before recovering a
// historical namesake. Current aliases must not retarget that namesake.
for (final label in winning.difference(bases)) {
final candidates = bindings[label]!;
if (candidates.length == 1) remaining.removeAll(candidates);
}
for (final base in bases) {
// Multiple historical labels or leftover tagged keys cannot be paired
// deterministically. Leave them as non-clickable references, not guesses.
bindings[base] = bases.length == 1 ? remaining.toSet() : <String>{};
}
return bindings;
}

View File

@@ -67,4 +67,46 @@ void main() {
expect(tester.takeException(), isNull);
semantics.dispose();
});
testWidgets('historical namesakes preserve targets with ordinary siblings', (
tester,
) async {
final first = 'a' * 64, second = 'b' * 64, sibling = 'c' * 64;
for (final firstName in ['Scout', 'Renamed Scout', first]) {
for (final secondName in ['Scout', 'Renamed Scout', second]) {
for (final reverse in [false, true]) {
for (final ambiguous in [false, true]) {
final entries = {
first: firstName,
second: secondName,
sibling: 'Alice',
if (ambiguous) 'd' * 64: 'Unknown',
}.entries.toList();
String? tapped;
await tester.pumpWidget(
WidgetHelpers.testable(
child: MessageContent(
content: '@Scout @Scout ($second) @Alice',
mentionNames: Map.fromEntries(
reverse ? entries.reversed : entries,
),
onMentionTap: (key) => tapped = key,
),
),
);
await tester.pumpAndSettle();
if (ambiguous) {
expect(find.text(firstName), findsNothing);
} else {
await tester.tap(find.text(firstName));
expect(tapped, first);
}
await tester.tap(find.text('Scout (bbbbbbbb…bbbb)'));
expect(tapped, second);
await tester.tap(find.text('Alice'));
expect(tapped, sibling);
}
}
}
}
});
}