@buoy-gg/jotai 7.0.35 → 7.0.38

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 (52) hide show
  1. package/README.md +32 -5
  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 -67
  11. package/lib/commonjs/jotai/sync/jotaiSyncAdapter.js +1 -149
  12. package/lib/commonjs/jotai/utils/jotaiStateStore.js +1 -321
  13. package/lib/commonjs/jotai/utils/snapshotProvider.js +1 -215
  14. package/lib/commonjs/jotai/utils/testIDs.js +1 -90
  15. package/lib/commonjs/jotai/utils/watchAtoms.js +1 -162
  16. package/lib/commonjs/jotai/utils/writeSemantics.js +1 -47
  17. package/lib/commonjs/preset.js +1 -90
  18. package/lib/module/index.js +1 -85
  19. package/lib/module/jotai/components/JotaiAtomBrowser.js +1 -195
  20. package/lib/module/jotai/components/JotaiAtomChangeItem.js +1 -81
  21. package/lib/module/jotai/components/JotaiAtomDetailContent.js +1 -710
  22. package/lib/module/jotai/components/JotaiAtomValueDetailContent.js +1 -442
  23. package/lib/module/jotai/components/JotaiEventFilterView.js +1 -39
  24. package/lib/module/jotai/components/JotaiIcon.js +1 -11
  25. package/lib/module/jotai/components/JotaiModal.js +1 -738
  26. package/lib/module/jotai/hooks/useJotaiAtomChanges.js +1 -63
  27. package/lib/module/jotai/sync/jotaiSyncAdapter.js +1 -146
  28. package/lib/module/jotai/utils/jotaiStateStore.js +1 -317
  29. package/lib/module/jotai/utils/snapshotProvider.js +1 -214
  30. package/lib/module/jotai/utils/testIDs.js +1 -82
  31. package/lib/module/jotai/utils/watchAtoms.js +1 -157
  32. package/lib/module/jotai/utils/writeSemantics.js +1 -43
  33. package/lib/module/preset.js +1 -87
  34. package/lib/typescript/jotai/sync/jotaiSyncAdapter.d.ts +52 -2
  35. package/package.json +4 -4
  36. package/lib/typescript/index.d.ts.map +0 -1
  37. package/lib/typescript/jotai/components/JotaiAtomBrowser.d.ts.map +0 -1
  38. package/lib/typescript/jotai/components/JotaiAtomChangeItem.d.ts.map +0 -1
  39. package/lib/typescript/jotai/components/JotaiAtomDetailContent.d.ts.map +0 -1
  40. package/lib/typescript/jotai/components/JotaiAtomValueDetailContent.d.ts.map +0 -1
  41. package/lib/typescript/jotai/components/JotaiEventFilterView.d.ts.map +0 -1
  42. package/lib/typescript/jotai/components/JotaiIcon.d.ts.map +0 -1
  43. package/lib/typescript/jotai/components/JotaiModal.d.ts.map +0 -1
  44. package/lib/typescript/jotai/hooks/useJotaiAtomChanges.d.ts.map +0 -1
  45. package/lib/typescript/jotai/sync/jotaiSyncAdapter.d.ts.map +0 -1
  46. package/lib/typescript/jotai/types/index.d.ts.map +0 -1
  47. package/lib/typescript/jotai/utils/jotaiStateStore.d.ts.map +0 -1
  48. package/lib/typescript/jotai/utils/snapshotProvider.d.ts.map +0 -1
  49. package/lib/typescript/jotai/utils/testIDs.d.ts.map +0 -1
  50. package/lib/typescript/jotai/utils/watchAtoms.d.ts.map +0 -1
  51. package/lib/typescript/jotai/utils/writeSemantics.d.ts.map +0 -1
  52. package/lib/typescript/preset.d.ts.map +0 -1
@@ -1,67 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.useJotaiAtomChanges = useJotaiAtomChanges;
7
- var _react = require("react");
8
- var _sharedUi = require("@buoy-gg/shared-ui");
9
- var _jotaiStateStore = require("../utils/jotaiStateStore");
10
- /**
11
- * Hook for consuming Jotai atom changes from the store
12
- *
13
- * The subscribe / filter / pause / clear plumbing is shared `useChangeLog`;
14
- * this file owns the jotai-specific filter, stats and atom registry.
15
- */
16
-
17
- function filterChanges(changes, filter) {
18
- let filtered = changes;
19
- if (filter.searchText) {
20
- const search = filter.searchText.toLowerCase();
21
- filtered = filtered.filter(c => c.atomLabel.toLowerCase().includes(search) || c.valuePreview.toLowerCase().includes(search) || c.changedKeys.some(k => k.toLowerCase().includes(search)));
22
- }
23
- if (filter.atomLabels && filter.atomLabels.length > 0) {
24
- filtered = filtered.filter(c => filter.atomLabels.includes(c.atomLabel));
25
- }
26
- if (filter.onlyWithChanges) {
27
- filtered = filtered.filter(c => c.hasValueChange);
28
- }
29
- return filtered;
30
- }
31
- const readAtoms = () => _jotaiStateStore.jotaiStateStore.getAtoms();
32
- const subscribeToAtoms = listener => _jotaiStateStore.jotaiStateStore.subscribeToAtoms(listener);
33
- function useJotaiAtomChanges() {
34
- const log = (0, _sharedUi.useChangeLog)(_jotaiStateStore.jotaiStateStore, {
35
- filterItems: filterChanges
36
- });
37
- const atoms = (0, _sharedUi.useSubscribedValue)(readAtoms, subscribeToAtoms);
38
- const stats = (0, _react.useMemo)(() => {
39
- const total = log.items.length;
40
- const withChanges = log.items.filter(c => c.hasValueChange).length;
41
- return {
42
- totalChanges: total,
43
- changesWithValueChange: withChanges,
44
- changesWithoutValueChange: total - withChanges,
45
- atomCount: atoms.length
46
- };
47
- }, [log.items, atoms]);
48
- const atomLabels = (0, _react.useMemo)(() => {
49
- return _jotaiStateStore.jotaiStateStore.getUniqueAtomLabels();
50
- }, [atoms]);
51
- const getChangeById = (0, _react.useCallback)(id => {
52
- return _jotaiStateStore.jotaiStateStore.getAtomChangeById(id);
53
- }, []);
54
- return {
55
- atomChanges: log.items,
56
- filteredChanges: log.filteredItems,
57
- filter: log.filter,
58
- setFilter: log.setFilter,
59
- stats,
60
- atoms,
61
- clearChanges: log.clear,
62
- isEnabled: log.isEnabled,
63
- toggleCapture: log.toggleCapture,
64
- atomLabels,
65
- getChangeById
66
- };
67
- }
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.useJotaiAtomChanges=useJotaiAtomChanges;var _react=require("react"),_sharedUi=require("@buoy-gg/shared-ui"),_jotaiStateStore=require("../utils/jotaiStateStore");function filterChanges(e,t){let a=e;if(t.searchText){const e=t.searchText.toLowerCase();a=a.filter(t=>t.atomLabel.toLowerCase().includes(e)||t.valuePreview.toLowerCase().includes(e)||t.changedKeys.some(t=>t.toLowerCase().includes(e)))}return t.atomLabels&&t.atomLabels.length>0&&(a=a.filter(e=>t.atomLabels.includes(e.atomLabel))),t.onlyWithChanges&&(a=a.filter(e=>e.hasValueChange)),a}const readAtoms=()=>_jotaiStateStore.jotaiStateStore.getAtoms(),subscribeToAtoms=e=>_jotaiStateStore.jotaiStateStore.subscribeToAtoms(e);function useJotaiAtomChanges(){const e=(0,_sharedUi.useChangeLog)(_jotaiStateStore.jotaiStateStore,{filterItems:filterChanges}),t=(0,_sharedUi.useSubscribedValue)(readAtoms,subscribeToAtoms),a=(0,_react.useMemo)(()=>{const a=e.items.length,o=e.items.filter(e=>e.hasValueChange).length;return{totalChanges:a,changesWithValueChange:o,changesWithoutValueChange:a-o,atomCount:t.length}},[e.items,t]),o=(0,_react.useMemo)(()=>_jotaiStateStore.jotaiStateStore.getUniqueAtomLabels(),[t]),s=(0,_react.useCallback)(e=>_jotaiStateStore.jotaiStateStore.getAtomChangeById(e),[]);return{atomChanges:e.items,filteredChanges:e.filteredItems,filter:e.filter,setFilter:e.setFilter,stats:a,atoms:t,clearChanges:_jotaiStateStore.jotaiStateStore.clearAtomChanges.bind(_jotaiStateStore.jotaiStateStore),isEnabled:e.isEnabled,toggleCapture:e.toggleCapture,atomLabels:o,getChangeById:s}}
@@ -1,149 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.jotaiSyncAdapter = exports.VALUE_ON_DEVICE = void 0;
7
- var _jotaiStateStore = require("../utils/jotaiStateStore");
8
- var _sharedUi = require("@buoy-gg/shared-ui");
9
- /**
10
- * Sentinel used in WIRE-FORM snapshots in place of raw prevValue/nextValue.
11
- * Same hazard as redux/zustand: up to 200 changes × 2 values on every
12
- * snapshot. Dashboards fetch values via `getChangeDetail`.
13
- */
14
- const VALUE_ON_DEVICE = exports.VALUE_ON_DEVICE = (0, _sharedUi.onDeviceMarker)("Value", "Atom values stay on the device — fetched on demand via getChangeDetail / getAtomValue.");
15
- /**
16
- * Per-change wire cache — the store prepends and never mutates a recorded
17
- * change, so identity is a correct, self-invalidating key.
18
- */
19
- const toWireChange = (0, _sharedUi.memoByIdentity)(change => ({
20
- ...change,
21
- prevValue: VALUE_ON_DEVICE,
22
- nextValue: VALUE_ON_DEVICE
23
- }));
24
- const toWireValue = value => (0, _sharedUi.toWire)(value, VALUE_ON_DEVICE);
25
-
26
- /**
27
- * Sync adapter for the jotai tool, consumed by @buoy-gg/external-sync's
28
- * `useExternalSync`.
29
- *
30
- * Atom changes are captured by watchAtoms(), independent of whether a
31
- * dashboard is watching — subscribing here only streams what the store
32
- * already records. The snapshot carries both the change timeline and the
33
- * atom registry (with each atom's current value) so the dashboard can render
34
- * the atom browser.
35
- *
36
- * v2: change-log prevValue/nextValue are replaced with on-device markers.
37
- * Dashboards fetch values via `getChangeDetail`. Oversized / unserializable
38
- * `atoms[].currentValue` uses the same marker and is fetched via `getAtomValue`.
39
- */
40
- const jotaiSyncAdapter = exports.jotaiSyncAdapter = (0, _sharedUi.createSyncAdapter)({
41
- version: 2,
42
- getSnapshot: () => ({
43
- changes: _jotaiStateStore.jotaiStateStore.getAtomChanges().map(toWireChange),
44
- atoms: _jotaiStateStore.jotaiStateStore.getAtomSnapshots().map(atom => ({
45
- ...atom,
46
- currentValue: toWireValue(atom.currentValue)
47
- }))
48
- }),
49
- subscribe: [onChange => _jotaiStateStore.jotaiStateStore.subscribe(onChange), onChange => _jotaiStateStore.jotaiStateStore.subscribeToAtoms(onChange)],
50
- actions: {
51
- clearEvents: () => {
52
- _jotaiStateStore.jotaiStateStore.clearAtomChanges();
53
- },
54
- /**
55
- * On-demand detail: one change's real prevValue/nextValue.
56
- * The per-snapshot stream stays value-free; this is the explicit,
57
- * size-guarded channel for the detail pane.
58
- */
59
- getChangeDetail: (0, _sharedUi.detailByIdAction)(id => _jotaiStateStore.jotaiStateStore.getAtomChangeById(id), (change, cap) => ({
60
- prevValue: cap(change.prevValue),
61
- nextValue: cap(change.nextValue)
62
- })),
63
- /**
64
- * On-demand current value for one atom. Wire-form snapshots replace
65
- * oversized / unserializable `currentValue` with VALUE_ON_DEVICE.
66
- */
67
- getAtomValue: params => {
68
- const {
69
- label
70
- } = (0, _sharedUi.readParams)(params);
71
- if (!label) return {
72
- found: false,
73
- reason: "missing label"
74
- };
75
- const atom = _jotaiStateStore.jotaiStateStore.getAtom(label);
76
- if (!atom) return {
77
- found: false,
78
- reason: "unknown label"
79
- };
80
- let currentValue;
81
- try {
82
- currentValue = atom.getValue();
83
- } catch {
84
- currentValue = undefined;
85
- }
86
- try {
87
- JSON.stringify(currentValue);
88
- } catch {
89
- return {
90
- found: true,
91
- label,
92
- currentValue: VALUE_ON_DEVICE
93
- };
94
- }
95
- return {
96
- found: true,
97
- label,
98
- currentValue: (0, _sharedUi.capDetail)(currentValue, _sharedUi.WIRE_DETAIL_LIMIT_BYTES)
99
- };
100
- },
101
- /**
102
- * Compact current-value reader for a remote driver (MCP/LLM): registered
103
- * atoms with light metadata; each atom's `currentValue` stays on the device
104
- * unless `includeValues` is set. `writable` tells the caller which atoms
105
- * can be set via `setAtom`.
106
- */
107
- listAtoms: params => {
108
- const {
109
- includeValues = false,
110
- limit
111
- } = (0, _sharedUi.readParams)(params);
112
- const all = _jotaiStateStore.jotaiStateStore.getAtoms();
113
- const capped = typeof limit === "number" && limit > 0 ? all.slice(0, limit) : all;
114
- const atoms = capped.map(a => {
115
- let value;
116
- if (includeValues) {
117
- try {
118
- value = a.getValue();
119
- } catch {
120
- value = undefined;
121
- }
122
- }
123
- return {
124
- label: a.label,
125
- changes: a.changeCount,
126
- writable: typeof a.setValue === "function",
127
- ...(includeValues ? {
128
- currentValue: value
129
- } : {})
130
- };
131
- });
132
- return {
133
- atoms,
134
- total: all.length,
135
- returned: atoms.length,
136
- includedValues: includeValues
137
- };
138
- },
139
- /** Write a value to a writable atom (throws for unknown/read-only atoms). */
140
- setAtom: params => {
141
- const {
142
- label,
143
- value
144
- } = (0, _sharedUi.readParams)(params);
145
- if (!label) throw new Error("setAtom requires a `label`");
146
- _jotaiStateStore.jotaiStateStore.setAtomValue(label, value);
147
- }
148
- }
149
- });
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.jotaiSyncAdapter=exports.VALUE_ON_DEVICE=void 0;var _jotaiStateStore=require("../utils/jotaiStateStore"),_sharedUi=require("@buoy-gg/shared-ui");const VALUE_ON_DEVICE=exports.VALUE_ON_DEVICE=(0,_sharedUi.onDeviceMarker)("Value","Atom values stay on the device — fetched on demand via getChangeDetail / getAtomValue."),isPlainObj=e=>!!e&&"object"==typeof e&&!Array.isArray(e);function deepMergeInto(e,t){if(!isPlainObj(e)||!isPlainObj(t))return t;const a={...e};for(const[r,o]of Object.entries(t))a[r]=r in e?deepMergeInto(e[r],o):o;return a}function touchesList(e,t){let a=e;for(const e of t.split(".")){if(!isPlainObj(a))return!1;if(!(e in a))return!1;a=a[e]}return Array.isArray(a)?a.length>0:!!a&&"object"==typeof a}const toWireChange=(0,_sharedUi.memoByIdentity)(e=>({...e,prevValue:VALUE_ON_DEVICE,nextValue:VALUE_ON_DEVICE})),toWireValue=e=>(0,_sharedUi.toWire)(e,VALUE_ON_DEVICE),jotaiSyncAdapter=exports.jotaiSyncAdapter=(0,_sharedUi.createSyncAdapter)({version:2,getSnapshot:()=>({changes:_jotaiStateStore.jotaiStateStore.getAtomChanges().map(toWireChange),atoms:_jotaiStateStore.jotaiStateStore.getAtomSnapshots().map(e=>({...e,currentValue:toWireValue(e.currentValue)}))}),subscribe:[e=>_jotaiStateStore.jotaiStateStore.subscribe(e),e=>_jotaiStateStore.jotaiStateStore.subscribeToAtoms(e)],actions:{clearEvents:()=>{_jotaiStateStore.jotaiStateStore.clearAtomChanges()},getChangeDetail:(0,_sharedUi.detailByIdAction)(e=>_jotaiStateStore.jotaiStateStore.getAtomChangeById(e),(e,t)=>({prevValue:t(e.prevValue),nextValue:t(e.nextValue)})),getAtomValue:e=>{const{label:t,path:a}=(0,_sharedUi.readParams)(e);if(!t)return{found:!1,reason:"missing label"};const r=_jotaiStateStore.jotaiStateStore.getAtom(t);if(!r)return{found:!1,reason:"unknown label"};let o;try{o=r.getValue()}catch{o=void 0}try{JSON.stringify(o)}catch{return{found:!0,label:t,currentValue:VALUE_ON_DEVICE}}if(a){const e=(0,_sharedUi.readPath)(o,a);return e.ok?{found:!0,label:t,path:a,shape:(0,_sharedUi.shapeSummary)(e.value),currentValue:e.value}:{found:!1,reason:e.error}}return{found:!0,label:t,...(0,_sharedUi.shapeSummary)(o)?{shape:(0,_sharedUi.shapeSummary)(o)}:{},currentValue:(0,_sharedUi.capDetail)(o,_sharedUi.WIRE_DETAIL_LIMIT_BYTES)}},listAtoms:e=>{const{includeValues:t=!1,limit:a}=(0,_sharedUi.readParams)(e),r=_jotaiStateStore.jotaiStateStore.getAtoms(),o=("number"==typeof a&&a>0?r.slice(0,a):r).map(e=>{let a,r;if(t)try{a=e.getValue()}catch{a=void 0}try{r=(0,_sharedUi.shapeSummary)(e.getValue())}catch{r=void 0}return{label:e.label,changes:e.changeCount,writable:"function"==typeof e.setValue,...r?{shape:r}:{},...t?{currentValue:a}:{}}});return{atoms:o,total:r.length,returned:o.length,includedValues:t}},setAtom:e=>{const{label:t,value:a,merge:r,path:o,force:i}=(0,_sharedUi.readParams)(e);if(!t)throw new Error("setAtom requires a `label`");const s=_jotaiStateStore.jotaiStateStore.getAtom(t);let n;try{n=s?.getValue()}catch{n=void 0}let u=a,l=[],d=[];const h=!0===r||void 0!==o;if(void 0!==o){if(void 0===a)return{ok:!1,error:`Refused: sent \`path\` without \`value\`. Say what to set "${o}" to.`};const e=(0,_sharedUi.patchFromPath)(n,o,a);if(!e.ok)return{ok:!1,error:`Refused: ${e.error}`};u=e.patch}if(!i&&h&&void 0!==n){const e=(0,_sharedUi.expandKeyedArrays)(n,u);if(e.error)return{ok:!1,error:`Refused: ${e.error}`};u=e.patch,l=e.notes,d=(0,_sharedUi.unshapedLists)(n).filter(e=>touchesList(u,e));const t=(0,_sharedUi.typedEditViolations)(n,u,{noun:"atom"});if(t.length){const e=(0,_sharedUi.suggestedPatch)(u,t);return{ok:!1,error:(0,_sharedUi.refusalText)(t,"atom",e),violations:t,...e?{suggestedValue:e}:{}}}}return _jotaiStateStore.jotaiStateStore.setAtomValue(t,h?deepMergeInto(n,u):u),{ok:!0,...void 0!==o?{path:o}:{},...l.length?{keyed:l}:{},...d.length?{unchecked:d.map(e=>`\`${e}\` was empty, so nothing knows what its items look like and the shape of what you added was NOT checked.`)}:{}}}}});
@@ -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:!0}),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(e){return(0,_sharedUi.entityColor)(e,ATOM_COLORS)}function formatValuePreview(e,t=40){return(0,_sharedUi.formatValuePreview)(e,{maxLength:t,functionLabel:"(fn)"})}const getValueDiffSummary=_sharedUi.shallowDiffSummary;class JotaiStateStore extends _sharedUi.ChangeLog{atoms=new Map;atomListeners=new Set;addAtomChange(e){if(!this.canRecord())return;const{atomLabel:t,prevValue:a,nextValue:s,category:o="write"}=e,r=a!==s,{summary:n,changedKeys:i,changedCount:l}=getValueDiffSummary(a,s),h=formatValuePreview(s),u={id:this.nextId(),atomLabel:t,timestamp:Date.now(),prevValue:a,nextValue:s,hasValueChange:r,category:o,changedKeys:i,changedKeysCount:l,diffSummary:n,valuePreview:h,isSlowUpdate:!1};this.push(u);const m=this.atoms.get(t);m&&m.changeCount++,this.notifyListeners(),this.notifyAtomListeners()}getAtomChanges(){return this.getAll()}getAtomChangeById(e){return this.getById(e)}clearAtomChanges(){for(const e of this.atoms.values())e.changeCount=0;this.clear(),this.notifyAtomListeners()}getAtomSnapshots(){return Array.from(this.atoms.values()).map(e=>{let t;try{t=e.getValue()}catch{t=void 0}return{label:e.label,changeCount:e.changeCount,color:e.color,currentValue:t,writeKind:e.writeKind}})}replaceFromSnapshot(e,t){if(!this.getEnabled())return;const a=new Map;for(const e of this.items){const t=e.prevValue;t&&!0===t.__buoyValueOnDevice||a.set(e.id,e)}const s=e.slice(0,this.max).map(e=>{const t=a.get(e.id),s=e.prevValue;return t&&!0===s?.__buoyValueOnDevice?{...e,prevValue:t.prevValue,nextValue:t.nextValue}:e}),o=new Map;for(const[e,t]of this.atoms)try{const a=t.getValue();a&&!0===a.__buoyValueOnDevice||o.set(e,a)}catch{}this.atoms=new Map(t.map(e=>{const t=e.currentValue,a=o.get(e.label),s=void 0!==a&&!0===t?.__buoyValueOnDevice?a:e.currentValue;return[e.label,{label:e.label,changeCount:e.changeCount,color:e.color,getValue:()=>s,writeKind:e.writeKind}]})),this.setItems(s),this.notifyAtomListeners()}hydrateChange(e,t){this.hydrateById(e,t)}hydrateAtomValue(e,t){const a=this.atoms.get(e);a&&(this.atoms.set(e,{...a,getValue:()=>t}),this.notifyAtomListeners())}registerAtom(e,t,a,s){if(this.atoms.has(e))return;const o={label:e,changeCount:0,color:getAtomColor(e),getValue:t,setValue:a,writeKind:s};this.atoms.set(e,o),this.notifyAtomListeners()}setAtomValue(e,t){const a=this.atoms.get(e);if(!a)throw new Error(`Atom "${e}" is not registered`);if(!a.setValue)throw new Error(`Atom "${e}" is read-only (derived) — cannot set`);a.setValue(t)}unregisterAtom(e){this.atoms.delete(e),this.notifyAtomListeners()}getAtoms(){return Array.from(this.atoms.values())}getAtom(e){return this.atoms.get(e)}getAtomColor(e){return this.atoms.get(e)?.color??getAtomColor(e)}filterAtomChanges(e){let t=this.getAll();if(e.searchText){const a=e.searchText.toLowerCase();t=t.filter(e=>e.atomLabel.toLowerCase().includes(a)||e.valuePreview.toLowerCase().includes(a)||e.changedKeys.some(e=>e.toLowerCase().includes(a)))}return e.atomLabels&&e.atomLabels.length>0&&(t=t.filter(t=>e.atomLabels.includes(t.atomLabel))),e.onlyWithChanges&&(t=t.filter(e=>e.hasValueChange)),t}getStats(){const e=this.items.length,t=this.items.filter(e=>e.hasValueChange).length;return{totalChanges:e,changesWithValueChange:t,changesWithoutValueChange:e-t,atomCount:this.atoms.size}}getUniqueAtomLabels(){return Array.from(this.atoms.keys()).sort()}setMaxChanges(e){this.setMax(e)}subscribeToAtoms(e){return this.atomListeners.add(e),()=>this.atomListeners.delete(e)}notifyAtomListeners(){const e=this.getAtoms();this.atomListeners.forEach(t=>t(e))}}const jotaiStateStore=exports.jotaiStateStore=new JotaiStateStore;