镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

fix(mobile): render Markdown backslash escapes - #8925

Merged
brow merged 12 commits into
mainfrom
mobile-markdown-link-escapes
Nov 2, 2026
Merged

brow merged 12 commits into
mainfrom
mobile-markdown-link-escapes

Conversation

@brow

@brow brow commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

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

  • Added tests: a MessageContent table covering escapes in prose, bold, link labels (HTTP and buzz://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 analyze is clean and the full mobile suite passes, except for 11 notification_toggle_test.dart tests. 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.
  • iPhone 17 Pro simulator (iOS 27) signed in to the internal community: loganj's original message now shows #buzz-github-prs and #buzz-mobile.
  • Full-app run on a3964ab: debug build on a fresh iPhone 17 Pro simulator (iOS 27), signed in to the internal community with live data. Covered the #buzz-github-prs timeline (long linked lists, bare URLs, mentions, reactions, thread summaries), a thread view, Inbox previews (the shared preview helpers), a DM with a bare link and a channel link, and search results. Everything rendered as before apart from the fixed escapes, with no stray stand-in characters. Limitations: debug build, iOS only, no gallery or code-block messages were in the visited history (the widget tests cover those), and a 165-second recording was taken but GitHub can't take it through our upload script. Two quirks seen are pre-existing and unchanged here: a bare link absorbs a trailing comma, and search shows raw Markdown for messages over 512 characters.
Full-app screenshots
Channel Channel (scrolled) Thread
channel scrolled thread
Inbox DM Search
inbox dm search

Before / after

Before (production build, reported):

01-before

After (this branch, same message):

02-after

🤖 Generated with Claude Code

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 <noreply@anthropic.com>
Signed-off-by: Tom Brow <tomb@squareup.com>
brow pushed a commit that referenced this pull request Nov 2, 2026
@brow

brow commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-02T18:15:47.224446Z d5b415a Manual request
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

@brow
brow marked this pull request as ready for review November 2, 2026 16:34
@brow
brow requested a review from a team as a code owner November 2, 2026 16:34
square-tomb and others added 2 commits November 2, 2026 08:35
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 <noreply@anthropic.com>
Signed-off-by: Tom Brow <tomb@squareup.com>
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 <noreply@anthropic.com>
Signed-off-by: Tom Brow <tomb@squareup.com>
@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

🔐 Codex Security Review

Status: review required for the current range.

The current range is 5f8d0aab98298f05b8676c1176d693e8db2c89d6...d5b415abfb743af0f18b3cf0fa4aa20db792322f.
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 d5b415abfb743af0f18b3cf0fa4aa20db792322f to authorize a new review.
Any previous review applies only to its recorded range.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

@github-actions github-actions Bot added codex-security-review-current The posted Codex security review matches its recorded range. and removed codex-security-review-current The posted Codex security review matches its recorded range. labels Nov 2, 2026
square-tomb and others added 2 commits November 2, 2026 08:42
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 <noreply@anthropic.com>
Signed-off-by: Tom Brow <tomb@squareup.com>
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 <noreply@anthropic.com>
Signed-off-by: Tom Brow <tomb@squareup.com>
@brow

brow commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator Author

@buzz-security-review 7c0e477

@brow

brow commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@github-actions github-actions Bot added codex-security-review-current The posted Codex security review matches its recorded range. and removed codex-security-review-current The posted Codex security review matches its recorded range. labels Nov 2, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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 <noreply@anthropic.com>
Signed-off-by: Tom Brow <tomb@squareup.com>
@github-actions github-actions Bot removed the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 2, 2026
@brow

brow commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator Author

@buzz-security-review 43f58e1

@brow

brow commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 2, 2026
for (var i = runStart - 1; i >= 0; i--) {
final char = content[i];
if (char == '\n') return true;
if (char != '>' && char.trim().isNotEmpty) return false;

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.

🤖 P2 · End fence protection when its quote ends

Accepting > here opens a message-wide fence state, but that state only ends at an explicit closing delimiter. For a message containing:

> ```sh
> echo example

https://example.com
plain \#text

gpt_markdown's BlockQuote matches only the quoted lines and parses the fence inside that substring. The following paragraph is ordinary prose. mapMarkdownProse instead preserves everything from the opening fence to the end of the message, so both link normalization and escape shielding skip the paragraph.

User/system effect: the URL after the quote is no longer clickable (MessageContent disables renderer autolinking), and its escaped punctuation keeps the backslash. Recognizing container fences also requires bounding protection to that container.

Test: an unclosed quoted fence followed by a blank line, an ordinary URL and escaped punctuation; the URL remains clickable and the prose shows #text.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🤖 Agreed, fixed in d5b415a. A fence opened at quote depth N now ends at the first line with fewer than N > markers, whether it closed or not. Your case renders plain #text and the URL after the quote is a link again. c847244 also makes a fence close only at the depth it opened at.

@github-actions github-actions Bot removed the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 2, 2026
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 <noreply@anthropic.com>
Signed-off-by: Tom Brow <tomb@squareup.com>
@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 2, 2026
@github-actions github-actions Bot removed the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 2, 2026
@brow

brow commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator Author

@buzz-security-review d5b415a

@brow

brow commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@brow
brow requested a review from wesbillman November 2, 2026 18:11

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 2, 2026

@johnmatthewtennant johnmatthewtennant 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.

🤖 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.

@brow

brow commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator Author

@buzz-security-review d5b415a

@github-actions github-actions Bot removed the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 2, 2026
@brow
brow merged commit 54ca2a3 into main Nov 2, 2026
52 of 53 checks passed
@brow
brow deleted the mobile-markdown-link-escapes branch November 2, 2026 18:35
brow added a commit that referenced this pull request Nov 2, 2026
…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 <tomb@squareup.com>
Co-authored-by: Tom Brow <tomb@squareup.com>
Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
loganj added a commit that referenced this pull request Nov 3, 2026
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 <noreply@anthropic.com>
Signed-off-by: Logan Johnson <loganj@squareup.com>
loganj added a commit that referenced this pull request Nov 4, 2026
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 <noreply@anthropic.com>
Signed-off-by: Logan Johnson <loganj@squareup.com>
loganj added a commit that referenced this pull request Nov 4, 2026
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 <noreply@anthropic.com>
Signed-off-by: Logan Johnson <loganj@squareup.com>
saminsohag added a commit to Infinitix-LLC/buzz that referenced this pull request Nov 4, 2026
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 <sssohag172@gmail.com>
block#8925's known-limitation row expects # instead of its fullwidth form.
saminsohag added a commit to Infinitix-LLC/buzz that referenced this pull request Nov 9, 2026
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 <sssohag172@gmail.com>
block#8925's known-limitation row expects # instead of its fullwidth form.
loganj added a commit that referenced this pull request Nov 9, 2026
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 <noreply@anthropic.com>
Signed-off-by: Logan Johnson <loganj@squareup.com>
loganj added a commit that referenced this pull request Nov 10, 2026
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 <noreply@anthropic.com>
Signed-off-by: Logan Johnson <loganj@squareup.com>

This branch was successfully deployed

No deployments
codex-review — d5b415ab Deployed Nov 2, 2026 by brow via Run Codex Security Review #11836
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.

4 participants