From 3815550a103b50189a80aab60288b94ea20506ff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 15:05:52 +0000 Subject: [PATCH 1/7] Initial plan From f875ef70f5bf3067467c514240514795ca887c34 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 15:15:42 +0000 Subject: [PATCH 2/7] Optimize transaction filtering and add debounce to search Co-authored-by: Kushmanmb <193178375+Kushmanmb@users.noreply.github.com> --- .../UsernameProfileSectionHeatmap/index.tsx | 76 ++++++++++++------- apps/web/src/components/Ecosystem/List.tsx | 8 +- .../src/components/Ecosystem/SearchBar.tsx | 22 +++++- 3 files changed, 71 insertions(+), 35 deletions(-) diff --git a/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/index.tsx b/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/index.tsx index 25c62cde65d..947ecd0e581 100644 --- a/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/index.tsx +++ b/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/index.tsx @@ -137,12 +137,17 @@ export default function UsernameProfileSectionHeatmap() { const firstTransactionDate = new Date(Math.min(...timestamps) * 1000); const lastTransactionDate = new Date(Math.max(...timestamps) * 1000); - const uniqueActiveDaysSet = new Set( - filteredTransactions.map((tx) => new Date(parseInt(tx.timeStamp, 10) * 1000).toDateString()), - ); + // Optimize: create Date objects only once during Set construction + const uniqueActiveDaysSet = new Set(); + filteredTransactions.forEach((tx) => { + const timestamp = parseInt(tx.timeStamp, 10) * 1000; + const dateStart = new Date(timestamp); + dateStart.setHours(0, 0, 0, 0); + uniqueActiveDaysSet.add(dateStart.getTime()); + }); const sortedDates = Array.from(uniqueActiveDaysSet) - .map((dateStr) => new Date(dateStr)) + .map((timestamp) => new Date(timestamp)) .sort((a, b) => a.getTime() - b.getTime()); let longestStreakDays = 0; @@ -358,36 +363,51 @@ export default function UsernameProfileSectionHeatmap() { setCurrentStreak(currentStreakDays); setActivityPeriod(activity); - setTokenSwapCount( - allTransactions.filter( - (tx) => + // Optimize: single pass through transactions instead of multiple filters + const ensAddresses = [ + ETH_REGISTRAR_CONTROLLER_1, + ETH_REGISTRAR_CONTROLLER_2, + BASENAMES_REGISTRAR_CONTROLLER, + BASENAMES_EA_REGISTRAR_CONTROLLER, + ]; + + const counts = allTransactions.reduce( + (acc, tx) => { + // Token swap count + if ( ((tx.functionName && SWAP_FUNCTION_NAMES.some((fn) => tx.functionName?.includes(fn))) ?? tx.to === UNISWAP_ROUTER) || tx.to === AERODROME_ROUTER || - tx.to === ONEINCH_ROUTER, - ).length, - ); - - // ENS count calculation - setEnsCount( - allTransactions.filter((tx) => - [ - ETH_REGISTRAR_CONTROLLER_1, - ETH_REGISTRAR_CONTROLLER_2, - BASENAMES_REGISTRAR_CONTROLLER, - BASENAMES_EA_REGISTRAR_CONTROLLER, - ].includes(tx.to), - ).length, + tx.to === ONEINCH_ROUTER + ) { + acc.tokenSwap++; + } + + // ENS count + if (ensAddresses.includes(tx.to)) { + acc.ens++; + } + + // Bridge count + if (bridges.has(tx.to)) { + acc.bridge++; + } + + // Lend count + if (lendBorrowEarn.has(tx.to) || tx.from === MOONWELL_WETH_UNWRAPPER) { + acc.lend++; + } + + return acc; + }, + { tokenSwap: 0, ens: 0, bridge: 0, lend: 0 }, ); - setBridgeCount(allTransactions.filter((tx) => bridges.has(tx.to)).length); - - setLendCount( - allTransactions.filter( - (tx) => lendBorrowEarn.has(tx.to) || tx.from === MOONWELL_WETH_UNWRAPPER, - ).length, - ); + setTokenSwapCount(counts.tokenSwap); + setEnsCount(counts.ens); + setBridgeCount(counts.bridge); + setLendCount(counts.lend); setBuildCount( allEthereumDeployments.length + allBaseDeployments.length + allSepoliaDeployments.length, diff --git a/apps/web/src/components/Ecosystem/List.tsx b/apps/web/src/components/Ecosystem/List.tsx index 824f369d2bb..b347a423e9e 100644 --- a/apps/web/src/components/Ecosystem/List.tsx +++ b/apps/web/src/components/Ecosystem/List.tsx @@ -3,7 +3,7 @@ import classNames from 'classnames'; import { AnimatePresence, motion, cubicBezier } from 'motion/react'; import EcosystemCard from './Card'; import { EcosystemApp } from 'apps/web/src/components/Ecosystem/Content'; -import { Dispatch, SetStateAction, useCallback } from 'react'; +import { Dispatch, SetStateAction, useCallback, memo } from 'react'; import { Button, ButtonSizes, @@ -23,7 +23,7 @@ const cardAnimations = { exit: { opacity: 0 }, }; -function AnimatedEcosystemCard({ app }: { app: EcosystemApp }) { +const AnimatedEcosystemCard = memo(({ app }: { app: EcosystemApp }) => { return ( ); -} +}); + +AnimatedEcosystemCard.displayName = 'AnimatedEcosystemCard'; export function List({ selectedCategories, diff --git a/apps/web/src/components/Ecosystem/SearchBar.tsx b/apps/web/src/components/Ecosystem/SearchBar.tsx index 917dfa95077..89e3673cbb1 100644 --- a/apps/web/src/components/Ecosystem/SearchBar.tsx +++ b/apps/web/src/components/Ecosystem/SearchBar.tsx @@ -52,16 +52,29 @@ export function SearchBar({ setSearch: Dispatch>; }) { const [isExpanded, setIsExpanded] = useState(false); + const [localSearch, setLocalSearch] = useState(search); const debounced = useRef(); const inputRef = useRef(null); const mobileInputRef = useRef(null); + // Sync local search with prop when cleared externally + useEffect(() => { + if (search === '') { + setLocalSearch(''); + } + }, [search]); + const onChange = useCallback( (e: React.ChangeEvent) => { clearTimeout(debounced.current); const value = e.target.value; - setSearch(value); + setLocalSearch(value); // Update local state immediately for responsive input + + // Debounce the parent state update with 300ms delay + debounced.current = window.setTimeout(() => { + setSearch(value); + }, 300); }, [setSearch], ); @@ -71,6 +84,7 @@ export function SearchBar({ }, []); const clearInput = useCallback(() => { + setLocalSearch(''); setSearch(''); setIsExpanded(false); }, [setSearch]); @@ -106,7 +120,7 @@ export function SearchBar({ ref={inputRef} type="text" id="appsSearchBar" - value={search} + value={localSearch} onChange={onChange} className="hidden max-w-[100px] flex-1 font-sans text-base text-black placeholder:text-base-gray-200 focus:outline-none md:block md:max-w-none" placeholder="Search" @@ -118,7 +132,7 @@ export function SearchBar({ - {search && ( + {localSearch && ( Date: Tue, 10 Feb 2026 15:17:15 +0000 Subject: [PATCH 3/7] Address code review: optimize Date creation and add cleanup Co-authored-by: Kushmanmb <193178375+Kushmanmb@users.noreply.github.com> --- .../Basenames/UsernameProfileSectionHeatmap/index.tsx | 10 ++++++---- apps/web/src/components/Ecosystem/SearchBar.tsx | 7 +++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/index.tsx b/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/index.tsx index 947ecd0e581..f95c9a5db71 100644 --- a/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/index.tsx +++ b/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/index.tsx @@ -137,13 +137,15 @@ export default function UsernameProfileSectionHeatmap() { const firstTransactionDate = new Date(Math.min(...timestamps) * 1000); const lastTransactionDate = new Date(Math.max(...timestamps) * 1000); - // Optimize: create Date objects only once during Set construction + // Optimize: use mathematical operations to normalize timestamps to day boundaries + // This avoids creating Date objects during Set construction const uniqueActiveDaysSet = new Set(); + const msPerDay = 86400000; // 24 * 60 * 60 * 1000 filteredTransactions.forEach((tx) => { const timestamp = parseInt(tx.timeStamp, 10) * 1000; - const dateStart = new Date(timestamp); - dateStart.setHours(0, 0, 0, 0); - uniqueActiveDaysSet.add(dateStart.getTime()); + // Normalize to UTC day start (00:00:00) using math instead of Date operations + const dayStart = Math.floor(timestamp / msPerDay) * msPerDay; + uniqueActiveDaysSet.add(dayStart); }); const sortedDates = Array.from(uniqueActiveDaysSet) diff --git a/apps/web/src/components/Ecosystem/SearchBar.tsx b/apps/web/src/components/Ecosystem/SearchBar.tsx index 89e3673cbb1..67965eaa9f7 100644 --- a/apps/web/src/components/Ecosystem/SearchBar.tsx +++ b/apps/web/src/components/Ecosystem/SearchBar.tsx @@ -64,6 +64,13 @@ export function SearchBar({ } }, [search]); + // Cleanup timeout on unmount to prevent setState on unmounted component + useEffect(() => { + return () => { + clearTimeout(debounced.current); + }; + }, []); + const onChange = useCallback( (e: React.ChangeEvent) => { clearTimeout(debounced.current); From 7d61e234ab9476c26b242231b4b64a8fa327247f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 15:20:11 +0000 Subject: [PATCH 4/7] Fix Date optimization to cache objects and preserve timezone behavior Co-authored-by: Kushmanmb <193178375+Kushmanmb@users.noreply.github.com> --- .../UsernameProfileSectionHeatmap/index.tsx | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/index.tsx b/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/index.tsx index f95c9a5db71..c61974fd36b 100644 --- a/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/index.tsx +++ b/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/index.tsx @@ -137,19 +137,20 @@ export default function UsernameProfileSectionHeatmap() { const firstTransactionDate = new Date(Math.min(...timestamps) * 1000); const lastTransactionDate = new Date(Math.max(...timestamps) * 1000); - // Optimize: use mathematical operations to normalize timestamps to day boundaries - // This avoids creating Date objects during Set construction - const uniqueActiveDaysSet = new Set(); - const msPerDay = 86400000; // 24 * 60 * 60 * 1000 + // Optimize: avoid creating Date objects twice + // Store the first occurrence of each unique day's Date object + const uniqueDatesMap = new Map(); filteredTransactions.forEach((tx) => { const timestamp = parseInt(tx.timeStamp, 10) * 1000; - // Normalize to UTC day start (00:00:00) using math instead of Date operations - const dayStart = Math.floor(timestamp / msPerDay) * msPerDay; - uniqueActiveDaysSet.add(dayStart); + const date = new Date(timestamp); + const dateStr = date.toDateString(); + if (!uniqueDatesMap.has(dateStr)) { + uniqueDatesMap.set(dateStr, date); + } }); - const sortedDates = Array.from(uniqueActiveDaysSet) - .map((timestamp) => new Date(timestamp)) + // Use the cached Date objects directly instead of recreating them + const sortedDates = Array.from(uniqueDatesMap.values()) .sort((a, b) => a.getTime() - b.getTime()); let longestStreakDays = 0; @@ -168,7 +169,7 @@ export default function UsernameProfileSectionHeatmap() { longestStreakDays = Math.max(longestStreakDays, streak); return { - uniqueActiveDays: uniqueActiveDaysSet.size, + uniqueActiveDays: uniqueDatesMap.size, longestStreakDays, currentStreakDays: sortedDates[sortedDates.length - 1].toDateString() === new Date().toDateString() From bd37db381671f959937fba2c4e8c9b42b08eccf6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 15:22:16 +0000 Subject: [PATCH 5/7] Move ENS_ADDRESSES to module scope and remove window prefix Co-authored-by: Kushmanmb <193178375+Kushmanmb@users.noreply.github.com> --- .../UsernameProfileSectionHeatmap/index.tsx | 17 +++++++++-------- apps/web/src/components/Ecosystem/SearchBar.tsx | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/index.tsx b/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/index.tsx index c61974fd36b..22d6cf354cd 100644 --- a/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/index.tsx +++ b/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/index.tsx @@ -25,6 +25,14 @@ const ETH_REGISTRAR_CONTROLLER_2 = '0x253553366da8546fc250f225fe3d25d0c782303b'; const BASENAMES_REGISTRAR_CONTROLLER = '0x4ccb0bb02fcaba27e82a56646e81d8c5bc4119a5'; // Basenames RegistrarController const BASENAMES_EA_REGISTRAR_CONTROLLER = '0xd3e6775ed9b7dc12b205c8e608dc3767b9e5efda'; // Basenames EARegistrarController +// ENS Addresses array for efficient lookups +const ENS_ADDRESSES = [ + ETH_REGISTRAR_CONTROLLER_1, + ETH_REGISTRAR_CONTROLLER_2, + BASENAMES_REGISTRAR_CONTROLLER, + BASENAMES_EA_REGISTRAR_CONTROLLER, +]; + // Lending and Borrowing const MOONWELL_WETH_UNWRAPPER = '0x1382cff3cee10d283dcca55a30496187759e4caf'; // Base Moonwell WETH Unwrapper @@ -367,13 +375,6 @@ export default function UsernameProfileSectionHeatmap() { setActivityPeriod(activity); // Optimize: single pass through transactions instead of multiple filters - const ensAddresses = [ - ETH_REGISTRAR_CONTROLLER_1, - ETH_REGISTRAR_CONTROLLER_2, - BASENAMES_REGISTRAR_CONTROLLER, - BASENAMES_EA_REGISTRAR_CONTROLLER, - ]; - const counts = allTransactions.reduce( (acc, tx) => { // Token swap count @@ -388,7 +389,7 @@ export default function UsernameProfileSectionHeatmap() { } // ENS count - if (ensAddresses.includes(tx.to)) { + if (ENS_ADDRESSES.includes(tx.to)) { acc.ens++; } diff --git a/apps/web/src/components/Ecosystem/SearchBar.tsx b/apps/web/src/components/Ecosystem/SearchBar.tsx index 67965eaa9f7..45e0ea1b3bc 100644 --- a/apps/web/src/components/Ecosystem/SearchBar.tsx +++ b/apps/web/src/components/Ecosystem/SearchBar.tsx @@ -79,7 +79,7 @@ export function SearchBar({ setLocalSearch(value); // Update local state immediately for responsive input // Debounce the parent state update with 300ms delay - debounced.current = window.setTimeout(() => { + debounced.current = setTimeout(() => { setSearch(value); }, 300); }, From bbec82fd9eb8e2b34dd21c3197af1874ac2b34ac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 15:23:21 +0000 Subject: [PATCH 6/7] Fix setTimeout ref type for better type safety Co-authored-by: Kushmanmb <193178375+Kushmanmb@users.noreply.github.com> --- apps/web/src/components/Ecosystem/SearchBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/Ecosystem/SearchBar.tsx b/apps/web/src/components/Ecosystem/SearchBar.tsx index 45e0ea1b3bc..50c53f8aeb9 100644 --- a/apps/web/src/components/Ecosystem/SearchBar.tsx +++ b/apps/web/src/components/Ecosystem/SearchBar.tsx @@ -53,7 +53,7 @@ export function SearchBar({ }) { const [isExpanded, setIsExpanded] = useState(false); const [localSearch, setLocalSearch] = useState(search); - const debounced = useRef(); + const debounced = useRef>(); const inputRef = useRef(null); const mobileInputRef = useRef(null); From 6a887d0b4f9aecea56fac78345b9480f9cdfa219 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 15:24:25 +0000 Subject: [PATCH 7/7] Convert ENS_ADDRESSES to Set for O(1) lookups and improve formatting Co-authored-by: Kushmanmb <193178375+Kushmanmb@users.noreply.github.com> --- .../Basenames/UsernameProfileSectionHeatmap/index.tsx | 8 ++++---- apps/web/src/components/Ecosystem/SearchBar.tsx | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/index.tsx b/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/index.tsx index 22d6cf354cd..2bd4f3317d0 100644 --- a/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/index.tsx +++ b/apps/web/src/components/Basenames/UsernameProfileSectionHeatmap/index.tsx @@ -25,13 +25,13 @@ const ETH_REGISTRAR_CONTROLLER_2 = '0x253553366da8546fc250f225fe3d25d0c782303b'; const BASENAMES_REGISTRAR_CONTROLLER = '0x4ccb0bb02fcaba27e82a56646e81d8c5bc4119a5'; // Basenames RegistrarController const BASENAMES_EA_REGISTRAR_CONTROLLER = '0xd3e6775ed9b7dc12b205c8e608dc3767b9e5efda'; // Basenames EARegistrarController -// ENS Addresses array for efficient lookups -const ENS_ADDRESSES = [ +// ENS Addresses Set for O(1) lookups +const ENS_ADDRESSES = new Set([ ETH_REGISTRAR_CONTROLLER_1, ETH_REGISTRAR_CONTROLLER_2, BASENAMES_REGISTRAR_CONTROLLER, BASENAMES_EA_REGISTRAR_CONTROLLER, -]; +]); // Lending and Borrowing const MOONWELL_WETH_UNWRAPPER = '0x1382cff3cee10d283dcca55a30496187759e4caf'; // Base Moonwell WETH Unwrapper @@ -389,7 +389,7 @@ export default function UsernameProfileSectionHeatmap() { } // ENS count - if (ENS_ADDRESSES.includes(tx.to)) { + if (ENS_ADDRESSES.has(tx.to)) { acc.ens++; } diff --git a/apps/web/src/components/Ecosystem/SearchBar.tsx b/apps/web/src/components/Ecosystem/SearchBar.tsx index 50c53f8aeb9..53e57a25429 100644 --- a/apps/web/src/components/Ecosystem/SearchBar.tsx +++ b/apps/web/src/components/Ecosystem/SearchBar.tsx @@ -77,7 +77,6 @@ export function SearchBar({ const value = e.target.value; setLocalSearch(value); // Update local state immediately for responsive input - // Debounce the parent state update with 300ms delay debounced.current = setTimeout(() => { setSearch(value);