Skip to content

feat(mobile/ios): show the message's own text in notifications - #9346

Merged
brow merged 4 commits into
mainfrom
ios-notification-full-body
Nov 11, 2026
Merged

brow merged 4 commits into
mainfrom
ios-notification-full-body

Conversation

@brow

@brow brow commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator

Expanding a Buzz notification today shows the same stub as the banner: one line of at most 180 characters, with code replaced by [code] and links by [link]. A long message can only be read by opening the app.

The notification body is now the message's own text. It keeps line breaks, the text of code and bare URLs, and drops the Markdown syntax around them ([text](url) shows text). It is capped at 256 UTF-16 units, which is the most iOS shows even when a notification is expanded. iOS cuts longer bodies mid-word with no ellipsis, so the cap stops at 255 units and adds "…" without splitting a character. A link label, image alt text or bold run may contain code, and a code fence with no closing fence runs to the end of the message. The body is built from at most the first 4096 UTF-16 units of the message, so very long or hostile Markdown stays fast (under 300 ms for a 256 KB message in an optimized build); when that cut drops text, the body also ends with "…".

Before (expanded) After (expanded)
Before After

ADR 0002 records the rules so other mobile platforms follow them, and the cases in test-fixtures/notification-body.json are the contract. The iOS tests run every case, and Android's notification code can run the same file when it lands.

What gets better, what doesn't get worse: only the body text of message notifications changes. The title, subtitle, grouping, avatars, navigation and dedupe are untouched, and the change lives in one pure function. A notification can now show code and URLs from the message, as the message itself does, and the user's lock-screen preview setting still applies.

Validation

  • Added tests: testPreviewBodyMatchesSharedNotificationBodyCases runs the 30 shared cases (links, mentions, images, fenced and inline code, code inside link labels and bold runs, Markdown inside code, unclosed and cut fences, whitespace, the cap at 256 and 257 units, emoji and ZWJ sequences at the cap, the 4096-unit input cut). The full BuzzPushKit suite passes: 112 XCTest and 70 Swift Testing tests.
  • iOS limit measured on an iPhone 17 Pro simulator (iOS 27) with a local notification: the expanded body and its accessibility label both stopped at exactly 256 UTF-16 units (256 ASCII characters, 128 emoji), cut mid-word with no ellipsis. A 40-line body showed about 31 lines.
  • The screenshots show this function's before and after output for the same message, posted as a local notification. The simulator doesn't run the Notification Service Extension for simctl push, so the full push path (relay fetch through to display) was not exercised. An iPad was not measured.
  • Human testing is waived; the automated run above covered display of the changed body only, since nothing else in the app changes.

🤖 Generated with Claude Code

Keep line breaks, code text and bare URLs in the notification body
instead of one 180-character line with [code] and [link], and cap it
at the 256 UTF-16 units iOS shows when expanded. ADR 0002 and the
shared test-fixtures/notification-body.json cases define the rules
for every mobile platform.

Signed-off-by: Tom Brow <[email protected]>
@brow

brow commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

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

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

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

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-11T13:34:47.627629Z 3ec5f94 Draft marked ready
ℹ️ 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: 9ad376d48b

ℹ️ 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".

square-tomb and others added 3 commits November 11, 2026 05:17
Take code-span text with NSString ranges instead of force-unwrapped
String range conversions, so a match that starts or ends inside a
grapheme cluster can't trap the notification service extension.

Signed-off-by: Tom Brow <[email protected]>
Cut content to its first 4096 UTF-16 code units (whole characters)
before stripping Markdown. The link and bold regexes take quadratic time
on unclosed markup, and relay content can be 256 KB, which could run the
notification service extension past its time budget. A cut body always
ends with an ellipsis.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Tom Brow <[email protected]>
Swap each code span for a private-use placeholder, run the link, image,
bold and strikethrough rules once over the whole text, then restore the
code in one pass. A link label or bold run can now contain code, and
Markdown inside code stays literal. A fence with no closing fence runs to
the end of the text, so a block cut at 4096 units keeps its code text.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Tom Brow <[email protected]>
@brow
brow marked this pull request as ready for review November 11, 2026 13:32
@brow
brow requested a review from a team as a code owner November 11, 2026 13:32
@brow
brow enabled auto-merge (squash) November 11, 2026 13:32
@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 11, 2026
@github-actions

Copy link
Copy Markdown

🔐 Codex Security Review

Note: This is an automated, security-focused review generated by Codex.
Use it as a supplement to human review; false positives are possible.

Scope

  • Exact PR diff: 1adde0e6bfe732b1b66a1db140596108c5f294a4...3ec5f94bb84d571de182b4eb2a84fdbe29e1de0b
  • Model: gpt-6.1-sol

💡 Click "edited" above to see earlier reviews for this PR.


Review Summary

Overall Risk: NONE

No concrete security, correctness, or reliability findings were identified in the authorized PR range.

Findings

No concrete security, correctness, or reliability findings were identified.

Notes

  • Review used read-only inspection of the diff, surrounding source, callers, and tests. Builds and tests were not executed, as requested.

Generated by Codex Security Review |
Requested by: @brow |
Workflow run

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

Kyle reviewed this PR and the Nexus review-tracker notes. Approving with one non-blocking suggestion.

Posted by Codex (gpt-6.1-sol) on behalf of kylez

/// span. A fence with no closing fence runs to the end of the text, as in
/// CommonMark, which also covers a block cut by `previewInputLimit`.
private static let previewCode = try! NSRegularExpression(
pattern: #"```(?:[^\n`]*\n)?([\s\S]*?)(?:\n?```|\z)|`([^`\n]*)`"#)

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] Recognize closing fences only on fence lines

A fenced code block containing a string with three backticks closes this match inside the string. Subsequent code then passes through previewProse; for example, const text = "**literal**"; becomes const text = "literal";. Could the fence matcher require a valid closing-fence line so literal code remains unchanged?

Posted by Codex (gpt-6.1-sol) on behalf of kylez

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.

🤖 Good catch, agreed: the lazy match closes at a `````` inside the code, and the rest gets prose treatment. I'll fix it in a follow-up PR (closing fence only at the start of its own line, plus a shared case for it) so this one can land on your approval.

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.

🤖 Fixed in #9349.

@brow
brow merged commit 95dd9d6 into main Nov 11, 2026
60 checks passed
@brow
brow deleted the ios-notification-full-body branch November 11, 2026 15:17
philazar added a commit that referenced this pull request Nov 11, 2026
Co-Authored-By: Claude Opus 5.5 <[email protected]>

* origin/main:
  feat(mobile/ios): show the message's own text in notifications (#9346)
  feat(mobile): prefill the agents you last tagged in a conversation (#9248)
  feat(mobile): start a Solo chat from a message or inbox row (#9251)
  feat(mobile): link to Builderlab web from Settings (#9300)
  fix(relay): bound invite preview host lookup (#9308)
  fix(relay): saturate filter limits instead of casting (#9230)
  fix(mobile): tell same-name agents apart in Solo (#9321)
  fix(mobile): join an open channel from the chat footer (#9253)
  fix(relay): fence pending subscriptions on close or replacement (#2382)
  fix(mobile): hide archived people and agents from choices; give them their own members section (#9320)
  Add video stacks and native iOS zoom playback (#9316)
  Support PNG and HEIC photos in the native iOS zoom gallery (#9314)
  fix(mobile): list known people in recipient pickers before a name is typed (#9311)
  feat(mobile): mark edited messages after the body (#8863)

Signed-off-by: Philip Azar <[email protected]>
philazar added a commit that referenced this pull request Nov 11, 2026
Co-Authored-By: Claude Opus 5.5 <[email protected]>

* origin/main: (22 commits)
  chore(deps): bump hickory-proto to 0.26.2 for RUSTSEC-2026-0487..0491 (#9351)
  feat(mobile/ios): show the message's own text in notifications (#9346)
  feat(mobile): prefill the agents you last tagged in a conversation (#9248)
  feat(mobile): start a Solo chat from a message or inbox row (#9251)
  feat(mobile): link to Builderlab web from Settings (#9300)
  fix(relay): bound invite preview host lookup (#9308)
  fix(relay): saturate filter limits instead of casting (#9230)
  fix(mobile): tell same-name agents apart in Solo (#9321)
  fix(mobile): join an open channel from the chat footer (#9253)
  fix(relay): fence pending subscriptions on close or replacement (#2382)
  fix(mobile): hide archived people and agents from choices; give them their own members section (#9320)
  Add video stacks and native iOS zoom playback (#9316)
  Support PNG and HEIC photos in the native iOS zoom gallery (#9314)
  fix(mobile): list known people in recipient pickers before a name is typed (#9311)
  feat(mobile): mark edited messages after the body (#8863)
  Add swipeable photo stacks and native iOS gallery preview (#9259)
  ci(docker): skip relay image publishes for main pushes outside the build context (#9310)
  Add manual-only relay image publishing (#9303)
  fix(mobile): bump Flutter to 3.47.9 for Android TLS fix (#9309)
  Serve video and poster previews for invite links (#9306)
  ...

Signed-off-by: Philip Azar <[email protected]>
brow added a commit that referenced this pull request Nov 12, 2026
…ences (#9349)

Follow-up to [#9346 notification message
text](#9346), from [Kyle's
review](#9346 (comment)).

A fenced code block that contains three backticks, such as a `"```"`
string literal, closed at those backticks. The rest of its code then got
the prose rules, so `"**literal**"` lost its asterisks in the
notification. A fenced block now closes only at a fence at least as long
as its opening fence that ends its line, as in CommonMark and the in-app
preview.

Along the way, fences the old regex handled loosely are now consistent
with the app: a block fenced with four or more backticks keeps its code
(and can hold a three-backtick fence), and a block's opening line is
dropped as its language even when it contains a backtick. A one-line
fenced span (`` ```just ci``` ``) and an unclosed fence running to the
end of the text, on one line or several, are unchanged.

**What gets better, what doesn't get worse:** code inside a fenced block
now shows exactly as written. The change is one regex in the
notification body function, and every existing shared case still passes.

### Validation

- Added tests: six shared cases in
`test-fixtures/notification-body.json`: backticks inside a block don't
close it, a closing fence at the end of the last code line closes the
block, a four-backtick block keeps its code, a four-backtick block can
hold a three-backtick fence, an unclosed one-line fence runs to the end,
and an opening line with a backtick is dropped. All 112 BuzzPushKit
XCTest cases and the Swift Testing suite pass.
- No visible change outside notification text for fenced code, so there
are no new screenshots. Human testing is waived; the shared-case run
covers the changed behavior.

🤖 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: Claude Opus 5.5 <[email protected]>

This branch was previously deployed

1 inactive deployment
codex-review — 3ec5f94b Deployed Nov 11, 2026 by brow via Run Codex Security Review #14357
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

codex-security-review-current The posted Codex security review matches its recorded range.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants