fix(mobile): loading and skeleton polish - #4772
Conversation
Replace the per-instance opacity pulse with a soft horizontal gradient highlight driven by one refcounted module-level Reanimated clock shared across all Skeleton instances. Reduced motion keeps the static muted block; the component API is unchanged.
… load The transient loading skeleton in the list header collapsed in the same update that prepended an older page; FlashList's content-position maintenance compensates prepended items but not the header height change, producing a visible jump at the page boundary. Suppress the placeholder at the render-model layer (loading maps to the omitted banner when one is visible, else hidden) and start the older-page fetch two viewports before the boundary so the prepend lands before the user reaches it. Mirror the fix in the kilo-chat message list and remove the now-unused isFetchingOlder prop end to end.
Match category and master-row placeholders to the settled row geometry, and gate the master Switch and Enable-notifications CTA on a new pure deriveGateSettled predicate so a fully enabled user never sees a transient Enable card or a Switch with a transient wrong value while the push-token queries settle.
Layer an opacity breathe (0.4->1.0, 850ms half-cycle, 1700ms full cycle; pre-branch pulse was 1000/2000ms) on the shimmer sweep, and keep the reduced-motion path fully static.
|
(bot) Standin review of head Standin reviewer checks (all green on Findings (all Low; no critical/high/medium)1. Low — Notifications master row's leading content is not gated, so the "wrong value" flash survives in the row body 2. Low — Pagination load is now completely silent to screen readers 3. Low — 4. Low — Unrelated lockfile churn in 5. Low — PR description overstates the merged skeleton behavior Scope completed by the standin reviewer: full diff and commit boundaries; surrounding reads of every touched file; auth-gating reachability of the notifications screen; Reanimated 4.2.1 Residual testing risks it noted: shimmer-clock behavior under fast-refresh and FlashList cell recycling is reasoned, not unit-tested (no pre-existing Triage and repairs are in progress; outcomes will be recorded here per finding. |
|
(bot) Standin-review loop complete on the new head
A fresh standin reviewer then re-reviewed the full diff at Because findings 1–2 affect behavior, a targeted on-device re-verification of the notifications loading gate (and a pagination smoke pass) is queued behind the shared device-slot semaphore; results will be posted here. CI is running on |
|
(bot) On-device re-verification of the repair head
Zero product or test-environment failures. All CI checks are green on |
Summary
Mobile loading polish, three items:
Skeletonshimmer — a soft horizontal gradient sweep (expo-linear-gradient) is layered over the existing opacity pulse. The sweep is driven by one refcounted module-level Reanimated clock shared by every instance, so the sweep costs a single UI-thread animation no matter how many skeletons are on screen (each instance also keeps its per-instance pulse, now at a faster 1700 ms cadence). Reduce Motion renders the same static muted block as before. API unchanged:Skeleton({ className }).maintainVisibleContentPositioncompensates prepended items but not the header height change, which produced a measured ~43pt upward jump of on-screen content at every page boundary (60fps frame analysis on the baseline). The placeholder is now suppressed at the render-model layer (an already-visible omitted-items banner stays stable through the load), and the fetch starts two viewports before the boundary so the prepend usually lands before the user reaches it. The identical pattern in the kilo-chat message list is fixed the same way, with the deadisFetchingOlderprop removed.deriveGateSettledpredicate (permission settled AND, when granted, both push-token queries fetched-or-errored). A fully enabled user no longer sees a transient Enable card or a Switch flashing a wrong value while the token queries settle.Test plan
Automated: full mobile suite green (226 files / 1881 tests), including the new
deriveGateSettledtruth table and updated pagination-header render-model tests.oxfmt,tsgo,oxlint,knip,git diff --checkclean;expo-doctorintroduces no new findings (the out-of-date-packages advisory pre-exists on main).On-device (iPhone 17 Pro simulator, dev client built from this branch):
E2E notes: the loading window on the local stack is ~1 frame, so shimmer/skeleton captures used the deterministic suspend protocol (backend process briefly STOPped while the app was already bootstrapped, then resumed; screen settled to real data afterwards). A1.3's scroll-drag instrument is structurally inapplicable — no loading screen in the app is scrollable (every loading branch renders a static placeholder view that fits the viewport; verified across all Skeleton call sites); smoothness is covered by the no-freeze frame analysis, and interactivity by gestures/navigation processing immediately during the shimmer window plus the single-shared-sweep-clock design.