feat(mobile): start the splash purple and keep only the pixel dismiss - #1763
Merged
Merged
Conversation
janicduplessis
commented
Sep 28, 2026
janicduplessis
left a comment
Collaborator
Author
There was a problem hiding this comment.
Review: clear
I read issue #1739, the diff against origin/main, the new test and the PR description. In apps/mobile I ran pnpm test (29 suites, 222 tests), pnpm run typecheck and pnpm run lint. All three pass.
Correctness
- Unmount timer and timing. The overlay unmounts at 100 + (300 + 160) + 50 = 610 ms. The corner pixel's delay stays below
SPREAD_MS, because the distance to a pixel's centre is less than the distance to the grid's corner. So the last pixel finishes by about 545 ms, and the logo finishes by 220 ms. The whole dismiss takes about 700 ms, including the app settle, which runs outside the overlay. That is inside the 650-800 ms target. - Handoff. It still runs through
onDisplayon the logoImage, with the 300 ms fallback.playedis set when the splash is hidden, so the dismiss plays once per process, and a remount starts in thestartedstate. The first overlay frame is a full purple grid (colors.light.brand=#5521FF, the same colour as the config) with the 120 pt logo centred on top, which matches the native splash. - Grid coverage.
rowsis even androws * size >= height. The grid is centred vertically and is exactlywidthwide, and the +1 overlap covers the gaps left by fractional sizes. - Reduce Motion. It renders a solid purple fill and the logo.
dismissedstays false, so nothing scales. The overlay fades out over 250 ms and unmounts at 300 ms, and the app jumps to scale 1 withtype: 'none'. - Theme background. The backdrop uses Unistyles
theme.colors.backgroundbehind the 0.98-scaled app. It follows the in-app theme, and the parent tree still lays out withflex: 1. - Requirements kept.
useHardwareLayeris still on the overlay, and the boot-test unmount assertion is unchanged and passes.
Non-blocking notes
pixel-dismiss.tsx:51-52: the grid size comes fromuseWindowDimensions(), and the grid no longer has the extra margin rows the old flood grid had. Where the Android window height differs from the overlay's real size (3-button navigation, split screen, foldables), a strip at the top or bottom would show the app on the first frame instead of purple. The clips cover a 1080x2400 emulator. A cold start with 3-button navigation would confirm it. If a strip shows, size the grid from the container'sonLayoutinstead of the window.splash-overlay.tsx:14-16:SPLASH_COLORandLOGO_SIZEduplicate'#5521FF'andimageWidth: 120fromapp.config.ts, and only the doc comment links them. That is acceptable, but a shared constant would keep them from drifting.pixel-dismiss.tsx:13: "until the last pixel is gone" is an upper bound, because the outermost pixel starts a little beforeSPREAD_MS. Consider "at most" for readers who reuse the constant.
Readability and comment policy. PixelDismiss is small and self-contained. It takes only color, dismissed and delay, the grid layout is easy to follow, rippleDelay is pure and has a test for its symmetric output, and the constants are named. The remaining comments are public API doc comments or name an external constraint (the splash config values, sub-pixel seams, when the native animations start). I found no violations.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
The native splash was
icon.pngat 120 pt on white (dark#15121D), so the overlay from #1714 first had to flood the icon's purple square across the screen before the pixel ripple could start. The splash now starts brand purple, so the flood goes away and the pixel dismiss is the whole animation.Also fixes the edge strip: while the app settled from 0.98 to 1, a 1–2 px strip of the root view showed at the screen edges. With a purple native background it would turn purple, so the app now sits on its own theme background.
Native change, no OTA. The splash config and its image change, so the runtime fingerprint changes (iOS
1e83e2fc…->61b8fe9d…, Android695c5f9d…->f12ea3fd…). This ships in TestFlight build 16, together with the asset catalog change already on main (#1732). Build numbers come from EAS (appVersionSource: remote,autoIncrement), so nothing is bumped here.Solution
Native splash.
#5521FFin light and dark, with a newassets/images/splash-logo.png:icon.pngwith everything outside the white circle made transparent. It keeps the 1024 px canvas, soimageWidth: 120and the on-screen circle (66 pt) are unchanged and the handoff geometry is the same as before.Overlay. Its first frame is a full-screen grid of purple pixels with the logo on top, matching the native splash. Then, all as native
EaseViewanimations from one render:About 700 ms in total; the overlay unmounts at 610 ms.
The grid lives in its own component,
PixelDismiss(src/components/pixel-dismiss.tsx), with the stagger as a purerippleDelay(row, column, rows, columns, spreadMs)and a unit test.splash-overlay.tsxkeeps the lifecycle: native handoff ononDisplay, the 300 ms fallback, once per process, Reduce Motion,useHardwareLayer.setOptions({ duration: 0 })and the boot test's unmount assertion are unchanged.Reduce Motion renders a solid purple fill plus the logo instead of the grid and crossfades it out over 250 ms. With Android's "Remove animations" (all three scales 0) the animators jump to their end, so the splash cuts straight to the app.
Edge strip. The overlay wraps the scaled app in a view with
theme.colors.backgroundfrom Unistyles, so it follows the in-app theme setting.Native checks after prebuild:
SplashScreenLogoimageset, separate from the React Native asset catalog (RCTUseAssetCatalog), which only serves the overlay's copy of the logo.Test plan
Release builds (
stim ios --configuration Release,stim android --variant release), cold starts. The long launch waits caused by host load are trimmed from the iOS light and Android animations-off clips.ios-light.mp4
ios-dark.mp4
ios-reduce.mp4
android-light.mp4
android-dark.mp4
android-reduce.mp4
Android with all animation scales at 0:
android-animations-off.mp4
Reduce Motion:
defaults write com.apple.Accessibility ReduceMotionEnabledon iOS,settings put global transition_animation_scale 0on Android.Frame-by-frame checks on the extracted frames:
rgb(19,16,28)on iOS), with no purple.Fixes #1739