@buoy-gg/jotai 7.0.40 → 7.0.42

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.
Files changed (54) hide show
  1. package/README.md +11 -76
  2. package/lib/commonjs/index.js +1 -112
  3. package/lib/commonjs/jotai/components/JotaiAtomBrowser.js +1 -199
  4. package/lib/commonjs/jotai/components/JotaiAtomChangeItem.js +1 -85
  5. package/lib/commonjs/jotai/components/JotaiAtomDetailContent.js +1 -715
  6. package/lib/commonjs/jotai/components/JotaiAtomValueDetailContent.js +1 -444
  7. package/lib/commonjs/jotai/components/JotaiEventFilterView.js +1 -43
  8. package/lib/commonjs/jotai/components/JotaiIcon.js +1 -18
  9. package/lib/commonjs/jotai/components/JotaiModal.js +1 -743
  10. package/lib/commonjs/jotai/hooks/useJotaiAtomChanges.js +1 -69
  11. package/lib/commonjs/jotai/sync/jotaiSyncAdapter.js +1 -306
  12. package/lib/commonjs/jotai/types/index.js +1 -1
  13. package/lib/commonjs/jotai/utils/jotaiStateStore.js +1 -321
  14. package/lib/commonjs/jotai/utils/snapshotProvider.js +1 -215
  15. package/lib/commonjs/jotai/utils/testIDs.js +1 -90
  16. package/lib/commonjs/jotai/utils/watchAtoms.js +1 -162
  17. package/lib/commonjs/jotai/utils/writeSemantics.js +1 -47
  18. package/lib/commonjs/preset.js +1 -90
  19. package/lib/module/index.js +1 -85
  20. package/lib/module/jotai/components/JotaiAtomBrowser.js +1 -195
  21. package/lib/module/jotai/components/JotaiAtomChangeItem.js +1 -81
  22. package/lib/module/jotai/components/JotaiAtomDetailContent.js +1 -710
  23. package/lib/module/jotai/components/JotaiAtomValueDetailContent.js +1 -442
  24. package/lib/module/jotai/components/JotaiEventFilterView.js +1 -39
  25. package/lib/module/jotai/components/JotaiIcon.js +1 -11
  26. package/lib/module/jotai/components/JotaiModal.js +1 -738
  27. package/lib/module/jotai/hooks/useJotaiAtomChanges.js +1 -65
  28. package/lib/module/jotai/sync/jotaiSyncAdapter.js +1 -303
  29. package/lib/module/jotai/types/index.js +1 -1
  30. package/lib/module/jotai/utils/jotaiStateStore.js +1 -317
  31. package/lib/module/jotai/utils/snapshotProvider.js +1 -214
  32. package/lib/module/jotai/utils/testIDs.js +1 -82
  33. package/lib/module/jotai/utils/watchAtoms.js +1 -157
  34. package/lib/module/jotai/utils/writeSemantics.js +1 -43
  35. package/lib/module/preset.js +1 -87
  36. package/lib/web/index.mjs +2 -0
  37. package/package.json +35 -12
  38. package/lib/typescript/index.d.ts.map +0 -1
  39. package/lib/typescript/jotai/components/JotaiAtomBrowser.d.ts.map +0 -1
  40. package/lib/typescript/jotai/components/JotaiAtomChangeItem.d.ts.map +0 -1
  41. package/lib/typescript/jotai/components/JotaiAtomDetailContent.d.ts.map +0 -1
  42. package/lib/typescript/jotai/components/JotaiAtomValueDetailContent.d.ts.map +0 -1
  43. package/lib/typescript/jotai/components/JotaiEventFilterView.d.ts.map +0 -1
  44. package/lib/typescript/jotai/components/JotaiIcon.d.ts.map +0 -1
  45. package/lib/typescript/jotai/components/JotaiModal.d.ts.map +0 -1
  46. package/lib/typescript/jotai/hooks/useJotaiAtomChanges.d.ts.map +0 -1
  47. package/lib/typescript/jotai/sync/jotaiSyncAdapter.d.ts.map +0 -1
  48. package/lib/typescript/jotai/types/index.d.ts.map +0 -1
  49. package/lib/typescript/jotai/utils/jotaiStateStore.d.ts.map +0 -1
  50. package/lib/typescript/jotai/utils/snapshotProvider.d.ts.map +0 -1
  51. package/lib/typescript/jotai/utils/testIDs.d.ts.map +0 -1
  52. package/lib/typescript/jotai/utils/watchAtoms.d.ts.map +0 -1
  53. package/lib/typescript/jotai/utils/writeSemantics.d.ts.map +0 -1
  54. package/lib/typescript/preset.d.ts.map +0 -1
@@ -1,321 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.jotaiStateStore = void 0;
7
- var _sharedUi = require("@buoy-gg/shared-ui");
8
- /**
9
- * Jotai state store — captures and stores Jotai atom changes
10
- *
11
- * Mirrors the architecture of zustandStateStore.ts from @buoy-gg/zustand
12
- */
13
-
14
- // ============================================
15
- // Atom Color Palette
16
- // ============================================
17
-
18
- const ATOM_COLORS = {
19
- count: "#10B981",
20
- // emerald
21
- auth: "#8B5CF6",
22
- // purple
23
- user: "#3B82F6",
24
- // blue
25
- cart: "#EC4899",
26
- // pink
27
- app: "#6366F1",
28
- // indigo
29
- ui: "#F59E0B",
30
- // amber
31
- settings: "#14B8A6",
32
- // teal
33
- theme: "#06B6D4",
34
- // cyan
35
- nav: "#F97316",
36
- // orange
37
- form: "#EF4444",
38
- // red
39
- modal: "#A855F7",
40
- // violet
41
- filter: "#84CC16" // lime
42
- };
43
- function getAtomColor(label) {
44
- return (0, _sharedUi.entityColor)(label, ATOM_COLORS);
45
- }
46
-
47
- // ============================================
48
- // Helper Functions
49
- // ============================================
50
-
51
- function formatValuePreview(value, maxLength = 40) {
52
- return (0, _sharedUi.formatValuePreview)(value, {
53
- maxLength,
54
- functionLabel: "(fn)"
55
- });
56
- }
57
- const getValueDiffSummary = _sharedUi.shallowDiffSummary;
58
-
59
- // ============================================
60
- // Jotai State Store
61
- // ============================================
62
-
63
- class JotaiStateStore extends _sharedUi.ChangeLog {
64
- atoms = new Map();
65
- atomListeners = new Set();
66
-
67
- // ---- Change Tracking ----
68
-
69
- addAtomChange(params) {
70
- if (!this.canRecord()) return;
71
- const {
72
- atomLabel,
73
- prevValue,
74
- nextValue,
75
- category = "write"
76
- } = params;
77
- const hasValueChange = prevValue !== nextValue;
78
- const {
79
- summary,
80
- changedKeys,
81
- changedCount
82
- } = getValueDiffSummary(prevValue, nextValue);
83
- const valuePreview = formatValuePreview(nextValue);
84
- const change = {
85
- id: this.nextId(),
86
- atomLabel,
87
- timestamp: Date.now(),
88
- prevValue,
89
- nextValue,
90
- hasValueChange,
91
- category,
92
- changedKeys,
93
- changedKeysCount: changedCount,
94
- diffSummary: summary,
95
- valuePreview,
96
- isSlowUpdate: false
97
- };
98
- this.push(change);
99
- const atomInfo = this.atoms.get(atomLabel);
100
- if (atomInfo) {
101
- atomInfo.changeCount++;
102
- }
103
- this.notifyListeners();
104
- this.notifyAtomListeners();
105
- }
106
- getAtomChanges() {
107
- return this.getAll();
108
- }
109
- getAtomChangeById(id) {
110
- return this.getById(id);
111
- }
112
- clearAtomChanges() {
113
- for (const atom of this.atoms.values()) {
114
- atom.changeCount = 0;
115
- }
116
- this.clear();
117
- this.notifyAtomListeners();
118
- }
119
-
120
- // ---- Remote Mirror Mode ----
121
-
122
- /**
123
- * Serializable snapshot of the tracked atoms (registry metadata plus each
124
- * atom's current value). Used by the sync adapter on the device side —
125
- * the live `getValue` handles can't go over the wire.
126
- */
127
- getAtomSnapshots() {
128
- return Array.from(this.atoms.values()).map(atom => {
129
- let currentValue;
130
- try {
131
- currentValue = atom.getValue();
132
- } catch {
133
- currentValue = undefined;
134
- }
135
- return {
136
- label: atom.label,
137
- changeCount: atom.changeCount,
138
- color: atom.color,
139
- currentValue,
140
- writeKind: atom.writeKind
141
- };
142
- });
143
- }
144
-
145
- /**
146
- * Replace the entire mirror contents from a synced device snapshot. The
147
- * rebuilt registry entries get stub `getValue` handles serving the
148
- * snapshotted value. Respects the enabled flag so the UI's pause button
149
- * freezes the mirror.
150
- */
151
- replaceFromSnapshot(changes, atomSnapshots) {
152
- if (!this.getEnabled()) return;
153
-
154
- // Preserve values a remote mirror already hydrated — wire-form snapshots
155
- // always carry markers, and a later snapshot must not blank the detail pane.
156
- const hydrated = new Map();
157
- for (const existing of this.items) {
158
- const prev = existing.prevValue;
159
- if (!prev || prev.__buoyValueOnDevice !== true) {
160
- hydrated.set(existing.id, existing);
161
- }
162
- }
163
- const merged = changes.slice(0, this.max).map(change => {
164
- const kept = hydrated.get(change.id);
165
- const incoming = change.prevValue;
166
- return kept && incoming?.__buoyValueOnDevice === true ? {
167
- ...change,
168
- prevValue: kept.prevValue,
169
- nextValue: kept.nextValue
170
- } : change;
171
- });
172
-
173
- // Preserve currentValue a remote mirror already hydrated — oversized
174
- // atoms arrive as VALUE_ON_DEVICE markers on every snapshot.
175
- const hydratedAtoms = new Map();
176
- for (const [label, atom] of this.atoms) {
177
- try {
178
- const value = atom.getValue();
179
- const marker = value;
180
- if (!marker || marker.__buoyValueOnDevice !== true) {
181
- hydratedAtoms.set(label, value);
182
- }
183
- } catch {
184
- // ignore
185
- }
186
- }
187
- this.atoms = new Map(atomSnapshots.map(snapshot => {
188
- const incoming = snapshot.currentValue;
189
- const kept = hydratedAtoms.get(snapshot.label);
190
- const currentValue = kept !== undefined && incoming?.__buoyValueOnDevice === true ? kept : snapshot.currentValue;
191
- return [snapshot.label, {
192
- label: snapshot.label,
193
- changeCount: snapshot.changeCount,
194
- color: snapshot.color,
195
- getValue: () => currentValue,
196
- // No setValue: the mirror has no wire back to the device from
197
- // this entry (yet) — the detail screen renders read-only there.
198
- writeKind: snapshot.writeKind
199
- }];
200
- }));
201
- this.setItems(merged);
202
- this.notifyAtomListeners();
203
- }
204
-
205
- /**
206
- * Hydrate one mirrored change with real values fetched on demand from the
207
- * device via the adapter's `getChangeDetail`. Remote-mirror (desktop) only.
208
- */
209
- hydrateChange(id, patch) {
210
- this.hydrateById(id, patch);
211
- }
212
-
213
- /**
214
- * Hydrate one mirrored atom's currentValue fetched on demand from the
215
- * device via the adapter's `getAtomValue`. Remote-mirror (desktop) only.
216
- */
217
- hydrateAtomValue(label, currentValue) {
218
- const atom = this.atoms.get(label);
219
- if (!atom) return;
220
- this.atoms.set(label, {
221
- ...atom,
222
- getValue: () => currentValue
223
- });
224
- this.notifyAtomListeners();
225
- }
226
-
227
- // ---- Atom Registry ----
228
-
229
- registerAtom(label, getValue, setValue, writeKind) {
230
- if (this.atoms.has(label)) return;
231
- const atomInfo = {
232
- label,
233
- changeCount: 0,
234
- color: getAtomColor(label),
235
- getValue,
236
- setValue,
237
- writeKind
238
- };
239
- this.atoms.set(label, atomInfo);
240
- this.notifyAtomListeners();
241
- }
242
-
243
- /**
244
- * Write a value to a registered atom (remote write from the dashboard/MCP).
245
- * Throws if the atom is unknown or read-only (derived atoms have no setter).
246
- */
247
- setAtomValue(label, value) {
248
- const atom = this.atoms.get(label);
249
- if (!atom) {
250
- throw new Error(`Atom "${label}" is not registered`);
251
- }
252
- if (!atom.setValue) {
253
- throw new Error(`Atom "${label}" is read-only (derived) — cannot set`);
254
- }
255
- atom.setValue(value);
256
- }
257
- unregisterAtom(label) {
258
- this.atoms.delete(label);
259
- this.notifyAtomListeners();
260
- }
261
- getAtoms() {
262
- return Array.from(this.atoms.values());
263
- }
264
- getAtom(label) {
265
- return this.atoms.get(label);
266
- }
267
- getAtomColor(label) {
268
- return this.atoms.get(label)?.color ?? getAtomColor(label);
269
- }
270
-
271
- // ---- Filtering ----
272
-
273
- filterAtomChanges(filter) {
274
- let filtered = this.getAll();
275
- if (filter.searchText) {
276
- const search = filter.searchText.toLowerCase();
277
- filtered = filtered.filter(c => c.atomLabel.toLowerCase().includes(search) || c.valuePreview.toLowerCase().includes(search) || c.changedKeys.some(k => k.toLowerCase().includes(search)));
278
- }
279
- if (filter.atomLabels && filter.atomLabels.length > 0) {
280
- filtered = filtered.filter(c => filter.atomLabels.includes(c.atomLabel));
281
- }
282
- if (filter.onlyWithChanges) {
283
- filtered = filtered.filter(c => c.hasValueChange);
284
- }
285
- return filtered;
286
- }
287
-
288
- // ---- Stats ----
289
-
290
- getStats() {
291
- const total = this.items.length;
292
- const withChanges = this.items.filter(c => c.hasValueChange).length;
293
- return {
294
- totalChanges: total,
295
- changesWithValueChange: withChanges,
296
- changesWithoutValueChange: total - withChanges,
297
- atomCount: this.atoms.size
298
- };
299
- }
300
- getUniqueAtomLabels() {
301
- return Array.from(this.atoms.keys()).sort();
302
- }
303
-
304
- // ---- Enable / Disable ----
305
-
306
- setMaxChanges(max) {
307
- this.setMax(max);
308
- }
309
-
310
- // ---- Subscriptions ----
311
-
312
- subscribeToAtoms(listener) {
313
- this.atomListeners.add(listener);
314
- return () => this.atomListeners.delete(listener);
315
- }
316
- notifyAtomListeners() {
317
- const atoms = this.getAtoms();
318
- this.atomListeners.forEach(listener => listener(atoms));
319
- }
320
- }
321
- const jotaiStateStore = exports.jotaiStateStore = new JotaiStateStore();
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.jotaiStateStore=void 0;var _sharedUi=require("@buoy-gg/shared-ui");const ATOM_COLORS={count:"#10B981",auth:"#8B5CF6",user:"#3B82F6",cart:"#EC4899",app:"#6366F1",ui:"#F59E0B",settings:"#14B8A6",theme:"#06B6D4",nav:"#F97316",form:"#EF4444",modal:"#A855F7",filter:"#84CC16"};function getAtomColor(h){return(0,_sharedUi.entityColor)(h,ATOM_COLORS)}function formatValuePreview(h,t=40){return(0,_sharedUi.formatValuePreview)(h,{maxLength:t,functionLabel:"(fn)"})}const getValueDiffSummary=_sharedUi.shallowDiffSummary;class JotaiStateStore extends _sharedUi.ChangeLog{atoms=new Map;atomListeners=new Set;addAtomChange(t){if(!this.canRecord())return;const{atomLabel:e,prevValue:o,nextValue:a,category:i="write"}=t;const s=o!==a;const{summary:r,changedKeys:n,changedCount:u}=getValueDiffSummary(o,a);const l=formatValuePreview(a);const c={id:this.nextId(),atomLabel:e,timestamp:Date.now(),prevValue:o,nextValue:a,hasValueChange:s,category:i,changedKeys:n,changedKeysCount:u,diffSummary:r,valuePreview:l,isSlowUpdate:false};this.push(c);const m=this.atoms.get(e);if(m){m.changeCount++}this.notifyListeners();this.notifyAtomListeners()}getAtomChanges(){return this.getAll()}getAtomChangeById(t){return this.getById(t)}clearAtomChanges(){for(const t of this.atoms.values()){t.changeCount=0}this.clear();this.notifyAtomListeners()}getAtomSnapshots(){return Array.from(this.atoms.values()).map(t=>{let e;try{e=t.getValue()}catch{e=void 0}return{label:t.label,changeCount:t.changeCount,color:t.color,currentValue:e,writeKind:t.writeKind}})}replaceFromSnapshot(t,e){if(!this.getEnabled())return;const o=new Map;for(const s of this.items){const r=s.prevValue;if(!r||r.__buoyValueOnDevice!==true){o.set(s.id,s)}}const a=t.slice(0,this.max).map(s=>{const r=o.get(s.id);const n=s.prevValue;return r&&n?.__buoyValueOnDevice===true?{...s,prevValue:r.prevValue,nextValue:r.nextValue}:s});const i=new Map;for(const[s,r]of this.atoms){try{const n=r.getValue();const u=n;if(!u||u.__buoyValueOnDevice!==true){i.set(s,n)}}catch{}}this.atoms=new Map(e.map(s=>{const r=s.currentValue;const n=i.get(s.label);const u=n!==void 0&&r?.__buoyValueOnDevice===true?n:s.currentValue;return[s.label,{label:s.label,changeCount:s.changeCount,color:s.color,getValue:()=>u,writeKind:s.writeKind}]}));this.setItems(a);this.notifyAtomListeners()}hydrateChange(t,e){this.hydrateById(t,e)}hydrateAtomValue(t,e){const o=this.atoms.get(t);if(!o)return;this.atoms.set(t,{...o,getValue:()=>e});this.notifyAtomListeners()}registerAtom(t,e,o,a){if(this.atoms.has(t))return;const i={label:t,changeCount:0,color:getAtomColor(t),getValue:e,setValue:o,writeKind:a};this.atoms.set(t,i);this.notifyAtomListeners()}setAtomValue(t,e){const o=this.atoms.get(t);if(!o){throw new Error(`Atom "${t}" is not registered`)}if(!o.setValue){throw new Error(`Atom "${t}" is read-only (derived) \u2014 cannot set`)}o.setValue(e)}unregisterAtom(t){this.atoms.delete(t);this.notifyAtomListeners()}getAtoms(){return Array.from(this.atoms.values())}getAtom(t){return this.atoms.get(t)}getAtomColor(t){return this.atoms.get(t)?.color??getAtomColor(t)}filterAtomChanges(t){let e=this.getAll();if(t.searchText){const o=t.searchText.toLowerCase();e=e.filter(a=>a.atomLabel.toLowerCase().includes(o)||a.valuePreview.toLowerCase().includes(o)||a.changedKeys.some(i=>i.toLowerCase().includes(o)))}if(t.atomLabels&&t.atomLabels.length>0){e=e.filter(o=>t.atomLabels.includes(o.atomLabel))}if(t.onlyWithChanges){e=e.filter(o=>o.hasValueChange)}return e}getStats(){const t=this.items.length;const e=this.items.filter(o=>o.hasValueChange).length;return{totalChanges:t,changesWithValueChange:e,changesWithoutValueChange:t-e,atomCount:this.atoms.size}}getUniqueAtomLabels(){return Array.from(this.atoms.keys()).sort()}setMaxChanges(t){this.setMax(t)}subscribeToAtoms(t){this.atomListeners.add(t);return()=>this.atomListeners.delete(t)}notifyAtomListeners(){const t=this.getAtoms();this.atomListeners.forEach(e=>e(t))}}const jotaiStateStore=exports.jotaiStateStore=new JotaiStateStore;
@@ -1,215 +1 @@
1
- "use strict";
2
-
3
- var _sharedUi = require("@buoy-gg/shared-ui");
4
- var _dataViewer = require("@buoy-gg/shared-ui/dataViewer");
5
- var _jotaiStateStore = require("./jotaiStateStore");
6
- /**
7
- * Time Machine snapshot provider for Jotai.
8
- *
9
- * Registered into @buoy-gg/shared-ui's stateSnapshotRegistry at module init;
10
- * consumed by @buoy-gg/time-machine.
11
- *
12
- * Coverage is opt-in by nature: only atoms the app passed to `watchAtoms` /
13
- * `watchDefaultStoreAtoms` are visible. Derived (read-only) atoms are captured
14
- * for inspection but never restored — they recompute from their sources.
15
- * Caveat (documented): atoms with custom write functions (`atomWithReducer`)
16
- * receive ACTIONS through `store.set`, so restoring a raw value can misbehave;
17
- * nothing in the registry distinguishes them from plain atoms.
18
- */
19
-
20
- function status() {
21
- const atoms = _jotaiStateStore.jotaiStateStore.getAtoms();
22
- if (atoms.length === 0) {
23
- return {
24
- canCapture: false,
25
- canRestore: false,
26
- detail: "No Jotai atoms registered — pass your atoms to watchAtoms() / watchDefaultStoreAtoms()"
27
- };
28
- }
29
- const writable = atoms.filter(a => a.setValue).length;
30
- return {
31
- canCapture: true,
32
- canRestore: writable > 0,
33
- detail: writable < atoms.length ? `${atoms.length - writable} derived atom(s) recompute automatically and are not restored` : undefined
34
- };
35
- }
36
- async function capture() {
37
- const warnings = [];
38
- const atoms = {};
39
- let derived = 0;
40
- for (const info of _jotaiStateStore.jotaiStateStore.getAtoms()) {
41
- try {
42
- const value = info.getValue();
43
- if (typeof value === "function") {
44
- warnings.push(`atom "${info.label}": function value not capturable`);
45
- continue;
46
- }
47
- if (!info.setValue) derived += 1;
48
- atoms[info.label] = {
49
- value,
50
- writable: Boolean(info.setValue)
51
- };
52
- } catch (e) {
53
- warnings.push(`atom "${info.label}": capture failed (${String(e)})`);
54
- }
55
- }
56
- if (derived > 0) {
57
- warnings.push(`${derived} derived atom(s) captured for inspection only (recompute on restore)`);
58
- }
59
- return {
60
- data: {
61
- atoms
62
- },
63
- warnings
64
- };
65
- }
66
- async function restore(data, options) {
67
- const snapshot = data;
68
- const scope = (0, _sharedUi.createRestoreScope)(options);
69
- const result = (0, _sharedUi.emptyRestoreResult)();
70
- if (!snapshot?.atoms) {
71
- return (0, _sharedUi.failedRestore)("empty jotai snapshot");
72
- }
73
- let derived = 0;
74
- for (const [label, entry] of Object.entries(snapshot.atoms)) {
75
- if (!scope.allows(label)) continue;
76
- if (!entry.writable) {
77
- derived += 1;
78
- continue;
79
- }
80
- const info = _jotaiStateStore.jotaiStateStore.getAtom(label);
81
- if (!info) {
82
- result.skipped.push({
83
- id: label,
84
- reason: "atom not registered (renamed, or unwatched since snapshot?)"
85
- });
86
- continue;
87
- }
88
- if (!info.setValue) {
89
- result.skipped.push({
90
- id: label,
91
- reason: "atom is now read-only (derived)"
92
- });
93
- continue;
94
- }
95
- try {
96
- info.setValue(entry.value);
97
- result.applied += 1;
98
- } catch (e) {
99
- result.skipped.push({
100
- id: label,
101
- reason: String(e)
102
- });
103
- result.ok = false;
104
- }
105
- }
106
- if (derived > 0) {
107
- result.warnings.push(`${derived} derived atom(s) left to recompute`);
108
- }
109
- const excludedWarning = scope.excludedWarning("atom(s)");
110
- if (excludedWarning) result.warnings.push(excludedWarning);
111
- return result;
112
- }
113
-
114
- /** Per-atom restore preview. `base` omitted = compare against live atoms. */
115
- async function previewItems(target, base) {
116
- const targetAtoms = target?.atoms ?? {};
117
- const liveMode = base === undefined;
118
- const baseAtoms = {};
119
- if (liveMode) {
120
- for (const info of _jotaiStateStore.jotaiStateStore.getAtoms()) {
121
- try {
122
- baseAtoms[info.label] = {
123
- value: info.getValue(),
124
- writable: Boolean(info.setValue)
125
- };
126
- } catch {
127
- // unreadable atom — treated as absent
128
- }
129
- }
130
- } else {
131
- Object.assign(baseAtoms, base?.atoms ?? {});
132
- }
133
- const items = [];
134
- const notes = [];
135
- for (const label of (0, _sharedUi.unionKeys)(baseAtoms, targetAtoms)) {
136
- const inTarget = label in targetAtoms;
137
- if (!inTarget) {
138
- if (liveMode) notes.push(`atom "${label}" not in snapshot — untouched`);else items.push({
139
- key: label,
140
- label,
141
- verdict: "removed",
142
- oldValue: baseAtoms[label]?.value
143
- });
144
- continue;
145
- }
146
- const entry = targetAtoms[label];
147
- const equal = label in baseAtoms && (0, _dataViewer.boundedDeepEqual)(baseAtoms[label].value, entry.value);
148
- if (!entry.writable) {
149
- items.push({
150
- key: label,
151
- label,
152
- verdict: equal ? "unchanged" : "recomputes",
153
- reason: equal ? undefined : "derived atom — recomputes from its sources",
154
- oldValue: baseAtoms[label]?.value,
155
- newValue: entry.value
156
- });
157
- continue;
158
- }
159
- if (liveMode) {
160
- const info = _jotaiStateStore.jotaiStateStore.getAtom(label);
161
- if (!info) {
162
- items.push({
163
- key: label,
164
- label,
165
- verdict: "wont-apply",
166
- reason: "atom not registered (renamed, or unwatched since snapshot?)"
167
- });
168
- continue;
169
- }
170
- if (!info.setValue) {
171
- items.push({
172
- key: label,
173
- label,
174
- verdict: "wont-apply",
175
- reason: "atom is now read-only (derived)"
176
- });
177
- continue;
178
- }
179
- }
180
- if (!(label in baseAtoms)) {
181
- items.push({
182
- key: label,
183
- label,
184
- verdict: "added",
185
- newValue: entry.value
186
- });
187
- } else if (equal) {
188
- items.push({
189
- key: label,
190
- label,
191
- verdict: "unchanged"
192
- });
193
- } else {
194
- items.push({
195
- key: label,
196
- label,
197
- verdict: "changed",
198
- oldValue: baseAtoms[label].value,
199
- newValue: entry.value
200
- });
201
- }
202
- }
203
- return {
204
- items,
205
- notes
206
- };
207
- }
208
- (0, _sharedUi.registerStateSnapshotProvider)({
209
- id: "jotai",
210
- label: "Jotai",
211
- status,
212
- capture,
213
- restore,
214
- previewItems
215
- });
1
+ "use strict";var _sharedUi=require("@buoy-gg/shared-ui");var _dataViewer=require("@buoy-gg/shared-ui/dataViewer");var _jotaiStateStore=require("./jotaiStateStore");function status(){const o=_jotaiStateStore.jotaiStateStore.getAtoms();if(o.length===0){return{canCapture:false,canRestore:false,detail:"No Jotai atoms registered \u2014 pass your atoms to watchAtoms() / watchDefaultStoreAtoms()"}}const r=o.filter(n=>n.setValue).length;return{canCapture:true,canRestore:r>0,detail:r<o.length?`${o.length-r} derived atom(s) recompute automatically and are not restored`:void 0}}async function capture(){const o=[];const r={};let n=0;for(const a of _jotaiStateStore.jotaiStateStore.getAtoms()){try{const t=a.getValue();if(typeof t==="function"){o.push(`atom "${a.label}": function value not capturable`);continue}if(!a.setValue)n+=1;r[a.label]={value:t,writable:Boolean(a.setValue)}}catch(t){o.push(`atom "${a.label}": capture failed (${String(t)})`)}}if(n>0){o.push(`${n} derived atom(s) captured for inspection only (recompute on restore)`)}return{data:{atoms:r},warnings:o}}async function restore(o,r){const n=o;const a=(0,_sharedUi.createRestoreScope)(r);const t=(0,_sharedUi.emptyRestoreResult)();if(!n?.atoms){return(0,_sharedUi.failedRestore)("empty jotai snapshot")}let s=0;for(const[e,c]of Object.entries(n.atoms)){if(!a.allows(e))continue;if(!c.writable){s+=1;continue}const i=_jotaiStateStore.jotaiStateStore.getAtom(e);if(!i){t.skipped.push({id:e,reason:"atom not registered (renamed, or unwatched since snapshot?)"});continue}if(!i.setValue){t.skipped.push({id:e,reason:"atom is now read-only (derived)"});continue}try{i.setValue(c.value);t.applied+=1}catch(l){t.skipped.push({id:e,reason:String(l)});t.ok=false}}if(s>0){t.warnings.push(`${s} derived atom(s) left to recompute`)}const u=a.excludedWarning("atom(s)");if(u)t.warnings.push(u);return t}async function previewItems(o,r){const n=o?.atoms??{};const a=r===void 0;const t={};if(a){for(const e of _jotaiStateStore.jotaiStateStore.getAtoms()){try{t[e.label]={value:e.getValue(),writable:Boolean(e.setValue)}}catch{}}}else{Object.assign(t,r?.atoms??{})}const s=[];const u=[];for(const e of(0,_sharedUi.unionKeys)(t,n)){const c=e in n;if(!c){if(a)u.push(`atom "${e}" not in snapshot \u2014 untouched`);else s.push({key:e,label:e,verdict:"removed",oldValue:t[e]?.value});continue}const i=n[e];const l=e in t&&(0,_dataViewer.boundedDeepEqual)(t[e].value,i.value);if(!i.writable){s.push({key:e,label:e,verdict:l?"unchanged":"recomputes",reason:l?void 0:"derived atom \u2014 recomputes from its sources",oldValue:t[e]?.value,newValue:i.value});continue}if(a){const d=_jotaiStateStore.jotaiStateStore.getAtom(e);if(!d){s.push({key:e,label:e,verdict:"wont-apply",reason:"atom not registered (renamed, or unwatched since snapshot?)"});continue}if(!d.setValue){s.push({key:e,label:e,verdict:"wont-apply",reason:"atom is now read-only (derived)"});continue}}if(!(e in t)){s.push({key:e,label:e,verdict:"added",newValue:i.value})}else if(l){s.push({key:e,label:e,verdict:"unchanged"})}else{s.push({key:e,label:e,verdict:"changed",oldValue:t[e].value,newValue:i.value})}}return{items:s,notes:u}}(0,_sharedUi.registerStateSnapshotProvider)({id:"jotai",label:"Jotai",status,capture,restore,previewItems});
@@ -1,90 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.JOTAI_TEST_ID = void 0;
7
- exports.atomRowTestID = atomRowTestID;
8
- exports.buildChangeRowTestIDs = buildChangeRowTestIDs;
9
- exports.changeRowTestID = changeRowTestID;
10
- exports.slugForTestID = slugForTestID;
11
- /**
12
- * Jotai tool test handles.
13
- *
14
- * Every tappable and every value the e2e suite reads back is addressed
15
- * `buoy-jotai-<thing>`. Two rules need explaining, and the second is the one
16
- * this tool does not share with any other.
17
- *
18
- * **A change row's handle carries an ORDINAL, and it counts per ATOM.** Same
19
- * reason as the Zustand tool: an atom a screen writes to fifty times produces
20
- * fifty separate, equally real rows, and addressing them by label alone would
21
- * give fifty views one handle — Detox fails a *matcher* that hits more than
22
- * one element, so the suite would break on `toExist()` before any assertion,
23
- * with an error that says nothing about duplicate changes. The handle is
24
- * `buoy-jotai-row-<slug(atomLabel)>-<n>`, n counting back from the newest row
25
- * for that atom (1 = newest). Deliberately relative: an atom change has no
26
- * identity a host could know in advance, and what a test CAN know is "the
27
- * write I just fired is the newest change to that atom". Read and tap in one
28
- * beat — a row's ordinal moves the instant another change lands.
29
- *
30
- * **The ordinal is per ATOM, and one write can move several atoms.** This is
31
- * where jotai differs from every store-shaped tool. Atoms form a dependency
32
- * graph, and this tool watches NODES: a single `set` on a primitive atom
33
- * notifies every mounted derived atom whose value actually moved, so one
34
- * press can add a row under three different labels — and add nothing at all
35
- * under a fourth whose recomputed value happened to be unchanged. Counting
36
- * ordinals across the whole list would make a row's handle depend on how many
37
- * *other* atoms the write happened to touch, which is exactly the thing a
38
- * test of a graph must not assume.
39
- *
40
- * **An atom row's handle does NOT carry an ordinal.** An atom is registered
41
- * once under a label that is unique by construction
42
- * (`jotaiStateStore.registerAtom` returns early on a label it already holds,
43
- * and `watchAtoms` marks the atom with a per-label symbol), so
44
- * `buoy-jotai-atom-<slug(label)>` is as stable as the registration itself —
45
- * and unlike a change, it is a thing the host knows the name of before the
46
- * app has booted.
47
- *
48
- * The suite mirrors these rules in CommonJS (`example-dev-build/utils/
49
- * jotaiBench.js`) and asserts the two agree — Detox runs in Node on the host
50
- * and cannot import TypeScript.
51
- */
52
-
53
- /** Prefix every handle in this tool shares. */
54
- const JOTAI_TEST_ID = exports.JOTAI_TEST_ID = "buoy-jotai";
55
-
56
- /** `jbenchCount` → `jbenchcount`. Shared with every Buoy tool. */
57
- function slugForTestID(value) {
58
- return String(value ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
59
- }
60
-
61
- /**
62
- * Handle for one change row: `buoy-jotai-row-jbenchcount-1`.
63
- *
64
- * `ordinal` is 1-based and counts back from the newest row for this atom.
65
- */
66
- function changeRowTestID(atomLabel, ordinal) {
67
- return `${JOTAI_TEST_ID}-row-${slugForTestID(atomLabel)}-${ordinal}`;
68
- }
69
-
70
- /** Handle for one atom row in the browser: `buoy-jotai-atom-jbenchcount`. */
71
- function atomRowTestID(atomLabel) {
72
- return `${JOTAI_TEST_ID}-atom-${slugForTestID(atomLabel)}`;
73
- }
74
-
75
- /**
76
- * Row handle per change id for one rendered list, newest-first.
77
- *
78
- * Built once for the whole list rather than per row, because an ordinal is a
79
- * fact about the list and a row cannot compute its own.
80
- */
81
- function buildChangeRowTestIDs(changes) {
82
- const seen = new Map();
83
- const handles = new Map();
84
- for (const change of changes) {
85
- const ordinal = (seen.get(change.atomLabel) ?? 0) + 1;
86
- seen.set(change.atomLabel, ordinal);
87
- handles.set(change.id, changeRowTestID(change.atomLabel, ordinal));
88
- }
89
- return handles;
90
- }
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.JOTAI_TEST_ID=void 0;exports.atomRowTestID=atomRowTestID;exports.buildChangeRowTestIDs=buildChangeRowTestIDs;exports.changeRowTestID=changeRowTestID;exports.slugForTestID=slugForTestID;const JOTAI_TEST_ID=exports.JOTAI_TEST_ID="buoy-jotai";function slugForTestID(e){return String(e??"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"unknown"}function changeRowTestID(e,t){return`${JOTAI_TEST_ID}-row-${slugForTestID(e)}-${t}`}function atomRowTestID(e){return`${JOTAI_TEST_ID}-atom-${slugForTestID(e)}`}function buildChangeRowTestIDs(e){const t=new Map;const n=new Map;for(const o of e){const s=(t.get(o.atomLabel)??0)+1;t.set(o.atomLabel,s);n.set(o.id,changeRowTestID(o.atomLabel,s))}return n}