@buoy-gg/jotai 7.0.38 → 7.0.40

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 (50) hide show
  1. package/lib/commonjs/index.js +112 -1
  2. package/lib/commonjs/jotai/components/JotaiAtomBrowser.js +199 -1
  3. package/lib/commonjs/jotai/components/JotaiAtomChangeItem.js +85 -1
  4. package/lib/commonjs/jotai/components/JotaiAtomDetailContent.js +715 -1
  5. package/lib/commonjs/jotai/components/JotaiAtomValueDetailContent.js +444 -1
  6. package/lib/commonjs/jotai/components/JotaiEventFilterView.js +43 -1
  7. package/lib/commonjs/jotai/components/JotaiIcon.js +18 -1
  8. package/lib/commonjs/jotai/components/JotaiModal.js +743 -1
  9. package/lib/commonjs/jotai/hooks/useJotaiAtomChanges.js +69 -1
  10. package/lib/commonjs/jotai/sync/jotaiSyncAdapter.js +306 -1
  11. package/lib/commonjs/jotai/utils/jotaiStateStore.js +321 -1
  12. package/lib/commonjs/jotai/utils/snapshotProvider.js +215 -1
  13. package/lib/commonjs/jotai/utils/testIDs.js +90 -1
  14. package/lib/commonjs/jotai/utils/watchAtoms.js +162 -1
  15. package/lib/commonjs/jotai/utils/writeSemantics.js +47 -1
  16. package/lib/commonjs/preset.js +90 -1
  17. package/lib/module/index.js +85 -1
  18. package/lib/module/jotai/components/JotaiAtomBrowser.js +195 -1
  19. package/lib/module/jotai/components/JotaiAtomChangeItem.js +81 -1
  20. package/lib/module/jotai/components/JotaiAtomDetailContent.js +710 -1
  21. package/lib/module/jotai/components/JotaiAtomValueDetailContent.js +442 -1
  22. package/lib/module/jotai/components/JotaiEventFilterView.js +39 -1
  23. package/lib/module/jotai/components/JotaiIcon.js +11 -1
  24. package/lib/module/jotai/components/JotaiModal.js +738 -1
  25. package/lib/module/jotai/hooks/useJotaiAtomChanges.js +65 -1
  26. package/lib/module/jotai/sync/jotaiSyncAdapter.js +303 -1
  27. package/lib/module/jotai/utils/jotaiStateStore.js +317 -1
  28. package/lib/module/jotai/utils/snapshotProvider.js +214 -1
  29. package/lib/module/jotai/utils/testIDs.js +82 -1
  30. package/lib/module/jotai/utils/watchAtoms.js +157 -1
  31. package/lib/module/jotai/utils/writeSemantics.js +43 -1
  32. package/lib/module/preset.js +87 -1
  33. package/lib/typescript/index.d.ts.map +1 -0
  34. package/lib/typescript/jotai/components/JotaiAtomBrowser.d.ts.map +1 -0
  35. package/lib/typescript/jotai/components/JotaiAtomChangeItem.d.ts.map +1 -0
  36. package/lib/typescript/jotai/components/JotaiAtomDetailContent.d.ts.map +1 -0
  37. package/lib/typescript/jotai/components/JotaiAtomValueDetailContent.d.ts.map +1 -0
  38. package/lib/typescript/jotai/components/JotaiEventFilterView.d.ts.map +1 -0
  39. package/lib/typescript/jotai/components/JotaiIcon.d.ts.map +1 -0
  40. package/lib/typescript/jotai/components/JotaiModal.d.ts.map +1 -0
  41. package/lib/typescript/jotai/hooks/useJotaiAtomChanges.d.ts.map +1 -0
  42. package/lib/typescript/jotai/sync/jotaiSyncAdapter.d.ts.map +1 -0
  43. package/lib/typescript/jotai/types/index.d.ts.map +1 -0
  44. package/lib/typescript/jotai/utils/jotaiStateStore.d.ts.map +1 -0
  45. package/lib/typescript/jotai/utils/snapshotProvider.d.ts.map +1 -0
  46. package/lib/typescript/jotai/utils/testIDs.d.ts.map +1 -0
  47. package/lib/typescript/jotai/utils/watchAtoms.d.ts.map +1 -0
  48. package/lib/typescript/jotai/utils/writeSemantics.d.ts.map +1 -0
  49. package/lib/typescript/preset.d.ts.map +1 -0
  50. package/package.json +3 -3
@@ -1 +1,710 @@
1
- "use strict";import React,{memo,useState,useCallback,useMemo}from"react";import{View,Text,StyleSheet,ScrollView}from"react-native";import{buoyColors,night,EventStepperFooter,formatRelativeTime,parseValue,Info,Zap,FileText,Database,GitBranch,copyToClipboard,ProUpgradeModal,DiffModeTabs,CompareBar,DetailViewToggle,monoFont}from"@buoy-gg/shared-ui";import{DataViewer,SplitDiffViewer,TreeDiffViewer,diffThemes}from"@buoy-gg/shared-ui/dataViewer";import{useIsPro}from"@buoy-gg/license";import{jotaiStateStore}from"../utils/jotaiStateStore";import{JOTAI_TEST_ID,slugForTestID}from"../utils/testIDs";import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";const VIEW_CONFIGS=[{key:"change",label:"CHANGE",icon:FileText},{key:"value",label:"VALUE",icon:Database},{key:"diff",label:"DIFF",icon:GitBranch}],DIFF_DISABLED=["diff"];function getCategoryInfo(e){return"initial"===e?{label:"INITIAL",color:night.color.textTertiary,bgColor:night.color.textTertiary+"26"}:{label:"WRITE",color:buoyColors.success,bgColor:buoyColors.success+"26"}}function formatTime(e){const t=new Date(e);return`${t.getHours().toString().padStart(2,"0")}:${t.getMinutes().toString().padStart(2,"0")}:${t.getSeconds().toString().padStart(2,"0")}.${t.getMilliseconds().toString().padStart(3,"0")}`}function formatTimestamp(e){return new Date(e).toLocaleTimeString("en-US",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit"})}function getValueType(e){return null===e?"null":void 0===e?"undefined":Array.isArray(e)?"array":typeof e}function getChangeLabel(e){return"initial"===e?"INIT":"WRITE"}const DIFF_MODE_TABS=[{key:"tree",label:"TREE VIEW"},{key:"split",label:"SPLIT VIEW"}],JotaiAtomInfoView=memo(function({change:e}){const t=useIsPro(),[o,n]=useState(!1),i=getCategoryInfo(e.category),a=jotaiStateStore.getAtomColor(e.atomLabel),l=useCallback(async()=>{t?await copyToClipboard(e.nextValue):n(!0)},[e.nextValue,t]);return _jsxs(ScrollView,{style:infoStyles.container,contentContainerStyle:infoStyles.content,showsVerticalScrollIndicator:!1,testID:`${JOTAI_TEST_ID}-detail-scroll`,children:[_jsxs(View,{style:infoStyles.card,testID:`${JOTAI_TEST_ID}-info-card`,children:[_jsx(View,{style:infoStyles.sectionHeader,children:_jsxs(View,{style:infoStyles.sectionHeaderLeft,children:[_jsx(Info,{size:14,color:night.color.accent}),_jsx(Text,{style:infoStyles.sectionTitle,children:"CHANGE INFO"})]})}),_jsxs(View,{style:infoStyles.cardContent,children:[_jsxs(View,{style:infoStyles.infoRow,children:[_jsx(Text,{style:infoStyles.infoLabel,children:"Atom"}),_jsx(Text,{style:[infoStyles.infoValue,infoStyles.infoValueMono,{color:a}],numberOfLines:1,ellipsizeMode:"middle",testID:`${JOTAI_TEST_ID}-info-atom`,children:e.atomLabel})]}),_jsxs(View,{style:infoStyles.infoRow,children:[_jsx(Text,{style:infoStyles.infoLabel,children:"Time"}),_jsx(Text,{style:[infoStyles.infoValue,infoStyles.infoValueMono],testID:`${JOTAI_TEST_ID}-info-time`,children:formatTime(e.timestamp)})]}),_jsxs(View,{style:infoStyles.badgeRow,children:[_jsx(View,{style:[infoStyles.categoryBadge,{backgroundColor:i.bgColor}],children:_jsx(Text,{style:[infoStyles.categoryText,{color:i.color}],testID:`${JOTAI_TEST_ID}-info-category`,children:i.label})}),e.hasValueChange?_jsxs(View,{style:infoStyles.changesBadge,children:[_jsx(Zap,{size:10,color:buoyColors.success}),_jsx(Text,{style:infoStyles.changesText,testID:`${JOTAI_TEST_ID}-info-changes`,children:e.diffSummary||"changed"})]}):_jsx(View,{style:infoStyles.noChangesBadge,children:_jsx(Text,{style:infoStyles.noChangesText,testID:`${JOTAI_TEST_ID}-info-no-changes`,children:"no change"})})]})]})]}),e.changedKeys.length>0&&_jsxs(View,{style:infoStyles.card,testID:`${JOTAI_TEST_ID}-keys-card`,children:[_jsxs(View,{style:infoStyles.sectionHeader,children:[_jsxs(View,{style:infoStyles.sectionHeaderLeft,children:[_jsx(Zap,{size:14,color:night.color.accent}),_jsx(Text,{style:infoStyles.sectionTitle,children:"CHANGED KEYS"})]}),_jsx(View,{style:infoStyles.countBadge,children:_jsx(Text,{style:infoStyles.countText,testID:`${JOTAI_TEST_ID}-keys-count`,children:e.changedKeys.length})})]}),_jsx(View,{style:infoStyles.cardContent,children:_jsx(View,{style:infoStyles.keysContainer,children:e.changedKeys.map(e=>_jsx(View,{style:infoStyles.keyBadge,children:_jsx(Text,{style:infoStyles.keyText,testID:`${JOTAI_TEST_ID}-key-${slugForTestID(e)}`,children:e})},e))})})]}),_jsx(View,{style:infoStyles.actionsRow,children:_jsx(View,{style:[infoStyles.actionButton,infoStyles.actionButtonCopy],onTouchEnd:l,testID:`${JOTAI_TEST_ID}-copy-value`,accessibilityRole:"button",accessibilityLabel:"Copy value",children:_jsx(Text,{style:[infoStyles.actionButtonText,{color:night.color.accent}],children:"COPY VALUE"})})}),_jsx(ProUpgradeModal,{visible:o,onClose:()=>n(!1),featureName:"Copy"})]})});export function JotaiAtomDetailContent({change:e,changes:t,selectedIndex:o,onIndexChange:n,disableInternalFooter:i=!1,initialView:a="change"}){const[l,r]=useState(a),[s,c]=useState("tree"),d=t.length,g=jotaiStateStore.getAtomColor(e.atomLabel),f=getChangeLabel(e.category),y=getValueType(parseValue(e.nextValue)),h=d-o,u=o<d-1?o+1:null,x=null!==u?t[u]:null,m=h-1,T=useMemo(()=>({index:u??0,label:m>0?`#${m}`:"Initial",timestamp:x?formatTimestamp(x.timestamp):"",relativeTime:x?formatRelativeTime(new Date(x.timestamp)):"value",badge:x?_jsx(View,{style:[styles.changeBadgeSmall,{backgroundColor:`${jotaiStateStore.getAtomColor(x.atomLabel)}20`}],children:_jsx(Text,{style:[styles.changeTextSmall,{color:jotaiStateStore.getAtomColor(x.atomLabel)}],children:getChangeLabel(x.category)})}):void 0}),[u,x,m]),S=useMemo(()=>({index:o,label:`#${h}`,timestamp:formatTimestamp(e.timestamp),relativeTime:formatRelativeTime(new Date(e.timestamp)),badge:_jsx(View,{style:[styles.changeBadgeSmall,{backgroundColor:`${g}20`}],children:_jsx(Text,{style:[styles.changeTextSmall,{color:g}],children:f})})}),[o,e.timestamp,g,f,h]),p=useCallback(()=>{const t=parseValue(e.nextValue);return _jsxs(View,{style:styles.contentCard,testID:`${JOTAI_TEST_ID}-value-card`,children:[_jsxs(View,{style:styles.valueHeader,children:[_jsx(Text,{style:styles.valueLabel,children:"VALUE AFTER CHANGE"}),_jsxs(View,{style:styles.valueHeaderBadges,children:[_jsx(View,{style:[styles.changeBadge,{backgroundColor:`${g}20`}],children:_jsx(Text,{style:[styles.changeText,{color:g}],children:f})}),_jsx(View,{style:styles.typeBadge,children:_jsx(Text,{style:styles.typeText,testID:`${JOTAI_TEST_ID}-value-type`,children:y.toUpperCase()})})]})]}),_jsx(View,{style:infoStyles.dataViewerContainer,children:_jsx(DataViewer,{title:"",data:t,showTypeFilter:!0,rawMode:!0,initialExpanded:!0})})]})},[e.nextValue,g,f,y]),b=useCallback(()=>{const t=parseValue(e.prevValue),o=parseValue(e.nextValue);return"split"===s?_jsx(ScrollView,{style:{flex:1},showsVerticalScrollIndicator:!0,children:_jsx(SplitDiffViewer,{oldValue:t,newValue:o,theme:diffThemes.devToolsDefault,options:{hideLineNumbers:!1,disableWordDiff:!1,showDiffOnly:!1,compareMethod:"words",contextLines:3},showThemeName:!1})}):_jsx(TreeDiffViewer,{oldValue:t,newValue:o})},[e.prevValue,e.nextValue,s]),_=useCallback(()=>{n(Math.min(d-1,o+1))},[o,d,n]),I=useCallback(()=>{n(Math.max(0,o-1))},[o,n]),V=void 0===e.prevValue,w=!i&&d>1;return _jsxs(View,{style:styles.container,children:[_jsx(DetailViewToggle,{views:VIEW_CONFIGS,activeView:l,onViewChange:r,disabledKeys:V?DIFF_DISABLED:void 0,testIDPrefix:JOTAI_TEST_ID}),_jsxs(View,{style:[styles.contentArea,w&&styles.contentAreaFooterPad],children:["change"===l&&_jsx(JotaiAtomInfoView,{change:e}),"value"===l&&_jsx(View,{style:styles.stateContainer,children:p()}),"diff"===l&&_jsxs(View,{style:styles.diffContainer,testID:`${JOTAI_TEST_ID}-diff`,children:[_jsx(DiffModeTabs,{tabs:DIFF_MODE_TABS,activeTab:s,onTabChange:c,testIDPrefix:`${JOTAI_TEST_ID}-diff-tab`}),_jsx(CompareBar,{leftEvent:T,rightEvent:S}),_jsx(View,{style:styles.diffContent,testID:`${JOTAI_TEST_ID}-diff-content`,children:b()})]})]}),w&&_jsx(EventStepperFooter,{currentIndex:h-1,totalItems:d,onPrevious:_,onNext:I,itemLabel:"Change",subtitle:formatRelativeTime(new Date(e.timestamp)),absolute:!0})]})}export function JotaiAtomDetailFooter({change:e,changes:t,selectedIndex:o,onIndexChange:n}){const i=t.length;return i<=1?null:_jsx(EventStepperFooter,{currentIndex:i-o-1,totalItems:i,onPrevious:()=>n(Math.min(i-1,o+1)),onNext:()=>n(Math.max(0,o-1)),itemLabel:"Change",subtitle:formatRelativeTime(new Date(e.timestamp))})}const styles=StyleSheet.create({container:{flex:1,backgroundColor:"transparent"},contentArea:{flex:1},contentAreaFooterPad:{paddingBottom:96},stateContainer:{flex:1,padding:14},contentCard:{backgroundColor:night.color.surface,borderRadius:14,padding:14,borderWidth:1,borderColor:night.color.border},valueHeader:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",marginBottom:8},valueLabel:{fontSize:10,color:night.color.textSecondary,fontFamily:monoFont,letterSpacing:.5,fontWeight:"600"},valueHeaderBadges:{flexDirection:"row",alignItems:"center",gap:6},changeBadge:{paddingHorizontal:8,paddingVertical:2,borderRadius:4},changeText:{fontSize:9,fontWeight:"700",fontFamily:monoFont,letterSpacing:.3},typeBadge:{paddingHorizontal:8,paddingVertical:2,borderRadius:4,backgroundColor:night.color.surfaceElevated},typeText:{fontSize:9,fontWeight:"600",color:night.color.textTertiary,fontFamily:monoFont},valueBox:{backgroundColor:night.color.bg,borderRadius:6,borderWidth:1,borderColor:night.color.border,padding:8},valueText:{fontSize:12,color:night.color.text,fontFamily:monoFont,lineHeight:18},diffContainer:{flex:1},diffContent:{flex:1,paddingHorizontal:14},changeBadgeSmall:{paddingHorizontal:6,paddingVertical:1,borderRadius:3},changeTextSmall:{fontSize:8,fontWeight:"700",fontFamily:monoFont}}),infoStyles=StyleSheet.create({container:{flex:1},content:{padding:8,paddingBottom:100,gap:16},card:{backgroundColor:night.color.surface,borderRadius:6,borderWidth:1,borderColor:night.color.accentBorderStrong,overflow:"hidden",shadowColor:night.color.accent,shadowOffset:{width:0,height:0},shadowOpacity:.1,shadowRadius:6},sectionHeader:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",paddingHorizontal:12,paddingVertical:10,borderBottomWidth:1,borderBottomColor:night.color.accentBorder,backgroundColor:night.color.accentSoft},sectionHeaderLeft:{flexDirection:"row",alignItems:"center",gap:6},sectionTitle:{fontSize:12,fontWeight:"600",letterSpacing:.5,color:night.color.accent,fontFamily:monoFont},cardContent:{padding:14,gap:10},infoRow:{flexDirection:"row",alignItems:"center",justifyContent:"space-between"},infoLabel:{fontSize:11,color:night.color.textTertiary,fontWeight:"500"},infoValue:{fontSize:11,color:night.color.text,fontWeight:"500",flex:1,textAlign:"right",marginLeft:12},infoValueMono:{fontFamily:monoFont},badgeRow:{flexDirection:"row",flexWrap:"wrap",gap:8,marginTop:6},categoryBadge:{paddingHorizontal:8,paddingVertical:3,borderRadius:4},categoryText:{fontSize:9,fontWeight:"700",letterSpacing:.3},changesBadge:{flexDirection:"row",alignItems:"center",gap:4,paddingHorizontal:8,paddingVertical:3,borderRadius:4,backgroundColor:buoyColors.success+"26"},changesText:{fontSize:9,fontWeight:"600",color:buoyColors.success},noChangesBadge:{paddingHorizontal:8,paddingVertical:3,borderRadius:4,backgroundColor:night.color.surfaceElevated},noChangesText:{fontSize:9,fontWeight:"600",color:night.color.textTertiary},keysContainer:{flexDirection:"row",flexWrap:"wrap",gap:6},keyBadge:{paddingHorizontal:8,paddingVertical:4,borderRadius:4,backgroundColor:night.color.accentSoft,borderWidth:1,borderColor:night.color.accentBorder},keyText:{fontSize:11,fontWeight:"600",color:night.color.accent,fontFamily:monoFont},countBadge:{paddingHorizontal:8,paddingVertical:3,borderRadius:4,backgroundColor:night.color.accentFill},countText:{fontSize:9,fontWeight:"700",color:night.color.accent,fontFamily:monoFont},actionsRow:{flexDirection:"row",gap:12},actionButton:{paddingHorizontal:14,paddingVertical:8,borderRadius:8,borderWidth:1},actionButtonCopy:{borderColor:night.color.accentBorder,backgroundColor:night.color.accentSoft},actionButtonText:{fontSize:10,fontWeight:"700",fontFamily:monoFont,letterSpacing:.3},dataViewerContainer:{marginTop:-12,marginHorizontal:-12,marginBottom:-12}});
1
+ "use strict";
2
+
3
+ /**
4
+ * JotaiAtomDetailContent
5
+ *
6
+ * Detail view for a Jotai atom change with 3 view modes:
7
+ * - CHANGE: Atom label, timestamp, category, changed keys
8
+ * - VALUE: Full atom value after change
9
+ * - DIFF: Compare prev vs next value
10
+ *
11
+ * Mirrors ZustandStateDetailContent.tsx from @buoy-gg/zustand
12
+ */
13
+
14
+ import React, { memo, useState, useCallback, useMemo } from "react";
15
+ import { View, Text, StyleSheet, ScrollView } from "react-native";
16
+ import { buoyColors, night, EventStepperFooter, formatRelativeTime, parseValue, Info, Zap, FileText, Database, GitBranch, copyToClipboard, ProUpgradeModal, DiffModeTabs, CompareBar, DetailViewToggle, monoFont } from "@buoy-gg/shared-ui";
17
+ import { DataViewer, SplitDiffViewer, TreeDiffViewer, diffThemes } from "@buoy-gg/shared-ui/dataViewer";
18
+ import { useIsPro } from "@buoy-gg/license";
19
+ import { jotaiStateStore } from "../utils/jotaiStateStore";
20
+ import { JOTAI_TEST_ID, slugForTestID } from "../utils/testIDs";
21
+
22
+ // ---- View Toggle ----
23
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
24
+ const VIEW_CONFIGS = [{
25
+ key: "change",
26
+ label: "CHANGE",
27
+ icon: FileText
28
+ }, {
29
+ key: "value",
30
+ label: "VALUE",
31
+ icon: Database
32
+ }, {
33
+ key: "diff",
34
+ label: "DIFF",
35
+ icon: GitBranch
36
+ }];
37
+ const DIFF_DISABLED = ["diff"];
38
+
39
+ // ---- Helpers ----
40
+
41
+ function getCategoryInfo(category) {
42
+ switch (category) {
43
+ case "initial":
44
+ return {
45
+ label: "INITIAL",
46
+ color: night.color.textTertiary,
47
+ bgColor: night.color.textTertiary + "26"
48
+ };
49
+ default:
50
+ return {
51
+ label: "WRITE",
52
+ color: buoyColors.success,
53
+ bgColor: buoyColors.success + "26"
54
+ };
55
+ }
56
+ }
57
+ function formatTime(timestamp) {
58
+ const date = new Date(timestamp);
59
+ const h = date.getHours().toString().padStart(2, "0");
60
+ const m = date.getMinutes().toString().padStart(2, "0");
61
+ const s = date.getSeconds().toString().padStart(2, "0");
62
+ const ms = date.getMilliseconds().toString().padStart(3, "0");
63
+ return `${h}:${m}:${s}.${ms}`;
64
+ }
65
+ function formatTimestamp(timestamp) {
66
+ return new Date(timestamp).toLocaleTimeString("en-US", {
67
+ hour12: false,
68
+ hour: "2-digit",
69
+ minute: "2-digit",
70
+ second: "2-digit"
71
+ });
72
+ }
73
+ function getValueType(value) {
74
+ if (value === null) return "null";
75
+ if (value === undefined) return "undefined";
76
+ if (Array.isArray(value)) return "array";
77
+ return typeof value;
78
+ }
79
+ function getChangeLabel(category) {
80
+ return category === "initial" ? "INIT" : "WRITE";
81
+ }
82
+ const DIFF_MODE_TABS = [{
83
+ key: "tree",
84
+ label: "TREE VIEW"
85
+ }, {
86
+ key: "split",
87
+ label: "SPLIT VIEW"
88
+ }];
89
+
90
+ // ---- Change Info View (CHANGE tab) ----
91
+
92
+ const JotaiAtomInfoView = /*#__PURE__*/memo(function JotaiAtomInfoView({
93
+ change
94
+ }) {
95
+ const isPro = useIsPro();
96
+ const [showProModal, setShowProModal] = useState(false);
97
+ const categoryInfo = getCategoryInfo(change.category);
98
+ const atomColor = jotaiStateStore.getAtomColor(change.atomLabel);
99
+ const handleCopyValue = useCallback(async () => {
100
+ if (!isPro) {
101
+ setShowProModal(true);
102
+ return;
103
+ }
104
+ await copyToClipboard(change.nextValue);
105
+ }, [change.nextValue, isPro]);
106
+ return /*#__PURE__*/_jsxs(ScrollView, {
107
+ style: infoStyles.container,
108
+ contentContainerStyle: infoStyles.content,
109
+ showsVerticalScrollIndicator: false,
110
+ testID: `${JOTAI_TEST_ID}-detail-scroll`,
111
+ children: [/*#__PURE__*/_jsxs(View, {
112
+ style: infoStyles.card,
113
+ testID: `${JOTAI_TEST_ID}-info-card`,
114
+ children: [/*#__PURE__*/_jsx(View, {
115
+ style: infoStyles.sectionHeader,
116
+ children: /*#__PURE__*/_jsxs(View, {
117
+ style: infoStyles.sectionHeaderLeft,
118
+ children: [/*#__PURE__*/_jsx(Info, {
119
+ size: 14,
120
+ color: night.color.accent
121
+ }), /*#__PURE__*/_jsx(Text, {
122
+ style: infoStyles.sectionTitle,
123
+ children: "CHANGE INFO"
124
+ })]
125
+ })
126
+ }), /*#__PURE__*/_jsxs(View, {
127
+ style: infoStyles.cardContent,
128
+ children: [/*#__PURE__*/_jsxs(View, {
129
+ style: infoStyles.infoRow,
130
+ children: [/*#__PURE__*/_jsx(Text, {
131
+ style: infoStyles.infoLabel,
132
+ children: "Atom"
133
+ }), /*#__PURE__*/_jsx(Text, {
134
+ style: [infoStyles.infoValue, infoStyles.infoValueMono, {
135
+ color: atomColor
136
+ }],
137
+ numberOfLines: 1,
138
+ ellipsizeMode: "middle",
139
+ testID: `${JOTAI_TEST_ID}-info-atom`,
140
+ children: change.atomLabel
141
+ })]
142
+ }), /*#__PURE__*/_jsxs(View, {
143
+ style: infoStyles.infoRow,
144
+ children: [/*#__PURE__*/_jsx(Text, {
145
+ style: infoStyles.infoLabel,
146
+ children: "Time"
147
+ }), /*#__PURE__*/_jsx(Text, {
148
+ style: [infoStyles.infoValue, infoStyles.infoValueMono],
149
+ testID: `${JOTAI_TEST_ID}-info-time`,
150
+ children: formatTime(change.timestamp)
151
+ })]
152
+ }), /*#__PURE__*/_jsxs(View, {
153
+ style: infoStyles.badgeRow,
154
+ children: [/*#__PURE__*/_jsx(View, {
155
+ style: [infoStyles.categoryBadge, {
156
+ backgroundColor: categoryInfo.bgColor
157
+ }],
158
+ children: /*#__PURE__*/_jsx(Text, {
159
+ style: [infoStyles.categoryText, {
160
+ color: categoryInfo.color
161
+ }],
162
+ testID: `${JOTAI_TEST_ID}-info-category`,
163
+ children: categoryInfo.label
164
+ })
165
+ }), change.hasValueChange ? /*#__PURE__*/_jsxs(View, {
166
+ style: infoStyles.changesBadge,
167
+ children: [/*#__PURE__*/_jsx(Zap, {
168
+ size: 10,
169
+ color: buoyColors.success
170
+ }), /*#__PURE__*/_jsx(Text, {
171
+ style: infoStyles.changesText,
172
+ testID: `${JOTAI_TEST_ID}-info-changes`,
173
+ children: change.diffSummary || "changed"
174
+ })]
175
+ }) : /*#__PURE__*/_jsx(View, {
176
+ style: infoStyles.noChangesBadge,
177
+ children: /*#__PURE__*/_jsx(Text, {
178
+ style: infoStyles.noChangesText,
179
+ testID: `${JOTAI_TEST_ID}-info-no-changes`,
180
+ children: "no change"
181
+ })
182
+ })]
183
+ })]
184
+ })]
185
+ }), change.changedKeys.length > 0 && /*#__PURE__*/_jsxs(View, {
186
+ style: infoStyles.card,
187
+ testID: `${JOTAI_TEST_ID}-keys-card`,
188
+ children: [/*#__PURE__*/_jsxs(View, {
189
+ style: infoStyles.sectionHeader,
190
+ children: [/*#__PURE__*/_jsxs(View, {
191
+ style: infoStyles.sectionHeaderLeft,
192
+ children: [/*#__PURE__*/_jsx(Zap, {
193
+ size: 14,
194
+ color: night.color.accent
195
+ }), /*#__PURE__*/_jsx(Text, {
196
+ style: infoStyles.sectionTitle,
197
+ children: "CHANGED KEYS"
198
+ })]
199
+ }), /*#__PURE__*/_jsx(View, {
200
+ style: infoStyles.countBadge,
201
+ children: /*#__PURE__*/_jsx(Text, {
202
+ style: infoStyles.countText,
203
+ testID: `${JOTAI_TEST_ID}-keys-count`,
204
+ children: change.changedKeys.length
205
+ })
206
+ })]
207
+ }), /*#__PURE__*/_jsx(View, {
208
+ style: infoStyles.cardContent,
209
+ children: /*#__PURE__*/_jsx(View, {
210
+ style: infoStyles.keysContainer,
211
+ children: change.changedKeys.map(key => /*#__PURE__*/_jsx(View, {
212
+ style: infoStyles.keyBadge,
213
+ children: /*#__PURE__*/_jsx(Text, {
214
+ style: infoStyles.keyText,
215
+ testID: `${JOTAI_TEST_ID}-key-${slugForTestID(key)}`,
216
+ children: key
217
+ })
218
+ }, key))
219
+ })
220
+ })]
221
+ }), /*#__PURE__*/_jsx(View, {
222
+ style: infoStyles.actionsRow,
223
+ children: /*#__PURE__*/_jsx(View, {
224
+ style: [infoStyles.actionButton, infoStyles.actionButtonCopy],
225
+ onTouchEnd: handleCopyValue,
226
+ testID: `${JOTAI_TEST_ID}-copy-value`,
227
+ accessibilityRole: "button",
228
+ accessibilityLabel: "Copy value",
229
+ children: /*#__PURE__*/_jsx(Text, {
230
+ style: [infoStyles.actionButtonText, {
231
+ color: night.color.accent
232
+ }],
233
+ children: "COPY VALUE"
234
+ })
235
+ })
236
+ }), /*#__PURE__*/_jsx(ProUpgradeModal, {
237
+ visible: showProModal,
238
+ onClose: () => setShowProModal(false),
239
+ featureName: "Copy"
240
+ })]
241
+ });
242
+ });
243
+
244
+ // ---- Main Detail Content ----
245
+
246
+ export function JotaiAtomDetailContent({
247
+ change,
248
+ changes,
249
+ selectedIndex,
250
+ onIndexChange,
251
+ disableInternalFooter = false,
252
+ initialView = "change"
253
+ }) {
254
+ // View state (initialView seeds it once; taps own it afterwards)
255
+ const [activeView, setActiveView] = useState(initialView);
256
+ const [diffMode, setDiffMode] = useState("tree");
257
+ const totalChanges = changes.length;
258
+ const atomColor = jotaiStateStore.getAtomColor(change.atomLabel);
259
+ const changeLabel = getChangeLabel(change.category);
260
+ const valueType = getValueType(parseValue(change.nextValue));
261
+ const chronologicalNumber = totalChanges - selectedIndex;
262
+ const prevChangeIndex = selectedIndex < totalChanges - 1 ? selectedIndex + 1 : null;
263
+ const prevChange = prevChangeIndex !== null ? changes[prevChangeIndex] : null;
264
+ const prevChronologicalNumber = chronologicalNumber - 1;
265
+ const leftEvent = useMemo(() => ({
266
+ index: prevChangeIndex ?? 0,
267
+ label: prevChronologicalNumber > 0 ? `#${prevChronologicalNumber}` : "Initial",
268
+ timestamp: prevChange ? formatTimestamp(prevChange.timestamp) : "",
269
+ relativeTime: prevChange ? formatRelativeTime(new Date(prevChange.timestamp)) : "value",
270
+ badge: prevChange ? /*#__PURE__*/_jsx(View, {
271
+ style: [styles.changeBadgeSmall, {
272
+ backgroundColor: `${jotaiStateStore.getAtomColor(prevChange.atomLabel)}20`
273
+ }],
274
+ children: /*#__PURE__*/_jsx(Text, {
275
+ style: [styles.changeTextSmall, {
276
+ color: jotaiStateStore.getAtomColor(prevChange.atomLabel)
277
+ }],
278
+ children: getChangeLabel(prevChange.category)
279
+ })
280
+ }) : undefined
281
+ }), [prevChangeIndex, prevChange, prevChronologicalNumber]);
282
+ const rightEvent = useMemo(() => ({
283
+ index: selectedIndex,
284
+ label: `#${chronologicalNumber}`,
285
+ timestamp: formatTimestamp(change.timestamp),
286
+ relativeTime: formatRelativeTime(new Date(change.timestamp)),
287
+ badge: /*#__PURE__*/_jsx(View, {
288
+ style: [styles.changeBadgeSmall, {
289
+ backgroundColor: `${atomColor}20`
290
+ }],
291
+ children: /*#__PURE__*/_jsx(Text, {
292
+ style: [styles.changeTextSmall, {
293
+ color: atomColor
294
+ }],
295
+ children: changeLabel
296
+ })
297
+ })
298
+ }), [selectedIndex, change.timestamp, atomColor, changeLabel, chronologicalNumber]);
299
+ const renderValueView = useCallback(() => {
300
+ const parsed = parseValue(change.nextValue);
301
+ return /*#__PURE__*/_jsxs(View, {
302
+ style: styles.contentCard,
303
+ testID: `${JOTAI_TEST_ID}-value-card`,
304
+ children: [/*#__PURE__*/_jsxs(View, {
305
+ style: styles.valueHeader,
306
+ children: [/*#__PURE__*/_jsx(Text, {
307
+ style: styles.valueLabel,
308
+ children: "VALUE AFTER CHANGE"
309
+ }), /*#__PURE__*/_jsxs(View, {
310
+ style: styles.valueHeaderBadges,
311
+ children: [/*#__PURE__*/_jsx(View, {
312
+ style: [styles.changeBadge, {
313
+ backgroundColor: `${atomColor}20`
314
+ }],
315
+ children: /*#__PURE__*/_jsx(Text, {
316
+ style: [styles.changeText, {
317
+ color: atomColor
318
+ }],
319
+ children: changeLabel
320
+ })
321
+ }), /*#__PURE__*/_jsx(View, {
322
+ style: styles.typeBadge,
323
+ children: /*#__PURE__*/_jsx(Text, {
324
+ style: styles.typeText,
325
+ testID: `${JOTAI_TEST_ID}-value-type`,
326
+ children: valueType.toUpperCase()
327
+ })
328
+ })]
329
+ })]
330
+ }), /*#__PURE__*/_jsx(View, {
331
+ style: infoStyles.dataViewerContainer,
332
+ children: /*#__PURE__*/_jsx(DataViewer, {
333
+ title: "",
334
+ data: parsed,
335
+ showTypeFilter: true,
336
+ rawMode: true,
337
+ initialExpanded: true
338
+ })
339
+ })]
340
+ });
341
+ }, [change.nextValue, atomColor, changeLabel, valueType]);
342
+ const renderDiffContent = useCallback(() => {
343
+ const prevVal = parseValue(change.prevValue);
344
+ const nextVal = parseValue(change.nextValue);
345
+ if (diffMode === "split") {
346
+ return /*#__PURE__*/_jsx(ScrollView, {
347
+ style: {
348
+ flex: 1
349
+ },
350
+ showsVerticalScrollIndicator: true,
351
+ children: /*#__PURE__*/_jsx(SplitDiffViewer, {
352
+ oldValue: prevVal,
353
+ newValue: nextVal,
354
+ theme: diffThemes.devToolsDefault,
355
+ options: {
356
+ hideLineNumbers: false,
357
+ disableWordDiff: false,
358
+ showDiffOnly: false,
359
+ compareMethod: "words",
360
+ contextLines: 3
361
+ },
362
+ showThemeName: false
363
+ })
364
+ });
365
+ }
366
+ return /*#__PURE__*/_jsx(TreeDiffViewer, {
367
+ oldValue: prevVal,
368
+ newValue: nextVal
369
+ });
370
+ }, [change.prevValue, change.nextValue, diffMode]);
371
+ const handleFooterPrevious = useCallback(() => {
372
+ onIndexChange(Math.min(totalChanges - 1, selectedIndex + 1));
373
+ }, [selectedIndex, totalChanges, onIndexChange]);
374
+ const handleFooterNext = useCallback(() => {
375
+ onIndexChange(Math.max(0, selectedIndex - 1));
376
+ }, [selectedIndex, onIndexChange]);
377
+
378
+ // The diff compares THIS change's prevValue/nextValue — no siblings needed.
379
+ // Gating on `changes.length` greyed DIFF out for every jotai event opened
380
+ // from the Events timeline (a one-element array). Only a change with no
381
+ // captured previous value has nothing to compare.
382
+ const diffDisabled = change.prevValue === undefined;
383
+ const showInternalFooter = !disableInternalFooter && totalChanges > 1;
384
+ return /*#__PURE__*/_jsxs(View, {
385
+ style: styles.container,
386
+ children: [/*#__PURE__*/_jsx(DetailViewToggle, {
387
+ views: VIEW_CONFIGS,
388
+ activeView: activeView,
389
+ onViewChange: setActiveView,
390
+ disabledKeys: diffDisabled ? DIFF_DISABLED : undefined,
391
+ testIDPrefix: JOTAI_TEST_ID
392
+ }), /*#__PURE__*/_jsxs(View, {
393
+ style: [styles.contentArea, showInternalFooter && styles.contentAreaFooterPad],
394
+ children: [activeView === "change" && /*#__PURE__*/_jsx(JotaiAtomInfoView, {
395
+ change: change
396
+ }), activeView === "value" && /*#__PURE__*/_jsx(View, {
397
+ style: styles.stateContainer,
398
+ children: renderValueView()
399
+ }), activeView === "diff" && /*#__PURE__*/_jsxs(View, {
400
+ style: styles.diffContainer,
401
+ testID: `${JOTAI_TEST_ID}-diff`,
402
+ children: [/*#__PURE__*/_jsx(DiffModeTabs, {
403
+ tabs: DIFF_MODE_TABS,
404
+ activeTab: diffMode,
405
+ onTabChange: setDiffMode,
406
+ testIDPrefix: `${JOTAI_TEST_ID}-diff-tab`
407
+ }), /*#__PURE__*/_jsx(CompareBar, {
408
+ leftEvent: leftEvent,
409
+ rightEvent: rightEvent
410
+ }), /*#__PURE__*/_jsx(View, {
411
+ style: styles.diffContent,
412
+ testID: `${JOTAI_TEST_ID}-diff-content`,
413
+ children: renderDiffContent()
414
+ })]
415
+ })]
416
+ }), showInternalFooter && /*#__PURE__*/_jsx(EventStepperFooter, {
417
+ currentIndex: chronologicalNumber - 1,
418
+ totalItems: totalChanges,
419
+ onPrevious: handleFooterPrevious,
420
+ onNext: handleFooterNext,
421
+ itemLabel: "Change",
422
+ subtitle: formatRelativeTime(new Date(change.timestamp)),
423
+ absolute: true
424
+ })]
425
+ });
426
+ }
427
+ export function JotaiAtomDetailFooter({
428
+ change,
429
+ changes,
430
+ selectedIndex,
431
+ onIndexChange
432
+ }) {
433
+ const totalChanges = changes.length;
434
+ if (totalChanges <= 1) return null;
435
+ const chronologicalNumber = totalChanges - selectedIndex;
436
+ return /*#__PURE__*/_jsx(EventStepperFooter, {
437
+ currentIndex: chronologicalNumber - 1,
438
+ totalItems: totalChanges,
439
+ onPrevious: () => onIndexChange(Math.min(totalChanges - 1, selectedIndex + 1)),
440
+ onNext: () => onIndexChange(Math.max(0, selectedIndex - 1)),
441
+ itemLabel: "Change",
442
+ subtitle: formatRelativeTime(new Date(change.timestamp))
443
+ });
444
+ }
445
+ const styles = StyleSheet.create({
446
+ container: {
447
+ flex: 1,
448
+ // Transparent so the night sheet's star field shows through.
449
+ backgroundColor: "transparent"
450
+ },
451
+ contentArea: {
452
+ flex: 1
453
+ },
454
+ // Clearance for the absolute EventStepperFooter — only when it renders,
455
+ // otherwise hosts like the Events tool get a dead band at the sheet bottom.
456
+ contentAreaFooterPad: {
457
+ paddingBottom: 96
458
+ },
459
+ stateContainer: {
460
+ flex: 1,
461
+ padding: 14
462
+ },
463
+ contentCard: {
464
+ backgroundColor: night.color.surface,
465
+ borderRadius: 14,
466
+ padding: 14,
467
+ borderWidth: 1,
468
+ borderColor: night.color.border
469
+ },
470
+ valueHeader: {
471
+ flexDirection: "row",
472
+ alignItems: "center",
473
+ justifyContent: "space-between",
474
+ marginBottom: 8
475
+ },
476
+ valueLabel: {
477
+ fontSize: 10,
478
+ color: night.color.textSecondary,
479
+ fontFamily: monoFont,
480
+ letterSpacing: 0.5,
481
+ fontWeight: "600"
482
+ },
483
+ valueHeaderBadges: {
484
+ flexDirection: "row",
485
+ alignItems: "center",
486
+ gap: 6
487
+ },
488
+ changeBadge: {
489
+ paddingHorizontal: 8,
490
+ paddingVertical: 2,
491
+ borderRadius: 4
492
+ },
493
+ changeText: {
494
+ fontSize: 9,
495
+ fontWeight: "700",
496
+ fontFamily: monoFont,
497
+ letterSpacing: 0.3
498
+ },
499
+ typeBadge: {
500
+ paddingHorizontal: 8,
501
+ paddingVertical: 2,
502
+ borderRadius: 4,
503
+ backgroundColor: night.color.surfaceElevated
504
+ },
505
+ typeText: {
506
+ fontSize: 9,
507
+ fontWeight: "600",
508
+ color: night.color.textTertiary,
509
+ fontFamily: monoFont
510
+ },
511
+ valueBox: {
512
+ // Inset code box — darker than the card, the night track color.
513
+ backgroundColor: night.color.bg,
514
+ borderRadius: 6,
515
+ borderWidth: 1,
516
+ borderColor: night.color.border,
517
+ padding: 8
518
+ },
519
+ valueText: {
520
+ fontSize: 12,
521
+ color: night.color.text,
522
+ fontFamily: monoFont,
523
+ lineHeight: 18
524
+ },
525
+ diffContainer: {
526
+ flex: 1
527
+ },
528
+ diffContent: {
529
+ flex: 1,
530
+ paddingHorizontal: 14
531
+ },
532
+ changeBadgeSmall: {
533
+ paddingHorizontal: 6,
534
+ paddingVertical: 1,
535
+ borderRadius: 3
536
+ },
537
+ changeTextSmall: {
538
+ fontSize: 8,
539
+ fontWeight: "700",
540
+ fontFamily: monoFont
541
+ }
542
+ });
543
+ const infoStyles = StyleSheet.create({
544
+ container: {
545
+ flex: 1
546
+ },
547
+ content: {
548
+ padding: 8,
549
+ paddingBottom: 100,
550
+ gap: 16
551
+ },
552
+ card: {
553
+ backgroundColor: night.color.surface,
554
+ borderRadius: 6,
555
+ borderWidth: 1,
556
+ borderColor: night.color.accentBorderStrong,
557
+ overflow: "hidden",
558
+ shadowColor: night.color.accent,
559
+ shadowOffset: {
560
+ width: 0,
561
+ height: 0
562
+ },
563
+ shadowOpacity: 0.1,
564
+ shadowRadius: 6
565
+ },
566
+ sectionHeader: {
567
+ flexDirection: "row",
568
+ alignItems: "center",
569
+ justifyContent: "space-between",
570
+ paddingHorizontal: 12,
571
+ paddingVertical: 10,
572
+ borderBottomWidth: 1,
573
+ borderBottomColor: night.color.accentBorder,
574
+ backgroundColor: night.color.accentSoft
575
+ },
576
+ sectionHeaderLeft: {
577
+ flexDirection: "row",
578
+ alignItems: "center",
579
+ gap: 6
580
+ },
581
+ sectionTitle: {
582
+ fontSize: 12,
583
+ fontWeight: "600",
584
+ letterSpacing: 0.5,
585
+ color: night.color.accent,
586
+ fontFamily: monoFont
587
+ },
588
+ cardContent: {
589
+ padding: 14,
590
+ gap: 10
591
+ },
592
+ infoRow: {
593
+ flexDirection: "row",
594
+ alignItems: "center",
595
+ justifyContent: "space-between"
596
+ },
597
+ infoLabel: {
598
+ fontSize: 11,
599
+ color: night.color.textTertiary,
600
+ fontWeight: "500"
601
+ },
602
+ infoValue: {
603
+ fontSize: 11,
604
+ color: night.color.text,
605
+ fontWeight: "500",
606
+ flex: 1,
607
+ textAlign: "right",
608
+ marginLeft: 12
609
+ },
610
+ infoValueMono: {
611
+ fontFamily: monoFont
612
+ },
613
+ badgeRow: {
614
+ flexDirection: "row",
615
+ flexWrap: "wrap",
616
+ gap: 8,
617
+ marginTop: 6
618
+ },
619
+ categoryBadge: {
620
+ paddingHorizontal: 8,
621
+ paddingVertical: 3,
622
+ borderRadius: 4
623
+ },
624
+ categoryText: {
625
+ fontSize: 9,
626
+ fontWeight: "700",
627
+ letterSpacing: 0.3
628
+ },
629
+ changesBadge: {
630
+ flexDirection: "row",
631
+ alignItems: "center",
632
+ gap: 4,
633
+ paddingHorizontal: 8,
634
+ paddingVertical: 3,
635
+ borderRadius: 4,
636
+ backgroundColor: buoyColors.success + "26"
637
+ },
638
+ changesText: {
639
+ fontSize: 9,
640
+ fontWeight: "600",
641
+ color: buoyColors.success
642
+ },
643
+ noChangesBadge: {
644
+ paddingHorizontal: 8,
645
+ paddingVertical: 3,
646
+ borderRadius: 4,
647
+ backgroundColor: night.color.surfaceElevated
648
+ },
649
+ noChangesText: {
650
+ fontSize: 9,
651
+ fontWeight: "600",
652
+ color: night.color.textTertiary
653
+ },
654
+ keysContainer: {
655
+ flexDirection: "row",
656
+ flexWrap: "wrap",
657
+ gap: 6
658
+ },
659
+ keyBadge: {
660
+ paddingHorizontal: 8,
661
+ paddingVertical: 4,
662
+ borderRadius: 4,
663
+ backgroundColor: night.color.accentSoft,
664
+ borderWidth: 1,
665
+ borderColor: night.color.accentBorder
666
+ },
667
+ keyText: {
668
+ fontSize: 11,
669
+ fontWeight: "600",
670
+ color: night.color.accent,
671
+ fontFamily: monoFont
672
+ },
673
+ countBadge: {
674
+ paddingHorizontal: 8,
675
+ paddingVertical: 3,
676
+ borderRadius: 4,
677
+ backgroundColor: night.color.accentFill
678
+ },
679
+ countText: {
680
+ fontSize: 9,
681
+ fontWeight: "700",
682
+ color: night.color.accent,
683
+ fontFamily: monoFont
684
+ },
685
+ actionsRow: {
686
+ flexDirection: "row",
687
+ gap: 12
688
+ },
689
+ actionButton: {
690
+ paddingHorizontal: 14,
691
+ paddingVertical: 8,
692
+ borderRadius: 8,
693
+ borderWidth: 1
694
+ },
695
+ actionButtonCopy: {
696
+ borderColor: night.color.accentBorder,
697
+ backgroundColor: night.color.accentSoft
698
+ },
699
+ actionButtonText: {
700
+ fontSize: 10,
701
+ fontWeight: "700",
702
+ fontFamily: monoFont,
703
+ letterSpacing: 0.3
704
+ },
705
+ dataViewerContainer: {
706
+ marginTop: -12,
707
+ marginHorizontal: -12,
708
+ marginBottom: -12
709
+ }
710
+ });