@rozenite/storage-plugin 1.13.0 → 2.0.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 (79) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +6 -1
  3. package/dist/devtools/assets/panel-B6Wp0eie.css +1 -0
  4. package/dist/devtools/assets/panel-D7MFc4HG.js +32 -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 -64
  10. package/dist/react-native/chunks/secure-storage.require.cjs +1 -1
  11. package/dist/react-native/chunks/secure-storage.require.js +22 -14
  12. package/dist/react-native/chunks/useRozeniteStoragePlugin.require.cjs +1 -1
  13. package/dist/react-native/chunks/useRozeniteStoragePlugin.require.js +863 -315
  14. package/dist/react-native/index.d.ts +154 -18
  15. package/dist/react-native/index.js +10 -2
  16. package/dist/rozenite.json +1 -1
  17. package/dist/sdk/index.cjs +1 -1
  18. package/dist/sdk/index.d.ts +13 -5
  19. package/dist/sdk/index.js +21 -12
  20. package/package.json +13 -15
  21. package/react-native.ts +29 -14
  22. package/rozenite.config.ts +78 -0
  23. package/src/react-native/__tests__/entry-preview-pagination.test.ts +262 -0
  24. package/src/react-native/__tests__/export-snapshot.test.ts +142 -0
  25. package/src/react-native/__tests__/full-entry-request.test.ts +155 -0
  26. package/src/react-native/__tests__/import.test.ts +172 -10
  27. package/src/react-native/__tests__/storage-discovery.test.ts +155 -0
  28. package/src/react-native/__tests__/storage-view.test.ts +172 -0
  29. package/src/react-native/__tests__/stress-verification.test.ts +153 -0
  30. package/src/react-native/__tests__/use-storage-agent-tools.test.ts +231 -0
  31. package/src/react-native/adapters/async-storage.ts +9 -7
  32. package/src/react-native/adapters/mmkv.ts +5 -0
  33. package/src/react-native/adapters/secure-storage.ts +9 -1
  34. package/src/react-native/entry-preview-pagination.ts +277 -0
  35. package/src/react-native/export-snapshot.ts +94 -0
  36. package/src/react-native/full-entry-request.ts +96 -0
  37. package/src/react-native/import.ts +190 -16
  38. package/src/react-native/storage-discovery.ts +96 -0
  39. package/src/react-native/storage-view.ts +45 -103
  40. package/src/react-native/useRozeniteStoragePlugin.ts +108 -72
  41. package/src/react-native/useStorageAgentTools.ts +207 -86
  42. package/src/shared/__tests__/entry-preview.test.ts +117 -0
  43. package/src/shared/__tests__/snapshot.test.ts +21 -0
  44. package/src/shared/agent-tools.ts +27 -11
  45. package/src/shared/entry-preview.ts +59 -0
  46. package/src/shared/messaging.ts +136 -21
  47. package/src/shared/snapshot.ts +8 -1
  48. package/src/shared/types.ts +20 -1
  49. package/src/ui/__tests__/binary-value-editor-state.test.ts +13 -0
  50. package/src/ui/__tests__/binary.test.ts +18 -0
  51. package/src/ui/__tests__/large-value-viewer-state.test.ts +21 -0
  52. package/src/ui/__tests__/large-value-viewer.test.tsx +63 -0
  53. package/src/ui/__tests__/panel.test.tsx +409 -0
  54. package/src/ui/__tests__/storage-groups.test.ts +91 -0
  55. package/src/ui/__tests__/use-storage-entries.test.tsx +353 -0
  56. package/src/ui/add-entry-dialog.tsx +65 -105
  57. package/src/ui/binary-value-editor-state.ts +10 -1
  58. package/src/ui/binary-value-editor.tsx +57 -33
  59. package/src/ui/binary.ts +56 -4
  60. package/src/ui/edit-entry-dialog.tsx +71 -99
  61. package/src/ui/editor-switcher.tsx +7 -5
  62. package/src/ui/entry-detail-dialog.tsx +69 -179
  63. package/src/ui/format-value.tsx +32 -0
  64. package/src/ui/globals.css +1 -123
  65. package/src/ui/import-dialog.tsx +66 -91
  66. package/src/ui/large-value-viewer-state.ts +32 -0
  67. package/src/ui/large-value-viewer.tsx +71 -0
  68. package/src/ui/panel.tsx +724 -496
  69. package/src/ui/query-client.tsx +34 -0
  70. package/src/ui/storage-groups.ts +66 -0
  71. package/src/ui/typed-value-editor.tsx +14 -15
  72. package/src/ui/use-storage-entries.ts +207 -0
  73. package/tsconfig.json +5 -1
  74. package/dist/devtools/assets/panel-Bm-SWF7d.js +0 -33
  75. package/dist/devtools/assets/panel-DIqI4WSp.css +0 -1
  76. package/postcss.config.js +0 -6
  77. package/src/ui/confirm-dialog.tsx +0 -100
  78. package/src/ui/editable-table.tsx +0 -300
  79. package/tailwind.config.ts +0 -94
@@ -1,36 +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 = (
17
+ export const jsonSafeParse = (
34
18
  value: string,
35
19
  ): Record<string, unknown> | unknown[] | null => {
36
20
  try {
@@ -50,179 +34,85 @@ const jsonSafeParse = (
50
34
  }
51
35
  };
52
36
 
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
37
  export const EntryDetailDialog = ({
109
- isOpen,
110
- onClose,
38
+ open,
39
+ onOpenChange,
111
40
  onEdit,
112
41
  entry,
113
42
  }: EntryDetailDialogProps) => {
114
43
  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();
44
+ const stringValue = isStringValue ? entry.value : '';
45
+ const jsonValue = useMemo(() => {
46
+ if (!isStringValue || stringValue.length > MAX_AUTO_JSON_INSPECTION_LENGTH) {
47
+ return null;
124
48
  }
125
- };
126
49
 
127
- if (!isOpen || !entry) return null;
50
+ return jsonSafeParse(stringValue);
51
+ }, [isStringValue, stringValue]);
128
52
 
129
53
  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">
54
+ <Dialog open={open} onOpenChange={onOpenChange}>
55
+ <Dialog.Content className="max-w-2xl">
56
+ <Dialog.Header>
140
57
  <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>
58
+ <Info className="h-4 w-4 text-muted-foreground" />
59
+ <Dialog.Title>Entry Details</Dialog.Title>
145
60
  </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}
61
+ </Dialog.Header>
62
+
63
+ {entry && (
64
+ <div className="flex max-h-[70vh] flex-col gap-4 overflow-auto">
65
+ <div>
66
+ <div className="mb-1 text-sm font-medium text-foreground">
67
+ Key
68
+ </div>
69
+ <div className="w-full break-all rounded-md border border-input bg-muted px-3 py-2 font-mono text-sm text-foreground">
70
+ {entry.key}
71
+ </div>
163
72
  </div>
164
- </div>
165
73
 
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>
74
+ <div>
75
+ <div className="mb-1 text-sm font-medium text-foreground">
76
+ Type
77
+ </div>
78
+ <Badge variant="outline">{entry.type}</Badge>
179
79
  </div>
180
- </div>
181
80
 
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
- )}
81
+ <div className="flex min-h-0 flex-1 flex-col">
82
+ <div className="mb-1 text-sm font-medium text-foreground">
83
+ Value
84
+ </div>
85
+ <div className="max-h-96 overflow-auto rounded-md border border-input bg-muted p-3">
86
+ {jsonValue ? (
87
+ <JsonInspector data={jsonValue} />
88
+ ) : entry.type === 'buffer' ? (
89
+ <div className="flex flex-col gap-2">
90
+ <div className="text-xs text-muted-foreground">
91
+ {entry.value.length}{' '}
92
+ {entry.value.length === 1 ? 'byte' : 'bytes'}
93
+ </div>
94
+ <HexdumpValueViewer bytes={entry.value} />
95
+ </div>
96
+ ) : entry.type === 'string' ? (
97
+ <TextValueViewer value={entry.value} />
98
+ ) : (
99
+ <div className="text-sm">{formatValue(entry)}</div>
100
+ )}
101
+ </div>
198
102
  </div>
199
103
  </div>
200
- </div>
104
+ )}
201
105
 
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" />
106
+ <Dialog.Footer>
107
+ {entry && onEdit && (
108
+ <Button variant="outline" onClick={() => onEdit(entry)}>
109
+ <Edit3 className="h-3.5 w-3.5" />
214
110
  Edit
215
- </button>
111
+ </Button>
216
112
  )}
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>
113
+ <Dialog.Close render={<Button />}>Close</Dialog.Close>
114
+ </Dialog.Footer>
115
+ </Dialog.Content>
116
+ </Dialog>
227
117
  );
228
118
  };
@@ -0,0 +1,32 @@
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
17
+ className={cn(
18
+ 'font-mono',
19
+ entry.value ? 'text-primary' : 'text-destructive',
20
+ )}
21
+ >
22
+ {entry.value ? 'true' : 'false'}
23
+ </span>
24
+ );
25
+ }
26
+
27
+ return (
28
+ <span className="font-mono text-muted-foreground">
29
+ {compactBufferPreview(entry.value)}
30
+ </span>
31
+ );
32
+ };
@@ -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';