Skip to content

Commit db5b610

Browse files
committed
internal(skill): Add devtools debugging reference
1 parent 76adee4 commit db5b610

File tree

5 files changed

+241
-1
lines changed

5 files changed

+241
-1
lines changed

.cursor/skills/data-client-react/SKILL.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,13 @@ const todosByUser = useQuery(groupTodoByUser);
109109

110110
---
111111

112+
## Browser Debugging (Chrome DevTools MCP)
113+
114+
To inspect store state, track dispatched [actions](references/Actions.md), or invoke
115+
[Controller](references/Controller.md) methods from a browser MCP (`user-chrome-devtools`),
116+
see [devtools-debugging](references/devtools-debugging.md). Uses `globalThis.__DC_CONTROLLERS__`
117+
available in dev mode.
118+
112119
## Managers
113120

114121
Custom [Managers](https://dataclient.io/docs/api/Manager) allow for global side effect handling.
@@ -138,5 +145,7 @@ For detailed API documentation, see the [references](references/) directory:
138145
- [DataProvider](references/DataProvider.md) - Root provider
139146
- [data-dependency](references/data-dependency.md) - Rendering guide
140147
- [mutations](references/mutations.md);[_VoteDemo.mdx](references/_VoteDemo.mdx) - Mutations guide
148+
- [Actions](references/Actions.md) - Store action types (FETCH, SET, etc.)
149+
- [devtools-debugging](references/devtools-debugging.md) - Debug with Chrome DevTools MCP
141150

142151
**ALWAYS follow these patterns and refer to the official docs for edge cases. Prioritize code generation that is idiomatic, type-safe, and leverages automatic normalization/caching via skill "data-client-schema" definitions.**
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../data-client-manager/references/Actions.md
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
# Debugging @data-client/react with Chrome DevTools MCP
2+
3+
Use `user-chrome-devtools` MCP tools to programmatically inspect and interact with the
4+
data-client store in a running browser. Requires dev mode (`NODE_ENV !== 'production'`).
5+
6+
For full [Controller](./Controller.md) API (fetch, set, invalidate, etc.) and
7+
[Action](./Actions.md) types (FETCH, SET, INVALIDATE, etc.), see those references.
8+
9+
## Setup: Inject Action Logger
10+
11+
On first navigation (or reload), inject a logging shim via `initScript` to capture all
12+
dispatched actions before the app boots. This gives you a persistent action log to query.
13+
14+
```js
15+
navigate_page → initScript:
16+
17+
window.__DC_ACTION_LOG__ = [];
18+
const _origDispatch = Object.getOwnPropertyDescriptor(Object.getPrototypeOf({}), 'dispatch');
19+
Object.defineProperty(globalThis, '__DC_INTERCEPT_DISPATCH__', {
20+
value(action) {
21+
const entry = {
22+
type: action.type,
23+
key: action.meta?.key,
24+
schema: action.endpoint?.name ?? action.schema?.constructor?.name,
25+
timestamp: Date.now(),
26+
};
27+
if (action.error) entry.error = true;
28+
globalThis.__DC_ACTION_LOG__.push(entry);
29+
if (globalThis.__DC_ACTION_LOG__.length > 500)
30+
globalThis.__DC_ACTION_LOG__ = globalThis.__DC_ACTION_LOG__.slice(-250);
31+
},
32+
writable: true, configurable: true,
33+
});
34+
```
35+
36+
Then after the app loads, hook into the controller's dispatch. First discover
37+
the `devtoolsName` key (see "Accessing the Controller"), then use it:
38+
39+
```js
40+
evaluate_script:
41+
42+
() => {
43+
const ctrl = globalThis.__DC_CONTROLLERS__?.get('Data Client: My App');
44+
if (!ctrl) return 'no controller yet';
45+
const orig = ctrl._dispatch.bind(ctrl);
46+
ctrl._dispatch = (action) => {
47+
globalThis.__DC_INTERCEPT_DISPATCH__?.(action);
48+
return orig(action);
49+
};
50+
return 'dispatch intercepted';
51+
}
52+
```
53+
54+
## Accessing the Controller
55+
56+
`DevToolsManager` registers controllers in `globalThis.__DC_CONTROLLERS__` (a `Map`) keyed by
57+
`devtoolsName` — defaults to `"Data Client: <page title>"`.
58+
59+
**Step 1: Discover available controllers and their keys.**
60+
61+
```js
62+
() => {
63+
const m = globalThis.__DC_CONTROLLERS__;
64+
if (!m || m.size === 0) return 'no controllers registered';
65+
return [...m.keys()];
66+
}
67+
```
68+
69+
**Step 2: Get a controller by its `devtoolsName` key.** Use the key from Step 1.
70+
71+
```js
72+
() => {
73+
const ctrl = globalThis.__DC_CONTROLLERS__?.get('Data Client: My App');
74+
return ctrl ? 'controller found' : 'not found';
75+
}
76+
```
77+
78+
Always use `.get(devtoolsName)` with the actual key from Step 1 — not `.values().next().value`
79+
— so you target the correct store when multiple `DataProvider`s exist.
80+
81+
## Reading State
82+
83+
The store state shape is `{ entities, endpoints, indexes, meta, entitiesMeta, optimistic, lastReset }`.
84+
85+
### Full state overview
86+
87+
```js
88+
() => {
89+
const state = globalThis.__DC_CONTROLLERS__?.get('Data Client: My App')?.getState();
90+
if (!state) return 'no state';
91+
return {
92+
entityTypes: Object.keys(state.entities),
93+
endpointCount: Object.keys(state.endpoints).length,
94+
optimisticCount: state.optimistic.length,
95+
lastReset: state.lastReset,
96+
};
97+
}
98+
```
99+
100+
### List all entities of a type
101+
102+
```js
103+
() => {
104+
const state = globalThis.__DC_CONTROLLERS__?.get('Data Client: My App')?.getState();
105+
const entities = state?.entities?.['Todo'];
106+
if (!entities) return 'no Todo entities';
107+
return Object.values(entities);
108+
}
109+
```
110+
111+
### Inspect a specific entity by pk
112+
113+
```js
114+
() => {
115+
const state = globalThis.__DC_CONTROLLERS__?.get('Data Client: My App')?.getState();
116+
return state?.entities?.['Todo']?.['5'];
117+
}
118+
```
119+
120+
### List cached endpoint keys
121+
122+
```js
123+
() => {
124+
const state = globalThis.__DC_CONTROLLERS__?.get('Data Client: My App')?.getState();
125+
return Object.keys(state?.endpoints ?? {});
126+
}
127+
```
128+
129+
### Get endpoint response (raw, before denormalization)
130+
131+
```js
132+
() => {
133+
const state = globalThis.__DC_CONTROLLERS__?.get('Data Client: My App')?.getState();
134+
const key = Object.keys(state?.endpoints ?? {}).find(k => k.includes('GET /todos'));
135+
return key ? { key, data: state.endpoints[key] } : 'not found';
136+
}
137+
```
138+
139+
### Check endpoint metadata (expiry, errors, invalidation)
140+
141+
```js
142+
() => {
143+
const state = globalThis.__DC_CONTROLLERS__?.get('Data Client: My App')?.getState();
144+
const key = Object.keys(state?.meta ?? {}).find(k => k.includes('/todos'));
145+
return key ? { key, ...state.meta[key] } : 'no meta found';
146+
}
147+
```
148+
149+
## Dispatching Actions
150+
151+
Use the [Controller](./Controller.md) to mutate the store. Always look up by `devtoolsName`.
152+
See [Actions](./Actions.md) for the full list of action types dispatched.
153+
154+
### Reset the entire store
155+
156+
```js
157+
() => {
158+
const ctrl = globalThis.__DC_CONTROLLERS__?.get('Data Client: My App');
159+
ctrl?.resetEntireStore();
160+
return 'store reset';
161+
}
162+
```
163+
164+
### Invalidate all endpoints (force refetch)
165+
166+
```js
167+
() => {
168+
const ctrl = globalThis.__DC_CONTROLLERS__?.get('Data Client: My App');
169+
ctrl?.invalidateAll({ testKey: () => true });
170+
return 'all invalidated';
171+
}
172+
```
173+
174+
### Invalidate endpoints matching a pattern
175+
176+
```js
177+
() => {
178+
const ctrl = globalThis.__DC_CONTROLLERS__?.get('Data Client: My App');
179+
ctrl?.invalidateAll({ testKey: key => key.includes('/todos') });
180+
return 'todo endpoints invalidated';
181+
}
182+
```
183+
184+
### Expire endpoints (mark stale, refetch on next use)
185+
186+
```js
187+
() => {
188+
const ctrl = globalThis.__DC_CONTROLLERS__?.get('Data Client: My App');
189+
ctrl?.expireAll({ testKey: key => key.includes('/todos') });
190+
return 'todo endpoints expired';
191+
}
192+
```
193+
194+
## Reading the Action Log
195+
196+
After injecting the dispatch interceptor (see Setup):
197+
198+
### Recent actions
199+
200+
```js
201+
() => globalThis.__DC_ACTION_LOG__?.slice(-20) ?? 'no log'
202+
```
203+
204+
### Filter by type
205+
206+
```js
207+
() => globalThis.__DC_ACTION_LOG__?.filter(a => a.type === 'rdc/set') ?? []
208+
```
209+
210+
### Filter errors
211+
212+
```js
213+
() => globalThis.__DC_ACTION_LOG__?.filter(a => a.error) ?? []
214+
```
215+
216+
## Correlating with Network Requests
217+
218+
Use `list_network_requests` with `resourceTypes: ["fetch", "xhr"]` to see API calls,
219+
then cross-reference with endpoint keys in state.
220+
221+
## Debugging Checklist
222+
223+
1. **Verify controller exists**: Check `__DC_CONTROLLERS__` map size
224+
2. **Inspect state shape**: Get entity types and endpoint count
225+
3. **Check specific data**: Look up entities by type and pk
226+
4. **Review endpoint metadata**: Check expiry, errors, invalidation status
227+
5. **Track actions**: Read the action log for recent dispatches
228+
6. **Correlate network**: Compare `list_network_requests` with endpoint keys
229+
7. **Force refresh**: Use `invalidateAll` or `expireAll` to trigger refetches

packages/core/src/manager/DevtoolsManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export default class DevToolsManager implements Manager {
9494
protected actions: [ActionTypes, State<unknown>][] = [];
9595
declare protected controller: Controller;
9696
declare skipLogging?: (action: ActionTypes) => boolean;
97-
declare protected devtoolsName: string;
97+
declare devtoolsName: string;
9898
maxBufferLength = 100;
9999

100100
constructor(

website/src/components/Playground/editor-types/@data-client/core.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,6 +1393,7 @@ declare class DevToolsManager implements Manager {
13931393
protected actions: [ActionTypes, State<unknown>][];
13941394
protected controller: Controller;
13951395
skipLogging?: (action: ActionTypes) => boolean;
1396+
devtoolsName: string;
13961397
maxBufferLength: number;
13971398
constructor(config?: DevToolsConfig, skipLogging?: (action: ActionTypes) => boolean);
13981399
handleAction(action: any, state: any): void;

0 commit comments

Comments
 (0)