@rozenite/storage-plugin 1.13.0 → 2.1.0

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 (84) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +6 -1
  3. package/dist/devtools/assets/panel-BlDRXgVW.js +32 -0
  4. package/dist/devtools/assets/panel-DBweZnW5.css +1 -0
  5. package/dist/devtools/panel.html +2 -2
  6. package/dist/react-native/chunks/async-storage.require.cjs +1 -1
  7. package/dist/react-native/chunks/async-storage.require.js +8 -7
  8. package/dist/react-native/chunks/index.require.cjs +1 -1
  9. package/dist/react-native/chunks/index.require.js +70 -66
  10. package/dist/react-native/chunks/secure-storage.require.cjs +1 -1
  11. package/dist/react-native/chunks/secure-storage.require.js +17 -13
  12. package/dist/react-native/chunks/useRozeniteStoragePlugin.require.cjs +1 -1
  13. package/dist/react-native/chunks/useRozeniteStoragePlugin.require.js +863 -344
  14. package/dist/react-native/index.d.ts +166 -18
  15. package/dist/rozenite.json +1 -1
  16. package/dist/sdk/index.cjs +1 -1
  17. package/dist/sdk/index.d.ts +13 -5
  18. package/dist/sdk/index.js +21 -12
  19. package/package.json +35 -37
  20. package/react-native.ts +12 -22
  21. package/rozenite.config.ts +74 -0
  22. package/sdk.ts +1 -5
  23. package/src/react-native/__tests__/entry-preview-pagination.test.ts +234 -0
  24. package/src/react-native/__tests__/export-snapshot.test.ts +138 -0
  25. package/src/react-native/__tests__/full-entry-request.test.ts +143 -0
  26. package/src/react-native/__tests__/import.test.ts +174 -11
  27. package/src/react-native/__tests__/storage-discovery.test.ts +144 -0
  28. package/src/react-native/__tests__/storage-view.test.ts +172 -0
  29. package/src/react-native/__tests__/stress-verification.test.ts +140 -0
  30. package/src/react-native/__tests__/use-storage-agent-tools.test.ts +221 -0
  31. package/src/react-native/adapters/__tests__/mmkv.test.ts +20 -30
  32. package/src/react-native/adapters/async-storage.ts +8 -9
  33. package/src/react-native/adapters/index.ts +2 -8
  34. package/src/react-native/adapters/mmkv.ts +12 -24
  35. package/src/react-native/adapters/secure-storage.ts +4 -0
  36. package/src/react-native/entry-preview-pagination.ts +261 -0
  37. package/src/react-native/export-snapshot.ts +88 -0
  38. package/src/react-native/full-entry-request.ts +91 -0
  39. package/src/react-native/import.ts +179 -16
  40. package/src/react-native/storage-discovery.ts +92 -0
  41. package/src/react-native/storage-view.ts +47 -109
  42. package/src/react-native/useRozeniteStoragePlugin.ts +153 -129
  43. package/src/react-native/useStorageAgentTools.ts +196 -91
  44. package/src/shared/__tests__/entry-preview.test.ts +99 -0
  45. package/src/shared/__tests__/snapshot.test.ts +31 -32
  46. package/src/shared/agent-tools.ts +33 -38
  47. package/src/shared/entry-preview.ts +54 -0
  48. package/src/shared/messaging.ts +140 -21
  49. package/src/shared/snapshot.ts +12 -18
  50. package/src/shared/types.ts +21 -4
  51. package/src/ui/__tests__/binary-value-editor-state.test.ts +16 -13
  52. package/src/ui/__tests__/binary.test.ts +31 -33
  53. package/src/ui/__tests__/large-value-viewer-state.test.ts +21 -0
  54. package/src/ui/__tests__/large-value-viewer.test.tsx +54 -0
  55. package/src/ui/__tests__/panel.test.tsx +382 -0
  56. package/src/ui/__tests__/storage-groups.test.ts +88 -0
  57. package/src/ui/__tests__/type-conversion.test.ts +8 -24
  58. package/src/ui/__tests__/use-storage-entries.test.tsx +337 -0
  59. package/src/ui/add-entry-dialog.tsx +66 -117
  60. package/src/ui/binary-value-editor-state.ts +16 -25
  61. package/src/ui/binary-value-editor.tsx +59 -47
  62. package/src/ui/binary.ts +54 -15
  63. package/src/ui/edit-entry-dialog.tsx +68 -104
  64. package/src/ui/editor-switcher.tsx +9 -16
  65. package/src/ui/entry-detail-dialog.tsx +62 -181
  66. package/src/ui/format-value.tsx +25 -0
  67. package/src/ui/globals.css +1 -123
  68. package/src/ui/import-dialog.tsx +72 -109
  69. package/src/ui/large-value-viewer-state.ts +25 -0
  70. package/src/ui/large-value-viewer.tsx +65 -0
  71. package/src/ui/panel.tsx +649 -509
  72. package/src/ui/query-client.tsx +23 -0
  73. package/src/ui/storage-groups.ts +66 -0
  74. package/src/ui/type-conversion.ts +2 -5
  75. package/src/ui/typed-value-editor.tsx +15 -20
  76. package/src/ui/use-storage-entries.ts +192 -0
  77. package/src/ui/utils.ts +1 -4
  78. package/tsconfig.json +5 -1
  79. package/dist/devtools/assets/panel-Bm-SWF7d.js +0 -33
  80. package/dist/devtools/assets/panel-DIqI4WSp.css +0 -1
  81. package/postcss.config.js +0 -6
  82. package/src/ui/confirm-dialog.tsx +0 -100
  83. package/src/ui/editable-table.tsx +0 -300
  84. package/tailwind.config.ts +0 -94
@@ -1,38 +1,20 @@
1
- import { X, Info, Edit3 } from 'lucide-react';
2
- import { JSONTree } from 'react-json-tree';
3
- import { StorageEntry } from '../shared/types';
1
+ import { Badge, Button, Dialog, JsonInspector } from '@rozenite/ui';
4
2
  import { useMemo } from 'react';
5
- import { bytesToHexdump } from './binary';
3
+ import { Edit3, Info } from 'lucide-react';
4
+ import type { StorageEntry } from '../shared/types';
5
+ import { formatValue } from './format-value';
6
+ import { HexdumpValueViewer, TextValueViewer } from './large-value-viewer';
6
7
 
7
8
  export type EntryDetailDialogProps = {
8
- isOpen: boolean;
9
- onClose: () => void;
9
+ open: boolean;
10
+ onOpenChange: (open: boolean) => void;
10
11
  onEdit?: (entry: StorageEntry) => void;
11
12
  entry: StorageEntry | null;
12
13
  };
13
14
 
14
- const jsonTreeTheme = {
15
- base00: 'transparent',
16
- base01: '#374151', // bg-gray-700
17
- base02: '#4b5563', // bg-gray-600
18
- base03: '#6b7280', // text-gray-500
19
- base04: '#9ca3af', // text-gray-400
20
- base05: '#d1d5db', // text-gray-300
21
- base06: '#e5e7eb', // text-gray-200
22
- base07: '#f9fafb', // text-gray-100
23
- base08: '#ef4444', // text-red-500
24
- base09: '#f59e0b', // text-yellow-500
25
- base0A: '#10b981', // text-green-500
26
- base0B: '#3b82f6', // text-blue-500
27
- base0C: '#06b6d4', // text-cyan-500
28
- base0D: '#8b5cf6', // text-purple-500
29
- base0E: '#ec4899', // text-pink-500
30
- base0F: '#f97316', // text-orange-500
31
- };
15
+ const MAX_AUTO_JSON_INSPECTION_LENGTH = 50_000;
32
16
 
33
- const jsonSafeParse = (
34
- value: string,
35
- ): Record<string, unknown> | unknown[] | null => {
17
+ export const jsonSafeParse = (value: string): Record<string, unknown> | unknown[] | null => {
36
18
  try {
37
19
  const parsed = JSON.parse(value) as unknown;
38
20
 
@@ -50,179 +32,78 @@ const jsonSafeParse = (
50
32
  }
51
33
  };
52
34
 
53
- const getTypeColorClass = (type: string) => {
54
- switch (type) {
55
- case 'string':
56
- return 'bg-green-600';
57
- case 'number':
58
- return 'bg-blue-600';
59
- case 'boolean':
60
- return 'bg-yellow-600';
61
- case 'buffer':
62
- return 'bg-purple-600';
63
- default:
64
- return 'bg-gray-600';
65
- }
66
- };
67
-
68
- const formatValue = (entry: StorageEntry) => {
69
- switch (entry.type) {
70
- case 'string':
71
- return (
72
- <span className="text-green-300 font-mono break-all">
73
- "{entry.value as string}"
74
- </span>
75
- );
76
- case 'number':
77
- return (
78
- <span className="text-blue-300 font-mono">{entry.value as number}</span>
79
- );
80
- case 'boolean':
81
- return (
82
- <span
83
- className={`font-mono ${
84
- entry.value ? 'text-green-400' : 'text-red-400'
85
- }`}
86
- >
87
- {entry.value ? 'true' : 'false'}
88
- </span>
89
- );
90
- case 'buffer': {
91
- const bufferArray = entry.value as number[];
92
- return (
93
- <div className="space-y-2">
94
- <div className="text-xs text-gray-400">
95
- {bufferArray.length} {bufferArray.length === 1 ? 'byte' : 'bytes'}
96
- </div>
97
- <pre className="text-purple-200 font-mono text-xs whitespace-pre overflow-auto leading-snug">
98
- {bytesToHexdump(bufferArray)}
99
- </pre>
100
- </div>
101
- );
102
- }
103
- default:
104
- return <span className="text-gray-400">Unknown</span>;
105
- }
106
- };
107
-
108
35
  export const EntryDetailDialog = ({
109
- isOpen,
110
- onClose,
36
+ open,
37
+ onOpenChange,
111
38
  onEdit,
112
39
  entry,
113
40
  }: EntryDetailDialogProps) => {
114
41
  const isStringValue = entry?.type === 'string';
115
- const stringValue = isStringValue ? (entry.value as string) : '';
116
- const jsonValue = useMemo(
117
- () => (isStringValue ? jsonSafeParse(stringValue) : null),
118
- [isStringValue, stringValue],
119
- );
120
-
121
- const handleKeyDown = (e: React.KeyboardEvent) => {
122
- if (e.key === 'Escape') {
123
- onClose();
42
+ const stringValue = isStringValue ? entry.value : '';
43
+ const jsonValue = useMemo(() => {
44
+ if (!isStringValue || stringValue.length > MAX_AUTO_JSON_INSPECTION_LENGTH) {
45
+ return null;
124
46
  }
125
- };
126
47
 
127
- if (!isOpen || !entry) return null;
48
+ return jsonSafeParse(stringValue);
49
+ }, [isStringValue, stringValue]);
128
50
 
129
51
  return (
130
- <div
131
- className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
132
- onClick={onClose}
133
- >
134
- <div
135
- className="bg-gray-800 rounded-lg p-6 w-[90vw] max-w-2xl max-h-[90vh] flex flex-col"
136
- onClick={(e) => e.stopPropagation()}
137
- onKeyDown={handleKeyDown}
138
- >
139
- <div className="flex items-center justify-between mb-4">
52
+ <Dialog open={open} onOpenChange={onOpenChange}>
53
+ <Dialog.Content className="max-w-2xl">
54
+ <Dialog.Header>
140
55
  <div className="flex items-center gap-2">
141
- <Info className="h-5 w-5 text-blue-400" />
142
- <h2 className="text-lg font-semibold text-gray-100">
143
- Entry Details
144
- </h2>
56
+ <Info className="h-4 w-4 text-muted-foreground" />
57
+ <Dialog.Title>Entry Details</Dialog.Title>
145
58
  </div>
146
- <button
147
- onClick={onClose}
148
- className="p-1 text-gray-400 hover:text-gray-200 hover:bg-gray-700 rounded transition-colors"
149
- title="Close dialog"
150
- >
151
- <X className="h-4 w-4" />
152
- </button>
153
- </div>
154
-
155
- <div className="space-y-4 flex-1 overflow-auto">
156
- {/* Key Display */}
157
- <div>
158
- <label className="block text-sm font-medium text-gray-200 mb-1">
159
- Key
160
- </label>
161
- <div className="w-full px-3 py-2 text-sm bg-gray-700 border border-gray-600 rounded text-gray-100 font-mono break-all">
162
- {entry.key}
59
+ </Dialog.Header>
60
+
61
+ {entry && (
62
+ <div className="flex max-h-[70vh] flex-col gap-4 overflow-auto">
63
+ <div>
64
+ <div className="mb-1 text-sm font-medium text-foreground">Key</div>
65
+ <div className="w-full break-all rounded-md border border-input bg-muted px-3 py-2 font-mono text-sm text-foreground">
66
+ {entry.key}
67
+ </div>
163
68
  </div>
164
- </div>
165
69
 
166
- {/* Type Display */}
167
- <div>
168
- <label className="block text-sm font-medium text-gray-200 mb-1">
169
- Type
170
- </label>
171
- <div className="flex items-center">
172
- <span
173
- className={`px-2 py-1 text-xs font-medium rounded text-white ${getTypeColorClass(
174
- entry.type,
175
- )}`}
176
- >
177
- {entry.type}
178
- </span>
70
+ <div>
71
+ <div className="mb-1 text-sm font-medium text-foreground">Type</div>
72
+ <Badge variant="outline">{entry.type}</Badge>
179
73
  </div>
180
- </div>
181
74
 
182
- {/* Value Display */}
183
- <div className="flex-1 min-h-0 flex flex-col">
184
- <label className="block text-sm font-medium text-gray-200 mb-1">
185
- Value
186
- </label>
187
- <div className="max-h-96 overflow-auto bg-gray-700 border border-gray-600 rounded p-3">
188
- {jsonValue ? (
189
- <JSONTree
190
- data={jsonValue}
191
- theme={jsonTreeTheme}
192
- invertTheme={false}
193
- shouldExpandNodeInitially={(keyPath) => keyPath.length <= 2}
194
- />
195
- ) : (
196
- <div className="text-sm">{formatValue(entry)}</div>
197
- )}
75
+ <div className="flex min-h-0 flex-1 flex-col">
76
+ <div className="mb-1 text-sm font-medium text-foreground">Value</div>
77
+ <div className="max-h-96 overflow-auto rounded-md border border-input bg-muted p-3">
78
+ {jsonValue ? (
79
+ <JsonInspector data={jsonValue} />
80
+ ) : entry.type === 'buffer' ? (
81
+ <div className="flex flex-col gap-2">
82
+ <div className="text-xs text-muted-foreground">
83
+ {entry.value.length} {entry.value.length === 1 ? 'byte' : 'bytes'}
84
+ </div>
85
+ <HexdumpValueViewer bytes={entry.value} />
86
+ </div>
87
+ ) : entry.type === 'string' ? (
88
+ <TextValueViewer value={entry.value} />
89
+ ) : (
90
+ <div className="text-sm">{formatValue(entry)}</div>
91
+ )}
92
+ </div>
198
93
  </div>
199
94
  </div>
200
- </div>
95
+ )}
201
96
 
202
- {/* Dialog Actions */}
203
- <div className="flex items-center justify-end gap-2 mt-6">
204
- {onEdit && (
205
- <button
206
- onClick={() => {
207
- if (entry && onEdit) {
208
- onEdit(entry);
209
- }
210
- }}
211
- className="flex items-center gap-2 px-4 py-2 text-sm bg-gray-700 hover:bg-gray-600 text-white rounded transition-colors"
212
- >
213
- <Edit3 className="h-4 w-4" />
97
+ <Dialog.Footer>
98
+ {entry && onEdit && (
99
+ <Button variant="outline" onClick={() => onEdit(entry)}>
100
+ <Edit3 className="h-3.5 w-3.5" />
214
101
  Edit
215
- </button>
102
+ </Button>
216
103
  )}
217
- <button
218
- onClick={onClose}
219
- className="px-4 py-2 text-sm bg-blue-600 hover:bg-blue-700 text-white rounded transition-colors"
220
- autoFocus
221
- >
222
- Close
223
- </button>
224
- </div>
225
- </div>
226
- </div>
104
+ <Dialog.Close render={<Button />}>Close</Dialog.Close>
105
+ </Dialog.Footer>
106
+ </Dialog.Content>
107
+ </Dialog>
227
108
  );
228
109
  };
@@ -0,0 +1,25 @@
1
+ import { cn } from '@rozenite/ui';
2
+ import type { StorageEntry } from '../shared/types';
3
+ import { compactBufferPreview } from './binary';
4
+
5
+ export const formatValue = (entry: StorageEntry) => {
6
+ if (entry.type === 'string') {
7
+ return <span className="font-mono text-foreground">"{entry.value}"</span>;
8
+ }
9
+
10
+ if (entry.type === 'number') {
11
+ return <span className="font-mono text-foreground">{entry.value}</span>;
12
+ }
13
+
14
+ if (entry.type === 'boolean') {
15
+ return (
16
+ <span className={cn('font-mono', entry.value ? 'text-primary' : 'text-destructive')}>
17
+ {entry.value ? 'true' : 'false'}
18
+ </span>
19
+ );
20
+ }
21
+
22
+ return (
23
+ <span className="font-mono text-muted-foreground">{compactBufferPreview(entry.value)}</span>
24
+ );
25
+ };
@@ -1,123 +1 @@
1
- @tailwind base;
2
- @tailwind components;
3
- @tailwind utilities;
4
-
5
- @layer utilities {
6
- .text-balance {
7
- text-wrap: balance;
8
- }
9
-
10
- .wrap-anywhere {
11
- overflow-wrap: anywhere;
12
- }
13
- }
14
-
15
- @layer base {
16
- :root {
17
- --background: 0 0% 100%;
18
- --foreground: 0 0% 3.9%;
19
- --card: 0 0% 100%;
20
- --card-foreground: 0 0% 3.9%;
21
- --popover: 0 0% 100%;
22
- --popover-foreground: 0 0% 3.9%;
23
- --primary: 0 0% 9%;
24
- --primary-foreground: 0 0% 98%;
25
- --secondary: 0 0% 96.1%;
26
- --secondary-foreground: 0 0% 9%;
27
- --muted: 0 0% 96.1%;
28
- --muted-foreground: 0 0% 45.1%;
29
- --accent: 0 0% 96.1%;
30
- --accent-foreground: 0 0% 9%;
31
- --destructive: 0 84.2% 60.2%;
32
- --destructive-foreground: 0 0% 98%;
33
- --border: 0 0% 89.8%;
34
- --input: 0 0% 89.8%;
35
- --ring: 0 0% 3.9%;
36
- --chart-1: 12 76% 61%;
37
- --chart-2: 173 58% 39%;
38
- --chart-3: 197 37% 24%;
39
- --chart-4: 43 74% 66%;
40
- --chart-5: 27 87% 67%;
41
- --radius: 0.5rem;
42
- --sidebar-background: 0 0% 98%;
43
- --sidebar-foreground: 240 5.3% 26.1%;
44
- --sidebar-primary: 240 5.9% 10%;
45
- --sidebar-primary-foreground: 0 0% 98%;
46
- --sidebar-accent: 240 4.8% 95.9%;
47
- --sidebar-accent-foreground: 240 5.9% 10%;
48
- --sidebar-border: 220 13% 91%;
49
- --sidebar-ring: 217.2 91.2% 59.8%;
50
- }
51
- .dark {
52
- --background: 0 0% 3.9%;
53
- --foreground: 0 0% 98%;
54
- --card: 0 0% 3.9%;
55
- --card-foreground: 0 0% 98%;
56
- --popover: 0 0% 3.9%;
57
- --popover-foreground: 0 0% 98%;
58
- --primary: 0 0% 98%;
59
- --primary-foreground: 0 0% 9%;
60
- --secondary: 0 0% 14.9%;
61
- --secondary-foreground: 0 0% 98%;
62
- --muted: 0 0% 14.9%;
63
- --muted-foreground: 0 0% 63.9%;
64
- --accent: 0 0% 14.9%;
65
- --accent-foreground: 0 0% 98%;
66
- --destructive: 0 62.8% 30.6%;
67
- --destructive-foreground: 0 0% 98%;
68
- --border: 0 0% 14.9%;
69
- --input: 0 0% 14.9%;
70
- --ring: 0 0% 83.1%;
71
- --chart-1: 220 70% 50%;
72
- --chart-2: 160 60% 45%;
73
- --chart-3: 30 80% 55%;
74
- --chart-4: 280 65% 60%;
75
- --chart-5: 340 75% 55%;
76
- --sidebar-background: 240 5.9% 10%;
77
- --sidebar-foreground: 240 4.8% 95.9%;
78
- --sidebar-primary: 224.3 76.3% 48%;
79
- --sidebar-primary-foreground: 0 0% 100%;
80
- --sidebar-accent: 240 3.7% 15.9%;
81
- --sidebar-accent-foreground: 240 4.8% 95.9%;
82
- --sidebar-border: 240 3.7% 15.9%;
83
- --sidebar-ring: 217.2 91.2% 59.8%;
84
- }
85
- }
86
-
87
- @layer base {
88
- * {
89
- @apply border-border;
90
- }
91
- body {
92
- @apply bg-background text-foreground;
93
- }
94
- }
95
-
96
- /* Custom scrollbar styles to match plugin theme */
97
- ::-webkit-scrollbar {
98
- width: 8px;
99
- height: 8px;
100
- }
101
-
102
- ::-webkit-scrollbar-track {
103
- background: #1f2937; /* gray-800 */
104
- border-radius: 4px;
105
- }
106
-
107
- ::-webkit-scrollbar-thumb {
108
- background: #374151; /* gray-700 */
109
- border-radius: 4px;
110
- border: 1px solid #1f2937; /* gray-800 */
111
- }
112
-
113
- ::-webkit-scrollbar-thumb:hover {
114
- background: #4b5563; /* gray-600 */
115
- }
116
-
117
- ::-webkit-scrollbar-thumb:active {
118
- background: #6b7280; /* gray-500 */
119
- }
120
-
121
- ::-webkit-scrollbar-corner {
122
- background: #1f2937; /* gray-800 */
123
- }
1
+ @import '@rozenite/ui/styles.css';