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

fix(mobile): show already-loaded custom emoji in the iOS reactions sheet - #9150

Merged
brow merged 2 commits into
mainfrom
reactions-sheet-custom-emoji
Nov 9, 2026
Merged

brow merged 2 commits into
mainfrom
reactions-sheet-custom-emoji

Conversation

@brow

@brow brow commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

On iOS, the reactions sheet (long-press a reaction pill) could show a custom emoji as truncated :shortcode: text, for example :eye…, even though the same emoji rendered as an image on the message pill a moment before.

The sheet is a native UIKit view that downloaded each custom emoji again with its own URLSession request and showed the shortcode label until that download finished, and for good when it failed. A shortcode is illegible in a chip that size.

Now:

  • Flutter keeps the encoded bytes of custom emoji it has downloaded (a bounded LRU: 512 KiB per emoji, 8 MiB total, rebuilt when the media auth scope changes so accounts never share bytes) and hands them to the sheet, which decodes them without a fetch. Emoji the timeline never loaded still download natively as before.
  • A custom emoji in the sheet never shows its shortcode: it stays blank while loading and shows the sparkles icon, matching the native emoji picker, if the image cannot load. Reaction pills in Flutter get the same sparkles fallback. Inline emoji in message text keep the shortcode fallback, where it reads as text. Avatars in the sheet keep their initial.

Before / after

Reactions sheet header and chips right after opening, on a message with two custom-emoji reactions (synthetic test channel, captured from a 30 fps screen recording).

Before (main) After (this branch)
Before: custom emoji chips show truncated :qa-... shortcodes After: custom emoji chips show images

Validation

  • Added tests: the native sheet's payload carries the bytes the pill already downloaded; the combined reaction tray shows sparkles, not the shortcode, when a custom emoji fails to load (the existing alignment test previously asserted the shortcode).
  • Full mobile suite and just mobile-check pass; the iOS simulator build compiles.
  • Automated simulator run (iPhone 17 Pro, iOS 27, main vs this branch, signed in to a test community): channel list, opening a channel, the message action sheet, the emoji picker, and the reactions sheet on a message carrying two custom emoji I added plus a thumbs-up. On main the sheet's custom-emoji chips show truncated :qa-... shortcodes for a few frames before the images arrive; on this branch they render as images from the first frame (see Before / after). Limitations: the emoji images load fine over the network in this setup, so the failed-download sparkles fallback was covered by tests only, not seen in the app; a long press on the combined reaction tray opened the message menu in the simulator, a tap opened the sheet.

🤖 Generated with Claude Code

The native reactions sheet downloaded each custom emoji again with
URLSession and showed the `:shortcode:` label until that download
succeeded, keeping it when the download failed. So an emoji visible on
the message pill could appear as truncated shortcode text in the sheet.

Flutter now keeps the encoded bytes of custom emoji it has downloaded
(a bounded LRU, rebuilt with the media auth scope) and passes them to the
sheet, which decodes them without a fetch. A custom emoji in the sheet
or a reaction pill never shows its shortcode: the sheet stays blank while
loading, and both show sparkles if the image cannot load, like the native
emoji picker.

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 5, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 5, 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-08T21:49:45.966524Z 9c0c3ca 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: 9c0c3ca18b

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

Comment on lines +403 to +406
if let decoded { self.show(decoded) } else { fallback() }
}
}
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fall back to the URL when cached decoding fails

When Flutter receives a transient malformed 2xx response, MediaImageProvider._loadAsync invokes onBytesLoaded before attempting its own decode, so these bytes are cached even though the pill displays its error state. This branch treats those bytes as authoritative: if native decoding fails it shows sparkles and returns without trying the URL, whereas the previous sheet implementation could make a fresh request and recover after the server response was fixed. Continue through the existing URL-loading path when cached-byte decoding returns nil.

Useful? React with 👍 / 👎.

Comment on lines +398 to +400
if let bytes = data["bytes"] as? FlutterStandardTypedData {
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
let decoded = NativeMessageImageLoader.decode(bytes.data)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Deduplicate cached-byte decoding across glyphs

When a custom-emoji reaction has multiple visible reactors, the filter glyph and every reactor-row glyph receive the same encoded bytes, but each independently submits a full animated-image decode to the global queue. Rapidly scrolling a large reactor list can therefore enqueue many uncancelled duplicate decodes, bypassing the four-operation admission limit and decoded-image cache used by NativeMessageImageLoader; route cached bytes through a shared URL-keyed, bounded decoder/cache instead.

AGENTS.md reference: AGENTS.md:L249-L257

Useful? React with 👍 / 👎.

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] Share decoded images across cached-byte glyphs

This also repeats during profile hydration: updateProfiles reloads visible rows, each row creates a fresh glyph, and each glyph decodes the same animation again. Could cached bytes use the shared decoded-image cache and admission limit, rather than dispatching a decode per glyph?

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

@brow
brow marked this pull request as ready for review November 8, 2026 21:47
@brow
brow requested a review from a team as a code owner November 8, 2026 21:47

@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: 9c0c3ca18b

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

filterQuality: FilterQuality.medium,
semanticLabel: ':$shortcode:',
errorBuilder: (_, _, _) => Text(':$shortcode:', style: fallbackStyle),
onBytesLoaded: (bytes) => bytesCache.remember(url, bytes),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Forward bytes that arrive after opening the sheet

When the sheet is opened while the pill's image request is still in flight, reactionData() snapshots a cache miss and UIKit starts its own request; this callback may then populate the cache, but the cache is non-reactive and updateReactions() only runs when the reaction list changes. If the Flutter request subsequently succeeds while UIKit's separate request fails, the pill displays the emoji but the open sheet remains on sparkles. Notify the active sheet when cached bytes arrive, or otherwise share the in-flight load.

Useful? React with 👍 / 👎.

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

github-actions Bot commented Nov 8, 2026 •

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: 8cce12f591988f29cc2beab5ff0c89d51862f31b...e47b32c07b22d6ade16464a0ff6e8ab2ad28e675
  • Model: gpt-6.1-sol

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


Review Summary

Overall Risk: MEDIUM

The cached emoji path introduces resource amplification that can exhaust iOS client memory.

Findings

[MEDIUM] Deduplicate and bound decoding of cached emoji bytes

  • Category: Reliability
  • Location: mobile/ios/Runner/NativeReactionDetails.swift:398 (source)
  • Description: Every byte-backed glyph now starts an independent decode, bypassing NativeMessageImageLoader's request deduplication, decoded-image cache, admission limit, and cancellation. A member can submit many distinct reaction shortcodes pointing to the same animated image; reaction uniqueness includes the shortcode, and timeline aggregation preserves these groups. Flutter shares the image and caches its bytes, but opening the native sheet creates every filter eagerly and decodes a separate image for each alias. A cached animation below 512 KiB can expand to roughly 3 MiB across 60 frames, so hundreds of aliases can retain around a gigabyte of duplicate decoded images.
  • Impact: Opening an affected reaction sheet can stall or terminate the iOS client through memory exhaustion.
  • Recommendation: Route cached bytes through a shared, bounded decoding path keyed by URL and auth scope, with cancellation and decoded-image reuse. Also deduplicate encoded bytes in the platform payload.

Notes

  • Validation was limited to read-only source inspection; builds and tests were not run.

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

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

Copy link
Copy Markdown
Collaborator

🤖 Meli, Tyler’s AI agent, posting on his explicit behalf.

The remaining gap is verification of the transient fetch boundary. Both main and PR render the loaded custom-emoji pill and native reactions sheet successfully in the online happy path, so those captures do not demonstrate the reported failure or its fix. Please exercise opening the sheet after the pill has loaded while a subsequent fetch fails, and show that the retained bytes still render the emoji. The full mobile suite passed; no new production defect is established by this verification gap.

Reviewed head: e47b32c07b22d6ade16464a0ff6e8ab2ad28e675.

@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. Approved with the non-blocking cached-byte decoding feedback in the existing thread.

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

@brow
brow merged commit ac2c583 into main Nov 9, 2026
53 checks passed
@brow
brow deleted the reactions-sheet-custom-emoji branch November 9, 2026 14:59
bostonaholic added a commit that referenced this pull request Nov 9, 2026
…ction-p

* origin/main: (111 commits)
  feat(mobile): tag the reacted-to message author on reactions (#9141)
  fix(mobile): steady New DM picker rows and animate picks (#9249)
  perf(mobile): keep remote images on disk and show small ones as thumbnails (#9158)
  fix(relay): deliver slow cross-node events late instead of closing their recipients (#9235)
  fix(mobile): read only shown events from filtered Activity rows (#9215)
  fix(mobile): show the channel name as the notification subtitle (#9173)
  fix(mobile): keep Home's header clean under sheets (#9152)
  fix(mobile): show already-loaded custom emoji in the iOS reactions sheet (#9150)
  mobile: add follow and remind actions to inbox row menu (#9142)
  fix(mobile): start no threads in Solo chats (#9123)
  mobile: persist user profiles so names and avatars render on launch (#9116)
  fix(mobile): keep a sent thread reply above the composer (#9026)
  [buzz] Recover database pool capacity after stalled connections (#9267)
  feat: send a User-Agent on relay WebSocket upgrades (#9246)
  Record why relay connections close and quiet close warnings (#9245)
  fix(relay): say "channel owner or admin" in the call_webhook save refusal (#9169)
  feat(mobile): consolidate Solo chat settings in one sheet (#9255)
  fix(cli): preserve attachments across successive edits (#9226)
  fix(mobile): swipe left to reply so swipe-back keeps rightward drags (#9127)
  mobile: long-press a message link for open, copy and share (#9135)
  ...

Signed-off-by: Matthew Boston <mboston@squareup.com>
fsola-sq added a commit that referenced this pull request Nov 9, 2026
…-profile

* origin/main: (307 commits)
  fix: inline ARM64 atomics in Sprig image build (#9291)
  fix(mobile): show observed thread replies in Activity (#9286)
  Enable mobile huddles with Codex agents (#9269)
  fix(mobile): read mentions taller than the screen (#9243)
  test(relay): give every media e2e upload its own blob hash (#9272)
  Add invite preview video and fallback poster (#9282)
  NEXUS-4336: Wait for desktop-provider socket ownership proof (#9270)
  fix: unblock image audit with patched MCP SDK (#9287)
  feat(ci): allow admin dispatch of desktop candidate validation (#9288)
  feat(mobile): tag the reacted-to message author on reactions (#9141)
  fix(mobile): steady New DM picker rows and animate picks (#9249)
  perf(mobile): keep remote images on disk and show small ones as thumbnails (#9158)
  fix(relay): deliver slow cross-node events late instead of closing their recipients (#9235)
  fix(mobile): read only shown events from filtered Activity rows (#9215)
  fix(mobile): show the channel name as the notification subtitle (#9173)
  fix(mobile): keep Home's header clean under sheets (#9152)
  fix(mobile): show already-loaded custom emoji in the iOS reactions sheet (#9150)
  mobile: add follow and remind actions to inbox row menu (#9142)
  fix(mobile): start no threads in Solo chats (#9123)
  mobile: persist user profiles so names and avatars render on launch (#9116)
  ...

Signed-off-by: Franco Sola <fsola@squareup.com>

# Conflicts:
#	docs/nips/NIP-FA.md
#	docs/nips/NIP-FI.md
wpfleger96 added a commit that referenced this pull request Nov 9, 2026
…e-mode

* origin/main: (36 commits)
  feat(relay): let an operator mint invites for a community owner or admin (#9265)
  fix: inline ARM64 atomics in Sprig image build (#9291)
  fix(mobile): show observed thread replies in Activity (#9286)
  Enable mobile huddles with Codex agents (#9269)
  fix(mobile): read mentions taller than the screen (#9243)
  test(relay): give every media e2e upload its own blob hash (#9272)
  Add invite preview video and fallback poster (#9282)
  NEXUS-4336: Wait for desktop-provider socket ownership proof (#9270)
  fix: unblock image audit with patched MCP SDK (#9287)
  feat(ci): allow admin dispatch of desktop candidate validation (#9288)
  feat(mobile): tag the reacted-to message author on reactions (#9141)
  fix(mobile): steady New DM picker rows and animate picks (#9249)
  perf(mobile): keep remote images on disk and show small ones as thumbnails (#9158)
  fix(relay): deliver slow cross-node events late instead of closing their recipients (#9235)
  fix(mobile): read only shown events from filtered Activity rows (#9215)
  fix(mobile): show the channel name as the notification subtitle (#9173)
  fix(mobile): keep Home's header clean under sheets (#9152)
  fix(mobile): show already-loaded custom emoji in the iOS reactions sheet (#9150)
  mobile: add follow and remind actions to inbox row menu (#9142)
  fix(mobile): start no threads in Solo chats (#9123)
  ...

Signed-off-by: Will Pfleger <wpfleger@block.xyz>

This branch was previously deployed

1 inactive deployment
codex-review — e47b32c0 Deployed Nov 8, 2026 by brow via Run Codex Security Review #13897
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.

4 participants