Skip to content

fix(mobile): draw the chat's bottom mask on iOS instead of UIKit's edge effect - #2398

Merged
DoodleBears merged 10 commits into
mainfrom
claude/project-thread-xvy5v4
Oct 20, 2026
Merged

DoodleBears merged 10 commits into
mainfrom
claude/project-thread-xvy5v4

Conversation

@DoodleBears

@DoodleBears DoodleBears commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Vain · project thread

Why the change

On iOS 26+ the native composer (#2355) used UIKit's bottom scroll edge effect through UIScrollEdgeElementContainerInteraction. UIKit picked the hard style there: an opaque band in a different colour from the message list with a hairline across its top, at a height that ignored a wrapped draft, so with a 4-line draft and the keyboard closed messages showed crisp under the capsule. Re-adding the interaction and pinning scrollEdgeEffects.bottom: 'soft' did not fix it (shots -r2). The target is Grok's chat: messages fade naturally into an almost opaque background under the composer.

Refs #2355

Special things to note

  • Merge risk: mobile chat screen only. iOS now uses the same drawn bottom mask as Android (feat(mobile): float the Android chat header and mask both chat edges like iOS #2394); the native bottom edge effect is hidden and the local scroll-edge-element native module is removed (a dev client that still contains it is harmless, nothing calls it). The top edge stays UIKit's automatic effect. Revert = this PR.
  • Verification: tsc, pnpm lint, mobile vitest (938 passed, 9 skipped) locally; CI green. Simulator (iOS 27, default text size, light and dark, DM with long history): empty composer with keyboard closed has no hairline and no hard band (r3 on 79c328b, r4 on 3df8ee8); 4-line draft opened fresh is masked under and around the capsule (r3); keyboard open fine; header unchanged.
  • Not yet re-verified: typing a 4-line draft with the keyboard up, then dismissing it with an interactive drag. On r3 the mask kept its old shorter height there; 3df8ee8 sizes the SVG to its pixel height to fix that, but the retest was blocked by the Simulator's hardware-keyboard mode. It will be retested on main in the next batch. Also not run: Android, large and max text sizes, Reduce Transparency.
  • CI test plan: affected (mobile source, DESIGN.md, change fragment).

Change outline

  • ConversationScreen: on iOS 26 scrollEdgeEffects: { top: 'automatic', bottom: 'hidden' }; the composer is no longer wrapped in ScrollEdgeElement; one EdgeFade at the bottom with cover={composerHeight} in the chat's background colour on every platform.
  • EdgeFade: with a cover, always draws the gradient mask (not the EdgeMaterial blur), and sizes the SVG to its pixel height so it follows the composer.
  • Removed modules/scroll-edge-element and ui/primitives/scroll-edge-element*.
  • DESIGN.md and the unreleased changes/2355-native-composer.md describe the drawn mask.
Light Dark
Before (#2355, 4-line draft, keyboard closed) <img src="https://github.com/BotHarness/DeepSeekBot/blob/616fe3fb4106721a0a595778ec0f52f85f80f191/.github/pr-screenshots/2355-native-composer/c17b-b-draft4-nokbd-light.png?raw=true" width="240"> <img src="https://github.com/BotHarness/DeepSeekBot/blob/616fe3fb4106721a0a595778ec0f52f85f80f191/.github/pr-screenshots/2355-native-composer/c17b-2-draft4-nokbd-dark.png?raw=true" width="240">
After, empty composer, scrolled (r4) <img src="https://github.com/BotHarness/DeepSeekBot/blob/616fe3fb4106721a0a595778ec0f52f85f80f191/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-scrolled-r4-light.png?raw=true" width="240"> <img src="https://github.com/BotHarness/DeepSeekBot/blob/616fe3fb4106721a0a595778ec0f52f85f80f191/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-scrolled-r4-dark.png?raw=true" width="240">
After, 4-line draft, keyboard closed (r3) <img src="https://github.com/BotHarness/DeepSeekBot/blob/616fe3fb4106721a0a595778ec0f52f85f80f191/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-reopened-scrolled-r3-light.png?raw=true" width="240"> <img src="https://github.com/BotHarness/DeepSeekBot/blob/616fe3fb4106721a0a595778ec0f52f85f80f191/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-reopened-scrolled-r3-dark.png?raw=true" width="240">
After, keyboard open (r3) <img src="https://github.com/BotHarness/DeepSeekBot/blob/616fe3fb4106721a0a595778ec0f52f85f80f191/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-r3-light.png?raw=true" width="240"> <img src="https://github.com/BotHarness/DeepSeekBot/blob/616fe3fb4106721a0a595778ec0f52f85f80f191/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-r3-dark.png?raw=true" width="240">

Top-edge crop (r4, light above dark): <img src="https://github.com/BotHarness/DeepSeekBot/blob/616fe3fb4106721a0a595778ec0f52f85f80f191/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-top-edge-crop-r4.png?raw=true" width="480">

🤖 Generated with Claude Code

https://claude.ai/code/session_01T3dAFSSAHytUi4igDx3iwg

…mposer

With the keyboard closed and a wrapped (or large-text) draft, UIKit's
bottom scroll edge effect leaves the strip below the composer capsule
unsoftened, even when the scroll edge interaction is re-added. Draw the
bottom fade over only that strip in that state; every other state keeps
the native edge effect alone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3dAFSSAHytUi4igDx3iwg
@DoodleBears DoodleBears self-assigned this Oct 20, 2026
@coderabbitai

coderabbitai Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 00412fd6-4d45-4517-a0b9-f1353b3db016
📥 Commits

Reviewing files that changed from the base of the PR and between a6bb3d6 and 3df8ee8.

⛔ Files ignored due to path filters: 23
  • .github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-rest-dark.png is excluded by !**/*.png
  • .github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-rest-light.png is excluded by !**/*.png
  • .github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-scrolled-r2-dark.png is excluded by !**/*.png
  • .github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-scrolled-r2-light.png is excluded by !**/*.png
  • .github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-scrolled-r3-dark.png is excluded by !**/*.png
  • .github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-scrolled-r3-light.png is excluded by !**/*.png
  • .github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-top-edge-crop-r2.png is excluded by !**/*.png
  • .github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-top-edge-crop-r3.png is excluded by !**/*.png
  • .github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-closed-scrolled-dark.png is excluded by !**/*.png
  • .github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-closed-scrolled-light.png is excluded by !**/*.png
  • .github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-closed-scrolled-r2-dark.png is excluded by !**/*.png
  • .github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-closed-scrolled-r2-light.png is excluded by !**/*.png
  • .github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-closed-scrolled-r3-dark.png is excluded by !**/*.png
  • .github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-closed-scrolled-r3-light.png is excluded by !**/*.png
  • .github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-dark.png is excluded by !**/*.png
  • .github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-light.png is excluded by !**/*.png
  • .github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-r2-dark.png is excluded by !**/*.png
  • .github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-r2-light.png is excluded by !**/*.png
  • .github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-r3-dark.png is excluded by !**/*.png
  • .github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-r3-light.png is excluded by !**/*.png
  • .github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-reopened-scrolled-r3-dark.png is excluded by !**/*.png
  • .github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-reopened-scrolled-r3-light.png is excluded by !**/*.png
  • .github/pr-screenshots/2398-composer-bottom-fade/2398-header-r3.png is excluded by !**/*.png
📒 Files selected for processing: 10
  • apps/mobile/DESIGN.md
  • apps/mobile/modules/scroll-edge-element/README.md
  • apps/mobile/modules/scroll-edge-element/expo-module.config.json
  • apps/mobile/modules/scroll-edge-element/ios/ScrollEdgeElement.podspec
  • apps/mobile/modules/scroll-edge-element/ios/ScrollEdgeElementModule.swift
  • apps/mobile/src/ui/chat/conversation-screen.tsx
  • apps/mobile/src/ui/chat/header-chrome.tsx
  • apps/mobile/src/ui/primitives/scroll-edge-element.ios.tsx
  • apps/mobile/src/ui/primitives/scroll-edge-element.tsx
  • changes/2355-native-composer.md
💤 Files with no reviewable changes: 6
  • apps/mobile/modules/scroll-edge-element/expo-module.config.json
  • apps/mobile/modules/scroll-edge-element/README.md
  • apps/mobile/modules/scroll-edge-element/ios/ScrollEdgeElement.podspec
  • apps/mobile/src/ui/primitives/scroll-edge-element.ios.tsx
  • apps/mobile/src/ui/primitives/scroll-edge-element.tsx
  • apps/mobile/modules/scroll-edge-element/ios/ScrollEdgeElementModule.swift

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 0 remain after this review.

📝 Walkthrough

The chat composer now uses a drawn bottom mask and fade across iOS and Android. The conversation screen retains the measured composer layout and top-edge behavior. The iOS ScrollEdgeElement module, wrappers, and module configuration are removed.

Changes: Composer edge fade

Layer / File(s) Summary
Render the composer fade
apps/mobile/src/ui/chat/conversation-screen.tsx, apps/mobile/src/ui/chat/header-chrome.tsx, apps/mobile/DESIGN.md, changes/2355-native-composer.md
The conversation screen renders a bottom EdgeFade using composer height, coverage, and background color. Covered fades use an SVG gradient. Glass headers enable the automatic top effect and hide the bottom effect. The design notes and release notes describe the drawn fade.
Remove ScrollEdgeElement
apps/mobile/src/ui/primitives/scroll-edge-element*, apps/mobile/modules/scroll-edge-element/*
The React Native primitives, iOS Expo module, module configuration, podspec, and module README are removed.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Suggested reviewers: claude

Merge Risk: ⚪ Minimal · up to 3df8e

The composer fade replacement is mergeable after normal checks; no actionable issue was identified.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 2 files. (2 skipped: 2 … Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Title check Passed The title clearly identifies the main change: replacing UIKit’s iOS chat bottom edge effect with a drawn mask.
Description check Passed The description includes the required purpose, references, risks, verification results, CI plan, change outline, and before/after UI evidence. It also identifies unverified Android, large and maximum …
Full details: Description check

Explanation

The description includes the required purpose, references, risks, verification results, CI plan, change outline, and before/after UI evidence. It also identifies unverified Android, large and maximum text sizes, Reduce Transparency, and interactive keyboard-dismissal cases. The native acceptance matrix is not complete because intermediate and maximum text-size coverage, Android coverage, and some requested screenshot evidence remain unverified.

Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 2 files. (2 skipped: 2 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

claude and others added 3 commits October 20, 2026 10:22
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3dAFSSAHytUi4igDx3iwg
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3dAFSSAHytUi4igDx3iwg
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@DoodleBears
DoodleBears marked this pull request as ready for review October 20, 2026 10:45
claude and others added 6 commits October 20, 2026 10:53
On automatic, UIKit picked the hard style under the resting composer: an
opaque band in a different colour from the list with a hairline on top.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3dAFSSAHytUi4igDx3iwg
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ge effect

UIKit picked the hard bottom edge style under the composer (an opaque
band in another colour with a hairline on top, at a height that ignored
a wrapped draft), and pinning the soft style did not change it. Hide the
native bottom edge, drop the scroll-edge-element module, and use the
same drawn mask as Android: nearly opaque under the composer, then the
light edgeFade curve above it, in the chat's background colour.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3dAFSSAHytUi4igDx3iwg
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… composer

After an interactive keyboard dismissal the percentage-sized SVG kept its
old, shorter height, leaving text under the composer uncovered.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3dAFSSAHytUi4igDx3iwg
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@DoodleBears DoodleBears changed the title fix(mobile): soften the home-indicator strip under an expanded iOS composer fix(mobile): draw the chat's bottom mask on iOS instead of UIKit's edge effect Oct 20, 2026
@DoodleBears
DoodleBears merged commit f3d58b9 into main Oct 20, 2026
13 checks passed
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.

2 participants