react-state-basis 0.6.3 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,9 +5,10 @@
5
5
  <div align="center">
6
6
 
7
7
  # react-state-basis
8
- ### Runtime Architectural Auditor for React
9
8
 
10
- **Basis tracks when state updates (never what) to catch architectural debt that standard tools miss, while keeping your data private.**
9
+ ### Runtime diagnostics for React state
10
+
11
+ **Basis observes when state updates happen and uses those patterns to highlight state-management issues that can be difficult to spot in a code review or profiler. It does not inspect state values.**
11
12
 
12
13
  [![npm version](https://img.shields.io/npm/v/react-state-basis.svg?style=flat-square)](https://www.npmjs.com/package/react-state-basis)
13
14
  [![GitHub stars](https://img.shields.io/github/stars/liovic/react-state-basis.svg?style=flat-square)](https://github.com/liovic/react-state-basis/stargazers)
@@ -20,12 +21,16 @@
20
21
  ## Quick Start
21
22
 
22
23
  ### 1. Install
24
+
23
25
  ```bash
24
26
  npm i react-state-basis
25
27
  ```
26
28
 
27
- ### 2. Setup (Vite)
28
- Add the plugin to your `vite.config.ts`. The Babel plugin auto-labels your hooks - you continue importing from `react` as normal.
29
+ ### 2. Setup with Vite
30
+
31
+ Add the plugin to your `vite.config.ts`.
32
+
33
+ The Babel plugin labels React hooks automatically, so you can continue importing from `react` as usual.
29
34
 
30
35
  ```ts
31
36
  import { defineConfig } from 'vite';
@@ -34,43 +39,55 @@ import { basis } from 'react-state-basis/vite';
34
39
 
35
40
  export default defineConfig({
36
41
  plugins: [
37
- react({
38
- babel: { plugins: [['react-state-basis/plugin']] }
42
+ react({
43
+ babel: {
44
+ plugins: [['react-state-basis/plugin']],
45
+ },
39
46
  }),
40
- basis()
41
- ]
47
+ basis(),
48
+ ],
42
49
  });
43
50
  ```
51
+ This is the supported setup today. Next.js / SWC is not instrumented yet.
44
52
 
45
53
  ### 3. Initialize
54
+
46
55
  ```tsx
47
56
  import { BasisProvider } from 'react-state-basis';
48
57
 
49
58
  root.render(
50
- <BasisProvider
59
+ <BasisProvider
51
60
  debug={true}
52
- showHUD={true} // Set to false for console-only forensics
61
+ showHUD={true}
53
62
  >
54
63
  <App />
55
64
  </BasisProvider>
56
65
  );
57
66
  ```
58
67
 
59
- ### 4. Verify the Signal
60
- Drop this pattern into any component. For this pattern, Basis typically flags the rhythm of the debt within ~100ms; detection latency can vary for other patterns.
68
+ Set `showHUD={false}` to keep the diagnostics in the console without showing the overlay.
69
+
70
+ ### 4. Try it
71
+
72
+ For example:
61
73
 
62
74
  ```tsx
63
75
  const [a, setA] = useState(0);
64
76
  const [b, setB] = useState(0);
65
77
 
66
78
  useEffect(() => {
67
- setB(a + 1); // ⚡ BASIS: "Double Render Detected"
79
+ setB(a + 1);
68
80
  }, [a]);
69
81
 
70
- return <button onClick={() => setA(a + 1)}>Pulse Basis</button>;
82
+ return (
83
+ <button onClick={() => setA(a + 1)}>
84
+ Update
85
+ </button>
86
+ );
71
87
  ```
72
88
 
73
- Click the button. You should see this in your console:
89
+ When the button is clicked, Basis can identify the effect-driven update pattern and report where it originated. You should see in your console:
90
+
74
91
  ```
75
92
  ⚡ BASIS | DOUBLE RENDER
76
93
  📍 Location: YourComponent.tsx
@@ -78,64 +95,141 @@ Issue: effect_L5 triggers b in a separate frame.
78
95
  Fix: Derive b during the render phase (remove effect) or wrap in useMemo.
79
96
  ```
80
97
 
81
- ---
82
-
83
- ### 5. Control & Scope
84
- * **Ghost Mode:** Disable the visual overlay while keeping console-based forensics active by setting `showHUD={false}` on the provider.
85
- * **Selective Auditing:** Add `// @basis-ignore` at the top of any file to disable instrumentation. Recommended for:
86
- * High-frequency animation logic (>60fps)
87
- * Third-party library wrappers
88
- * Intentional synchronization (e.g., local mirrors of external caches)
98
+ Detection happens at runtime and timing varies by pattern.
89
99
 
90
100
  ---
91
101
 
92
102
  ## HUD
93
103
 
94
- The optional overlay shows your component's state updates in real time. Purple pulses mark updates coming from Context; red pulses mark state that looks like a redundant copy of something else.
104
+ The optional HUD shows state updates as they happen.
95
105
 
96
106
  <p align="center">
97
- <img src="./assets/050Basis.gif" width="800" alt="Basis Demo" />
107
+ <img src="./assets/050Basis.gif" width="800" alt="Basis Demo">
98
108
  </p>
99
109
 
100
- > **Note:** The HUD shows updates as they happen. The **Architectural Health Report** (Console) looks at the whole update graph together, so it can catch patterns a single glance at the HUD would miss.
110
+ The HUD is useful for seeing individual updates. The console report looks at the observed update graph over time, which can reveal patterns that are harder to see from a single interaction.
101
111
 
102
112
  ---
103
113
 
104
- ## What Basis Detects
114
+ ## What Basis Looks For
115
+
116
+ Basis does not try to determine whether your state is "correct." Instead, it looks for update patterns that are often worth investigating.
117
+
118
+ ### Effect-driven updates
119
+
120
+ A `useEffect` causes another state update immediately after rendering.
121
+
122
+ This can be a sign that some state could be derived during render instead.
105
123
 
106
- Basis watches *when* your state updates and looks for timing patterns that usually mean architectural debt - two states always changing together, an effect immediately re-triggering a render, a click that fans out into updates across unrelated files. Every flag below is a signal to investigate, not a verified defect:
124
+ ### Correlated state
107
125
 
108
- - **⚡ Double Renders (Sync Leaks)** - A `useEffect` triggers a state update immediately after a render, forcing the browser to paint twice.
109
- - **⚡ Prime Movers (Likely Root Causes)** - Skips downstream symptoms and points you to the hook or event most likely to have started the chain reaction. When multiple updates fire in the same tick, Basis ranks candidates by their position in the update graph rather than asserting a single definitive cause.
110
- - **⚡ Fragmented Updates** - A single click forces updates in multiple different files or contexts at once (tearing risk).
111
- - **Context Mirroring** - You're redundantly copying Global Context data into local state, creating two sources of truth.
112
- - **Duplicate State** - Two variables always update at the exact same time and should probably be merged (e.g. `isLoading` + `isSuccess`).
113
- - **🛑 Infinite Loops** - A safety circuit-breaker that kills the auditor before a recursive update freezes your browser.
126
+ Two pieces of state repeatedly update within the same time window.
127
+
128
+ For example:
129
+
130
+ ```tsx
131
+ const [isLoading, setIsLoading] = useState(false);
132
+ const [isSuccess, setIsSuccess] = useState(false);
133
+ ```
114
134
 
115
- Under the hood this is timing correlation over an update graph - ideas borrowed loosely from graph theory and signal processing, not a formal proof. [**See examples & fixes →**](https://github.com/liovic/react-state-basis/wiki/The-Forensic-Catalog)
135
+ If these values consistently change together, it may be worth checking whether they could be represented by a single state value.
136
+
137
+ Basis reports the correlation; it does not assume that the states should be merged.
138
+
139
+ ### Fragmented updates
140
+
141
+ A single interaction causes updates across multiple components, files, contexts, or stores.
142
+
143
+ Sometimes this is intentional. In other cases, it can indicate that state ownership is spread across several places.
144
+
145
+ ### Context mirroring
146
+
147
+ Local state is repeatedly updated from Context state.
148
+
149
+ This can create two representations of the same information and is worth reviewing when the local copy does not have an independent purpose.
150
+
151
+ ### Update origins
152
+
153
+ When several updates occur together, Basis can use the observed update graph to identify which updates appear upstream of others.
154
+
155
+ This is intended to help investigate a chain of updates rather than simply reporting every downstream symptom.
156
+
157
+ ### Infinite update protection
158
+
159
+ Basis includes safeguards to stop its own instrumentation from continuing indefinitely when an application enters a recursive update loop.
116
160
 
117
161
  ---
118
162
 
119
- ## Reports & Telemetry
163
+ ### Important: these are signals, not proofs
120
164
 
121
- ### Architectural Health Report
122
- Check your entire app's state architecture by running `window.printBasisReport()` in the console.
165
+ Basis uses runtime timing and correlation heuristics.
123
166
 
124
- * **Refactor Priorities:** Ranks issues by blast radius on the update graph, so you can see which hook or event has the widest fan-out across the rest of your app.
125
- * **Efficiency Score:** A rough ratio of independent update sources vs effect-driven follow-up updates. Diagnostic, not a grade.
126
- * **Sync Issues:** Groups variables that tend to update together into clusters (e.g., boolean pairs that are really one piece of state).
167
+ A detected pattern is **not automatically a bug**, and Basis does not know the intent behind your application architecture.
127
168
 
128
- ### Hardware Telemetry
129
- Verify engine efficiency and heap stability in real-time via `window.getBasisMetrics()`.
169
+ Use the results as prompts for investigation rather than as rules for how React code should be written.
170
+
171
+ [See examples and possible fixes →](https://github.com/liovic/react-state-basis/wiki/The-Forensic-Catalog)
130
172
 
131
173
  ---
132
174
 
133
- ## Real-World Evidence
175
+ ## Reports
176
+
177
+ Run:
178
+
179
+ ```js
180
+ window.printBasisReport()
181
+ ```
182
+
183
+ to print a summary of the observed update graph.
184
+
185
+ The report can include:
134
186
 
135
- Basis has been tested against industry-standard codebases:
187
+ * **Update sources** - where observed update chains appear to originate.
188
+ * **Fan-out** - which updates are followed by the largest number of downstream updates.
189
+ * **Correlated state** - state variables that repeatedly update together.
190
+ * **Effect-driven updates** - updates that occur as a consequence of effects.
191
+ * **Engine metrics** - runtime measurements collected by the Basis engine.
136
192
 
137
- * **Excalidraw (114k⭐)** - Proposed a theme-sync fix [**PR #10637**](https://github.com/excalidraw/excalidraw/pull/10637) (not merged)
138
- * **shadcn-admin (10k⭐)** - Detected redundant state pattern in viewport detection hooks. [**PR #274**](https://github.com/satnaing/shadcn-admin/pull/274) (merged)
193
+ These metrics are diagnostic rather than a score for the quality of your application.
194
+
195
+ ### Runtime metrics
196
+
197
+ You can inspect engine metrics with:
198
+
199
+ ```js
200
+ window.getBasisMetrics()
201
+ ```
202
+
203
+ ---
204
+
205
+ ## Controlling the Instrumentation
206
+
207
+ ### Console-only mode
208
+
209
+ Disable the HUD while keeping diagnostics enabled:
210
+
211
+ ```tsx
212
+ <BasisProvider showHUD={false}>
213
+ <App />
214
+ </BasisProvider>
215
+ ```
216
+
217
+ ### Ignoring files
218
+
219
+ Add:
220
+
221
+ ```ts
222
+ // @basis-ignore
223
+ ```
224
+
225
+ to a file to disable Basis instrumentation for that file.
226
+
227
+ This can be useful for:
228
+
229
+ * high-frequency animation code
230
+ * third-party library wrappers
231
+ * intentionally synchronized state
232
+ * code where instrumentation is not useful
139
233
 
140
234
  ---
141
235
 
@@ -143,62 +237,89 @@ Basis has been tested against industry-standard codebases:
143
237
 
144
238
  ### Zustand
145
239
 
146
- Wrap your store with `basisLogger` to give Basis visibility into external
147
- store updates. Store signals appear in the HUD and health report alongside your React state.
240
+ Basis can observe Zustand store updates alongside React state.
148
241
 
149
242
  ```typescript
150
243
  import { create } from 'zustand';
151
244
  import { basisLogger } from 'react-state-basis/zustand';
152
245
 
153
246
  export const useStore = create(
154
- basisLogger((set) => ({
155
- theme: 'light',
156
- toggleTheme: () => set((state) => ({
157
- theme: state.theme === 'light' ? 'dark' : 'light'
158
- })),
159
- }), 'MyStore')
247
+ basisLogger(
248
+ (set) => ({
249
+ theme: 'light',
250
+
251
+ toggleTheme: () =>
252
+ set((state) => ({
253
+ theme: state.theme === 'light' ? 'dark' : 'light',
254
+ })),
255
+ }),
256
+ 'MyStore'
257
+ )
160
258
  );
161
259
  ```
162
260
 
163
- This enables detection of **Store Mirroring**, **Store Sync Leaks**, and
164
- **Global Event Fragmentation** across React and Zustand state simultaneously.
261
+ This allows React and Zustand updates to appear in the same runtime graph.
165
262
 
166
- [See full Zustand example →](./examples/basis-zustand/)
263
+ [See the Zustand example →](./examples/basis-zustand/)
167
264
 
168
- ### More integrations coming
265
+ ### Planned integrations
169
266
 
170
- Planned: XState, React Query, Redux Toolkit. Community PRs welcome.
267
+ XState, React Query, and Redux Toolkit are planned.
268
+
269
+ Community contributions are welcome.
171
270
 
172
271
  ---
173
272
 
174
273
  ## Performance & Privacy
175
274
 
176
- **Development:** <1ms overhead per update cycle, zero heap growth
177
- **Production:** ~0.01ms per hook (monitoring disabled, ~2-3KB bundle)
178
- **Privacy:** Only tracks update timing, never state values
275
+ Basis is designed primarily as a development-time diagnostic tool.
276
+
277
+ * **Development:** instrumentation overhead is designed to remain small; current benchmarks show less than 1ms per update cycle in tested scenarios.
278
+ * **Production:** monitoring is disabled, with a small production footprint.
279
+ * **Privacy:** Basis records update timing and relationships, not application state values.
179
280
 
180
- [**See benchmarks →**](https://github.com/liovic/react-state-basis/wiki/Performance-Forensics)
281
+ Actual overhead depends on the application and instrumentation configuration.
282
+
283
+ [See benchmarks →](https://github.com/liovic/react-state-basis/wiki/Performance-Forensics)
181
284
 
182
285
  ---
183
286
 
184
- ## Documentation & Theory
287
+ ## Real-World Examples
288
+
289
+ Basis has also been tested against existing open-source applications.
185
290
 
186
- The engine uses graph and timing heuristics to infer likely architectural issues from *when* state changes, not *what* it changes to. [**The wiki**](https://github.com/liovic/react-state-basis/wiki) explains the full mental model, the math it borrows from, and the engine internals. It is a heuristic, not a proof.
291
+ * **Excalidraw** - Basis identified a theme synchronization pattern and a possible simplification. [PR #10637](https://github.com/excalidraw/excalidraw/pull/10637) was proposed but not merged.
292
+ * **shadcn-admin** - Basis identified a redundant state pattern in viewport detection hooks. [PR #274](https://github.com/satnaing/shadcn-admin/pull/274) was merged.
293
+
294
+ These examples are intended as demonstrations of the tool's output, not as claims that every detected pattern represents a defect.
187
295
 
188
296
  ---
189
297
 
190
- ## Roadmap
298
+ ## How It Works
191
299
 
192
- Each version answers a different architectural question:
300
+ Basis observes the timing and relationships between state updates while your application runs.
193
301
 
194
- **v0.4.x** - Detect states that always move together *(The Correlation Era)*
195
- ✓ **v0.5.x** - Detect local copies of Context *(The Decomposition Era)*
196
- → **v0.6.x** - Rank which update fans out widest *(The Graph Era)*
197
- **v0.7.x** - Detect derivative vs. independent state *(The Information Era)*
198
- **v0.8.x** - Estimate how much local state a component actually needs *(The Manifold Era)*
302
+ It builds an in-memory representation of those updates and applies heuristics to identify recurring patterns.
199
303
 
304
+ It does **not** need to inspect the values stored in your state to perform these checks.
200
305
 
201
- [**More info**](https://github.com/liovic/react-state-basis/wiki/Roadmap)
306
+ The analysis is intentionally heuristic. Runtime behavior can show that two things consistently happen together, but it cannot by itself prove why they happen together or whether the relationship is intentional.
307
+
308
+ For a deeper look at the implementation and underlying model:
309
+
310
+ [Read the documentation and theory →](https://github.com/liovic/react-state-basis/wiki)
311
+
312
+ ---
313
+
314
+ ## Roadmap
315
+
316
+ * ✓ **v0.4.x** - Identify state that repeatedly updates together
317
+ * ✓ **v0.5.x** - Identify local state synchronized from Context
318
+ * → **v0.6.x** - Analyze update fan-out and likely upstream sources
319
+ * **v0.7.x** - Improve detection of derived vs. independent state
320
+ * **v0.8.x** - Explore how much local state components actually use
321
+
322
+ [See the full roadmap →](https://github.com/liovic/react-state-basis/wiki/Roadmap)
202
323
 
203
324
  ---
204
325
 
@@ -206,4 +327,4 @@ Each version answers a different architectural question:
206
327
 
207
328
  Built by [LP](https://github.com/liovic) • [MIT License](https://opensource.org/licenses/MIT)
208
329
 
209
- </div>
330
+ </div>
@@ -837,6 +837,13 @@ var unregisterVariable = (l) => {
837
837
  instance.loopCounters.delete(l);
838
838
  instance.pausedVariables.delete(l);
839
839
  instance.redundantLabels.delete(l);
840
+ instance.graph.delete(l);
841
+ instance.graph.forEach((targets) => targets.delete(l));
842
+ instance.violationMap.delete(l);
843
+ instance.violationMap.forEach((list) => {
844
+ const idx = list.findIndex((v) => v.target === l);
845
+ if (idx !== -1) list.splice(idx, 1);
846
+ });
840
847
  };
841
848
  var beginEffectTracking = (l) => {
842
849
  if (instance.config.debug) instance.currentEffectSource = l;
@@ -874,4 +881,4 @@ export {
874
881
  printBasisHealthReport,
875
882
  getBasisMetrics
876
883
  };
877
- //# sourceMappingURL=chunk-E3D6YEKB.mjs.map
884
+ //# sourceMappingURL=chunk-6GBZSOB3.mjs.map