block / block/buzz

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

Open
#6,124 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
32.7k
Forks
4.3k
Avg merge
1d 13h
Merged PRs (30d)
253

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

```markdown
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:

```text
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` @ f956e6fe06a76e50cbd8fba1a162482e752e7f1a

**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 `_ChannelLinkMd` — `mobile/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: []))` — 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**

- #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 `#4572` being turned into a pill, which is the same component firing where it shouldn't.
- #5257 — inline code unstyled on mobile; also a gpt_markdown config gap.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.