### TanStack Devtools version @tanstack/devtools-vite: 0.7.0 @tanstack/react-devtools: 0.10.5 ### Framework/Library version react: 19.2.6 ### Describe the bug and the steps to reproduce it When using the inspect hotkey (Shift+Alt+Cmd) to click a component, the request to `/__tsd/open-source?source=` is sent with an **empty** `source` parameter, even though the clicked element has a valid `data-tsd-source` attribute. The dev server then hangs the request indefinitely, so the editor never opens. This is fully reproducible in any React app using `@tanstack/react-devtools@0.10.x` (which pulls in `@tanstack/devtools@0.12.2`) + `@tanstack/devtools-vite@0.7.0`. I am seeing it across multiple projects with similar stacks. ## Versions - `@tanstack/devtools@0.12.2` - `@tanstack/react-devtools@0.10.5` - `@tanstack/devtools-vite@0.7.0` - `vite@8.0.14`, `react@19.2.6`, Solid (bundled via devtools) ## Reproduction 1. Add `@tanstack/devtools-vite` to a Vite + React project with a configured `editor`. 2. Mount `<TanStackDevtools>` in the app. 3. Hold Shift+Alt+Cmd and click any rendered component. 4. **Observed:** Network panel shows `GET /__tsd/open-source?source=` (empty value), status `pending` forever. Editor never opens. 5. **Expected:** URL should include the clicked element's `data-tsd-source`, server should respond `200`, editor should open. I verified the clicked element has a well-formed `data-tsd-source` attribute (matching `/^\/.+:\d+:\d+$/`). On the page tested there were 1869 such elements, 0 empty, 0 malformed. ## Root cause `packages/devtools/src/.../LGFFJRYV.js` (the inspect overlay component) registers this click handler: ```js createEventListener(document, "click", (e) => { if (!highlightState.element) return; window.getSelection()?.removeAllRanges(); e.preventDefault(); e.stopPropagation(); setDisabledAfterClick(true); // <-- step 1 if (settings().sourceAction === "copy-path") { navigator.clipboard.writeText(highlightState.dataSource).catch(() => {}); return; } const baseUrl = new URL(import.meta.env?.BASE_URL ?? "/", location.origin); const url = new URL( `__tsd/open-source?source=${encodeURIComponent(highlightState.dataSource)}`, // <-- step 2 baseUrl ); fetch(url).catch(() => {}); }); ``` `setDisabledAfterClick(true)` flips this memo: ```js const isActive = createMemo( () => isHighlightingKeysHeld() && !disabledAfterClick() ); ``` …which makes `isActive()` false, which **synchronously** re-runs the highlight `createEffect`: ```js createEffect(() => { if (!isActive()) { resetHighlight(); // <-- sets element=null, dataSource="" return; } ... }); ``` By the time the next line in the click handler reads `highlightState.dataSource`, it is `""`. The constructed URL is `/__tsd/open-source?source=` and is sent. ## Compounding server-side bug In `@tanstack/devtools-vite` (`utils.js`), the middleware does: ```js if (req.url?.includes("__tsd/open-source")) { const source = new URLSearchParams(req.url.split("?")[1]).get("source"); if (!source) return; // <-- leaves the response hanging const parsed = parseOpenSourceParam(source); if (!parsed) return; // <-- same ... } ``` Both early returns omit both `res.end()` and `next()`, so the connection sits open until the browser times out. This is why users see "pending" rather than a quick failure. ## Evidence Captured at click time on a component whose `data-tsd-source` attribute is `/src/routes/tournaments/$tournament_id/leaderboard.tsx:55:7`: ``` { "url": "http://localhost:5173/__tsd/open-source?source=", "rawSource": "", "stack": "...at HTMLDocument.<anonymous> (LGFFJRYV-DaE8v0zr.js:6195:3)" } ``` `reqid=101 GET http://localhost:5173/__tsd/open-source?source= [pending]` in the Network panel. The clicked element under the cursor at the same instant: ``` { tag: "H1", src: "/src/routes/tournaments/$tournament_id/leaderboard.tsx:55:7" } ``` ## Suggested fix **Client (`packages/devtools/.../LGFFJRYV.js`):** capture `dataSource` before mutating signals. ```diff createEventListener(document, "click", (e) => { if (!highlightState.element) return; + const source = highlightState.dataSource; // capture before any setSignal window.getSelection()?.removeAllRanges(); e.preventDefault(); e.stopPropagation(); - setDisabledAfterClick(true); if (settings().sourceAction === "copy-path") { - navigator.clipboard.writeText(highlightState.dataSource).catch(() => {}); + navigator.clipboard.writeText(source).catch(() => {}); + setDisabledAfterClick(true); return; } const baseUrl = new URL(import.meta.env?.BASE_URL ?? "/", location.origin); - const url = new URL(`__tsd/open-source?source=${encodeURIComponent(highlightState.dataSource)}`, baseUrl); + const url = new URL(`__tsd/open-source?source=${encodeURIComponent(source)}`, baseUrl); fetch(url).catch(() => {}); + setDisabledAfterClick(true); }); ``` Wrapping the signal write in `batch(() => setDisabledAfterClick(true))` would not help here — the read happens *after* the write, so the value is already reset by the time we read. **Server (`packages/devtools-vite/src/utils.ts`):** defensively end the response on bad input instead of leaving the socket open. ```diff if (req.url?.includes("__tsd/open-source")) { const source = new URLSearchParams(req.url.split("?")[1]).get("source"); - if (!source) return; + if (!source) { res.statusCode = 400; res.end(); return; } const parsed = parseOpenSourceParam(source); - if (!parsed) return; + if (!parsed) { res.statusCode = 400; res.end(); return; } ... } ``` ## Workaround for users until fixed Add this to your client entry (e.g., `src/main.tsx`) in dev only: ```ts if (import.meta.env.DEV) { let lastSource: string | null = null; document.addEventListener('click', (e) => { if (e.shiftKey && e.altKey && (e.metaKey || e.ctrlKey)) { const t = document.elementFromPoint(e.clientX, e.clientY); const el = t?.closest?.('[data-tsd-source]') as HTMLElement | null; if (el) lastSource = el.getAttribute('data-tsd-source'); } }, true); // capture phase: runs before the buggy handler const orig = window.fetch; window.fetch = function(input, init) { const url = typeof input === 'string' ? input : input instanceof URL ? input.href : (input as Request).url; if (typeof url === 'string' && url.endsWith('/__tsd/open-source?source=') && lastSource) { const fixed = url + encodeURIComponent(lastSource); lastSource = null; return orig.call(this, fixed, init); } return orig.call(this, input, init); }; } ``` ### Your Minimal, Reproducible Example - (Sandbox Highly Recommended) A minimal sandbox isn't practical for this bug. ### Screenshots or Videos (Optional) _No response_ ### Do you intend to try to help solve this bug with your own PR? Maybe, I'll investigate and start debugging ### Terms & Code of Conduct - [x] I agree to follow this project's Code of Conduct - [x] I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.