chore(deps): upgrade valtio to v2 (#48031)

Audited all proxy()/useSnapshot() usage against the v1→v2 migration
guide; no breaking changes apply (no reused proxy() inputs, no
promise-valued state, all consumers already client components).

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Updated the Valtio dependency to a newer version for improved
compatibility.

* **Bug Fixes**
* Improved AI assistant persistence in IndexedDB so chat sessions
reliably save (while keeping only the most recent 20 messages per chat).
* Hardened tabs restoration from storage to fall back to fresh defaults
when data is missing, invalid, or fails validation.

* **Refactor**
* Switched multiple studio panels to use fresh initial-state factories
for initialization and reset reliability.
* Updated advisor state so the derived notification filter count is no
longer exposed.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Ivan Vasilov authored and GitHub committed 2026-07-17 15:51:54 +02:00
1 parent 06c2039d0b
commit dc3c8684cc
9 files changed
+70 -80

No files matched your search

@@ -52,13 +52,13 @@ function createBucketFilePickerState({
export type BucketFilePickerState = ReturnType<typeof createBucketFilePickerState>
const DEFAULT_STATE_CONFIG = {
const createDefaultStateConfig = () => ({
bucket: {} as Bucket,
maxFiles: 1 as const,
}
})
const BucketFilePickerStateContext = createContext<BucketFilePickerState>(
createBucketFilePickerState(DEFAULT_STATE_CONFIG)
createBucketFilePickerState(createDefaultStateConfig())
)
export const BucketFilePickerStateContextProvider = ({
+4 -7
View File
@@ -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())
},
})
+24 -20
View File
@@ -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<string, ChatSession>
)
: {},
}
debouncedSaveAiState(stateToSave)
+4 -4
View File
@@ -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())
},
})
+3 -3
View File
@@ -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,
+4 -4
View File
@@ -1839,16 +1839,16 @@ function createStorageExplorerState({
export type StorageExplorerState = ReturnType<typeof createStorageExplorerState>
const DEFAULT_STATE_CONFIG = {
const createDefaultStateConfig = () => ({
projectRef: '',
connectionString: '',
resumableUploadUrl: '',
clientEndpoint: '',
bucket: {} as Bucket,
}
})
const StorageExplorerStateContext = createContext<StorageExplorerState>(
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 {
+9 -9
View File
@@ -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<string, Tab>,
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()
}
}
+18 -29
View File
@@ -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)
+1 -1
View File
@@ -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