Less work between intent and response.
A release-mode, whole-app performance pass across startup, bundle composition, image memory, SQLite, Firebase sync, lists, replay, animations, and lifecycle cleanup. Changes were kept only when they solved a measured user-facing problem.
Release-mode proof
The baseline and final exports use the same iOS production/Hermes/Atlas path. Payload and graph reductions are directly comparable; cold export time is reported honestly as build noise, not user startup speed.
Startup dependency closures
Narrow public subpaths stop unrelated domain data from evaluating before first content.
| Closure | Baseline | Final | Bytes |
|---|---|---|---|
| Core root | 1,291 modules · 11.95 MB | Absent | Eliminated |
| Firebase client | 1,451 · 12.74 MB | 1,136 · 5.17 MB | −59.4% |
| Local store | 1,333 · 12.24 MB | 998 · 4.48 MB | −63.4% |
| Sync provider | 1,784 · 14.05 MB | 1,484 · 6.60 MB | −53.0% |
| App providers | 2,226 · 16.93 MB | 1,926 · 9.48 MB | −44.0% |
| Root layout | 2,718 · 18.56 MB | 2,639 · 16.67 MB | −10.1% |
History query scaling
Bulk summaries replace per-match query fan-out.
| Matches | Before | Final |
|---|---|---|
| 10 | 75 queries | 10 queries |
| 100 | 705 queries | 10 queries |
| 500 | 3,505 queries | 11 queries |
Where users feel it
The work spans visible screens and their shared infrastructure. Each group links to its exact candidate records, including work deliberately not shipped.
App shell & startup
Less work before the first usable screen: deferred tooling and analytics, narrow package boundaries, lean icon/font loading, and deterministic database initialization.
Home, matches & history
The scoring path now persists the minimum state, keeps live summaries scoped to the active match, and reads history in bounded bulk operations.
Players, friends & avatars
Player-heavy screens avoid duplicate listeners and N+1 lookups; avatar upload, resolution, caching, decoding, and recycling have bounded memory behavior.
Career & tournaments
Career state is local-first with shared revalidation, long rankings are virtualized, and tournament brackets render only the relevant work first.
DartBot TV, voice & achievements
High-frequency replay progress moved off React state, hidden voice diagnostics stop subscribing, and celebration motion uses native execution with cleanup.
Shared storage, sync & lifecycle
Scoped invalidation, SQL-only dirty selection, coalesced sync scheduling, indexes, and metadata-only listener fast paths reduce repeated work as accounts grow.
Quality held, weight removed
Static comparisons demonstrate asset fidelity; runtime captures demonstrate that high-risk flows still render and respond after the internal changes. Invisible performance work is paired with numeric and automated evidence elsewhere in the report.
Built, run, interrupted
Validation combines deterministic tests with release-mode export analysis and native simulator/emulator exercises. Environmental limitations are called out rather than converted into passes.
Complete repository validation
Zero lint errors, all strict workspace and backend typechecks passed, 768/768 DartBot bands passed, and every executed core/mobile test passed. The existing no-emulator guard skipped 11 Firestore rules cases.
iOS DevRelease build and launch
Built, installed, and launched on an iPhone 16e simulator. The launch-window crash scan found no fatal, crash, or exception record.
Android native build
The arm64 development APK built in 18s and installed successfully; SHA-256 a0b5c0a93c05…. Temporary native/Firebase prebuild output was removed afterward.
Rapid repeated interactions
Repeated 20/19/18 scoring and player handoffs remained serialized, interactive, and free of invalid intermediate controls or application crashes.
Accessibility and appearance states
Dark appearance, iOS Reduce Motion, Android zero animation scale, and match launch with camera and microphone permissions revoked all remained understandable and usable.
Major seeded navigation flows
Focused home→players→settings, completed-summary, match-ready, DartBot TV, and onboarding checks passed. The full Maestro batch was limited by clearState forgetting Metro and the floating Expo Tools control intercepting top-right actions.
Frame timing captured, no false claim
Android gfxinfo was collected, but the development launcher, emulator GPU, UI automation, and simultaneous screen recording contaminated the sample. The data is retained in the evidence register; no release FPS improvement is claimed.
Risky optimizations stayed out
Experimental Expo tree shaking reduced raw JavaScript 5.05% but increased build time about 2.8× and lacked full ordering coverage, so it was not shipped.
Every candidate, one count
Search or filter the 41 distinct candidates. Completed items include the original problem, shipped change, user benefit, implementation location, verification, accessibility behavior, and reduced-motion behavior.
PERF-001Performance measurement tooling
The repository has no repeatable production bundle measurement command or checked-in baseline schema, making bundle regressions difficult to detect.
Installed Expo Atlas as mobile development tooling, added repeatable production export and asset-budget commands, emitted performance-report.json, and retained this candidate register.
Future changes are less likely to silently increase startup work, download size, or install size.
The scripted final iOS production/Hermes export completed and wrote the canonical metrics recorded in docs/validation/evidence/mobile-performance-final.json: 4,855 modules and a 16,364,336-byte bundle.
Implementation, accessibility & regression notes
package.jsonpackage-lock.jsonapps/mobile/package.jsonscripts/mobile-performance-export.mjsscripts/check-mobile-asset-budgets.mjsdocs/validation/mobile-performance-register.json
scripts/mobile-performance-export.mjsscripts/check-mobile-asset-budgets.mjsdocs/validation/evidence/mobile-performance-final.json
None directly; protects overall responsiveness for users of assistive technology as well as touch users. Verified behavior remained unchanged in the relevant automated/source checks.
Reduced motionNot applicable; this improvement does not add or alter motion.
Tooling is development-only and the production application runtime is unchanged; the report schema was parsed successfully with jq.
PERF-002Achievement imagery
Achievement artwork is encoded at 1254 by 1254 pixels even though its largest in-app presentation is much smaller, inflating bundled bytes and decoded texture memory.
Resized and recompressed all 115 achievement WebP assets from 1254px sources to 512x512 display-appropriate images while preserving alpha.
Faster installs and updates, lower memory pressure, and fewer image-decoding stalls on achievement-heavy screens.
The asset-budget check passes for all 115 files: 9,479,352 total bytes, 111,982-byte largest file, and 512x512 maximum dimensions. The final exported asset payload fell from 88,506,690 to 41,402,730 bytes across all asset optimizations.
Implementation, accessibility & regression notes
apps/mobile/assets/achievements/images/*.webpscripts/check-mobile-asset-budgets.mjs
apps/mobile/assets/achievements/imagesscripts/check-mobile-asset-budgets.mjsdocs/validation/evidence/mobile-performance-final.json
Badge silhouettes, transparent edges, and contrast remain legible at the application's maximum rendered size.
Reduced motionNot applicable; this improvement does not add or alter motion.
Representative simple and detailed badges were inspected after recompression; no source dimensions exceed the verified budget.
PERF-003Fallback avatar imagery
The light and dark unknown-player avatars are 1254-square PNGs above 1.4 MB each despite appearing as small avatars.
Resized both light and dark unknown-player fallback PNGs to 512x512 and recompressed them to approximately 224KB and 240KB.
Lower app/update size and reduced peak decode memory in lists and profiles.
The asset-budget check passes for both 512x512 files: 463,906 total bytes and a 239,605-byte largest file; the final production export includes the reduced assets.
Implementation, accessibility & regression notes
apps/mobile/assets/dart-home/unknown-avatars/unknown-player.pngapps/mobile/assets/dart-home/unknown-avatars/unknown-player-light.pngscripts/check-mobile-asset-budgets.mjs
apps/mobile/assets/dart-home/unknown-avatarsscripts/check-mobile-asset-budgets.mjsdocs/validation/evidence/mobile-performance-final.json
The fallback silhouette remains distinct in both light and dark themes.
Reduced motionNot applicable; this improvement does not add or alter motion.
Both theme variants were retained with stable paths and no consumer API change.
PERF-004Production startup
The root layout statically imports and mounts the development E2E harness in every application variant.
Replaced the root's static E2E harness import with a tiny variant-aware host that dynamically imports seeded tournament/career/DartBot code only in development or debug variants; the host buffers initial and incoming URLs while the chunk loads.
Less JavaScript module evaluation and less startup work before the first usable screen.
The standard production Atlas graph excludes the E2E harness feature closure. Warm seeded links passed in the Android development client and the deferred harness loaded as its own Metro chunk. A standalone DevRelease cold seeded-link routing limitation is documented in the native evidence; normal customer cold launch passed.
Implementation, accessibility & regression notes
apps/mobile/app/_layout.tsxapps/mobile/src/testing/e2e-harness-host.tsxapps/mobile/src/testing/e2e-harness.tsxapps/mobile/app.config.js
apps/mobile/src/testing/e2e-harness-host.tsxapps/mobile/app/_layout.tsxdocs/validation/evidence/mobile-performance-final.json
Product behavior is unchanged; seeded development links remain available for accessibility QA.
Reduced motionNot applicable; this improvement does not add or alter motion.
Production selection uses the configured native app variant; Metro-backed clients additionally use __DEV__ because Constants can expose the native variant after root mount. Production exports exclude the harness, and pending URLs are buffered until the development chunk is ready.
PERF-005Production startup
The root layout unconditionally imports expo-dev-client, including production export analysis.
Removed the unconditional expo-dev-client JavaScript side-effect import from the root layout while retaining the native expo-dev-client config plugin for development builds.
Potentially less production startup evaluation and a smaller dependency surface.
Source audit finds no mobile runtime import of expo-dev-client and the production Atlas payload contains zero expo-dev-client modules.
Implementation, accessibility & regression notes
apps/mobile/app/_layout.tsxapps/mobile/app.config.js
apps/mobile/app/_layout.tsxapps/mobile/app.config.jsdocs/validation/evidence/mobile-performance-final.json
None. Verified behavior remained unchanged in the relevant automated/source checks.
Reduced motionNot applicable; this improvement does not add or alter motion.
The native development-client plugin remains configured, so removing the redundant JavaScript import does not remove development launcher support.
PERF-006Startup preferences
Theme and analytics providers gate their children on separate SecureStore reads in nested order, creating an avoidable serial startup waterfall.
Made analytics preference hydration non-blocking so the application subtree no longer waits behind a second SecureStore read; theme remains the sole visual gate to prevent a theme flash.
The first screen becomes available sooner without a second storage round trip on the critical path.
Provider policy tests and the full mobile suite pass; analytics renders children immediately and never defaults an unhydrated preference to active capture.
Implementation, accessibility & regression notes
apps/mobile/src/providers/app-providers.tsxapps/mobile/src/providers/theme-provider.tsxapps/mobile/src/providers/analytics-provider.tsxapps/mobile/src/providers/analytics-runtime-policy.ts
apps/mobile/src/providers/analytics-provider.tsxapps/mobile/src/providers/analytics-runtime-policy.test.tsapps/mobile/src/providers/theme-provider.tsx
No additional spinner or provider remount was introduced, and the visual theme still resolves before focusable content appears.
Reduced motionNot applicable; this improvement does not add or alter motion.
Consent remains fail-closed for capture while the serialized preference state loads.
PERF-007Local state subscriptions
One untyped global store-change event forces every mounted local-store hook to requery after unrelated writes.
Introduced typed local-store invalidation topics and routed each hook/provider subscription only to the records it reads, retaining an explicit all-scope fallback.
Fewer redundant SQLite queries and React updates during scoring, sync, settings changes, and background writes.
Focused invalidation tests cover scoped delivery, batched all-scope events, and isolated sync metadata; mobile typecheck and the full state/mobile suites pass.
Implementation, accessibility & regression notes
apps/mobile/src/state/local-store.tsapps/mobile/src/state/local-store-invalidation.tsapps/mobile/src/state/local-store-invalidation.test.tsapps/mobile/src/providers/sync-provider.tsx
apps/mobile/src/state/local-store-invalidation.tsapps/mobile/src/state/local-store-invalidation.test.tsapps/mobile/src/state/local-store.ts
Visible and announced state remains immediate for the subscribed entity while unrelated writes no longer trigger re-queries.
Reduced motionNot applicable; this improvement does not add or alter motion.
High fan-out correctness risk is covered by topic-routing tests plus the all-topic escape hatch.
PERF-008Match scoring persistence
Each saved dart triggers a full player aggregate and achievement rebuild across historical contribution rows and also scans live matches.
Separated snapshot persistence from completed-history aggregation so ordinary in-progress dart/score/undo writes persist game state without rebuilding lifetime statistics and achievements; terminal and completed-correction boundaries still rebuild.
Lower input latency and fewer frame drops during the app's core scoring interaction, especially with long histories.
Policy tests prove ordinary scoring mutations perform snapshot-only work and terminal/correction paths receive the unchanged aggregate evidence; core/state/mobile suites pass.
Implementation, accessibility & regression notes
apps/mobile/src/state/local-store.tsapps/mobile/src/state/local-match-persistence-policy.tsapps/mobile/src/state/local-match-persistence-policy.test.ts
apps/mobile/src/state/local-match-persistence-policy.tsapps/mobile/src/state/local-match-persistence-policy.test.tsapps/mobile/src/state/local-store.ts
Live score state is still persisted and announced immediately; only historical aggregate recomputation moved off the hot path.
Reduced motionNot applicable; this improvement does not add or alter motion.
Terminal and correction parity tests mitigate the high risk of stale lifetime statistics or achievement state.
PERF-009Sync payload queries
Dirty-sync payload generation lists and parses every match and player before filtering dirty records in JavaScript.
Added SQL read models for dirty players, matches, counts, detachments, overrides, statistics, achievements, and trends so sync parses only rows it uploads.
Background sync consumes less CPU, memory, and battery as match history grows.
SQLite fixtures verify dirty/clean mixtures, tournament-simulation exclusion, payload counts, unchanged-write guards, and indexed query plans; state/mobile suites pass.
Implementation, accessibility & regression notes
apps/mobile/src/state/local-db.tsapps/mobile/src/state/local-store.tsapps/mobile/src/state/local-sync-queries.tsapps/mobile/src/state/local-sync-queries.test.ts
apps/mobile/src/state/local-sync-queries.tsapps/mobile/src/state/local-sync-queries.test.tsapps/mobile/src/state/local-db.ts
None directly; background work should not delay assistive interactions. Verified behavior remained unchanged in the relevant automated/source checks.
Reduced motionNot applicable; this improvement does not add or alter motion.
Payload equality and stale-write protection are covered by deterministic SQL fixtures.
PERF-010Sync scheduling
A store change causes SyncProvider to refresh counters and then build a dirty payload, duplicating overlapping full-table scans.
Replaced duplicate global-change scans with a scoped sync topic, cheap dirty counts, a 50ms single-flight inspection queue, and one delayed sync timer for bursts.
Less repeated background work during rapid scoring and batch imports.
Rapid local-change, metadata-only snapshot, offline, retry, and cancellation policies pass in backend/provider/state tests and the full mobile suite.
Implementation, accessibility & regression notes
apps/mobile/src/providers/sync-provider.tsxapps/mobile/src/state/local-db.tsapps/mobile/src/state/local-store.tsapps/mobile/src/state/local-store-invalidation.ts
apps/mobile/src/providers/sync-provider.tsxapps/mobile/src/state/local-store-invalidation.test.tsapps/mobile/src/backend/sync-snapshot-policy.test.ts
Sync status remains accurate while coalescing prevents repeated status churn from the same mutation burst.
Reduced motionNot applicable; this improvement does not add or alter motion.
Queued inspection and unmount cleanup prevent overlapping work or stale status commits.
PERF-011Match history summaries
Match, player, and DartBot history screens call getLocalMatchSummary once per match with unbounded Promise.all; each summary issues additional per-player history queries.
Added a bounded bulk local-match overview projection and replaced per-match unbounded Promise.all summary/history fan-out across Matches, Home, notifications, player profiles, and DartBot profiles.
History and profile screens open and scroll faster with large match libraries, without memory spikes.
Focused tests cover stable ordering, deduplication, single-pass parsing, and bounded reads for 10/100/500 matches. Estimated normal two-player query shape improves from 5+7N to 9+ceil(N/400): 75 to 10 at 10 matches, 705 to 10 at 100, and 3,505 to 11 at 500.
Implementation, accessibility & regression notes
apps/mobile/src/state/local-match-overview.tsapps/mobile/src/state/local-match-overview.test.tsapps/mobile/src/state/local-db.tsapps/mobile/src/state/local-store.tsapps/mobile/src/features/matches/matches-tab-body.tsxapps/mobile/src/features/home/home-dashboard.tsxapps/mobile/src/features/home/home-notifications-screen.tsxapps/mobile/src/features/dartbot/dartbot-profile-screen.tsxapps/mobile/app/(app)/(tabs)/players/[id].tsx
apps/mobile/src/state/local-match-overview.tsapps/mobile/src/state/local-match-overview.test.tsapps/mobile/src/features/matches/matches-tab-body.tsx
Stable source ordering and keys preserve list reading order while loading semantics remain unchanged.
Reduced motionNot applicable; this improvement does not add or alter motion.
Projection parity tests and selective leg-detail retention protect summary correctness.
PERF-012Firebase match listener
The cloud sync listener subscribes to the entire unbounded match history and reapplies full local aggregate reconciliation on listener or metadata changes.
Process document changes incrementally, avoid metadata-only reapplication, and define a safe initial-history pagination strategy.
Lower network, CPU, and battery use for established accounts and faster foreground resumption.
Test add/modify/remove, offline cache, metadata-only snapshots, account switches, reconnects, and old-history retrieval.
Implementation, accessibility & regression notes
apps/mobile/src/backend/firebase-client.tsapps/mobile/src/state/local-store.ts
apps/mobile/src/backend/firebase-client.tsapps/mobile/src/backend/sync-snapshot-policy.tsapps/mobile/src/backend/sync-snapshot-policy.test.ts
Remote changes must remain complete and deterministic; no stale screen-reader state.
Reduced motionNot applicable; this candidate changes background data processing.
high
Status rationaleMetadata-only Firestore callbacks now skip local snapshot reconciliation and real add/modify/remove revisions are distinguished, but the initial listener still subscribes to the full match history and safe old-history pagination was not implemented.
Remaining workDefine and test bounded initial-history pagination plus incremental local reconciliation for established accounts, including offline cache, reconnect, account switch, and historical retrieval.
PERF-013Career persistence
Every career screen mount attempts a cloud restore and then can upload unchanged local career state.
Added per-account/profile/generation single-flight career revalidation with a freshness window, explicit reload bypass, restore decision policy, and unchanged-write suppression.
Career navigation performs fewer network and serialization operations and uses less battery.
Career tests cover same-profile sharing, account-switch generations, explicit reload, cloud failure retry, local/remote lineage decisions, and zero echo writes; 51 career tests and the mobile suite pass.
Implementation, accessibility & regression notes
apps/mobile/src/features/career/use-career.tsapps/mobile/src/features/career/career-service.tsapps/mobile/src/features/career/career-revalidation-cache.tsapps/mobile/src/features/career/career-revalidation-cache.test.tsapps/mobile/src/features/career/career-restore-policy.tsapps/mobile/src/features/career/career-restore-policy.test.ts
apps/mobile/src/features/career/career-revalidation-cache.test.tsapps/mobile/src/features/career/career-restore-policy.test.tsapps/mobile/src/features/career/career-service.ts
Loading and errors remain profile-scoped and no stale result can expose another account's career.
Reduced motionNot applicable; this improvement does not add or alter motion.
Generation checks and failure eviction prevent stale-account reuse or permanently poisoned cache entries.
PERF-014Relationship subscriptions
Friend search starts a second relationship subscription and relationship updates discard listener data before issuing profile/player N+1 reads.
Projected friend relationships directly from coordinated profile/player snapshot listeners and removed duplicate relationship/N+1 fetch behavior from friend search and related consumers.
Friend and search screens react faster with fewer listeners and network round trips.
Backend tests cover listener readiness, selected main-player snapshots, request rows without private player reads, stable order, and avatar projection; the backend suite passes 11/11.
Implementation, accessibility & regression notes
apps/mobile/src/backend/firebase-client.tsapps/mobile/src/backend/friend-relationship-projection.tsapps/mobile/src/backend/friend-relationship-projection.test.tsapps/mobile/src/features/players/player-add-data.tsapps/mobile/app/(app)/(tabs)/players/new.tsx
apps/mobile/src/backend/friend-relationship-projection.tsapps/mobile/src/backend/friend-relationship-projection.test.tsapps/mobile/src/backend/firebase-client.ts
Relationship state and stable ordering remain accurate for screen-reader announcements and focus.
Reduced motionNot applicable; this improvement does not add or alter motion.
Readiness gating prevents publishing incomplete friend rows while listeners converge.
PERF-015Long lists
Several potentially unbounded data collections are rendered with ScrollView or with FlatList defaults that do not reflect row size and viewport needs.
Virtualize genuinely unbounded collections and tune initialNumToRender, windowSize, batch size, clipping, stable keys, and layout hints only where measurement supports it.
Lower mount time and memory use, with smoother scrolling on large datasets.
Profile representative small and large datasets, rapid scroll, focus traversal, empty states, and dynamic insertion/removal.
Implementation, accessibility & regression notes
apps/mobile/src/featuresapps/mobile/app/(app)
apps/mobile/src/features/matches/matches-tab-body.tsxapps/mobile/src/features/career/career-secondary-screens.tsxapps/mobile/src/features/players/players-rankings-screen.tsx
Preserve logical focus order, reachable content, and announcements as rows recycle.
Reduced motionList recycling itself has no motion; any remaining row transitions must continue to honor the shared motion preference.
high
Status rationaleHigh-impact lists in Matches, Order of Merit, Tournament Bracket, and Player Rankings were virtualized and tuned under their distinct candidate IDs, but the complete app-wide inventory of potentially unbounded ScrollViews was not profiled or converted.
Remaining workProfile remaining notification, catalog, tournament, and profile collections with large seeded datasets and verify focus traversal before any additional conversions.
PERF-016List row rendering
Expensive history and catalog rows may receive unstable callbacks or recompute derived data on every parent update.
Use stable renderItem/keyExtractor and focused memoization only on measured hot rows not already optimized by the compiler.
Reduced JS work during scrolling and unrelated state changes.
Compare Profiler commit counts and duration before and after each focused change.
Implementation, accessibility & regression notes
apps/mobile/src/featuresapps/mobile/app/(app)
apps/mobile/src/features/players/players-rankings-components.tsxapps/mobile/src/features/matches/matches-tab-body.tsx
Rows must still update immediately when labels, state, or disabled semantics change.
Reduced motionNot applicable to row identity/memoization.
medium
Status rationaleSeveral hot list rows gained stable keys/callbacks and React Compiler remains enabled, but there is no complete React Profiler comparison proving which remaining rows benefit from manual memoization.
Remaining workCapture release-mode Profiler commits for representative large histories/catalogs and add focused memoization only where compiler output does not already eliminate the work.
PERF-017Expo Image usage
List imagery is not consistently given cache policy, priority, transition, or recycling identity appropriate to its role.
Add role-based image defaults and list-specific recycling keys/priority where reused views can otherwise show stale or delayed content.
Fewer decode/fetch stalls and less incorrect-image flashing during fast scrolls.
Test cold/warm cache, offline mode, rapid list recycling, theme changes, and replaced profile images.
Implementation, accessibility & regression notes
apps/mobile/src/componentsapps/mobile/src/features
apps/mobile/src/components/player-avatar.tsx
Alternative labels and decorative-image behavior must remain correct.
Reduced motionAny future image transition must be disabled or immediate under reduced motion.
medium
Status rationalePlayerAvatar now has memory-disk caching and a recycling key, completing the remote-avatar-specific work in PERF-039, but role-based defaults were not verified across every Expo Image call site.
Remaining workAudit cold/warm/offline behavior, priority, transitions, and recycling identity for achievement, tournament, profile, and decorative image roles.
PERF-018Animation runtime
Layout-property animations, JavaScript-thread shared-value reads, or excessive simultaneously animated nodes can reduce frame throughput.
Move measured hotspots to transform/opacity, remove JS-thread shared-value polling, and cap offscreen or repeated animation work.
More stable gestures, navigation, and scrolling without changing the established motion language.
Inspect interruption behavior and release-mode frame traces with motion enabled and reduced motion enabled.
Implementation, accessibility & regression notes
apps/mobile/src/componentsapps/mobile/src/featuresapps/mobile/src/lib/motion.ts
apps/mobile/src/features/achievements/achievement-celebration.tsxapps/mobile/src/features/dartbot-tv/replay-clock.tsapps/mobile/src/providers/motion-provider.tsx
Reduced-motion behavior must remain immediate and equivalent.
Reduced motionAll landed hotspot fixes retain immediate final states; the remaining audit must verify every other motion path.
medium
Status rationaleSpecific measured hotspots moved to native/UI-thread or bounded animation paths in PERF-027 through PERF-030, and ongoing indicators pause while inactive/reduced, but an app-wide release frame trace and complete layout-animation audit remain outstanding.
Remaining workCapture release-mode frame/ETTrace evidence for navigation, long lists, gestures, and interruption; then address only measured remaining layout/shared-value hotspots.
PERF-019Static career data
The production module graph contains a 1.2 MB canonical career starting-state JSON module.
Measure parse/evaluation cost and determine whether a compact generated representation or deferred evaluation can reduce startup and bundle cost without harming deterministic career state.
Potentially faster startup and smaller downloads, especially for users who never enter Career.
Byte-compare generated data, run all career determinism tests, and measure release bundle and evaluation timing.
Implementation, accessibility & regression notes
packages/core/src/domain/career/starting-state/canonical-v1.jsonpackages/core/src/domain/career-starting-state.ts
packages/core/src/domain/career/starting-state/canonical-v1.jsonpackages/core/src/domain/career-starting-state.tsdocs/validation/evidence/mobile-performance-final.json
None.
Reduced motionNot applicable; static data representation only.
high
Status rationaleNarrow core imports remove canonical-v1.json from the startup provider closure, but the 1.2MB deterministic artifact remains in the production route graph and was not reformatted.
Remaining workDesign a byte-stable compact representation or route-level deferred loader, regenerate the artifact, run every career determinism/migration test, and compare parse/evaluation timing.
PERF-020Static DartBot data
The production module graph contains a generated DartBot player pool above 1 MB plus additional calibration modules.
Measure access patterns and evaluate a compact generated representation, lookup partitioning, or deferred construction while preserving deterministic bot selection.
Potentially lower bundle parse cost and faster non-DartBot startup.
Run the full DartBot and calibration suites, compare generated records, and remeasure production export.
Implementation, accessibility & regression notes
packages/core/src/domain/dartbot/generated-player-pool.tspackages/core/src/domain/dartbot/profile-registry.tspackages/core/src/domain/dartbot/catalog.ts
packages/core/src/domain/dartbot/generated-player-pool.tspackages/core/src/domain/dartbot/profile-registry.tsdocs/validation/evidence/mobile-performance-final.json
None.
Reduced motionNot applicable; static data representation only.
high
Status rationaleNarrow core imports remove the generated player pool from AppProviders/startup, but the pool and calibration data remain required by DartBot routes and were not compacted or partitioned.
Remaining workMeasure DartBot route access/parse cost, prototype a compact or partitioned generated format, and run the complete generation, calibration, simulation, and determinism suites.
PERF-021Observability bundle/runtime
Sentry replay and feedback packages contribute substantial production module weight, and runtime integrations may impose work even when features are unused.
Verify configured integrations and tree-shaking behavior; exclude unused replay/feedback functionality only if crash and performance monitoring remain intact.
Potentially smaller download and less monitoring overhead without losing actionable crash diagnostics.
Compare Atlas package totals and send a controlled test error plus performance span in a release build.
Implementation, accessibility & regression notes
apps/mobile/src/lib/sentry.tsapps/mobile/app/_layout.tsxapps/mobile/metro.config.js
apps/mobile/src/lib/sentry.tsapps/mobile/app/_layout.tsx
None.
Reduced motionNot applicable; observability integration selection only.
high
Status rationaleSentry crash monitoring remained intact, but replay/feedback integrations were not removed because release error/span verification was unavailable and a blind import change could reduce diagnostic coverage.
Remaining workMeasure configured release integrations, send a controlled crash and performance span, and exclude replay/feedback only with proof that required diagnostics remain.
PERF-022Production logging
Console calls in production can block the JavaScript thread if application or dependency logs reach hot paths.
Confirm hot-path logging behavior and configure production console removal only if it does not hide Sentry breadcrumbs or actionable errors.
Avoidable logging cannot cause scoring or navigation stalls.
Inspect release output and confirm error reporting remains operational.
Implementation, accessibility & regression notes
apps/mobile/metro.config.jsapps/mobile/src/lib/perf-timing.ts
apps/mobile/src/lib/perf-timing.tsapps/mobile/src/lib/sentry.ts
None.
Reduced motionNot applicable; logging only.
medium
Status rationaleRejected after source and release-output inspection found limited application logging, with performance timing already gated; blanket console removal had no demonstrated hot-path benefit and risked hiding Sentry breadcrumbs or actionable errors.
Remaining workNone for this pull request. Reopen only if a release trace identifies a concrete production logging hotspot.
PERF-023SQLite indexes and query plans
Growth-sensitive local queries may lack indexes for dirty state, player contribution history, status, and update ordering.
Added partial/composite indexes for dirty sync rows, match ordering, contribution history, and trend history, then committed EXPLAIN QUERY PLAN assertions for the hot reads.
Local history, aggregate rebuild, and sync performance scales better as data grows.
SQLite query-plan tests assert the named dirty/history indexes are used and verify fresh-schema execution; the state and full mobile suites pass.
Implementation, accessibility & regression notes
apps/mobile/src/state/local-db.tsapps/mobile/src/state/local-sync-queries.tsapps/mobile/src/state/local-sync-queries.test.ts
apps/mobile/src/state/local-sync-queries.tsapps/mobile/src/state/local-sync-queries.test.tsapps/mobile/src/state/local-db.ts
None directly. Verified behavior remained unchanged in the relevant automated/source checks.
Reduced motionNot applicable; this improvement does not add or alter motion.
Indexes are created idempotently with IF NOT EXISTS and query-plan fixtures guard against silent planner regressions.
PERF-024Lifecycle cleanup and background work
Timers, app-state handlers, listeners, and async effects across providers and screens can continue work after blur or overlap after rapid remounts.
Cancel superseded work, unsubscribe on blur/unmount, and coalesce bursts at lifecycle boundaries.
Lower battery usage, fewer stale updates, and more responsive rapid navigation.
Rapidly navigate, background/foreground, switch accounts, cancel operations, and inspect listener/timer counts.
Implementation, accessibility & regression notes
apps/mobile/src/providersapps/mobile/src/featuresapps/mobile/app
apps/mobile/src/providers/sync-provider.tsxapps/mobile/src/providers/motion-provider.tsxapps/mobile/src/providers/toast-provider.tsxapps/mobile/src/features/dartbot-tv/replay-clock.ts
Necessary status updates must not be cancelled before assistive users receive the final state.
Reduced motionLanded ongoing/animation cleanup honors reduced motion; remaining lifecycle work must preserve final accessible state delivery.
medium
Status rationaleConcrete cleanup/coalescing fixes landed in sync, replay, toast, settings, and motion paths, but a complete app-wide listener/timer inventory with runtime counts was not finished.
Remaining workInstrument listener/timer counts during rapid navigation, background/foreground, cancellation, and account switching, then fix only traced leaks or overlaps.
PERF-025Live match rendering
The live match route mounts both the game-state loader and the heavyweight lifetime-aware match-summary loader, so every dart repeats historical contribution and achievement work.
The live route now consumes the match aggregate's lightweight liveSummary projection and reserves lifetime-aware completed summary/achievement history work for terminal confirmation paths.
Lower dart-to-score latency and fewer dropped frames during long matches or on accounts with deep history.
Live and completed scoring/state tests pass, ordinary scoring persistence performs no contribution-history rebuild, and the live route renders its statistics from liveSummary.
Implementation, accessibility & regression notes
apps/mobile/app/(app)/match/[id]/index.tsxapps/mobile/src/state/local-store.tsapps/mobile/src/state/local-match-persistence-policy.ts
apps/mobile/app/(app)/match/[id]/index.tsxapps/mobile/src/state/local-store.tsapps/mobile/src/state/local-match-persistence-policy.test.ts
Visible statistics and score announcements still derive from the current persisted match state.
Reduced motionNot applicable; this improvement does not add or alter motion.
Completed/correction boundaries retain full history evidence, mitigating summary and adaptive-level regressions.
PERF-026Voice debug rendering
The core voice-input hook subscribes React state to the debug log even when debug UI is hidden, and training ignores the returned log entirely.
Removed React debug-log subscription from the core voice input hook and mounted it only in the visible match debug panel; training no longer subscribes to unused log rows.
Voice scoring no longer causes unrelated full-screen React commits for hidden diagnostic information.
Voice and training suites pass; source inspection confirms hidden debug collection no longer owns React state in the full input hook.
Implementation, accessibility & regression notes
apps/mobile/src/features/voice/use-match-voice-input.tsapps/mobile/src/features/voice/voice-debug-log.tsapps/mobile/app/(app)/match/[id]/index.tsxapps/mobile/src/features/training/training-screens.tsx
apps/mobile/src/features/voice/use-match-voice-input.tsapps/mobile/src/features/voice/voice-debug-log.tsapps/mobile/app/(app)/match/[id]/index.tsx
Spoken scoring status and rejection feedback are unchanged; only hidden developer diagnostics stopped rerendering the screen.
Reduced motionNot applicable; this improvement does not add or alter motion.
Debug entries remain available when the explicit panel is enabled.
PERF-027Achievement animation
Achievement celebration entrance, reveal, float, and confetti transform/opacity animations explicitly run on the JavaScript thread.
Moved all eligible achievement entrance, reveal, float, and confetti transform/opacity animations to the React Native native driver while retaining interruption cleanup.
Achievement completion remains smooth when JavaScript is simultaneously persisting and synchronizing the completed match.
All four animation groups now use useNativeDriver: true; motion/core/mobile suites pass without unsupported-driver warnings.
Implementation, accessibility & regression notes
apps/mobile/src/features/achievements/achievement-celebration.tsx
apps/mobile/src/features/achievements/achievement-celebration.tsxapps/mobile/src/lib/motion-spec.test.ts
Achievement content and completion announcements remain unchanged.
Reduced motionReduced-motion mode bypasses meaningful travel and exposes the completed celebration state immediately.
Only native-driver-supported transform and opacity properties were moved; cancellation paths remain.
PERF-028Career Order of Merit
The ranking screen mounts and animates approximately 361 rows inside a ScrollView.
Converted the approximately 361-row Order of Merit from ScrollView mapping to a tuned FlatList and limited rank-transition animation to mounted rows with actual rank changes.
Faster ranking-screen opening, bounded memory, and smooth access to low-ranked entries.
Career tests prove virtualized rows do not animate initial layout and mounted rows animate only real changes; rank 361 remains reachable with stable ordering.
Implementation, accessibility & regression notes
apps/mobile/src/features/career/career-secondary-screens.tsxapps/mobile/src/features/career/career-screen-shell.tsxapps/mobile/src/features/career/career-rank-transition.tsxapps/mobile/src/features/career/career-rank-transition-policy.test.ts
apps/mobile/src/features/career/career-secondary-screens.tsxapps/mobile/src/features/career/career-rank-transition-policy.test.ts
Stable keys and source order preserve rank reading and focus identity while rows recycle.
Reduced motionRank changes render their final position immediately when reduced motion is enabled.
Conservative FlatList batching and explicit policy tests cover recycling and rank-change behavior.
PERF-029Tournament bracket
A 128-player bracket initially mounts the first two heavy round columns and only later imperatively scrolls to the desired round; every fixture receives entry/layout animation.
Added fixed getItemLayout, initialScrollIndex, one-column initial/batch rendering, a small window, and removed initial per-fixture layout animation for tournament brackets.
Large brackets show the relevant round sooner with less layout and animation work.
Tournament motion tests cover active/completed initial round selection and layout fallback; the tournament and mobile suites pass.
Implementation, accessibility & regression notes
apps/mobile/src/features/tournaments/tournament-run-detail-screen.tsxapps/mobile/src/features/tournaments/tournament-run-motion.tsapps/mobile/src/features/tournaments/tournament-run-motion.test.ts
apps/mobile/src/features/tournaments/tournament-run-detail-screen.tsxapps/mobile/src/features/tournaments/tournament-run-motion.test.ts
The initially visible and announced round now matches the active/final round instead of imperatively jumping after mount.
Reduced motionInitial bracket cards appear without decorative entry/layout motion; reduced-motion users see the target round immediately.
Fixed dimensions provide deterministic offsets and the existing failure fallback remains.
PERF-030DartBot TV replay
Replay updates top-level React elapsed state every 32 ms, rerendering board, scoreboard, chrome, and controls while animating progress-bar width.
Replaced the 32ms top-level React elapsed-state loop with one UI-thread shared clock; React state advances only at discrete replay boundaries and progress uses transform-based animation.
Replay remains smooth and controls stay responsive on slower devices.
Replay-clock tests cover presentation boundaries, pause/resume, completion, and monotonic elapsed projection; DartBot TV and mobile suites pass.
Implementation, accessibility & regression notes
apps/mobile/src/features/dartbot-tv/career-broadcast-replay-screen.tsxapps/mobile/src/features/dartbot-tv/replay-clock.tsapps/mobile/src/features/dartbot-tv/replay-clock.test.ts
apps/mobile/src/features/dartbot-tv/replay-clock.tsapps/mobile/src/features/dartbot-tv/replay-clock.test.tsapps/mobile/src/features/dartbot-tv/career-broadcast-replay-screen.tsx
Pause and replay position remain semantically current without announcing frame-level progress.
Reduced motionReduced motion preserves immediate discrete phase changes and avoids decorative continuous transitions while playback semantics remain usable.
A pure clock policy and boundary tests mitigate pause/background/interruption drift.
PERF-031Icon asset bundle
Imports from the @expo/vector-icons root expose 19 icon font families even though the mobile app uses only Ionicons, MaterialCommunityIcons, and Feather.
Replaced @expo/vector-icons barrel imports with direct Ionicons, MaterialCommunityIcons, and Feather family modules and added an ESLint restriction against the root import.
Smaller install/update payload without any visible icon change.
Full lint has zero errors, source audit finds no root icon import, and the final production export bundles only the three used font families.
Implementation, accessibility & regression notes
apps/mobile/**/*.tsxapps/mobile/eslint.config.mjs
apps/mobile/eslint.config.mjsdocs/validation/evidence/mobile-performance-final.json
Visible glyphs, labels, and accompanying accessible text are unchanged.
Reduced motionNot applicable; this improvement does not add or alter motion.
Typecheck and route smoke coverage guard family/name compatibility.
PERF-032Unused UI runtime
ThemeProvider mounts RepoUIProvider/Tamagui even though the active mobile app does not render shared Tamagui components.
Exposed shared UI tokens through a narrow package subpath, imported those values directly, and removed the unused RepoUIProvider/Tamagui runtime wrapper from MobileThemeProvider.
Less startup evaluation and a smaller JavaScript bundle with unchanged visual tokens.
Source audit finds no active Tamagui component consumer/provider; mobile typecheck, dark/light theme tests, and final Atlas export pass.
Implementation, accessibility & regression notes
packages/ui/package.jsonpackages/ui/src/tokens.tsapps/mobile/src/lib/theme.tsapps/mobile/src/providers/theme-provider.tsx
packages/ui/package.jsonapps/mobile/src/providers/theme-provider.tsxdocs/validation/evidence/mobile-performance-final.json
The same token values and system light/dark contrast behavior remain in use.
Reduced motionNot applicable; this improvement does not add or alter motion.
Only an unused runtime provider was removed; token resolution remains a supported package API.
PERF-033Font startup
Navigation waits for runtime loading of five font files after persisted-provider gates, and two included font files are unused.
Embed only the four used fonts with the expo-font config plugin and remove the runtime font gate.
First application content can render without a separate JavaScript font-loading phase.
Build a fresh native app, inspect font availability on cold launch, and exercise English/German text at large sizes.
Implementation, accessibility & regression notes
apps/mobile/app.config.jsapps/mobile/app/_layout.tsxapps/mobile/src/lib/fonts.ts
apps/mobile/app.config.jsapps/mobile/app/_layout.tsxapps/mobile/src/lib/fonts.ts
No fallback-font flash or missing glyphs; Dynamic Type behavior remains unchanged.
Reduced motionThe current spinner is static; eventual gate removal must not introduce a font transition or layout shift.
medium
Status rationaleThe expo-font config plugin now embeds only the four used Satoshi files and two unused font assets were removed from the load map, but the JavaScript useFonts spinner gate remains pending a fresh native-build verification of embedded family names.
Remaining workBuild a fresh iOS and Android development app, verify all four embedded families on cold launch and at large text sizes, then remove useFonts/AppBootstrap gating and the runtime asset map.
PERF-034Core module evaluation
Startup consumers import @repo/core's root barrel, which eagerly reexports most domains and pulls career/DartBot generated data into the root evaluation closure.
Added supported narrow @repo/core exports, migrated every mobile runtime root-barrel import to subpaths across 48 core-consuming mobile files, split username helpers from Zod, and split pure career archive normalization from DartBot replay execution; ESLint now rejects runtime root imports while allowing type-only imports.
Less JavaScript evaluation and memory allocation before the first screen.
Runtime @repo/core root imports are zero. Atlas root closure changed from 1,291 modules/11,951,072 transformed bytes to absent; AppProviders changed from 2,226/16,928,746 to 1,926/9,476,024 (-44.0%) and contains neither canonical career JSON nor the DartBot pool.
Implementation, accessibility & regression notes
packages/core/package.jsonpackages/core/srcpackages/core/src/domain/career/archive.tspackages/core/src/domain/career/archive-replay.tsapps/mobile/eslint.config.mjsapps/mobile/src/backend/firebase-client.tsapps/mobile/src/state/local-store.tsapps/mobile/src/providers/sync-provider.tsxapps/mobile/src/lib/sentry.ts
packages/core/package.jsonapps/mobile/eslint.config.mjsdocs/validation/evidence/mobile-performance-final.json
This is an import/evaluation-boundary change; product semantics and accessible output remain unchanged.
Reduced motionNot applicable; this improvement does not add or alter motion.
Core/mobile typechecks, core 210/210, backend 11/11, career 51/51, affected feature suites, production export, and the lint boundary all pass.
PERF-035Root component imports
The root layout imports AppStatusBar through a broad Midnight UI barrel, expanding its eager dependency closure.
Changed the root layout to import AppStatusBar directly from midnight-screen instead of the broad Midnight UI barrel.
Less eager module evaluation during startup with identical status-bar behavior.
Mobile typecheck/lint pass and Atlas no longer reaches the barrel's approximately 168KB exclusive closure through AppStatusBar.
Implementation, accessibility & regression notes
apps/mobile/app/_layout.tsxapps/mobile/src/components/chrome/midnight-screen.tsx
apps/mobile/app/_layout.tsxapps/mobile/src/components/chrome/midnight-screen.tsxdocs/validation/evidence/mobile-performance-final.json
Status-bar theme behavior is unchanged in light and dark mode.
Reduced motionNot applicable; this improvement does not add or alter motion.
The defining component is imported directly without changing its API.
PERF-036Local database startup
Concurrent consumers can race local-store initialization, while both local databases retry historical ALTER TABLE statements and catch duplicate-column errors on every launch.
Replaced boolean/racing initialization with shared retryable promises and added transactional PRAGMA user_version migrations for local and career databases, inspecting actual columns instead of relying on duplicate-column exceptions.
Less duplicate startup work and deterministic database readiness on cold launch.
Migration tests cover fresh, original, partial, current-unversioned, failed-open retry, and future-version databases; legacy SQL smoke preserved rows, backfilled defaults, and stamped the expected version.
Implementation, accessibility & regression notes
apps/mobile/src/state/local-store.tsapps/mobile/src/state/local-db.tsapps/mobile/src/state/local-db-migrations.tsapps/mobile/src/state/local-db-migrations.test.tsapps/mobile/src/features/career/career-repository.tsapps/mobile/src/features/career/career-database-migrations.tsapps/mobile/src/features/career/career-database-migrations.test.ts
apps/mobile/src/state/local-db-migrations.test.tsapps/mobile/src/features/career/career-database-migrations.test.tsapps/mobile/src/features/career/career-repository.ts
Loading and recovery surfaces are unchanged; readiness is now deterministic.
Reduced motionNot applicable; this improvement does not add or alter motion.
High schema risk is mitigated by transactional upgrades, column inspection, future-version non-downgrade, handle cleanup, and retry tests.
PERF-037Avatar upload memory
Custom avatar upload reads the complete JPEG into a JavaScript base64 string before handing it back to the native Firebase Storage SDK.
Replaced FileSystem base64 loading plus Firebase putString with the native Storage putFile(localUri) upload path and removed the JavaScript-sized base64 allocation.
Lower peak memory, less bridge work, and fewer upload stalls or memory-pressure failures on large camera/library images.
Mobile typecheck/backend tests pass and the upload retains content type, ownership metadata, bucket error translation, cache invalidation, and updatedAt behavior.
Implementation, accessibility & regression notes
apps/mobile/src/backend/firebase-client.tsapps/mobile/src/features/players/player-avatar-image.ts
apps/mobile/src/backend/firebase-client.tsapps/mobile/src/features/players/player-avatar-image.ts
Upload progress/error presentation and avatar labels are unchanged.
Reduced motionNot applicable; this improvement does not add or alter motion.
The native SDK's documented file path replaces only the transport representation; error handling remains intact.
PERF-038Player rankings
Rankings performs per-player local/remote statistic reads, computes ranks with repeated array searches, and non-bot filters can mount every row in a ScrollView.
Added a lean remote leaderboard statistic read, computed rank maps in one pass, stabilized ranking derivation, and rendered both bot and non-bot filters with tuned accessible FlatLists.
Faster rankings first paint and smooth scrolling as local players and friends grow.
Ranking tests verify stable ordering/rank output; 0/10/100/500 data paths, filters, remote errors, account changes, typecheck, lint, and full mobile tests pass.
Implementation, accessibility & regression notes
apps/mobile/src/features/players/players-rankings-screen.tsxapps/mobile/src/features/players/players-rankings-data.tsapps/mobile/src/features/players/players-rankings-data.test.tsapps/mobile/src/features/players/players-rankings-components.tsxapps/mobile/src/backend/firebase-client.ts
apps/mobile/src/features/players/players-rankings-data.test.tsapps/mobile/src/features/players/players-rankings-screen.tsxapps/mobile/src/backend/firebase-client.ts
Stable keys and rank order preserve focus identity and logical reading order while rows recycle.
Reduced motionNot applicable; this improvement does not add or alter motion.
Conservative list batching and deterministic rank tests protect filter and ordering parity.
PERF-039Remote avatar resolution
Simultaneously mounted avatar rows can request the same Firebase Storage download URL more than once, and the resolved URL cache grows without a bound.
Added a 128-entry LRU promise cache keyed by avatar path/version, sharing in-flight URL loads, evicting failures, invalidating every version after upload/delete, and assigning Expo Image memory-disk cache policy plus recycling identity.
Avatar-heavy rankings, history, and profile screens make fewer network calls, reuse decoded content reliably, and avoid unbounded JavaScript cache growth.
Four focused cache tests cover concurrent single-flight, resolved reuse, rejection retry, LRU eviction, and path-version invalidation; the combined backend suite passes 11/11 and mobile typecheck/lint pass.
Implementation, accessibility & regression notes
apps/mobile/src/backend/bounded-async-cache.tsapps/mobile/src/backend/bounded-async-cache.test.tsapps/mobile/src/backend/firebase-client.tsapps/mobile/src/components/player-avatar.tsxapps/mobile/package.json
apps/mobile/src/backend/bounded-async-cache.test.tsapps/mobile/src/backend/firebase-client.tsapps/mobile/src/components/player-avatar.tsx
Recycling identity follows the resolved player image/name and never changes the player's accessible label.
Reduced motionNot applicable; this improvement does not add or alter motion.
Failures cannot poison the cache, in-flight invalidation cannot reinsert stale values, and the size is strictly bounded.
PERF-040Opt-in analytics startup
The application evaluates PostHog, touch-autocapture, and session-replay JavaScript at startup even when analytics is unconfigured, disabled, or the persisted preference has not loaded.
Removed static PostHog and PostHogProvider evaluation, rendered the product subtree immediately, and dynamically loaded one shared client only after configuration plus persisted opt-in; rapid preference and identity commands are serialized with stale-generation guards.
First content appears with less JavaScript evaluation and no analytics-only loading gate; disabled analytics consumes no SDK runtime work.
Provider tests cover configuration, hydration, stale loads, unmount, and rapid toggles; production closure inspection shows asyncRequire only with no top-level PostHog require/provider wrapper; full mobile suite/typecheck/lint pass.
Implementation, accessibility & regression notes
apps/mobile/src/providers/analytics-provider.tsxapps/mobile/src/providers/analytics-runtime-policy.tsapps/mobile/src/providers/analytics-runtime-policy.test.tsapps/mobile/package.json
apps/mobile/src/providers/analytics-provider.tsxapps/mobile/src/providers/analytics-runtime-policy.test.tsdocs/validation/evidence/mobile-performance-final.json
The provider never adds a spinner or remounts the product subtree, so delayed SDK loading cannot reset focus.
Reduced motionNot applicable; this improvement does not add or alter motion.
Generation checks, serialized commands, fail-closed consent, and unmount tests mitigate stale SDK activation.
PERF-041Experimental Expo graph optimization
Standard production minification retains some unused cross-module exports that Expo's experimental whole-graph optimizer can remove.
Evaluate Expo's experimental whole-graph optimization and tree-shaking flags, adopting them only after comparable native release coverage proves module order and side effects remain correct.
Potentially smaller OTA downloads and less JavaScript to parse or store.
Compare production exports and then run the complete native/seeded suite with both experimental flags before adoption.
Implementation, accessibility & regression notes
apps/mobile/metro.config.jsapps/mobile/.env.production.local.example
apps/mobile/metro.config.jsdocs/validation/evidence/mobile-performance-final.json
No intended UI change; a module-order regression could break any flow, including assistive behavior.
Reduced motionNo intended motion change; optimizer adoption still requires reduced-motion flow coverage.
high
Status rationaleThe experiment demonstrated a 743,483-byte (5.05%) raw-JavaScript reduction, but the whole-graph optimizer is experimental, increased build time about 2.8x, and was not adopted without full native side-effect/module-order coverage.
Remaining workRun comparable iOS/Android native, seeded navigation, auth, sync, audio, voice, and accessibility suites with both optimizer flags before considering production adoption.
Official guidance, checked against this app
Release-mode measurement, virtualized-list tuning, image caching/recycling, native-thread animation, bundle analysis, Hermes, Firestore offline behavior, and native file upload guidance informed the candidate register. Experimental features were measured and deferred when their risk outweighed the benefit.
- reactnative.dev/docs/performance
- reactnative.dev/docs/0.85/flatlist
- reactnative.dev/docs/0.85/optimizing-flatlist-configuration
- reactnative.dev/docs/0.85/global-performance
- react.dev/reference/react/Profiler
- react.dev/reference/react/memo
- react.dev/reference/react/useMemo
- docs.expo.dev/guides/analyzing-bundles
- docs.expo.dev/guides/tree-shaking
- docs.expo.dev/guides/customizing-metro
- docs.expo.dev/guides/minify
- docs.expo.dev/guides/using-hermes
- docs.expo.dev/guides/debugging-runtime-issues
- docs.expo.dev/eas-update/optimize-assets
- docs.expo.dev/versions/latest/sdk/image
- docs.expo.dev/develop/user-interface/fonts
- docs.expo.dev/router/web/async-routes
- docs.swmansion.com/react-native-reanimated/docs/guides/performance
- firebase.google.com/docs/firestore/manage-data/enable-offline
- rnfirebase.io/storage/usage
- reference.rnfirebase.io/storage/reference/storagereference#putfile