packages/devtools-utils/skills/devtools-framework-adapters/SKILL.md
Use devtools-utils factory functions to create per-framework plugin adapters. createReactPlugin/createSolidPlugin/createVuePlugin/createPreactPlugin, createReactPanel/createSolidPanel/createVuePanel/createPreactPanel. [Plugin, NoOpPlugin] tuple for tree-shaking. DevtoolsPanelProps (theme). Vue uses (name, component) not options object. Solid render must be function.
npx skillsauth add tanstack/devtools devtools-framework-adaptersInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Use @tanstack/devtools-utils factory functions to create per-framework devtools plugin adapters. Each framework has a subpath export (/react, /vue, /solid, /preact) with two factories:
createXPlugin -- wraps a component into a [Plugin, NoOpPlugin] tuple for tree-shaking.createXPanel -- wraps a class-based devtools core (mount/unmount) into a [Panel, NoOpPanel] component tuple.packages/devtools-utils/src/react/plugin.tsx -- createReactPluginpackages/devtools-utils/src/react/panel.tsx -- createReactPanel, DevtoolsPanelPropspackages/devtools-utils/src/vue/plugin.ts -- createVuePlugin (different API)packages/devtools-utils/src/vue/panel.ts -- createVuePanel, DevtoolsPanelProps (includes 'system' theme)packages/devtools-utils/src/solid/plugin.tsx -- createSolidPluginpackages/devtools-utils/src/solid/panel.tsx -- createSolidPanelpackages/devtools-utils/src/solid/class.ts -- constructCoreClass (Solid-specific)packages/devtools-utils/src/preact/plugin.tsx -- createPreactPluginpackages/devtools-utils/src/preact/panel.tsx -- createPreactPanelAll four frameworks follow the same two-factory pattern:
Every createXPlugin returns readonly [Plugin, NoOpPlugin]:
render function that renders your component.Every createXPanel returns readonly [Panel, NoOpPanel]:
<div style="height:100%">, instantiates the core class, calls core.mount(el, theme) on mount, and core.unmount() on cleanup.// React, Solid, Preact
interface DevtoolsPanelProps {
theme?: 'light' | 'dark'
}
// Vue (note: includes 'system')
interface DevtoolsPanelProps {
theme?: 'dark' | 'light' | 'system'
}
Import from the framework subpath:
import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/react'
import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/vue'
import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/solid'
import type { DevtoolsPanelProps } from '@tanstack/devtools-utils/preact'
import { createReactPlugin } from '@tanstack/devtools-utils/react'
function MyStorePanel({ theme }: { theme?: 'light' | 'dark' }) {
return <div className={theme}>My Store Devtools</div>
}
const [MyPlugin, NoOpPlugin] = createReactPlugin({
name: 'My Store',
id: 'my-store',
defaultOpen: false,
Component: MyStorePanel,
})
// Tree-shaking: use NoOp in production
const ActivePlugin =
process.env.NODE_ENV === 'development' ? MyPlugin : NoOpPlugin
import {
createReactPanel,
createReactPlugin,
} from '@tanstack/devtools-utils/react'
class MyDevtoolsCore {
mount(el: HTMLElement, theme: 'light' | 'dark') {
/* render into el */
}
unmount() {
/* cleanup */
}
}
const [MyPanel, NoOpPanel] = createReactPanel(MyDevtoolsCore)
const [MyPlugin, NoOpPlugin] = createReactPlugin({
name: 'My Store',
Component: MyPanel,
})
createReactPlugin({ name, id?, defaultOpen?, Component }) // => [Plugin, NoOpPlugin]
createPreactPlugin({ name, id?, defaultOpen?, Component }) // => [Plugin, NoOpPlugin]
Component receives DevtoolsPanelProps (with theme).Plugin() returns { name, id?, defaultOpen?, render(el, theme) }.preact/hooks.createVuePlugin(name: string, component: DefineComponent) // => [Plugin, NoOpPlugin]
(name, component) as separate arguments, NOT an options object.Plugin(props) returns { name, component, props } -- it passes props through.NoOpPlugin(props) returns { name, component: Fragment, props }.DevtoolsPanelProps.theme also accepts 'system'.createSolidPlugin({ name, id?, defaultOpen?, Component }) // => [Plugin, NoOpPlugin]
Component must be a Solid component function (props: DevtoolsPanelProps) => JSX.Element.<Component theme={theme} /> -- Solid handles reactivity.constructCoreClass from @tanstack/devtools-utils/solid/class for building lazy-loaded devtools cores.Vue uses positional (name, component) arguments, NOT an options object.
// WRONG -- will fail at compile time or produce garbage at runtime
const [MyPlugin, NoOpPlugin] = createVuePlugin({
name: 'My Plugin',
Component: MyPanel,
})
// CORRECT
const [MyPlugin, NoOpPlugin] = createVuePlugin('My Plugin', MyPanel)
Vue plugins also work differently at call time -- you pass props:
// WRONG -- calling Plugin() with no args (React pattern)
const plugin = MyPlugin()
// CORRECT -- Vue Plugin takes props
const plugin = MyPlugin({ theme: 'dark' })
When using Solid components, Component must be a function reference, not raw JSX.
// WRONG -- evaluates immediately, breaks Solid reactivity
createSolidPlugin({
name: 'My Store',
Component: <MyPanel />, // This is JSX.Element, not a component function
})
// CORRECT -- pass the component function itself
createSolidPlugin({
name: 'My Store',
Component: (props) => <MyPanel theme={props.theme} />,
})
// ALSO CORRECT -- pass the component reference directly
createSolidPlugin({
name: 'My Store',
Component: MyPanel,
})
The factory returns [Plugin, NoOpPlugin]. Both must be destructured and used for proper tree-shaking.
// WRONG -- NoOp variant discarded, devtools code ships to production
const [MyPlugin] = createReactPlugin({ name: 'Store', Component: MyPanel })
// CORRECT -- conditionally use NoOp in production
const [MyPlugin, NoOpPlugin] = createReactPlugin({
name: 'Store',
Component: MyPanel,
})
const ActivePlugin =
process.env.NODE_ENV === 'development' ? MyPlugin : NoOpPlugin
DevtoolsPanelProps includes theme. The devtools shell passes it so panels can match light/dark mode. If your component ignores it, the panel will not adapt to theme changes.
// WRONG -- theme is ignored
const Component = () => <div>My Panel</div>
// CORRECT -- use theme for styling
const Component = ({ theme }: DevtoolsPanelProps) => (
<div className={theme === 'dark' ? 'dark-mode' : 'light-mode'}>My Panel</div>
)
The core architecture is framework-agnostic, but each framework has different idioms:
Component as a JSX function component.DefineComponent and passes props through.Agents trained on React patterns will get Vue wrong. Always check the import path to determine which factory API to use.
references/react.md -- Full React factory API and examplesreferences/vue.md -- Full Vue factory API and examples (different from React)references/solid.md -- Full Solid factory API and examplesreferences/preact.md -- Full Preact factory API and examplestools
Handle devtools in production vs development. removeDevtoolsOnBuild, devDependency vs regular dependency, conditional imports, NoOp plugin variants for tree-shaking, non-Vite production exclusion patterns.
tools
Publish plugin to npm and submit to TanStack Devtools Marketplace. PluginMetadata registry format, plugin-registry.ts, pluginImport (importName, type), requires (packageName, minVersion), framework tagging, multi-framework submissions, featured plugins.
tools
Configure @tanstack/devtools-vite for source inspection (data-tsd-source, inspectHotkey, ignore patterns), console piping (client-to-server, server-to-client, levels), enhanced logging, server event bus (port, host, HTTPS), production stripping (removeDevtoolsOnBuild), editor integration (launch-editor, custom editor.open). Must be FIRST plugin in Vite config. Vite ^6 || ^7 only.
tools
Analyze library codebase for critical architecture and debugging points, add strategic event emissions. Identify middleware boundaries, state transitions, lifecycle hooks. Consolidate events (1 not 15), debounce high-frequency updates, DRY shared payload fields, guard emit() for production. Transparent server/client event bridging.