Skip to content

fix(mobile): keep token labels visible in authored Markdown links - #7545

Open
masfour7 wants to merge 3 commits into
block:mainfrom
Infinitix-LLC:fix/mobile-link-label-scopes
Open

masfour7 wants to merge 3 commits into
block:mainfrom
Infinitix-LLC:fix/mobile-link-label-scopes

Conversation

@masfour7

@masfour7 masfour7 commented Sep 9, 2026

Copy link
Copy Markdown

Summary

On iOS, an authored Markdown link renders as blank when its label is recognized as a Buzz channel, mention, or custom emoji. For example:

[#2959](https://example.com/)

The surrounding text remains visible, but the link has no visible label or tap target.

Buzz's _ChannelLinkMd, _MentionMd, and CustomEmojiMd components return WidgetSpans. gpt_markdown also renders an authored link through a WidgetSpan and reparses its label through the registered components. Because the Buzz components were allowed to run inside link labels, the result was a placeholder nested inside another placeholder, which does not paint on iOS.

This PR adds the same scope restriction used by gpt_markdown's built-in link component:

@override
Set<MarkdownScope> get scopes =>
    MarkdownComponent.allScopesExceptLinkLabel;

The override is applied to all three widget-based components. Authored labels such as [#2959](url), [@Alice](url), and [:wave:](url) therefore remain ordinary link text, while the same tokens outside links continue rendering as Buzz pills and custom emoji.

The PR also updates the shared test helpers to find RichText subclasses. Since the upgrade to gpt_markdown 1.2.1, body paragraphs can render through BidiRichText; find.byType(RichText) does not match that subclass and could allow an absence assertion to pass without inspecting the message text.

The handling of unknown bare #tokens is deliberately unchanged. Whether those should remain non-interactive pills or become plain text is a separate product decision.

Related issue

Fixes #6124.

This supersedes #6135. That PR solves the same issue by maintaining a custom fork of gpt_markdown's link component; this PR instead uses the scoped-component API already available in gpt_markdown 1.2.1.

Testing

Focused suites:

flutter test \
  test/features/channels/message_content_test.dart \
  test/features/channels/message_content_custom_emoji_test.dart

Result: 95 tests passed, 0 failed.

Additional checks:

  • just mobile-check — passed; analyzer reported no issues.
  • just mobile-fmt — passed; 549 files formatted with 0 changes.
  • Six new regression tests cover channel, mention, emoji, plain-link control, and outside-link behavior.
  • Removing the _ChannelLinkMd override causes two regression tests to fail; removing _MentionMd or CustomEmojiMd causes one test to fail in each case.
  • Three tests in message_content_test.dart directly assert that the nested-placeholder structure is absent. The custom-emoji tests separately verify literal text and the absence or presence of CustomEmojiImage, depending on whether the shortcode is inside or outside a link.

The complete local mobile suite reports 15 failures that reproduce identically on a clean checkout of main at 44316ff7. In CI for that same commit, Clients / Mobile, Mobile, and Mobile Swift Domain / Mobile Swift all passed.

No native-device screenshot is included. The nested-placeholder fix was verified through the rendered widget structure, but an iOS simulator build was blocked by the existing MLImage.framework arm64-simulator incompatibility.

@masfour7
masfour7 requested a review from a team as a code owner September 9, 2026 22:06
@github-actions

github-actions Bot commented Sep 9, 2026 •

Copy link
Copy Markdown

🔐 Codex Security Review

Status: review required for the current range.

The current range is 93ffc37cbdb7ced6a07eca11744931cdffb82e20...68643b6f7567853d6654adf1691dd5ef198452ae.
A new review must complete for this exact range. When manual authorization
is required, a user with write access must comment exactly
@buzz-security-review 68643b6f7567853d6654adf1691dd5ef198452ae to authorize a new review.
Any previous review applies only to its recorded range.

@masfour7
masfour7 force-pushed the fix/mobile-link-label-scopes branch from 73bcafb to 1ec6c4d Compare September 9, 2026 22:10
@ngthuydiem

Copy link
Copy Markdown
Contributor

@masfour7 Do you mind signing your commits following this guide https://github.com/block/buzz/blob/main/CONTRIBUTING.md#sign-your-commits?

gpt_markdown renders a link's label by recursing with
MarkdownScope.linkLabel. _ChannelLinkMd, _MentionMd, and CustomEmojiMd
must not return WidgetSpans inside that label, because nested placeholders
do not paint on iOS.

Opt the three components out of the link-label scope and update the tests
to find RichText subclasses rendered through BidiRichText.

Fixes block#6124

Signed-off-by: Mohammad Asfour <masfour700@gmail.com>

@ngthuydiem ngthuydiem left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix looks good. One blocker: #8109 replaced LucideIcons with BuzzIcons, so on current main this test file fails to compile (Undefined name 'LucideIcons' at lines 3103 and 3155). CI is green only because the re-runs tested against the Oct 7 main.

Please merge or rebase onto the latest main (with sign-off) and apply the two suggestions below.

);

expect(_findRich('#2959'), findsOneWidget);
expect(find.byIcon(LucideIcons.hash), findsNothing);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
expect(find.byIcon(LucideIcons.hash), findsNothing);
expect(find.byIcon(BuzzIcons.hash), findsNothing);


// Excluding the link-label scope must not disable the components
// everywhere else: the bare tokens keep their pills.
expect(find.byIcon(LucideIcons.hash), findsOneWidget);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
expect(find.byIcon(LucideIcons.hash), findsOneWidget);
expect(find.byIcon(BuzzIcons.hash), findsOneWidget);

Merge upstream main at 5397ea4 and use BuzzIcons.hash in both
link-label regression assertions, as requested in review.

Signed-off-by: Mohammad Asfour <masfour700@gmail.com>
@masfour7

Copy link
Copy Markdown
Author

@ngthuydiem Thanks for the feedback. Please check now.

@masfour7
masfour7 requested a review from ngthuydiem October 15, 2026 14:59
…el-scopes

Signed-off-by: Samin Yeasar Sohag <sssohag172@gmail.com>
@saminsohag

Copy link
Copy Markdown

@ngthuydiem Both suggestions are applied (BuzzIcons.hash in message_content_test.dart) and the branch is merged with the latest main with sign-off. Locally on the merged tree, just mobile-check is clean and just mobile-test passes (3498 passed, 4 skipped). Could you re-review, and approve the pending CI workflow runs for this fork PR? Thanks!

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

mobile: Markdown links with a #-prefixed label render as nothing (desktop renders them fine)

4 participants