From e88b1872b9de4ad6cc75644a41d4ee72437d58b8 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Thu, 11 Sep 2025 11:06:10 +0200 Subject: [PATCH 1/4] fix: remove invalid plugins from active plugins on mount --- .../src/context/devtools-context.test.ts | 56 +++++++++++++++++++ .../devtools/src/context/devtools-context.tsx | 37 +++++++++--- .../devtools/src/context/devtools-store.ts | 4 +- pnpm-lock.yaml | 2 + 4 files changed, 90 insertions(+), 9 deletions(-) create mode 100644 packages/devtools/src/context/devtools-context.test.ts diff --git a/packages/devtools/src/context/devtools-context.test.ts b/packages/devtools/src/context/devtools-context.test.ts new file mode 100644 index 00000000..91dd5b98 --- /dev/null +++ b/packages/devtools/src/context/devtools-context.test.ts @@ -0,0 +1,56 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { TANSTACK_DEVTOOLS_STATE } from "../utils/storage"; +import { getStateFromLocalStorage } from "./devtools-context"; + + +describe('getStateFromLocalStorage', () => { + beforeEach(() => { + localStorage.clear(); + }); + it('should return undefined when no data in localStorage', () => { + const state = getStateFromLocalStorage(); + expect(state).toEqual(undefined); + }); + it('should return parsed state from localStorage and not remove valid plugins', () => { + const mockState = { + activePlugins: ['plugin1'], + settings: { + theme: 'dark', + }, + }; + localStorage.setItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(mockState)); + const state = getStateFromLocalStorage([{ + id: 'plugin1', render: () => { }, name: 'Plugin 1' + }]); + expect(state).toEqual(mockState); + }); + it('should filter out inactive plugins', () => { + const mockState = { + activePlugins: ['plugin1', 'plugin2'], + settings: { + theme: 'dark', + }, + }; + localStorage.setItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(mockState)); + const plugins = [{ id: 'plugin1', render: () => { }, name: 'Plugin 1' }]; + const state = getStateFromLocalStorage(plugins); + expect(state?.activePlugins).toEqual(['plugin1']); + }); + it('should return empty plugin state if all active plugins are invalid', () => { + const mockState = { + activePlugins: ['plugin1', 'plugin2'], + settings: { + theme: 'dark', + }, + }; + localStorage.setItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(mockState)); + const plugins = [{ id: 'plugin3', render: () => { }, name: 'Plugin 3' }]; + const state = getStateFromLocalStorage(plugins); + expect(state?.activePlugins).toEqual([]); + }); + it('should handle invalid JSON in localStorage gracefully', () => { + localStorage.setItem(TANSTACK_DEVTOOLS_STATE, "invalid json"); + const state = getStateFromLocalStorage(); + expect(state).toEqual(undefined); + }); +}); \ No newline at end of file diff --git a/packages/devtools/src/context/devtools-context.tsx b/packages/devtools/src/context/devtools-context.tsx index b3da5a53..dbe351b6 100644 --- a/packages/devtools/src/context/devtools-context.tsx +++ b/packages/devtools/src/context/devtools-context.tsx @@ -39,11 +39,11 @@ export interface TanStackDevtoolsPlugin { * ``` */ name: - | string - | (( - el: HTMLHeadingElement, - theme: DevtoolsStore['settings']['theme'], - ) => void) + | string + | (( + el: HTMLHeadingElement, + theme: DevtoolsStore['settings']['theme'], + ) => void) /** * Unique identifier for the plugin. * If not provided, it will be generated based on the name. @@ -99,11 +99,34 @@ const generatePluginId = (plugin: TanStackDevtoolsPlugin, index: number) => { return index.toString() } +export function getStateFromLocalStorage(plugins?: Array) { + const existingStateString = getStorageItem(TANSTACK_DEVTOOLS_STATE) + const existingState = tryParseJson(existingStateString) + const pluginIds = plugins?.map((plugin, i) => generatePluginId(plugin, i)) || [] + if (existingState?.activePlugins) { + const originalLength = existingState.activePlugins.length + // Filter out any active plugins that are no longer available + existingState.activePlugins = existingState.activePlugins.filter((id) => + pluginIds.includes(id), + ) + + if (existingState.activePlugins.length !== originalLength) { + // If any active plugins were removed, update local storage + setStorageItem( + TANSTACK_DEVTOOLS_STATE, + JSON.stringify(existingState), + ) + } + } + + return existingState +} + const getExistingStateFromStorage = ( config?: TanStackDevtoolsConfig, plugins?: Array, ) => { - const existingState = getStorageItem(TANSTACK_DEVTOOLS_STATE) + const existingState = getStateFromLocalStorage() const settings = getSettings() const state: DevtoolsStore = { @@ -118,7 +141,7 @@ const getExistingStateFromStorage = ( }) || [], state: { ...initialState.state, - ...(existingState ? JSON.parse(existingState) : {}), + ...existingState, }, settings: { ...initialState.settings, diff --git a/packages/devtools/src/context/devtools-store.ts b/packages/devtools/src/context/devtools-store.ts index ddc5ebb2..debfaf7e 100644 --- a/packages/devtools/src/context/devtools-store.ts +++ b/packages/devtools/src/context/devtools-store.ts @@ -81,8 +81,8 @@ export const initialState: DevtoolsStore = { requireUrlFlag: false, urlFlag: 'tanstack-devtools', theme: - typeof window !== 'undefined' && - window.matchMedia('(prefers-color-scheme: dark)').matches + typeof window !== 'undefined' && typeof window.matchMedia !== "undefined" && + window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light', }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9f11877..96c694db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -359,6 +359,8 @@ importers: specifier: ^4.2.4 version: 4.2.4 + examples/react/start/generated/prisma: {} + examples/react/time-travel: dependencies: '@tanstack/devtools-event-client': From 1894b7e6af88e9a532e80d9993605794a1e45f32 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 09:07:01 +0000 Subject: [PATCH 2/4] ci: apply automated fixes --- .../src/context/devtools-context.test.ts | 69 ++++++++++--------- .../devtools/src/context/devtools-context.tsx | 25 +++---- .../devtools/src/context/devtools-store.ts | 5 +- 3 files changed, 52 insertions(+), 47 deletions(-) diff --git a/packages/devtools/src/context/devtools-context.test.ts b/packages/devtools/src/context/devtools-context.test.ts index 91dd5b98..4496bad8 100644 --- a/packages/devtools/src/context/devtools-context.test.ts +++ b/packages/devtools/src/context/devtools-context.test.ts @@ -1,56 +1,59 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; -import { TANSTACK_DEVTOOLS_STATE } from "../utils/storage"; -import { getStateFromLocalStorage } from "./devtools-context"; - +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { TANSTACK_DEVTOOLS_STATE } from '../utils/storage' +import { getStateFromLocalStorage } from './devtools-context' describe('getStateFromLocalStorage', () => { beforeEach(() => { - localStorage.clear(); - }); + localStorage.clear() + }) it('should return undefined when no data in localStorage', () => { - const state = getStateFromLocalStorage(); - expect(state).toEqual(undefined); - }); + const state = getStateFromLocalStorage() + expect(state).toEqual(undefined) + }) it('should return parsed state from localStorage and not remove valid plugins', () => { const mockState = { activePlugins: ['plugin1'], settings: { theme: 'dark', }, - }; - localStorage.setItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(mockState)); - const state = getStateFromLocalStorage([{ - id: 'plugin1', render: () => { }, name: 'Plugin 1' - }]); - expect(state).toEqual(mockState); - }); + } + localStorage.setItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(mockState)) + const state = getStateFromLocalStorage([ + { + id: 'plugin1', + render: () => {}, + name: 'Plugin 1', + }, + ]) + expect(state).toEqual(mockState) + }) it('should filter out inactive plugins', () => { const mockState = { activePlugins: ['plugin1', 'plugin2'], settings: { theme: 'dark', }, - }; - localStorage.setItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(mockState)); - const plugins = [{ id: 'plugin1', render: () => { }, name: 'Plugin 1' }]; - const state = getStateFromLocalStorage(plugins); - expect(state?.activePlugins).toEqual(['plugin1']); - }); + } + localStorage.setItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(mockState)) + const plugins = [{ id: 'plugin1', render: () => {}, name: 'Plugin 1' }] + const state = getStateFromLocalStorage(plugins) + expect(state?.activePlugins).toEqual(['plugin1']) + }) it('should return empty plugin state if all active plugins are invalid', () => { const mockState = { activePlugins: ['plugin1', 'plugin2'], settings: { theme: 'dark', }, - }; - localStorage.setItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(mockState)); - const plugins = [{ id: 'plugin3', render: () => { }, name: 'Plugin 3' }]; - const state = getStateFromLocalStorage(plugins); - expect(state?.activePlugins).toEqual([]); - }); + } + localStorage.setItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(mockState)) + const plugins = [{ id: 'plugin3', render: () => {}, name: 'Plugin 3' }] + const state = getStateFromLocalStorage(plugins) + expect(state?.activePlugins).toEqual([]) + }) it('should handle invalid JSON in localStorage gracefully', () => { - localStorage.setItem(TANSTACK_DEVTOOLS_STATE, "invalid json"); - const state = getStateFromLocalStorage(); - expect(state).toEqual(undefined); - }); -}); \ No newline at end of file + localStorage.setItem(TANSTACK_DEVTOOLS_STATE, 'invalid json') + const state = getStateFromLocalStorage() + expect(state).toEqual(undefined) + }) +}) diff --git a/packages/devtools/src/context/devtools-context.tsx b/packages/devtools/src/context/devtools-context.tsx index dbe351b6..efb7ecb8 100644 --- a/packages/devtools/src/context/devtools-context.tsx +++ b/packages/devtools/src/context/devtools-context.tsx @@ -39,11 +39,11 @@ export interface TanStackDevtoolsPlugin { * ``` */ name: - | string - | (( - el: HTMLHeadingElement, - theme: DevtoolsStore['settings']['theme'], - ) => void) + | string + | (( + el: HTMLHeadingElement, + theme: DevtoolsStore['settings']['theme'], + ) => void) /** * Unique identifier for the plugin. * If not provided, it will be generated based on the name. @@ -99,10 +99,14 @@ const generatePluginId = (plugin: TanStackDevtoolsPlugin, index: number) => { return index.toString() } -export function getStateFromLocalStorage(plugins?: Array) { +export function getStateFromLocalStorage( + plugins?: Array, +) { const existingStateString = getStorageItem(TANSTACK_DEVTOOLS_STATE) - const existingState = tryParseJson(existingStateString) - const pluginIds = plugins?.map((plugin, i) => generatePluginId(plugin, i)) || [] + const existingState = + tryParseJson(existingStateString) + const pluginIds = + plugins?.map((plugin, i) => generatePluginId(plugin, i)) || [] if (existingState?.activePlugins) { const originalLength = existingState.activePlugins.length // Filter out any active plugins that are no longer available @@ -112,10 +116,7 @@ export function getStateFromLocalStorage(plugins?: Array if (existingState.activePlugins.length !== originalLength) { // If any active plugins were removed, update local storage - setStorageItem( - TANSTACK_DEVTOOLS_STATE, - JSON.stringify(existingState), - ) + setStorageItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(existingState)) } } diff --git a/packages/devtools/src/context/devtools-store.ts b/packages/devtools/src/context/devtools-store.ts index debfaf7e..e7e077ab 100644 --- a/packages/devtools/src/context/devtools-store.ts +++ b/packages/devtools/src/context/devtools-store.ts @@ -81,8 +81,9 @@ export const initialState: DevtoolsStore = { requireUrlFlag: false, urlFlag: 'tanstack-devtools', theme: - typeof window !== 'undefined' && typeof window.matchMedia !== "undefined" && - window.matchMedia('(prefers-color-scheme: dark)').matches + typeof window !== 'undefined' && + typeof window.matchMedia !== 'undefined' && + window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light', }, From 39d96830295d2e1c2f35435c5b8fcc3ac15c34d1 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Thu, 11 Sep 2025 11:12:21 +0200 Subject: [PATCH 3/4] fix: remove invalid plugins from active plugins on mount --- packages/devtools/src/context/devtools-context.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/devtools/src/context/devtools-context.test.ts b/packages/devtools/src/context/devtools-context.test.ts index 91dd5b98..e137b563 100644 --- a/packages/devtools/src/context/devtools-context.test.ts +++ b/packages/devtools/src/context/devtools-context.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { beforeEach, describe, expect, it, } from "vitest"; import { TANSTACK_DEVTOOLS_STATE } from "../utils/storage"; import { getStateFromLocalStorage } from "./devtools-context"; From f4114f742af0d87c233395717572135f7981c6c3 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 09:13:34 +0000 Subject: [PATCH 4/4] ci: apply automated fixes --- .../devtools/src/context/devtools-context.test.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/devtools/src/context/devtools-context.test.ts b/packages/devtools/src/context/devtools-context.test.ts index 6a8e31dc..5ebb4520 100644 --- a/packages/devtools/src/context/devtools-context.test.ts +++ b/packages/devtools/src/context/devtools-context.test.ts @@ -1,7 +1,6 @@ -import { beforeEach, describe, expect, it, } from "vitest"; -import { TANSTACK_DEVTOOLS_STATE } from "../utils/storage"; -import { getStateFromLocalStorage } from "./devtools-context"; - +import { beforeEach, describe, expect, it } from 'vitest' +import { TANSTACK_DEVTOOLS_STATE } from '../utils/storage' +import { getStateFromLocalStorage } from './devtools-context' describe('getStateFromLocalStorage', () => { beforeEach(() => { @@ -22,7 +21,7 @@ describe('getStateFromLocalStorage', () => { const state = getStateFromLocalStorage([ { id: 'plugin1', - render: () => { }, + render: () => {}, name: 'Plugin 1', }, ]) @@ -36,7 +35,7 @@ describe('getStateFromLocalStorage', () => { }, } localStorage.setItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(mockState)) - const plugins = [{ id: 'plugin1', render: () => { }, name: 'Plugin 1' }] + const plugins = [{ id: 'plugin1', render: () => {}, name: 'Plugin 1' }] const state = getStateFromLocalStorage(plugins) expect(state?.activePlugins).toEqual(['plugin1']) }) @@ -48,7 +47,7 @@ describe('getStateFromLocalStorage', () => { }, } localStorage.setItem(TANSTACK_DEVTOOLS_STATE, JSON.stringify(mockState)) - const plugins = [{ id: 'plugin3', render: () => { }, name: 'Plugin 3' }] + const plugins = [{ id: 'plugin3', render: () => {}, name: 'Plugin 3' }] const state = getStateFromLocalStorage(plugins) expect(state?.activePlugins).toEqual([]) })