From 4aa1c42d0ed8ebf98a37a0396303d078adc44039 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Oct 2026 16:26:30 +0000 Subject: [PATCH 01/11] docs: Shorten Debugging with agents, give agents the __DC__ steps The site section now shows what to ask your agent and what it needs. The .md/llms version instead tells agents how to read __DC__, with the recent-actions and errors snippets, and where the full devtools-debugging reference is. check:browser-snippets fails if those copies drift. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01GdtHs1YHGyHL3LtFQtD1W7 --- docs/core/getting-started/debugging.md | 87 ++++++++++++++----- .../framework-docs/checkBrowserSnippets.mjs | 12 +++ 2 files changed, 75 insertions(+), 24 deletions(-) diff --git a/docs/core/getting-started/debugging.md b/docs/core/getting-started/debugging.md index e6971af1c16d..0f5880f7cf97 100644 --- a/docs/core/getting-started/debugging.md +++ b/docs/core/getting-started/debugging.md @@ -7,40 +7,79 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import ThemedImage from '@theme/ThemedImage'; import useBaseUrl from '@docusaurus/useBaseUrl'; +import SiteOnly from '@site/src/components/SiteOnly'; +import AgentOnly from '@site/src/components/AgentOnly'; ## Debugging with agents -For many debugging tasks, the fastest path is to use an agent that already knows the -:react[`@data-client/react`]:vue[`@data-client/vue`] debugging workflow. + -Install the :react[[`data-client-react` skill](https://skills.sh/reactive/data-client/data-client-react)]:vue[[`data-client-vue` skill](https://skills.sh/reactive/data-client/data-client-vue)] -in your coding agent, then ask it to inspect the current page or app state. +Your coding agent can debug the running app. Ask it about what you see: -### How agent debugging works - -In dev mode, [DevToolsManager](../api/DevToolsManager.md) exposes live `Controller` instances so an agent can inspect -cache state, endpoint metadata, and dispatched actions directly from the running app. +```prompt +The todo list still shows the old title after I save. Find out why. +``` -Technically, the agent reads the page's store from [`globalThis.__DC__`](../api/DevToolsManager.md#controllers): -`__DC__.controller` is its `Controller` and `__DC__.actions` its recent actions. +With Data Client's [skills](./agent-skills.md) set up, it reads the page's store, recent actions and errors through +[`globalThis.__DC__`](../api/DevToolsManager.md#controllers) to find the cause, instead of guessing from code. It +needs a browser tool that runs JavaScript in the page: Cursor's browser, +[Chrome DevTools MCP](https://github.com/ChromeDevTools/chrome-devtools-mcp), +[Playwright MCP](https://github.com/microsoft/playwright-mcp) or Claude in Chrome. + + + + + +When the running app shows wrong or stale data, read its store in the page before reading code for the cause. In +dev mode, `__DC__.controller` is the page's [Controller](../api/Controller.md), so every Controller method works, +and `__DC__.actions` holds its recent [actions](../api/Actions.md). Use any browser tool that runs JavaScript in +the page; one that can only click and read the console can't. + +Each snippet is a function. Pass it as is to a tool that takes a function (Chrome DevTools MCP `evaluate_script`, +Playwright MCP `browser_evaluate`), or evaluate `()()` in one that takes an expression (Claude in Chrome +`javascript_tool`, CDP `Runtime.evaluate` with `returnByValue`). + +```js +// run in page — recent actions +() => __DC__.actions.slice(-20).map(a => ({ + type: a.type, + key: a.key, + endpoint: a.endpoint?.name, + args: a.args, + fetchedAt: a.meta?.fetchedAt, + error: a.error ? (a.response?.message || true) : undefined, +})) +``` -At a high level, the agent can: +Errors thrown while storing a response, like a missing primary key or a response wrapped in `{ data }`, land only in +`meta`, with no `error` on the action, so check both: + +```js +// run in page — errors +() => ({ + actions: __DC__.actions + .filter(a => a.error) + .map(a => ({ type: a.type, key: a.key, error: a.response?.message || true })), + meta: Object.entries(__DC__.controller.getState().meta) + .filter(([, m]) => m.error) + .map(([key, m]) => ({ key, error: m.error.message, status: m.error.status })), +}) +``` -- discover the active :react[`DataProvider`]:vue[`DataClientPlugin`] controllers -- read normalized or denormalized cache state -- inspect recent fetches, responses, errors, and invalidations -- correlate store changes with browser network activity -- trigger safe controller operations like invalidation or expiration for investigation +- `__DC__.controller` throws `Set __DC__.store to one of: ...` when the page has several stores: set + `__DC__.store` to the key in `__DC__.stores` whose `getState().endpoints` holds the keys you are debugging. +- `__DC__` is undefined in production builds, when the managers leave out `DevToolsManager`, and before + `@data-client` 0.19. -This is useful when you want a quick answer to questions like "why didn't this refetch?", -"what is in the cache right now?", or "which action updated this entity?" without manually -clicking through each inspector panel. +For everything else (reading and refetching a key's response, a table of symptoms and what to check first, changing +state, and setup for each browser tool), read the `devtools-debugging` reference of skill +:react[`data-client-react`]:vue[`data-client-vue`]: +:react[`.agents/skills/data-client-react/references/devtools-debugging.md`]:vue[`.agents/skills/data-client-vue/references/devtools-debugging.md`]. +If it's missing, link the skills the app's packages ship by running `npx --no data-client-skills` from the project +root, or read +:react[[the published copy](https://raw.githubusercontent.com/reactive/data-client/master/.agents/skills/data-client-react/references/devtools-debugging.md)]:vue[[the published copy](https://raw.githubusercontent.com/reactive/data-client/master/.agents/skills/data-client-vue/references/devtools-debugging.md)]. -The skill works with any browser tool that can run JavaScript in the page, such as -Cursor's built-in browser, -[Chrome DevTools MCP](https://github.com/ChromeDevTools/chrome-devtools-mcp), -[Playwright MCP](https://github.com/microsoft/playwright-mcp) or Claude in Chrome. If your editor's built-in -browser can only click and read the console, add one of these. + ## Manual debugging diff --git a/website/framework-docs/checkBrowserSnippets.mjs b/website/framework-docs/checkBrowserSnippets.mjs index 3c95898fffe3..c89c48ccac0d 100644 --- a/website/framework-docs/checkBrowserSnippets.mjs +++ b/website/framework-docs/checkBrowserSnippets.mjs @@ -43,6 +43,18 @@ const SNIPPETS = BLOCKS.filter( src: value, })); +// getting-started/debugging.md gives agents a few of these: keep them copies +for (const { lang, value } of docCodeBlocks( + path.join(ROOT, 'docs/core/getting-started/debugging.md'), + 'react', +)) { + if (lang === 'js' && value.startsWith('// run in page')) + assert.ok( + SNIPPETS.some(({ src }) => src === value), + `debugging.md snippet differs from devtools-debugging.md: ${value.split('\n')[0]}`, + ); +} + const SOURCE = fs.readFileSync(DEBUGGING, 'utf8'); /** A `### heading` section of the page's source */ function section(heading) { From 67363caf12e9cff9742982428cbf9ca979026c8f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Oct 2026 16:30:14 +0000 Subject: [PATCH 02/11] docs: Share the __DC__ snippets through partials instead of copies The Debugging page's agent steps and the devtools-debugging reference now import the same recent-actions and errors partials, so the drift check is gone. Trimmed the agent prose to the steps and one pointer. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01GdtHs1YHGyHL3LtFQtD1W7 --- docs/core/_agents/_errors.mdx | 14 ++++++ docs/core/_agents/_recent-actions.mdx | 11 +++++ docs/core/_agents/devtools-debugging.md | 30 ++---------- docs/core/getting-started/debugging.md | 48 ++++--------------- .../framework-docs/checkBrowserSnippets.mjs | 12 ----- 5 files changed, 40 insertions(+), 75 deletions(-) create mode 100644 docs/core/_agents/_errors.mdx create mode 100644 docs/core/_agents/_recent-actions.mdx diff --git a/docs/core/_agents/_errors.mdx b/docs/core/_agents/_errors.mdx new file mode 100644 index 000000000000..434d5fe4d206 --- /dev/null +++ b/docs/core/_agents/_errors.mdx @@ -0,0 +1,14 @@ +Errors thrown while storing a response (for example a missing primary key) land only in `meta`, with no `error` +on the action, so check both: + +```js +// run in page — errors +() => ({ + actions: __DC__.actions + .filter(a => a.error) + .map(a => ({ type: a.type, key: a.key, error: a.response?.message || true })), + meta: Object.entries(__DC__.controller.getState().meta) + .filter(([, m]) => m.error) + .map(([key, m]) => ({ key, error: m.error.message, status: m.error.status })), +}) +``` diff --git a/docs/core/_agents/_recent-actions.mdx b/docs/core/_agents/_recent-actions.mdx new file mode 100644 index 000000000000..c51089c7d34c --- /dev/null +++ b/docs/core/_agents/_recent-actions.mdx @@ -0,0 +1,11 @@ +```js +// run in page — recent actions +() => __DC__.actions.slice(-20).map(a => ({ + type: a.type, + key: a.key, + endpoint: a.endpoint?.name, + args: a.args, + fetchedAt: a.meta?.fetchedAt, + error: a.error ? (a.response?.message || true) : undefined, +})) +``` diff --git a/docs/core/_agents/devtools-debugging.md b/docs/core/_agents/devtools-debugging.md index 61eeb6c04731..2218b15284e5 100644 --- a/docs/core/_agents/devtools-debugging.md +++ b/docs/core/_agents/devtools-debugging.md @@ -2,6 +2,9 @@ title: Debugging in the Browser --- +import RecentActions from './_recent-actions.mdx'; +import Errors from './_errors.mdx'; + Debug :react[`@data-client/react`]:vue[`@data-client/vue`] state and actions programmatically from any browser tool that runs JavaScript in the page. In dev mode, the app's [DevToolsManager](../api/DevToolsManager.md#controllers) puts the store on `globalThis.__DC__`: `__DC__.controller` is its [Controller](../api/Controller.md), so every Controller method works, and `__DC__.actions` is its recent [actions](../api/Actions.md). ## Prerequisites @@ -179,34 +182,11 @@ The log keeps the newest 100 actions, and each polling key (endpoint and args) o front, so select by time (`meta.fetchedAt >= start`), not by index. Actions hold endpoints and promises, so map them to plain fields: -```js -// run in page — recent actions -() => __DC__.actions.slice(-20).map(a => ({ - type: a.type, - key: a.key, - endpoint: a.endpoint?.name, - args: a.args, - fetchedAt: a.meta?.fetchedAt, - error: a.error ? (a.response?.message || true) : undefined, -})) -``` + ### Errors -Errors thrown while storing a response (for example a missing primary key) land only in `meta`, with no `error` -on the action, so check both: - -```js -// run in page — errors -() => ({ - actions: __DC__.actions - .filter(a => a.error) - .map(a => ({ type: a.type, key: a.key, error: a.response?.message || true })), - meta: Object.entries(__DC__.controller.getState().meta) - .filter(([, m]) => m.error) - .map(([key, m]) => ({ key, error: m.error.message, status: m.error.status })), -}) -``` + ## Change State diff --git a/docs/core/getting-started/debugging.md b/docs/core/getting-started/debugging.md index 0f5880f7cf97..fcb4d5be7517 100644 --- a/docs/core/getting-started/debugging.md +++ b/docs/core/getting-started/debugging.md @@ -9,6 +9,8 @@ import ThemedImage from '@theme/ThemedImage'; import useBaseUrl from '@docusaurus/useBaseUrl'; import SiteOnly from '@site/src/components/SiteOnly'; import AgentOnly from '@site/src/components/AgentOnly'; +import RecentActions from '../_agents/_recent-actions.mdx'; +import Errors from '../_agents/_errors.mdx'; ## Debugging with agents @@ -32,51 +34,21 @@ needs a browser tool that runs JavaScript in the page: Cursor's browser, When the running app shows wrong or stale data, read its store in the page before reading code for the cause. In dev mode, `__DC__.controller` is the page's [Controller](../api/Controller.md), so every Controller method works, -and `__DC__.actions` holds its recent [actions](../api/Actions.md). Use any browser tool that runs JavaScript in -the page; one that can only click and read the console can't. +and `__DC__.actions` holds its recent [actions](../api/Actions.md). Each snippet is a function. Pass it as is to a tool that takes a function (Chrome DevTools MCP `evaluate_script`, Playwright MCP `browser_evaluate`), or evaluate `()()` in one that takes an expression (Claude in Chrome `javascript_tool`, CDP `Runtime.evaluate` with `returnByValue`). -```js -// run in page — recent actions -() => __DC__.actions.slice(-20).map(a => ({ - type: a.type, - key: a.key, - endpoint: a.endpoint?.name, - args: a.args, - fetchedAt: a.meta?.fetchedAt, - error: a.error ? (a.response?.message || true) : undefined, -})) -``` + -Errors thrown while storing a response, like a missing primary key or a response wrapped in `{ data }`, land only in -`meta`, with no `error` on the action, so check both: - -```js -// run in page — errors -() => ({ - actions: __DC__.actions - .filter(a => a.error) - .map(a => ({ type: a.type, key: a.key, error: a.response?.message || true })), - meta: Object.entries(__DC__.controller.getState().meta) - .filter(([, m]) => m.error) - .map(([key, m]) => ({ key, error: m.error.message, status: m.error.status })), -}) -``` + + +`__DC__` is undefined in production builds, without `DevToolsManager` in the managers, or before `@data-client` 0.19. -- `__DC__.controller` throws `Set __DC__.store to one of: ...` when the page has several stores: set - `__DC__.store` to the key in `__DC__.stores` whose `getState().endpoints` holds the keys you are debugging. -- `__DC__` is undefined in production builds, when the managers leave out `DevToolsManager`, and before - `@data-client` 0.19. - -For everything else (reading and refetching a key's response, a table of symptoms and what to check first, changing -state, and setup for each browser tool), read the `devtools-debugging` reference of skill -:react[`data-client-react`]:vue[`data-client-vue`]: -:react[`.agents/skills/data-client-react/references/devtools-debugging.md`]:vue[`.agents/skills/data-client-vue/references/devtools-debugging.md`]. -If it's missing, link the skills the app's packages ship by running `npx --no data-client-skills` from the project -root, or read +The rest (a key's response and refetch, symptoms and what to check first, changing state, each browser tool's setup) +is in :react[`.agents/skills/data-client-react/references/devtools-debugging.md`]:vue[`.agents/skills/data-client-vue/references/devtools-debugging.md`]. +If it's missing, `npx --no data-client-skills` from the project root links it, or read :react[[the published copy](https://raw.githubusercontent.com/reactive/data-client/master/.agents/skills/data-client-react/references/devtools-debugging.md)]:vue[[the published copy](https://raw.githubusercontent.com/reactive/data-client/master/.agents/skills/data-client-vue/references/devtools-debugging.md)]. diff --git a/website/framework-docs/checkBrowserSnippets.mjs b/website/framework-docs/checkBrowserSnippets.mjs index c89c48ccac0d..3c95898fffe3 100644 --- a/website/framework-docs/checkBrowserSnippets.mjs +++ b/website/framework-docs/checkBrowserSnippets.mjs @@ -43,18 +43,6 @@ const SNIPPETS = BLOCKS.filter( src: value, })); -// getting-started/debugging.md gives agents a few of these: keep them copies -for (const { lang, value } of docCodeBlocks( - path.join(ROOT, 'docs/core/getting-started/debugging.md'), - 'react', -)) { - if (lang === 'js' && value.startsWith('// run in page')) - assert.ok( - SNIPPETS.some(({ src }) => src === value), - `debugging.md snippet differs from devtools-debugging.md: ${value.split('\n')[0]}`, - ); -} - const SOURCE = fs.readFileSync(DEBUGGING, 'utf8'); /** A `### heading` section of the page's source */ function section(heading) { From 277dca303d5eb77f1b79628a231e0cefe5595aba Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Oct 2026 16:36:26 +0000 Subject: [PATCH 03/11] docs: Give agents the read-a-key snippet, fix the CDP awaitPromise hint The Debugging page's agent steps now also import the response-for-a-key snippet, say to await with CDP like the reference does, and note the Todo placeholders. The rule, README and checker header say partials hold some snippets. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01GdtHs1YHGyHL3LtFQtD1W7 --- .../references/devtools-debugging.md | 4 +++- .../references/devtools-debugging.md | 4 +++- .claude/rules/agent-browser-docs.md | 2 +- .cursor/rules/agent-browser-docs.mdc | 2 +- docs/core/_agents/_response-for-key.mdx | 18 +++++++++++++++++ docs/core/_agents/devtools-debugging.md | 20 +++---------------- docs/core/getting-started/debugging.md | 12 +++++++---- website/framework-docs/README.md | 2 +- .../framework-docs/checkBrowserSnippets.mjs | 2 +- 9 files changed, 39 insertions(+), 27 deletions(-) create mode 100644 docs/core/_agents/_response-for-key.mdx diff --git a/.agents/skills/data-client-react/references/devtools-debugging.md b/.agents/skills/data-client-react/references/devtools-debugging.md index 3078576a38c7..5e5fc620d80e 100644 --- a/.agents/skills/data-client-react/references/devtools-debugging.md +++ b/.agents/skills/data-client-react/references/devtools-debugging.md @@ -141,7 +141,9 @@ so this works for any key read since the page loaded: } ``` -`expiryStatus`: `1` Invalid, `2` InvalidIfStale, `3` Valid. To fetch it again and read the fresh response: +`expiryStatus`: `1` Invalid, `2` InvalidIfStale, `3` Valid. + +To fetch it again and read the fresh response: ```js // run in page — refetch a key diff --git a/.agents/skills/data-client-vue/references/devtools-debugging.md b/.agents/skills/data-client-vue/references/devtools-debugging.md index 2944adccc409..5d97f03dbf03 100644 --- a/.agents/skills/data-client-vue/references/devtools-debugging.md +++ b/.agents/skills/data-client-vue/references/devtools-debugging.md @@ -141,7 +141,9 @@ so this works for any key read since the page loaded: } ``` -`expiryStatus`: `1` Invalid, `2` InvalidIfStale, `3` Valid. To fetch it again and read the fresh response: +`expiryStatus`: `1` Invalid, `2` InvalidIfStale, `3` Valid. + +To fetch it again and read the fresh response: ```js // run in page — refetch a key diff --git a/.claude/rules/agent-browser-docs.md b/.claude/rules/agent-browser-docs.md index 0d47ca844db3..8441b300736c 100644 --- a/.claude/rules/agent-browser-docs.md +++ b/.claude/rules/agent-browser-docs.md @@ -13,7 +13,7 @@ paths: ## Layout -- `docs/core/_agents/devtools-debugging.md` is the core: every snippet, the ways a tool can run one (Browser Tools, whose table is the only list of each tool's runner, way and readers), and fallbacks for tools without request or console readers. Each `browser-.md` is a thin adapter: install, open the page, which way it runs snippets, and quirks only that tool has. Anything a second tool could use goes in the core. +- `docs/core/_agents/devtools-debugging.md` is the core: every snippet (some through its `_*.mdx` partials, which the Debugging page's agent steps also import), the ways a tool can run one (Browser Tools, whose table is the only list of each tool's runner, way and readers), and fallbacks for tools without request or console readers. Each `browser-.md` is a thin adapter: install, open the page, which way it runs snippets, and quirks only that tool has. Anything a second tool could use goes in the core. - A tool that fits none of the ways adds a `###` way to the core and a runner to `WAYS` in `checkBrowserSnippets.mjs`. - Adding, renaming or dropping a tool touches: the Browser Tools table, the agent table in `browser-cursor.md` (Cursor only), the Status table below, the tool names on `getting-started/debugging.md`, `getting-started/agent-skills.md` and `api/DevToolsManager.md`, and both skills' `references.json` plus the reference line in their `SKILL.md`. diff --git a/.cursor/rules/agent-browser-docs.mdc b/.cursor/rules/agent-browser-docs.mdc index b3553fb45867..bafbffdb2431 100644 --- a/.cursor/rules/agent-browser-docs.mdc +++ b/.cursor/rules/agent-browser-docs.mdc @@ -8,7 +8,7 @@ alwaysApply: false ## Layout -- `docs/core/_agents/devtools-debugging.md` is the core: every snippet, the ways a tool can run one (Browser Tools, whose table is the only list of each tool's runner, way and readers), and fallbacks for tools without request or console readers. Each `browser-.md` is a thin adapter: install, open the page, which way it runs snippets, and quirks only that tool has. Anything a second tool could use goes in the core. +- `docs/core/_agents/devtools-debugging.md` is the core: every snippet (some through its `_*.mdx` partials, which the Debugging page's agent steps also import), the ways a tool can run one (Browser Tools, whose table is the only list of each tool's runner, way and readers), and fallbacks for tools without request or console readers. Each `browser-.md` is a thin adapter: install, open the page, which way it runs snippets, and quirks only that tool has. Anything a second tool could use goes in the core. - A tool that fits none of the ways adds a `###` way to the core and a runner to `WAYS` in `checkBrowserSnippets.mjs`. - Adding, renaming or dropping a tool touches: the Browser Tools table, the agent table in `browser-cursor.md` (Cursor only), the Status table below, the tool names on `getting-started/debugging.md`, `getting-started/agent-skills.md` and `api/DevToolsManager.md`, and both skills' `references.json` plus the reference line in their `SKILL.md`. diff --git a/docs/core/_agents/_response-for-key.mdx b/docs/core/_agents/_response-for-key.mdx new file mode 100644 index 000000000000..2b34b5f7b00a --- /dev/null +++ b/docs/core/_agents/_response-for-key.mdx @@ -0,0 +1,18 @@ +A key's denormalized response, as components see it. The action log holds each fetched key's endpoint and `args`, +so this works for any key read since the page loaded: + +```js +// run in page — response for a key +() => { + const c = __DC__.controller, state = c.getState(); + // a fetch of the key, not a mutation of an item under it + const a = __DC__.actions.findLast( + a => a.args && !a.endpoint?.sideEffect && a.key?.includes('/todos'), + ); + if (!a) return { error: 'key not in the action log; repeat the fetch' }; + const { data, expiryStatus, expiresAt } = c.getResponse(a.endpoint, ...a.args, state); + return { key: a.key, data, expiryStatus, expiresAt, error: c.getError(a.endpoint, ...a.args, state)?.message }; +} +``` + +`expiryStatus`: `1` Invalid, `2` InvalidIfStale, `3` Valid. diff --git a/docs/core/_agents/devtools-debugging.md b/docs/core/_agents/devtools-debugging.md index 2218b15284e5..cff5e33ac7d0 100644 --- a/docs/core/_agents/devtools-debugging.md +++ b/docs/core/_agents/devtools-debugging.md @@ -2,6 +2,7 @@ title: Debugging in the Browser --- +import ResponseForKey from './_response-for-key.mdx'; import RecentActions from './_recent-actions.mdx'; import Errors from './_errors.mdx'; @@ -127,24 +128,9 @@ Snippets use the entity `Todo` and paths containing `/todos`: replace them with ## Read State -A key's denormalized response, as components see it. The action log holds each fetched key's endpoint and `args`, -so this works for any key read since the page loaded: + -```js -// run in page — response for a key -() => { - const c = __DC__.controller, state = c.getState(); - // a fetch of the key, not a mutation of an item under it - const a = __DC__.actions.findLast( - a => a.args && !a.endpoint?.sideEffect && a.key?.includes('/todos'), - ); - if (!a) return { error: 'key not in the action log; repeat the fetch' }; - const { data, expiryStatus, expiresAt } = c.getResponse(a.endpoint, ...a.args, state); - return { key: a.key, data, expiryStatus, expiresAt, error: c.getError(a.endpoint, ...a.args, state)?.message }; -} -``` - -`expiryStatus`: `1` Invalid, `2` InvalidIfStale, `3` Valid. To fetch it again and read the fresh response: +To fetch it again and read the fresh response: ```js // run in page — refetch a key diff --git a/docs/core/getting-started/debugging.md b/docs/core/getting-started/debugging.md index fcb4d5be7517..8f5544fe9fda 100644 --- a/docs/core/getting-started/debugging.md +++ b/docs/core/getting-started/debugging.md @@ -9,6 +9,7 @@ import ThemedImage from '@theme/ThemedImage'; import useBaseUrl from '@docusaurus/useBaseUrl'; import SiteOnly from '@site/src/components/SiteOnly'; import AgentOnly from '@site/src/components/AgentOnly'; +import ResponseForKey from '../_agents/_response-for-key.mdx'; import RecentActions from '../_agents/_recent-actions.mdx'; import Errors from '../_agents/_errors.mdx'; @@ -36,9 +37,12 @@ When the running app shows wrong or stale data, read its store in the page befor dev mode, `__DC__.controller` is the page's [Controller](../api/Controller.md), so every Controller method works, and `__DC__.actions` holds its recent [actions](../api/Actions.md). -Each snippet is a function. Pass it as is to a tool that takes a function (Chrome DevTools MCP `evaluate_script`, -Playwright MCP `browser_evaluate`), or evaluate `()()` in one that takes an expression (Claude in Chrome -`javascript_tool`, CDP `Runtime.evaluate` with `returnByValue`). +Each snippet below is a sync function. Pass it as is to a tool that takes a function (Chrome DevTools MCP +`evaluate_script`, Playwright MCP `browser_evaluate`), or evaluate `()()` in one that takes an expression +(Claude in Chrome `javascript_tool`, CDP `Runtime.evaluate` with `awaitPromise` and `returnByValue`). They use the +entity `Todo` and paths containing `/todos`: replace them with the app's own. + + @@ -46,7 +50,7 @@ Playwright MCP `browser_evaluate`), or evaluate `()()` in one that take `__DC__` is undefined in production builds, without `DevToolsManager` in the managers, or before `@data-client` 0.19. -The rest (a key's response and refetch, symptoms and what to check first, changing state, each browser tool's setup) +The rest (refetching, symptoms and what to check first, changing state, each browser tool's setup) is in :react[`.agents/skills/data-client-react/references/devtools-debugging.md`]:vue[`.agents/skills/data-client-vue/references/devtools-debugging.md`]. If it's missing, `npx --no data-client-skills` from the project root links it, or read :react[[the published copy](https://raw.githubusercontent.com/reactive/data-client/master/.agents/skills/data-client-react/references/devtools-debugging.md)]:vue[[the published copy](https://raw.githubusercontent.com/reactive/data-client/master/.agents/skills/data-client-vue/references/devtools-debugging.md)]. diff --git a/website/framework-docs/README.md b/website/framework-docs/README.md index c4541e094a66..000897fe7913 100644 --- a/website/framework-docs/README.md +++ b/website/framework-docs/README.md @@ -128,7 +128,7 @@ Vue: ### Browser debugging snippets `yarn check:browser-snippets` (`checkBrowserSnippets.mjs`, run by the `regenerate` check) runs every -` ```js ` block starting `// run in page` in `docs/core/_agents/devtools-debugging.md` against a small +` ```js ` block starting `// run in page` in `docs/core/_agents/devtools-debugging.md` (including its `_*.mdx` partials) against a small React app built from the workspace sources, in Chromium, each way that page's Browser Tools section says a tool can run them, with their parameters and the `__DC_RESULT__` recipe read from the page (a new way needs a runner in its `WAYS`). It also checks the `Runtime.evaluate` results the page describes. Snippets use the entity diff --git a/website/framework-docs/checkBrowserSnippets.mjs b/website/framework-docs/checkBrowserSnippets.mjs index 3c95898fffe3..c32e2e1112ac 100644 --- a/website/framework-docs/checkBrowserSnippets.mjs +++ b/website/framework-docs/checkBrowserSnippets.mjs @@ -1,6 +1,6 @@ /** * Runs the "run in page" snippets agents use to debug a Data Client app - * (docs/core/_agents/devtools-debugging.md) in Chromium, each way its Browser + * (docs/core/_agents/devtools-debugging.md and its partials) in Chromium, each way its Browser * Tools section says a tool can run them, so a change to the snippets, those * instructions, or DevToolsManager's globals fails here instead of in an * agent's session. The ways' parameters and the `__DC_RESULT__` recipe are read From 85361c5267c468b3e886db029d3a66bffd1d5c08 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Oct 2026 17:06:02 +0000 Subject: [PATCH 04/11] docs: Lead Debugging with agents with what __DC__ gives agents Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01GdtHs1YHGyHL3LtFQtD1W7 --- docs/core/getting-started/debugging.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/core/getting-started/debugging.md b/docs/core/getting-started/debugging.md index 8f5544fe9fda..22353e661f3c 100644 --- a/docs/core/getting-started/debugging.md +++ b/docs/core/getting-started/debugging.md @@ -17,14 +17,14 @@ import Errors from '../_agents/_errors.mdx'; -Your coding agent can debug the running app. Ask it about what you see: +[`globalThis.__DC__`](../api/DevToolsManager.md#controllers) allows agents to introspect the Data Client store of the +running app: its state, recent actions and errors, so your coding agent can debug what you see. Ask it: ```prompt The todo list still shows the old title after I save. Find out why. ``` -With Data Client's [skills](./agent-skills.md) set up, it reads the page's store, recent actions and errors through -[`globalThis.__DC__`](../api/DevToolsManager.md#controllers) to find the cause, instead of guessing from code. It +With Data Client's [skills](./agent-skills.md) set up, it finds the cause there instead of guessing from code. It needs a browser tool that runs JavaScript in the page: Cursor's browser, [Chrome DevTools MCP](https://github.com/ChromeDevTools/chrome-devtools-mcp), [Playwright MCP](https://github.com/microsoft/playwright-mcp) or Claude in Chrome. From 70bb9c4d13568cc77e34014867896aaf7da5dafd Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Oct 2026 17:06:59 +0000 Subject: [PATCH 05/11] docs: Link an issue report for agents or browser tools debugging doesn't work with Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01GdtHs1YHGyHL3LtFQtD1W7 --- docs/core/getting-started/debugging.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/core/getting-started/debugging.md b/docs/core/getting-started/debugging.md index 22353e661f3c..f067b2fea9b0 100644 --- a/docs/core/getting-started/debugging.md +++ b/docs/core/getting-started/debugging.md @@ -27,7 +27,9 @@ The todo list still shows the old title after I save. Find out why. With Data Client's [skills](./agent-skills.md) set up, it finds the cause there instead of guessing from code. It needs a browser tool that runs JavaScript in the page: Cursor's browser, [Chrome DevTools MCP](https://github.com/ChromeDevTools/chrome-devtools-mcp), -[Playwright MCP](https://github.com/microsoft/playwright-mcp) or Claude in Chrome. +[Playwright MCP](https://github.com/microsoft/playwright-mcp) or Claude in Chrome. If debugging doesn't work with your +agent or browser tool, [open an issue](https://github.com/reactive/data-client/issues/new?template=bug_report.md&title=Agent%20debugging%3A%20) +naming both so we can support it. From 55107544b22e3e65f669b79e426ef759aae9d341 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Oct 2026 17:09:32 +0000 Subject: [PATCH 06/11] docs: Say __DC__ is for development builds in the human debugging section Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01GdtHs1YHGyHL3LtFQtD1W7 --- docs/core/getting-started/debugging.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/core/getting-started/debugging.md b/docs/core/getting-started/debugging.md index f067b2fea9b0..53d102aa1c87 100644 --- a/docs/core/getting-started/debugging.md +++ b/docs/core/getting-started/debugging.md @@ -17,14 +17,14 @@ import Errors from '../_agents/_errors.mdx'; -[`globalThis.__DC__`](../api/DevToolsManager.md#controllers) allows agents to introspect the Data Client store of the -running app: its state, recent actions and errors, so your coding agent can debug what you see. Ask it: +[`globalThis.__DC__`](../api/DevToolsManager.md#controllers) allows agents to introspect the Data Client store of your +app in development: its state, recent actions and errors, so your coding agent can debug what you see. Ask it: ```prompt The todo list still shows the old title after I save. Find out why. ``` -With Data Client's [skills](./agent-skills.md) set up, it finds the cause there instead of guessing from code. It +With Data Client's [skills](./agent-skills.md) set up, it finds the cause in the store instead of guessing from code. It needs a browser tool that runs JavaScript in the page: Cursor's browser, [Chrome DevTools MCP](https://github.com/ChromeDevTools/chrome-devtools-mcp), [Playwright MCP](https://github.com/microsoft/playwright-mcp) or Claude in Chrome. If debugging doesn't work with your From 8fd92cd4312dff4e9cb71d0483bc49212231eaae Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 17:58:55 +0000 Subject: [PATCH 07/11] docs: Put the debugging issue link in its own paragraph Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01GdtHs1YHGyHL3LtFQtD1W7 --- docs/core/getting-started/debugging.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/core/getting-started/debugging.md b/docs/core/getting-started/debugging.md index 53d102aa1c87..1785d9af4cc7 100644 --- a/docs/core/getting-started/debugging.md +++ b/docs/core/getting-started/debugging.md @@ -27,8 +27,9 @@ The todo list still shows the old title after I save. Find out why. With Data Client's [skills](./agent-skills.md) set up, it finds the cause in the store instead of guessing from code. It needs a browser tool that runs JavaScript in the page: Cursor's browser, [Chrome DevTools MCP](https://github.com/ChromeDevTools/chrome-devtools-mcp), -[Playwright MCP](https://github.com/microsoft/playwright-mcp) or Claude in Chrome. If debugging doesn't work with your -agent or browser tool, [open an issue](https://github.com/reactive/data-client/issues/new?template=bug_report.md&title=Agent%20debugging%3A%20) +[Playwright MCP](https://github.com/microsoft/playwright-mcp) or Claude in Chrome. + +If debugging doesn't work with your agent or browser tool, [open an issue](https://github.com/reactive/data-client/issues/new?template=bug_report.md&title=Agent%20debugging%3A%20) naming both so we can support it. From b24a9628ac351d22506f7fed116744af676b806f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 17:59:47 +0000 Subject: [PATCH 08/11] docs: Add a prompt that has the agent file the debugging bug report Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01GdtHs1YHGyHL3LtFQtD1W7 --- docs/core/getting-started/debugging.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/core/getting-started/debugging.md b/docs/core/getting-started/debugging.md index 1785d9af4cc7..b97c7cdb979c 100644 --- a/docs/core/getting-started/debugging.md +++ b/docs/core/getting-started/debugging.md @@ -30,7 +30,11 @@ needs a browser tool that runs JavaScript in the page: Cursor's browser, [Playwright MCP](https://github.com/microsoft/playwright-mcp) or Claude in Chrome. If debugging doesn't work with your agent or browser tool, [open an issue](https://github.com/reactive/data-client/issues/new?template=bug_report.md&title=Agent%20debugging%3A%20) -naming both so we can support it. +naming both so we can support it. Or have your agent write it: + +```prompt +Debugging this app through globalThis.__DC__ didn't work. Open a bug report on reactive/data-client titled "Agent debugging: + " (with gh issue create, or give me the text to paste), including your agent and browser tool names and versions, the app's @data-client versions, what you ran and what happened. +``` From 8687aed39b29c3f27e71ba188f3c3c0ac8fdc765 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 18:06:01 +0000 Subject: [PATCH 09/11] docs: Give agents the whole browser-debugging guide on the Debugging page The page's markdown (.md, llms.txt) now embeds docs/core/_agents/devtools-debugging.md in , so agents reading it get what the skill reference gives, plus one line on the browser extension to tell the user about. Manual debugging is site-only. remarkFramework strips on the site before the table of contents is built, so it can hold headings and partials. Links to _agents pages in page markdown go to the skill's generated copy on GitHub. The issue prompt now checks for duplicates, follows the bug template, redacts app data and asks first. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01GdtHs1YHGyHL3LtFQtD1W7 --- .../references/devtools-debugging.md | 4 +- .../references/devtools-debugging.md | 4 +- .claude/rules/agent-browser-docs.md | 2 +- .claude/rules/skills-sync.md | 2 +- .cursor/rules/agent-browser-docs.mdc | 2 +- .cursor/rules/skills-sync.mdc | 2 +- docs/core/_agents/_errors.mdx | 14 ------ docs/core/_agents/_recent-actions.mdx | 11 ---- docs/core/_agents/_response-for-key.mdx | 18 ------- docs/core/_agents/devtools-debugging.md | 50 ++++++++++++++++--- docs/core/getting-started/debugging.md | 37 +++++--------- website/framework-docs/README.md | 2 +- .../framework-docs/checkBrowserSnippets.mjs | 2 +- website/framework-docs/docsToMarkdown.mjs | 20 +++++++- website/framework-docs/remarkFramework.js | 21 ++++++-- website/llms-plugin.js | 4 +- website/src/components/AgentOnly.tsx | 3 +- 17 files changed, 102 insertions(+), 96 deletions(-) delete mode 100644 docs/core/_agents/_errors.mdx delete mode 100644 docs/core/_agents/_recent-actions.mdx delete mode 100644 docs/core/_agents/_response-for-key.mdx diff --git a/.agents/skills/data-client-react/references/devtools-debugging.md b/.agents/skills/data-client-react/references/devtools-debugging.md index 5e5fc620d80e..3078576a38c7 100644 --- a/.agents/skills/data-client-react/references/devtools-debugging.md +++ b/.agents/skills/data-client-react/references/devtools-debugging.md @@ -141,9 +141,7 @@ so this works for any key read since the page loaded: } ``` -`expiryStatus`: `1` Invalid, `2` InvalidIfStale, `3` Valid. - -To fetch it again and read the fresh response: +`expiryStatus`: `1` Invalid, `2` InvalidIfStale, `3` Valid. To fetch it again and read the fresh response: ```js // run in page — refetch a key diff --git a/.agents/skills/data-client-vue/references/devtools-debugging.md b/.agents/skills/data-client-vue/references/devtools-debugging.md index 5d97f03dbf03..2944adccc409 100644 --- a/.agents/skills/data-client-vue/references/devtools-debugging.md +++ b/.agents/skills/data-client-vue/references/devtools-debugging.md @@ -141,9 +141,7 @@ so this works for any key read since the page loaded: } ``` -`expiryStatus`: `1` Invalid, `2` InvalidIfStale, `3` Valid. - -To fetch it again and read the fresh response: +`expiryStatus`: `1` Invalid, `2` InvalidIfStale, `3` Valid. To fetch it again and read the fresh response: ```js // run in page — refetch a key diff --git a/.claude/rules/agent-browser-docs.md b/.claude/rules/agent-browser-docs.md index 8441b300736c..0d47ca844db3 100644 --- a/.claude/rules/agent-browser-docs.md +++ b/.claude/rules/agent-browser-docs.md @@ -13,7 +13,7 @@ paths: ## Layout -- `docs/core/_agents/devtools-debugging.md` is the core: every snippet (some through its `_*.mdx` partials, which the Debugging page's agent steps also import), the ways a tool can run one (Browser Tools, whose table is the only list of each tool's runner, way and readers), and fallbacks for tools without request or console readers. Each `browser-.md` is a thin adapter: install, open the page, which way it runs snippets, and quirks only that tool has. Anything a second tool could use goes in the core. +- `docs/core/_agents/devtools-debugging.md` is the core: every snippet, the ways a tool can run one (Browser Tools, whose table is the only list of each tool's runner, way and readers), and fallbacks for tools without request or console readers. Each `browser-.md` is a thin adapter: install, open the page, which way it runs snippets, and quirks only that tool has. Anything a second tool could use goes in the core. - A tool that fits none of the ways adds a `###` way to the core and a runner to `WAYS` in `checkBrowserSnippets.mjs`. - Adding, renaming or dropping a tool touches: the Browser Tools table, the agent table in `browser-cursor.md` (Cursor only), the Status table below, the tool names on `getting-started/debugging.md`, `getting-started/agent-skills.md` and `api/DevToolsManager.md`, and both skills' `references.json` plus the reference line in their `SKILL.md`. diff --git a/.claude/rules/skills-sync.md b/.claude/rules/skills-sync.md index b9e3d9776c90..16730d13c130 100644 --- a/.claude/rules/skills-sync.md +++ b/.claude/rules/skills-sync.md @@ -16,7 +16,7 @@ Skill `references/*.md` files listed in a skill's `references.json` are generate - **Renaming, moving or deleting a doc**: update every `references.json` entry and `SKILL.md` link to it (`grep -rn '' .agents/skills`). The generator fails on a missing source. - **Changing a public API** (rename, signature, new option, deprecation): grep `.agents/skills` for the old name and update `SKILL.md` examples and hand-written references in the same PR. Generated references only follow the docs. - **Agent-only references** (instructions for agents, not readers of the site): put the source in `docs/core/_agents/`. Docusaurus never publishes `_` folders, but `yarn build:skills` still renders the `:react[]`/`:vue[]` markers and resolves doc links, so one source serves both frameworks. -- **Site-only prose** (e.g. "install the skill and run `/data-client-setup`"): wrap it in ``. Skill references and `llms.txt` drop it, since agents read them: they get the detailed steps, not "tell your agent". Where a page's markdown (`.md`, `llms.txt`) still needs the step itself, such as which command to run, put it beside in ``: hidden on the site, kept in page markdown, dropped from skill references (the skill gives it). No headings inside: the site's table of contents would list them. Never send readers to `llms.txt` or tell them to point their agent at it. +- **Site-only prose** (e.g. "install the skill and run `/data-client-setup`"): wrap it in ``. Skill references and `llms.txt` drop it, since agents read them: they get the detailed steps, not "tell your agent". Where a page's markdown (`.md`, `llms.txt`) still needs the step itself, such as which command to run, put it beside in ``: hidden on the site, kept in page markdown, dropped from skill references (the skill gives it). It can hold headings and partials (the site strips it before building the table of contents), so a page can give agents a whole `_agents/` guide, like `getting-started/debugging.md`. Never send readers to `llms.txt` or tell them to point their agent at it. - **Adding a hand-written reference**: `.gitattributes` marks `references/**` as `linguist-generated` (collapsed in GitHub diffs); add a `-linguist-generated` line for it. `yarn build:skills` fails until you do. - **Never copy a doc into a reference**: the build fails on a hand-written reference named after a `docs/` page. List the page in `references.json`, and put what it lacks in the doc, or in an `_agents/` source if only agents need it. - **Framework-specific pages**: a `frameworks: [react]` page is skipped for Vue; a skill whose `frameworks` lists `vue` must not rely on it. Skills covering both frameworks link `name.md`; `name.vue.md` exists only where Vue differs. diff --git a/.cursor/rules/agent-browser-docs.mdc b/.cursor/rules/agent-browser-docs.mdc index bafbffdb2431..b3553fb45867 100644 --- a/.cursor/rules/agent-browser-docs.mdc +++ b/.cursor/rules/agent-browser-docs.mdc @@ -8,7 +8,7 @@ alwaysApply: false ## Layout -- `docs/core/_agents/devtools-debugging.md` is the core: every snippet (some through its `_*.mdx` partials, which the Debugging page's agent steps also import), the ways a tool can run one (Browser Tools, whose table is the only list of each tool's runner, way and readers), and fallbacks for tools without request or console readers. Each `browser-.md` is a thin adapter: install, open the page, which way it runs snippets, and quirks only that tool has. Anything a second tool could use goes in the core. +- `docs/core/_agents/devtools-debugging.md` is the core: every snippet, the ways a tool can run one (Browser Tools, whose table is the only list of each tool's runner, way and readers), and fallbacks for tools without request or console readers. Each `browser-.md` is a thin adapter: install, open the page, which way it runs snippets, and quirks only that tool has. Anything a second tool could use goes in the core. - A tool that fits none of the ways adds a `###` way to the core and a runner to `WAYS` in `checkBrowserSnippets.mjs`. - Adding, renaming or dropping a tool touches: the Browser Tools table, the agent table in `browser-cursor.md` (Cursor only), the Status table below, the tool names on `getting-started/debugging.md`, `getting-started/agent-skills.md` and `api/DevToolsManager.md`, and both skills' `references.json` plus the reference line in their `SKILL.md`. diff --git a/.cursor/rules/skills-sync.mdc b/.cursor/rules/skills-sync.mdc index 5b9fb6b1a4ea..9866c5c2dd7f 100644 --- a/.cursor/rules/skills-sync.mdc +++ b/.cursor/rules/skills-sync.mdc @@ -13,7 +13,7 @@ Skill `references/*.md` files listed in a skill's `references.json` are generate - **Renaming, moving or deleting a doc**: update every `references.json` entry and `SKILL.md` link to it (`grep -rn '' .agents/skills`). The generator fails on a missing source. - **Changing a public API** (rename, signature, new option, deprecation): grep `.agents/skills` for the old name and update `SKILL.md` examples and hand-written references in the same PR. Generated references only follow the docs. - **Agent-only references** (instructions for agents, not readers of the site): put the source in `docs/core/_agents/`. Docusaurus never publishes `_` folders, but `yarn build:skills` still renders the `:react[]`/`:vue[]` markers and resolves doc links, so one source serves both frameworks. -- **Site-only prose** (e.g. "install the skill and run `/data-client-setup`"): wrap it in ``. Skill references and `llms.txt` drop it, since agents read them: they get the detailed steps, not "tell your agent". Where a page's markdown (`.md`, `llms.txt`) still needs the step itself, such as which command to run, put it beside in ``: hidden on the site, kept in page markdown, dropped from skill references (the skill gives it). No headings inside: the site's table of contents would list them. Never send readers to `llms.txt` or tell them to point their agent at it. +- **Site-only prose** (e.g. "install the skill and run `/data-client-setup`"): wrap it in ``. Skill references and `llms.txt` drop it, since agents read them: they get the detailed steps, not "tell your agent". Where a page's markdown (`.md`, `llms.txt`) still needs the step itself, such as which command to run, put it beside in ``: hidden on the site, kept in page markdown, dropped from skill references (the skill gives it). It can hold headings and partials (the site strips it before building the table of contents), so a page can give agents a whole `_agents/` guide, like `getting-started/debugging.md`. Never send readers to `llms.txt` or tell them to point their agent at it. - **Adding a hand-written reference**: `.gitattributes` marks `references/**` as `linguist-generated` (collapsed in GitHub diffs); add a `-linguist-generated` line for it. `yarn build:skills` fails until you do. - **Never copy a doc into a reference**: the build fails on a hand-written reference named after a `docs/` page. List the page in `references.json`, and put what it lacks in the doc, or in an `_agents/` source if only agents need it. - **Framework-specific pages**: a `frameworks: [react]` page is skipped for Vue; a skill whose `frameworks` lists `vue` must not rely on it. Skills covering both frameworks link `name.md`; `name.vue.md` exists only where Vue differs. diff --git a/docs/core/_agents/_errors.mdx b/docs/core/_agents/_errors.mdx deleted file mode 100644 index 434d5fe4d206..000000000000 --- a/docs/core/_agents/_errors.mdx +++ /dev/null @@ -1,14 +0,0 @@ -Errors thrown while storing a response (for example a missing primary key) land only in `meta`, with no `error` -on the action, so check both: - -```js -// run in page — errors -() => ({ - actions: __DC__.actions - .filter(a => a.error) - .map(a => ({ type: a.type, key: a.key, error: a.response?.message || true })), - meta: Object.entries(__DC__.controller.getState().meta) - .filter(([, m]) => m.error) - .map(([key, m]) => ({ key, error: m.error.message, status: m.error.status })), -}) -``` diff --git a/docs/core/_agents/_recent-actions.mdx b/docs/core/_agents/_recent-actions.mdx deleted file mode 100644 index c51089c7d34c..000000000000 --- a/docs/core/_agents/_recent-actions.mdx +++ /dev/null @@ -1,11 +0,0 @@ -```js -// run in page — recent actions -() => __DC__.actions.slice(-20).map(a => ({ - type: a.type, - key: a.key, - endpoint: a.endpoint?.name, - args: a.args, - fetchedAt: a.meta?.fetchedAt, - error: a.error ? (a.response?.message || true) : undefined, -})) -``` diff --git a/docs/core/_agents/_response-for-key.mdx b/docs/core/_agents/_response-for-key.mdx deleted file mode 100644 index 2b34b5f7b00a..000000000000 --- a/docs/core/_agents/_response-for-key.mdx +++ /dev/null @@ -1,18 +0,0 @@ -A key's denormalized response, as components see it. The action log holds each fetched key's endpoint and `args`, -so this works for any key read since the page loaded: - -```js -// run in page — response for a key -() => { - const c = __DC__.controller, state = c.getState(); - // a fetch of the key, not a mutation of an item under it - const a = __DC__.actions.findLast( - a => a.args && !a.endpoint?.sideEffect && a.key?.includes('/todos'), - ); - if (!a) return { error: 'key not in the action log; repeat the fetch' }; - const { data, expiryStatus, expiresAt } = c.getResponse(a.endpoint, ...a.args, state); - return { key: a.key, data, expiryStatus, expiresAt, error: c.getError(a.endpoint, ...a.args, state)?.message }; -} -``` - -`expiryStatus`: `1` Invalid, `2` InvalidIfStale, `3` Valid. diff --git a/docs/core/_agents/devtools-debugging.md b/docs/core/_agents/devtools-debugging.md index cff5e33ac7d0..61eeb6c04731 100644 --- a/docs/core/_agents/devtools-debugging.md +++ b/docs/core/_agents/devtools-debugging.md @@ -2,10 +2,6 @@ title: Debugging in the Browser --- -import ResponseForKey from './_response-for-key.mdx'; -import RecentActions from './_recent-actions.mdx'; -import Errors from './_errors.mdx'; - Debug :react[`@data-client/react`]:vue[`@data-client/vue`] state and actions programmatically from any browser tool that runs JavaScript in the page. In dev mode, the app's [DevToolsManager](../api/DevToolsManager.md#controllers) puts the store on `globalThis.__DC__`: `__DC__.controller` is its [Controller](../api/Controller.md), so every Controller method works, and `__DC__.actions` is its recent [actions](../api/Actions.md). ## Prerequisites @@ -128,9 +124,24 @@ Snippets use the entity `Todo` and paths containing `/todos`: replace them with ## Read State - +A key's denormalized response, as components see it. The action log holds each fetched key's endpoint and `args`, +so this works for any key read since the page loaded: -To fetch it again and read the fresh response: +```js +// run in page — response for a key +() => { + const c = __DC__.controller, state = c.getState(); + // a fetch of the key, not a mutation of an item under it + const a = __DC__.actions.findLast( + a => a.args && !a.endpoint?.sideEffect && a.key?.includes('/todos'), + ); + if (!a) return { error: 'key not in the action log; repeat the fetch' }; + const { data, expiryStatus, expiresAt } = c.getResponse(a.endpoint, ...a.args, state); + return { key: a.key, data, expiryStatus, expiresAt, error: c.getError(a.endpoint, ...a.args, state)?.message }; +} +``` + +`expiryStatus`: `1` Invalid, `2` InvalidIfStale, `3` Valid. To fetch it again and read the fresh response: ```js // run in page — refetch a key @@ -168,11 +179,34 @@ The log keeps the newest 100 actions, and each polling key (endpoint and args) o front, so select by time (`meta.fetchedAt >= start`), not by index. Actions hold endpoints and promises, so map them to plain fields: - +```js +// run in page — recent actions +() => __DC__.actions.slice(-20).map(a => ({ + type: a.type, + key: a.key, + endpoint: a.endpoint?.name, + args: a.args, + fetchedAt: a.meta?.fetchedAt, + error: a.error ? (a.response?.message || true) : undefined, +})) +``` ### Errors - +Errors thrown while storing a response (for example a missing primary key) land only in `meta`, with no `error` +on the action, so check both: + +```js +// run in page — errors +() => ({ + actions: __DC__.actions + .filter(a => a.error) + .map(a => ({ type: a.type, key: a.key, error: a.response?.message || true })), + meta: Object.entries(__DC__.controller.getState().meta) + .filter(([, m]) => m.error) + .map(([key, m]) => ({ key, error: m.error.message, status: m.error.status })), +}) +``` ## Change State diff --git a/docs/core/getting-started/debugging.md b/docs/core/getting-started/debugging.md index b97c7cdb979c..453ea9f386ba 100644 --- a/docs/core/getting-started/debugging.md +++ b/docs/core/getting-started/debugging.md @@ -9,14 +9,12 @@ import ThemedImage from '@theme/ThemedImage'; import useBaseUrl from '@docusaurus/useBaseUrl'; import SiteOnly from '@site/src/components/SiteOnly'; import AgentOnly from '@site/src/components/AgentOnly'; -import ResponseForKey from '../_agents/_response-for-key.mdx'; -import RecentActions from '../_agents/_recent-actions.mdx'; -import Errors from '../_agents/_errors.mdx'; - -## Debugging with agents +import DevtoolsDebugging from '../_agents/devtools-debugging.md'; +## Debugging with agents + [`globalThis.__DC__`](../api/DevToolsManager.md#controllers) allows agents to introspect the Data Client store of your app in development: its state, recent actions and errors, so your coding agent can debug what you see. Ask it: @@ -33,37 +31,24 @@ If debugging doesn't work with your agent or browser tool, [open an issue](https naming both so we can support it. Or have your agent write it: ```prompt -Debugging this app through globalThis.__DC__ didn't work. Open a bug report on reactive/data-client titled "Agent debugging: + " (with gh issue create, or give me the text to paste), including your agent and browser tool names and versions, the app's @data-client versions, what you ran and what happened. +Debugging this app through globalThis.__DC__ didn't work with you or your browser tool. Search reactive/data-client issues for one about this agent and browser tool. If there's none, draft a bug report following its .github/ISSUE_TEMPLATE/bug_report.md, titled "Agent debugging: + ", with: agent and browser tool names and versions, the app's @data-client versions, whether globalThis.__DC__ was defined, what you ran and the exact error. Leave out app data and secrets. Show me the draft before filing it with gh issue create. ``` -When the running app shows wrong or stale data, read its store in the page before reading code for the cause. In -dev mode, `__DC__.controller` is the page's [Controller](../api/Controller.md), so every Controller method works, -and `__DC__.actions` holds its recent [actions](../api/Actions.md). - -Each snippet below is a sync function. Pass it as is to a tool that takes a function (Chrome DevTools MCP -`evaluate_script`, Playwright MCP `browser_evaluate`), or evaluate `()()` in one that takes an expression -(Claude in Chrome `javascript_tool`, CDP `Runtime.evaluate` with `awaitPromise` and `returnByValue`). They use the -entity `Todo` and paths containing `/todos`: replace them with the app's own. - - + - +## Manual inspection - - -`__DC__` is undefined in production builds, without `DevToolsManager` in the managers, or before `@data-client` 0.19. - -The rest (refetching, symptoms and what to check first, changing state, each browser tool's setup) -is in :react[`.agents/skills/data-client-react/references/devtools-debugging.md`]:vue[`.agents/skills/data-client-vue/references/devtools-debugging.md`]. -If it's missing, `npx --no data-client-skills` from the project root links it, or read -:react[[the published copy](https://raw.githubusercontent.com/reactive/data-client/master/.agents/skills/data-client-react/references/devtools-debugging.md)]:vue[[the published copy](https://raw.githubusercontent.com/reactive/data-client/master/.agents/skills/data-client-vue/references/devtools-debugging.md)]. +If the user wants to inspect the store themselves, point them to the Redux DevTools browser extension: this page on +the site shows how to install and open it. + + ## Manual debugging If you prefer to inspect everything yourself, the browser devtools workflow below remains @@ -208,3 +193,5 @@ app.mount('#app'); ``` ::: + + diff --git a/website/framework-docs/README.md b/website/framework-docs/README.md index 000897fe7913..c4541e094a66 100644 --- a/website/framework-docs/README.md +++ b/website/framework-docs/README.md @@ -128,7 +128,7 @@ Vue: ### Browser debugging snippets `yarn check:browser-snippets` (`checkBrowserSnippets.mjs`, run by the `regenerate` check) runs every -` ```js ` block starting `// run in page` in `docs/core/_agents/devtools-debugging.md` (including its `_*.mdx` partials) against a small +` ```js ` block starting `// run in page` in `docs/core/_agents/devtools-debugging.md` against a small React app built from the workspace sources, in Chromium, each way that page's Browser Tools section says a tool can run them, with their parameters and the `__DC_RESULT__` recipe read from the page (a new way needs a runner in its `WAYS`). It also checks the `Runtime.evaluate` results the page describes. Snippets use the entity diff --git a/website/framework-docs/checkBrowserSnippets.mjs b/website/framework-docs/checkBrowserSnippets.mjs index c32e2e1112ac..3c95898fffe3 100644 --- a/website/framework-docs/checkBrowserSnippets.mjs +++ b/website/framework-docs/checkBrowserSnippets.mjs @@ -1,6 +1,6 @@ /** * Runs the "run in page" snippets agents use to debug a Data Client app - * (docs/core/_agents/devtools-debugging.md and its partials) in Chromium, each way its Browser + * (docs/core/_agents/devtools-debugging.md) in Chromium, each way its Browser * Tools section says a tool can run them, so a change to the snippets, those * instructions, or DevToolsManager's globals fails here instead of in an * agent's session. The ways' parameters and the `__DC_RESULT__` recipe are read diff --git a/website/framework-docs/docsToMarkdown.mjs b/website/framework-docs/docsToMarkdown.mjs index 5390523057ba..6576834343aa 100644 --- a/website/framework-docs/docsToMarkdown.mjs +++ b/website/framework-docs/docsToMarkdown.mjs @@ -255,7 +255,7 @@ function render(file, framework, props = {}) { const source = sourceFor(file, framework); const parsed = parse(source); const tree = structuredClone(parsed.tree); - remarkFramework({ framework })(tree); + remarkFramework({ framework, agentOnly: true })(tree); const partials = {}; const tabLabels = []; @@ -598,6 +598,19 @@ function wrapPhrasing(tree) { }); } +/** + * Where a link to an unpublished agent page (`docs/core/_agents/`) goes outside + * skill references: the framework skill's generated copy on GitHub, or + * undefined for any other route. + */ +export function agentReferenceUrl(route) { + const match = route.match(/^\/(docs|vue)\/_agents\/([^#?]+)(.*)$/); + if (!match) return; + const [, base, name, hash] = match; + const skill = `data-client-${base === 'vue' ? 'vue' : 'react'}`; + return `https://raw.githubusercontent.com/reactive/data-client/master/.agents/skills/${skill}/references/${name}.md${hash}`; +} + /** * Markdown of a doc for a framework, or undefined if the page isn't in it. * `resolveRoute(route)` picks the URL for links to site pages (default: the site). @@ -607,7 +620,10 @@ function wrapPhrasing(tree) { export function docToMarkdown( file, framework, - { resolveRoute = route => SITE + route, agentOnly = true } = {}, + { + resolveRoute = route => agentReferenceUrl(route) ?? SITE + route, + agentOnly = true, + } = {}, ) { const tree = render(file, framework); if (!tree) return; diff --git a/website/framework-docs/remarkFramework.js b/website/framework-docs/remarkFramework.js index 1f4ccc931aa8..91d8ed77bd6d 100644 --- a/website/framework-docs/remarkFramework.js +++ b/website/framework-docs/remarkFramework.js @@ -12,6 +12,10 @@ * left unused afterwards (a React-only partial on a Vue page) are dropped so * they are not bundled. * + * `` blocks are removed unless `agentOnly` is set (docsToMarkdown + * keeps them for agents), so the site's table of contents never lists their + * headings or the headings of partials they import. + * * With `routeBasePath`, absolute `/docs/...` links are pointed at this * instance's route for the target doc when it exists there (`docs`, from * `docsFor()` in index.js, which honors `slug`), so Vue pages link to Vue @@ -28,10 +32,14 @@ const isEmptyHeading = node => c => c.type === 'text' && /^\s*(\\?\{#[^}]*\})?\s*$/.test(c.value), ); -function filterChildren(node, framework) { +const isAgentOnly = node => + node.type === 'mdxJsxFlowElement' && node.name === 'AgentOnly'; + +function filterChildren(node, framework, agentOnly) { if (!node.children) return; node.children = node.children.flatMap(child => { - filterChildren(child, framework); + if (!agentOnly && isAgentOnly(child)) return []; + filterChildren(child, framework, agentOnly); if (isEmptyHeading(child)) return []; if (!DIRECTIVES.includes(child.type) || !FRAMEWORKS.includes(child.name)) return [child]; @@ -105,9 +113,14 @@ function rewriteLinks(node, routeBasePath, docs) { node.children?.forEach(child => rewriteLinks(child, routeBasePath, docs)); } -module.exports = function remarkFramework({ framework, routeBasePath, docs }) { +module.exports = function remarkFramework({ + framework, + routeBasePath, + docs, + agentOnly = false, +}) { return tree => { - filterChildren(tree, framework); + filterChildren(tree, framework, agentOnly); rewriteImports(tree, framework); pruneImports(tree); if (routeBasePath) rewriteLinks(tree, routeBasePath, docs); diff --git a/website/llms-plugin.js b/website/llms-plugin.js index c03c2d6c7d3f..c67a80e57a22 100644 --- a/website/llms-plugin.js +++ b/website/llms-plugin.js @@ -62,7 +62,7 @@ module.exports = function llmsPlugin(context) { async postBuild({ outDir, plugins, siteConfig: { url } }) { // agents read the English source; other locales would only copy it if (context.i18n.currentLocale !== context.i18n.defaultLocale) return; - const { docToMarkdown, ROOT } = + const { docToMarkdown, agentReferenceUrl, ROOT } = await import('./framework-docs/docsToMarkdown.mjs'); const { setupCommand, linkCommand } = await import('./framework-docs/skillCommands.mjs'); @@ -96,6 +96,8 @@ module.exports = function llmsPlugin(context) { ), ); const resolveRoute = route => { + const agentReference = agentReferenceUrl(route); + if (agentReference) return agentReference; const [, pathname, hash] = route.match(/^([^#?]*)(.*)$/); return routes.has(trimRoute(pathname)) ? `${url}${mdRoute(pathname)}${hash}` diff --git a/website/src/components/AgentOnly.tsx b/website/src/components/AgentOnly.tsx index 4c0401674a8f..ef91241b0538 100644 --- a/website/src/components/AgentOnly.tsx +++ b/website/src/components/AgentOnly.tsx @@ -3,7 +3,8 @@ import type React from 'react'; /** Steps for agents reading a page's markdown (.md and llms.txt), where the * site's `` prose tells readers what to ask their agent. Hidden on * the site; skill references drop it too, since the skill gives these steps. - * Keep headings out of it: the site's table of contents would still list them. */ + * The site's remark plugin (remarkFramework) removes it before the table of + * contents is built, so it can hold headings and partials. */ export default function AgentOnly(_: { children: React.ReactNode }) { return null; } From 6e52195ab906021b233422f34ed02c212230643f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 18:10:56 +0000 Subject: [PATCH 10/11] docs: Link agents to the manual devtools steps, tighten the issue prompt Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01GdtHs1YHGyHL3LtFQtD1W7 --- docs/core/getting-started/debugging.md | 6 +++--- website/framework-docs/docsToMarkdown.mjs | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/core/getting-started/debugging.md b/docs/core/getting-started/debugging.md index 453ea9f386ba..7b70777ece63 100644 --- a/docs/core/getting-started/debugging.md +++ b/docs/core/getting-started/debugging.md @@ -31,7 +31,7 @@ If debugging doesn't work with your agent or browser tool, [open an issue](https naming both so we can support it. Or have your agent write it: ```prompt -Debugging this app through globalThis.__DC__ didn't work with you or your browser tool. Search reactive/data-client issues for one about this agent and browser tool. If there's none, draft a bug report following its .github/ISSUE_TEMPLATE/bug_report.md, titled "Agent debugging: + ", with: agent and browser tool names and versions, the app's @data-client versions, whether globalThis.__DC__ was defined, what you ran and the exact error. Leave out app data and secrets. Show me the draft before filing it with gh issue create. +Debugging this app through globalThis.__DC__ didn't work with you and your browser tool. Search reactive/data-client issues for one about this pair; if there's none, draft a bug report from its .github/ISSUE_TEMPLATE/bug_report.md titled "Agent debugging: + " with both names and versions, the app's @data-client versions, whether globalThis.__DC__ was defined, what you ran and the exact error, and no app data or secrets. Show me the draft before filing it with gh issue create. ``` @@ -42,8 +42,8 @@ Debugging this app through globalThis.__DC__ didn't work with you or your browse ## Manual inspection -If the user wants to inspect the store themselves, point them to the Redux DevTools browser extension: this page on -the site shows how to install and open it. +If the user wants to inspect the store themselves, send them to the Redux DevTools extension steps at +:react[https://dataclient.io/docs/getting-started/debugging#manual-debugging]:vue[https://dataclient.io/vue/getting-started/debugging#manual-debugging]. diff --git a/website/framework-docs/docsToMarkdown.mjs b/website/framework-docs/docsToMarkdown.mjs index 6576834343aa..62b765fa5590 100644 --- a/website/framework-docs/docsToMarkdown.mjs +++ b/website/framework-docs/docsToMarkdown.mjs @@ -613,7 +613,8 @@ export function agentReferenceUrl(route) { /** * Markdown of a doc for a framework, or undefined if the page isn't in it. - * `resolveRoute(route)` picks the URL for links to site pages (default: the site). + * `resolveRoute(route)` picks the URL for links to site pages (default: the site, + * with unpublished `_agents` pages going to the skill's copy on GitHub). * `agentOnly: false` drops `` steps, which skill references don't * need: the skill already gives them. */ From 94e098a70c1310ca77dc5f34b7501be6459c660b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 18:13:20 +0000 Subject: [PATCH 11/11] docs: Keep the _agents link fallback in llms-plugin only, name the remark flag keepAgentOnly Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01GdtHs1YHGyHL3LtFQtD1W7 --- docs/core/getting-started/debugging.md | 2 +- website/framework-docs/docsToMarkdown.mjs | 14 +++++--------- website/framework-docs/remarkFramework.js | 12 ++++++------ 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/docs/core/getting-started/debugging.md b/docs/core/getting-started/debugging.md index 7b70777ece63..68ace52f7d29 100644 --- a/docs/core/getting-started/debugging.md +++ b/docs/core/getting-started/debugging.md @@ -40,7 +40,7 @@ Debugging this app through globalThis.__DC__ didn't work with you and your brows -## Manual inspection +## Manual debugging If the user wants to inspect the store themselves, send them to the Redux DevTools extension steps at :react[https://dataclient.io/docs/getting-started/debugging#manual-debugging]:vue[https://dataclient.io/vue/getting-started/debugging#manual-debugging]. diff --git a/website/framework-docs/docsToMarkdown.mjs b/website/framework-docs/docsToMarkdown.mjs index 62b765fa5590..36855adca373 100644 --- a/website/framework-docs/docsToMarkdown.mjs +++ b/website/framework-docs/docsToMarkdown.mjs @@ -255,7 +255,7 @@ function render(file, framework, props = {}) { const source = sourceFor(file, framework); const parsed = parse(source); const tree = structuredClone(parsed.tree); - remarkFramework({ framework, agentOnly: true })(tree); + remarkFramework({ framework, keepAgentOnly: true })(tree); const partials = {}; const tabLabels = []; @@ -599,8 +599,8 @@ function wrapPhrasing(tree) { } /** - * Where a link to an unpublished agent page (`docs/core/_agents/`) goes outside - * skill references: the framework skill's generated copy on GitHub, or + * Where a link to an unpublished agent page (`docs/core/_agents/`) goes in page + * markdown (llms-plugin): the framework skill's generated copy on GitHub, or * undefined for any other route. */ export function agentReferenceUrl(route) { @@ -613,18 +613,14 @@ export function agentReferenceUrl(route) { /** * Markdown of a doc for a framework, or undefined if the page isn't in it. - * `resolveRoute(route)` picks the URL for links to site pages (default: the site, - * with unpublished `_agents` pages going to the skill's copy on GitHub). + * `resolveRoute(route)` picks the URL for links to site pages (default: the site). * `agentOnly: false` drops `` steps, which skill references don't * need: the skill already gives them. */ export function docToMarkdown( file, framework, - { - resolveRoute = route => agentReferenceUrl(route) ?? SITE + route, - agentOnly = true, - } = {}, + { resolveRoute = route => SITE + route, agentOnly = true } = {}, ) { const tree = render(file, framework); if (!tree) return; diff --git a/website/framework-docs/remarkFramework.js b/website/framework-docs/remarkFramework.js index 91d8ed77bd6d..3df7e5e4a4e5 100644 --- a/website/framework-docs/remarkFramework.js +++ b/website/framework-docs/remarkFramework.js @@ -12,7 +12,7 @@ * left unused afterwards (a React-only partial on a Vue page) are dropped so * they are not bundled. * - * `` blocks are removed unless `agentOnly` is set (docsToMarkdown + * `` blocks are removed unless `keepAgentOnly` is set (docsToMarkdown * keeps them for agents), so the site's table of contents never lists their * headings or the headings of partials they import. * @@ -35,11 +35,11 @@ const isEmptyHeading = node => const isAgentOnly = node => node.type === 'mdxJsxFlowElement' && node.name === 'AgentOnly'; -function filterChildren(node, framework, agentOnly) { +function filterChildren(node, framework, keepAgentOnly) { if (!node.children) return; node.children = node.children.flatMap(child => { - if (!agentOnly && isAgentOnly(child)) return []; - filterChildren(child, framework, agentOnly); + if (!keepAgentOnly && isAgentOnly(child)) return []; + filterChildren(child, framework, keepAgentOnly); if (isEmptyHeading(child)) return []; if (!DIRECTIVES.includes(child.type) || !FRAMEWORKS.includes(child.name)) return [child]; @@ -117,10 +117,10 @@ module.exports = function remarkFramework({ framework, routeBasePath, docs, - agentOnly = false, + keepAgentOnly = false, }) { return tree => { - filterChildren(tree, framework, agentOnly); + filterChildren(tree, framework, keepAgentOnly); rewriteImports(tree, framework); pruneImports(tree); if (routeBasePath) rewriteLinks(tree, routeBasePath, docs);