Repository navigation
fix(mobile): render Markdown backslash escapes - #8925
Conversation
gpt_markdown has no backslash escapes, so a message such as `[\#buzz-mobile](buzz://channel/...)` showed its backslash, and `\[x\]` rendered as LaTeX. Hold each escaped character outside code as a private-use stand-in before parsing, so no other syntax matches it, and restore it in a new inline component and in the link and image builders. The stand-in helpers move out of the preview text builder, which already handled escapes, so both share them. Shielding runs after trailing-gallery extraction so carousel URLs and labels stay as authored, and bare links no longer take a trailing backslash into their destination. Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Tom Brow <[email protected]>
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fd794a450a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The code-span scan took `\`` as a code delimiter, so `\`literal\`` rendered as code instead of literal backticks. Skip backtick runs preceded by an odd number of backslashes when looking for an opener. Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Tom Brow <[email protected]>
Every character in U+E021..U+E07E was read as an escape stand-in, so an authored U+E02A rendered as `*`. Prefix each stand-in with a U+E000 marker and double an authored marker, so only stand-ins are restored. Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Tom Brow <[email protected]>
🔐 Codex Security Review
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fd794a450a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Scan trailing backslashes off a bare URL by hand instead of matching `\\+$`, which retries from every offset and goes quadratic on a long backslash run. Prepend Buzz URL punctuation to the peeled backslashes instead of replacing them, so `buzz://channel/id.\` keeps its `\`. Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Tom Brow <[email protected]>
Gallery extraction runs before escape shielding, so an image label like `shot \#1` reached VoiceOver and the viewer with its backslash. Drop escapes when building the gallery item's semantic label. Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Tom Brow <[email protected]>
|
@buzz-security-review 7c0e477 |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7c0e47761c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Peel trailing backslashes again whenever stripping punctuation or an emphasis delimiter exposes one, so `*https://example.com/a\**` still becomes a link. Leave an escaped `\<url>` alone instead of rewriting it as an autolink; its URL is linked as a bare link and the `<` stays literal. Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Tom Brow <[email protected]>
|
@buzz-security-review 43f58e1 |
|
@codex review |
gpt_markdown parses a fence inside a quote only within the quoted lines, so an unclosed quoted fence ends where the quote does. The prose mapper kept treating the rest of the message as code, so a later URL lost its link and escaped punctuation kept its backslash. A fence opened at quote depth N now ends at the first line with fewer than N markers. Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Tom Brow <[email protected]>
|
@buzz-security-review d5b415a |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d5b415abfb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
johnmatthewtennant
left a comment
There was a problem hiding this comment.
🤖 Reviewed d5b415a with the complete teammate-review workflow (62 lenses, 124 outcomes). The URL-prefix and quoted-fence findings are fixed; no material correctness blockers remain. The escaped-audio segmentation report is nonblocking presentation feedback. No Flutter tests or device journey run by me.
|
@buzz-security-review d5b415a |
…8940) Follow-up to [#8925](#8925), from a [Codex finding](#8925 (comment)) there. After #8925, message Markdown arrives with backslash escapes shielded, but imeta is keyed by plain URLs. Two lookups used the raw, still-escaped destination: - **Bubble segmentation** (`bubble_segments.dart`): an attachment link such as `[voice.mp4](https://…/voice\_note.mp4)` missed its imeta entry and stayed inside the text bubble, even though the link builder rendered it as an attachment. The fix unshields the URL before the lookup. - **Trailing image gallery** (`extractTrailingImageGallery` in `message_gallery.dart`): with two or more trailing images, an escaped destination kept its backslash, so the carousel loaded the wrong URL and lost the imeta alt text. The fix unescapes the destination the same way as the label beside it. Its callers in `message_media_avatar.dart` and `message_skeleton_body.dart` get the fix too. This gap was already on main. ### Validation - The existing `attachments stay outside bubbles and text order is preserved` journey now uses an escaped destination with a plain imeta URL. The existing carousel journey now has one escaped image destination and taps it by its plain URL. Each fails with its fix reverted. - `flutter analyze` is clean and the full mobile suite passes (4151 tests). - Limitation: no simulator run. Both changes are key lookups, and the widget journeys exercise the rendered layout. The full-app run for the surrounding escape handling is recorded in #8925. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Tom Brow <[email protected]> Co-authored-by: Tom Brow <[email protected]> Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 <[email protected]>
Main now shields backslash escapes before rendering (#8925), so a trailing `\]` reaches the marker rule as text, never as display math. Drop that own-line rule; an escaped bracket now takes the inline marker. The thread edit-reload test from #8870 now expects the marker joined to the reply text. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Logan Johnson <[email protected]>
Main now shields backslash escapes before rendering (#8925), so a trailing `\]` reaches the marker rule as text, never as display math. Drop that own-line rule; an escaped bracket now takes the inline marker. The thread edit-reload test from #8870 now expects the marker joined to the reply text. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Logan Johnson <[email protected]>
Main now shields backslash escapes before rendering (#8925), so a trailing `\]` reaches the marker rule as text, never as display math. Drop that own-line rule; an escaped bracket now takes the inline marker. The thread edit-reload test from #8870 now expects the marker joined to the reply text. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Logan Johnson <[email protected]>
A backslash inside an angle autolink is literal in CommonMark, but shielding turned `\>` into a stand-in, so <https://e.com/a\> lost its closing bracket. Leave angle autolinks untouched (doubling only authored markers). gpt_markdown 1.3.4 now restores escapes in multi-line strikethrough, so Signed-off-by: Samin Yeasar Sohag <[email protected]> block#8925's known-limitation row expects # instead of its fullwidth form.
A backslash inside an angle autolink is literal in CommonMark, but shielding turned `\>` into a stand-in, so <https://e.com/a\> lost its closing bracket. Leave angle autolinks untouched (doubling only authored markers). gpt_markdown 1.3.4 now restores escapes in multi-line strikethrough, so Signed-off-by: Samin Yeasar Sohag <[email protected]> block#8925's known-limitation row expects # instead of its fullwidth form.
Main now shields backslash escapes before rendering (#8925), so a trailing `\]` reaches the marker rule as text, never as display math. Drop that own-line rule; an escaped bracket now takes the inline marker. The thread edit-reload test from #8870 now expects the marker joined to the reply text. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Logan Johnson <[email protected]>
Main now shields backslash escapes before rendering (#8925), so a trailing `\]` reaches the marker rule as text, never as display math. Drop that own-line rule; an escaped bracket now takes the inline marker. The thread edit-reload test from #8870 now expects the marker joined to the reply text. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Logan Johnson <[email protected]>
Message Markdown on mobile moves from gpt_markdown 1.2.1's legacy regex
pipeline to the CommonMark parser that 1.3 added ("plusparse"). Before
this, passing `inlineComponents` (now deprecated, removed in 2.0.0) kept
every message on the legacy pipeline.
### What changes for users
Rendering moves closer to desktop (react-markdown + remark-gfm):
- `_italic_` and `__bold__` work, backslash escapes and HTML entities
decode, and `[a [b] c](url)` labels are correct.
- Inline code follows CommonMark pairing: `"```npm install```"` in a
sentence is inline code, and four-backtick fences work.
- Setext headings, reference links, footnotes, `~~~` fences, `1)` lists
and CommonMark list/quote continuation render.
- HTML comments are hidden, as on desktop.
- `\[`, `\]`, `\(` and `\)` stay literal, as on desktop. Plusparse
hard-codes them as LaTeX delimiters, so they're written as entities
before rendering.
- Side effect: an unfenced `__init__` now renders as bold "init", and a
line over `---` becomes a heading, same as desktop.
Device benchmark (iPhone SE, profile build, 600 synthetic messages, 3
launches per build): scrolling spends 9% less time building, and frames
with more than 8 ms of build drop from 63 to 22. Jumping onto unbuilt
messages is 11% cheaper, and raster time is unchanged.
App size: the release app grows about 4.6 MB uncompressed (80.6 to 85.2
MB), mostly Dart code from the new math and highlighting packages.
### Before and after
Synthetic messages that reproduce differences found in real channel
history, rendered through `MessageContent` on main and on this branch:








### Demo
A scroll through synthetic messages covering common formatting
(headings, lists, quotes, links, highlighted code, a table, a task list,
escapes), recorded on an iPhone 17 Pro simulator running this branch:
https://github.com/user-attachments/assets/bdc9680c-d20b-41ec-8e1a-a5b51c602f9d
### How
- Mentions, custom emoji and `#channel` tokens are `InlinePattern`s.
Links go through `inlineLinkBuilder` into the existing `_buildLink`, so
chips, media links and taps are unchanged.
- Removed workarounds the parser makes unnecessary:
- the render-path escape shielding from #8925
- the `_MarkdownEscapeMd`, `_MarkdownLabelEntityMd` and `_MessageLinkMd`
components
- the zero-width prefix before a leading `#channel`
Plain-text previews and gallery helpers keep their own escape handling.
`normalizeBareLinks` stays because attachment and gallery detection
depend on its output. Bubble segmentation unescapes a link destination
itself before its imeta lookup, since content no longer arrives
shielded.
- Kept desktop parity where plusparse differs from GFM:
- a table typed directly under a caption line gets a blank line inserted
before it, since plusparse can't start a table mid-paragraph
- `> [!NOTE]` quotes render as quotes (`alertBuilder` returns
`asBlockQuote()`), since desktop has no alerts
- `mapMarkdownProse` now ends an unclosed backtick run at its paragraph.
Before, it could pair with a run in a later block and flip code and
prose for the rest of the message.
- Synthesized autolink labels escape backslashes instead of using
`\`, which the parser leaves undecoded in a URL-shaped label.
- Repo/PR/issue chips keep their own semantics node.
- Forum card previews clip at their 120 px cap instead of overflowing:
blocks now lay out as a column.
- Pinned to exactly 1.3.4. 1.4.0 renders HTML by default and themes
through the separate `material_ui` package, which would need a theme
bridge.
### Validation
- Full mobile suite passes and analyze is clean at 78b9c1a.
- All of #8925's and #8940's backslash-escape cases pass on the new
parser, including the strike-across-lines case that used to be a known
limitation.
- Real-message diff: 2,259 recent messages from 13 busy channels,
rendered through `MessageContent` on main and on this branch. 29
differed in text or styling. On inline and fenced code, the branch
matched desktop's parser (remark-gfm) in 23 of them and main in 7. The
review found the three parity gaps above, now fixed. The rest are
CommonMark behavior that desktop shares.
- Added tests, each confirmed to fail without its fix:
- a leading `#channel` renders as a link, not a heading
- a table under its caption renders as a table
- an alert-marker quote stays a quote
- an unclosed backtick run doesn't flip later code
- the transcript tests detect a rendered link by its style; the old
`LinkButton` check passed even with autolink on
- Automated simulator run (iPhone 17 Pro, iOS 27, debug build, signed in
to a Builderlab community): channel list, two channels with history
scrolled back several pages (mentions, link previews, thread summaries,
images), an agent Solo chat with long agent messages, and the inbox. No
rendering errors or layout breaks; the Solo chat screen also matched
main's build side by side.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Signed-off-by: Tom Brow <[email protected]>
Co-authored-by: Tom Brow <[email protected]>
Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Mobile showed Markdown backslash escapes literally. A message linking a channel as
[\#buzz-mobile](buzz://channel/…)rendered as\#buzz-mobile, and\[text\]rendered as LaTeX math. gpt_markdown has no backslash escapes.Outside code, each escaped ASCII punctuation character is now held as a stand-in (an invisible word joiner plus the character's fullwidth form) before parsing, so no other syntax (links, emphasis, mentions, channel tokens, math) matches it. A new inline component and the link and image builders restore the original character. The inbox/search preview builder already handled escapes this way; its helpers moved to a shared
markdown_escapes.dart.Two edge cases found in review are covered: shielding runs after trailing-gallery extraction so carousel URLs and labels stay as authored, and a bare link no longer takes a trailing backslash into its destination. Codex review added: escaped backticks stay literal instead of opening code, authored word joiners are not mistaken for stand-ins, Buzz URL punctuation peeling keeps a trailing backslash, gallery semantic labels drop escapes, the trailing-backslash peel is linear and reruns after delimiter stripping, an escaped
\<url>is not treated as an autolink,~~~fences are skipped like backtick fences, an autolink containing\keeps it in both label and destination, and the stand-ins use fullwidth characters so the one gpt_markdown fallback that shows raw text (multi-line strikethrough) still reads right.Later review: fence detection looks back only over blanks and
>markers, so it stays linear on long lines (security review) and treats an unclosed fence inside a quote or list as code, like the renderer (Carl).Behavior change:
\(x\)and\[x\]now show as(x)and[x]instead of math, per CommonMark. Desktop (buzz-app) doesn't render math either.$…$math is unchanged.Validation
MessageContenttable covering escapes in prose, bold, link labels (HTTP andbuzz://channel), fenced and inline code (left literal), a bare link ending in\, and escaped backticks, authored word joiners and fullwidth characters, a~~~fence, a multi-line strikethrough,<https://example.com/a\>, a Buzz URL ending in.\, an emphasized bare URL ending in\*, an escaped\<url>, and a trailing image gallery with escaped labels (including its semantics label), in plain and bubble layouts. Each row also asserts no stand-in reaches visible text or semantics. Reverting any one of these fixes fails it.flutter analyzeis clean and the full mobile suite passes, except for 11notification_toggle_test.darttests. Those fail locally on the Studio at the CI-green a3964ab as well, so they are environmental and unrelated to this change. A 256 KiB regression test covers the linear fence scan, and it fails on the earlier code.#buzz-github-prsand#buzz-mobile.Full-app screenshots
Before / after
Before (production build, reported):
After (this branch, same message):
🤖 Generated with Claude Code