diff --git a/__tests__/components/brain/ContentTabContext.test.tsx b/__tests__/components/brain/ContentTabContext.test.tsx index 1e06b7d940..765989273a 100644 --- a/__tests__/components/brain/ContentTabContext.test.tsx +++ b/__tests__/components/brain/ContentTabContext.test.tsx @@ -1,34 +1,47 @@ -import React from 'react'; -import { renderHook, act } from '@testing-library/react'; -import { ContentTabProvider, useContentTab, WaveVotingState } from '@/components/brain/ContentTabContext'; -import { MyStreamWaveTab } from '@/types/waves.types'; +import React from "react"; +import { renderHook, act } from "@testing-library/react"; +import { + ContentTabProvider, + useContentTab, + WaveVotingState, +} from "@/components/brain/ContentTabContext"; +import { MyStreamWaveTab } from "@/types/waves.types"; function setup() { - const wrapper: React.FC<{children: React.ReactNode}> = ({ children }) => ( + const wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => ( {children} ); return renderHook(() => useContentTab(), { wrapper }); } -describe('ContentTabContext', () => { - it('defaults to CHAT when params null', () => { +describe("ContentTabContext", () => { + it("defaults to CHAT when params null", () => { const { result } = setup(); act(() => result.current.updateAvailableTabs(null)); expect(result.current.availableTabs).toEqual([MyStreamWaveTab.CHAT]); expect(result.current.activeContentTab).toBe(MyStreamWaveTab.CHAT); }); - it('prevents setting unavailable tab', () => { + it("prevents setting unavailable tab", () => { const { result } = setup(); - act(() => result.current.updateAvailableTabs({ isChatWave: true, isMemesWave: false, votingState: WaveVotingState.NOT_STARTED, hasFirstDecisionPassed: false })); + act(() => + result.current.updateAvailableTabs({ + waveId: "chat-wave", + isChatWave: true, + isMemesWave: false, + votingState: WaveVotingState.NOT_STARTED, + hasFirstDecisionPassed: false, + }) + ); act(() => result.current.setActiveContentTab(MyStreamWaveTab.WINNERS)); expect(result.current.activeContentTab).toBe(MyStreamWaveTab.CHAT); }); - it('sets meme wave tabs correctly', () => { + it("sets meme wave tabs correctly", () => { const { result } = setup(); act(() => result.current.updateAvailableTabs({ + waveId: "meme-wave", isChatWave: false, isMemesWave: true, votingState: WaveVotingState.NOT_STARTED, @@ -36,11 +49,96 @@ describe('ContentTabContext', () => { }) ); expect(result.current.availableTabs).toEqual([ - MyStreamWaveTab.CHAT, MyStreamWaveTab.LEADERBOARD, + MyStreamWaveTab.CHAT, MyStreamWaveTab.MY_VOTES, MyStreamWaveTab.OUTCOME, MyStreamWaveTab.FAQ, ]); }); + + it("defaults to LEADERBOARD for memes waves", () => { + const { result } = setup(); + act(() => + result.current.updateAvailableTabs({ + waveId: "meme-wave", + isChatWave: false, + isMemesWave: true, + votingState: WaveVotingState.NOT_STARTED, + hasFirstDecisionPassed: false, + }) + ); + expect(result.current.activeContentTab).toBe(MyStreamWaveTab.LEADERBOARD); + }); + + it("defaults to CHAT for non-memes waves", () => { + const { result } = setup(); + act(() => + result.current.updateAvailableTabs({ + waveId: "default-wave", + isChatWave: false, + isMemesWave: false, + votingState: WaveVotingState.NOT_STARTED, + hasFirstDecisionPassed: false, + }) + ); + expect(result.current.activeContentTab).toBe(MyStreamWaveTab.CHAT); + }); + + it("restores stored tab for memes wave even when it is CHAT", () => { + const { result } = setup(); + act(() => + result.current.updateAvailableTabs({ + waveId: "meme-wave", + isChatWave: false, + isMemesWave: true, + votingState: WaveVotingState.NOT_STARTED, + hasFirstDecisionPassed: false, + }) + ); + act(() => result.current.setActiveContentTab(MyStreamWaveTab.CHAT)); + act(() => + result.current.updateAvailableTabs({ + waveId: "default-wave", + isChatWave: false, + isMemesWave: false, + votingState: WaveVotingState.NOT_STARTED, + hasFirstDecisionPassed: false, + }) + ); + act(() => + result.current.updateAvailableTabs({ + waveId: "meme-wave", + isChatWave: false, + isMemesWave: true, + votingState: WaveVotingState.NOT_STARTED, + hasFirstDecisionPassed: false, + }) + ); + expect(result.current.activeContentTab).toBe(MyStreamWaveTab.CHAT); + }); + + it("falls back to default when stored tab is unavailable", () => { + const { result } = setup(); + act(() => + result.current.updateAvailableTabs({ + waveId: "default-wave", + isChatWave: false, + isMemesWave: false, + votingState: WaveVotingState.NOT_STARTED, + hasFirstDecisionPassed: true, + }) + ); + act(() => result.current.setActiveContentTab(MyStreamWaveTab.WINNERS)); + act(() => + result.current.updateAvailableTabs({ + waveId: "default-wave", + isChatWave: false, + isMemesWave: false, + votingState: WaveVotingState.NOT_STARTED, + hasFirstDecisionPassed: false, + }) + ); + expect(result.current.activeContentTab).toBe(MyStreamWaveTab.CHAT); + }); }); diff --git a/components/brain/ContentTabContext.tsx b/components/brain/ContentTabContext.tsx index 2b289fa4e5..c88a899c3d 100644 --- a/components/brain/ContentTabContext.tsx +++ b/components/brain/ContentTabContext.tsx @@ -4,12 +4,14 @@ import type { ReactNode } from "react"; import React, { createContext, useState, + useEffect, useContext, useCallback, useMemo, useRef, } from "react"; import { MyStreamWaveTab } from "@/types/waves.types"; +import useLocalPreference from "@/hooks/useLocalPreference"; export enum WaveVotingState { NOT_STARTED = "NOT_STARTED", @@ -33,6 +35,55 @@ interface ContentTabContextType { updateAvailableTabs: (params: WaveTabParams | null) => void; } +const MEMES_WAVE_LAST_TAB_STORAGE_KEY = "memes_wave_last_tab_by_id"; + +const isValidWaveTab = (value: unknown): value is MyStreamWaveTab => + Object.values(MyStreamWaveTab).includes(value as MyStreamWaveTab); + +const isValidWaveTabMap = ( + value: unknown +): value is Record => { + if (value === null || value === undefined || typeof value !== "object") { + return false; + } + + return Object.values(value).every((tab) => isValidWaveTab(tab)); +}; + +const buildMemesTabs = ( + votingState: WaveVotingState, + hasFirstDecisionPassed: boolean +) => { + const tabs: MyStreamWaveTab[] = []; + if (votingState !== WaveVotingState.ENDED) { + tabs.push(MyStreamWaveTab.LEADERBOARD); + } + tabs.push(MyStreamWaveTab.CHAT); + if (hasFirstDecisionPassed) { + tabs.push(MyStreamWaveTab.WINNERS); + } + tabs.push(MyStreamWaveTab.MY_VOTES); + tabs.push(MyStreamWaveTab.OUTCOME); + tabs.push(MyStreamWaveTab.FAQ); + return tabs; +}; + +const buildDefaultTabs = ( + votingState: WaveVotingState, + hasFirstDecisionPassed: boolean +) => { + const tabs: MyStreamWaveTab[] = [MyStreamWaveTab.CHAT]; + if (votingState !== WaveVotingState.ENDED) { + tabs.push(MyStreamWaveTab.LEADERBOARD); + } + if (hasFirstDecisionPassed) { + tabs.push(MyStreamWaveTab.WINNERS); + } + tabs.push(MyStreamWaveTab.OUTCOME); + tabs.push(MyStreamWaveTab.MY_VOTES); + return tabs; +}; + // Create the context with a default value const ContentTabContext = createContext({ activeContentTab: MyStreamWaveTab.CHAT, @@ -45,12 +96,30 @@ const ContentTabContext = createContext({ export const ContentTabProvider: React.FC<{ children: ReactNode }> = ({ children, }) => { + const [tabsByWaveId, setTabsByWaveId] = useLocalPreference< + Record + >(MEMES_WAVE_LAST_TAB_STORAGE_KEY, {}, isValidWaveTabMap); const [activeContentTabRaw, setActiveContentTabRaw] = useState(MyStreamWaveTab.CHAT); const [availableTabs, setAvailableTabs] = useState([ MyStreamWaveTab.CHAT, ]); - const lastWaveIdRef = useRef(null); + const currentWaveIdRef = useRef(null); + const activeContentTabRef = useRef(activeContentTabRaw); + const tabsByWaveIdRef = useRef>(tabsByWaveId); + + useEffect(() => { + activeContentTabRef.current = activeContentTabRaw; + }, [activeContentTabRaw]); + + useEffect(() => { + tabsByWaveIdRef.current = tabsByWaveId; + }, [tabsByWaveId]); + + const setActiveTabInternal = useCallback((tab: MyStreamWaveTab) => { + setActiveContentTabRaw(tab); + activeContentTabRef.current = tab; + }, []); // Function to determine which tabs are available based on wave state // Now accepts a params object or null @@ -58,12 +127,8 @@ export const ContentTabProvider: React.FC<{ children: ReactNode }> = ({ (params: WaveTabParams | null) => { if (!params) { setAvailableTabs([MyStreamWaveTab.CHAT]); - lastWaveIdRef.current = null; - - // If current tab is not CHAT, switch to it - if (activeContentTabRaw !== MyStreamWaveTab.CHAT) { - setActiveContentTabRaw(MyStreamWaveTab.CHAT); - } + currentWaveIdRef.current = null; + setActiveTabInternal(MyStreamWaveTab.CHAT); return; } @@ -75,83 +140,32 @@ export const ContentTabProvider: React.FC<{ children: ReactNode }> = ({ hasFirstDecisionPassed, } = params; - // Chat-type waves only show chat tab + let tabs: MyStreamWaveTab[]; if (isChatWave) { - setAvailableTabs([MyStreamWaveTab.CHAT]); - - // If current tab is not CHAT, switch to it - if (activeContentTabRaw !== MyStreamWaveTab.CHAT) { - setActiveContentTabRaw(MyStreamWaveTab.CHAT); - } - lastWaveIdRef.current = waveId ?? null; - return; - } - - // For Memes wave - don't set default tab, let it use standard behavior - if (isMemesWave) { - const getDefaultTab = (tabsToCheck: MyStreamWaveTab[]) => - tabsToCheck.includes(MyStreamWaveTab.LEADERBOARD) - ? MyStreamWaveTab.LEADERBOARD - : MyStreamWaveTab.CHAT; - const tabs: MyStreamWaveTab[] = []; - if (votingState !== WaveVotingState.ENDED) { - tabs.push(MyStreamWaveTab.LEADERBOARD); - } - tabs.push(MyStreamWaveTab.CHAT); - if (hasFirstDecisionPassed) { - tabs.push(MyStreamWaveTab.WINNERS); - } - tabs.push(MyStreamWaveTab.MY_VOTES); - tabs.push(MyStreamWaveTab.OUTCOME); - tabs.push(MyStreamWaveTab.FAQ); - - setAvailableTabs(tabs); - - const isNewWave = !!waveId && waveId !== lastWaveIdRef.current; - - if (isNewWave || !tabs.includes(activeContentTabRaw)) { - setActiveContentTabRaw(getDefaultTab(tabs)); - } - - lastWaveIdRef.current = waveId ?? null; - return; - } - - // Default tabs - const tabs: MyStreamWaveTab[] = [MyStreamWaveTab.CHAT]; - - // Add Leaderboard if voting hasn't ended - if (votingState !== WaveVotingState.ENDED) { - tabs.push(MyStreamWaveTab.LEADERBOARD); - } - - // Add Winners if first decision has passed - if (hasFirstDecisionPassed) { - tabs.push(MyStreamWaveTab.WINNERS); + tabs = [MyStreamWaveTab.CHAT]; + } else if (isMemesWave) { + tabs = buildMemesTabs(votingState, hasFirstDecisionPassed); + } else { + tabs = buildDefaultTabs(votingState, hasFirstDecisionPassed); } - // Always add Outcome - tabs.push(MyStreamWaveTab.OUTCOME); - tabs.push(MyStreamWaveTab.MY_VOTES); - - // FAQ tab is only available for Memes waves, which is handled in the isMemesWave block above - - // Update available tabs setAvailableTabs(tabs); + currentWaveIdRef.current = waveId ?? null; - // If current tab is no longer available, switch to a default available tab - if (!tabs.includes(activeContentTabRaw)) { - // Prefer to switch to LEADERBOARD if available, otherwise CHAT - if (tabs.includes(MyStreamWaveTab.LEADERBOARD)) { - setActiveContentTabRaw(MyStreamWaveTab.LEADERBOARD); - } else { - setActiveContentTabRaw(MyStreamWaveTab.CHAT); - } - } + const storedTab = waveId ? tabsByWaveIdRef.current[waveId] : undefined; + const defaultTab = + isMemesWave && tabs.includes(MyStreamWaveTab.LEADERBOARD) + ? MyStreamWaveTab.LEADERBOARD + : MyStreamWaveTab.CHAT; - lastWaveIdRef.current = waveId ?? null; + const nextTab = + storedTab !== undefined && tabs.includes(storedTab) + ? storedTab + : defaultTab; + + setActiveTabInternal(nextTab); }, - [activeContentTabRaw] + [setActiveTabInternal] ); // Wrapper for setActiveContentTab that validates the tab @@ -159,13 +173,22 @@ export const ContentTabProvider: React.FC<{ children: ReactNode }> = ({ (tab: MyStreamWaveTab) => { // Only set the tab if it's available if (availableTabs.includes(tab)) { - setActiveContentTabRaw(tab); + setActiveTabInternal(tab); + const waveId = currentWaveIdRef.current; + if (waveId) { + const nextMap = { + ...tabsByWaveIdRef.current, + [waveId]: tab, + }; + tabsByWaveIdRef.current = nextMap; + setTabsByWaveId(nextMap); + } } else { // If tab is not available, default to CHAT - setActiveContentTabRaw(MyStreamWaveTab.CHAT); + setActiveTabInternal(MyStreamWaveTab.CHAT); } }, - [availableTabs] + [availableTabs, setActiveTabInternal, setTabsByWaveId] ); // Memoize the context value to prevent unnecessary re-renders diff --git a/package-lock.json b/package-lock.json index 38a6e2f0b0..1326887961 100644 --- a/package-lock.json +++ b/package-lock.json @@ -310,6 +310,7 @@ "node_modules/@babel/core": { "version": "7.28.5", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -852,6 +853,7 @@ "node_modules/@capacitor/core": { "version": "7.4.1", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -919,6 +921,7 @@ "node_modules/@coinbase/wallet-sdk": { "version": "4.3.6", "license": "Apache-2.0", + "peer": true, "dependencies": { "@noble/hashes": "1.4.0", "clsx": "1.2.1", @@ -1700,6 +1703,7 @@ "node_modules/@fortawesome/fontawesome-svg-core": { "version": "6.7.2", "license": "MIT", + "peer": true, "dependencies": { "@fortawesome/fontawesome-common-types": "6.7.2" }, @@ -1741,6 +1745,7 @@ "node_modules/@gemini-wallet/core": { "version": "0.3.2", "license": "MIT", + "peer": true, "dependencies": { "@metamask/rpc-errors": "7.0.2", "eventemitter3": "5.0.1" @@ -3436,7 +3441,6 @@ "node_modules/@jridgewell/source-map": { "version": "0.3.11", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -4081,6 +4085,7 @@ }, "node_modules/@metamask/sdk": { "version": "0.33.1", + "peer": true, "dependencies": { "@babel/runtime": "^7.26.0", "@metamask/onboarding": "^1.0.1", @@ -4138,6 +4143,7 @@ "node_modules/@metamask/sdk/node_modules/cross-fetch": { "version": "4.1.0", "license": "MIT", + "peer": true, "dependencies": { "node-fetch": "^2.7.0" } @@ -4285,6 +4291,7 @@ "node_modules/@nestjs/common": { "version": "11.1.9", "license": "MIT", + "peer": true, "dependencies": { "file-type": "21.1.0", "iterare": "1.2.1", @@ -4495,6 +4502,7 @@ "node_modules/@noble/ciphers": { "version": "1.3.0", "license": "MIT", + "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -4643,6 +4651,7 @@ "node_modules/@opentelemetry/api": { "version": "1.9.0", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -4660,6 +4669,7 @@ "node_modules/@opentelemetry/context-async-hooks": { "version": "2.2.0", "license": "Apache-2.0", + "peer": true, "engines": { "node": "^18.19.0 || >=20.6.0" }, @@ -4670,6 +4680,7 @@ "node_modules/@opentelemetry/core": { "version": "2.2.0", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, @@ -4683,6 +4694,7 @@ "node_modules/@opentelemetry/instrumentation": { "version": "0.208.0", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/api-logs": "0.208.0", "import-in-the-middle": "^2.0.0", @@ -5022,6 +5034,7 @@ "node_modules/@opentelemetry/resources": { "version": "2.2.0", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" @@ -5036,6 +5049,7 @@ "node_modules/@opentelemetry/sdk-trace-base": { "version": "2.2.0", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", @@ -5051,6 +5065,7 @@ "node_modules/@opentelemetry/semantic-conventions": { "version": "1.38.0", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=14" } @@ -5696,6 +5711,7 @@ "version": "1.57.0", "devOptional": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "playwright": "1.57.0" }, @@ -5709,6 +5725,7 @@ "node_modules/@popperjs/core": { "version": "2.11.8", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -7657,6 +7674,7 @@ "node_modules/@solana/kit": { "version": "3.0.3", "license": "MIT", + "peer": true, "dependencies": { "@solana/accounts": "3.0.3", "@solana/addresses": "3.0.3", @@ -8187,6 +8205,7 @@ "node_modules/@tanstack/query-core": { "version": "5.90.11", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -8195,6 +8214,7 @@ "node_modules/@tanstack/react-query": { "version": "5.90.11", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/query-core": "5.90.11" }, @@ -8377,8 +8397,7 @@ "node_modules/@types/aria-query": { "version": "5.0.4", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -8434,7 +8453,6 @@ "node_modules/@types/eslint": { "version": "9.6.1", "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -8443,7 +8461,6 @@ "node_modules/@types/eslint-scope": { "version": "3.7.7", "license": "MIT", - "peer": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -8727,6 +8744,7 @@ "node_modules/@types/node": { "version": "20.19.25", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -8771,6 +8789,7 @@ "node_modules/@types/react": { "version": "19.2.3", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -8778,6 +8797,7 @@ "node_modules/@types/react-dom": { "version": "19.2.3", "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -8871,6 +8891,7 @@ "version": "8.48.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.48.0", @@ -8907,6 +8928,7 @@ "version": "8.48.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.48.0", "@typescript-eslint/types": "8.48.0", @@ -9501,7 +9523,6 @@ "node_modules/@wagmi/core/node_modules/zustand": { "version": "5.0.0", "license": "MIT", - "peer": true, "engines": { "node": ">=12.20.0" }, @@ -9583,6 +9604,7 @@ "node_modules/@walletconnect/ethereum-provider": { "version": "2.21.1", "license": "Apache-2.0", + "peer": true, "dependencies": { "@reown/appkit": "1.7.8", "@walletconnect/jsonrpc-http-connection": "1.0.8", @@ -10546,6 +10568,7 @@ "node_modules/@walletconnect/ethereum-provider/node_modules/ws": { "version": "8.18.0", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -10972,7 +10995,6 @@ "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" @@ -10980,23 +11002,19 @@ }, "node_modules/@webassemblyjs/floating-point-hex-parser": { "version": "1.13.2", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", @@ -11005,13 +11023,11 @@ }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { "version": "1.13.2", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -11022,7 +11038,6 @@ "node_modules/@webassemblyjs/ieee754": { "version": "1.13.2", "license": "MIT", - "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -11030,20 +11045,17 @@ "node_modules/@webassemblyjs/leb128": { "version": "1.13.2", "license": "Apache-2.0", - "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { "version": "1.13.2", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -11058,7 +11070,6 @@ "node_modules/@webassemblyjs/wasm-gen": { "version": "1.14.1", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", @@ -11070,7 +11081,6 @@ "node_modules/@webassemblyjs/wasm-opt": { "version": "1.14.1", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -11081,7 +11091,6 @@ "node_modules/@webassemblyjs/wasm-parser": { "version": "1.14.1", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", @@ -11094,7 +11103,6 @@ "node_modules/@webassemblyjs/wast-printer": { "version": "1.14.1", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" @@ -11106,13 +11114,11 @@ }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/abab": { "version": "2.0.6", @@ -11141,6 +11147,7 @@ "node_modules/acorn": { "version": "8.15.0", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -11167,7 +11174,6 @@ "node_modules/acorn-import-phases": { "version": "1.0.4", "license": "MIT", - "peer": true, "engines": { "node": ">=10.13.0" }, @@ -11236,7 +11242,6 @@ "node_modules/ajv-formats": { "version": "2.1.1", "license": "MIT", - "peer": true, "dependencies": { "ajv": "^8.0.0" }, @@ -11252,7 +11257,6 @@ "node_modules/ajv-formats/node_modules/ajv": { "version": "8.17.1", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -11266,8 +11270,7 @@ }, "node_modules/ajv-formats/node_modules/json-schema-traverse": { "version": "1.0.0", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/ansi-escapes": { "version": "4.3.2", @@ -11640,6 +11643,7 @@ "node_modules/axios": { "version": "1.13.2", "license": "MIT", + "peer": true, "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", @@ -11717,6 +11721,7 @@ "version": "1.0.0", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/types": "^7.26.0" } @@ -11953,6 +11958,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -12023,6 +12029,7 @@ "version": "4.0.9", "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -12256,6 +12263,7 @@ "node_modules/chart.js": { "version": "4.5.1", "license": "MIT", + "peer": true, "dependencies": { "@kurkle/color": "^0.3.0" }, @@ -12336,7 +12344,6 @@ "node_modules/chrome-trace-event": { "version": "1.0.4", "license": "MIT", - "peer": true, "engines": { "node": ">=6.0" } @@ -13365,8 +13372,7 @@ "node_modules/dom-accessibility-api": { "version": "0.5.16", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dom-helpers": { "version": "5.2.1", @@ -13480,6 +13486,7 @@ "node_modules/eciesjs": { "version": "0.4.16", "license": "MIT", + "peer": true, "dependencies": { "@ecies/ciphers": "^0.2.4", "@noble/ciphers": "^1.3.0", @@ -13519,7 +13526,8 @@ }, "node_modules/emoji-mart": { "version": "5.6.0", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/emoji-regex": { "version": "10.6.0", @@ -13602,7 +13610,6 @@ "node_modules/enhanced-resolve": { "version": "5.18.3", "license": "MIT", - "peer": true, "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -13745,8 +13752,7 @@ }, "node_modules/es-module-lexer": { "version": "1.7.0", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.1.1", @@ -13898,6 +13904,7 @@ "version": "9.39.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -14092,6 +14099,7 @@ "version": "2.32.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -14754,7 +14762,8 @@ }, "node_modules/eventemitter2": { "version": "6.4.9", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/eventemitter3": { "version": "5.0.1", @@ -14917,8 +14926,7 @@ "url": "https://opencollective.com/fastify" } ], - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/fastest-stable-stringify": { "version": "2.0.2", @@ -15439,8 +15447,7 @@ }, "node_modules/glob-to-regexp": { "version": "0.4.1", - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/glob/node_modules/minimatch": { "version": "10.1.1", @@ -16697,7 +16704,6 @@ "node_modules/isomorphic.js": { "version": "0.2.5", "license": "MIT", - "peer": true, "funding": { "type": "GitHub Sponsors ❤", "url": "https://github.com/sponsors/dmonad" @@ -20078,7 +20084,6 @@ "node_modules/lib0": { "version": "0.2.114", "license": "MIT", - "peer": true, "dependencies": { "isomorphic.js": "^0.2.4" }, @@ -20161,7 +20166,6 @@ "node_modules/loader-runner": { "version": "4.3.1", "license": "MIT", - "peer": true, "engines": { "node": ">=6.11.5" }, @@ -20246,7 +20250,6 @@ "version": "1.5.0", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -21348,6 +21351,7 @@ "resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz", "integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==", "license": "MIT", + "peer": true, "dependencies": { "@next/env": "16.1.6", "@swc/helpers": "0.5.15", @@ -22522,6 +22526,7 @@ "node_modules/porto": { "version": "0.2.35", "license": "MIT", + "peer": true, "dependencies": { "hono": "^4.10.3", "idb-keyval": "^6.2.1", @@ -22668,6 +22673,7 @@ "node_modules/porto/node_modules/zod": { "version": "4.1.13", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -22696,6 +22702,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -22832,6 +22839,7 @@ "version": "3.7.4", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -22923,7 +22931,6 @@ "version": "27.5.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -22937,7 +22944,6 @@ "version": "5.2.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -22948,8 +22954,7 @@ "node_modules/pretty-format/node_modules/react-is": { "version": "17.0.2", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/prismjs": { "version": "1.30.0", @@ -23006,6 +23011,7 @@ "node_modules/prop-types": { "version": "15.8.1", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -23292,7 +23298,6 @@ "node_modules/randombytes": { "version": "2.1.0", "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "^5.1.0" } @@ -23300,6 +23305,7 @@ "node_modules/react": { "version": "19.2.3", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -23344,6 +23350,7 @@ "node_modules/react-dom": { "version": "19.2.3", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -23397,6 +23404,7 @@ "node_modules/react-redux": { "version": "9.2.0", "license": "MIT", + "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -23561,6 +23569,7 @@ "node_modules/readable-stream": { "version": "3.6.2", "license": "MIT", + "peer": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -23611,7 +23620,8 @@ }, "node_modules/redux": { "version": "5.0.1", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -23633,7 +23643,8 @@ }, "node_modules/reflect-metadata": { "version": "0.2.2", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", @@ -23791,7 +23802,6 @@ "node_modules/require-from-string": { "version": "2.0.2", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -23920,6 +23930,7 @@ "node_modules/rollup": { "version": "4.53.3", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -24043,6 +24054,7 @@ "node_modules/rxjs": { "version": "7.8.2", "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -24135,6 +24147,7 @@ "node_modules/sass": { "version": "1.94.2", "license": "MIT", + "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -24192,7 +24205,6 @@ "node_modules/schema-utils": { "version": "4.3.3", "license": "MIT", - "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -24225,7 +24237,6 @@ "node_modules/schema-utils/node_modules/ajv-keywords": { "version": "5.1.0", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -24235,8 +24246,7 @@ }, "node_modules/schema-utils/node_modules/json-schema-traverse": { "version": "1.0.0", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/screenfull": { "version": "5.2.0", @@ -24291,7 +24301,6 @@ "node_modules/serialize-javascript": { "version": "6.0.2", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "randombytes": "^2.1.0" } @@ -24593,6 +24602,7 @@ "node_modules/socket.io-client": { "version": "4.8.1", "license": "MIT", + "peer": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", @@ -25238,6 +25248,7 @@ "node_modules/tailwindcss": { "version": "3.4.18", "license": "MIT", + "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -25297,6 +25308,7 @@ "node_modules/tailwindcss/node_modules/jiti": { "version": "1.21.7", "license": "MIT", + "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -25344,7 +25356,6 @@ "node_modules/tapable": { "version": "2.3.0", "license": "MIT", - "peer": true, "engines": { "node": ">=6" }, @@ -25356,7 +25367,6 @@ "node_modules/terser": { "version": "5.44.1", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -25373,7 +25383,6 @@ "node_modules/terser-webpack-plugin": { "version": "5.3.14", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", @@ -25406,7 +25415,6 @@ "node_modules/terser-webpack-plugin/node_modules/jest-worker": { "version": "27.5.1", "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -25419,7 +25427,6 @@ "node_modules/terser-webpack-plugin/node_modules/supports-color": { "version": "8.1.1", "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -25432,13 +25439,11 @@ }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/terser/node_modules/source-map-support": { "version": "0.5.21", "license": "MIT", - "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -25525,7 +25530,8 @@ }, "node_modules/three": { "version": "0.163.0", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/throttle-debounce": { "version": "3.0.1", @@ -25796,12 +25802,14 @@ }, "node_modules/tslib": { "version": "2.8.1", - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tsx": { "version": "4.21.0", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -26397,6 +26405,7 @@ "node_modules/typescript": { "version": "5.9.3", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -26861,6 +26870,7 @@ "version": "5.0.10", "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -26910,6 +26920,7 @@ "node_modules/valtio": { "version": "2.1.7", "license": "MIT", + "peer": true, "dependencies": { "proxy-compare": "^3.0.1" }, @@ -26962,6 +26973,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", @@ -27088,6 +27100,7 @@ "node_modules/wagmi": { "version": "2.19.5", "license": "MIT", + "peer": true, "dependencies": { "@wagmi/connectors": "6.2.0", "@wagmi/core": "2.22.1", @@ -27139,6 +27152,7 @@ "node_modules/wagmi/node_modules/@wagmi/core": { "version": "2.22.1", "license": "MIT", + "peer": true, "dependencies": { "eventemitter3": "5.0.1", "mipd": "0.0.7", @@ -27164,6 +27178,7 @@ "node_modules/wagmi/node_modules/use-sync-external-store": { "version": "1.4.0", "license": "MIT", + "peer": true, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } @@ -27221,7 +27236,6 @@ "node_modules/watchpack": { "version": "2.4.4", "license": "MIT", - "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -27256,7 +27270,6 @@ "node_modules/webpack": { "version": "5.103.0", "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -27314,7 +27327,6 @@ "node_modules/webpack/node_modules/eslint-scope": { "version": "5.1.1", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -27326,7 +27338,6 @@ "node_modules/webpack/node_modules/estraverse": { "version": "4.3.0", "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=4.0" } @@ -27517,6 +27528,7 @@ "node_modules/ws": { "version": "8.18.3", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -27589,6 +27601,7 @@ "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "devOptional": true, "license": "ISC", + "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -27651,6 +27664,7 @@ "node_modules/zod": { "version": "3.25.76", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" }