diff --git a/apps/studio/components/interfaces/Storage/BucketFilePickerDialog/BucketFilePickerState.tsx b/apps/studio/components/interfaces/Storage/BucketFilePickerDialog/BucketFilePickerState.tsx index 7ff3c850a1d..c62f89ab258 100644 --- a/apps/studio/components/interfaces/Storage/BucketFilePickerDialog/BucketFilePickerState.tsx +++ b/apps/studio/components/interfaces/Storage/BucketFilePickerDialog/BucketFilePickerState.tsx @@ -52,13 +52,13 @@ function createBucketFilePickerState({ export type BucketFilePickerState = ReturnType -const DEFAULT_STATE_CONFIG = { +const createDefaultStateConfig = () => ({ bucket: {} as Bucket, maxFiles: 1 as const, -} +}) const BucketFilePickerStateContext = createContext( - createBucketFilePickerState(DEFAULT_STATE_CONFIG) + createBucketFilePickerState(createDefaultStateConfig()) ) export const BucketFilePickerStateContextProvider = ({ diff --git a/apps/studio/state/advisor-state.ts b/apps/studio/state/advisor-state.ts index 8bf49ba6a0b..adb66003ef7 100644 --- a/apps/studio/state/advisor-state.ts +++ b/apps/studio/state/advisor-state.ts @@ -4,7 +4,7 @@ export type AdvisorTab = 'all' | 'security' | 'performance' | 'messages' export type AdvisorSeverity = 'critical' | 'warning' | 'info' export type AdvisorItemSource = 'lint' | 'notification' | 'signal' -const initialState = { +const createInitialState = () => ({ activeTab: 'all' as AdvisorTab, severityFilters: ['critical', 'warning'] as AdvisorSeverity[], selectedItemId: undefined as string | undefined, @@ -12,13 +12,10 @@ const initialState = { // Notification filters notificationFilterStatuses: [] as string[], notificationFilterPriorities: [] as string[], - get numNotificationFiltersApplied() { - return [...this.notificationFilterStatuses, ...this.notificationFilterPriorities].length - }, -} +}) export const advisorState = proxy({ - ...initialState, + ...createInitialState(), setActiveTab(tab: AdvisorTab) { advisorState.activeTab = tab }, @@ -68,7 +65,7 @@ export const advisorState = proxy({ advisorState.notificationFilterPriorities = [] }, reset() { - Object.assign(advisorState, initialState) + Object.assign(advisorState, createInitialState()) }, }) diff --git a/apps/studio/state/ai-assistant-state.tsx b/apps/studio/state/ai-assistant-state.tsx index f7e59494d94..c083fa10e52 100644 --- a/apps/studio/state/ai-assistant-state.tsx +++ b/apps/studio/state/ai-assistant-state.tsx @@ -88,7 +88,7 @@ type StoredAiAssistantState = { model?: AssistantModel } -const INITIAL_AI_ASSISTANT: AiAssistantData = { +const createInitialAiAssistantData = (): AiAssistantData => ({ initialInput: '', sqlSnippets: undefined, suggestions: undefined, @@ -97,7 +97,7 @@ const INITIAL_AI_ASSISTANT: AiAssistantData = { activeChatId: undefined, model: undefined, context: {}, -} +}) const DB_NAME = 'ai-assistant-db' const DB_VERSION = 1 @@ -210,7 +210,7 @@ async function tryMigrateFromLocalStorage( projectRef: projectRef, activeChatId: parsedFromLocalStorage.activeChatId, chats: parsedFromLocalStorage.chats, - model: parsedFromLocalStorage.model ?? INITIAL_AI_ASSISTANT.model, + model: parsedFromLocalStorage.model ?? createInitialAiAssistantData().model, } } else { console.warn('Data in localStorage is not in the expected format, ignoring.') @@ -356,7 +356,7 @@ function createChatInstance( export const createAiAssistantState = (): AiAssistantState => { // Initialize with defaults, loading happens asynchronously in the provider - const initialState = { ...INITIAL_AI_ASSISTANT } + const initialState = createInitialAiAssistantData() const state: AiAssistantState = proxy({ ...initialState, // Spread initial values directly @@ -369,7 +369,7 @@ export const createAiAssistantState = (): AiAssistantState => { }, resetAiAssistantPanel: () => { - Object.assign(state, INITIAL_AI_ASSISTANT) + Object.assign(state, createInitialAiAssistantData()) }, setModel: (model: AssistantModel) => { @@ -414,10 +414,11 @@ export const createAiAssistantState = (): AiAssistantState => { } // Update non-chat related state based on options, falling back to current state, then initial - state.initialInput = options?.initialInput ?? INITIAL_AI_ASSISTANT.initialInput - state.sqlSnippets = options?.sqlSnippets ?? INITIAL_AI_ASSISTANT.sqlSnippets - state.suggestions = options?.suggestions ?? INITIAL_AI_ASSISTANT.suggestions - state.tables = options?.tables ?? INITIAL_AI_ASSISTANT.tables + const initialAiAssistantData = createInitialAiAssistantData() + state.initialInput = options?.initialInput ?? initialAiAssistantData.initialInput + state.sqlSnippets = options?.sqlSnippets ?? initialAiAssistantData.sqlSnippets + state.suggestions = options?.suggestions ?? initialAiAssistantData.suggestions + state.tables = options?.tables ?? initialAiAssistantData.tables return chatId }, @@ -561,7 +562,7 @@ export const createAiAssistantState = (): AiAssistantState => { state.model = storedModel && isKnownAssistantModelId(storedModel) ? storedModel - : INITIAL_AI_ASSISTANT.model + : createInitialAiAssistantData().model // Reset sync guards on any support chats (can't be mid-sync after reload) Object.values(state.chats).forEach((chat) => { @@ -698,22 +699,25 @@ export const AiAssistantStateContextProvider = ({ children }: PropsWithChildren) const unsubscribe = subscribe(state, () => { const snap = snapshot(state) + // Prepare state for IndexedDB const stateToSave: StoredAiAssistantState = { projectRef: project?.ref, activeChatId: snap.activeChatId, model: snap.model, chats: snap.chats - ? Object.entries(snap.chats).reduce((acc, [chatId, chat]) => { - // Limit messages before saving - return { - ...acc, - [chatId]: { - ...chat, - messages: chat.messages?.slice(-20) || [], - }, - } - }, {}) + ? (Object.entries(snap.chats) as Array<[string, ChatSession]>).reduce( + (acc, [chatId, chat]) => { + return { + ...acc, + [chatId]: { + ...chat, + messages: chat.messages?.slice(-20) || [], + }, + } + }, + {} as Record + ) : {}, } debouncedSaveAiState(stateToSave) diff --git a/apps/studio/state/editor-panel-state.tsx b/apps/studio/state/editor-panel-state.tsx index 79b41e586b5..723066a5d6e 100644 --- a/apps/studio/state/editor-panel-state.tsx +++ b/apps/studio/state/editor-panel-state.tsx @@ -24,7 +24,7 @@ type EditorPanelState = { pendingReset: boolean } -const initialState: EditorPanelState = { +const createInitialState = (): EditorPanelState => ({ value: safeSql``, templates: [], results: undefined, @@ -33,10 +33,10 @@ const initialState: EditorPanelState = { onChange: undefined, activeSnippetId: null, pendingReset: false, -} +}) export const editorPanelState = proxy({ - ...initialState, + ...createInitialState(), setValue(value: DisplayableSqlFragment) { editorPanelState.value = value editorPanelState.onChange?.(value) @@ -65,7 +65,7 @@ export const editorPanelState = proxy({ editorPanelState.pendingReset = true }, reset() { - Object.assign(editorPanelState, initialState) + Object.assign(editorPanelState, createInitialState()) }, }) diff --git a/apps/studio/state/sidebar-manager-state.tsx b/apps/studio/state/sidebar-manager-state.tsx index 1a3f1f4ac59..77a978b8f15 100644 --- a/apps/studio/state/sidebar-manager-state.tsx +++ b/apps/studio/state/sidebar-manager-state.tsx @@ -36,16 +36,16 @@ type SidebarManagerState = SidebarManagerData & { toggleMaximise: () => void } -const INITIAL_SIDEBAR_MANAGER_DATA: SidebarManagerData = { +const createInitialSidebarManagerData = (): SidebarManagerData => ({ sidebars: {}, activeSidebar: undefined, pendingSidebarOpen: undefined, isMaximised: false, -} +}) const createSidebarManagerState = () => { const state: SidebarManagerState = proxy({ - ...INITIAL_SIDEBAR_MANAGER_DATA, + ...createInitialSidebarManagerData(), registerSidebar( id: string, diff --git a/apps/studio/state/storage-explorer.tsx b/apps/studio/state/storage-explorer.tsx index 1bb58b10c3f..d4fd8a84691 100644 --- a/apps/studio/state/storage-explorer.tsx +++ b/apps/studio/state/storage-explorer.tsx @@ -1839,16 +1839,16 @@ function createStorageExplorerState({ export type StorageExplorerState = ReturnType -const DEFAULT_STATE_CONFIG = { +const createDefaultStateConfig = () => ({ projectRef: '', connectionString: '', resumableUploadUrl: '', clientEndpoint: '', bucket: {} as Bucket, -} +}) const StorageExplorerStateContext = createContext( - createStorageExplorerState(DEFAULT_STATE_CONFIG) + createStorageExplorerState(createDefaultStateConfig()) ) export const StorageExplorerStateContextProvider = ({ children }: PropsWithChildren) => { @@ -1856,7 +1856,7 @@ export const StorageExplorerStateContextProvider = ({ children }: PropsWithChild const { data: bucket } = useSelectedBucket() const isPaused = project?.status === PROJECT_STATUS.INACTIVE - const [state, setState] = useState(() => createStorageExplorerState(DEFAULT_STATE_CONFIG)) + const [state, setState] = useState(() => createStorageExplorerState(createDefaultStateConfig())) const stateRef = useLatest(state) const { diff --git a/apps/studio/state/tabs.tsx b/apps/studio/state/tabs.tsx index 2b72256579b..c0ef9b95fe2 100644 --- a/apps/studio/state/tabs.tsx +++ b/apps/studio/state/tabs.tsx @@ -116,27 +116,27 @@ function getSavedRecentItems(ref: string): RecentItem[] { } } -const DEFAULT_TABS_STATE = { +const createDefaultTabsState = () => ({ activeTab: null as string | null, openTabs: [] as string[], tabsMap: {} as Record, previewTabId: undefined as string | undefined, recentItems: [], -} +}) const TABS_STORAGE_KEY = 'supabase_studio_tabs' const getTabsStorageKey = (ref: string) => `${TABS_STORAGE_KEY}_${ref}` function getSavedTabs(ref: string) { - if (!ref) return DEFAULT_TABS_STATE + if (!ref) return createDefaultTabsState() const stored = safeLocalStorage.getItem(getTabsStorageKey(ref)) - if (!stored) return DEFAULT_TABS_STATE + if (!stored) return createDefaultTabsState() try { - const parsed = JSON.parse( - stored ?? JSON.stringify(DEFAULT_TABS_STATE) - ) as typeof DEFAULT_TABS_STATE + const parsed = JSON.parse(stored ?? JSON.stringify(createDefaultTabsState())) as ReturnType< + typeof createDefaultTabsState + > if ( !parsed.openTabs || @@ -144,12 +144,12 @@ function getSavedTabs(ref: string) { !parsed.tabsMap || typeof parsed.tabsMap !== 'object' ) { - return DEFAULT_TABS_STATE + return createDefaultTabsState() } return parsed } catch (error) { - return DEFAULT_TABS_STATE + return createDefaultTabsState() } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d816ed3c30e..28c11dee308 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -88,8 +88,8 @@ catalogs: specifier: ~6.0.2 version: 6.0.2 valtio: - specifier: ^1.12.0 - version: 1.12.0 + specifier: ^2.3.2 + version: 2.3.2 vite: specifier: ^8.0.16 version: 8.0.16 @@ -581,7 +581,7 @@ importers: version: 14.0.0 valtio: specifier: 'catalog:' - version: 1.12.0(@types/react@19.2.14)(react@19.2.6) + version: 2.3.2(@types/react@19.2.14)(react@19.2.6) yaml: specifier: ^2.8.1 version: 2.9.0 @@ -1229,7 +1229,7 @@ importers: version: 14.0.0 valtio: specifier: 'catalog:' - version: 1.12.0(@types/react@19.2.14)(react@19.2.6) + version: 2.3.2(@types/react@19.2.14)(react@19.2.6) zod: specifier: 'catalog:' version: 3.25.76 @@ -2180,7 +2180,7 @@ importers: version: 17.6.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) valtio: specifier: 'catalog:' - version: 1.12.0(@types/react@19.2.14)(react@19.2.6) + version: 2.3.2(@types/react@19.2.14)(react@19.2.6) devDependencies: '@types/lodash': specifier: 4.17.5 @@ -2742,7 +2742,7 @@ importers: version: 5.0.0 valtio: specifier: 'catalog:' - version: 1.12.0(@types/react@19.2.14)(react@19.2.6) + version: 2.3.2(@types/react@19.2.14)(react@19.2.6) zod: specifier: 'catalog:' version: 3.25.76 @@ -10605,11 +10605,6 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - derive-valtio@0.1.0: - resolution: {integrity: sha512-OCg2UsLbXK7GmmpzMXhYkdO64vhJ1ROUUGaTFyHjVwEdMEcTTRj7W1TxLbSBxdY8QLBPCcp66MTyaSy0RpO17A==} - peerDependencies: - valtio: '*' - destr@2.0.5: resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} @@ -14988,8 +14983,8 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} - proxy-compare@2.5.1: - resolution: {integrity: sha512-oyfc0Tx87Cpwva5ZXezSp5V9vht1c7dZBhvuV/y3ctkgMVUmiAGDVeeB0dKhGSyT0v1ZTEQYpe/RXlBVBNuCLA==} + proxy-compare@3.0.1: + resolution: {integrity: sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q==} proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} @@ -17042,12 +17037,12 @@ packages: validate.io-function@1.0.2: resolution: {integrity: sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ==} - valtio@1.12.0: - resolution: {integrity: sha512-co8NkCHeY0NsL0XsL/cSICt5VhTjwZlYT8mi50dYY5thx3r3w1D15A04Lvs9WL/y/Rf98vUKY5PAAJCTLHvkJw==} + valtio@2.3.2: + resolution: {integrity: sha512-YXhWQei9IN/ZDce9rhL3trCq9+vVq8M1gWmKVdP3YSZ2gxsmmNWVbxXwf9yG6ffu/dAvAD91nevg8xirGr4Dhg==} engines: {node: '>=12.20.0'} peerDependencies: - '@types/react': '>=16.8' - react: '>=16.8' + '@types/react': '>=18.0.0' + react: '>=18.0.0' peerDependenciesMeta: '@types/react': optional: true @@ -24143,7 +24138,7 @@ snapshots: srvx: 0.11.16 tinyglobby: 0.2.17 ufo: 1.6.4 - vitefu: 1.1.1(vite@8.0.16(@types/node@22.13.14)(jiti@2.7.0)(sass@1.77.4)(terser@5.39.0)(tsx@4.22.4)(yaml@2.9.0)) + vitefu: 1.1.1(vite@8.0.16(@types/node@22.13.14)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.77.4)(terser@5.39.0)(tsx@4.22.4)(yaml@2.9.0)) xmlbuilder2: 4.0.3 zod: 4.4.3 optionalDependencies: @@ -24176,7 +24171,7 @@ snapshots: srvx: 0.11.16 tinyglobby: 0.2.17 ufo: 1.6.4 - vitefu: 1.1.1(vite@8.0.16(@types/node@22.13.14)(jiti@2.7.0)(sass@1.77.4)(terser@5.39.0)(tsx@4.22.4)(yaml@2.9.0)) + vitefu: 1.1.1(vite@8.0.16(@types/node@22.13.14)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.77.4)(terser@5.39.0)(tsx@4.22.4)(yaml@2.9.0)) xmlbuilder2: 4.0.3 zod: 4.4.3 optionalDependencies: @@ -26886,10 +26881,6 @@ snapshots: dequal@2.0.3: {} - derive-valtio@0.1.0(valtio@1.12.0(@types/react@19.2.14)(react@19.2.6)): - dependencies: - valtio: 1.12.0(@types/react@19.2.14)(react@19.2.6) - destr@2.0.5: {} destroy@1.2.0: {} @@ -32412,7 +32403,7 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 - proxy-compare@2.5.1: {} + proxy-compare@3.0.1: {} proxy-from-env@1.1.0: {} @@ -34893,11 +34884,9 @@ snapshots: validate.io-function@1.0.2: {} - valtio@1.12.0(@types/react@19.2.14)(react@19.2.6): + valtio@2.3.2(@types/react@19.2.14)(react@19.2.6): dependencies: - derive-valtio: 0.1.0(valtio@1.12.0(@types/react@19.2.14)(react@19.2.6)) - proxy-compare: 2.5.1 - use-sync-external-store: 1.2.0(react@19.2.6) + proxy-compare: 3.0.1 optionalDependencies: '@types/react': 19.2.14 react: 19.2.6 @@ -35129,7 +35118,7 @@ snapshots: tsx: 4.22.4 yaml: 2.9.0 - vitefu@1.1.1(vite@8.0.16(@types/node@22.13.14)(jiti@2.7.0)(sass@1.77.4)(terser@5.39.0)(tsx@4.22.4)(yaml@2.9.0)): + vitefu@1.1.1(vite@8.0.16(@types/node@22.13.14)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.77.4)(terser@5.39.0)(tsx@4.22.4)(yaml@2.9.0)): optionalDependencies: vite: 8.0.16(@types/node@22.13.14)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.77.4)(terser@5.39.0)(tsx@4.22.4)(yaml@2.9.0) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 44b5c9e1b6a..17cbf3f5abb 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -43,7 +43,7 @@ catalog: tailwindcss: ^4.2.4 tsx: ^4.22.0 typescript: ~6.0.2 - valtio: ^1.12.0 + valtio: ^2.3.2 vite: ^8.0.16 vite-tsconfig-paths: ^6.1.1 vitest: ^4.1.4