On iOS, custom emoji in the native emoji picker often render as the
sparkle fallback icon instead of the image.
The native picker downloads custom emoji with `URLSession` and asks
Flutter for request headers over the `mediaHeaders` channel call. That
call returned only the media read `Authorization` header. Hosted relays
also require the `Nostr-Federated-Identity` assertion, which the Flutter
image path gets from `HostedRelayHttpClient`, so relay-hosted emoji get
a 401 and the picker falls back to sparkles. Emoji hosted elsewhere
still load, which is why only some are missing.
The `mediaHeaders` bridge now also includes the hosted relay assertion
via a new `HostedRelayAuth.headersForUrl`, scoped to the relay's own
origin like `HostedRelayHttpClient`. The native loader refuses redirects
for requests that carry the assertion, matching the Dart client, so the
token can't be forwarded to another host.
The iOS reactions sheet had the same gap: it passes avatar and custom
emoji URLs to native views with only the `Authorization` header. A new
`nativeMediaHeadersProvider` combines both headers for any native media
consumer, and the reactions sheet uses it.
### Validation
- Added tests: the native picker's `mediaHeaders` call returns the relay
assertion for relay media and no headers for an external emoji URL;
`headersForUrl` returns the assertion only for the relay origin (not
another host, subdomain or port). The reactions sheet passes the
combined headers for avatars and custom emoji, and
`nativeMediaHeadersProvider` merges both headers only for relay URLs.
- Focused existing validation: the emoji picker and organization relay
auth suites pass, and `flutter analyze` is clean. In the full mobile
suite, the only failures are 10 in `notification_toggle_test`,
`push_bootstrap_test` and `push_bridge_test`, and they fail the same way
on `main`.
- Automated full-app simulator run (iPhone 17 Pro, iOS 27, debug build
of this branch with hosted auth enabled, signed in to two hosted
communities through a cloned simulator keychain, driven with AXe).
Covered: launch signed in, channel and DM lists, opening a channel and a
DM, the native emoji picker (its Custom section shows the emoji as an
image, not the sparkle fallback), sending a custom emoji message and
seeing it render, reacting with the custom emoji (shown in the
quick-reaction row and as a reaction pill), opening a thread, switching
communities, avatars and community icons, background and resume, and a
cold relaunch. Result: no crashes or regressions seen. Screenshots are
omitted because they show real account and community names.
- Limitations: this run does not reproduce the original bug. The hosted
community's custom emoji also loaded in a temporary build with the relay
assertion removed from the `mediaHeaders` bridge, so its emoji URL
evidently doesn't need the assertion, and the run doesn't prove the fix.
The reactions detail sheet could not be reached because simulated long
presses on the pill opened the message menu, and inline image
attachments were not exercised. A device build against a community whose
emoji are relay-hosted is still needed to confirm the fix.
🤖 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: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz>
## Summary
Polish mobile Home and Solo: group Search/Inbox beside a separate
profile avatar, move Inbox and Settings to bottom-up pages with Close
controls, and place tabs opposite the plus/compose action. Morph plus to
compose clockwise when entering Solo and reverse counterclockwise when
returning Home, with a directional quarter-turn of the icon inside a
level button and a subtle scale pulse, respecting reduced motion. Align
Search’s Close control with Inbox, simplify its placeholder, and
initialize selected filter contrast correctly. Collapse the plus menu
when leaving Home. Center Solo’s native-glass New chat empty state and
keep it stationary behind the keyboard.
Standardize Tabler icons and neutral backgrounds, retain SF Back/Close
on iOS, use native Liquid Glass headers, tab container, and actions
(solid grey selected tab, slightly darker on iOS) on iOS and black
actions on Android, and smooth the expanded menu corners. Repair native
header safe-area/return layout and compose movement after cancellation.
### Related issue
None found for this UI refinement; follows #8337.
### Testing
- Formatting, analysis, full mobile suite: 3,487 passed, 4 skipped; 3
push-configuration tests passed. File-size checks passed.
- Base UI built and installed on Pixel and iPhone in place. Pixel
navigation/New chat cancellation checked. Subsequent glass and rotation
refinements are previewed on iPhone; motion feel awaits human
confirmation.
- Draft pending human confirmation of the final Settings entrance/return
behavior on iPhone.
### Pixel snapshots
| Solo | Expanded actions |
| --- | --- |
| <img
src="https://raw.githubusercontent.com/block/buzz/16dc0f8af971e63d788fee20974997fab925dec9/docs/screenshots/mobile-home-solo/pixel-solo.png"
width="280" alt="Solo empty state with New chat" /> | <img
src="https://raw.githubusercontent.com/block/buzz/16dc0f8af971e63d788fee20974997fab925dec9/docs/screenshots/mobile-home-solo/pixel-actions.png"
width="280" alt="Expanded channel actions with smooth corners" /> |
---------
Signed-off-by: kenny lopez <klopez4212@gmail.com>
Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz>