Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/thirty-spies-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@tanstack/preact-devtools': minor
'@tanstack/devtools-utils': minor
'@tanstack/react-devtools': minor
'@tanstack/solid-devtools': minor
'@tanstack/devtools': minor
---

Change the way props are passed to the plugins
Comment on lines +2 to +9
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

This API change needs major version bumps.

The plugin callback contract now changes from (el, theme) to (el, { theme, devtoolsOpen }), which breaks existing plugin implementations at both type level and runtime. Shipping these packages as minor will understate the break for consumers.

Suggested changeset update
-'@tanstack/preact-devtools': minor
-'@tanstack/devtools-utils': minor
-'@tanstack/react-devtools': minor
-'@tanstack/solid-devtools': minor
-'@tanstack/devtools': minor
+'@tanstack/preact-devtools': major
+'@tanstack/devtools-utils': major
+'@tanstack/react-devtools': major
+'@tanstack/solid-devtools': major
+'@tanstack/devtools': major
@@
-Change the way props are passed to the plugins
+BREAKING: plugin callbacks now receive `TanStackDevtoolsPluginProps` (`theme` + `devtoolsOpen`) instead of a theme string
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
'@tanstack/preact-devtools': minor
'@tanstack/devtools-utils': minor
'@tanstack/react-devtools': minor
'@tanstack/solid-devtools': minor
'@tanstack/devtools': minor
---
Change the way props are passed to the plugins
'@tanstack/preact-devtools': major
'@tanstack/devtools-utils': major
'@tanstack/react-devtools': major
'@tanstack/solid-devtools': major
'@tanstack/devtools': major
---
BREAKING: plugin callbacks now receive `TanStackDevtoolsPluginProps` (`theme` + `devtoolsOpen`) instead of a theme string
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.changeset/thirty-spies-fetch.md around lines 2 - 9, Update the changeset to
mark a breaking change by changing the release type from minor to major for each
listed package ('@tanstack/preact-devtools', '@tanstack/devtools-utils',
'@tanstack/react-devtools', '@tanstack/solid-devtools', '@tanstack/devtools'),
and update the summary to state the API change explicitly: the plugin callback
signature changed from (el, theme) to (el, { theme, devtoolsOpen }), so
consumers must update implementations; ensure the changeset text clearly
documents this breaking change so the packages will be released with a major
version bump.

11 changes: 6 additions & 5 deletions packages/devtools-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@
"dependencies": {
"@tanstack/devtools-ui": "workspace:^"
},
"devDependencies": {
"@tanstack/devtools": "workspace:^",
"tsup": "^8.5.0",
"tsup-preset-solid": "^2.2.0",
"vite-plugin-solid": "^2.11.8"
},
"peerDependencies": {
"@types/react": ">=17.0.0",
"preact": ">=10.0.0",
Expand Down Expand Up @@ -114,10 +120,5 @@
"test:types": "tsc",
"test:build": "publint --strict",
"build": "vite build && vite build --config vite.config.preact.ts && vite build --config vite.config.vue.ts && vite build --config vite.config.solid-class.ts && tsup"
},
"devDependencies": {
"tsup": "^8.5.0",
"tsup-preset-solid": "^2.2.0",
"vite-plugin-solid": "^2.11.8"
}
}
13 changes: 6 additions & 7 deletions packages/devtools-utils/src/preact/panel.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
/** @jsxImportSource preact */

import { useEffect, useRef } from 'preact/hooks'
import type { TanStackDevtoolsPluginProps } from '@tanstack/devtools'

export interface DevtoolsPanelProps {
theme?: 'light' | 'dark'
}
export interface DevtoolsPanelProps extends TanStackDevtoolsPluginProps {}

/**
* Creates a Preact component that dynamically imports and mounts a devtools panel. SSR friendly.
Expand All @@ -24,9 +23,9 @@ export interface DevtoolsPanelProps {
* ```
*/
export function createPreactPanel<
TComponentProps extends DevtoolsPanelProps | undefined,
TComponentProps extends DevtoolsPanelProps,
TCoreDevtoolsClass extends {
mount: (el: HTMLElement, theme: 'light' | 'dark') => void
mount: (el: HTMLElement, props: TanStackDevtoolsPluginProps) => void
unmount: () => void
},
>(CoreClass: new () => TCoreDevtoolsClass) {
Expand All @@ -38,7 +37,7 @@ export function createPreactPanel<
devtools.current = new CoreClass()

if (devToolRef.current) {
devtools.current.mount(devToolRef.current, props?.theme ?? 'dark')
devtools.current.mount(devToolRef.current, props)
}

return () => {
Expand All @@ -47,7 +46,7 @@ export function createPreactPanel<
devtools.current = null
}
}
}, [props?.theme])
}, [props])

return <div style={{ height: '100%' }} ref={devToolRef} />
}
Expand Down
7 changes: 4 additions & 3 deletions packages/devtools-utils/src/preact/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import type { JSX } from 'preact'
import type { DevtoolsPanelProps } from './panel'
import type { TanStackDevtoolsPluginProps } from '@tanstack/devtools'

export function createPreactPlugin({
Component,
Expand All @@ -15,15 +16,15 @@ export function createPreactPlugin({
function Plugin() {
return {
...config,
render: (_el: HTMLElement, theme: 'light' | 'dark') => (
<Component theme={theme} />
render: (_el: HTMLElement, props: TanStackDevtoolsPluginProps) => (
<Component {...props} />
),
}
}
function NoOpPlugin() {
return {
...config,
render: (_el: HTMLElement, _theme: 'light' | 'dark') => <></>,
render: (_el: HTMLElement, _props: TanStackDevtoolsPluginProps) => <></>,
}
}
return [Plugin, NoOpPlugin] as const
Expand Down
13 changes: 6 additions & 7 deletions packages/devtools-utils/src/react/panel.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { useEffect, useRef } from 'react'
import type { TanStackDevtoolsPluginProps } from '@tanstack/devtools'

export interface DevtoolsPanelProps {
theme?: 'light' | 'dark'
}
export interface DevtoolsPanelProps extends TanStackDevtoolsPluginProps {}

/**
* Creates a React component that dynamically imports and mounts a devtools panel. SSR friendly.
Expand All @@ -22,9 +21,9 @@ export interface DevtoolsPanelProps {
* ```
*/
export function createReactPanel<
TComponentProps extends DevtoolsPanelProps | undefined,
TComponentProps extends TanStackDevtoolsPluginProps,
TCoreDevtoolsClass extends {
mount: (el: HTMLElement, theme: 'light' | 'dark') => void
mount: (el: HTMLElement, props: TanStackDevtoolsPluginProps) => void
unmount: () => void
},
>(CoreClass: new () => TCoreDevtoolsClass) {
Expand All @@ -36,7 +35,7 @@ export function createReactPanel<
devtools.current = new CoreClass()

if (devToolRef.current) {
devtools.current.mount(devToolRef.current, props?.theme ?? 'dark')
devtools.current.mount(devToolRef.current, props)
}

return () => {
Expand All @@ -45,7 +44,7 @@ export function createReactPanel<
devtools.current = null
}
}
}, [props?.theme])
}, [props])

return <div style={{ height: '100%' }} ref={devToolRef} />
}
Expand Down
7 changes: 4 additions & 3 deletions packages/devtools-utils/src/react/plugin.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { JSX } from 'react'
import type { DevtoolsPanelProps } from './panel'
import type { TanStackDevtoolsPluginProps } from '@tanstack/devtools'

export function createReactPlugin({
Component,
Expand All @@ -13,15 +14,15 @@ export function createReactPlugin({
function Plugin() {
return {
...config,
render: (_el: HTMLElement, theme: 'light' | 'dark') => (
<Component theme={theme} />
render: (_el: HTMLElement, props: TanStackDevtoolsPluginProps) => (
<Component {...props} />
),
}
}
function NoOpPlugin() {
return {
...config,
render: (_el: HTMLElement, _theme: 'light' | 'dark') => <></>,
render: (_el: HTMLElement, _props: TanStackDevtoolsPluginProps) => <></>,
}
}
return [Plugin, NoOpPlugin] as const
Expand Down
7 changes: 4 additions & 3 deletions packages/devtools-utils/src/solid/class-mount-impl.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
/** @jsxImportSource solid-js - we use Solid.js as JSX here */

import type { TanStackDevtoolsPluginProps } from '@tanstack/devtools'
import { lazy } from 'solid-js'
import { Portal, render } from 'solid-js/web'
import type { JSX } from 'solid-js'
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

export function __mountComponent(
el: HTMLElement,
theme: 'light' | 'dark',
props: TanStackDevtoolsPluginProps,
importFn: () => Promise<{ default: () => JSX.Element }>,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
): () => void {
const Component = lazy(importFn)
Expand All @@ -20,8 +21,8 @@ export function __mountComponent(
() => (
<Portal mount={el}>
<div style={{ height: '100%' }}>
<ThemeProvider theme={theme}>
<Component />
<ThemeProvider theme={props.theme}>
<Component {...props} />
</ThemeProvider>
</div>
</Portal>
Expand Down
55 changes: 43 additions & 12 deletions packages/devtools-utils/src/solid/class.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,23 @@ describe('constructCoreClass', () => {
const [DevtoolsCore] = constructCoreClass(importFn)
const instance = new DevtoolsCore()
const el = document.createElement('div')
await instance.mount(el, 'dark')
expect(mountComponentMock).toHaveBeenCalledWith(el, 'dark', importFn)
const props = { theme: 'dark' as const, devtoolsOpen: true }
await instance.mount(el, props)
expect(mountComponentMock).toHaveBeenCalledWith(el, props, importFn)
})

it('DevtoolsCore should throw if mount is called twice without unmounting', async () => {
const [DevtoolsCore] = constructCoreClass(importFn)
const instance = new DevtoolsCore()
await instance.mount(document.createElement('div'), 'dark')
await instance.mount(document.createElement('div'), {
theme: 'dark',
devtoolsOpen: true,
})
await expect(
instance.mount(document.createElement('div'), 'dark'),
instance.mount(document.createElement('div'), {
theme: 'dark',
devtoolsOpen: true,
}),
).rejects.toThrow('Devtools is already mounted')
})

Expand All @@ -50,25 +57,37 @@ describe('constructCoreClass', () => {
it('DevtoolsCore should allow mount after unmount', async () => {
const [DevtoolsCore] = constructCoreClass(importFn)
const instance = new DevtoolsCore()
await instance.mount(document.createElement('div'), 'dark')
await instance.mount(document.createElement('div'), {
theme: 'dark',
devtoolsOpen: true,
})
instance.unmount()
await expect(
instance.mount(document.createElement('div'), 'dark'),
instance.mount(document.createElement('div'), {
theme: 'dark',
devtoolsOpen: true,
}),
).resolves.not.toThrow()
})

it('DevtoolsCore should call dispose on unmount', async () => {
const [DevtoolsCore] = constructCoreClass(importFn)
const instance = new DevtoolsCore()
await instance.mount(document.createElement('div'), 'dark')
await instance.mount(document.createElement('div'), {
theme: 'dark',
devtoolsOpen: true,
})
instance.unmount()
expect(disposeMock).toHaveBeenCalled()
})

it('DevtoolsCore should abort mount if unmount is called during mounting', async () => {
const [DevtoolsCore] = constructCoreClass(importFn)
const instance = new DevtoolsCore()
const mountPromise = instance.mount(document.createElement('div'), 'dark')
const mountPromise = instance.mount(document.createElement('div'), {
theme: 'dark',
devtoolsOpen: true,
})
// Unmount while mount is in progress β€” triggers abort path
// Note: since the mock resolves immediately, this tests the #abortMount flag
await mountPromise
Expand All @@ -80,16 +99,25 @@ describe('constructCoreClass', () => {
it('NoOpDevtoolsCore should not call __mountComponent when mount is called', async () => {
const [, NoOpDevtoolsCore] = constructCoreClass(importFn)
const noOpInstance = new NoOpDevtoolsCore()
await noOpInstance.mount(document.createElement('div'), 'dark')
await noOpInstance.mount(document.createElement('div'), {
theme: 'dark',
devtoolsOpen: true,
})
expect(mountComponentMock).not.toHaveBeenCalled()
})

it('NoOpDevtoolsCore should not throw if mount is called multiple times', async () => {
const [, NoOpDevtoolsCore] = constructCoreClass(importFn)
const noOpInstance = new NoOpDevtoolsCore()
await noOpInstance.mount(document.createElement('div'), 'dark')
await noOpInstance.mount(document.createElement('div'), {
theme: 'dark',
devtoolsOpen: true,
})
await expect(
noOpInstance.mount(document.createElement('div'), 'dark'),
noOpInstance.mount(document.createElement('div'), {
theme: 'dark',
devtoolsOpen: true,
}),
).resolves.not.toThrow()
})

Expand All @@ -102,7 +130,10 @@ describe('constructCoreClass', () => {
it('NoOpDevtoolsCore should not throw if unmount is called after mount', async () => {
const [, NoOpDevtoolsCore] = constructCoreClass(importFn)
const noOpInstance = new NoOpDevtoolsCore()
await noOpInstance.mount(document.createElement('div'), 'dark')
await noOpInstance.mount(document.createElement('div'), {
theme: 'dark',
devtoolsOpen: true,
})
expect(() => noOpInstance.unmount()).not.toThrow()
})
})
13 changes: 10 additions & 3 deletions packages/devtools-utils/src/solid/class.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { TanStackDevtoolsPluginProps } from '@tanstack/devtools'
import type { JSX } from 'solid-js'

/**
Expand All @@ -20,7 +21,10 @@ export function constructCoreClass(

constructor() {}

async mount<T extends HTMLElement>(el: T, theme: 'light' | 'dark') {
async mount<T extends HTMLElement>(
el: T,
props: TanStackDevtoolsPluginProps,
) {
if (this.#isMounted || this.#isMounting) {
throw new Error('Devtools is already mounted')
}
Expand All @@ -35,7 +39,7 @@ export function constructCoreClass(
return
}

this.#dispose = __mountComponent(el, theme, importFn)
this.#dispose = __mountComponent(el, props, importFn)
this.#isMounted = true
this.#isMounting = false
} catch (err) {
Expand All @@ -62,7 +66,10 @@ export function constructCoreClass(
constructor() {
super()
}
async mount<T extends HTMLElement>(_el: T, _theme: 'light' | 'dark') {}
async mount<T extends HTMLElement>(
_el: T,
_props: TanStackDevtoolsPluginProps,
) {}
unmount() {}
}

Expand Down
13 changes: 6 additions & 7 deletions packages/devtools-utils/src/solid/panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,19 @@

import { createSignal, onCleanup, onMount } from 'solid-js'
import type { ClassType } from './class'
import type { TanStackDevtoolsPluginProps } from '@tanstack/devtools'

export interface DevtoolsPanelProps {
theme?: 'light' | 'dark'
}
export interface DevtoolsPanelProps extends TanStackDevtoolsPluginProps {}

export function createSolidPanel<
TComponentProps extends DevtoolsPanelProps | undefined,
>(CoreClass: ClassType) {
export function createSolidPanel<TComponentProps extends DevtoolsPanelProps>(
CoreClass: ClassType,
) {
function Panel(props: TComponentProps) {
let devToolRef: HTMLDivElement | undefined
const [devtools] = createSignal(new CoreClass())
onMount(() => {
if (devToolRef) {
devtools().mount(devToolRef, props?.theme ?? 'dark')
devtools().mount(devToolRef, props)
}
onCleanup(() => {
devtools().unmount()
Expand Down
7 changes: 4 additions & 3 deletions packages/devtools-utils/src/solid/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import type { JSX } from 'solid-js'
import type { DevtoolsPanelProps } from './panel'
import type { TanStackDevtoolsPluginProps } from '@tanstack/devtools'

export function createSolidPlugin({
Component,
Expand All @@ -15,15 +16,15 @@ export function createSolidPlugin({
function Plugin() {
return {
...config,
render: (_el: HTMLElement, theme: 'light' | 'dark') => {
return <Component theme={theme} />
render: (_el: HTMLElement, props: TanStackDevtoolsPluginProps) => {
return <Component {...props} />
},
}
}
function NoOpPlugin() {
return {
...config,
render: (_el: HTMLElement, _theme: 'light' | 'dark') => <></>,
render: (_el: HTMLElement, _props: TanStackDevtoolsPluginProps) => <></>,
}
}
return [Plugin, NoOpPlugin] as const
Expand Down
Loading
Loading