Skip to content

[MOO-2404] - fix intro screen - #573

Open
YogendraShelke wants to merge 1 commit into
mx/11.12.xfrom
moo/MOO-2404-intro-screen
Open

[MOO-2404] - fix intro screen#573
YogendraShelke wants to merge 1 commit into
mx/11.12.xfrom
moo/MOO-2404-intro-screen

Conversation

@YogendraShelke

@YogendraShelke YogendraShelke commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Problem

Three defects in how the IntroScreen decided which slide was showing:

  • The slide on screen could stop following the buttons and slide indicators, so navigating showed the controls for one slide while the previous slide was still displayed. The Android e2e flow caught this as a missing FINISH button.
  • Swiping slowly moved the slide without updating the indicators, the buttons or the active slide attribute.
  • Where the active slide attribute already pointed at the slide the widget opened on, the first swipe was ignored and the slide swiped to sprang back.

Root cause

The widget was working out for itself which gesture phase ended a swipe. It rounded contentOffset.x / width in onMomentumScrollEnd and gated that behind a hand-kept isInitializing latch. Both halves were wrong:

  • A drag lifted with no velocity produces no momentum phase at all, so a slow swipe was never reported. This is the slow-swipe defect.
  • The latch existed to tell the list's own opening scroll apart from a real swipe, and it was cleared only inside the sync effect's slide !== activeIndex branch. Start with the attribute agreeing with the slide the widget opens on and that branch never runs, so the latch is never cleared, so the first swipe is swallowed — and the sync effect then scrolls back, reverting the slide the user swiped to.

Separately, @shopify/flash-list 2.x enables maintainVisibleContentPosition by default. It anchors the list to whichever item was first visible and, whenever a re-render moves that item, scrolls back by the difference. Navigating re-renders the slides, so that correction silently undid the scrollToOffset in goToSlide — state advanced, pixels did not. Confirmed on-device: after goToSlide(2) requested offset 822.86 (valid — content width 1234), onScroll reported x=0. Not a clamp, which would have given 411.

Fix

Which slide is showing is asked of the list through onViewableItemsChanged instead of inferred from offsets and gesture phases. flash-list computes viewability inside its own scroll handling, so a drag lifted with no velocity is reported like any other arrival, and waitForInteraction: true supplies the "not the initial scroll" distinction the widget was keeping by hand — flash-list already withholds reports until the first real interaction (RecyclerView.tsx only calls recordInteraction() once isInitialScrollComplete). itemVisiblePercentThreshold: 60 is unambiguous because slides are exactly one window wide; minimumViewTime: 250 keeps positions merely passed through from counting as arrivals.

Four gesture handlers, a manual dragging flag and a settle-timeout fallback are replaced by one handler.

Also in the same sync path:

  • maintainVisibleContentPosition disabled — paged slides are all one window wide and every position here is asked for explicitly, so there is nothing to preserve.
  • The list mounted before a width was known, laying every slide out at width zero and stacking them all on the first page. It now mounts once onLayout reports a width, with a measuring placeholder before that.
  • initialScrollIndex was passed the live active index, so flash-list's post-mount re-apply raced our own scrollToOffset. The slide the list opens on is frozen once mounted.
  • A write to the attribute round-trips through the runtime, so for a render or two it still reads the slide just left. Syncing from it scrolled straight back and overwrote the value just written.

Also in this PR

Two infrastructure fixes that are independent of the widget but were found while chasing it. Both are broken on mx/11.12.x today.

CI was testing the wrong code. upload-artifact strips the least-common ancestor of the paths it is given. Adding resources-manifest.txt at the repo root moved that ancestor from packages/ to the root, every path gained a packages/ prefix, and the overlay's resources/pluggableWidgets/**/dist/*/*.mpk glob silently stopped matching — no built mpk was installed, and the e2e suite tested the baseline project's widgets instead of the commit under test. The mpk and both jsActions overlays now locate their sources with find rather than a fixed-depth glob, and installing nothing while widgets were built is a hard failure instead of a silent pass. (The nanoflow-actions guard was also checking the mobile-resources directory, so it moved either both or neither.)

Maestro e2e. Swipes use explicit start/end points inside the slide: an edge-anchored swipe loses travel to the system back gesture, and the backward one fell short of halfway and snapped back to where it started — a flake independent of the widget fix. NEXT is what becomes FINISH on the last slide, so the flow now waits for the slide before reaching for the button.

Maestro diagnostics. A failed attempt's video and view hierarchy are moved aside before the retry, which reuses both paths — a flake that passed on retry previously left nothing to diagnose from.

🤖 Generated with Claude Code

@YogendraShelke
YogendraShelke requested a review from a team as a code owner July 29, 2026 08:53
@YogendraShelke YogendraShelke changed the title Moo/moo 2404 intro screen [MOO-2404] intro screen Jul 29, 2026
@YogendraShelke
YogendraShelke force-pushed the moo/MOO-2404-intro-screen branch from 42c7164 to 44d06bc Compare July 29, 2026 10:23
@YogendraShelke YogendraShelke changed the title [MOO-2404] intro screen [MOO-2404] - fix intro screen Jul 29, 2026
@YogendraShelke
YogendraShelke force-pushed the moo/MOO-2404-intro-screen branch from 44d06bc to 09dbee9 Compare July 30, 2026 03:35
@YogendraShelke
YogendraShelke force-pushed the moo/MOO-2404-intro-screen branch from 09dbee9 to 3d9b426 Compare July 30, 2026 07:30
…e (MOO-2404)

The IntroScreen did not open on the slide named by the active slide attribute,
and swiping between slides did not work reliably on slower Android devices.
Three separate causes, all of them races that only lose on a slow device:

Which slide is showing is now asked of the list rather than worked out from
scroll offsets and gesture phases. A drag lifted with no velocity produces no
momentum phase, so a slow swipe moved the slide while the indicators, buttons
and attribute stayed behind. flash-list reports viewability from its own scroll
handling, so such a slide is reported like any other. flash-list's
waitForInteraction cannot gate those reports: it is set only from onScroll and
only once a 100ms timer has fired, so a swipe made before that dropped every
report for the whole gesture. A touch is the one signal the list cannot produce
by scrolling itself, and it needs no timer.

The list is held back until the attribute has a value to open on. It arrives
Loading with no value on a fresh page and the real value follows a render or
more later, but flash-list applies initialScrollIndex at most once, from a
commit effect behind a 100ms timer, with no retry. Mounting during that window
opened the widget on slide 1 and left it there while the indicators and buttons
followed the value that landed moments later. The wait ends once the list is up:
writing to the attribute returns it to Loading with no value, and waiting there
would remount the list and spring the slide back on every swipe.

maintainVisibleContentPosition is turned off. It is on by default in
flash-list 2.x and anchors the list to the first visible item, scrolling back by
however far a re-render moved it — which silently undid the scrollToOffset that
navigation depends on, so buttons and indicators advanced while the slide on
screen did not.

The Maestro flow waits for the app to go idle before each swipe. A slide change
writes the attribute and waits for the runtime to hand the value back, so the JS
thread is still busy when the assertions after it pass. A swipe starting there
loses its opening touch-move events: measured on a CI failure, the finger moved
75% of the width while the list moved 45%, leaving it short of the halfway point
when the touch lifted, so it snapped back to the slide it started on with every
counter correctly still on that slide. Gesture duration is left at the default on
purpose — a paging list commits on position or lift-off velocity, so a slower
drag travels no further and arrives with less velocity to carry it over.

Verified on a CPU-throttled emulator that reproduces the failures reliably, and
on CI, where Android and iOS both pass. The swipe flake above was seen once
across roughly a dozen Android runs and cleared on retry, so the settle is not
yet proven by repetition.
@YogendraShelke
YogendraShelke force-pushed the moo/MOO-2404-intro-screen branch from 3d9b426 to f505f5e Compare July 30, 2026 08:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants