Repository navigation
perf: load Solana, wallet and purchase code on demand - #14664
Merged
Merged
Conversation
The services barrel pulled AudiusBackend, audius-backend/solana and WalletClient into the web entry chunk, and with them @solana/web3.js, spl-token, buffer-layout and Jupiter. Query hooks in the api barrel did the same. - Move AudiusBackend's Solana methods into solanaBackend.ts, loaded on first call. findAssociatedTokenAddress is now async. - Stop re-exporting audius-backend/solana from the services barrel; importers use the deep path or a dynamic import. - WalletClient, the hedgehog Solana wallet service, wallet/jupiter/coin query hooks and the userbank/coinflow/mint recovery hooks import web3.js, spl-token, tweetnacl, viem and Jupiter inside the async code that needs them. - Split the Solana instruction helpers out of jupiter/utils into solanaUtils; useSwapCoins loads the swap orchestrator on demand. - isValidSolAddress checks for a 32 byte base58 key instead of constructing a PublicKey. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add lazySagas, which waits for the first action a feature's sagas handle, imports the saga module, starts it and replays the buffered actions. Replays go straight onto the saga middleware's channel (exposed to sagas as the `sagaChannel` context) so reducers don't run twice. - purchaseContentSagas, withdrawUSDCSagas and stripeModalUISagas from @audius/common/store now load their real sagas on the first matching action, so the web and mobile roots are unchanged. - buyUSDCSagas loads right after boot rather than on demand because it runs the purchase recovery check on page load. - Mobile token dashboard and wallet-connect sagas load on first use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wagmi was in the entry chunk only for a placeholder config and the root WagmiProvider. The one hook that reads wagmi context, useConnectExternalWallets, now passes AppKit's config explicitly, and ReownAppKitModal reconnects a persisted wallet when it loads (what the provider's Hydrate used to do). AppProviders still loads AppKit at startup when a wallet connection is persisted. auth.ts and sign-out import @wagmi/core dynamically. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
These NiceModal modals were imported eagerly by registerNiceModals and pulled Solana and purchase code into the entry chunk. Register them with registerLazyModal like the AppKit/Coinflow modals. Also mount CoinflowPurchaseProtection when the browser is idle, in its own Suspense boundary. It shared the boundary around <Outlet />, so page content waited on the Coinflow chunk. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Recovery no longer runs on page load (#14660), and startRecoveryIfNecessary is itself a trigger, so the sagas can wait for an onramp or recovery request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
This was referenced Oct 13, 2026
dylanjeffers
added a commit
that referenced
this pull request
Oct 13, 2026
…unk (#14667) The non-crypto half of the web bundle audit. The crypto half is #14660–#14664. ## Impact (web entry chunk, prod build) | | min | gzip | |---|---|---| | main | 5,627 KB | 1,526 KB | | this PR, entry | 3,763 KB | 1,113 KB | | this PR, entry + new vendor chunk | 4,103 KB | 1,222 KB | ## Changes (one commit each) 1. **Lazy Optimizely.** `@optimizely/optimizely-sdk` loads inside `createOptimizelyClient` (~357 KB raw). 2. **Lazy queue popover.** It loads on first open and is prefetched on hover, which takes the atlaskit drag-and-drop library out of the entry (~225 KB raw). 3. **Lazy notification panel and mobile-web notification page.** The panel mounts on the first hover or open of the bell (~105 KB raw). The notification list is no longer fetched at boot. 4. **`SubPage` enum moved to `settings-page/types.ts`.** `WebPlayer` imported it from the mobile `SettingsPage` and dragged in the whole page (~78 KB raw). 5. **redux-persist uses localStorage instead of localforage** (~96 KB raw). Each key is copied once from localforage, behind a per-key done flag, and localforage is dynamically imported only for that copy. Search history and library filters carry over; tested by seeding IndexedDB. 6. **SDK: tus-js-client and Node file-type load on demand** (~209 KB raw). Uploads also stop if aborted while tus loads. A changeset is included. 7. **SDK: the storage node list is fetched on first `getSelectedNode()`** instead of in the constructor. That's one fewer request on every web and mobile boot. Concurrent callers share the request, and a failed request is retried. 8. **Trending fetches only the visible lineup** (desktop and mobile web). The other time ranges load on hover or press of their filter, and Underground when its tab opens. 4 fewer requests on load. 9. **The 25 remaining NiceModals use `registerLazyModal`**, appended as a separate block. Expect a small conflict with #14664 in `registerNiceModals.ts`: drop the side-effect imports and keep both lazy blocks. 10. **svgo for SVGs.** It uses mobile's preset and keeps ids, colors, `viewBox` and geometry. The icon SVGs in the entry go from about 385 to 247 KB. Pixel-diffing all 279 SVGs at 8x showed only sub-pixel edge changes. Looser settings visibly moved logos with embedded images, so they're off. 11. **Vendor chunk** for react, react-dom, react-router, redux, react-redux, redux-saga and query-core. It stays cached across deploys and is modulepreloaded. SSR is unchanged. 12. **Preconnect** to the API host and experiments.audius.co. ## Test plan - [x] tsc: web, sdk, mobile. Web vitest (36 files), sdk vitest (21 files), with new tests for the storage migration and the lazy storage node fetch. - [x] Prod build smoke test signed out: no app errors; the queue popover, trending tabs and the Share modal work; no `/health_check` on load; only the week trending request on load. - [ ] Signed in: notification panel (bell hover/open, `?openNotifications`), the mobile-web notifications page, mobile-web settings sub-pages - [ ] Search history and library filters survive the upgrade (localforage → localStorage) - [ ] Track and image upload on web and mobile (lazy tus, lazy storage node selection) - [ ] Open a sample of lazy modals - [ ] Feature flags still apply 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
dylanjeffers
added a commit
that referenced
this pull request
Oct 13, 2026
Mobile startup work from the bundle/startup audit. Everything here is JS-only, so it ships via OTA. The common and `web/src/common` changes also help web. ## Changes - **Drawers and screens load on first open (mobile).** The 54 drawer components in `Drawers.tsx` were object-literal values, which defeats inline requires. They're now thunks called only when a drawer is visible. Every stack screen except `AppTabs` uses `getComponent`, plus `UpdateStack`, `ResetPassword` and `OAuthScreen` in `RootScreen`. About 680 of 7,526 modules (4.7 MB unminified source) are now reachable only through these lazy paths. - **Account load no longer waits on identity (common).** - `recordIPIfNotRecent` and the chat block-list fetches are spawned (detached), so they don't hold `fetchAccountAsync` or its sign-up callers. Block lists wait 5s; only chat send checks read them. - The timezone is sent only when it or the user changes, or weekly. Sign-in and sign-up still send it. - `hasTracks` comes from the account user's `track_count` instead of a `getTracksByUserHandle` request. - **Device token registration is skipped when unchanged (mobile).** The last token and user are cached for 1 week. Still registers on a new install, a different user, or a rotated token. Sign-out and turning off push clear the cache. - **Chat connect and chat list wait for startup interactions (mobile).** Capped at 3s. The unread count still fetches immediately. - **Challenge polling (web + mobile).** - Fast polling (the remote interval) runs only on the rewards page and for 2 minutes after challenge activity: a listen, a claim, the rewards screen focusing, or a challenge drawer opening. - Otherwise it polls at max(5 min, remote interval). - It **pauses** while the tab is hidden or the app is backgrounded. Before, native polled every 30s even during background playback. A poll that came due meanwhile runs on return. - The foreground/background lifecycle actions moved to `web/src/common/store/lifecycle/actions.ts`. - Production `CHALLENGE_REFRESH_INTERVAL_MS` was also raised to 300000 in Optimizely (2026-10-12). - **Smaller persisted query cache (mobile).** Only trending lineup tracks, their owners and the account user are persisted, not every viewed entity. This speeds the restore the splash waits on, and the 5s save. - **Offline download metadata is read 8 files at a time** instead of one by one. - **Optimizely SDK is required when the client is created**, not at module load. ## Not done Moving Android Cast init off the main thread. `react-native-google-cast` calls the synchronous `CastContext.getSharedInstance()` internally, so async init could return null and crash. It needs a library patch and a native release. ## Merge notes - Touches `RootScreen.tsx` and `account/sagas.ts` near #14660's edits, without overlapping them. - Doesn't touch `metro.config.js`, `index.js` or `store/sagas.ts` (#14664). ## Test plan - [x] tsc: mobile, common, web. eslint on changed files. Common account saga test. Mobile jest (9 suites). Minified iOS production bundle builds. - [ ] Open drawers: overflow, add to playlist, premium purchase, add cash, Coinflow, send tokens, queue, feed and trending filters - [ ] Deep links: track, profile, chat. Navigate to settings, upload, edit track, OAuth, reset password. - [ ] Sign up (normal and guest checkout) completes without a delay - [ ] IP and timezone requests fire once, not on every reload - [ ] Artist dashboard and upload entry points still show for artists - [ ] Push: a fresh install receives push. Sign out, sign in as another user, and push goes to the new user. - [ ] Unread chat badge appears on launch; new messages arrive live - [ ] Background playback makes no challenge requests. Completing a listen streak updates the claimable badge within about 15s. Claiming shows confetti. - [ ] Cold start offline: trending and account paint before the splash clears; the offline downloads list fills in - [ ] Feature flags resolve on launch, including the override screen 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # packages/web/src/app/registerNiceModals.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of moving crypto code off the app's startup path. This is the common/web/mobile half; #14661 (SDK), #14662 (hedgehog), #14663 (crypto shim) and #14660 (boot work) are the others.
Problem
Crypto libraries were statically reachable from the app root through shared barrels, not through sagas:
local-storage.ts→@audius/common/services→AudiusBackend.ts/solana.tspulled in web3.js, spl-token, viem and Jupiter.reducers.ts→ thebuy-usdc/purchase-content/uiindex files, which re-export their sagas.AppProviders→WagmiProvider.registerNiceModals.tsstatically registered the purchase, withdraw, wallet and rewards modals.Changes
solanaBackend.ts, which loads on first call.findAssociatedTokenAddressis now async; its 3 callers are updated.audius-backend/solanais no longer re-exported from the services barrel.isValidSolAddressnow checks for a 32-byte base58 key instead of constructing aPublicKey.lazySagashelper waits for a feature's first trigger action, dynamic-imports its sagas, spawns them, and replays the buffered actions.onrampOpened/startRecoveryIfNecessary)WagmiProvider(web).useConnectExternalWalletswas the only wagmi-context consumer; it now passes AppKit's config explicitly.ReownAppKitModalcallsreconnect()when it loads.auth.tsand sign-out import@wagmi/coredynamically.registerLazyModal.CoinflowPurchaseProtectionnow mounts when the browser is idle, in its own Suspense boundary. It used to share a boundary with<Outlet />, so page content waited on the Coinflow chunk.Impact (web entry chunk)
@wagmi/coreand@jup-ag/apiare out, and so is most crypto feature code.@solana/web3.jsvia the SDK'sSolanaRelay/SolanaClient; that's the next step.Test plan
upload/selectors.test.tsfailures also happen on main), web vitest, mobile jest 34/34lazySagastests, including no double reducer run on replay/trendingand/signinrender with no app errors; a lazy modal opens; a dispatched trigger loads its sagas and handles the replayed action🤖 Generated with Claude Code