-
Notifications
You must be signed in to change notification settings - Fork 198
perf: improve app performance with Web Worker for IndexedDB and faster selector memoization #11605
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
perf: improve app performance with Web Worker for IndexedDB and faster selector memoization #11605
Conversation
📝 WalkthroughWalkthrough📝 WalkthroughA performance profiling feature was added: a profiler utility, a UI PerformanceProfiler component, worker-backed profiled storage (IndexedDB and localStorage adapters), selector profiling, and config/translation toggles gated by VITE_FEATURE_PERFORMANCE_PROFILER. Several persist configs now use the worker/localStorage adapters. Changes
Sequence Diagram(s)sequenceDiagram
participant App as App (main thread)
participant UI as PerformanceProfiler UI
participant Prof as profiler (singleton)
participant Worker as indexedDB.worker (Web Worker)
participant DB as IndexedDB / localforage
App->>Prof: profiler.enable() (on flag)
App->>UI: render profiler panel
UI->>Prof: request getSnapshot / enable/disable/export/reset
Prof-->>UI: report metrics
App->>Worker: send getItem/setItem/removeItem (via WorkerStorage)
Worker->>DB: perform localforage op
DB-->>Worker: return result + duration
Worker-->>App: postMessage result (includes duration)
App->>Prof: profiler.trackIndexedDB(key, duration, op)
note right of Prof: Prof aggregates metrics for UI export
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
049891c to
711743c
Compare
- Add performance profiler with visual overlay for tracking IndexedDB, selectors, and asset search performance across browsers - Add VITE_FEATURE_PERFORMANCE_PROFILER feature flag (dev-only) - Add throttle (1000ms) to all 13 redux-persist configs to reduce write frequency and main thread blocking - Move small slices (preferences, localWallet, gridplus, addressBook) to localStorage for faster reads vs IndexedDB - Add createLocalStorageAdapter and createProfiledStorage utilities - Instrument useAssetSearchWorker to track search latency Profiling revealed Firefox IndexedDB reads are ~6x slower than Chrome (9.5s vs 1.5s avg). These changes reduce IndexedDB operations and improve runtime performance, though Firefox's core IndexedDB read performance remains a browser limitation. Co-Authored-By: Claude <noreply@anthropic.com>
Add Web Worker for IndexedDB operations to prevent main thread blocking, especially critical for Firefox where IndexedDB reads were ~6x slower. - Add indexedDB.worker.ts - handles getItem/setItem/removeItem in worker - Add workerStorage.ts - adapter that communicates with worker via postMessage - Update reducer.ts to use worker storage instead of direct localforage Results: - Firefox: Read time reduced from 9,628ms to 109ms (99% improvement) - Chrome: Read time reduced from 1,339ms to 2.18ms (99.8% improvement) The worker moves IndexedDB operations off the main thread, keeping the UI responsive while data loads in the background. Co-Authored-By: Claude <noreply@anthropic.com>
…ation Switch from lodash/isEqual to fast-deep-equal for the resultEqualityCheck in createDeepEqualOutputSelector. fast-deep-equal is 2-7x faster for JSON-like data comparisons, which improves performance for all 157 selectors using deep equality checks across the codebase. Co-Authored-By: Claude <noreply@anthropic.com>
The worker was resolving init() immediately after construction, before the worker script finished loading and localforage was ready. This caused a 5-second timeout fallback on every page load. Now uses a ping/ready handshake to ensure localforage.ready() completes before processing any storage operations. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The profiledStorage module was throwing ReferenceError in Node.js test environment where localStorage is not defined. Added guards to safely return early when localStorage is unavailable. Co-Authored-By: Claude <noreply@anthropic.com>
66be5b7 to
0c35c61
Compare
- Create .skills/ directory with skills/ and commands/ subdirectories - Move git-commit-push skill to .skills/skills/ (portable across projects) - Move weekly-summary and test-agent commands to .skills/commands/ - Create symlinks for both OpenCode and Claude Code discovery: - .opencode/skill -> ../.skills/skills - .opencode/command -> ../.skills/commands - .claude/commands -> ../.skills/commands - .claude/skills/git-commit-push -> ../../.skills/skills/git-commit-push - ShapeShift-specific skills remain in .claude/skills/ - OpenCode auto-discovers .claude/skills/ for project-specific skills
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@src/components/PerformanceProfiler/PerformanceProfiler.tsx`:
- Around line 1-180: The component PerformanceProfiler currently contains
hardcoded user-facing strings and aria labels; import and call the translation
hook (e.g., const t = useTranslate()) at top of the PerformanceProfiler
component and replace every literal string and aria-label/title prop (examples:
"Perf Profiler", "Export", "Reset", "Profiling disabled. Click eye icon to
enable.", aria-labels like 'Toggle profiler', 'Collapse', 'Expand profiler',
button titles, and any inline labels inside JSX) with t('key.path') lookups
using appropriate translation keys; ensure keys are descriptive (e.g.,
perfProfiler.title, perfProfiler.export, perfProfiler.reset,
perfProfiler.disabledMessage, perfProfiler.toggleAria) and update
IconButton/title props to use translated strings as well so all user-facing copy
is localized.
In `@src/components/TradeAssetSearch/hooks/useAssetSearchWorker.ts`:
- Around line 134-139: The Map searchStartTimeRef is never pruned, causing
unbounded growth when requestIdRef advances rapidly; before setting a new entry
in useAssetSearchWorker (where requestIdRef.current is incremented and
searchStartTimeRef.current.set(...) is called), delete any existing entries with
requestId lower than the new nextRequestId or clear the Map entirely, and also
ensure you remove the corresponding entry when a response is ignored (i.e., in
the branch that rejects non-latest request IDs) so that searchStartTimeRef
entries are cleaned up; update the logic around requestIdRef and
searchStartTimeRef to either searchStartTimeRef.current.delete(oldId) for stale
IDs or searchStartTimeRef.current.clear() before setting the new entry.
In `@src/lib/profiledStorage.ts`:
- Line 39: Remove the unused singleton export profiledStorage created by calling
createProfiledStorage(true); locate the statement exporting profiledStorage
(export const profiledStorage = createProfiledStorage(true)) and delete it so
only the factory functions (createLocalStorageAdapter, createProfiledStorage)
remain exported; ensure no other code references profiledStorage after removal
and run tests/grep to confirm no imports rely on that symbol.
🧹 Nitpick comments (8)
.skills/commands/weekly-summary.md (3)
26-34: Consider making the Linux date command more accessible.The Linux date calculation is relegated to a comment, which might be overlooked. Consider providing both commands with clearer platform guidance or adding automatic detection.
♻️ Proposed improvement
```bash -# macOS -START_DATE=$(date -v-7d +%Y-%m-%d) -# Linux fallback: date -d "7 days ago" +%Y-%m-%d +# Detect platform and calculate date from 7 days ago +if [[ "$OSTYPE" == "darwin"* ]]; then + START_DATE=$(date -v-7d +%Y-%m-%d) +else + START_DATE=$(date -d "7 days ago" +%Y-%m-%d) +fi
36-54: Consider adding error handling guidance for gh CLI commands.While Line 44 mentions skipping repositories with errors, the instructions don't demonstrate how to handle failures gracefully. Users might benefit from an example pattern.
Example error handling pattern
# Example: Iterate through repos with error handling for repo in web unchained hdwallet ...; do echo "Querying shapeshift/$repo..." gh pr list --repo "shapeshift/$repo" --state merged \ --search "merged:>=$START_DATE" \ --json number,title,mergedAt,author,labels,body \ --limit 50 2>/dev/null || echo " Skipped (no access or error)" done
79-81: Consider flexibility in bullet count.The requirement for "exactly 6 bullet points" might be too rigid. Some weeks may have more significant work to highlight, while others may have less. Consider allowing flexibility (e.g., "5-7 bullets") while maintaining conciseness.
src/components/PerformanceProfiler/PerformanceProfiler.tsx (1)
49-84: Use color-mode aware tokens for panel styling.The panel uses fixed dark colors (
gray.800,gray.600), which won’t adapt in light mode. Please useuseColorModeValue()for background and border colors.As per coding guidelines, UI should account for light/dark mode.🎨 Example fix
-import { Box, Button, Flex, HStack, IconButton, Text, VStack } from '@chakra-ui/react' +import { Box, Button, Flex, HStack, IconButton, Text, VStack, useColorModeValue } from '@chakra-ui/react' @@ export const PerformanceProfiler = () => { + const bg = useColorModeValue('gray.50', 'gray.800') + const borderColor = useColorModeValue('gray.200', 'gray.600') @@ - bg='gray.800' + bg={bg} @@ - borderColor='gray.600' + borderColor={borderColor}src/lib/performanceProfiler.ts (1)
79-205: Consider capping stored metrics to avoid unbounded memory growth.Arrays (
metrics,indexedDBMetrics.reads/writes,assetSearchMetrics) grow indefinitely when profiling is enabled, which can inflate memory during long dev sessions. A simple ring buffer or max-length trim would keep this bounded.src/lib/storage/indexedDB.worker.ts (1)
112-121: Async handlers are not awaited in the switch statement.The
handleGetItem,handleSetItem, andhandleRemoveItemfunctions are async but their returned promises are not awaited. While this doesn't cause functional issues (each handler manages its own error handling and posts results independently), it means unhandled promise rejections won't propagate to theonmessagehandler's catch block if one existed.This is acceptable since each handler has its own try/catch, but consider adding
voidprefix to make the fire-and-forget intent explicit:♻️ Suggested improvement
switch (data.type) { case 'getItem': - handleGetItem(data.requestId, data.key) + void handleGetItem(data.requestId, data.key) break case 'setItem': - handleSetItem(data.requestId, data.key, data.value) + void handleSetItem(data.requestId, data.key, data.value) break case 'removeItem': - handleRemoveItem(data.requestId, data.key) + void handleRemoveItem(data.requestId, data.key) breaksrc/lib/profiledStorage.ts (1)
33-36:removeItemdoes not track profiling, unlikegetItemandsetItem.For consistency with the other operations, consider adding profiling to
removeItemwhen profiling is enabled:♻️ Suggested fix
- removeItem(key: string): Promise<void> { - return localforage.removeItem(key) - }, + async removeItem(key: string): Promise<void> { + const start = performance.now() + await localforage.removeItem(key) + const duration = performance.now() - start + profiler.trackIndexedDB('write', duration, key) + },src/lib/storage/workerStorage.ts (1)
162-167: Singleton factory ignoresenableProfilingparameter after first instantiation.If
createWorkerStorageis called first withenableProfiling: falseand later withenableProfiling: true(or vice versa), the second call's profiling preference is silently ignored. In this PR, the factory is only called once inreducer.ts, but this could cause subtle bugs if usage expands.♻️ Suggested fix
let workerStorageInstance: WorkerStorage | null = null +let workerStorageProfilingEnabled: boolean | null = null export const createWorkerStorage = (enableProfiling: boolean): WorkerStorage => { if (!workerStorageInstance) { workerStorageInstance = new WorkerStorage(enableProfiling) + workerStorageProfilingEnabled = enableProfiling + } else if (workerStorageProfilingEnabled !== enableProfiling) { + console.warn( + `[WorkerStorage] Profiling setting mismatch: initialized with ${workerStorageProfilingEnabled}, requested ${enableProfiling}`, + ) } return workerStorageInstance }
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (20)
.claude/commands.claude/skills/git-commit-push.env.opencode/command.opencode/skill.skills/commands/test-agent.md.skills/commands/weekly-summary.md.skills/skills/git-commit-push/SKILL.mdpackage.jsonsrc/components/PerformanceProfiler/PerformanceProfiler.tsxsrc/components/PerformanceProfiler/index.tssrc/components/TradeAssetSearch/hooks/useAssetSearchWorker.tssrc/config.tssrc/index.tsxsrc/lib/performanceProfiler.tssrc/lib/profiledStorage.tssrc/lib/storage/indexedDB.worker.tssrc/lib/storage/workerStorage.tssrc/state/reducer.tssrc/state/selector-utils.ts
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx,js,jsx}: Never assume a library is available - always check imports/package.json first
Prefer composition over inheritance
Write self-documenting code with clear variable and function names
Keep functions small and focused on a single responsibility
Avoid deep nesting - use early returns instead
Prefer procedural and easy to understand code
Never expose, log, or commit secrets, API keys, or credentials
Validate all inputs, especially user inputs
Handle errors gracefully with meaningful messages
Don't silently catch and ignore exceptions
Log errors appropriately for debugging
Provide fallback behavior when possible
Use appropriate data structures for the task
Never add code comments unless explicitly requested
When modifying code, do not add comments that reference previous implementations or explain what changed. Comments should only describe the current logic and functionality.
Use meaningful names for branches, variables, and functions
Always runyarn lint --fixandyarn type-checkafter making changes
Avoidletvariable assignments - preferconstwith inline IIFE switch statements or extract to functions for conditional logic
Files:
src/components/PerformanceProfiler/index.tssrc/lib/storage/indexedDB.worker.tssrc/components/PerformanceProfiler/PerformanceProfiler.tsxsrc/state/reducer.tssrc/lib/storage/workerStorage.tssrc/lib/profiledStorage.tssrc/index.tsxsrc/components/TradeAssetSearch/hooks/useAssetSearchWorker.tssrc/lib/performanceProfiler.tssrc/state/selector-utils.tssrc/config.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Avoid useEffect where practical - use it only when necessary and following best practices
Avoid 'any' types - use specific type annotations instead
For default values with user overrides, use computed values (useMemo) instead of useEffect - pattern:userSelected ?? smartDefault ?? fallback
When function parameters are unused due to interface requirements, refactor the interface or implementation to remove them rather than prefixing with underscore
Sanitize data before displaying to prevent XSS
Memoize aggressively - wrap component variables inuseMemoand callbacks inuseCallbackwhere possible
For static JSX icon elements (e.g.,<TbCopy />) that don't depend on state/props, define them as constants outside the component to avoid re-renders instead of using useMemo
Account for light/dark mode usinguseColorModeValuehook
Account for responsive mobile designs in all UI components
When applying styles, use the existing standards and conventions of the codebase
Use Chakra UI components and conventions
All copy/text must use translation keys - never hardcode strings
Use the translation hook:useTranslate()fromreact-polyglot
UseuseFeatureFlag('FlagName')hook to access feature flag values in components
Prefertypeoverinterfacefor type definitions
Use strict typing - avoidany
UseNominaltypes for domain identifiers (e.g.,WalletId,AccountId)
Import types from@shapeshiftoss/caipfor chain/account/asset IDs
UseuseAppSelectorfor Redux state
UseuseAppDispatchfor Redux actions
Memoize expensive computations withuseMemo
Memoize callbacks withuseCallback
**/*.{ts,tsx}: UseResult<T, E>pattern for error handling in swappers and APIs; ALWAYS useOk()andErr()from@sniptt/monads; AVOID throwing within swapper API implementations
ALWAYS use custom error classes from@shapeshiftoss/errorswith meaningful error codes for internationalization and relevant details in error objects
ALWAYS wrap async op...
Files:
src/components/PerformanceProfiler/index.tssrc/lib/storage/indexedDB.worker.tssrc/components/PerformanceProfiler/PerformanceProfiler.tsxsrc/state/reducer.tssrc/lib/storage/workerStorage.tssrc/lib/profiledStorage.tssrc/index.tsxsrc/components/TradeAssetSearch/hooks/useAssetSearchWorker.tssrc/lib/performanceProfiler.tssrc/state/selector-utils.tssrc/config.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/naming-conventions.mdc)
**/*.{js,jsx,ts,tsx}: Use camelCase for variables, functions, and methods with descriptive names that explain the purpose
Use verb prefixes for functions that perform actions (e.g., fetch, validate, execute, update, calculate)
Use UPPER_SNAKE_CASE for constants and configuration values with descriptive names
Usehandleprefix for event handlers with descriptive names in camelCase
Use descriptive boolean variable names withis,has,can,shouldprefixes
Use named exports for components, functions, and utilities instead of default exports
Use descriptive import names and avoid renaming imports unless necessary
Avoid non-descriptive variable names likedata,item,obj, and single-letter variable names except in loops
Avoid abbreviations in names unless they are widely understood
Avoid generic function names likefn,func, orcallback
Files:
src/components/PerformanceProfiler/index.tssrc/lib/storage/indexedDB.worker.tssrc/components/PerformanceProfiler/PerformanceProfiler.tsxsrc/state/reducer.tssrc/lib/storage/workerStorage.tssrc/lib/profiledStorage.tssrc/index.tsxsrc/components/TradeAssetSearch/hooks/useAssetSearchWorker.tssrc/lib/performanceProfiler.tssrc/state/selector-utils.tssrc/config.ts
**/*.{tsx,jsx}
📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)
**/*.{tsx,jsx}: ALWAYS wrap React components in error boundaries and provide user-friendly fallback components with error logging
ALWAYS useuseErrorToasthook for displaying errors with translated error messages and handle different error types appropriatelyUse PascalCase for React component names and match the component name to the file name
Files:
src/components/PerformanceProfiler/PerformanceProfiler.tsxsrc/index.tsx
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/react-best-practices.mdc)
**/*.{jsx,tsx}: ALWAYS useuseMemofor expensive computations, object/array creations, and filtered data
ALWAYS useuseMemofor derived values and computed properties
ALWAYS useuseMemofor conditional values and simple transformations
ALWAYS useuseCallbackfor event handlers and functions passed as props
ALWAYS useuseCallbackfor any function that could be passed as a prop or dependency
ALWAYS include all dependencies inuseEffect,useMemo,useCallbackdependency arrays
NEVER use// eslint-disable-next-line react-hooks/exhaustive-depsunless absolutely necessary, and ALWAYS explain why dependencies are excluded if using eslint disable
ALWAYS use named exports for components; NEVER use default exports for components
KEEP component files under 200 lines when possible; BREAK DOWN large components into smaller, reusable pieces
EXTRACT complex logic into custom hooks
ALWAYS wrap components in error boundaries for production
ALWAYS handle async errors properly in async operations
ALWAYS provide user-friendly error messages in error handling
ALWAYS use virtualization for lists with 100+ items
ALWAYS implement proper key props for list items
ALWAYS lazy load heavy components using React.lazy for code splitting
ALWAYS use Suspense wrapper for lazy loaded components
USE local state for component-level state; LIFT state up when needed across multiple components; USE Context for avoiding prop drilling; USE Redux only for global state shared across multiple places
Wrap components receiving props withmemofor performance optimization
Files:
src/components/PerformanceProfiler/PerformanceProfiler.tsxsrc/index.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/react-best-practices.mdc)
Ensure TypeScript types are explicit and proper; avoid use of
anytype
Files:
src/components/PerformanceProfiler/PerformanceProfiler.tsxsrc/index.tsx
src/state/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
src/state/**/*.{ts,tsx}: UsecreateDeepEqualOutputSelectorfrom@/state/selector-utilsfor deep equality checks
UsecreateCachedSelectorfromre-reselectfor parameterized selectors
Files:
src/state/reducer.tssrc/state/selector-utils.ts
src/config.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Default values always come from environment variables prefixed with
VITE_FEATURE_
Files:
src/config.ts
🧠 Learnings (38)
📚 Learning: 2025-11-24T21:20:04.979Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T21:20:04.979Z
Learning: When creating commits, follow the Git Safety Protocol (see session notes)
Applied to files:
.skills/skills/git-commit-push/SKILL.md.claude/skills/git-commit-push
📚 Learning: 2025-11-24T21:20:57.909Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: .cursor/rules/swapper.mdc:0-0
Timestamp: 2025-11-24T21:20:57.909Z
Learning: Applies to packages/swapper/src/index.ts : Export unique functions and types from packages/swapper/src/index.ts only if needed for external consumption
Applied to files:
src/components/PerformanceProfiler/index.ts
📚 Learning: 2025-11-19T22:20:25.661Z
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10767
File: package.json:324-324
Timestamp: 2025-11-19T22:20:25.661Z
Learning: In shapeshift/web package.json, the resolution "gridplus-sdk/bs58check": "2.1.2" is intentional and must not be removed. It forces gridplus-sdk's transitive bs58check dependency from 4.0.0 down to 2.1.2 because bs58check 4.0.0 breaks legacy address validation (due to bs58 v6.0.0 and noble/hash vs 2.1.2's bs58 v4.0.0 and create-hash).
Applied to files:
package.json
📚 Learning: 2025-08-21T00:49:17.309Z
Learnt from: premiumjibles
Repo: shapeshift/web PR: 10318
File: vite.config.mts:112-114
Timestamp: 2025-08-21T00:49:17.309Z
Learning: The shapeshift/web codebase does not currently contain any Worker or SharedWorker instantiations in the src directory, so concerns about missing `{ type: 'module' }` flags in worker creation are not applicable.
Applied to files:
src/lib/storage/indexedDB.worker.tssrc/lib/storage/workerStorage.ts
📚 Learning: 2025-11-24T21:20:04.979Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T21:20:04.979Z
Learning: Applies to src/config.ts : Default values always come from environment variables prefixed with `VITE_FEATURE_`
Applied to files:
.envsrc/state/reducer.tssrc/index.tsxsrc/config.ts
📚 Learning: 2025-09-09T06:01:24.130Z
Learnt from: 0xApotheosis
Repo: shapeshift/web PR: 10424
File: .env.production:3-3
Timestamp: 2025-09-09T06:01:24.130Z
Learning: In Vite, environment variables have a fallback mechanism where .env.production takes precedence over .env, but variables defined only in .env will still be available in the production environment if not overridden in .env.production.
Applied to files:
.env
📚 Learning: 2026-01-07T15:36:13.236Z
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 11569
File: headers/csps/chains/katana.ts:8-10
Timestamp: 2026-01-07T15:36:13.236Z
Learning: In CSP chain files (headers/csps/chains/*.ts), explicitly filtering undefined environment variables with .filter(Boolean) is unnecessary because serializeCsp() in headers/util.ts filters all falsy values during serialization at line 51: `.map(([k, v]) => [k, v.filter(x => !!x)])`. The direct pattern [env.VITE_*_NODE_URL] is sufficient and preferred for consistency with second-class chain CSP files like plasma.ts and monad.ts.
<!-- </add_learning>
Applied to files:
.envsrc/config.ts
📚 Learning: 2025-12-03T23:19:39.158Z
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 11275
File: headers/csps/chains/plasma.ts:1-10
Timestamp: 2025-12-03T23:19:39.158Z
Learning: For CSP files in headers/csps/chains/, gomesalexandre prefers using Vite's loadEnv() pattern directly to load environment variables (e.g., VITE_PLASMA_NODE_URL, VITE_MONAD_NODE_URL) for consistency with existing second-class chain CSP files, rather than using getConfig() from src/config.ts, even though other parts of the codebase use validated config values.
Applied to files:
.envsrc/config.ts
📚 Learning: 2025-11-24T21:20:04.979Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T21:20:04.979Z
Learning: To add a new feature flag: (1) Add to `FeatureFlags` type in `src/state/slices/preferencesSlice/preferencesSlice.ts`, (2) Add environment variable validation in `src/config.ts`, (3) Add to initial state in `preferencesSlice.ts`, (4) Add to test mock in `src/test/mocks/store.ts`, (5) Set appropriate values in `.env`, `.env.development`, and `.env.production`
Applied to files:
.envsrc/config.ts
📚 Learning: 2025-12-27T16:02:52.792Z
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 11536
File: src/components/MultiHopTrade/components/TradeConfirm/hooks/useTradeExecution.tsx:252-265
Timestamp: 2025-12-27T16:02:52.792Z
Learning: When reviewing bug fixes, especially in shapeshift/web, prefer minimal changes that fix correctness over introducing broader refactors or quality-of-life improvements (e.g., extracting duplicated logic) unless such improvements are essential to the fix. Apply this guideline broadly to TSX files and related components, not just the specific location, to keep changes focused and maintainable.
Applied to files:
src/components/PerformanceProfiler/PerformanceProfiler.tsxsrc/index.tsx
📚 Learning: 2025-11-24T21:20:04.979Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T21:20:04.979Z
Learning: State is persisted with redux-persist (localforage)
Applied to files:
src/state/reducer.ts
📚 Learning: 2025-11-24T21:20:04.979Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T21:20:04.979Z
Learning: Applies to src/state/slices/**/*.ts : Migrations are required when changing persisted state structure (see `src/state/migrations/`)
Applied to files:
src/state/reducer.ts
📚 Learning: 2025-11-24T21:20:57.909Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: .cursor/rules/swapper.mdc:0-0
Timestamp: 2025-11-24T21:20:57.909Z
Learning: Applies to packages/swapper/src/swappers/**/*.ts : Avoid side effects in swap logic; ensure swap methods are deterministic and stateless
Applied to files:
src/state/reducer.tssrc/state/selector-utils.ts
📚 Learning: 2025-11-24T21:20:04.979Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T21:20:04.979Z
Learning: Uses Redux Toolkit with createSlice
Applied to files:
src/state/reducer.ts
📚 Learning: 2025-11-24T21:20:44.637Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: .cursor/rules/react-best-practices.mdc:0-0
Timestamp: 2025-11-24T21:20:44.637Z
Learning: Applies to **/*.{jsx,tsx} : USE local state for component-level state; LIFT state up when needed across multiple components; USE Context for avoiding prop drilling; USE Redux only for global state shared across multiple places
Applied to files:
src/state/reducer.ts
📚 Learning: 2025-11-24T21:20:04.979Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T21:20:04.979Z
Learning: When adding new Redux slices: (1) Create slice in `src/state/slices/<sliceName>/`, (2) Add to `src/state/reducer.ts` (both `slices` and `sliceReducers`), (3) Add to `src/state/store.ts` `clearState()` function, (4) Add persist config if needed, (5) Export selectors from slice using `selectors` property
Applied to files:
src/state/reducer.ts
📚 Learning: 2025-09-04T10:18:34.140Z
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10427
File: src/hooks/useActionCenterSubscribers/useSwapActionSubscriber.tsx:40-40
Timestamp: 2025-09-04T10:18:34.140Z
Learning: In the shapeshift/web codebase, src/state/slices/selectors.ts uses wildcard exports (`export * from './[sliceName]/selectors'`) to re-export all selectors from individual slice selector files, making them available through the barrel import. This means selectors like selectTxByFilter from txHistorySlice/selectors are properly accessible via '@/state/slices/selectors' even though they don't appear in explicit named exports.
Applied to files:
src/state/reducer.tssrc/state/selector-utils.ts
📚 Learning: 2025-11-24T21:20:04.979Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T21:20:04.979Z
Learning: Feature flags are stored in Redux state under `preferences.featureFlags`
Applied to files:
src/state/reducer.ts
📚 Learning: 2025-08-27T13:49:48.668Z
Learnt from: NeOMakinG
Repo: shapeshift/web PR: 10375
File: src/state/migrations/index.ts:214-215
Timestamp: 2025-08-27T13:49:48.668Z
Learning: The ShapeShift web app uses an automated versioning system for Redux Persist migrations. The version is calculated as `Math.max(...Object.keys(clearAssetsMigrations).map(Number))`, which automatically uses the highest migration number as the version. This eliminates the need to manually update persistConfig versions when adding new migrations - the system automatically bumps the version when new migration numbers are added to the migration objects.
Applied to files:
src/state/reducer.ts
📚 Learning: 2025-11-24T21:20:44.637Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: .cursor/rules/react-best-practices.mdc:0-0
Timestamp: 2025-11-24T21:20:44.637Z
Learning: Applies to **/*.{jsx,tsx} : Wrap components receiving props with `memo` for performance optimization
Applied to files:
src/index.tsxsrc/state/selector-utils.ts
📚 Learning: 2025-09-04T17:29:59.479Z
Learnt from: NeOMakinG
Repo: shapeshift/web PR: 10380
File: src/components/TradeAssetSearch/hooks/useGetPopularAssetsQuery.tsx:28-33
Timestamp: 2025-09-04T17:29:59.479Z
Learning: In shapeshift/web, the useGetPopularAssetsQuery function in src/components/TradeAssetSearch/hooks/useGetPopularAssetsQuery.tsx intentionally uses primaryAssets[assetId] instead of falling back to assets[assetId]. The design distributes primary assets across chains by iterating through their related assets and adding the primary asset to each related asset's chain. This ensures primary assets appear in all chains where they have related assets, supporting the grouped asset system.
Applied to files:
src/components/TradeAssetSearch/hooks/useAssetSearchWorker.ts
📚 Learning: 2025-08-14T17:54:32.563Z
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10276
File: src/pages/ThorChainLP/components/ReusableLpStatus/ReusableLpStatus.tsx:97-108
Timestamp: 2025-08-14T17:54:32.563Z
Learning: In ReusableLpStatus component (src/pages/ThorChainLP/components/ReusableLpStatus/ReusableLpStatus.tsx), the txAssets dependency is stable from first render because poolAsset, baseAsset, actionSide, and action are all defined first render, making the current txAssetsStatuses initialization pattern safe without needing useEffect synchronization.
Applied to files:
src/components/TradeAssetSearch/hooks/useAssetSearchWorker.ts
📚 Learning: 2025-11-24T21:20:04.979Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T21:20:04.979Z
Learning: Applies to src/state/**/*.{ts,tsx} : Use `createDeepEqualOutputSelector` from `@/state/selector-utils` for deep equality checks
Applied to files:
src/state/selector-utils.ts
📚 Learning: 2025-11-24T21:20:04.979Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T21:20:04.979Z
Learning: Applies to src/state/**/*.{ts,tsx} : Use `createCachedSelector` from `re-reselect` for parameterized selectors
Applied to files:
src/state/selector-utils.ts
📚 Learning: 2025-11-24T21:20:04.979Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T21:20:04.979Z
Learning: Applies to src/state/slices/**/*.ts : Export selectors from slice using inline `selectors` property
Applied to files:
src/state/selector-utils.ts
📚 Learning: 2025-11-24T21:20:04.979Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T21:20:04.979Z
Learning: Applies to **/*.{ts,tsx} : Use `useAppSelector` for Redux state
Applied to files:
src/state/selector-utils.ts
📚 Learning: 2025-11-24T21:20:04.979Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T21:20:04.979Z
Learning: Applies to **/*.{ts,tsx} : Memoize expensive computations with `useMemo`
Applied to files:
src/state/selector-utils.ts
📚 Learning: 2025-11-24T21:20:04.979Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T21:20:04.979Z
Learning: Applies to **/*.{ts,tsx} : Memoize aggressively - wrap component variables in `useMemo` and callbacks in `useCallback` where possible
Applied to files:
src/state/selector-utils.ts
📚 Learning: 2025-11-24T21:20:44.637Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: .cursor/rules/react-best-practices.mdc:0-0
Timestamp: 2025-11-24T21:20:44.637Z
Learning: Applies to **/*.{jsx,tsx} : ALWAYS use `useMemo` for expensive computations, object/array creations, and filtered data
Applied to files:
src/state/selector-utils.ts
📚 Learning: 2025-08-07T11:20:44.614Z
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 10206
File: src/config.ts:127-128
Timestamp: 2025-08-07T11:20:44.614Z
Learning: gomesalexandre prefers required environment variables without default values in the config file (src/config.ts). They want explicit configuration and fail-fast behavior when environment variables are missing, rather than having fallback defaults.
Applied to files:
src/config.ts
📚 Learning: 2025-11-24T21:20:30.085Z
Learnt from: CR
Repo: shapeshift/web PR: 0
File: .cursor/rules/naming-conventions.mdc:0-0
Timestamp: 2025-11-24T21:20:30.085Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Use UPPER_SNAKE_CASE for constants and configuration values with descriptive names
Applied to files:
src/config.ts
📚 Learning: 2025-11-20T12:00:45.005Z
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 11078
File: src/setupVitest.ts:11-15
Timestamp: 2025-11-20T12:00:45.005Z
Learning: In shapeshift/web, src/setupVitest.ts must redirect 'ethers' to 'ethers5' for shapeshiftoss/hdwallet-trezor (and -trezor-connect), same as ledger and shapeshift-multichain. Removing 'trezor' from the regex causes CI/Vitest failures due to ethers v6 vs v5 API differences.
Applied to files:
src/config.ts
📚 Learning: 2025-07-29T10:22:27.037Z
Learnt from: NeOMakinG
Repo: shapeshift/web PR: 10136
File: src/lib/asset-service/service/encodedRelatedAssetIndex.json:1-1
Timestamp: 2025-07-29T10:22:27.037Z
Learning: PRs with titles starting with "feat: regenerate asset data" are routine daily asset updates that don't need detailed code analysis. Users prefer to skip automated reviews for these maintenance PRs using coderabbitai ignore.
Applied to files:
.skills/commands/weekly-summary.md
📚 Learning: 2025-08-08T15:00:49.887Z
Learnt from: NeOMakinG
Repo: shapeshift/web PR: 10231
File: src/components/AssetSearch/components/AssetList.tsx:2-2
Timestamp: 2025-08-08T15:00:49.887Z
Learning: Project shapeshift/web: NeOMakinG prefers avoiding minor a11y/UI nitpicks (e.g., adding aria-hidden to decorative icons in empty states like src/components/AssetSearch/components/AssetList.tsx) within feature PRs; defer such suggestions to a follow-up instead of blocking the PR.
Applied to files:
.skills/commands/weekly-summary.md
📚 Learning: 2025-11-25T21:43:10.838Z
Learnt from: gomesalexandre
Repo: shapeshift/web PR: 11170
File: patches/@shapeshiftoss+bitcoinjs-lib+7.0.0-shapeshift.0.patch:9-19
Timestamp: 2025-11-25T21:43:10.838Z
Learning: In shapeshift/web, gomesalexandre will not expand PR scope to fix latent bugs in unused API surface (like bitcoinjs-lib patch validation methods) when comprehensive testing proves the actual used code paths work correctly, preferring to avoid costly hdwallet/web verdaccio publish cycles and full regression testing for conceptual issues with zero runtime impact.
Applied to files:
.skills/commands/weekly-summary.md
📚 Learning: 2025-08-08T15:00:22.321Z
Learnt from: NeOMakinG
Repo: shapeshift/web PR: 10231
File: src/components/MultiHopTrade/components/TradeInput/components/HighlightedTokens.tsx:14-14
Timestamp: 2025-08-08T15:00:22.321Z
Learning: In shapeshift/web reviews for NeOMakinG, avoid nitpicks to change deep-relative imports to '@/…' alias paths within feature/non-refactor PRs; defer such style-only changes to a dedicated follow-up refactor unless they fix an issue.
Applied to files:
.skills/commands/weekly-summary.md
📚 Learning: 2025-07-24T21:05:13.642Z
Learnt from: 0xApotheosis
Repo: shapeshift/web PR: 10073
File: src/components/Layout/Header/ActionCenter/components/Details/ClaimDetails.tsx:10-11
Timestamp: 2025-07-24T21:05:13.642Z
Learning: In the ShapeShift web repository, translation workflow follows a two-step process: 1) First PR adds only English translations to src/assets/translations/en/main.json, 2) Globalization team handles follow-up PRs to add keys to remaining language files (de, es, fr, id, ja, ko, pt, ru, tr, uk, zh). Don't suggest verifying all locale files simultaneously during initial feature PRs.
Applied to files:
.skills/commands/weekly-summary.md
📚 Learning: 2025-10-21T23:21:22.304Z
Learnt from: premiumjibles
Repo: shapeshift/web PR: 10759
File: src/components/Modals/Send/hooks/useFormSend/useFormSend.tsx:41-50
Timestamp: 2025-10-21T23:21:22.304Z
Learning: In the shapeshift/web repository, the translation workflow follows an "English-first" approach: English translations in src/assets/translations/en/main.json are updated first in PRs, and translations for the other supported languages (de, es, fr, id, ja, ko, pt, ru, tr, uk, zh) are updated "after the fact" in follow-up work. Temporary mismatches between English and other language translation keys/formats during active development are expected and acceptable.
<!--
Applied to files:
.skills/commands/weekly-summary.md
🧬 Code graph analysis (7)
src/components/PerformanceProfiler/PerformanceProfiler.tsx (2)
src/components/PerformanceProfiler/index.ts (1)
PerformanceProfiler(1-1)src/lib/performanceProfiler.ts (3)
PerformanceReport(25-57)isEnabled(97-99)profiler(273-273)
src/state/reducer.ts (3)
src/config.ts (1)
getConfig(268-272)src/lib/storage/workerStorage.ts (1)
createWorkerStorage(162-167)src/lib/profiledStorage.ts (1)
createLocalStorageAdapter(74-100)
src/lib/storage/workerStorage.ts (2)
src/lib/storage/indexedDB.worker.ts (2)
IndexedDBWorkerOutboundMessage(28-33)IndexedDBWorkerInboundMessage(22-26)src/lib/performanceProfiler.ts (1)
profiler(273-273)
src/lib/profiledStorage.ts (1)
src/lib/performanceProfiler.ts (1)
profiler(273-273)
src/index.tsx (3)
src/lib/performanceProfiler.ts (1)
profiler(273-273)src/components/PerformanceProfiler/PerformanceProfiler.tsx (1)
PerformanceProfiler(10-186)src/components/PerformanceProfiler/index.ts (1)
PerformanceProfiler(1-1)
src/components/TradeAssetSearch/hooks/useAssetSearchWorker.ts (1)
src/lib/performanceProfiler.ts (1)
profiler(273-273)
src/state/selector-utils.ts (1)
src/lib/performanceProfiler.ts (1)
profiler(273-273)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Call / Static
🔇 Additional comments (32)
.claude/commands (1)
1-1: Simple path reference.This file establishes a bridge to the centralized commands directory, consistent with the pattern used in
.opencode/command..skills/commands/weekly-summary.md (5)
1-7: LGTM!Clear frontmatter and overview that establishes the document's purpose.
9-23: LGTM!Comprehensive list of repositories with clear descriptions.
56-67: LGTM!Excellent guidance on determining production status for features. The emphasis on stakeholder value is appropriate.
93-115: LGTM!Clear and well-structured output template with appropriate placeholders.
117-129: LGTM!Excellent practical guidance that aligns with project conventions. The filtering of automated "regenerate asset data" PRs matches established practices, and the example bullet point effectively demonstrates the expected format with all required elements.
.opencode/command (1)
1-1: The path../.skills/commandsis a valid symlink target that exists and contains command definitions (test-agent.mdandweekly-summary.md). No action required..opencode/skill (1)
1-1: The skills directory reference is valid. The path../.skills/skillscorrectly resolves to a functional directory containing skill definitions (verified:.skills/skills/git-commit-push/SKILL.mdexists). The.opencode/skillsymlink properly references this location..claude/skills/git-commit-push (1)
1-1: The path../../.skills/skills/git-commit-pushresolves correctly to a valid skill definition. No action needed..skills/skills/git-commit-push/SKILL.md (1)
1-50: LGTM!The skill documentation provides clear, concise instructions for the git commit-push workflow with appropriate safety guards for sensitive files.
.env (1)
67-68: LGTM!The new feature flag follows established naming conventions and is appropriately defaulted to
falsefor a dev-only profiling tool.src/components/PerformanceProfiler/index.ts (1)
1-1: LGTM!Clean barrel export following project conventions with named exports.
src/index.tsx (2)
21-33: LGTM!The profiler integration is well-structured:
- Feature flag controls both the profiler singleton activation and UI rendering
- Module-level initialization ensures profiling captures metrics from app startup
- Follows established config access patterns via
getConfig()
232-232: Conditional rendering is appropriate here.Placing
PerformanceProfilerinsideAppProvidersbut afterAppallows the profiler UI to overlay the app while accessing any context providers if needed in the future.package.json (1)
165-165: Good choice for performance-critical equality checks.
fast-deep-equalis a well-established, lightweight library that's significantly faster than lodash'sisEqualfor deep comparisons. It's already integrated into the codebase insrc/state/selector-utils.tsto power thecreateDeepEqualOutputSelectorfor selector memoization, which aligns with the project's performance optimization strategy.src/lib/performanceProfiler.ts (1)
61-218: Guard browser-only APIs for non-DOM environments.
navigator,document,Blob, andURLare accessed without guards. If this module is imported in non-browser tests or SSR, it can throw. Add runtime checks and early returns to keep it safe.🛡️ Suggested guard pattern
const getBrowserInfo = (): string => { - const ua = navigator.userAgent + if (typeof navigator === 'undefined') return 'Unknown' + const ua = navigator.userAgent @@ exportJSON(): void { + if (typeof document === 'undefined' || typeof Blob === 'undefined' || typeof URL === 'undefined') + return const report = this.getSnapshot()⛔ Skipped due to learnings
Learnt from: NeOMakinG Repo: shapeshift/web PR: 10271 File: src/components/AssetHeader/AssetActions.tsx:20-20 Timestamp: 2025-08-13T13:16:11.290Z Learning: Project shapeshift/web: NeOMakinG confirmed that the project does not support server-side rendering (SSR), so concerns about guarding navigator/window access for SSR safety are not applicable to this codebase.Learnt from: gomesalexandre Repo: shapeshift/web PR: 10912 File: src/context/WalletProvider/NewWalletViews/NewWalletViewsSwitch.tsx:290-290 Timestamp: 2025-11-05T23:37:30.632Z Learning: In the ShapeShift web codebase, `isMobile` imported from '@/lib/globals' is a module-level constant (defined as `export const isMobile = Boolean(window?.isShapeShiftMobile)`) that is evaluated once at module load time. It is a stable reference that does not need to be included in useCallback/useMemo/useEffect dependency arrays.src/state/selector-utils.ts (1)
1-9:fast-deep-equaldependency is present and semantically safe for all selector outputs.The dependency
"fast-deep-equal": "^3.1.3"is confirmed in package.json. Verification across 100+ selector usages shows all return plain objects, arrays, and primitive values—no Maps, Sets, Date, RegExp, or circular structures. Redux state inherently serializes to plain data, making fast-deep-equal semantically equivalent to lodash/isEqual for this codebase's use cases.src/config.ts (1)
255-255: The feature flag is properly wired.VITE_FEATURE_PERFORMANCE_PROFILERis correctly added tosrc/config.tsand is being used insrc/index.tsxandsrc/state/reducer.tsviagetConfig()to conditionally enable performance profiling at startup. This is a system configuration flag, not a user preference, so it does not require entries in the preferences slice, test mocks, or committed.envfiles.src/components/PerformanceProfiler/PerformanceProfiler.tsx (1)
10-185: The component is already protected by the outer ErrorBoundary in AppProviders. Since PerformanceProfiler is a pure DOM manipulation component with no suspense boundaries or async operations, an additional direct wrapper is unnecessary. Per team practice, error boundaries are placed strategically around features that require them (e.g., async operations, suspense boundaries), rather than wrapping every component.src/state/reducer.ts (4)
77-81: Module-levelgetConfig()call is evaluated at import time.Calling
getConfig()at module scope means this runs when the module is first imported, before any React lifecycle or provider setup. This is typically fine sincegetConfig()reads fromimport.meta.env, but verify that the profiler flag is available at this point in the application bootstrap sequence.The storage adapter instantiation and throttle constant look good—1000ms throttle is appropriate for reducing IndexedDB write pressure during rapid updates like WebSocket price streams.
83-88: Preferences slice now uses localStorage instead of IndexedDB.This is a sensible optimization for a small, frequently-accessed slice. The
blacklist: ['featureFlags']ensures sensitive/volatile data isn't persisted to localStorage.
122-128: LocalWallet slice migration to localStorage maintains existing migration support.Good that
localWalletMigrationsis still applied—this ensures backward compatibility when reading data previously stored in IndexedDB. Users upgrading will have their wallet data migrated correctly.
168-181: GridPlus and AddressBook slices correctly use localStorage adapter.Both are small slices appropriate for localStorage. AddressBook retains its migration support while GridPlus starts at version 0. The consistent throttle application across all configs is good.
src/lib/storage/indexedDB.worker.ts (3)
9-20: Readiness synchronization pattern is well-implemented.The singleton
readyPromisepattern ensureslocalforage.ready()is called exactly once, preventing race conditions when multiple operations arrive before initialization completes.
22-33: Message types are well-defined with discriminated unions.The type definitions provide clear contracts for worker communication. Each operation type includes
requestIdfor correlation, and outbound messages includedurationfor profiling.
35-55: Handler implementations are consistent and robust.Each handler properly:
- Captures timing with
performance.now()- Wraps operations in try/catch
- Posts typed results back to the main thread
- Extracts error messages safely with the
instanceof Errorchecksrc/lib/profiledStorage.ts (2)
43-56: localStorage adapter correctly handles edge cases.Good defensive programming:
- Checks
isLocalStorageAvailablebefore operations- Handles
nullitems explicitly- Catches JSON parse errors and returns
nullgracefullyNote: Using
trackIndexedDBwithlocalStorage:${key}prefix is functional but slightly misleading. Consider renaming the profiler method or using a separate tracking method for localStorage if this becomes confusing in profiler output.
74-100:createLocalStorageAdaptercorrectly branches on profiling flag.The non-profiled path returns a minimal adapter without timing overhead, which is the right optimization. The implementation matches the relevant code snippet from
src/lib/storage/workerStorage.ts.src/lib/storage/workerStorage.ts (4)
33-70: Worker initialization with ping/ready handshake and timeout fallback is well-designed.The pattern handles several edge cases gracefully:
pingtriggersensureReady()in the worker before respondingready- 5-second timeout prevents indefinite hangs if the worker fails to initialize
- Errors during worker creation are caught and resolve (rather than reject) to enable fallback
- The
resolvedflag prevents double-resolutionThis ensures the app remains functional even when Web Workers are unavailable or slow to start.
115-128:getItemfallback to localforage is a good resilience pattern.Dynamic import of
localforagein the fallback path keeps the main bundle smaller when the worker is functional. The warning log helps with debugging.
72-97: Message handler correctly routes responses and tracks profiling.The handler:
- Ignores duplicate
readymessages- Cleans up pending requests after processing
- Rejects on error messages
- Conditionally tracks profiling only when enabled and duration exists
- Routes to correct resolve based on message type
The early return on missing
pendingrequest (line 77) handles edge cases where responses arrive for already-timed-out or cancelled requests.
99-113:sendMessagecorrectly validates worker availability.The null check on line 105 with immediate rejection prevents the operation from being added to
pendingRequestswhen the worker isn't available, avoiding memory leaks from orphaned pending requests.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
6cad002 to
50dfbfa
Compare
…PerformanceProfiler - Fix unbounded growth of searchStartTimeRef Map by clearing before setting new entry and deleting stale entries when requests are ignored - Remove unused profiledStorage singleton export from profiledStorage.ts - Internationalize PerformanceProfiler component with translation keys
premiumjibles
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Played around with this on firefox + chrome to verify speed looks good and persistence works.
Code also looks all good. Added a couple of comments for extra robustness improvements if you think they're worthwhile but non-blocking
- Add 10s timeout to prevent hanging if worker dies or IndexedDB gets stuck - Add per-key locking to prevent race conditions on concurrent writes - Clear timeout when response received to prevent memory leaks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/lib/storage/workerStorage.ts`:
- Around line 54-56: Add a disableWorker method that terminates the Worker,
clears any pending request map/handlers, sets this.worker = null and marks it
unavailable; call disableWorker from the worker.onerror handler (replace the
current console.error) and from any operation timeout handlers so the worker is
fully disabled on error/timeout; update sendMessage usage inside getItem,
setItem, and removeItem to be wrapped in try-catch so failures throw to the
catch which then falls back to direct localforage access (preserving the
existing fallback checks in getItem/setItem/removeItem); ensure any pending
Promises are rejected/cleared when disabling the worker so subsequent calls hit
the fallback logic.
🧹 Nitpick comments (3)
src/lib/storage/workerStorage.ts (3)
1-5: Remove the new header comment block.Guidelines discourage adding code comments; the file/class name already conveys intent.
🧹 Proposed change
-/** - * Storage adapter that uses a Web Worker for IndexedDB operations. - * This keeps IndexedDB operations off the main thread, preventing UI blocking - * especially in Firefox where IndexedDB is significantly slower. - */ -As per coding guidelines, please avoid adding code comments unless explicitly requested.
15-23: Prefer a string enum for operation type.TS guidelines favor enums for fixed status values; replacing the
'read' | 'write'union keeps this aligned and makes future refactors safer.♻️ Proposed change
+enum WorkerStorageOperationType { + Read = 'read', + Write = 'write', +} + type PendingRequest = { resolve: (value: unknown) => void reject: (error: Error) => void key: string - type: 'read' | 'write' + type: WorkerStorageOperationType timeoutId: ReturnType<typeof setTimeout> }As per coding guidelines, use string enums for fixed status values.
212-219: Singleton pattern is safe in practice but unclear—consider clarifying or removing unused parameter.The
enableProfilingparameter will always be the same value since it derives from a static environment variable (VITE_FEATURE_PERFORMANCE_PROFILER) evaluated at startup via memoizedgetConfig(). The single call site atsrc/state/reducer.ts:78means the singleton never receives different parameter values. However, the parameter is effectively ignored after the first call, which could confuse future maintainers. Consider either removing the parameter or documenting that it only applies to the first initialization.
| this.worker.onerror = err => { | ||
| console.error('[WorkerStorage] Worker error:', err) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cat -n src/lib/storage/workerStorage.tsRepository: shapeshift/web
Length of output: 7678
🏁 Script executed:
rg -i "from.*@shapeshiftoss/errors|import.*Error" src/lib/storage/ | head -20Repository: shapeshift/web
Length of output: 40
🏁 Script executed:
rg -i "@shapeshiftoss/errors" --type ts --type tsx | head -10Repository: shapeshift/web
Length of output: 85
🏁 Script executed:
rg "@shapeshiftoss/errors" -t ts | head -10Repository: shapeshift/web
Length of output: 40
🏁 Script executed:
rg "try.*catch|\.catch\(" src/lib/storage/ -A 2 | head -30Repository: shapeshift/web
Length of output: 40
Add error handling to disable the worker and fall back to localforage on worker errors or timeouts.
The worker.onerror handler and operation timeouts currently reject the pending request but never disable the worker. This leaves the worker marked as "available" for subsequent calls, which then timeout again without ever triggering the existing fallback logic. When a worker crashes or hangs, operations can become permanently stuck.
Implement a disableWorker method that terminates the worker, clears pending requests, and sets this.worker = null. Call it from:
- The
worker.onerrorhandler - Operation timeout handlers
- Wrap
sendMessagecalls in try-catch blocks ingetItem,setItem, andremoveItemto catch failures and fall back to direct localforage access
This ensures the existing fallback checks at lines 153–157, 173–177, and 195–199 are reachable when the worker fails.
🤖 Prompt for AI Agents
In `@src/lib/storage/workerStorage.ts` around lines 54 - 56, Add a disableWorker
method that terminates the Worker, clears any pending request map/handlers, sets
this.worker = null and marks it unavailable; call disableWorker from the
worker.onerror handler (replace the current console.error) and from any
operation timeout handlers so the worker is fully disabled on error/timeout;
update sendMessage usage inside getItem, setItem, and removeItem to be wrapped
in try-catch so failures throw to the catch which then falls back to direct
localforage access (preserving the existing fallback checks in
getItem/setItem/removeItem); ensure any pending Promises are rejected/cleared
when disabling the worker so subsequent calls hit the fallback logic.
Description
This PR delivers significant performance improvements for the ShapeShift web app across all browsers, with particularly impactful gains for Firefox users. The changes address three main bottlenecks: IndexedDB operations, redux-persist write frequency, and Redux selector memoization.
UAT:
Some jams of this branch (left) vs develop (right) on a simulated 4x CPU throttle:
Key Improvements
Web Worker for IndexedDB Operations
Redux-Persist Throttling
localStorage Migration for Small Slices
preferences,localWallet,gridplus,addressBookfast-deep-equal for Selector Memoization
lodash/isEqualwithfast-deep-equalincreateDeepEqualOutputSelectorPerformance Profiling Infrastructure (dev-only)
VITE_FEATURE_PERFORMANCE_PROFILERflagPerformance Metrics
User Experience: UI Responsiveness
The main improvement is eliminating "jank" (UI freezing) caused by IndexedDB operations blocking the main thread.
These freezes occurred in chunks (e.g., 50-200ms at a time) causing stuttering, dropped frames, and unresponsive buttons/inputs.
Page Load: Time to Interactive
Ongoing Responsiveness: State Persistence
When you interact with the app (change settings, get quotes, etc.), state is saved to IndexedDB. Previously this caused micro-freezes.
Selector Performance (fast-deep-equal)
This reduces CPU time spent comparing Redux state, making all UI updates snappier.
Trade-offs
Why Firefox Benefits Most
Chrome's IndexedDB is highly optimized and rarely blocks for long. Firefox's IndexedDB is significantly slower - operations that take 2ms in Chrome can take 50-200ms in Firefox. By moving these operations to a Web Worker, Firefox users see the biggest improvement: from a janky, freezing experience to a smooth, responsive app.
How to Verify
Quick test: Open the app in Firefox on develop branch, then on this branch. Notice:
DevTools test:
Issue (if applicable)
N/A - Performance improvement initiative
Risk
Low-Medium Risk - These changes affect core state persistence and selector infrastructure, but:
All wallet connections and state persistence. The
actionslice (pending swaps, limit orders, bridge claims) remains persisted via IndexedDB/Worker to ensure user notifications survive page refresh.Testing
Engineering
To test locally:
yarn devand open in browserVITE_FEATURE_PERFORMANCE_PROFILER=truein.envOperations
Tested by ops here: https://discord.com/channels/554694662431178782/1460762426072109056/1460822148234088632
The performance profiler is behind
VITE_FEATURE_PERFORMANCE_PROFILERflag (disabled in production). All other changes are infrastructure improvements that don't change user-facing behavior.Functional testing:
Screenshots (if applicable)
N/A
Summary by CodeRabbit
New Features
Refactor
Chores
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.