From 68bc278b0bac92e6a4949b76b28a3af8ae392097 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Tue, 6 Jan 2026 12:18:09 +0100 Subject: [PATCH 1/6] fix: make the import meta conditional to work outside of vite --- .../src/components/source-inspector.tsx | 4 +- packages/devtools/src/context/pip-context.tsx | 69 ++++++++++--------- packages/devtools/src/core.tsx | 4 +- 3 files changed, 40 insertions(+), 37 deletions(-) diff --git a/packages/devtools/src/components/source-inspector.tsx b/packages/devtools/src/components/source-inspector.tsx index 519bcb51..37d14321 100644 --- a/packages/devtools/src/components/source-inspector.tsx +++ b/packages/devtools/src/components/source-inspector.tsx @@ -80,14 +80,14 @@ export const SourceInspector = () => { e.stopPropagation() // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - const baseUrl = new URL(import.meta.env?.BASE_URL ?? '/', location.origin) + const baseUrl = new URL(import.meta?.env?.BASE_URL ?? '/', location.origin) const url = new URL( `__tsd/open-source?source=${encodeURIComponent( highlightState.dataSource, )}`, baseUrl, ) - fetch(url).catch(() => {}) + fetch(url).catch(() => { }) }) const currentElementBoxStyles = createMemo(() => { diff --git a/packages/devtools/src/context/pip-context.tsx b/packages/devtools/src/context/pip-context.tsx index 3166b59d..dafb8766 100644 --- a/packages/devtools/src/context/pip-context.tsx +++ b/packages/devtools/src/context/pip-context.tsx @@ -54,8 +54,9 @@ export const PiPProvider = (props: PiPProviderProps) => { 'Failed to open popup. Please allow popups for this site to view the devtools in picture-in-picture mode.', ) } - - import.meta.hot?.on('vite:beforeUpdate', () => { + // can be run outside of vite so we ignore the rule + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + import.meta?.hot?.on('vite:beforeUpdate', () => { localStorage.setItem('pip_open', 'false') closePipWindow() }) @@ -77,39 +78,39 @@ export const PiPProvider = (props: PiPProviderProps) => { closePipWindow() }) - // It is important to copy all parent window styles. Otherwise, there would be no CSS available at all - // https://developer.chrome.com/docs/web-platform/document-picture-in-picture/#copy-style-sheets-to-the-picture-in-picture-window - ;[...document.styleSheets].forEach((styleSheet) => { - try { - const cssRules = [...styleSheet.cssRules] - .map((rule) => rule.cssText) - .join('') - const style = document.createElement('style') - const style_node = styleSheet.ownerNode - let style_id = '' - - if (style_node && 'id' in style_node) { - style_id = style_node.id - } - - if (style_id) { - style.setAttribute('id', style_id) - } - style.textContent = cssRules - pip.document.head.appendChild(style) - } catch (e) { - const link = document.createElement('link') - if (styleSheet.href == null) { - return + // It is important to copy all parent window styles. Otherwise, there would be no CSS available at all + // https://developer.chrome.com/docs/web-platform/document-picture-in-picture/#copy-style-sheets-to-the-picture-in-picture-window + ;[...document.styleSheets].forEach((styleSheet) => { + try { + const cssRules = [...styleSheet.cssRules] + .map((rule) => rule.cssText) + .join('') + const style = document.createElement('style') + const style_node = styleSheet.ownerNode + let style_id = '' + + if (style_node && 'id' in style_node) { + style_id = style_node.id + } + + if (style_id) { + style.setAttribute('id', style_id) + } + style.textContent = cssRules + pip.document.head.appendChild(style) + } catch (e) { + const link = document.createElement('link') + if (styleSheet.href == null) { + return + } + + link.rel = 'stylesheet' + link.type = styleSheet.type + link.media = styleSheet.media.toString() + link.href = styleSheet.href + pip.document.head.appendChild(link) } - - link.rel = 'stylesheet' - link.type = styleSheet.type - link.media = styleSheet.media.toString() - link.href = styleSheet.href - pip.document.head.appendChild(link) - } - }) + }) delegateEvents( [ 'focusin', diff --git a/packages/devtools/src/core.tsx b/packages/devtools/src/core.tsx index 63ca8307..ec338dcc 100644 --- a/packages/devtools/src/core.tsx +++ b/packages/devtools/src/core.tsx @@ -63,7 +63,9 @@ export class TanStackDevtoolsCore { mount(el: T) { // tsup-preset-solid statically replaces this variable during build, which eliminates this code from server bundle - if (import.meta.env.SSR) return + // can be run outside of vite so we ignore the rule + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (import.meta?.env?.SSR) return if (this.#isMounted) { throw new Error('Devtools is already mounted') From 11659e13a1212d66a02ab24a6290ccc061d450f1 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 11:18:29 +0000 Subject: [PATCH 2/6] ci: apply automated fixes --- .../src/components/source-inspector.tsx | 2 +- packages/devtools/src/context/pip-context.tsx | 64 +++++++++---------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/packages/devtools/src/components/source-inspector.tsx b/packages/devtools/src/components/source-inspector.tsx index 37d14321..6ae5401e 100644 --- a/packages/devtools/src/components/source-inspector.tsx +++ b/packages/devtools/src/components/source-inspector.tsx @@ -87,7 +87,7 @@ export const SourceInspector = () => { )}`, baseUrl, ) - fetch(url).catch(() => { }) + fetch(url).catch(() => {}) }) const currentElementBoxStyles = createMemo(() => { diff --git a/packages/devtools/src/context/pip-context.tsx b/packages/devtools/src/context/pip-context.tsx index dafb8766..cbaf3a0e 100644 --- a/packages/devtools/src/context/pip-context.tsx +++ b/packages/devtools/src/context/pip-context.tsx @@ -78,39 +78,39 @@ export const PiPProvider = (props: PiPProviderProps) => { closePipWindow() }) - // It is important to copy all parent window styles. Otherwise, there would be no CSS available at all - // https://developer.chrome.com/docs/web-platform/document-picture-in-picture/#copy-style-sheets-to-the-picture-in-picture-window - ;[...document.styleSheets].forEach((styleSheet) => { - try { - const cssRules = [...styleSheet.cssRules] - .map((rule) => rule.cssText) - .join('') - const style = document.createElement('style') - const style_node = styleSheet.ownerNode - let style_id = '' - - if (style_node && 'id' in style_node) { - style_id = style_node.id - } - - if (style_id) { - style.setAttribute('id', style_id) - } - style.textContent = cssRules - pip.document.head.appendChild(style) - } catch (e) { - const link = document.createElement('link') - if (styleSheet.href == null) { - return - } - - link.rel = 'stylesheet' - link.type = styleSheet.type - link.media = styleSheet.media.toString() - link.href = styleSheet.href - pip.document.head.appendChild(link) + // It is important to copy all parent window styles. Otherwise, there would be no CSS available at all + // https://developer.chrome.com/docs/web-platform/document-picture-in-picture/#copy-style-sheets-to-the-picture-in-picture-window + ;[...document.styleSheets].forEach((styleSheet) => { + try { + const cssRules = [...styleSheet.cssRules] + .map((rule) => rule.cssText) + .join('') + const style = document.createElement('style') + const style_node = styleSheet.ownerNode + let style_id = '' + + if (style_node && 'id' in style_node) { + style_id = style_node.id } - }) + + if (style_id) { + style.setAttribute('id', style_id) + } + style.textContent = cssRules + pip.document.head.appendChild(style) + } catch (e) { + const link = document.createElement('link') + if (styleSheet.href == null) { + return + } + + link.rel = 'stylesheet' + link.type = styleSheet.type + link.media = styleSheet.media.toString() + link.href = styleSheet.href + pip.document.head.appendChild(link) + } + }) delegateEvents( [ 'focusin', From 21ecc4a741640ee58ceaa032045cedd02736a808 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Tue, 6 Jan 2026 12:28:17 +0100 Subject: [PATCH 3/6] changeset --- .changeset/gold-beers-shine.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/gold-beers-shine.md diff --git a/.changeset/gold-beers-shine.md b/.changeset/gold-beers-shine.md new file mode 100644 index 00000000..9b31699d --- /dev/null +++ b/.changeset/gold-beers-shine.md @@ -0,0 +1,5 @@ +--- +'@tanstack/devtools': patch +--- + +fix issue with popup window From 650b8f53c91ccd9500460e4c7cc2af2df1f2c4c3 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Tue, 6 Jan 2026 12:29:09 +0100 Subject: [PATCH 4/6] fix: unmounting of devtools --- .changeset/fine-pugs-press.md | 5 +++++ packages/devtools-utils/src/react/panel.tsx | 12 +++++------- 2 files changed, 10 insertions(+), 7 deletions(-) create mode 100644 .changeset/fine-pugs-press.md diff --git a/.changeset/fine-pugs-press.md b/.changeset/fine-pugs-press.md new file mode 100644 index 00000000..03a75bbc --- /dev/null +++ b/.changeset/fine-pugs-press.md @@ -0,0 +1,5 @@ +--- +'@tanstack/devtools-utils': patch +--- + +fix issue with unmounting of devtools diff --git a/packages/devtools-utils/src/react/panel.tsx b/packages/devtools-utils/src/react/panel.tsx index 274ff8b3..cfc82f79 100644 --- a/packages/devtools-utils/src/react/panel.tsx +++ b/packages/devtools-utils/src/react/panel.tsx @@ -32,18 +32,16 @@ export function createReactPanel< const devToolRef = useRef(null) const devtools = useRef(null) useEffect(() => { - if (devtools.current) return - - devtools.current = new CoreClass() + const instance = new CoreClass() + devtools.current = instance if (devToolRef.current) { - devtools.current.mount(devToolRef.current, props?.theme ?? 'dark') + instance.mount(devToolRef.current, props?.theme ?? 'dark') } return () => { - if (devToolRef.current) { - devtools.current?.unmount() - } + instance.unmount() + devtools.current = null } }, [props?.theme]) From 4a37334889444a5d04b7acdfe187ff72a4b2cfc0 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Tue, 6 Jan 2026 13:12:49 +0100 Subject: [PATCH 5/6] fix: unmounting of devtools --- packages/devtools-utils/src/solid/class.tsx | 23 +++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/devtools-utils/src/solid/class.tsx b/packages/devtools-utils/src/solid/class.tsx index c48961c0..6f084c3d 100644 --- a/packages/devtools-utils/src/solid/class.tsx +++ b/packages/devtools-utils/src/solid/class.tsx @@ -14,13 +14,16 @@ import type { JSX } from 'solid-js' export function constructCoreClass(Component: () => JSX.Element) { class DevtoolsCore { #isMounted = false + #isMounting = false; + #mountCb: (() => void) | null = null; #dispose?: () => void #Component: any #ThemeProvider: any - constructor() {} + constructor() { } async mount(el: T, theme: 'light' | 'dark') { + this.#isMounting = true; const { lazy } = await import('solid-js') const { render, Portal } = await import('solid-js/web') if (this.#isMounted) { @@ -49,13 +52,25 @@ export function constructCoreClass(Component: () => JSX.Element) { ) }, mountTo) this.#isMounted = true + this.#isMounting = false; this.#dispose = dispose + if (this.#mountCb) { + this.#mountCb(); + this.#mountCb = null; + } } unmount() { - if (!this.#isMounted) { + if (!this.#isMounted && !this.#isMounting) { throw new Error('Devtools is not mounted') } + if (this.#isMounting) { + this.#mountCb = () => { + this.#dispose?.() + this.#isMounted = false + } + return; + } this.#dispose?.() this.#isMounted = false } @@ -64,8 +79,8 @@ export function constructCoreClass(Component: () => JSX.Element) { constructor() { super() } - async mount(_el: T, _theme: 'light' | 'dark') {} - unmount() {} + async mount(_el: T, _theme: 'light' | 'dark') { } + unmount() { } } return [DevtoolsCore, NoOpDevtoolsCore] as const } From 07641a91bb746833aa1879dbef037f35c37e5eac Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 12:13:08 +0000 Subject: [PATCH 6/6] ci: apply automated fixes --- packages/devtools-utils/src/solid/class.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/devtools-utils/src/solid/class.tsx b/packages/devtools-utils/src/solid/class.tsx index 6f084c3d..7cbb94ae 100644 --- a/packages/devtools-utils/src/solid/class.tsx +++ b/packages/devtools-utils/src/solid/class.tsx @@ -14,16 +14,16 @@ import type { JSX } from 'solid-js' export function constructCoreClass(Component: () => JSX.Element) { class DevtoolsCore { #isMounted = false - #isMounting = false; - #mountCb: (() => void) | null = null; + #isMounting = false + #mountCb: (() => void) | null = null #dispose?: () => void #Component: any #ThemeProvider: any - constructor() { } + constructor() {} async mount(el: T, theme: 'light' | 'dark') { - this.#isMounting = true; + this.#isMounting = true const { lazy } = await import('solid-js') const { render, Portal } = await import('solid-js/web') if (this.#isMounted) { @@ -52,11 +52,11 @@ export function constructCoreClass(Component: () => JSX.Element) { ) }, mountTo) this.#isMounted = true - this.#isMounting = false; + this.#isMounting = false this.#dispose = dispose if (this.#mountCb) { - this.#mountCb(); - this.#mountCb = null; + this.#mountCb() + this.#mountCb = null } } @@ -69,7 +69,7 @@ export function constructCoreClass(Component: () => JSX.Element) { this.#dispose?.() this.#isMounted = false } - return; + return } this.#dispose?.() this.#isMounted = false @@ -79,8 +79,8 @@ export function constructCoreClass(Component: () => JSX.Element) { constructor() { super() } - async mount(_el: T, _theme: 'light' | 'dark') { } - unmount() { } + async mount(_el: T, _theme: 'light' | 'dark') {} + unmount() {} } return [DevtoolsCore, NoOpDevtoolsCore] as const }