Skip to content

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

Description

@FabianHertwig

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

  1. From Buzz Desktop, post a message into a channel containing See [#2959](https://github.com/block/buzz/issues/2959) for details.
  2. Open the same message in the iOS app (App Store 0.10.0).
  3. 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.

  1. MessageContent registers three custom inline components ahead of the gpt_markdown defaults, one of which is _ChannelLinkMdmobile/lib/features/channels/message_content.dart:284-296.
  2. gpt_markdown's ATagMd.span renders a link's label by recursing: MarkdownComponent.generate(context, linkText, config, false), which resolves components from config.inlineComponents (gpt_markdown 1.1.6, lib/markdown_component.dart:41-44 and :858-864). So the custom components run inside link labels.
  3. _ChannelLinkMd's pattern (mobile/lib/features/channels/message_content/token_pill.dart:13-17, built by _buildPrefixPattern at :107-138) accepts a purely numeric token — genericTokenPattern is [A-Za-z0-9_][A-Za-z0-9_-]* — and its trailing boundary (?=$|[\s,;.!?:)\]}]) is satisfied at the end of the label. So the label #2959 matches and is replaced by a _TokenPill WidgetSpan.
  4. The resulting InlineSpan reaches Buzz's linkBuilder at mobile/lib/features/channels/message_content.dart:338-478. visitChildren there only accumulates TextSpan.text, so text comes out empty, and the widget handed back is Text.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

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions