Title: Click-to-source sends empty source= and hangs because setDisabledAfterClick(true) runs before reading highlightState.dataSource · Issue #451 · TanStack/devtools · GitHub
Open Graph Title: Click-to-source sends empty source= and hangs because setDisabledAfterClick(true) runs before reading highlightState.dataSource · Issue #451 · TanStack/devtools
X Title: Click-to-source sends empty source= and hangs because setDisabledAfterClick(true) runs before reading highlightState.dataSource · Issue #451 · TanStack/devtools
Description: 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 com...
Open Graph Description: 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 inspe...
X Description: 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 inspe...
Opengraph URL: https://github.com/TanStack/devtools/issues/451
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Click-to-source sends empty source= and hangs because setDisabledAfterClick(true) runs before reading highlightState.dataSource","articleBody":"### TanStack Devtools version\n\n@tanstack/devtools-vite: 0.7.0 @tanstack/react-devtools: 0.10.5\n\n### Framework/Library version\n\nreact: 19.2.6\n\n### Describe the bug and the steps to reproduce it\n\n When using the inspect hotkey (Shift+Alt+Cmd) to click a component, the request to `/__tsd/open-source?source=` is\n sent with an **empty** `source` parameter, even though the clicked element has a valid `data-tsd-source`\n attribute. The dev server then hangs the request indefinitely, so the editor never opens.\n\n This is fully reproducible in any React app using `@tanstack/react-devtools@0.10.x` (which pulls in\n `@tanstack/devtools@0.12.2`) + `@tanstack/devtools-vite@0.7.0`. I am seeing it across multiple projects with\n similar stacks.\n\n ## Versions\n\n - `@tanstack/devtools@0.12.2`\n - `@tanstack/react-devtools@0.10.5`\n - `@tanstack/devtools-vite@0.7.0`\n - `vite@8.0.14`, `react@19.2.6`, Solid (bundled via devtools)\n\n ## Reproduction\n\n 1. Add `@tanstack/devtools-vite` to a Vite + React project with a configured `editor`.\n 2. Mount `\u003cTanStackDevtools>` in the app.\n 3. Hold Shift+Alt+Cmd and click any rendered component.\n 4. **Observed:** Network panel shows `GET /__tsd/open-source?source=` (empty value), status `pending` forever.\n Editor never opens.\n 5. **Expected:** URL should include the clicked element's `data-tsd-source`, server should respond `200`, editor\n should open.\n\n I verified the clicked element has a well-formed `data-tsd-source` attribute (matching `/^\\/.+:\\d+:\\d+$/`). On the\n page tested there were 1869 such elements, 0 empty, 0 malformed.\n\n ## Root cause\n\n `packages/devtools/src/.../LGFFJRYV.js` (the inspect overlay component) registers this click handler:\n\n ```js\n createEventListener(document, \"click\", (e) => {\n if (!highlightState.element) return;\n window.getSelection()?.removeAllRanges();\n e.preventDefault();\n e.stopPropagation();\n setDisabledAfterClick(true); // \u003c-- step 1\n if (settings().sourceAction === \"copy-path\") {\n navigator.clipboard.writeText(highlightState.dataSource).catch(() => {});\n return;\n }\n const baseUrl = new URL(import.meta.env?.BASE_URL ?? \"/\", location.origin);\n const url = new URL(\n `__tsd/open-source?source=${encodeURIComponent(highlightState.dataSource)}`, // \u003c-- step 2\n baseUrl\n );\n fetch(url).catch(() => {});\n });\n ```\n\n `setDisabledAfterClick(true)` flips this memo:\n\n ```js\n const isActive = createMemo(\n () => isHighlightingKeysHeld() && !disabledAfterClick()\n );\n ```\n\n …which makes `isActive()` false, which **synchronously** re-runs the highlight `createEffect`:\n\n ```js\n createEffect(() => {\n if (!isActive()) {\n resetHighlight(); // \u003c-- sets element=null, dataSource=\"\"\n return;\n }\n ...\n });\n ```\n\n By the time the next line in the click handler reads `highlightState.dataSource`, it is `\"\"`. The constructed URL\n is `/__tsd/open-source?source=` and is sent.\n\n ## Compounding server-side bug\n\n In `@tanstack/devtools-vite` (`utils.js`), the middleware does:\n\n ```js\n if (req.url?.includes(\"__tsd/open-source\")) {\n const source = new URLSearchParams(req.url.split(\"?\")[1]).get(\"source\");\n if (!source) return; // \u003c-- leaves the response hanging\n const parsed = parseOpenSourceParam(source);\n if (!parsed) return; // \u003c-- same\n ...\n }\n ```\n\n Both early returns omit both `res.end()` and `next()`, so the connection sits open until the browser times out.\n This is why users see \"pending\" rather than a quick failure.\n\n ## Evidence\n\n Captured at click time on a component whose `data-tsd-source` attribute is\n `/src/routes/tournaments/$tournament_id/leaderboard.tsx:55:7`:\n\n ```\n {\n \"url\": \"http://localhost:5173/__tsd/open-source?source=\",\n \"rawSource\": \"\",\n \"stack\": \"...at HTMLDocument.\u003canonymous> (LGFFJRYV-DaE8v0zr.js:6195:3)\"\n }\n ```\n\n `reqid=101 GET http://localhost:5173/__tsd/open-source?source= [pending]` in the Network panel.\n\n The clicked element under the cursor at the same instant:\n\n ```\n { tag: \"H1\", src: \"/src/routes/tournaments/$tournament_id/leaderboard.tsx:55:7\" }\n ```\n\n ## Suggested fix\n\n **Client (`packages/devtools/.../LGFFJRYV.js`):** capture `dataSource` before mutating signals.\n\n ```diff\n createEventListener(document, \"click\", (e) => {\n if (!highlightState.element) return;\n + const source = highlightState.dataSource; // capture before any setSignal\n window.getSelection()?.removeAllRanges();\n e.preventDefault();\n e.stopPropagation();\n - setDisabledAfterClick(true);\n if (settings().sourceAction === \"copy-path\") {\n - navigator.clipboard.writeText(highlightState.dataSource).catch(() => {});\n + navigator.clipboard.writeText(source).catch(() => {});\n + setDisabledAfterClick(true);\n return;\n }\n const baseUrl = new URL(import.meta.env?.BASE_URL ?? \"/\", location.origin);\n - const url = new URL(`__tsd/open-source?source=${encodeURIComponent(highlightState.dataSource)}`, baseUrl);\n + const url = new URL(`__tsd/open-source?source=${encodeURIComponent(source)}`, baseUrl);\n fetch(url).catch(() => {});\n + setDisabledAfterClick(true);\n });\n ```\n\n Wrapping the signal write in `batch(() => setDisabledAfterClick(true))` would not help here — the read happens\n *after* the write, so the value is already reset by the time we read.\n\n **Server (`packages/devtools-vite/src/utils.ts`):** defensively end the response on bad input instead of leaving\n the socket open.\n\n ```diff\n if (req.url?.includes(\"__tsd/open-source\")) {\n const source = new URLSearchParams(req.url.split(\"?\")[1]).get(\"source\");\n - if (!source) return;\n + if (!source) { res.statusCode = 400; res.end(); return; }\n const parsed = parseOpenSourceParam(source);\n - if (!parsed) return;\n + if (!parsed) { res.statusCode = 400; res.end(); return; }\n ...\n }\n ```\n\n ## Workaround for users until fixed\n\n Add this to your client entry (e.g., `src/main.tsx`) in dev only:\n\n ```ts\n if (import.meta.env.DEV) {\n let lastSource: string | null = null;\n document.addEventListener('click', (e) => {\n if (e.shiftKey && e.altKey && (e.metaKey || e.ctrlKey)) {\n const t = document.elementFromPoint(e.clientX, e.clientY);\n const el = t?.closest?.('[data-tsd-source]') as HTMLElement | null;\n if (el) lastSource = el.getAttribute('data-tsd-source');\n }\n }, true); // capture phase: runs before the buggy handler\n const orig = window.fetch;\n window.fetch = function(input, init) {\n const url = typeof input === 'string' ? input\n : input instanceof URL ? input.href\n : (input as Request).url;\n if (typeof url === 'string' && url.endsWith('/__tsd/open-source?source=') && lastSource) {\n const fixed = url + encodeURIComponent(lastSource);\n lastSource = null;\n return orig.call(this, fixed, init);\n }\n return orig.call(this, input, init);\n };\n }\n ```\n\n### Your Minimal, Reproducible Example - (Sandbox Highly Recommended)\n\nA minimal sandbox isn't practical for this bug.\n\n### Screenshots or Videos (Optional)\n\n_No response_\n\n### Do you intend to try to help solve this bug with your own PR?\n\nMaybe, I'll investigate and start debugging\n\n### Terms & Code of Conduct\n\n- [x] I agree to follow this project's Code of Conduct\n- [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.","author":{"url":"https://github.com/samkuehn","@type":"Person","name":"samkuehn"},"datePublished":"2026-05-25T16:36:58Z","url":"https://github.com/TanStack/devtools/issues/451"}
| react-profiling | 0 |
| react-import-map | react,react-dom,react-dom/client,react-dom/profiling,react-is,react-reconciler,react/compiler-runtime,react/jsx-dev-runtime,react/jsx-runtime,scheduler@d5750093facf |
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:9d1c7cb6-452a-253b-91e5-452d983edb97 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | 9CF2:268853:2A6E73A:372D13D:6AF9AA74 |
| html-safe-nonce | 8f61d4145f98f82ee3add65d3e89de5ced8eda5dfc929d71bac6de45e0b895ff |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5Q0YyOjI2ODg1MzoyQTZFNzNBOjM3MkQxM0Q6NkFGOUFBNzQiLCJ2aXNpdG9yX2lkIjoiNDYwNTAxMTA1MjYzMzYzMTM0OCIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | 4aa7c29e56fb1e7900d64032846862c9d31d55f13e7127382c11cd0034ce6b2b |
| hovercard-subject-tag | issue:4518320325 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/TanStack/devtools/451/issue_layout |
| twitter:image | https://opengraph.githubassets.com/5c93c88584c99094f18bf71ba89966de94fa1926ce0803b0f8e738ae02b0ed07/TanStack/devtools/issues/451 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/5c93c88584c99094f18bf71ba89966de94fa1926ce0803b0f8e738ae02b0ed07/TanStack/devtools/issues/451 |
| og:image:alt | 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 inspe... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | samkuehn |
| hostname | github.com |
| expected-hostname | github.com |
| None | 612d3e6ffd693e7ea8d9fcf452370995872baef31d825b1fedef704cd3d24ac7 |
| turbo-cache-control | no-preview |
| go-import | github.com/TanStack/devtools git https://github.com/TanStack/devtools.git |
| octolytics-dimension-user_id | 72518640 |
| octolytics-dimension-user_login | TanStack |
| octolytics-dimension-repository_id | 1026402761 |
| octolytics-dimension-repository_nwo | TanStack/devtools |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 1026402761 |
| octolytics-dimension-repository_network_root_nwo | TanStack/devtools |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 072e34035c13c31543fba9f025cbbbda4210dae4 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
| react-app-name | issues |
Links:
Viewport: width=device-width