Skip to content

Conversation

@0xApotheosis
Copy link
Member

@0xApotheosis 0xApotheosis commented Jan 9, 2026

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:

Screenshot 2026-01-14 at 9 25 23 am

Some jams of this branch (left) vs develop (right) on a simulated 4x CPU throttle:

Key Improvements

  1. Web Worker for IndexedDB Operations

    • Moves all IndexedDB read/write operations to a dedicated Web Worker
    • Prevents main thread blocking during state persistence
    • Critical for Firefox where IndexedDB is significantly slower
  2. Redux-Persist Throttling

    • Added 1000ms throttle to all 14 persist configurations
    • Reduces IndexedDB write frequency during rapid state updates
    • Particularly impactful during WebSocket price updates and user interactions
  3. localStorage Migration for Small Slices

    • Moved 4 small slices to localStorage: preferences, localWallet, gridplus, addressBook
    • localStorage is synchronous and much faster than IndexedDB for small data
    • Remaining slices use IndexedDB via Web Worker for large data
  4. fast-deep-equal for Selector Memoization

    • Replaced lodash/isEqual with fast-deep-equal in createDeepEqualOutputSelector
    • 2-7x faster equality checks for JSON-like Redux state
    • Benefits all 157 selectors using deep equality across the codebase
  5. Performance Profiling Infrastructure (dev-only)

    • Visual overlay showing real-time IndexedDB, selector, and search metrics
    • Enables browser comparison testing
    • Controlled via VITE_FEATURE_PERFORMANCE_PROFILER flag

Performance Metrics

User Experience: UI Responsiveness

The main improvement is eliminating "jank" (UI freezing) caused by IndexedDB operations blocking the main thread.

Browser Before (develop) After (feature) User Experience
Firefox UI freezes for ~9.6 seconds total during page load as IndexedDB operations block the main thread UI stays responsive - IndexedDB work happens in background No more frozen UI
Chrome UI freezes for ~1.3 seconds total during page load UI stays responsive - IndexedDB work happens in background No more frozen UI

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

Browser Before After Improvement
Firefox 1,707ms 919ms 46% faster - app is usable almost 1 second sooner
Chrome ~500ms ~500ms Already fast, minimal change

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.

Scenario Before After
Rapid price updates (WebSocket) UI stutters as each update triggers IndexedDB write Smooth - writes throttled to 1/second, off main thread
Searching assets Brief freeze on each keystroke Smooth - no main thread blocking
Switching between pages Momentary freeze as state persists Smooth - persistence happens in background

Selector Performance (fast-deep-equal)

Metric Improvement
Deep equality checks 2-7x faster
Affected selectors 157 selectors across the codebase

This reduces CPU time spent comparing Redux state, making all UI updates snappier.

Trade-offs

Metric Before After Notes
Memory usage (Chrome) 518 MB 797 MB Higher due to Web Worker + profiler. Acceptable trade-off for responsiveness.

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:

  • Faster initial load
  • No stuttering when navigating
  • Smooth scrolling in asset lists
  • Responsive button clicks during state updates

DevTools test:

  1. Open Firefox DevTools → Performance tab
  2. Record while navigating the app
  3. On develop: Red "Long Task" bars during IndexedDB operations (UI was frozen)
  4. On this branch: Clean timeline, no long tasks from storage

Issue (if applicable)

N/A - Performance improvement initiative

Risk

Low-Medium Risk - These changes affect core state persistence and selector infrastructure, but:

  • Web Worker has automatic fallback to direct localforage if worker fails
  • fast-deep-equal handles all JSON-serializable data identically to lodash/isEqual
  • Profiling infrastructure is behind a feature flag (dev-only by default)
  • Throttling only affects persist frequency, not data integrity

What protocols, transaction types, wallets or contract interactions might be affected by this PR?

All wallet connections and state persistence. The action slice (pending swaps, limit orders, bridge claims) remains persisted via IndexedDB/Worker to ensure user notifications survive page refresh.

Testing

Engineering

To test locally:

  1. yarn dev and open in browser
  2. Enable profiler: Set VITE_FEATURE_PERFORMANCE_PROFILER=true in .env
  3. Verify profiler overlay appears in bottom-right corner
  4. Test state persistence: change preferences, refresh page, verify they persist
  5. Test trade flow: search assets, get quotes

Operations

Tested by ops here: https://discord.com/channels/554694662431178782/1460762426072109056/1460822148234088632

  • 🏁 My feature is behind a flag and doesn't require operations testing (yet)

The performance profiler is behind VITE_FEATURE_PERFORMANCE_PROFILER flag (disabled in production). All other changes are infrastructure improvements that don't change user-facing behavior.

Functional testing:

  • Verify app loads and is responsive
  • Test trade/swap flow works correctly
  • Verify wallet connections work
  • Check that settings/preferences persist after refresh

Screenshots (if applicable)

N/A

Summary by CodeRabbit

  • New Features

    • Interactive performance profiler UI with real-time metrics, toggle/expand controls, export and reset actions
    • App-level toggle and env flag to enable the profiler
  • Refactor

    • Background-worker backed storage and throttled persistence for improved responsiveness when profiling is enabled
    • Selectors and asset search instrumented for optional timing telemetry
  • Chores

    • Added fast-deep-equal for deep equality comparisons
  • Documentation

    • Added UI translation strings for the performance profiler

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 9, 2026

📝 Walkthrough

Walkthrough

📝 Walkthrough

A 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

Cohort / File(s) Summary
Config / Env
.env, src/config.ts
Added VITE_FEATURE_PERFORMANCE_PROFILER flag and small validator formatting changes; .env gets VITE_FEATURE_PERFORMANCE_PROFILER=false entry.
Core profiler library
src/lib/performanceProfiler.ts
New PerformanceProfiler class, exported profiler singleton and PerformanceReport type.
UI component
src/components/PerformanceProfiler/...
New PerformanceProfiler React component and index export; conditionally mounted in src/index.tsx when feature flag enabled.
Storage: worker & adapters
src/lib/storage/indexedDB.worker.ts, src/lib/storage/workerStorage.ts, src/lib/profiledStorage.ts
New worker for IndexedDB operations, a WorkerStorage adapter with fallbacks and profiling hooks, and profiled/localStorage adapter factory.
State persistence changes
src/state/reducer.ts
Persist configs now use worker-backed storage or localStorage adapter and add throttling (PERSIST_THROTTLE_MS).
Selectors / search profiling
src/state/selector-utils.ts, src/components/TradeAssetSearch/hooks/useAssetSearchWorker.ts
Replaced deep-equal with fast-deep-equal, added createProfiledSelector, and added per-request timing + profiler.trackAssetSearch in asset search worker hook.
Worker-backed asset search & translations
src/assets/translations/en/main.json
Added perf profiler translation keys.
Tooling
package.json
Added dependency fast-deep-equal.
Entrypoint
src/index.tsx
Conditionally enable profiler and render UI when feature flag is true.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

high risk

Suggested reviewers

  • gomesalexandre
  • NeOMakinG
  • premiumjibles

Poem

"I hopped through code with whiskers keen,
I timed each hop and counted green,
A profiler nest I built today,
To watch the bytes and time their play.
🐇 — Happy profiling!"

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main performance improvements: Web Worker for IndexedDB and faster selector memoization via fast-deep-equal. It directly reflects the core changes in the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/firefox-performance-profiling-and-fixes

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@0xApotheosis 0xApotheosis force-pushed the feat/firefox-performance-profiling-and-fixes branch from 049891c to 711743c Compare January 13, 2026 22:18
0xApotheosis and others added 5 commits January 16, 2026 15:34
- 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>
@0xApotheosis 0xApotheosis force-pushed the feat/firefox-performance-profiling-and-fixes branch from 66be5b7 to 0c35c61 Compare January 16, 2026 04:35
- 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
@0xApotheosis 0xApotheosis marked this pull request as ready for review January 16, 2026 06:06
@0xApotheosis 0xApotheosis requested a review from a team as a code owner January 16, 2026 06:06
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 use useColorModeValue() for background and border colors.

🎨 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}
As per coding guidelines, UI should account for light/dark mode.
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, and handleRemoveItem functions 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 the onmessage handler's catch block if one existed.

This is acceptable since each handler has its own try/catch, but consider adding void prefix 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)
       break
src/lib/profiledStorage.ts (1)

33-36: removeItem does not track profiling, unlike getItem and setItem.

For consistency with the other operations, consider adding profiling to removeItem when 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 ignores enableProfiling parameter after first instantiation.

If createWorkerStorage is called first with enableProfiling: false and later with enableProfiling: true (or vice versa), the second call's profiling preference is silently ignored. In this PR, the factory is only called once in reducer.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.

📥 Commits

Reviewing files that changed from the base of the PR and between c088171 and 50dfbfa.

⛔ Files ignored due to path filters (1)
  • yarn.lock is 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.md
  • package.json
  • src/components/PerformanceProfiler/PerformanceProfiler.tsx
  • src/components/PerformanceProfiler/index.ts
  • src/components/TradeAssetSearch/hooks/useAssetSearchWorker.ts
  • src/config.ts
  • src/index.tsx
  • src/lib/performanceProfiler.ts
  • src/lib/profiledStorage.ts
  • src/lib/storage/indexedDB.worker.ts
  • src/lib/storage/workerStorage.ts
  • src/state/reducer.ts
  • src/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 run yarn lint --fix and yarn type-check after making changes
Avoid let variable assignments - prefer const with inline IIFE switch statements or extract to functions for conditional logic

Files:

  • src/components/PerformanceProfiler/index.ts
  • src/lib/storage/indexedDB.worker.ts
  • src/components/PerformanceProfiler/PerformanceProfiler.tsx
  • src/state/reducer.ts
  • src/lib/storage/workerStorage.ts
  • src/lib/profiledStorage.ts
  • src/index.tsx
  • src/components/TradeAssetSearch/hooks/useAssetSearchWorker.ts
  • src/lib/performanceProfiler.ts
  • src/state/selector-utils.ts
  • src/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 in useMemo and callbacks in useCallback where 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 using useColorModeValue hook
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() from react-polyglot
Use useFeatureFlag('FlagName') hook to access feature flag values in components
Prefer type over interface for type definitions
Use strict typing - avoid any
Use Nominal types for domain identifiers (e.g., WalletId, AccountId)
Import types from @shapeshiftoss/caip for chain/account/asset IDs
Use useAppSelector for Redux state
Use useAppDispatch for Redux actions
Memoize expensive computations with useMemo
Memoize callbacks with useCallback

**/*.{ts,tsx}: Use Result<T, E> pattern for error handling in swappers and APIs; ALWAYS use Ok() and Err() from @sniptt/monads; AVOID throwing within swapper API implementations
ALWAYS use custom error classes from @shapeshiftoss/errors with meaningful error codes for internationalization and relevant details in error objects
ALWAYS wrap async op...

Files:

  • src/components/PerformanceProfiler/index.ts
  • src/lib/storage/indexedDB.worker.ts
  • src/components/PerformanceProfiler/PerformanceProfiler.tsx
  • src/state/reducer.ts
  • src/lib/storage/workerStorage.ts
  • src/lib/profiledStorage.ts
  • src/index.tsx
  • src/components/TradeAssetSearch/hooks/useAssetSearchWorker.ts
  • src/lib/performanceProfiler.ts
  • src/state/selector-utils.ts
  • src/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
Use handle prefix for event handlers with descriptive names in camelCase
Use descriptive boolean variable names with is, has, can, should prefixes
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 like data, item, obj, and single-letter variable names except in loops
Avoid abbreviations in names unless they are widely understood
Avoid generic function names like fn, func, or callback

Files:

  • src/components/PerformanceProfiler/index.ts
  • src/lib/storage/indexedDB.worker.ts
  • src/components/PerformanceProfiler/PerformanceProfiler.tsx
  • src/state/reducer.ts
  • src/lib/storage/workerStorage.ts
  • src/lib/profiledStorage.ts
  • src/index.tsx
  • src/components/TradeAssetSearch/hooks/useAssetSearchWorker.ts
  • src/lib/performanceProfiler.ts
  • src/state/selector-utils.ts
  • src/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 use useErrorToast hook for displaying errors with translated error messages and handle different error types appropriately

Use PascalCase for React component names and match the component name to the file name

Files:

  • src/components/PerformanceProfiler/PerformanceProfiler.tsx
  • src/index.tsx
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/react-best-practices.mdc)

**/*.{jsx,tsx}: ALWAYS use useMemo for expensive computations, object/array creations, and filtered data
ALWAYS use useMemo for derived values and computed properties
ALWAYS use useMemo for conditional values and simple transformations
ALWAYS use useCallback for event handlers and functions passed as props
ALWAYS use useCallback for any function that could be passed as a prop or dependency
ALWAYS include all dependencies in useEffect, useMemo, useCallback dependency arrays
NEVER use // eslint-disable-next-line react-hooks/exhaustive-deps unless 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 with memo for performance optimization

Files:

  • src/components/PerformanceProfiler/PerformanceProfiler.tsx
  • src/index.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/react-best-practices.mdc)

Ensure TypeScript types are explicit and proper; avoid use of any type

Files:

  • src/components/PerformanceProfiler/PerformanceProfiler.tsx
  • src/index.tsx
src/state/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

src/state/**/*.{ts,tsx}: Use createDeepEqualOutputSelector from @/state/selector-utils for deep equality checks
Use createCachedSelector from re-reselect for parameterized selectors

Files:

  • src/state/reducer.ts
  • src/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.ts
  • src/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:

  • .env
  • src/state/reducer.ts
  • src/index.tsx
  • src/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:

  • .env
  • src/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:

  • .env
  • src/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:

  • .env
  • src/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.tsx
  • src/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.ts
  • 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: 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.ts
  • 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: 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.tsx
  • src/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/commands is a valid symlink target that exists and contains command definitions (test-agent.md and weekly-summary.md). No action required.

.opencode/skill (1)

1-1: The skills directory reference is valid. The path ../.skills/skills correctly resolves to a functional directory containing skill definitions (verified: .skills/skills/git-commit-push/SKILL.md exists). The .opencode/skill symlink properly references this location.

.claude/skills/git-commit-push (1)

1-1: The path ../../.skills/skills/git-commit-push resolves 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 false for 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 PerformanceProfiler inside AppProviders but after App allows 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-equal is a well-established, lightweight library that's significantly faster than lodash's isEqual for deep comparisons. It's already integrated into the codebase in src/state/selector-utils.ts to power the createDeepEqualOutputSelector for 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, and URL are 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-equal dependency 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_PROFILER is correctly added to src/config.ts and is being used in src/index.tsx and src/state/reducer.ts via getConfig() 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 .env files.

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-level getConfig() 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 since getConfig() reads from import.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 localWalletMigrations is 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 readyPromise pattern ensures localforage.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 requestId for correlation, and outbound messages include duration for profiling.


35-55: Handler implementations are consistent and robust.

Each handler properly:

  1. Captures timing with performance.now()
  2. Wraps operations in try/catch
  3. Posts typed results back to the main thread
  4. Extracts error messages safely with the instanceof Error check
src/lib/profiledStorage.ts (2)

43-56: localStorage adapter correctly handles edge cases.

Good defensive programming:

  • Checks isLocalStorageAvailable before operations
  • Handles null items explicitly
  • Catches JSON parse errors and returns null gracefully

Note: Using trackIndexedDB with localStorage:${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: createLocalStorageAdapter correctly 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:

  1. ping triggers ensureReady() in the worker before responding ready
  2. 5-second timeout prevents indefinite hangs if the worker fails to initialize
  3. Errors during worker creation are caught and resolve (rather than reject) to enable fallback
  4. The resolved flag prevents double-resolution

This ensures the app remains functional even when Web Workers are unavailable or slow to start.


115-128: getItem fallback to localforage is a good resilience pattern.

Dynamic import of localforage in 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:

  1. Ignores duplicate ready messages
  2. Cleans up pending requests after processing
  3. Rejects on error messages
  4. Conditionally tracks profiling only when enabled and duration exists
  5. Routes to correct resolve based on message type

The early return on missing pending request (line 77) handles edge cases where responses arrive for already-timed-out or cancelled requests.


99-113: sendMessage correctly validates worker availability.

The null check on line 105 with immediate rejection prevents the operation from being added to pendingRequests when 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.

@0xApotheosis 0xApotheosis force-pushed the feat/firefox-performance-profiling-and-fixes branch from 6cad002 to 50dfbfa Compare January 16, 2026 06:17
@0xApotheosis 0xApotheosis marked this pull request as draft January 16, 2026 06:36
…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
@0xApotheosis 0xApotheosis marked this pull request as ready for review January 16, 2026 08:01
@premiumjibles premiumjibles self-requested a review January 19, 2026 23:29
Copy link
Collaborator

@premiumjibles premiumjibles left a 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
@0xApotheosis 0xApotheosis merged commit 2a943ab into develop Jan 20, 2026
3 of 4 checks passed
@0xApotheosis 0xApotheosis deleted the feat/firefox-performance-profiling-and-fixes branch January 20, 2026 02:10
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 enableProfiling parameter will always be the same value since it derives from a static environment variable (VITE_FEATURE_PERFORMANCE_PROFILER) evaluated at startup via memoized getConfig(). The single call site at src/state/reducer.ts:78 means 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.

Comment on lines +54 to +56
this.worker.onerror = err => {
console.error('[WorkerStorage] Worker error:', err)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/lib/storage/workerStorage.ts

Repository: shapeshift/web

Length of output: 7678


🏁 Script executed:

rg -i "from.*@shapeshiftoss/errors|import.*Error" src/lib/storage/ | head -20

Repository: shapeshift/web

Length of output: 40


🏁 Script executed:

rg -i "@shapeshiftoss/errors" --type ts --type tsx | head -10

Repository: shapeshift/web

Length of output: 85


🏁 Script executed:

rg "@shapeshiftoss/errors" -t ts | head -10

Repository: shapeshift/web

Length of output: 40


🏁 Script executed:

rg "try.*catch|\.catch\(" src/lib/storage/ -A 2 | head -30

Repository: 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.onerror handler
  • Operation timeout handlers
  • Wrap sendMessage calls in try-catch blocks in getItem, setItem, and removeItem to 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants