Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/rules/skills-sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 '<old path or name>' .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 `<SiteOnly>`. 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 `<AgentOnly>`: 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 `<SiteOnly>`. 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 `<AgentOnly>`: 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.
Expand Down
2 changes: 1 addition & 1 deletion .cursor/rules/skills-sync.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -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 '<old path or name>' .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 `<SiteOnly>`. 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 `<AgentOnly>`: 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 `<SiteOnly>`. 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 `<AgentOnly>`: 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.
Expand Down
55 changes: 32 additions & 23 deletions docs/core/getting-started/debugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,40 +7,47 @@ 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';
import DevtoolsDebugging from '../_agents/devtools-debugging.md';

<SiteOnly>

## 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.
[`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:

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.
```prompt
The todo list still shows the old title after I save. Find out why.
```

### How agent debugging works
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.

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.
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. Or have your agent write it:

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.
```prompt
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: <agent> + <browser tool>" 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.
```

At a high level, the agent can:
</SiteOnly>

- 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
<AgentOnly>

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.
<DevtoolsDebugging />

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

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].

</AgentOnly>

<SiteOnly>

## Manual debugging

Expand Down Expand Up @@ -186,3 +193,5 @@ app.mount('#app');
```

:::

</SiteOnly>
15 changes: 14 additions & 1 deletion website/framework-docs/docsToMarkdown.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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, keepAgentOnly: true })(tree);
const partials = {};
const tabLabels = [];

Expand Down Expand Up @@ -598,6 +598,19 @@ function wrapPhrasing(tree) {
});
}

/**
* 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) {
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).
Expand Down
21 changes: 17 additions & 4 deletions website/framework-docs/remarkFramework.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
* left unused afterwards (a React-only partial on a Vue page) are dropped so
* they are not bundled.
*
* `<AgentOnly>` 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.
*
* 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
Expand All @@ -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, keepAgentOnly) {
if (!node.children) return;
node.children = node.children.flatMap(child => {
filterChildren(child, framework);
if (!keepAgentOnly && isAgentOnly(child)) return [];
filterChildren(child, framework, keepAgentOnly);
if (isEmptyHeading(child)) return [];
if (!DIRECTIVES.includes(child.type) || !FRAMEWORKS.includes(child.name))
return [child];
Expand Down Expand Up @@ -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,
keepAgentOnly = false,
}) {
return tree => {
filterChildren(tree, framework);
filterChildren(tree, framework, keepAgentOnly);
rewriteImports(tree, framework);
pruneImports(tree);
if (routeBasePath) rewriteLinks(tree, routeBasePath, docs);
Expand Down
4 changes: 3 additions & 1 deletion website/llms-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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}`
Expand Down
3 changes: 2 additions & 1 deletion website/src/components/AgentOnly.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<SiteOnly>` 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;
}
Loading