ensure web scroll

This commit is contained in:
vineyardbovines
2026-06-04 11:23:14 -04:00
parent e38628e0d7
commit 68fe67e0f7
2 changed files with 145 additions and 6 deletions
+127
View File
@@ -0,0 +1,127 @@
# Bluesky × Software Mansion — Expo 55 / New Architecture Migration
This document covers the outcomes we care about, how we'd like to work together, and how we'll measure success.
This brief gives them the goals, constraints, and targets needed to determine all of the above. Much of this is open to discuss
or change; the goal of this brief is to give a baseline.
---
## 1. Goals of the engagement
Four goals anchor this work. Everything else serves these:
1. **Reduce the technical burden** of maintenance and ongoing improvement.
2. **Reduce reliance on patches.**
3. **Remove or replace incompatible libraries.**
4. **Remove or rework features** where doing so serves goals 13.
From those, the outcomes that define success:
- **Land Expo 55 / New Arch / Reanimated 4 across iOS, Android, and web** with **no
performance regressions** on our priority surfaces — and ideally measurable gains.
- **Establish objective, repeatable performance measurement.** We do not have a process
for measuring performance objectively today. We want to come out of this with reasonable baselines and existing deltas.
This will help us with establishing perf practices within CI down the road.
During the engagement, we'd propose two collaborative pieces:
1. When a surface is slow or a component is hard to migrate, we want SWM to identify *why* and bring it back to us with options, unless there is a known/hardened workaround with low stakes.
2. **A QA-driven discovery pass is welcome.** One model on the table: enable New
Architecture, run a thorough QA pass across the app, enumerate the issues that emerge, then
jointly prioritize them into fix-now / we-fix / backlog. We're open to SWM's preferred
variant of this.
---
## 2. Performance: targets & success criteria
We don't have an objective performance process today; establishing one is a goal of ours.
The targets below are our starting success criteria, sourced from platform guidance
(Apple talks, Android vitals), web vitals, arithmetic on frame budgets, and team practice.
**These are a starting point.** We'd welcome SWM validating and refining them against real
baselines on real devices, especially any that lean on judgment calls.
### Hard-sourced targets
| Metric | Target | Borderline | Broken |
| ----------------------------- | -------- | ---------- | --------- |
| Hitch time ratio (iOS) | < 5 ms/s | 510 ms/s | > 10 ms/s |
| INP interaction latency (web) | < 200 ms | 200500 ms | > 500 ms |
**Frame budgets:** 60 Hz ≈ 16.6 ms · 90 Hz ≈ 11 ms · 120 Hz ≈ 8.3 ms.
**Android frames:**
- *Slow frames* = 16 ms700 ms. Excessive = > 50% of frames missing the 16 ms deadline per
session.
- *Frozen frames* = > 700 ms. Excessive = > 0.1% of frames over 700 ms per session.
### Engineering-best-practice targets
| Metric | Target | Borderline | Notes |
| ------------------------------ | ------ | ---------- | ------------------------------------------------------------------------------------------------------- |
| Feed scroll UI FPS (60 Hz) | ≥ 58 | 5057 | Smooth within ~1 frame of refresh rate is the consensus bar. |
| Feed scroll UI FPS p10 (60 Hz) | ≥ 52 | 4552 | p10 catches occasional jank the median hides. Generalized threshold. |
| Feed scroll UI FPS (120 Hz) | > 110 | 90109 | ProMotion can legitimately drop to ~80 under thermal load; expect false positives. |
| JS FPS during animations | ≥ 50 | 4050 | Worklets run on the UI thread, so this is a JS-starvation / tap-response indicator, not scroll quality. |
| Cold start TTI | < 2 s | 25 s | |
---
## 3. Priority surfaces
Our priority surfaces, ranked by importance:
1. **Feed scroll**: the central performance concern. Reanimated wraps effectively every
list, and this is where the jank lives.
2. **Lightbox**
3. **Context menu**: measured as long-press-to-visible time.
4. **Dialog / bottom sheet**
5. **Pager**
Baseline surfaces we specifically care about: **feed scrolling, lightbox, and composer.**
**Specific area of interest is Reanimated in lists, and the pager.** We suspect the pager
forces Reanimated into the scroll path for our lists. We'd value SWM's read on whether
removing or reworking the pager would let lists shed Reanimated, and what that buys us.
We're open to a significant rework here if the performance case supports it.
---
## 4. Device matrix & measurement methodology
Device matrix (three tiers) to cover our usage bases:
- **High-end iOS** (e.g., a recent ProMotion iPhone)
- **Mid-range Android**
- **Bottom-tier Android** — Go-class / ≤ 3 GB RAM
**Per-device baseline captures:**
- Feed scroll FPS during a 30-second flick
- Context-menu long-press-to-visible time
- Dialog-open frames
- Cold-start TTI
**Existing tooling to build on.** The repo already has `flashlight` + `maestro` perf
scripts (`perf:test`, `perf:test:measure`, `perf:measure`) as a starting point.
We're open to ideas on what to add/change/remove for this.
**Understanding the perf delta.** We'd like to baseline across all three devices, then
migrate a perf-critical surface (likely feed scroll) and remeasure — so we can see a clean
before/after delta and understand the real performance gaps before sizing the rest of the
work. Also open to a better method/surface of determining the deltas.
---
## 5. Open questions for SWM
Not needed immediately, but comes to mind while thinking through the full engagement:
- For **bottom sheets and video** — what are we doing that's already been solved upstream?
- What **custom modules and patches** can be retired because Expo or the ecosystem now
covers them?
- Given how heavily we lean on Reanimated in lists, **where's the biggest perf win** — and
is removing/reworking the pager part of it?
- What **performance targets** would *you* set, given the baselines?
- Which **surface should we pilot first** for the cleanest perf delta?
+18 -6
View File
@@ -1,5 +1,5 @@
import {useCallback, useEffect, useRef, useState} from 'react'
import {Pressable, StyleSheet, View} from 'react-native'
import {Pressable, ScrollView, StyleSheet, View} from 'react-native'
import {Image} from 'expo-image'
import {Trans, useLingui} from '@lingui/react/macro'
import {FocusGuards, FocusScope} from 'radix-ui/internal'
@@ -226,13 +226,17 @@ function LightboxGallery({
)}
</View>
{img.alt ? (
<View
<ScrollView
// Cap the overlay height so long alt text scrolls within the overlay
// instead of growing past the top of the screen and pushing the image
// out of view. Only scrollable once expanded.
style={[
a.px_4xl,
a.py_2xl,
styles.altScroll,
{backgroundColor: 'rgba(0, 0, 0, 0.45)'},
delayedFadeInAnim,
]}>
]}
scrollEnabled={isAltExpanded}
contentContainerStyle={[a.px_4xl, a.py_2xl]}>
<Pressable
accessibilityLabel={l`Expand alt text`}
accessibilityHint={l`If alt text is long, toggles alt text expanded state`}
@@ -246,7 +250,7 @@ function LightboxGallery({
{img.alt}
</Text>
</Pressable>
</View>
</ScrollView>
) : null}
{imgs.length > 1 && (
<div aria-live="polite" aria-atomic="true" style={a.sr_only}>
@@ -445,6 +449,14 @@ const styles = StyleSheet.create({
padding: 16,
boxSizing: 'border-box',
},
altScroll: {
// Size to content like the View it replaced, rather than filling the
// column via ScrollView's default flexGrow.
flexGrow: 0,
flexShrink: 0,
// @ts-ignore web-only -sfn
maxHeight: '50vh',
},
menuBtn: {
top: 20,
left: 20,