Repository navigation
feat(core): Log recent actions on __DC_ACTIONS__ and give same-named stores unique keys - #4393
Conversation
🦋 Changeset detectedLatest commit: af22339 The changes in this PR will be included in the next version bump. This PR includes changesets to release 7 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #4393 +/- ##
=======================================
Coverage 98.37% 98.37%
=======================================
Files 161 161
Lines 3072 3072
Branches 604 604
=======================================
Hits 3022 3022
Misses 10 10
Partials 40 40 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Staff engineer (Cursor agent): Re-review of 4cb6039..725d1f6 (summarize old actions, cap polling keys). One small change requested. Everything else in the delta looks right. Requested change: the polling cap only holds on the first subscribe In Suggested fix (makes the code simpler and drops the invariant): don't break, and remove every match past the cap: for (let i = log.length - 1, seen = 0; i >= 0; i--) {
if ((log[i] as { key?: string }).key === key && ++seen >= this.maxPollingActions)
log.splice(i, 1);
}This scans the whole log (≤ What I validated, and how
Only read in code, not run
|
|
Agreed, and fixed in this PR. On the non-blocking note: leaving it as is. The class already ships Generated by Claude Code |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b3a44a2f2b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 74d740e3f0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2206128056
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…stores unique keys DevToolsManager now records each store's recent actions in development, from startup and with or without the Redux DevTools extension, on globalThis.__DC_ACTIONS__ (keyed like __DC_CONTROLLERS__). A store whose devtools name is taken registers as 'name (2)' instead of replacing the first. The debugging skill uses the built-in log and falls back to wrapping dispatch on older versions. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U7fXTuhewgyp2SGAqY6LgJ
Per-request SSR stores (e.g. Vue's DataClientPlugin with default managers) never run cleanup, so suffixing their keys would keep every request's store alive in development. Unique keys now apply only on a page. Also covers the third-collision key and the cleanup guard in tests. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U7fXTuhewgyp2SGAqY6LgJ
… keys on React Native React Native has no document but does define window, so the document check made same-named React Native stores replace each other. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U7fXTuhewgyp2SGAqY6LgJ
Pins the React Native case, and words the docs and comment as the condition the code checks. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U7fXTuhewgyp2SGAqY6LgJ
…ling keys Only the newest 20 actions keep args and response; older ones keep type, key, endpoint, meta and error responses. While an endpoint polls, the log keeps only its last maxPollingActions (default 4) actions. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U7fXTuhewgyp2SGAqY6LgJ
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U7fXTuhewgyp2SGAqY6LgJ
Also say the polling cap is per key (endpoint and args), and rename the subscriber count map. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U7fXTuhewgyp2SGAqY6LgJ
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U7fXTuhewgyp2SGAqY6LgJ
… key on re-init Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U7fXTuhewgyp2SGAqY6LgJ
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U7fXTuhewgyp2SGAqY6LgJ
… 0 as polling Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U7fXTuhewgyp2SGAqY6LgJ
…ed sections Skill evals (with and without the skill on three bugs) showed agents never used the polling pattern, checklist or network-correlation sections, and asked for a symptom-to-first-check hint. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U7fXTuhewgyp2SGAqY6LgJ
…ation loops in devtools debugging Harder evals (refetch loop, wrapped mutation response, stale list cache) on Sonnet and Haiku showed: normalization errors live only in meta, so the errors snippet missed them; agents sliced the trimmed log by index; and one run never opened the reference. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U7fXTuhewgyp2SGAqY6LgJ
6452b16 to
13c3de3
Compare
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Size Change: 0 B Total Size: 112 kB ℹ️ View Unchanged
|
There was a problem hiding this comment.
Benchmark React
Details
| Benchmark suite | Current: af22339 | Previous: 7c55890 | Ratio |
|---|---|---|---|
data-client: getlist-100 |
149.29 ops/s (± 7.8%) |
157.49 ops/s (± 8.3%) |
1.05 |
data-client: getlist-500 |
47.2 ops/s (± 9.5%) |
49.38 ops/s (± 7.4%) |
1.05 |
data-client: update-entity |
322.58 ops/s (± 6.9%) |
357.14 ops/s (± 8.5%) |
1.11 |
data-client: update-user |
327.96 ops/s (± 9.4%) |
357.14 ops/s (± 6.6%) |
1.09 |
data-client: getlist-500-sorted |
51.55 ops/s (± 8.6%) |
53.63 ops/s (± 7.4%) |
1.04 |
data-client: update-entity-sorted |
303.03 ops/s (± 6.6%) |
318.18 ops/s (± 8.9%) |
1.05 |
data-client: update-entity-multi-view |
303.03 ops/s (± 7.6%) |
322.58 ops/s (± 6.4%) |
1.06 |
data-client: list-detail-switch-10 |
9.66 ops/s (± 9.6%) |
9.07 ops/s (± 9.3%) |
0.94 |
data-client: update-user-10000 |
81.63 ops/s (± 9.1%) |
76.05 ops/s (± 10.4%) |
0.93 |
data-client: invalidate-and-resolve |
37.81 ops/s (± 6.8%) |
41.84 ops/s (± 5.3%) |
1.11 |
data-client: unshift-item |
227.27 ops/s (± 2.1%) |
274.02 ops/s (± 1.7%) |
1.21 |
data-client: delete-item |
312.5 ops/s (± 8.6%) |
298.57 ops/s (± 9.4%) |
0.96 |
data-client: move-item |
181.88 ops/s (± 6.6%) |
185.19 ops/s (± 6.8%) |
1.02 |
This comment was automatically generated by workflow using github-action-benchmark.
|
Docs preview: https://docs-site-pr-4393-data-client.vercel.app Built from af22339 merged into |
There was a problem hiding this comment.
Benchmark
Details
| Benchmark suite | Current: af22339 | Previous: 7c55890 | Ratio |
|---|---|---|---|
normalizeLong |
584 ops/sec (±2.81%) |
514 ops/sec (±2.92%) |
0.88 |
normalizeLong Values |
526 ops/sec (±0.29%) |
461 ops/sec (±0.36%) |
0.88 |
normalizeLong Scalar |
514 ops/sec (±2.11%) |
451 ops/sec (±2.88%) |
0.88 |
normalizeLong Scalar update |
1054 ops/sec (±0.10%) |
940 ops/sec (±0.85%) |
0.89 |
denormalizeLong |
364 ops/sec (±5.83%) |
308 ops/sec (±6.55%) |
0.85 |
denormalizeLong Values |
326 ops/sec (±4.53%) |
283 ops/sec (±4.69%) |
0.87 |
denormalizeLong donotcache |
1425 ops/sec (±0.78%) |
1313 ops/sec (±0.97%) |
0.92 |
denormalizeLong Values donotcache |
980 ops/sec (±0.13%) |
872 ops/sec (±1.07%) |
0.89 |
denormalizeLong Scalar donotcache |
1439 ops/sec (±0.09%) |
1261 ops/sec (±0.86%) |
0.88 |
denormalizeShort donotcache 500x |
2020 ops/sec (±0.10%) |
1754 ops/sec (±0.87%) |
0.87 |
denormalizeShort 500x |
916 ops/sec (±7.34%) |
770 ops/sec (±6.56%) |
0.84 |
denormalizeShort 500x withCache |
9105 ops/sec (±0.11%) |
8798 ops/sec (±0.42%) |
0.97 |
queryShort 500x withCache |
4840 ops/sec (±0.13%) |
4590 ops/sec (±0.60%) |
0.95 |
buildQueryKey All |
78139 ops/sec (±1.01%) |
72518 ops/sec (±0.57%) |
0.93 |
query All withCache |
7450 ops/sec (±3.96%) |
7243 ops/sec (±6.06%) |
0.97 |
denormalizeLong with mixin Entity |
338 ops/sec (±6.52%) |
280 ops/sec (±8.60%) |
0.83 |
denormalizeLong withCache |
7403 ops/sec (±0.18%) |
7241 ops/sec (±0.37%) |
0.98 |
denormalizeLong withCache (Scalar churn) |
7368 ops/sec (±0.67%) |
7142 ops/sec (±1.18%) |
0.97 |
denormalizeLong Values withCache |
6539 ops/sec (±1.10%) |
6328 ops/sec (±1.87%) |
0.97 |
denormalizeLong Scalar withCache |
8982 ops/sec (±0.27%) |
8343 ops/sec (±1.05%) |
0.93 |
denormalizeLong Scalar update withCache |
3617 ops/sec (±0.18%) |
3509 ops/sec (±0.41%) |
0.97 |
denormalizeLong All withCache |
6120 ops/sec (±0.24%) |
6458 ops/sec (±0.30%) |
1.06 |
denormalizeLong Query-sorted withCache |
7652 ops/sec (±4.73%) |
7408 ops/sec (±5.81%) |
0.97 |
denormalizeLongAndShort withEntityCacheOnly |
2150 ops/sec (±0.21%) |
1845 ops/sec (±0.64%) |
0.86 |
denormalize bidirectional 50 |
7042 ops/sec (±9.81%) |
6284 ops/sec (±11.56%) |
0.89 |
denormalize bidirectional 50 donotcache |
60240 ops/sec (±0.48%) |
53624 ops/sec (±0.83%) |
0.89 |
getResponse |
5618 ops/sec (±2.68%) |
4915 ops/sec (±5.74%) |
0.87 |
getResponse (null) |
15114528 ops/sec (±0.44%) |
13533430 ops/sec (±1.34%) |
0.90 |
getResponse (clear cache) |
303 ops/sec (±7.48%) |
259 ops/sec (±7.83%) |
0.85 |
getSmallResponse |
5218 ops/sec (±0.21%) |
4735 ops/sec (±1.05%) |
0.91 |
getSmallInferredResponse |
4227 ops/sec (±4.82%) |
4049 ops/sec (±0.96%) |
0.96 |
getResponse Collection |
4808 ops/sec (±2.75%) |
5296 ops/sec (±4.78%) |
1.10 |
get Collection |
2718 ops/sec (±0.32%) |
3986 ops/sec (±0.58%) |
1.47 |
get Query-sorted |
5791 ops/sec (±3.15%) |
5414 ops/sec (±5.78%) |
0.93 |
setLong |
586 ops/sec (±0.20%) |
519 ops/sec (±0.16%) |
0.89 |
setLongWithMerge |
325 ops/sec (±0.14%) |
294 ops/sec (±1.13%) |
0.90 |
setLongWithSimpleMerge |
353 ops/sec (±0.07%) |
310 ops/sec (±1.09%) |
0.88 |
setSmallResponse 500x |
1275 ops/sec (±1.00%) |
1056 ops/sec (±1.11%) |
0.83 |
setMany 50x one-per-row |
285 ops/sec (±0.33%) |
257 ops/sec (±1.01%) |
0.90 |
setMany 50 batch |
5417 ops/sec (±0.53%) |
4599 ops/sec (±1.07%) |
0.85 |
setMany 500x one-per-row |
31.26 ops/sec (±0.42%) |
29.39 ops/sec (±0.92%) |
0.94 |
setMany 500 batch |
1707 ops/sec (±0.10%) |
1540 ops/sec (±0.91%) |
0.90 |
This comment was automatically generated by workflow using github-action-benchmark.
… builds Move trackPolling and logAction into the development-only prototype block and create their state in a development-only constructor branch, so production bundles drop them (DevToolsManager's minified gzip size goes from +381 B over master to +35 B). Share one polling test setup. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U7fXTuhewgyp2SGAqY6LgJ
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U7fXTuhewgyp2SGAqY6LgJ
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
@codex review the changes since your last completed review at commit |
|
Codex Review: Didn't find any major issues. Breezy! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
…eware trackPolling and logAction are only called from the middleware, so they become local functions there instead of protected prototype members, dropping them from the published types. Comments in init() now sit by the statements they explain. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U7fXTuhewgyp2SGAqY6LgJ
Split the request-rate row so a request that never fires points at meta.expiresAt and a missing rdc/subscribe, point meta errors at the snippet that reads them, and cover a created item missing from a list. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U7fXTuhewgyp2SGAqY6LgJ
Requested by Nathaniel · project thread
Stacked on #4391.
Motivation
Before: a coding agent (or anyone in the console) could only see dispatched actions by wrapping
Controller._dispatchafter the page loaded, so the fetches made during page load were never visible unless the Redux DevTools extension was installed. Two stores on the default devtools name also replaced each other in__DC_CONTROLLERS__, so only one could be found, though the docs said multiple providers were fully supported.After: in development,
DevToolsManagerkeeps each store's recent actions from startup onglobalThis.__DC_ACTIONS__, keyed like__DC_CONTROLLERS__, with or without the extension. A store whose name is taken registers as'Data Client: My App (2)'.Solution
skipLogginglike the extension does, keeps up tomaxBufferLengthactions (twicemaxAge, 100 by default), and trims in place so the array on the global stays current. Production builds keep the pass-through middleware.init()picks the first free key (name,name (2), ...) andcleanup()removes that key from both maps. Where there is nowindow(server rendering), a same-named store replaces the previous one instead, because per-request stores are never cleaned up and unique keys would keep every request's store alive. The check iswindow, notdocument, so React Native (which has awindowbut nodocument) still gets unique keys.actionLog: 'built-in'. On older versions it falls back to wrapping dispatch ('shim'), because skills aren't pinned to the user's package version. Both paths store raw actions, and the Step 3 snippets return only the useful fields.DevToolsManager"Programmatic store access", the coding-agents draft post, and two changesets.Tests:
packages/core/src/manager/__tests__/devtoolsManager.tscovers pre-init logging without the extension, the cap,skipLogging, duplicate names, re-init and cleanup;devtoolsManager.node.tscovers server rendering (nowindow: replace) and React Native (windowonly: unique keys). I also ran the skill's Step 1 shim and Step 3 snippets, extracted from the doc, against a realControllerwithDevToolsManagerandNetworkManager, in both built-in and fallback modes (temporary test, not committed).Open questions
The Redux extension still shows two same-named stores under one name. Only the registry key is made unique, because the extension connection is opened in the constructor, before it's known which names are taken.
🤖 Generated with Claude Code
https://claude.ai/code/session_01U7fXTuhewgyp2SGAqY6LgJ
Generated by Claude Code
Note
Low Risk
Changes are dev-only middleware and documentation; production builds keep pass-through behavior. Registry key logic is covered by new unit tests for SSR, RN, polling, and duplicate names.
Overview
Dev-only programmatic debugging now exposes each store’s dispatch history on
globalThis.__DC_ACTIONS__(same keys as__DC_CONTROLLERS__), including page-load fetches, without requiring the Redux DevTools extension. The log is capped and summarized (newest 20 full payloads; polling keys limited via configurablemaxPollingActions).Multi-store registration no longer overwrites same default devtools names: browsers/React Native get suffixed keys (
(2),(3)…); SSR withoutwindowreplaces the prior entry to avoid leaking per-request stores.Docs and agent skills add a symptom → “check first” table, wire the debug shim to the built-in log (
actionLog: 'built-in') with a legacy dispatch-wrap fallback, and refresh Step 3 snippets (meta + action errors, safe serialization).DevToolsManagerAPI docs and the coding-agents post document__DC_ACTIONS__.Reviewed by Cursor Bugbot for commit af22339. Bugbot is set up for automated code reviews on this repo. Configure here.