Repository navigation
Description
Describe the bug
On iOS, a Markdown link whose label starts with # renders as nothing at all — the label, the underline, and the tap target are all gone. Only the surrounding punctuation survives.
A message body like
Dasselbe Muster gibt es dreimal ([#2959](https://github.com/block/buzz/issues/2959), [#2868](https://github.com/block/buzz/issues/2868), [#5409](https://github.com/block/buzz/issues/5409)).renders on iOS as:
Dasselbe Muster gibt es dreimal (, , ).
The same message renders correctly on Buzz Desktop (macOS), with three underlined, tappable links.
Steps to reproduce
- From Buzz Desktop, post a message into a channel containing
See [#2959](https://github.com/block/buzz/issues/2959) for details. - Open the same message in the iOS app (App Store 0.10.0).
- The link is invisible; the sentence reads
See for details.
Expected behavior
The link renders with its authored label #2959 and opens the URL on tap, as it does on desktop.
Actual behavior
Nothing is drawn where the link should be.
Version and platform
- iOS app: 0.10.0 (App Store, released 2026-08-13)
- Desktop for contrast: renders correctly
- Analysis below against
main@ f956e6f
Analysis
The link label is being consumed by the #channel inline component instead of being rendered as link text.
MessageContentregisters three custom inline components ahead of the gpt_markdown defaults, one of which is_ChannelLinkMd—mobile/lib/features/channels/message_content.dart:284-296.- gpt_markdown's
ATagMd.spanrenders a link's label by recursing:MarkdownComponent.generate(context, linkText, config, false), which resolves components fromconfig.inlineComponents(gpt_markdown 1.1.6,lib/markdown_component.dart:41-44and:858-864). So the custom components run inside link labels. _ChannelLinkMd's pattern (mobile/lib/features/channels/message_content/token_pill.dart:13-17, built by_buildPrefixPatternat:107-138) accepts a purely numeric token —genericTokenPatternis[A-Za-z0-9_][A-Za-z0-9_-]*— and its trailing boundary(?=$|[\s,;.!?:)\]}])is satisfied at the end of the label. So the label#2959matches and is replaced by a_TokenPillWidgetSpan.- The resulting
InlineSpanreaches Buzz'slinkBuilderatmobile/lib/features/channels/message_content.dart:338-478.visitChildrenthere only accumulatesTextSpan.text, sotextcomes out empty, and the widget handed back isText.rich(TextSpan(children: [<WidgetSpan pill>]))— a placeholder nested inside a placeholder. On device the result draws nothing.
Steps 1–3 are certain from the source. Step 4 is the observed outcome; I have not been able to run the Flutter app to confirm exactly where the nested placeholder is lost.
A useful discriminator when confirming: links whose label does not start with # (for example [`0f61f24`](https://github.com/block/buzz/commit/0f61f24ad)) are reported as still visible in the same message.
Note on scope
Reported as a bug, not a proposed patch. The crux is ordering: an authored [label](url) has its label consumed by the custom inline components before it is ever resolved as a link. Link resolution should win over #/@ token detection inside a label. Where in the pipeline that ordering belongs is for whoever owns the mobile Markdown rendering — the reporter is not prescribing a fix, and in particular "reject digits-only tokens" would only hide this instance, not the class.
Related
- mobile: message links not tappable / openable (works on desktop) #4572 — mobile: message links not tappable / openable. Same area, different symptom (that one is about a link that renders but doesn't open; this one renders nothing). The screenshot comment there also shows
#4572being turned into a pill, which is the same component firing where it shouldn't. - [Bug] Inline code is unstyled on mobile — no highlightBuilder, so it diverges from desktop #5257 — inline code unstyled on mobile; also a gpt_markdown config gap.
Activity
FabianHertwig commentedon Aug 18, 2026
Confirmed on device.
The reporter checked the discriminator named in the issue body: in the same message, on the same iOS build (App Store 0.10.0), the link whose label does not start with
#—[`0f61f24`](https://github.com/block/buzz/commit/0f61f24ad)— renders and is visible, while the three[#NNNN](…)links render as nothing ((, , )).So the
#in the label is the trigger, and steps 1–3 of the analysis (custom inline components running insideATagMd's label recursion) are the live cause. Step 4 — exactly where the nestedWidgetSpan-in-WidgetSpanis dropped — is still unverified in a running app.The "Suggested directions" section has been replaced with a scope note: this is a report, not a proposed patch, and the digits-only tweak was withdrawn as a fix candidate because it only masks this instance.
masfour7 commentedon Aug 23, 2026
We’re the team behind
gpt_markdown, and we traced this against Buzz’s mobile implementation. The fix is available in 1.2.1. Here are the required changes inbuzz/mobile.1. Upgrade
This requires Flutter 3.32 or newer; Buzz is already above that.
2. Update
imageBuilderBecause Buzz is upgrading from 1.1.6, update the call in
message_content.dart:codeBuilderandlinkBuilderare unchanged.3. Keep widget-based tokens out of link labels
This is the change that fixes the invisible links. Buzz’s mention, channel, and emoji components return
WidgetSpans. When one runs inside an authored link label, it becomes aWidgetSpannested inside the link’s own span, which does not paint on iOS.Add this override to
_MentionMd,_ChannelLinkMd, andCustomEmojiMd:After this,
[#2959](url)remains an authored Markdown link and renders normally on iOS.4. Stop unknown issue numbers from becoming channel pills
_ChannelLinkMdcurrently has a generic fallback that accepts digits, so#2959is treated as a channel even when it is not inchannelNames.We recommend matching only known channel names.
InlinePattern.prefixednow handles this directly:Leaving
genericTokenPatternunset means only real channels are claimed, while values such as#2959remain plain text.InlinePatternalso excludes link labels by default.5. Update the test helpers
Some paragraphs now render through
BidiRichText, which is a subclass ofRichText.find.byTypeonly matches the exact runtime type.In
message_content_test.dart, replace:With these changes, the custom
_AuthoredMarkdownLinkMdworkaround and package version pin in #6135 should no longer be necessary.fix(mobile): keep token labels visible in authored markdown links
fix(mobile): keep token labels visible in authored markdown links
fix(mobile): keep token labels visible in authored markdown links
fix(mobile): keep token labels visible in authored markdown links
fix(mobile): keep token labels visible in authored Markdown links (bl…