Wonder Round Inc.
2025.04 ~ Present
Day 390 on the job1.
Introduced a staged-mount pattern to optimize entry performance on key screens
- Problem
On heavy screens that accumulate many
hooksand API calls (lists / map / detail), every widget mounts at the same time on entry, which makes the navigation animation stutter and delays the appearance of theskeleton.SolutionSplit the screen into entry point and body in two stages — show the
skeletonimmediately right after the navigation transition, and detect the end of the transition withsetImmediateso the body only mounts after the animation finishes. This minimizes the cost of the first paint and shortens perceived entry time. Pre-warmed data shown at the top of the screen with a separate
prefetchhook so that it is already cache-hit by the time the body mounts.Introduced a shared hook that handles scroll event handlers in a JS-thread non-blocking way, so other interactions are not delayed during scrolling.
2.
Built push notification and deep link entry flows
- Problem
When a push is tapped from a cold start, the navigation stack is not ready yet, so routing is sometimes dropped.
SolutionStored the
pending payloadin a globalstoreand consumed the queue once navigation isready, applying an async-queue pattern for routing. - Problem
When the deep link target is a webview screen and the app is launched from a cold start, the navigation stack ends up containing only that single webview, so pressing back terminates the app instead of returning to the previous in-app screens.
SolutionInstead of a plain
navigatecall, I composed thenavigation statedirectly toresetit into a structure whereBottomTabsis the base route and aStack(related underlay screen + webview) is stacked on top. This guarantees a natural back flow: webview → related screen →BottomTabs. Added a normalization layer for the notification list response to match the updated push-sending API schema on the backend.
Implemented a deep link handler that parses the push payload and branches across cold-start / background / foreground entry cases.
3.
Introduced and standardized an optimistic-update pattern with
Tanstack Query- Problem
For toggle-like
mutationssuch as bookmark / like, the UI was not updated until the server response, so users repeatedly tapped the same button across multiple screens.SolutionApplied the standard
cancel → snapshot → cache update → rollbackflow to reflect the change in the UI immediately. On failure, theonErrorstep rolls back from thesnapshotand shows atoastso the flow stays consistent. Patched related screen caches directly with
setQueryDatato remove unnecessary network round-trips caused byinvalidate, and to keep state consistent across screens.
4.
Implemented a Naver Map–based store discovery screen and optimized search/render
- Problem
Code-driven camera moves (region filter selection, initial camera setup) were treated identically to user gestures, which caused the result-list API to be called multiple times.
SolutionTracked code-driven camera moves with a separate ref to distinguish them from real user gestures, and toggled a re-search-enabled flag so re-search is only triggered on meaningful user actions.
Built a util that derives the southwest/northeast bounds of the visible area from the map camera center and lat/lng deltas, and passes them as the search range to the result-list API. This way, only results that match exactly what the user is looking at on the map are fetched.
5.
Implemented a custom pull-to-refresh inside a
BottomSheetlayout where a banner is always visible- Problem
The main screen has a composite layout where a
BottomSheetis partially visible above a top banner, with theBottomSheetsnapPointfixed so the banner is always visible. In this setup, binding a standardRefreshControlto the list inside theBottomSheetdoes not detect apullgesture in the banner area, so pull-to-refresh does not work on either iOS or Android.SolutionWrapped the entire screen with a
GestureDetector, detected thepullgesture directly viaPan Gesture, and synced the indicator position / rotation /opacityto the pull distance withReanimated worklet. Triggered the refresh once the threshold was crossed and held the indicator until the response completed — designing the state flow myself. Resolved conflicts between scroll gestures inside the
BottomSheetand the screen-widePan Gestureby adjusting gesture priorities withwaitFor, so pull in the banner area and scroll inside theBottomSheetwere cleanly separated.Branched the indicator look / position per platform to follow iOS and Android guidelines.
6.
Android-specific platform troubleshooting
- Problem
On high-refresh-rate / high-density Android devices, fast
flingscrolls on virtualized lists briefly showed empty rows, making the scroll feel choppy.SolutionAdded an Android-specific branch in the shared
LegendListwrapper. IncreaseddrawDistance(which defines the pre-render area) from 250 to 400 to widen the pre-rendered range, and splitdecelerationRateintofastfor iOS /normalfor Android to compensate for platform inertia differences. ExposedremoveClippedSubviews(which temporarily detaches off-screen views) as an opt-in to reduce Androidoverdrawcost. - Problem
The image picker library cut off its action button area on Android because it was hidden behind the system bottom navigation bar.
SolutionPatched the library itself to respect
SafeArea inset, and managed it through apnpm patchworkflow so the patch is preserved across dependency upgrades. - Problem
A custom horizontal swiper built with
Reanimated Spring+Pan Gestureovershot too much on Android, missed fast swipes, and felt sluggish compared to iOS for the same gesture.SolutionBranched Spring settings and gesture thresholds per platform. For Android, increased
dampingto 25 (iOS 22) to suppress overshoot, narrowedPan GestureactiveOffsetXfrom ±10 to ±8 to improve swipe sensitivity, and lowered the swipe triggervelocity thresholdto 150 to compensate for Android reporting lowervelocityvalues — bringing the feel close to iOS.
7.
Built a Claude Code–based development productivity infrastructure
With the prior RN app developer leaving and domain knowledge handover broken, I had to maintain the app and ship new features alone with limited RN experience. To cut the learning cost and keep domain context always accessible, I designed and built a Claude Code–based assist infrastructure myself.
Split project rules per domain (UI ecosystem / performance / animation / server state / navigation / global state / i18n / new feature flow), so only the rules relevant to the current task are loaded based on keywords.
Composed a
planner → generator → evaluator3-stage loop sub-agent to automate parts of the implementation work.Built a separate sub-agent dedicated to Android performance optimization, so it can automatically diagnose Android-specific issues — virtualized list jank / gesture conflicts / render load / memory usage — and propose code changes.
Hardened the dev environment with RN debugging tool hook setup.
