You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Part of the Sep-2026 designer's-eye QA of the 138 "Artistant iOS Light" screens (theme T13, category slow; epic #161). Full report with evidence and per-screen coverage: docs/DESIGN_QA_2026-09.md; the per-section appendices under docs/design-qa/ carry the code excerpts.
Why it matters.RevealOnAppear runs a second 300 ms animation on twelve screens after the 300 ms push; every skeleton block recomposes every frame; Done and Discover read animated values in composition; toasts ignore reduce-motion; tab roots reflow mid-slide; no cover photo ever fades in.
Fix direction. Reveal only on loading→loaded after the transition ends; graphicsLayer {} for animated alpha/scale/rotation; one hoisted skeleton transition; a shared ImageLoader with crossfade; motionTween everywhere.
Findings (14: P1 ×2, P2 ×3, P3 ×9)
F-CC-15 (P2) — Pushed screens animate twice: a 300 ms reveal stacked on the 300 ms push
Fix: Reveal only when the screen transitions from Loading to Loaded AFTER the nav transition has finished (gate on !transition.isRunning or skip when data was available at first composition); never on tab roots.
F-CC-04 (P2) — Every skeleton block recomposes on every frame while loading
Screens: 59, 54 and every screen that draws SkeletonBlock/SkeletonPage (48 call sites)
Fix: drop RevealOnAppear on both screens (let the nav transition be the reveal), and move the chevron rotation into graphicsLayer { rotationZ = chevron }.
F-GS-22 (P3) — Done's pop-in reads an animated value in composition through Modifier.scale
Screens: 30
Where: feature/signup/DoneScreen.kt:73-78, :98
Fix: .graphicsLayer { scaleX = scale; scaleY = scale } (read the state inside the lambda).
F-DS-22 (P3) — Work in composition, and an animated rotation read in composition
Screens: 14, 15
Where: feature/search/SearchScreen.kt:377 (searchSuggestions(...) — filters facets and interleaves on every recomposition of BrowseSurface, not only when the query changes), :419 (eventTypes.chunked(2) allocated per recomposition); SearchFilterSheet.kt (chevron rotation)
Fix: set unreadOnArrival after log.markRead(unread) completes (or derive hasUnread from rows not in the arrival snapshot) so the control never appears for the marking pass.
F-SH-15 (P3) — The FAQ accordion animates on Compose defaults while its own chevron uses the tokens
Screens: 63
Where: feature/system/HelpCentreScreen.kt:233-240 vs :202-206
Fix: pass enter/exit built from motionTween(AppTheme.motion.contentReveal), matching the chevron's clock.
F-SH-19 (P3) — The toast's enter/exit ignores reduce-motion
Screens: 77
Where: designsystem/component/Toast.kt:105-112
Fix: swap both tween(...) calls for motionTween(AppTheme.motion.tabSwitch).
F-SH-21 (P3) — Tab-root content reflows the instant a push starts, mid-slide
Fix: keep the bar composed and animate it out, or give TabPane a remembered bottom inset that only collapses once the transition settles. (Not device-verified — mechanism read from code.)
F-AS-04 (P1) — The clash warning scrolls away while Accept stays pinned
Screens: 35
Where: GigRequestDetailScreen.kt:351-353 inside the scroll column :194-227; dock at :229-265
Fix: hoist the clash banner out of the scroll column and render it directly above the dock (inside .dockSurface()), so it is pinned with the button it is warning about.
F-BN-18 (P3) — Copy-to-clipboard acknowledges itself for a different length of time than its twin
Fix: one shared duration constant and one casing convention (sentence case, matching the toast copy rule).
Acceptance
Every finding above is closed on the cited lines (or refuted in a comment with the reason).
./gradlew :app:assembleDebug, :app:testDebugUnitTest and :app:lintDevDebug green.
The screens listed above walked on the artistant AVD with the debug harness (RELEASE.md §10) and the fix confirmed visually — this audit was code-grounded, not device-walked.
Part of the Sep-2026 designer's-eye QA of the 138 "Artistant iOS Light" screens (theme T13, category slow; epic #161). Full report with evidence and per-screen coverage: docs/DESIGN_QA_2026-09.md; the per-section appendices under
docs/design-qa/carry the code excerpts.Why it matters.
RevealOnAppearruns a second 300 ms animation on twelve screens after the 300 ms push; every skeleton block recomposes every frame; Done and Discover read animated values in composition; toasts ignore reduce-motion; tab roots reflow mid-slide; no cover photo ever fades in.Fix direction. Reveal only on loading→loaded after the transition ends;
graphicsLayer {}for animated alpha/scale/rotation; one hoisted skeleton transition; a sharedImageLoaderwith crossfade;motionTweeneverywhere.Findings (14: P1 ×2, P2 ×3, P3 ×9)
!transition.isRunningor skip when data was available at first composition); never on tab roots.SkeletonBlock/SkeletonPage(48 call sites)SkeletonPage) and apply it viagraphicsLayer { alpha = pulse.value }so only the draw phase runs per frame.AsyncImage(sites in feature/discover/DiscoverScreen.kt, feature/search/SearchScreen.kt, feature/artist/ArtistProfileScreen.kt, feature/artist/ArtistProfileMedia.kt, feature/bookings/BookingsScreen.kt (×2), feature/booking/BookingDetailScreen.kt, feature/booking/BookingChrome.kt, feature/epk/EpkHub.kt (×2), feature/epk/EpkPanes.kt, feature/profile/ArtistListScreen.kt, feature/wizard/WizardMediaSteps.kt, feature/wizard/WizardPublishSteps.kt — none passes acrossfade, and no sharedImageLoadersets itImageLoader(Hilt-provided) withcrossfade(AppTheme.motion.contentReveal), or a thinAppImagewrapper that every site uses.RevealOnAppearon both screens (let the nav transition be the reveal), and move the chevron rotation intographicsLayer { rotationZ = chevron }.Modifier.scale.graphicsLayer { scaleX = scale; scaleY = scale }(read the state inside the lambda).remember(state.query, state.facets, state.results) { … };remember { eventTypes.chunked(2) };Modifier.graphicsLayer { rotationZ = rotation }.RevealOnAppearon 05/06 (the nav transition already animates), and on 07 reserve the card with aSkeletonuntil the read resolves.unreadOnArrivalafterlog.markRead(unread)completes (or derivehasUnreadfrom rows not in the arrival snapshot) so the control never appears for the marking pass.enter/exitbuilt frommotionTween(AppTheme.motion.contentReveal), matching the chevron's clock.tween(...)calls formotionTween(AppTheme.motion.tabSwitch).TabPanea remembered bottom inset that only collapses once the transition settles. (Not device-verified — mechanism read from code.).dockSurface()), so it is pinned with the button it is warning about.Acceptance
./gradlew :app:assembleDebug,:app:testDebugUnitTestand:app:lintDevDebuggreen.artistantAVD with the debug harness (RELEASE.md §10) and the fix confirmed visually — this audit was code-grounded, not device-walked.