diff --git a/examples/react/time-travel/.eslintrc.cjs b/examples/react/time-travel/.eslintrc.cjs
new file mode 100644
index 00000000..9ff0b9fc
--- /dev/null
+++ b/examples/react/time-travel/.eslintrc.cjs
@@ -0,0 +1,13 @@
+// @ts-check
+
+/** @type {import('eslint').Linter.Config} */
+const config = {
+ settings: {
+ extends: ['plugin:react/recommended', 'plugin:react-hooks/recommended'],
+ rules: {
+ 'react/no-children-prop': 'off',
+ },
+ },
+}
+
+module.exports = config
diff --git a/examples/react/time-travel/.gitignore b/examples/react/time-travel/.gitignore
new file mode 100644
index 00000000..4673b022
--- /dev/null
+++ b/examples/react/time-travel/.gitignore
@@ -0,0 +1,27 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+pnpm-lock.yaml
+yarn.lock
+package-lock.json
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
diff --git a/examples/react/time-travel/README.md b/examples/react/time-travel/README.md
new file mode 100644
index 00000000..1cf88926
--- /dev/null
+++ b/examples/react/time-travel/README.md
@@ -0,0 +1,6 @@
+# Example
+
+To run this example:
+
+- `npm install`
+- `npm run dev`
diff --git a/examples/react/time-travel/index.html b/examples/react/time-travel/index.html
new file mode 100644
index 00000000..f24fe41d
--- /dev/null
+++ b/examples/react/time-travel/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+ Basic Example - TanStack Devtools
+
+
+ You need to enable JavaScript to run this app.
+
+
+
+
diff --git a/examples/react/time-travel/package.json b/examples/react/time-travel/package.json
new file mode 100644
index 00000000..ff855104
--- /dev/null
+++ b/examples/react/time-travel/package.json
@@ -0,0 +1,41 @@
+{
+ "name": "@tanstack/devtools-example-react-time-travel",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite --port=3005",
+ "build": "vite build",
+ "preview": "vite preview",
+ "test:types": "tsc"
+ },
+ "dependencies": {
+ "@tanstack/devtools-event-client": "workspace:^",
+ "@tanstack/react-devtools": "^0.3.0",
+ "@tanstack/react-query": "^5.83.0",
+ "@tanstack/react-query-devtools": "^5.83.0",
+ "@tanstack/react-router": "^1.130.2",
+ "@tanstack/react-router-devtools": "^1.130.2",
+ "react": "^19.1.0",
+ "react-dom": "^19.1.0",
+ "zod": "^4.0.14",
+ "zustand": "^5.0.7"
+ },
+ "devDependencies": {
+ "@types/react": "^19.1.2",
+ "@types/react-dom": "^19.1.2",
+ "@vitejs/plugin-react": "^4.5.2",
+ "vite": "^7.0.6"
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ }
+}
diff --git a/examples/react/time-travel/public/emblem-light.svg b/examples/react/time-travel/public/emblem-light.svg
new file mode 100644
index 00000000..a58e69ad
--- /dev/null
+++ b/examples/react/time-travel/public/emblem-light.svg
@@ -0,0 +1,13 @@
+
+
+
+ emblem-light
+ Created with Sketch.
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/react/time-travel/src/index.tsx b/examples/react/time-travel/src/index.tsx
new file mode 100644
index 00000000..e569c9d1
--- /dev/null
+++ b/examples/react/time-travel/src/index.tsx
@@ -0,0 +1,20 @@
+import { createRoot } from 'react-dom/client'
+import { useStore } from 'zustand'
+import Devtools from './setup'
+import { store } from './zustand-client'
+
+function App() {
+ const { count, increment, decrement } = useStore(store)
+ return (
+
+
Zustand time-travel
+ Current count: {count}
+ Increment
+ Decrement
+
+
+ )
+}
+
+const root = createRoot(document.getElementById('root')!)
+root.render( )
diff --git a/examples/react/time-travel/src/setup.tsx b/examples/react/time-travel/src/setup.tsx
new file mode 100644
index 00000000..bcec391b
--- /dev/null
+++ b/examples/react/time-travel/src/setup.tsx
@@ -0,0 +1,87 @@
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
+import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools'
+import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
+import {
+ Link,
+ Outlet,
+ RouterProvider,
+ createRootRoute,
+ createRoute,
+ createRouter,
+} from '@tanstack/react-router'
+import { TanstackDevtools } from '@tanstack/react-devtools'
+import { ZustandTimeTravel } from './zustand-time-travel'
+
+const rootRoute = createRootRoute({
+ component: () => (
+ <>
+
+
+ Home
+ {' '}
+
+ About
+
+
+
+
+ >
+ ),
+})
+
+const indexRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/',
+ component: function Index() {
+ return (
+
+
Welcome Home!
+
+ )
+ },
+})
+function About() {
+ return (
+
+
Hello from About!
+
+ )
+}
+
+const aboutRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/about',
+ component: About,
+})
+
+const routeTree = rootRoute.addChildren([indexRoute, aboutRoute])
+
+const router = createRouter({ routeTree })
+
+const queryClient = new QueryClient()
+
+export default function DevtoolsExample() {
+ return (
+ <>
+
+ ,
+ },
+ {
+ name: 'Tanstack Router',
+ render: ,
+ },
+ {
+ name: 'Zustand time-travel',
+ render: ,
+ },
+ ]}
+ />
+
+
+ >
+ )
+}
diff --git a/examples/react/time-travel/src/zustand-client.ts b/examples/react/time-travel/src/zustand-client.ts
new file mode 100644
index 00000000..404dfd75
--- /dev/null
+++ b/examples/react/time-travel/src/zustand-client.ts
@@ -0,0 +1,36 @@
+import { EventClient } from '@tanstack/devtools-event-client'
+import { createStore } from 'zustand'
+
+interface ZustandEventMap {
+ 'zustand:stateChange': any
+ 'zustand:revertSnapshot': any
+}
+export const eventClient = new EventClient({
+ pluginId: 'zustand',
+})
+
+export const store = createStore<{
+ count: number
+ increment: () => void
+ decrement: () => void
+}>((set) => ({
+ count: 0,
+ increment: () => {
+ return set((state) => {
+ eventClient.emit('stateChange', { count: state.count + 1 })
+ return { count: state.count + 1 }
+ })
+ },
+ decrement: () => {
+ return set((state) => {
+ eventClient.emit('stateChange', { count: state.count - 1 })
+ return { count: state.count - 1 }
+ })
+ },
+}))
+
+eventClient.on('revertSnapshot', (snapshot) => {
+ store.setState({
+ count: snapshot.payload.count,
+ })
+})
diff --git a/examples/react/time-travel/src/zustand-time-travel.tsx b/examples/react/time-travel/src/zustand-time-travel.tsx
new file mode 100644
index 00000000..04a20b9a
--- /dev/null
+++ b/examples/react/time-travel/src/zustand-time-travel.tsx
@@ -0,0 +1,32 @@
+import { useEffect, useState } from 'react'
+import { eventClient } from './zustand-client'
+
+export function ZustandTimeTravel() {
+ const [snapshots, setSnapshots] = useState>([])
+
+ useEffect(() => {
+ const cleanup = eventClient.on('stateChange', (event) =>
+ setSnapshots((prev) => [...prev, event.payload]),
+ )
+ return () => {
+ cleanup()
+ }
+ }, [])
+
+ return (
+
+ {/* Snapshot slider to change the current state */}
+ Drag Me to time travel through zustand states
+
+ {
+ const index = Number(e.target.value)
+ eventClient.emit('revertSnapshot', snapshots[index])
+ }}
+ />
+
+ )
+}
diff --git a/examples/react/time-travel/tsconfig.json b/examples/react/time-travel/tsconfig.json
new file mode 100644
index 00000000..6e9088d6
--- /dev/null
+++ b/examples/react/time-travel/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "Bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src", "vite.config.ts"]
+}
diff --git a/examples/react/time-travel/vite.config.ts b/examples/react/time-travel/vite.config.ts
new file mode 100644
index 00000000..4e194366
--- /dev/null
+++ b/examples/react/time-travel/vite.config.ts
@@ -0,0 +1,13 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [
+ react({
+ // babel: {
+ // plugins: [['babel-plugin-react-compiler', { target: '19' }]],
+ // },
+ }),
+ ],
+})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d8d14fd3..5f4a6a1f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -242,6 +242,52 @@ importers:
examples/react/start/generated/prisma: {}
+ examples/react/time-travel:
+ dependencies:
+ '@tanstack/devtools-event-client':
+ specifier: workspace:^
+ version: link:../../../packages/event-bus-client
+ '@tanstack/react-devtools':
+ specifier: ^0.3.0
+ version: link:../../../packages/react-devtools
+ '@tanstack/react-query':
+ specifier: ^5.83.0
+ version: 5.83.0(react@19.1.0)
+ '@tanstack/react-query-devtools':
+ specifier: ^5.83.0
+ version: 5.83.0(@tanstack/react-query@5.83.0(react@19.1.0))(react@19.1.0)
+ '@tanstack/react-router':
+ specifier: ^1.130.2
+ version: 1.130.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@tanstack/react-router-devtools':
+ specifier: ^1.130.2
+ version: 1.130.2(@tanstack/react-router@1.130.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.130.12)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(solid-js@1.9.7)(tiny-invariant@1.3.3)
+ react:
+ specifier: ^19.1.0
+ version: 19.1.0
+ react-dom:
+ specifier: ^19.1.0
+ version: 19.1.0(react@19.1.0)
+ zod:
+ specifier: ^4.0.14
+ version: 4.0.14
+ zustand:
+ specifier: ^5.0.7
+ version: 5.0.7(@types/react@19.1.2)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0))
+ devDependencies:
+ '@types/react':
+ specifier: ^19.1.2
+ version: 19.1.2
+ '@types/react-dom':
+ specifier: ^19.1.2
+ version: 19.1.2(@types/react@19.1.2)
+ '@vitejs/plugin-react':
+ specifier: ^4.5.2
+ version: 4.7.0(vite@7.0.6(@types/node@22.15.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))
+ vite:
+ specifier: ^7.0.6
+ version: 7.0.6(@types/node@22.15.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
+
examples/solid/basic:
dependencies:
'@tanstack/solid-devtools':
@@ -6898,6 +6944,24 @@ packages:
zod@4.0.14:
resolution: {integrity: sha512-nGFJTnJN6cM2v9kXL+SOBq3AtjQby3Mv5ySGFof5UGRHrRioSJ5iG680cYNjE/yWk671nROcpPj4hAS8nyLhSw==}
+ zustand@5.0.7:
+ resolution: {integrity: sha512-Ot6uqHDW/O2VdYsKLLU8GQu8sCOM1LcoE8RwvLv9uuRT9s6SOHCKs0ZEOhxg+I1Ld+A1Q5lwx+UlKXXUoCZITg==}
+ engines: {node: '>=12.20.0'}
+ peerDependencies:
+ '@types/react': '>=18.0.0'
+ immer: '>=9.0.6'
+ react: '>=18.0.0'
+ use-sync-external-store: '>=1.2.0'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+ use-sync-external-store:
+ optional: true
+
zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
@@ -8907,6 +8971,18 @@ snapshots:
'@tanstack/query-core': 5.83.0
react: 19.1.0
+ '@tanstack/react-router-devtools@1.130.2(@tanstack/react-router@1.130.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.130.12)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(solid-js@1.9.7)(tiny-invariant@1.3.3)':
+ dependencies:
+ '@tanstack/react-router': 1.130.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@tanstack/router-devtools-core': 1.130.2(@tanstack/router-core@1.130.12)(csstype@3.1.3)(solid-js@1.9.7)(tiny-invariant@1.3.3)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ transitivePeerDependencies:
+ - '@tanstack/router-core'
+ - csstype
+ - solid-js
+ - tiny-invariant
+
'@tanstack/react-router-devtools@1.130.2(@tanstack/react-router@1.130.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.130.12)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(solid-js@1.9.7)(tiny-invariant@1.3.3)':
dependencies:
'@tanstack/react-router': 1.130.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -14213,4 +14289,10 @@ snapshots:
zod@4.0.14: {}
+ zustand@5.0.7(@types/react@19.1.2)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)):
+ optionalDependencies:
+ '@types/react': 19.1.2
+ react: 19.1.0
+ use-sync-external-store: 1.5.0(react@19.1.0)
+
zwitch@2.0.4: {}