mirror of
https://github.com/block/buzz.git
synced 2026-10-30 22:11:36 +00:00
<!-- Draft PR body for #7385 · fix(mobile): bind same-name mentions to exact selected identities · https://github.com/block/buzz/pull/7385 --> 🤖 ## Summary If two people in a channel share a display name, mobile couldn't tell them apart in mentions: picking the second could overwrite the first's selection, a rendered mention linked whichever same-name person matched first, and a later rename or a shorter name could re-bind the text to the wrong recipient. This PR binds every mention to the exact selected identity: - Each same-name selection keeps its own recipient instead of overwriting by name; conflicting picks get a qualified label like `Name (key…)`. - The longest matching label wins, so a shorter or interior name can never claim part of a longer one and steal its identity. - Rendering resolves recipients by the signed identity key from the event's tags — never from message text alone — so qualified labels stay correct regardless of tag order and survive later renames; an untagged ambiguous label blocks shorter mentions instead of silently re-binding. Ports the landed Desktop exact-recipient behavior (see `docs/mention-editor.md`). ### Related issue - Fixes: N/A. No mobile issue; Desktop's landed exact-recipient fixes are the reference this ports. - Independent base (`main`). #7387 (child) persists these exact selections in saved drafts. - Landing note: branches in this series overlap in the composer — when rebasing, keep exact/durable mention bindings, the explicit invite/reference-only choice, the account/visit/revision fences, and authorization before membership preparation and publication; don't resolve conflicts by taking either side wholesale. - Draft — not requesting merge yet; the security advisory run for this range timed out without results (no verdict). ### Testing - Regressions cover same-name collisions, prefix/overlap, removal, tag order, and renames. - At `acd841354a692243f1cb4c04059baad42f1d2abb`: `just mobile-check` and full `just mobile-test` pass (2,082 tests). The earlier full `just ci` receipt linked below is reused only for unchanged non-mobile code/tooling, not claimed as a rerun at this head. - Previous-head evidence: `just mobile-check`, the full mobile test suite, and full local `just ci` all pass — receipts in the [exact-head evidence comment](https://github.com/block/buzz/pull/7385#issuecomment-5574635836). - Verification is widget-test level; no native device or simulator run is claimed. To see it: mention two teammates with the same display name — both stay distinct, the second shows a qualified label, and the rendered message keeps both correct even after either renames. ### Screenshots Flutter production-widget test renders — not native-device screenshots or acceptance captures. | Scenario | Before | After | |---|---|---| | Qualified mention chip for same-name recipients, at 200% text scale (deliberate stress fixture) |  |  | <details> <summary>Capture provenance</summary> Rendered by the Flutter widget engine in a `flutter test` run (production widgets, production theme; no device or simulator). Before: this PR's declared base `3c7f288c60d67df78577b237e27c3dfc8831aaa1`. After: its head `39afd73b0adfde14164f4b10dbd089cb498312b6`. </details> Signed-off-by: Logan Johnson <loganj@squareup.com>
77 lines
3.0 KiB
Dart
77 lines
3.0 KiB
Dart
import 'dart:async';
|
|
import 'dart:collection';
|
|
import 'dart:io';
|
|
import 'dart:math' as math;
|
|
import 'dart:ui' show FlutterView;
|
|
|
|
import 'package:camera/camera.dart' as camera;
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/physics.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
import 'package:image_picker/image_picker.dart';
|
|
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
|
|
|
import 'package:nostr/nostr.dart' as nostr;
|
|
|
|
import '../../shared/mentions/agent_identity_provider.dart';
|
|
import '../../shared/mentions/mention_bindings.dart';
|
|
import '../../shared/huddle/huddle_session.dart';
|
|
import '../../shared/relay/relay.dart';
|
|
import '../../shared/theme/theme.dart';
|
|
import '../../shared/widgets/avatar_image.dart';
|
|
import '../../shared/widgets/anchored_popover_menu.dart';
|
|
import '../../shared/widgets/buzz_loading_indicator.dart';
|
|
import '../../shared/widgets/concentric_sheet_surface.dart';
|
|
import '../../shared/widgets/keyboard_dismiss_on_drag.dart';
|
|
import '../../shared/widgets/ios_glass_navigation_button.dart';
|
|
import '../../shared/widgets/mobile_tab_footer_backdrop.dart';
|
|
import '../../shared/widgets/modal_presentation.dart';
|
|
import '../../shared/profile/user_cache_provider.dart';
|
|
import '../../shared/profile/user_profile.dart';
|
|
import '../../shared/custom_emoji/custom_emoji.dart';
|
|
import '../../shared/custom_emoji/custom_emoji_provider.dart';
|
|
import '../../shared/deeplink/deep_link.dart';
|
|
import '../activity/compose_drafts_provider.dart';
|
|
import 'camera_capture_cleanup.dart';
|
|
import 'channel.dart';
|
|
import 'channel_management_provider.dart';
|
|
import 'channels_provider.dart';
|
|
import 'emoji_picker.dart';
|
|
import 'mentions/mention_candidates.dart';
|
|
import 'mentions/mention_candidates_provider.dart';
|
|
import 'mentions/mention_ranking.dart';
|
|
import 'photo_library.dart';
|
|
import 'voice_note_attachment.dart';
|
|
import 'voice_note_composer_recorder.dart';
|
|
import 'voice_note_recording.dart';
|
|
|
|
part 'compose_bar/helpers.dart';
|
|
part 'compose_bar/agent_mention_labels.dart';
|
|
part 'compose_bar/markdown_editing_controller.dart';
|
|
part 'compose_bar/draft_lifecycle.dart';
|
|
part 'compose_bar/suggestions.dart';
|
|
part 'compose_bar/formatting_toolbar.dart';
|
|
part 'compose_bar/attachments.dart';
|
|
part 'compose_bar/upload_progress_pill.dart';
|
|
part 'compose_bar/photo_gallery_picker.dart';
|
|
part 'compose_bar/ios_photo_picker.dart';
|
|
part 'compose_bar/ios_attachment_popover.dart';
|
|
part 'compose_bar/camera_preview.dart';
|
|
part 'compose_bar/send_button.dart';
|
|
part 'compose_bar/voice_note.dart';
|
|
part 'compose_bar/layout.dart';
|
|
part 'compose_bar/dock.dart';
|
|
part 'compose_bar/compose_bar_widget.dart';
|
|
|
|
/// Callback used by channels and threads to submit composer content.
|
|
typedef ComposeBarOnSend =
|
|
Future<void> Function(
|
|
String content,
|
|
List<String> mentionPubkeys, {
|
|
List<List<String>> mediaTags,
|
|
});
|