@buoy-gg/jotai 5.0.0 → 6.0.1
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 +65 -0
- package/lib/commonjs/jotai/components/JotaiAtomChangeItem.js +10 -5
- package/lib/commonjs/jotai/components/JotaiModal.js +13 -8
- package/lib/module/jotai/components/JotaiAtomChangeItem.js +10 -5
- package/lib/module/jotai/components/JotaiModal.js +13 -8
- package/lib/typescript/jotai/components/JotaiAtomChangeItem.d.ts +1 -1
- package/lib/typescript/jotai/components/JotaiAtomChangeItem.d.ts.map +1 -1
- package/lib/typescript/jotai/components/JotaiModal.d.ts.map +1 -1
- package/package.json +3 -3
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# @buoy-gg/jotai
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@buoy-gg/jotai) [](https://www.npmjs.com/package/@buoy-gg/jotai)
|
|
4
|
+
|
|
5
|
+
**Jotai devtools on the device — every atom write shows `prev → next`, with per-atom history and live values.**
|
|
6
|
+
|
|
7
|
+
Part of [Buoy](https://github.com/Buoy-gg/buoy) — devtools that live inside your React Native app. Install it and it auto-appears in the floating menu from [`@buoy-gg/core`](https://www.npmjs.com/package/@buoy-gg/core).
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @buoy-gg/core @buoy-gg/jotai
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
One `watchAtoms(store, atoms)` call at module scope — pass your Jotai store and a named map of atoms. No wrappers, no `atomWithDevTools`, no changes to existing atoms; observation is subscribe-only.
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { getDefaultStore } from 'jotai';
|
|
21
|
+
import { watchAtoms } from '@buoy-gg/jotai';
|
|
22
|
+
import { countAtom } from './atoms/count';
|
|
23
|
+
import { authAtom } from './atoms/auth';
|
|
24
|
+
import { cartAtom } from './atoms/cart';
|
|
25
|
+
|
|
26
|
+
watchAtoms(getDefaultStore(), {
|
|
27
|
+
countAtom,
|
|
28
|
+
authAtom,
|
|
29
|
+
cartAtom,
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Using a `<Provider>` with a custom store? Pass it instead:
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
const myStore = createStore();
|
|
37
|
+
watchAtoms(myStore, { countAtom, authAtom });
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Registered atoms appear in the Jotai tool inside the floating menu.
|
|
41
|
+
|
|
42
|
+
## What you get
|
|
43
|
+
|
|
44
|
+
- **`prev → next` on every event** — see `0 → 5` or `null → {name, email}` at a glance, no tapping in for simple updates
|
|
45
|
+
- **Live Atoms tab** — every registered atom with its current value, value type, and change count, updated in real time
|
|
46
|
+
- **Per-atom history** — jump from any atom straight to its filtered event timeline to trace one piece of state through a flow
|
|
47
|
+
- **No-change flags** — writes that fired but didn't actually change the value are called out
|
|
48
|
+
- **Three-tab detail view** — change metadata, full value tree, and a side-by-side before/after diff
|
|
49
|
+
- **Atom color coding** — each atom keeps a consistent color across tabs and detail views
|
|
50
|
+
- **Search & filter** — find events by atom name or value content; hide noisy atoms everywhere at once
|
|
51
|
+
- **Recording toggle** — pause atom capture when you need to focus, resume when ready
|
|
52
|
+
|
|
53
|
+
## Desktop & AI
|
|
54
|
+
|
|
55
|
+
The same live session streams to [Buoy Desktop](https://github.com/Buoy-gg/Buoy-Desktop) (free, macOS/Windows/Linux) and to Claude Code or Cursor via the [Buoy MCP server](https://buoy.gg/buoy/latest/docs/mcp).
|
|
56
|
+
|
|
57
|
+
## Free vs Pro
|
|
58
|
+
|
|
59
|
+
Every tool is free. [Pro](https://buoy.gg/pricing) unlocks production builds, the MCP server, and unlimited capture. Every weekend, Pro features unlock free for everyone.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
📚 [Full docs](https://buoy.gg/buoy/latest/docs/tools/jotai) · [All Buoy tools](https://github.com/Buoy-gg/buoy)
|
|
64
|
+
|
|
65
|
+
Proprietary software. © Buoy LLC. [Terms](https://buoy.gg/terms)
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.JotaiAtomChangeItem =
|
|
6
|
+
exports.JotaiAtomChangeItem = void 0;
|
|
7
|
+
var _react = require("react");
|
|
7
8
|
var _reactNative = require("react-native");
|
|
8
9
|
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
9
10
|
var _jotaiStateStore = require("../utils/jotaiStateStore");
|
|
@@ -58,7 +59,10 @@ function getPrimaryText(change) {
|
|
|
58
59
|
}
|
|
59
60
|
return change.valuePreview || "atom()";
|
|
60
61
|
}
|
|
61
|
-
|
|
62
|
+
|
|
63
|
+
// Memo justified by profiling (tool-bench 2026-07-06) — see
|
|
64
|
+
// ZustandStateChangeItem (same pattern).
|
|
65
|
+
const JotaiAtomChangeItem = exports.JotaiAtomChangeItem = /*#__PURE__*/(0, _react.memo)(function JotaiAtomChangeItem({
|
|
62
66
|
change,
|
|
63
67
|
onPress
|
|
64
68
|
}) {
|
|
@@ -67,7 +71,6 @@ function JotaiAtomChangeItem({
|
|
|
67
71
|
const sublabel = getSublabel(change);
|
|
68
72
|
const primaryText = getPrimaryText(change);
|
|
69
73
|
const badgeText = getBadgeText(change);
|
|
70
|
-
const relativeTime = (0, _sharedUi.useRelativeTime)(change.timestamp);
|
|
71
74
|
const customBadge = change.hasValueChange ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
72
75
|
style: styles.badgeContainer,
|
|
73
76
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
@@ -88,14 +91,16 @@ function JotaiAtomChangeItem({
|
|
|
88
91
|
statusLabel: statusLabel,
|
|
89
92
|
statusSublabel: sublabel,
|
|
90
93
|
primaryText: primaryText,
|
|
91
|
-
bottomRightText:
|
|
94
|
+
bottomRightText: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.RelativeTime, {
|
|
95
|
+
timestamp: change.timestamp
|
|
96
|
+
}),
|
|
92
97
|
customBadge: customBadge,
|
|
93
98
|
badgeText: customBadge ? undefined : badgeText,
|
|
94
99
|
badgeColor: statusColor,
|
|
95
100
|
showChevron: true,
|
|
96
101
|
onPress: () => onPress(change)
|
|
97
102
|
});
|
|
98
|
-
}
|
|
103
|
+
});
|
|
99
104
|
const styles = _reactNative.StyleSheet.create({
|
|
100
105
|
badgeContainer: {
|
|
101
106
|
flexDirection: "row",
|
|
@@ -352,10 +352,10 @@ function JotaiModal({
|
|
|
352
352
|
contentContainerStyle: styles.listContent,
|
|
353
353
|
showsVerticalScrollIndicator: true,
|
|
354
354
|
removeClippedSubviews: true,
|
|
355
|
-
initialNumToRender:
|
|
356
|
-
maxToRenderPerBatch:
|
|
357
|
-
windowSize:
|
|
358
|
-
|
|
355
|
+
initialNumToRender: 12,
|
|
356
|
+
maxToRenderPerBatch: 8,
|
|
357
|
+
windowSize: 5,
|
|
358
|
+
updateCellsBatchingPeriod: 50
|
|
359
359
|
}) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
360
360
|
style: styles.emptyState,
|
|
361
361
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Box, {
|
|
@@ -411,10 +411,10 @@ function JotaiModal({
|
|
|
411
411
|
contentContainerStyle: styles.listContent,
|
|
412
412
|
showsVerticalScrollIndicator: true,
|
|
413
413
|
removeClippedSubviews: true,
|
|
414
|
-
initialNumToRender:
|
|
415
|
-
maxToRenderPerBatch:
|
|
416
|
-
windowSize:
|
|
417
|
-
|
|
414
|
+
initialNumToRender: 12,
|
|
415
|
+
maxToRenderPerBatch: 8,
|
|
416
|
+
windowSize: 5,
|
|
417
|
+
updateCellsBatchingPeriod: 50
|
|
418
418
|
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(EventsEmptyState, {
|
|
419
419
|
isEnabled: isEnabled
|
|
420
420
|
})]
|
|
@@ -429,6 +429,10 @@ function JotaiModal({
|
|
|
429
429
|
onIndexChange: handleIndexChange
|
|
430
430
|
}) : null;
|
|
431
431
|
const footerHeight = selectedChange && activeChanges.length > 1 ? 68 : 0;
|
|
432
|
+
|
|
433
|
+
// The change lists own their scrolling so FlatList virtualization works
|
|
434
|
+
// (see ZustandModal — same pattern, same tool-bench evidence).
|
|
435
|
+
const listOwnsScroll = !showFilters && !(selectedChange && selectedChangeIndex !== null) && (selectedHistoryAtom != null || activeTab === "events");
|
|
432
436
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.TickProvider, {
|
|
433
437
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.JsModal, {
|
|
434
438
|
visible: visible,
|
|
@@ -443,6 +447,7 @@ function JotaiModal({
|
|
|
443
447
|
enablePersistence: true,
|
|
444
448
|
initialMode: "bottomSheet",
|
|
445
449
|
enableGlitchEffects: true,
|
|
450
|
+
disableScrollWrapper: listOwnsScroll,
|
|
446
451
|
styles: {},
|
|
447
452
|
footer: footerNode,
|
|
448
453
|
footerHeight: footerHeight,
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* Mirrors ZustandStateChangeItem.tsx — uses shared CompactRow for consistent styling
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { memo } from "react";
|
|
9
10
|
import { View, Text, StyleSheet } from "react-native";
|
|
10
|
-
import { CompactRow, buoyColors, Zap,
|
|
11
|
+
import { CompactRow, buoyColors, Zap, RelativeTime } from "@buoy-gg/shared-ui";
|
|
11
12
|
import { jotaiStateStore } from "../utils/jotaiStateStore";
|
|
12
13
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
14
|
function getStatusLabel(category, atomLabel) {
|
|
@@ -54,7 +55,10 @@ function getPrimaryText(change) {
|
|
|
54
55
|
}
|
|
55
56
|
return change.valuePreview || "atom()";
|
|
56
57
|
}
|
|
57
|
-
|
|
58
|
+
|
|
59
|
+
// Memo justified by profiling (tool-bench 2026-07-06) — see
|
|
60
|
+
// ZustandStateChangeItem (same pattern).
|
|
61
|
+
export const JotaiAtomChangeItem = /*#__PURE__*/memo(function JotaiAtomChangeItem({
|
|
58
62
|
change,
|
|
59
63
|
onPress
|
|
60
64
|
}) {
|
|
@@ -63,7 +67,6 @@ export function JotaiAtomChangeItem({
|
|
|
63
67
|
const sublabel = getSublabel(change);
|
|
64
68
|
const primaryText = getPrimaryText(change);
|
|
65
69
|
const badgeText = getBadgeText(change);
|
|
66
|
-
const relativeTime = useRelativeTime(change.timestamp);
|
|
67
70
|
const customBadge = change.hasValueChange ? /*#__PURE__*/_jsxs(View, {
|
|
68
71
|
style: styles.badgeContainer,
|
|
69
72
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
@@ -84,14 +87,16 @@ export function JotaiAtomChangeItem({
|
|
|
84
87
|
statusLabel: statusLabel,
|
|
85
88
|
statusSublabel: sublabel,
|
|
86
89
|
primaryText: primaryText,
|
|
87
|
-
bottomRightText:
|
|
90
|
+
bottomRightText: /*#__PURE__*/_jsx(RelativeTime, {
|
|
91
|
+
timestamp: change.timestamp
|
|
92
|
+
}),
|
|
88
93
|
customBadge: customBadge,
|
|
89
94
|
badgeText: customBadge ? undefined : badgeText,
|
|
90
95
|
badgeColor: statusColor,
|
|
91
96
|
showChevron: true,
|
|
92
97
|
onPress: () => onPress(change)
|
|
93
98
|
});
|
|
94
|
-
}
|
|
99
|
+
});
|
|
95
100
|
const styles = StyleSheet.create({
|
|
96
101
|
badgeContainer: {
|
|
97
102
|
flexDirection: "row",
|
|
@@ -348,10 +348,10 @@ export function JotaiModal({
|
|
|
348
348
|
contentContainerStyle: styles.listContent,
|
|
349
349
|
showsVerticalScrollIndicator: true,
|
|
350
350
|
removeClippedSubviews: true,
|
|
351
|
-
initialNumToRender:
|
|
352
|
-
maxToRenderPerBatch:
|
|
353
|
-
windowSize:
|
|
354
|
-
|
|
351
|
+
initialNumToRender: 12,
|
|
352
|
+
maxToRenderPerBatch: 8,
|
|
353
|
+
windowSize: 5,
|
|
354
|
+
updateCellsBatchingPeriod: 50
|
|
355
355
|
}) : /*#__PURE__*/_jsxs(View, {
|
|
356
356
|
style: styles.emptyState,
|
|
357
357
|
children: [/*#__PURE__*/_jsx(Box, {
|
|
@@ -407,10 +407,10 @@ export function JotaiModal({
|
|
|
407
407
|
contentContainerStyle: styles.listContent,
|
|
408
408
|
showsVerticalScrollIndicator: true,
|
|
409
409
|
removeClippedSubviews: true,
|
|
410
|
-
initialNumToRender:
|
|
411
|
-
maxToRenderPerBatch:
|
|
412
|
-
windowSize:
|
|
413
|
-
|
|
410
|
+
initialNumToRender: 12,
|
|
411
|
+
maxToRenderPerBatch: 8,
|
|
412
|
+
windowSize: 5,
|
|
413
|
+
updateCellsBatchingPeriod: 50
|
|
414
414
|
}) : /*#__PURE__*/_jsx(EventsEmptyState, {
|
|
415
415
|
isEnabled: isEnabled
|
|
416
416
|
})]
|
|
@@ -425,6 +425,10 @@ export function JotaiModal({
|
|
|
425
425
|
onIndexChange: handleIndexChange
|
|
426
426
|
}) : null;
|
|
427
427
|
const footerHeight = selectedChange && activeChanges.length > 1 ? 68 : 0;
|
|
428
|
+
|
|
429
|
+
// The change lists own their scrolling so FlatList virtualization works
|
|
430
|
+
// (see ZustandModal — same pattern, same tool-bench evidence).
|
|
431
|
+
const listOwnsScroll = !showFilters && !(selectedChange && selectedChangeIndex !== null) && (selectedHistoryAtom != null || activeTab === "events");
|
|
428
432
|
return /*#__PURE__*/_jsxs(TickProvider, {
|
|
429
433
|
children: [/*#__PURE__*/_jsx(JsModal, {
|
|
430
434
|
visible: visible,
|
|
@@ -439,6 +443,7 @@ export function JotaiModal({
|
|
|
439
443
|
enablePersistence: true,
|
|
440
444
|
initialMode: "bottomSheet",
|
|
441
445
|
enableGlitchEffects: true,
|
|
446
|
+
disableScrollWrapper: listOwnsScroll,
|
|
442
447
|
styles: {},
|
|
443
448
|
footer: footerNode,
|
|
444
449
|
footerHeight: footerHeight,
|
|
@@ -8,6 +8,6 @@ interface JotaiAtomChangeItemProps {
|
|
|
8
8
|
change: JotaiAtomChange;
|
|
9
9
|
onPress: (change: JotaiAtomChange) => void;
|
|
10
10
|
}
|
|
11
|
-
export declare
|
|
11
|
+
export declare const JotaiAtomChangeItem: import("react").NamedExoticComponent<JotaiAtomChangeItemProps>;
|
|
12
12
|
export {};
|
|
13
13
|
//# sourceMappingURL=JotaiAtomChangeItem.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JotaiAtomChangeItem.d.ts","sourceRoot":"","sources":["../../../../src/jotai/components/JotaiAtomChangeItem.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"JotaiAtomChangeItem.d.ts","sourceRoot":"","sources":["../../../../src/jotai/components/JotaiAtomChangeItem.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAsB,MAAM,UAAU,CAAC;AAGpE,UAAU,wBAAwB;IAChC,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;CAC5C;AAsDD,eAAO,MAAM,mBAAmB,gEAiC9B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JotaiModal.d.ts","sourceRoot":"","sources":["../../../../src/jotai/components/JotaiModal.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAwCH,OAAO,KAAK,EAAE,eAAe,EAAmB,MAAM,UAAU,CAAC;AAoBjE,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,OAAO,EACP,MAAM,EACN,UAAU,EACV,2BAAmC,GACpC,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"JotaiModal.d.ts","sourceRoot":"","sources":["../../../../src/jotai/components/JotaiModal.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAwCH,OAAO,KAAK,EAAE,eAAe,EAAmB,MAAM,UAAU,CAAC;AAoBjE,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,OAAO,EACP,MAAM,EACN,UAAU,EACV,2BAAmC,GACpC,EAAE,eAAe,sCAufjB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buoy-gg/jotai",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.1",
|
|
4
4
|
"description": "Jotai atom DevTools for React Native",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
],
|
|
27
27
|
"sideEffects": false,
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@buoy-gg/shared-ui": "
|
|
30
|
-
"@buoy-gg/license": "
|
|
29
|
+
"@buoy-gg/shared-ui": "6.0.1",
|
|
30
|
+
"@buoy-gg/license": "6.0.1"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"jotai": ">=2.0.0",
|