@rozenite/storage-plugin 2.0.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 (68) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/devtools/assets/panel-BlDRXgVW.js +32 -0
  3. package/dist/devtools/assets/panel-DBweZnW5.css +1 -0
  4. package/dist/devtools/panel.html +2 -2
  5. package/dist/react-native/chunks/index.require.js +1 -3
  6. package/dist/react-native/chunks/secure-storage.require.js +2 -6
  7. package/dist/react-native/chunks/useRozeniteStoragePlugin.require.cjs +1 -1
  8. package/dist/react-native/chunks/useRozeniteStoragePlugin.require.js +266 -295
  9. package/dist/react-native/index.d.ts +14 -2
  10. package/dist/react-native/index.js +2 -10
  11. package/dist/rozenite.json +1 -1
  12. package/package.json +29 -29
  13. package/react-native.ts +11 -36
  14. package/rozenite.config.ts +2 -6
  15. package/sdk.ts +1 -5
  16. package/src/react-native/__tests__/entry-preview-pagination.test.ts +12 -40
  17. package/src/react-native/__tests__/export-snapshot.test.ts +2 -6
  18. package/src/react-native/__tests__/full-entry-request.test.ts +5 -17
  19. package/src/react-native/__tests__/import.test.ts +9 -8
  20. package/src/react-native/__tests__/storage-discovery.test.ts +7 -18
  21. package/src/react-native/__tests__/stress-verification.test.ts +5 -18
  22. package/src/react-native/__tests__/use-storage-agent-tools.test.ts +25 -35
  23. package/src/react-native/adapters/__tests__/mmkv.test.ts +20 -30
  24. package/src/react-native/adapters/async-storage.ts +2 -5
  25. package/src/react-native/adapters/index.ts +2 -8
  26. package/src/react-native/adapters/mmkv.ts +7 -24
  27. package/src/react-native/adapters/secure-storage.ts +2 -6
  28. package/src/react-native/entry-preview-pagination.ts +11 -27
  29. package/src/react-native/export-snapshot.ts +3 -9
  30. package/src/react-native/full-entry-request.ts +3 -8
  31. package/src/react-native/import.ts +10 -21
  32. package/src/react-native/storage-discovery.ts +2 -6
  33. package/src/react-native/storage-view.ts +2 -6
  34. package/src/react-native/useRozeniteStoragePlugin.ts +107 -119
  35. package/src/react-native/useStorageAgentTools.ts +8 -24
  36. package/src/shared/__tests__/entry-preview.test.ts +9 -27
  37. package/src/shared/__tests__/snapshot.test.ts +10 -32
  38. package/src/shared/agent-tools.ts +9 -30
  39. package/src/shared/entry-preview.ts +3 -8
  40. package/src/shared/messaging.ts +15 -11
  41. package/src/shared/snapshot.ts +4 -17
  42. package/src/shared/types.ts +2 -4
  43. package/src/ui/__tests__/binary-value-editor-state.test.ts +6 -16
  44. package/src/ui/__tests__/binary.test.ts +13 -33
  45. package/src/ui/__tests__/large-value-viewer.test.tsx +4 -13
  46. package/src/ui/__tests__/panel.test.tsx +41 -68
  47. package/src/ui/__tests__/storage-groups.test.ts +1 -4
  48. package/src/ui/__tests__/type-conversion.test.ts +8 -24
  49. package/src/ui/__tests__/use-storage-entries.test.tsx +18 -34
  50. package/src/ui/add-entry-dialog.tsx +4 -15
  51. package/src/ui/binary-value-editor-state.ts +6 -24
  52. package/src/ui/binary-value-editor.tsx +5 -17
  53. package/src/ui/binary.ts +6 -19
  54. package/src/ui/edit-entry-dialog.tsx +3 -11
  55. package/src/ui/editor-switcher.tsx +2 -11
  56. package/src/ui/entry-detail-dialog.tsx +5 -14
  57. package/src/ui/format-value.tsx +2 -9
  58. package/src/ui/import-dialog.tsx +7 -19
  59. package/src/ui/large-value-viewer-state.ts +2 -9
  60. package/src/ui/large-value-viewer.tsx +3 -9
  61. package/src/ui/panel.tsx +65 -153
  62. package/src/ui/query-client.tsx +4 -15
  63. package/src/ui/type-conversion.ts +2 -5
  64. package/src/ui/typed-value-editor.tsx +1 -5
  65. package/src/ui/use-storage-entries.ts +6 -21
  66. package/src/ui/utils.ts +1 -4
  67. package/dist/devtools/assets/panel-B6Wp0eie.css +0 -1
  68. package/dist/devtools/assets/panel-D7MFc4HG.js +0 -32
@@ -97,11 +97,8 @@ describe('storage entry query hooks', () => {
97
97
  if (payload.cursor === undefined)
98
98
  return Promise.resolve(response(undefined, { next: 'next' }));
99
99
  if (payload.cursor === 'next')
100
- return Promise.resolve(
101
- response('next', { next: 'last', previous: 'first' }),
102
- );
103
- if (payload.cursor === 'last')
104
- return Promise.resolve(response('last', { previous: 'next' }));
100
+ return Promise.resolve(response('next', { next: 'last', previous: 'first' }));
101
+ if (payload.cursor === 'last') return Promise.resolve(response('last', { previous: 'next' }));
105
102
  return Promise.resolve(response('first', { next: 'next' }));
106
103
  });
107
104
  const hook = await renderPreviewHook({
@@ -117,9 +114,11 @@ describe('storage entry query hooks', () => {
117
114
  });
118
115
  await act(async () => hook.result.fetchNextPage());
119
116
  await act(async () => hook.result.fetchNextPage());
120
- expect(
121
- request.mock.calls.map(([options]) => options.payload.cursor),
122
- ).toEqual([undefined, 'next', 'last']);
117
+ expect(request.mock.calls.map(([options]) => options.payload.cursor)).toEqual([
118
+ undefined,
119
+ 'next',
120
+ 'last',
121
+ ]);
123
122
  const queryKey = storageEntryPreviewQueryKey(target, '', 'ascending');
124
123
  const pageKeys = () =>
125
124
  hook.queryClient
@@ -134,9 +133,7 @@ describe('storage entry query hooks', () => {
134
133
 
135
134
  it('cancels stale target requests and starts a new sequence at the first page', async () => {
136
135
  globalThis.IS_REACT_ACT_ENVIRONMENT = true;
137
- let resolveFirst:
138
- | ((value: ReturnType<typeof response>) => void)
139
- | undefined;
136
+ let resolveFirst: ((value: ReturnType<typeof response>) => void) | undefined;
140
137
  const request = vi.fn(({ payload }) => {
141
138
  if (payload.target === target) {
142
139
  return new Promise((resolve) => {
@@ -171,8 +168,7 @@ describe('storage entry query hooks', () => {
171
168
  expect(
172
169
  hook.queryClient.getQueryData<{
173
170
  pages: Array<ReturnType<typeof response>>;
174
- }>(storageEntryPreviewQueryKey(otherTarget, 'two', 'descending'))
175
- ?.pages[0].target,
171
+ }>(storageEntryPreviewQueryKey(otherTarget, 'two', 'descending'))?.pages[0].target,
176
172
  ).toEqual(otherTarget),
177
173
  );
178
174
  await hook.unmount();
@@ -243,9 +239,7 @@ describe('storage entry query hooks', () => {
243
239
 
244
240
  it('resets every selected-storage preview variant but refetches only the active first page', async () => {
245
241
  globalThis.IS_REACT_ACT_ENVIRONMENT = true;
246
- const request = vi.fn(({ payload }) =>
247
- Promise.resolve(response(payload.cursor)),
248
- );
242
+ const request = vi.fn(({ payload }) => Promise.resolve(response(payload.cursor)));
249
243
  const hook = await renderPreviewHook({
250
244
  client: asClient(request),
251
245
  target,
@@ -255,28 +249,22 @@ describe('storage entry query hooks', () => {
255
249
  await act(async () => {
256
250
  await vi.waitFor(() => expect(hook.result.isSuccess).toBe(true));
257
251
  });
258
- hook.queryClient.setQueryData(
259
- storageEntryPreviewQueryKey(target, 'inactive', 'ascending'),
260
- { pages: [response(undefined)], pageParams: [undefined] },
261
- );
252
+ hook.queryClient.setQueryData(storageEntryPreviewQueryKey(target, 'inactive', 'ascending'), {
253
+ pages: [response(undefined)],
254
+ pageParams: [undefined],
255
+ });
262
256
 
263
- await act(async () =>
264
- resetSelectedStorageQueries(hook.queryClient, target),
265
- );
257
+ await act(async () => resetSelectedStorageQueries(hook.queryClient, target));
266
258
  expect(request).toHaveBeenCalledTimes(2);
267
259
  expect(
268
- hook.queryClient.getQueryData(
269
- storageEntryPreviewQueryKey(target, 'inactive', 'ascending'),
270
- ),
260
+ hook.queryClient.getQueryData(storageEntryPreviewQueryKey(target, 'inactive', 'ascending')),
271
261
  ).toBeUndefined();
272
262
  await hook.unmount();
273
263
  });
274
264
 
275
265
  it('invalidates only the changed storage preview prefix and drops its stale full value', async () => {
276
266
  globalThis.IS_REACT_ACT_ENVIRONMENT = true;
277
- const request = vi.fn(({ payload }) =>
278
- Promise.resolve(response(payload.cursor)),
279
- );
267
+ const request = vi.fn(({ payload }) => Promise.resolve(response(payload.cursor)));
280
268
  const hook = await renderPreviewHook({
281
269
  client: asClient(request),
282
270
  target,
@@ -286,11 +274,7 @@ describe('storage entry query hooks', () => {
286
274
  await act(async () => {
287
275
  await vi.waitFor(() => expect(hook.result.isSuccess).toBe(true));
288
276
  });
289
- const otherQueryKey = storageEntryPreviewQueryKey(
290
- otherTarget,
291
- 'other',
292
- 'ascending',
293
- );
277
+ const otherQueryKey = storageEntryPreviewQueryKey(otherTarget, 'other', 'ascending');
294
278
  hook.queryClient.setQueryDefaults(otherQueryKey, { gcTime: Infinity });
295
279
  hook.queryClient.setQueryData(otherQueryKey, {
296
280
  pages: [response(undefined)],
@@ -1,10 +1,6 @@
1
1
  import { Button, ConfirmDialog, Dialog, Field, Input } from '@rozenite/ui';
2
2
  import { useEffect, useState } from 'react';
3
- import type {
4
- StorageEntry,
5
- StorageEntryType,
6
- StorageEntryValue,
7
- } from '../shared/types';
3
+ import type { StorageEntry, StorageEntryType, StorageEntryValue } from '../shared/types';
8
4
  import { TypedValueEditor } from './typed-value-editor';
9
5
  import { defaultValueForType } from './type-conversion';
10
6
 
@@ -49,9 +45,7 @@ export const AddEntryDialog = ({
49
45
  const [currentValue, setCurrentValue] = useState<StorageEntryValue | null>(
50
46
  defaultValueForType(initialType),
51
47
  );
52
- const [alert, setAlert] = useState<{ title: string; message: string } | null>(
53
- null,
54
- );
48
+ const [alert, setAlert] = useState<{ title: string; message: string } | null>(null);
55
49
 
56
50
  // Reset state every time the dialog opens, so a previous session's
57
51
  // type / value doesn't bleed in.
@@ -96,19 +90,14 @@ export const AddEntryDialog = ({
96
90
  };
97
91
 
98
92
  const handleKeyDown = (event: React.KeyboardEvent) => {
99
- if (
100
- event.key === 'Enter' &&
101
- newEntryKey.trim() &&
102
- currentType !== 'buffer'
103
- ) {
93
+ if (event.key === 'Enter' && newEntryKey.trim() && currentType !== 'buffer') {
104
94
  handleAdd();
105
95
  }
106
96
  };
107
97
 
108
98
  // Unsavable when no key, no supported type, or when the value is
109
99
  // null — the hex editor signals invalid / empty hex via null.
110
- const isAddDisabled =
111
- !newEntryKey.trim() || !isCurrentTypeSupported || currentValue === null;
100
+ const isAddDisabled = !newEntryKey.trim() || !isCurrentTypeSupported || currentValue === null;
112
101
 
113
102
  return (
114
103
  <>
@@ -1,9 +1,4 @@
1
- import {
2
- base64ToBytes,
3
- bytesToBase64,
4
- bytesToGroupedHex,
5
- hexInputToBytes,
6
- } from './binary';
1
+ import { base64ToBytes, bytesToBase64, bytesToGroupedHex, hexInputToBytes } from './binary';
7
2
 
8
3
  export type EditorMode = 'hex' | 'base64';
9
4
 
@@ -20,9 +15,7 @@ export type EditorAction =
20
15
  | { type: 'switch-mode'; mode: EditorMode }
21
16
  | { type: 'replace-bytes'; bytes: number[] };
22
17
 
23
- export type Validation =
24
- | { ok: true; bytes: number[] }
25
- | { ok: false; reason: string };
18
+ export type Validation = { ok: true; bytes: number[] } | { ok: false; reason: string };
26
19
 
27
20
  const encode = (bytes: readonly number[], mode: EditorMode): string =>
28
21
  mode === 'hex' ? bytesToGroupedHex(bytes) : bytesToBase64(bytes);
@@ -30,20 +23,12 @@ const encode = (bytes: readonly number[], mode: EditorMode): string =>
30
23
  const parse = (text: string, mode: EditorMode) =>
31
24
  mode === 'hex' ? hexInputToBytes(text) : base64ToBytes(text);
32
25
 
33
- const parsedToState = (
34
- text: string,
35
- mode: EditorMode,
36
- ): Pick<EditorState, 'bytes' | 'error'> => {
26
+ const parsedToState = (text: string, mode: EditorMode): Pick<EditorState, 'bytes' | 'error'> => {
37
27
  const result = parse(text, mode);
38
- return result.ok
39
- ? { bytes: result.value, error: null }
40
- : { bytes: null, error: result.error };
28
+ return result.ok ? { bytes: result.value, error: null } : { bytes: null, error: result.error };
41
29
  };
42
30
 
43
- export const initialState = (args: {
44
- initialBytes?: number[];
45
- mode?: EditorMode;
46
- }): EditorState => {
31
+ export const initialState = (args: { initialBytes?: number[]; mode?: EditorMode }): EditorState => {
47
32
  const mode = args.mode ?? 'hex';
48
33
  if (args.initialBytes && args.initialBytes.length > 0) {
49
34
  return {
@@ -61,10 +46,7 @@ export const initialState = (args: {
61
46
  };
62
47
  };
63
48
 
64
- export const reduce = (
65
- state: EditorState,
66
- action: EditorAction,
67
- ): EditorState => {
49
+ export const reduce = (state: EditorState, action: EditorAction): EditorState => {
68
50
  switch (action.type) {
69
51
  case 'set-text': {
70
52
  return {
@@ -4,11 +4,7 @@ import { EditorView, keymap } from '@codemirror/view';
4
4
  import { cn } from '@rozenite/ui';
5
5
  import { useEffect, useReducer, useRef, useState } from 'react';
6
6
  import { compactAsciiPreview } from './binary';
7
- import {
8
- initialState,
9
- reduce,
10
- type EditorMode,
11
- } from './binary-value-editor-state';
7
+ import { initialState, reduce, type EditorMode } from './binary-value-editor-state';
12
8
 
13
9
  export type BinaryValueEditorProps = {
14
10
  initialBytes?: number[];
@@ -57,13 +53,8 @@ const ModeButton = ({
57
53
  </button>
58
54
  );
59
55
 
60
- export const BinaryValueEditor = ({
61
- initialBytes,
62
- onChange,
63
- }: BinaryValueEditorProps) => {
64
- const [state, dispatch] = useReducer(reduce, undefined, () =>
65
- initialState({}),
66
- );
56
+ export const BinaryValueEditor = ({ initialBytes, onChange }: BinaryValueEditorProps) => {
57
+ const [state, dispatch] = useReducer(reduce, undefined, () => initialState({}));
67
58
  const initialBytesRef = useRef(initialBytes);
68
59
  const [isPreparing, setIsPreparing] = useState(
69
60
  Boolean(initialBytesRef.current && initialBytesRef.current.length > 0),
@@ -108,9 +99,7 @@ export const BinaryValueEditor = ({
108
99
  if (!update.docChanged) return;
109
100
  const newText = update.state.doc.toString();
110
101
  if (newText === stateRef.current.text) return;
111
- const isPaste = update.transactions.some((tr) =>
112
- tr.isUserEvent('input.paste'),
113
- );
102
+ const isPaste = update.transactions.some((tr) => tr.isUserEvent('input.paste'));
114
103
  dispatch({
115
104
  type: isPaste ? 'normalize-paste' : 'set-text',
116
105
  text: newText,
@@ -181,8 +170,7 @@ export const BinaryValueEditor = ({
181
170
  <div className="text-muted-foreground">{byteCount} bytes</div>
182
171
  {asciiPreview && (
183
172
  <div className="overflow-hidden text-ellipsis whitespace-nowrap text-muted-foreground">
184
- ASCII:{' '}
185
- <span className="font-mono text-foreground">{asciiPreview}</span>
173
+ ASCII: <span className="font-mono text-foreground">{asciiPreview}</span>
186
174
  </div>
187
175
  )}
188
176
  {state.error && <div className="text-destructive">{state.error}</div>}
package/src/ui/binary.ts CHANGED
@@ -1,6 +1,4 @@
1
- export type ParseResult<T> =
2
- | { ok: true; value: T }
3
- | { ok: false; error: string };
1
+ export type ParseResult<T> = { ok: true; value: T } | { ok: false; error: string };
4
2
 
5
3
  const BYTES_PER_LINE = 16;
6
4
  const BYTES_PER_GROUP = 8;
@@ -11,13 +9,10 @@ const OFFSET_WIDTH = 8;
11
9
  const ASCII_PRINTABLE_MIN = 0x20;
12
10
  const ASCII_PRINTABLE_MAX = 0x7e;
13
11
 
14
- const toHexPair = (byte: number): string =>
15
- byte.toString(16).toUpperCase().padStart(2, '0');
12
+ const toHexPair = (byte: number): string => byte.toString(16).toUpperCase().padStart(2, '0');
16
13
 
17
14
  const toAsciiChar = (byte: number): string =>
18
- byte >= ASCII_PRINTABLE_MIN && byte <= ASCII_PRINTABLE_MAX
19
- ? String.fromCharCode(byte)
20
- : '.';
15
+ byte >= ASCII_PRINTABLE_MIN && byte <= ASCII_PRINTABLE_MAX ? String.fromCharCode(byte) : '.';
21
16
 
22
17
  const formatHexLine = (slice: readonly number[]): string => {
23
18
  const left = slice.slice(0, BYTES_PER_GROUP).map(toHexPair).join(' ');
@@ -33,10 +28,7 @@ export type HexdumpRow = {
33
28
 
34
29
  // Produces one visible hexdump row. Unlike bytesToHexdump this never builds a
35
30
  // representation for bytes outside the requested row.
36
- export const formatHexdumpRow = (
37
- bytes: readonly number[],
38
- rowStart: number,
39
- ): HexdumpRow => {
31
+ export const formatHexdumpRow = (bytes: readonly number[], rowStart: number): HexdumpRow => {
40
32
  let left = '';
41
33
  let right = '';
42
34
  let ascii = '';
@@ -82,10 +74,7 @@ export const bytesToHexdump = (bytes: readonly number[]): string => {
82
74
  export const bytesToAsciiPreview = (bytes: readonly number[]): string =>
83
75
  bytes.map(toAsciiChar).join('');
84
76
 
85
- export const compactAsciiPreview = (
86
- bytes: readonly number[],
87
- maxBytes = 64,
88
- ): string => {
77
+ export const compactAsciiPreview = (bytes: readonly number[], maxBytes = 64): string => {
89
78
  let preview = '';
90
79
  const limit = Math.min(bytes.length, maxBytes);
91
80
  for (let index = 0; index < limit; index++) {
@@ -133,9 +122,7 @@ const TRAILING_ASCII_COLUMN_RE = /\|[^|]*\|\s*$/;
133
122
  export const hexInputToBytes = (input: string): ParseResult<number[]> => {
134
123
  const cleaned = input
135
124
  .split(/\r?\n/)
136
- .map((line) =>
137
- line.replace(TRAILING_ASCII_COLUMN_RE, '').replace(HEXDUMP_OFFSET_RE, ''),
138
- )
125
+ .map((line) => line.replace(TRAILING_ASCII_COLUMN_RE, '').replace(HEXDUMP_OFFSET_RE, ''))
139
126
  .join('')
140
127
  .replace(/0x/gi, '')
141
128
  .replace(/\s+/g, '');
@@ -1,10 +1,6 @@
1
1
  import { Button, ConfirmDialog, Dialog, Field } from '@rozenite/ui';
2
2
  import { useEffect, useRef, useState } from 'react';
3
- import type {
4
- StorageEntry,
5
- StorageEntryType,
6
- StorageEntryValue,
7
- } from '../shared/types';
3
+ import type { StorageEntry, StorageEntryType, StorageEntryValue } from '../shared/types';
8
4
  import { TypedValueEditor } from './typed-value-editor';
9
5
  import { defaultValueForType } from './type-conversion';
10
6
 
@@ -32,9 +28,7 @@ export const EditEntryDialog = ({
32
28
  const [currentValue, setCurrentValue] = useState<StorageEntryValue | null>(
33
29
  defaultValueForType('string'),
34
30
  );
35
- const [alert, setAlert] = useState<{ title: string; message: string } | null>(
36
- null,
37
- );
31
+ const [alert, setAlert] = useState<{ title: string; message: string } | null>(null);
38
32
 
39
33
  useEffect(() => {
40
34
  if (entry && isOpen) {
@@ -103,9 +97,7 @@ export const EditEntryDialog = ({
103
97
  <div className="h-8 w-full truncate rounded-md border border-input bg-muted px-3 py-1.5 font-mono text-sm text-foreground">
104
98
  {entryForDisplay.key}
105
99
  </div>
106
- <Field.Description>
107
- Key cannot be changed during editing
108
- </Field.Description>
100
+ <Field.Description>Key cannot be changed during editing</Field.Description>
109
101
  </Field>
110
102
 
111
103
  <Field>
@@ -10,12 +10,7 @@ const TYPE_LABELS: Record<StorageEntryType, string> = {
10
10
 
11
11
  // Visual ordering of the pills. Keep `string` first because it's the
12
12
  // most common landing type after a fresh read on an MMKV key.
13
- const TYPE_ORDER: StorageEntryType[] = [
14
- 'string',
15
- 'number',
16
- 'boolean',
17
- 'buffer',
18
- ];
13
+ const TYPE_ORDER: StorageEntryType[] = ['string', 'number', 'boolean', 'buffer'];
19
14
 
20
15
  export type EditorSwitcherProps = {
21
16
  supportedTypes: StorageEntryType[];
@@ -23,11 +18,7 @@ export type EditorSwitcherProps = {
23
18
  onChange: (type: StorageEntryType) => void;
24
19
  };
25
20
 
26
- export const EditorSwitcher = ({
27
- supportedTypes,
28
- value,
29
- onChange,
30
- }: EditorSwitcherProps) => {
21
+ export const EditorSwitcher = ({ supportedTypes, value, onChange }: EditorSwitcherProps) => {
31
22
  const available = TYPE_ORDER.filter((type) => supportedTypes.includes(type));
32
23
 
33
24
  // Adaptive hide: the switcher is meaningless when the backend only
@@ -14,9 +14,7 @@ export type EntryDetailDialogProps = {
14
14
 
15
15
  const MAX_AUTO_JSON_INSPECTION_LENGTH = 50_000;
16
16
 
17
- export const jsonSafeParse = (
18
- value: string,
19
- ): Record<string, unknown> | unknown[] | null => {
17
+ export const jsonSafeParse = (value: string): Record<string, unknown> | unknown[] | null => {
20
18
  try {
21
19
  const parsed = JSON.parse(value) as unknown;
22
20
 
@@ -63,33 +61,26 @@ export const EntryDetailDialog = ({
63
61
  {entry && (
64
62
  <div className="flex max-h-[70vh] flex-col gap-4 overflow-auto">
65
63
  <div>
66
- <div className="mb-1 text-sm font-medium text-foreground">
67
- Key
68
- </div>
64
+ <div className="mb-1 text-sm font-medium text-foreground">Key</div>
69
65
  <div className="w-full break-all rounded-md border border-input bg-muted px-3 py-2 font-mono text-sm text-foreground">
70
66
  {entry.key}
71
67
  </div>
72
68
  </div>
73
69
 
74
70
  <div>
75
- <div className="mb-1 text-sm font-medium text-foreground">
76
- Type
77
- </div>
71
+ <div className="mb-1 text-sm font-medium text-foreground">Type</div>
78
72
  <Badge variant="outline">{entry.type}</Badge>
79
73
  </div>
80
74
 
81
75
  <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>
76
+ <div className="mb-1 text-sm font-medium text-foreground">Value</div>
85
77
  <div className="max-h-96 overflow-auto rounded-md border border-input bg-muted p-3">
86
78
  {jsonValue ? (
87
79
  <JsonInspector data={jsonValue} />
88
80
  ) : entry.type === 'buffer' ? (
89
81
  <div className="flex flex-col gap-2">
90
82
  <div className="text-xs text-muted-foreground">
91
- {entry.value.length}{' '}
92
- {entry.value.length === 1 ? 'byte' : 'bytes'}
83
+ {entry.value.length} {entry.value.length === 1 ? 'byte' : 'bytes'}
93
84
  </div>
94
85
  <HexdumpValueViewer bytes={entry.value} />
95
86
  </div>
@@ -13,20 +13,13 @@ export const formatValue = (entry: StorageEntry) => {
13
13
 
14
14
  if (entry.type === 'boolean') {
15
15
  return (
16
- <span
17
- className={cn(
18
- 'font-mono',
19
- entry.value ? 'text-primary' : 'text-destructive',
20
- )}
21
- >
16
+ <span className={cn('font-mono', entry.value ? 'text-primary' : 'text-destructive')}>
22
17
  {entry.value ? 'true' : 'false'}
23
18
  </span>
24
19
  );
25
20
  }
26
21
 
27
22
  return (
28
- <span className="font-mono text-muted-foreground">
29
- {compactBufferPreview(entry.value)}
30
- </span>
23
+ <span className="font-mono text-muted-foreground">{compactBufferPreview(entry.value)}</span>
31
24
  );
32
25
  };
@@ -82,8 +82,8 @@ const PreviewBody = ({
82
82
  <div className="flex items-start gap-2 rounded-md border border-border bg-muted p-2 text-xs text-foreground">
83
83
  <AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
84
84
  <div>
85
- This file was exported from <strong>{sourceLabel}</strong>. You
86
- are importing into <strong>{targetLabel}</strong>.
85
+ This file was exported from <strong>{sourceLabel}</strong>. You are importing into{' '}
86
+ <strong>{targetLabel}</strong>.
87
87
  </div>
88
88
  </div>
89
89
  )}
@@ -93,15 +93,10 @@ const PreviewBody = ({
93
93
  <XCircle className="mt-0.5 h-4 w-4 shrink-0" />
94
94
  <div>
95
95
  {preview.unsupportedTypes.length}{' '}
96
- {preview.unsupportedTypes.length === 1
97
- ? 'entry has a type'
98
- : 'entries have types'}{' '}
99
- not supported by this storage. Remove them from the file and try
100
- again.
96
+ {preview.unsupportedTypes.length === 1 ? 'entry has a type' : 'entries have types'}{' '}
97
+ not supported by this storage. Remove them from the file and try again.
101
98
  <div className="mt-1 max-h-20 overflow-auto rounded-md bg-muted px-2 py-1 font-mono text-xs">
102
- {preview.unsupportedTypes
103
- .map((u) => `${u.key} (${u.type})`)
104
- .join(', ')}
99
+ {preview.unsupportedTypes.map((u) => `${u.key} (${u.type})`).join(', ')}
105
100
  </div>
106
101
  </div>
107
102
  </div>
@@ -191,12 +186,7 @@ const ResultBody = ({
191
186
  </>
192
187
  );
193
188
 
194
- export const ImportDialog = ({
195
- state,
196
- onApply,
197
- onCancel,
198
- onClose,
199
- }: ImportDialogProps) => {
189
+ export const ImportDialog = ({ state, onApply, onCancel, onClose }: ImportDialogProps) => {
200
190
  const title =
201
191
  state === null
202
192
  ? ''
@@ -227,9 +217,7 @@ export const ImportDialog = ({
227
217
  <PreviewBody state={state} onApply={onApply} onCancel={onCancel} />
228
218
  )}
229
219
  {state?.phase === 'importing' && <ImportingBody state={state} />}
230
- {state?.phase === 'result' && (
231
- <ResultBody state={state} onClose={onClose} />
232
- )}
220
+ {state?.phase === 'result' && <ResultBody state={state} onClose={onClose} />}
233
221
  </Dialog.Content>
234
222
  </Dialog>
235
223
  );
@@ -14,15 +14,8 @@ export const textRowRanges = (text: string): TextRange[] => {
14
14
  const newline = text.indexOf('\n', start);
15
15
  const lineEnd = newline === -1 ? text.length : newline + 1;
16
16
 
17
- for (
18
- let chunkStart = start;
19
- chunkStart < lineEnd;
20
- chunkStart += TEXT_CHUNK_SIZE
21
- ) {
22
- ranges.push([
23
- chunkStart,
24
- Math.min(chunkStart + TEXT_CHUNK_SIZE, lineEnd),
25
- ]);
17
+ for (let chunkStart = start; chunkStart < lineEnd; chunkStart += TEXT_CHUNK_SIZE) {
18
+ ranges.push([chunkStart, Math.min(chunkStart + TEXT_CHUNK_SIZE, lineEnd)]);
26
19
  }
27
20
 
28
21
  start = lineEnd;
@@ -1,15 +1,11 @@
1
1
  import { useMemo } from 'react';
2
2
  import { Virtuoso } from 'react-virtuoso';
3
3
  import { formatHexdumpRow } from './binary';
4
- import {
5
- textRowRanges,
6
- VIRTUALIZED_TEXT_THRESHOLD,
7
- } from './large-value-viewer-state';
4
+ import { textRowRanges, VIRTUALIZED_TEXT_THRESHOLD } from './large-value-viewer-state';
8
5
 
9
6
  export const TextValueViewer = ({ value }: { value: string }) => {
10
7
  const ranges = useMemo(
11
- () =>
12
- value.length > VIRTUALIZED_TEXT_THRESHOLD ? textRowRanges(value) : null,
8
+ () => (value.length > VIRTUALIZED_TEXT_THRESHOLD ? textRowRanges(value) : null),
13
9
  [value],
14
10
  );
15
11
 
@@ -45,9 +41,7 @@ export const HexdumpValueViewer = ({ bytes }: { bytes: readonly number[] }) => {
45
41
  const totalCount = Math.ceil(bytes.length / 16);
46
42
 
47
43
  if (totalCount === 0) {
48
- return (
49
- <div className="text-xs text-muted italic">No bytes to display.</div>
50
- );
44
+ return <div className="text-xs text-muted italic">No bytes to display.</div>;
51
45
  }
52
46
 
53
47
  return (