@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
package/src/ui/panel.tsx CHANGED
@@ -1,9 +1,5 @@
1
1
  import { useQueryClient } from '@tanstack/react-query';
2
- import type {
3
- ColumnDef,
4
- OnChangeFn,
5
- SortingState,
6
- } from '@tanstack/react-table';
2
+ import type { ColumnDef, OnChangeFn, SortingState } from '@tanstack/react-table';
7
3
  import { useRozeniteDevToolsClient } from '@rozenite/plugin-bridge';
8
4
  import {
9
5
  Badge,
@@ -18,15 +14,7 @@ import {
18
14
  VirtualizedDataTable,
19
15
  } from '@rozenite/ui';
20
16
  import { useEffect, useMemo, useRef, useState, type ChangeEvent } from 'react';
21
- import {
22
- Database,
23
- Download,
24
- Edit3,
25
- Plus,
26
- RefreshCw,
27
- Trash2,
28
- Upload,
29
- } from 'lucide-react';
17
+ import { Database, Download, Edit3, Plus, RefreshCw, Trash2, Upload } from 'lucide-react';
30
18
  import type {
31
19
  StorageDiscoverStoragesResponseEvent,
32
20
  StorageEventMap,
@@ -66,9 +54,7 @@ type FullEntryInteraction = { key: string; mode: 'detail' | 'edit' };
66
54
  const sameTarget = (a: StorageTarget, b: StorageTarget) =>
67
55
  a.adapterId === b.adapterId && a.storageId === b.storageId;
68
56
 
69
- const getEntryTypeFromValue = (
70
- value: StorageEntryValue,
71
- ): StorageEntry['type'] => {
57
+ const getEntryTypeFromValue = (value: StorageEntryValue): StorageEntry['type'] => {
72
58
  if (typeof value === 'string') return 'string';
73
59
  if (typeof value === 'number') return 'number';
74
60
  if (typeof value === 'boolean') return 'boolean';
@@ -88,28 +74,18 @@ const useDebouncedValue = (value: string, delay = 250) => {
88
74
 
89
75
  function StoragePanelContent() {
90
76
  const [descriptors, setDescriptors] = useState<StorageDescriptor[]>([]);
91
- const [selectedTarget, setSelectedTarget] = useState<StorageTarget | null>(
92
- null,
93
- );
77
+ const [selectedTarget, setSelectedTarget] = useState<StorageTarget | null>(null);
94
78
  const [searchTerm, setSearchTerm] = useState('');
95
- const [keySortDirection, setKeySortDirection] = useState<
96
- 'ascending' | 'descending'
97
- >('ascending');
98
- const [interaction, setInteraction] = useState<FullEntryInteraction | null>(
99
- null,
100
- );
79
+ const [keySortDirection, setKeySortDirection] = useState<'ascending' | 'descending'>('ascending');
80
+ const [interaction, setInteraction] = useState<FullEntryInteraction | null>(null);
101
81
  const [showAddDialog, setShowAddDialog] = useState(false);
102
82
  const [deleteKey, setDeleteKey] = useState<string | null>(null);
103
83
  const [showPurgeDialog, setShowPurgeDialog] = useState(false);
104
84
  const [virtualListVersion, setVirtualListVersion] = useState(0);
105
85
  const [exportState, setExportState] = useState<
106
- | { status: 'idle' }
107
- | { status: 'loading' }
108
- | { status: 'error'; message: string }
86
+ { status: 'idle' } | { status: 'loading' } | { status: 'error'; message: string }
109
87
  >({ status: 'idle' });
110
- const [importFlight, setImportFlight] = useState<ImportFlightState | null>(
111
- null,
112
- );
88
+ const [importFlight, setImportFlight] = useState<ImportFlightState | null>(null);
113
89
  const [alertState, setAlertState] = useState<AlertState | null>(null);
114
90
  const fileInputRef = useRef<HTMLInputElement | null>(null);
115
91
  const selectedTargetRef = useRef<StorageTarget | null>(null);
@@ -127,8 +103,7 @@ function StoragePanelContent() {
127
103
  const queryClient = useQueryClient();
128
104
  const debouncedSearch = useDebouncedValue(searchTerm);
129
105
  const selectedDescriptor = descriptors.find(
130
- (descriptor) =>
131
- selectedTarget && sameTarget(descriptor.target, selectedTarget),
106
+ (descriptor) => selectedTarget && sameTarget(descriptor.target, selectedTarget),
132
107
  );
133
108
  const previews = useStorageEntryPreviews({
134
109
  client,
@@ -146,10 +121,7 @@ function StoragePanelContent() {
146
121
  const supportedTypes = selectedDescriptor?.capabilities.supportedTypes ?? [];
147
122
  const sidebarGroups = useMemo(() => {
148
123
  const storages = new Map(
149
- descriptors.map((descriptor) => [
150
- getStorageViewId(descriptor.target),
151
- descriptor,
152
- ]),
124
+ descriptors.map((descriptor) => [getStorageViewId(descriptor.target), descriptor]),
153
125
  );
154
126
  return buildStorageSidebarGroups(storages);
155
127
  }, [descriptors]);
@@ -169,17 +141,21 @@ function StoragePanelContent() {
169
141
  useEffect(() => {
170
142
  if (!client) return;
171
143
 
144
+ const requestDiscovery = () => {
145
+ discoveryRequestIdRef.current += 1;
146
+ client.send('discover-storages', {
147
+ type: 'discover-storages',
148
+ requestId: `discovery-${discoveryRequestIdRef.current}`,
149
+ });
150
+ };
151
+
172
152
  const descriptorsSubscription = client.onMessage(
173
153
  'storage-descriptors',
174
154
  (event: StorageDiscoverStoragesResponseEvent) => {
175
- if (event.requestId !== `discovery-${discoveryRequestIdRef.current}`)
176
- return;
155
+ if (event.requestId !== `discovery-${discoveryRequestIdRef.current}`) return;
177
156
  setDescriptors(event.storages);
178
157
  setSelectedTarget((previous) =>
179
- previous &&
180
- event.storages.some((descriptor) =>
181
- sameTarget(descriptor.target, previous),
182
- )
158
+ previous && event.storages.some((descriptor) => sameTarget(descriptor.target, previous))
183
159
  ? previous
184
160
  : (event.storages[0]?.target ?? null),
185
161
  );
@@ -234,23 +210,16 @@ function StoragePanelContent() {
234
210
  'storage-invalidated',
235
211
  (event: StorageInvalidatedEvent) => {
236
212
  void dropStorageFullEntries(queryClient, event.target, event.key);
237
- if (event.operation === 'purge') {
238
- setDescriptors((current) =>
239
- current.map((descriptor) =>
240
- sameTarget(descriptor.target, event.target)
241
- ? { ...descriptor, entryCount: 0 }
242
- : descriptor,
243
- ),
244
- );
245
- }
246
- if (
247
- selectedTargetRef.current &&
248
- sameTarget(event.target, selectedTargetRef.current)
249
- ) {
213
+ setDescriptors((current) =>
214
+ current.map((descriptor) =>
215
+ sameTarget(descriptor.target, event.target)
216
+ ? { ...descriptor, entryCount: event.entryCount }
217
+ : descriptor,
218
+ ),
219
+ );
220
+ if (selectedTargetRef.current && sameTarget(event.target, selectedTargetRef.current)) {
250
221
  setInteraction((current) =>
251
- current && (event.key == null || current.key === event.key)
252
- ? null
253
- : current,
222
+ current && (event.key == null || current.key === event.key) ? null : current,
254
223
  );
255
224
  }
256
225
 
@@ -259,10 +228,7 @@ function StoragePanelContent() {
259
228
  pendingInvalidationsRef.current.add(targetId);
260
229
  queueMicrotask(() => {
261
230
  pendingInvalidationsRef.current.delete(targetId);
262
- if (
263
- selectedTargetRef.current &&
264
- sameTarget(event.target, selectedTargetRef.current)
265
- ) {
231
+ if (selectedTargetRef.current && sameTarget(event.target, selectedTargetRef.current)) {
266
232
  setVirtualListVersion((version) => version + 1);
267
233
  }
268
234
  void invalidateStorageEntryPreviews(queryClient, event.target);
@@ -270,16 +236,21 @@ function StoragePanelContent() {
270
236
  },
271
237
  );
272
238
 
273
- discoveryRequestIdRef.current += 1;
274
- client.send('discover-storages', {
275
- type: 'discover-storages',
276
- requestId: `discovery-${discoveryRequestIdRef.current}`,
239
+ // The device announces itself once it is listening. It reconnects on every
240
+ // app reload, so discovery has to run again — the descriptors and cached
241
+ // entries the panel holds belong to the previous JS context.
242
+ const deviceReadySubscription = client.onMessage('device-ready', () => {
243
+ void queryClient.resetQueries();
244
+ requestDiscovery();
277
245
  });
246
+
247
+ requestDiscovery();
278
248
  return () => {
279
249
  descriptorsSubscription.remove();
280
250
  importProgressSubscription.remove();
281
251
  importResultSubscription.remove();
282
252
  invalidationSubscription.remove();
253
+ deviceReadySubscription.remove();
283
254
  };
284
255
  }, [client, queryClient]);
285
256
 
@@ -310,11 +281,7 @@ function StoragePanelContent() {
310
281
  };
311
282
 
312
283
  const handleValueChange = (key: string, newValue: StorageEntryValue) => {
313
- if (
314
- !client ||
315
- !selectedTarget ||
316
- !supportedTypes.includes(getEntryTypeFromValue(newValue))
317
- )
284
+ if (!client || !selectedTarget || !supportedTypes.includes(getEntryTypeFromValue(newValue)))
318
285
  return;
319
286
  const type = getEntryTypeFromValue(newValue);
320
287
  const entry =
@@ -367,8 +334,7 @@ function StoragePanelContent() {
367
334
  });
368
335
  };
369
336
 
370
- const showAlert = (title: string, message: string) =>
371
- setAlertState({ title, message });
337
+ const showAlert = (title: string, message: string) => setAlertState({ title, message });
372
338
 
373
339
  const handleImportClick = () => {
374
340
  if (fileInputRef.current) {
@@ -384,18 +350,12 @@ function StoragePanelContent() {
384
350
  try {
385
351
  raw = JSON.parse(await file.text());
386
352
  } catch (error) {
387
- showAlert(
388
- 'Could not read file',
389
- error instanceof Error ? error.message : String(error),
390
- );
353
+ showAlert('Could not read file', error instanceof Error ? error.message : String(error));
391
354
  return;
392
355
  }
393
356
  const parsed = parseSnapshot(raw);
394
357
  if (!parsed.ok) {
395
- showAlert(
396
- 'Invalid snapshot',
397
- `${parsed.error.path}: ${parsed.error.message}`,
398
- );
358
+ showAlert('Invalid snapshot', `${parsed.error.path}: ${parsed.error.message}`);
399
359
  return;
400
360
  }
401
361
  importPreviewAbortControllerRef.current?.abort();
@@ -410,10 +370,7 @@ function StoragePanelContent() {
410
370
  payload: { type: 'preview-import', target, snapshot: parsed.snapshot },
411
371
  signal: controller.signal,
412
372
  });
413
- if (
414
- controller.signal.aborted ||
415
- !sameTarget(target, selectedTargetRef.current ?? target)
416
- )
373
+ if (controller.signal.aborted || !sameTarget(target, selectedTargetRef.current ?? target))
417
374
  return;
418
375
  setImportFlight({
419
376
  phase: 'preview',
@@ -428,10 +385,7 @@ function StoragePanelContent() {
428
385
  ),
429
386
  });
430
387
  } catch {
431
- if (
432
- !controller.signal.aborted &&
433
- sameTarget(target, selectedTargetRef.current ?? target)
434
- ) {
388
+ if (!controller.signal.aborted && sameTarget(target, selectedTargetRef.current ?? target)) {
435
389
  showAlert(
436
390
  'Could not preview import',
437
391
  'Could not inspect the selected storage. Please try again.',
@@ -476,26 +430,19 @@ function StoragePanelContent() {
476
430
  payload: { type: 'export-snapshot', target },
477
431
  signal: controller.signal,
478
432
  });
479
- if (
480
- !controller.signal.aborted &&
481
- sameTarget(target, selectedTargetRef.current ?? target)
482
- ) {
433
+ if (!controller.signal.aborted && sameTarget(target, selectedTargetRef.current ?? target)) {
483
434
  downloadJson(response.snapshot, buildExportFilename(target));
484
435
  setExportState({ status: 'idle' });
485
436
  }
486
437
  } catch {
487
- if (
488
- !controller.signal.aborted &&
489
- sameTarget(target, selectedTargetRef.current ?? target)
490
- ) {
438
+ if (!controller.signal.aborted && sameTarget(target, selectedTargetRef.current ?? target)) {
491
439
  setExportState({
492
440
  status: 'error',
493
441
  message: 'Could not export the selected storage. Please try again.',
494
442
  });
495
443
  }
496
444
  } finally {
497
- if (exportAbortControllerRef.current === controller)
498
- exportAbortControllerRef.current = null;
445
+ if (exportAbortControllerRef.current === controller) exportAbortControllerRef.current = null;
499
446
  }
500
447
  };
501
448
 
@@ -507,9 +454,7 @@ function StoragePanelContent() {
507
454
  header: 'Key',
508
455
  enableSorting: true,
509
456
  cell: ({ row }) => (
510
- <span className="font-mono text-sm text-foreground">
511
- {row.original.key}
512
- </span>
457
+ <span className="font-mono text-sm text-foreground">{row.original.key}</span>
513
458
  ),
514
459
  },
515
460
  {
@@ -532,9 +477,7 @@ function StoragePanelContent() {
532
477
  <span
533
478
  className="ml-2 text-xs text-muted-foreground"
534
479
  title={
535
- row.original.isTruncated
536
- ? `Full value size: ${row.original.valueSize}`
537
- : undefined
480
+ row.original.isTruncated ? `Full value size: ${row.original.valueSize}` : undefined
538
481
  }
539
482
  >
540
483
  {row.original.isTruncated
@@ -549,16 +492,11 @@ function StoragePanelContent() {
549
492
  header: '',
550
493
  enableSorting: false,
551
494
  cell: ({ row }) => (
552
- <div
553
- className="flex items-center gap-1"
554
- onClick={(event) => event.stopPropagation()}
555
- >
495
+ <div className="flex items-center gap-1" onClick={(event) => event.stopPropagation()}>
556
496
  <Button
557
497
  variant="ghost"
558
498
  size="icon"
559
- onClick={() =>
560
- setInteraction({ key: row.original.key, mode: 'edit' })
561
- }
499
+ onClick={() => setInteraction({ key: row.original.key, mode: 'edit' })}
562
500
  aria-label={`Edit value for ${row.original.key}`}
563
501
  >
564
502
  <Edit3 className="h-3.5 w-3.5" />
@@ -578,9 +516,7 @@ function StoragePanelContent() {
578
516
  ],
579
517
  [],
580
518
  );
581
- const sorting: SortingState = [
582
- { id: 'key', desc: keySortDirection === 'descending' },
583
- ];
519
+ const sorting: SortingState = [{ id: 'key', desc: keySortDirection === 'descending' }];
584
520
  const handleSortingChange: OnChangeFn<SortingState> = (updater) => {
585
521
  const next = typeof updater === 'function' ? updater(sorting) : updater;
586
522
  const keySort = next.find((item) => item.id === 'key');
@@ -612,9 +548,7 @@ function StoragePanelContent() {
612
548
  isFetchingPreviousPageRef.current = false;
613
549
  });
614
550
  };
615
- const selectedStorageViewId = selectedTarget
616
- ? getStorageViewId(selectedTarget)
617
- : '';
551
+ const selectedStorageViewId = selectedTarget ? getStorageViewId(selectedTarget) : '';
618
552
 
619
553
  return (
620
554
  <PluginShell>
@@ -628,22 +562,16 @@ function StoragePanelContent() {
628
562
  </div>
629
563
  ) : (
630
564
  sidebarGroups.map((group) => (
631
- <Sidebar.Group
632
- key={group.adapterId}
633
- label={group.adapterName}
634
- >
565
+ <Sidebar.Group key={group.adapterId} label={group.adapterName}>
635
566
  {group.items.map((item) => (
636
567
  <Sidebar.Item
637
568
  key={item.viewId}
638
569
  selected={item.viewId === selectedStorageViewId}
639
- trailing={
640
- <Badge variant="secondary">{item.entryCount}</Badge>
641
- }
570
+ adornment={<Database />}
571
+ trailing={<Badge variant="secondary">{item.entryCount}</Badge>}
642
572
  onClick={() => {
643
573
  const descriptor = descriptors.find(
644
- (candidate) =>
645
- getStorageViewId(candidate.target) ===
646
- item.viewId,
574
+ (candidate) => getStorageViewId(candidate.target) === item.viewId,
647
575
  );
648
576
  if (descriptor) setSelectedTarget(descriptor.target);
649
577
  }}
@@ -705,20 +633,12 @@ function StoragePanelContent() {
705
633
  </Toolbar.Button>
706
634
  <Toolbar.Button
707
635
  onClick={handleExport}
708
- disabled={
709
- !client ||
710
- !selectedTarget ||
711
- exportState.status === 'loading'
712
- }
636
+ disabled={!client || !selectedTarget || exportState.status === 'loading'}
713
637
  aria-label={
714
- exportState.status === 'loading'
715
- ? 'Exporting storage'
716
- : 'Export storage'
638
+ exportState.status === 'loading' ? 'Exporting storage' : 'Export storage'
717
639
  }
718
640
  title={
719
- exportState.status === 'loading'
720
- ? 'Exporting storage'
721
- : 'Export storage'
641
+ exportState.status === 'loading' ? 'Exporting storage' : 'Export storage'
722
642
  }
723
643
  className="w-7 px-0"
724
644
  >
@@ -759,15 +679,11 @@ function StoragePanelContent() {
759
679
  getRowTextValue={(entry) => entry.key}
760
680
  loading={previews.isLoading}
761
681
  emptyMessage={
762
- debouncedSearch
763
- ? 'No entries match your search.'
764
- : 'This storage is empty.'
682
+ debouncedSearch ? 'No entries match your search.' : 'This storage is empty.'
765
683
  }
766
684
  manualSorting
767
685
  onEndReached={fetchNextPage}
768
- onRowClick={(entry) =>
769
- setInteraction({ key: entry.key, mode: 'detail' })
770
- }
686
+ onRowClick={(entry) => setInteraction({ key: entry.key, mode: 'detail' })}
771
687
  onSortingChange={handleSortingChange}
772
688
  onStartReached={fetchPreviousPage}
773
689
  scrollClassName="h-full w-full overflow-auto"
@@ -799,9 +715,7 @@ function StoragePanelContent() {
799
715
  if (!open) closeInteraction();
800
716
  }}
801
717
  onEdit={() =>
802
- setInteraction((current) =>
803
- current ? { ...current, mode: 'edit' } : current,
804
- )
718
+ setInteraction((current) => (current ? { ...current, mode: 'edit' } : current))
805
719
  }
806
720
  entry={fullEntry.entry ?? null}
807
721
  />
@@ -827,9 +741,7 @@ function StoragePanelContent() {
827
741
  destructive
828
742
  title="Delete Entry"
829
743
  description={
830
- deleteKey
831
- ? `Are you sure you want to delete the entry "${deleteKey}"?`
832
- : undefined
744
+ deleteKey ? `Are you sure you want to delete the entry "${deleteKey}"?` : undefined
833
745
  }
834
746
  confirmLabel="Delete"
835
747
  onConfirm={handleDeleteEntry}
@@ -1,8 +1,4 @@
1
- import {
2
- QueryClient,
3
- QueryClientProvider,
4
- type QueryClientConfig,
5
- } from '@tanstack/react-query';
1
+ import { QueryClient, QueryClientProvider, type QueryClientConfig } from '@tanstack/react-query';
6
2
  import { type ReactNode, useEffect, useState } from 'react';
7
3
 
8
4
  const storageQueryClientConfig: QueryClientConfig = {
@@ -16,19 +12,12 @@ const storageQueryClientConfig: QueryClientConfig = {
16
12
  },
17
13
  };
18
14
 
19
- export const createStorageQueryClient = () =>
20
- new QueryClient(storageQueryClientConfig);
15
+ export const createStorageQueryClient = () => new QueryClient(storageQueryClientConfig);
21
16
 
22
- export const StorageQueryClientProvider = ({
23
- children,
24
- }: {
25
- children: ReactNode;
26
- }) => {
17
+ export const StorageQueryClientProvider = ({ children }: { children: ReactNode }) => {
27
18
  const [queryClient] = useState(createStorageQueryClient);
28
19
 
29
20
  useEffect(() => () => queryClient.clear(), [queryClient]);
30
21
 
31
- return (
32
- <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
33
- );
22
+ return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
34
23
  };
@@ -15,8 +15,7 @@ const tryDecode = (bytes: readonly number[]): string => {
15
15
  }
16
16
  };
17
17
 
18
- const encode = (value: string): number[] =>
19
- Array.from(textEncoder.encode(value));
18
+ const encode = (value: string): number[] => Array.from(textEncoder.encode(value));
20
19
 
21
20
  // Convert a value between storage primitive types for the editor
22
21
  // switcher. The headline transition is `string` ↔ `buffer`, which
@@ -89,9 +88,7 @@ export const convertValue = (
89
88
 
90
89
  // Zero value for a type — used when seeding the add-entry dialog and
91
90
  // when conversion has no meaningful starting point.
92
- export const defaultValueForType = (
93
- type: StorageEntryType,
94
- ): StorageEntryValue => {
91
+ export const defaultValueForType = (type: StorageEntryType): StorageEntryValue => {
95
92
  switch (type) {
96
93
  case 'string':
97
94
  return '';
@@ -43,11 +43,7 @@ export const TypedValueEditor = ({
43
43
 
44
44
  return (
45
45
  <div className="flex flex-col gap-2">
46
- <EditorSwitcher
47
- supportedTypes={supportedTypes}
48
- value={type}
49
- onChange={handleTypeChange}
50
- />
46
+ <EditorSwitcher supportedTypes={supportedTypes} value={type} onChange={handleTypeChange} />
51
47
 
52
48
  {type === 'buffer' ? (
53
49
  <BinaryValueEditor
@@ -8,22 +8,15 @@ import {
8
8
  } from '@tanstack/react-query';
9
9
  import type { RozeniteDevToolsRequestClient } from '@rozenite/plugin-bridge';
10
10
  import { useCallback, useEffect, useMemo } from 'react';
11
- import type {
12
- StorageEventMap,
13
- StorageListEntryPreviewsResponseEvent,
14
- } from '../shared/messaging';
11
+ import type { StorageEventMap, StorageListEntryPreviewsResponseEvent } from '../shared/messaging';
15
12
  import type { StorageEntryPreview, StorageTarget } from '../shared/types';
16
13
 
17
14
  export const STORAGE_ENTRY_PREVIEW_PAGE_SIZE = 100;
18
15
  export const STORAGE_ENTRY_PREVIEW_MAX_PAGES = 5;
19
16
 
20
- type StorageRequestClient = Pick<
21
- RozeniteDevToolsRequestClient<StorageEventMap>,
22
- 'request'
23
- >;
17
+ type StorageRequestClient = Pick<RozeniteDevToolsRequestClient<StorageEventMap>, 'request'>;
24
18
 
25
- export const normalizeStorageKeySearch = (search: string) =>
26
- search.trim().toLowerCase();
19
+ export const normalizeStorageKeySearch = (search: string) => search.trim().toLowerCase();
27
20
 
28
21
  export const storageEntryPreviewQueryKeyPrefix = (target: StorageTarget) =>
29
22
  ['storage-entry-previews', target.adapterId, target.storageId] as const;
@@ -72,9 +65,7 @@ export const dropStorageFullEntries = async (
72
65
  key?: string,
73
66
  ) => {
74
67
  const queryKey =
75
- key == null
76
- ? storageFullEntryQueryKeyPrefix(target)
77
- : storageFullEntryQueryKey(target, key);
68
+ key == null ? storageFullEntryQueryKeyPrefix(target) : storageFullEntryQueryKey(target, key);
78
69
 
79
70
  await queryClient.cancelQueries({ queryKey, exact: key != null });
80
71
  queryClient.removeQueries({ queryKey, exact: key != null });
@@ -101,11 +92,7 @@ export const useStorageEntryPreviews = ({
101
92
  const queryKey = useMemo(
102
93
  () =>
103
94
  target
104
- ? storageEntryPreviewQueryKey(
105
- target,
106
- normalizedSearch,
107
- keySortDirection,
108
- )
95
+ ? storageEntryPreviewQueryKey(target, normalizedSearch, keySortDirection)
109
96
  : (['storage-entry-previews', 'no-target'] as const),
110
97
  [keySortDirection, normalizedSearch, target?.adapterId, target?.storageId],
111
98
  );
@@ -174,9 +161,7 @@ export const useStorageFullEntry = ({
174
161
  gcTime: 0,
175
162
  queryFn: ({ signal }) => {
176
163
  if (!client || !target || key == null) {
177
- throw new Error(
178
- 'A storage target, key, and request client are required.',
179
- );
164
+ throw new Error('A storage target, key, and request client are required.');
180
165
  }
181
166
 
182
167
  return client.request({
package/src/ui/utils.ts CHANGED
@@ -15,10 +15,7 @@ export const downloadJson = (data: unknown, filename: string): void => {
15
15
 
16
16
  const sanitize = (value: string) => value.replace(/[^a-zA-Z0-9_-]/g, '-');
17
17
 
18
- export const buildExportFilename = (
19
- target: StorageTarget,
20
- now: Date = new Date(),
21
- ): string => {
18
+ export const buildExportFilename = (target: StorageTarget, now: Date = new Date()): string => {
22
19
  const yyyy = now.getFullYear().toString().padStart(4, '0');
23
20
  const mm = (now.getMonth() + 1).toString().padStart(2, '0');
24
21
  const dd = now.getDate().toString().padStart(2, '0');
@@ -1 +0,0 @@
1
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-content:"";--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-2xl:42rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--font-weight-medium:500;--font-weight-semibold:600;--leading-snug:1.375;--radius-lg:var(--radius);--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*{border-color:var(--border);outline-color:var(--ring)}@supports (color:color-mix(in lab,red,red)){*{outline-color:color-mix(in oklab,var(--ring) 50%,transparent)}}body{background-color:var(--background);color:var(--foreground);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background-color:var(--border);border-radius:var(--radius-lg);background-clip:padding-box;border:2px solid #0000}::-webkit-scrollbar-thumb:hover{background-color:var(--muted-foreground)}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-0{inset:calc(var(--spacing) * 0)}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.top-1\/2{top:50%}.top-4{top:calc(var(--spacing) * 4)}.right-1\.5{right:calc(var(--spacing) * 1.5)}.right-2{right:calc(var(--spacing) * 2)}.right-4{right:calc(var(--spacing) * 4)}.bottom-4{bottom:calc(var(--spacing) * 4)}.left-0\.5{left:calc(var(--spacing) * .5)}.left-2\.5{left:calc(var(--spacing) * 2.5)}.z-10{z-index:10}.z-50{z-index:50}.row-0{grid-row:0}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.mx-1{margin-inline:calc(var(--spacing) * 1)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-3\.5{margin-left:calc(var(--spacing) * 3.5)}.ml-auto{margin-left:auto}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-3{height:calc(var(--spacing) * 3)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-16{height:calc(var(--spacing) * 16)}.h-full{height:100%}.h-screen{height:100vh}.max-h-20{max-height:calc(var(--spacing) * 20)}.max-h-24{max-height:calc(var(--spacing) * 24)}.max-h-64{max-height:calc(var(--spacing) * 64)}.max-h-96{max-height:calc(var(--spacing) * 96)}.max-h-\[70vh\]{max-height:70vh}.max-h-\[300px\]{max-height:300px}.min-h-0{min-height:calc(var(--spacing) * 0)}.min-h-28{min-height:calc(var(--spacing) * 28)}.min-h-56{min-height:calc(var(--spacing) * 56)}.min-h-\[120px\]{min-height:120px}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-3{width:calc(var(--spacing) * 3)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-6{width:calc(var(--spacing) * 6)}.w-7{width:calc(var(--spacing) * 7)}.w-8{width:calc(var(--spacing) * 8)}.w-9{width:calc(var(--spacing) * 9)}.w-56{width:calc(var(--spacing) * 56)}.w-80{width:calc(var(--spacing) * 80)}.w-fit{width:fit-content}.w-full{width:100%}.w-px{width:1px}.max-w-2xl{max-width:var(--container-2xl)}.max-w-md{max-width:var(--container-md)}.max-w-sm{max-width:var(--container-sm)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-40{min-width:calc(var(--spacing) * 40)}.min-w-\[--anchor-width\]{min-width:--anchor-width}.flex-1{flex:1}.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.origin-\(--transform-origin\){transform-origin:var(--transform-origin)}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.rotate-90{rotate:90deg}.animate-spin{animation:var(--animate-spin)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.resize{resize:both}.resize-y{resize:vertical}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) * .8)}.rounded-sm{border-radius:calc(var(--radius) * .6)}.border{border-style:var(--tw-border-style);border-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-r-0{border-right-style:var(--tw-border-style);border-right-width:0}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-border{border-color:var(--border)}.border-destructive\/40{border-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.border-destructive\/40{border-color:color-mix(in oklab,var(--destructive) 40%,transparent)}}.border-destructive\/50{border-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.border-destructive\/50{border-color:color-mix(in oklab,var(--destructive) 50%,transparent)}}.border-input{border-color:var(--input)}.border-ring{border-color:var(--ring)}.border-sidebar-border{border-color:var(--sidebar-border)}.bg-background,.bg-background\/70{background-color:var(--background)}@supports (color:color-mix(in lab,red,red)){.bg-background\/70{background-color:color-mix(in oklab,var(--background) 70%,transparent)}}.bg-border{background-color:var(--border)}.bg-card{background-color:var(--card)}.bg-destructive,.bg-destructive\/10{background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.bg-destructive\/10{background-color:color-mix(in oklab,var(--destructive) 10%,transparent)}}.bg-foreground{background-color:var(--foreground)}.bg-muted{background-color:var(--muted)}.bg-popover{background-color:var(--popover)}.bg-primary{background-color:var(--primary)}.bg-secondary{background-color:var(--secondary)}.bg-sidebar{background-color:var(--sidebar)}.bg-transparent{background-color:#0000}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-1{padding:calc(var(--spacing) * 1)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-6{padding:calc(var(--spacing) * 6)}.p-8{padding:calc(var(--spacing) * 8)}.px-0{padding-inline:calc(var(--spacing) * 0)}.px-1{padding-inline:calc(var(--spacing) * 1)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-10{padding-block:calc(var(--spacing) * 10)}.pr-3{padding-right:calc(var(--spacing) * 3)}.pr-8{padding-right:calc(var(--spacing) * 8)}.pr-14{padding-right:calc(var(--spacing) * 14)}.pl-2{padding-left:calc(var(--spacing) * 2)}.pl-8{padding-left:calc(var(--spacing) * 8)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.wrap-anywhere{overflow-wrap:anywhere}.break-all{word-break:break-all}.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-wrap{white-space:pre-wrap}.text-accent{color:var(--accent)}.text-accent-foreground{color:var(--accent-foreground)}.text-background{color:var(--background)}.text-card-foreground{color:var(--card-foreground)}.text-destructive{color:var(--destructive)}.text-destructive-foreground{color:var(--destructive-foreground)}.text-foreground{color:var(--foreground)}.text-muted{color:var(--muted)}.text-muted-foreground,.text-muted-foreground\/60{color:var(--muted-foreground)}@supports (color:color-mix(in lab,red,red)){.text-muted-foreground\/60{color:color-mix(in oklab,var(--muted-foreground) 60%,transparent)}}.text-popover-foreground{color:var(--popover-foreground)}.text-primary{color:var(--primary)}.text-primary-foreground{color:var(--primary-foreground)}.text-secondary-foreground{color:var(--secondary-foreground)}.text-sidebar-foreground,.text-sidebar-foreground\/60{color:var(--sidebar-foreground)}@supports (color:color-mix(in lab,red,red)){.text-sidebar-foreground\/60{color:color-mix(in oklab,var(--sidebar-foreground) 60%,transparent)}}.lowercase{text-transform:lowercase}.italic{font-style:italic}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[transform\,opacity\]{transition-property:transform,opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[transform\,scale\,opacity\]{transition-property:transform,scale,opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.running{animation-play-state:running}.group-aria-\[orientation\=horizontal\]\/split-handle\:h-3:is(:where(.group\/split-handle)[aria-orientation=horizontal] *){height:calc(var(--spacing) * 3)}.group-aria-\[orientation\=horizontal\]\/split-handle\:w-4:is(:where(.group\/split-handle)[aria-orientation=horizontal] *){width:calc(var(--spacing) * 4)}.group-aria-\[orientation\=horizontal\]\/split-handle\:rotate-90:is(:where(.group\/split-handle)[aria-orientation=horizontal] *){rotate:90deg}.group-aria-\[orientation\=vertical\]\/split-handle\:h-4:is(:where(.group\/split-handle)[aria-orientation=vertical] *){height:calc(var(--spacing) * 4)}.group-aria-\[orientation\=vertical\]\/split-handle\:w-3:is(:where(.group\/split-handle)[aria-orientation=vertical] *){width:calc(var(--spacing) * 3)}.placeholder\:text-muted-foreground::placeholder{color:var(--muted-foreground)}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:inset-y-0:after{content:var(--tw-content);inset-block:calc(var(--spacing) * 0)}.after\:left-1\/2:after{content:var(--tw-content);left:50%}.after\:w-1:after{content:var(--tw-content);width:calc(var(--spacing) * 1)}.after\:-translate-x-1\/2:after{content:var(--tw-content);--tw-translate-x: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.last\:border-0:last-child{border-style:var(--tw-border-style);border-width:0}@media(hover:hover){.hover\:bg-accent:hover,.hover\:bg-accent\/50:hover{background-color:var(--accent)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-accent\/50:hover{background-color:color-mix(in oklab,var(--accent) 50%,transparent)}}.hover\:bg-destructive\/90:hover{background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-destructive\/90:hover{background-color:color-mix(in oklab,var(--destructive) 90%,transparent)}}.hover\:bg-muted-foreground:hover{background-color:var(--muted-foreground)}.hover\:bg-primary\/90:hover{background-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-primary\/90:hover{background-color:color-mix(in oklab,var(--primary) 90%,transparent)}}.hover\:bg-secondary\/80:hover{background-color:var(--secondary)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-secondary\/80:hover{background-color:color-mix(in oklab,var(--secondary) 80%,transparent)}}.hover\:bg-sidebar-accent:hover{background-color:var(--sidebar-accent)}.hover\:text-accent-foreground:hover{color:var(--accent-foreground)}.hover\:text-destructive:hover{color:var(--destructive)}.hover\:text-foreground:hover{color:var(--foreground)}.hover\:text-primary:hover{color:var(--primary)}.hover\:text-sidebar-accent-foreground:hover{color:var(--sidebar-accent-foreground)}}.focus-visible\:border-ring:focus-visible{border-color:var(--ring)}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-ring:focus-visible,.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:var(--ring)}@supports (color:color-mix(in lab,red,red)){.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:color-mix(in oklab, var(--ring) 50%, transparent)}}.focus-visible\:ring-sidebar-ring:focus-visible{--tw-ring-color:var(--sidebar-ring)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\:ring-offset-background:focus-visible{--tw-ring-offset-color:var(--background)}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.aria-\[orientation\=horizontal\]\:h-px[aria-orientation=horizontal]{height:1px}.aria-\[orientation\=horizontal\]\:cursor-row-resize[aria-orientation=horizontal]{cursor:row-resize}.aria-\[orientation\=vertical\]\:w-px[aria-orientation=vertical]{width:1px}.aria-\[orientation\=vertical\]\:cursor-col-resize[aria-orientation=vertical]{cursor:col-resize}.data-\[checked\]\:translate-x-4[data-checked]{--tw-translate-x:calc(var(--spacing) * 4);translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[checked\]\:border-primary[data-checked]{border-color:var(--primary)}.data-\[checked\]\:bg-primary[data-checked]{background-color:var(--primary)}.data-\[disabled\]\:pointer-events-none[data-disabled]{pointer-events:none}.data-\[disabled\]\:opacity-50[data-disabled]{opacity:.5}.data-\[ending-style\]\:scale-90[data-ending-style]{--tw-scale-x:90%;--tw-scale-y:90%;--tw-scale-z:90%;scale:var(--tw-scale-x) var(--tw-scale-y)}.data-\[ending-style\]\:scale-95[data-ending-style]{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.data-\[ending-style\]\:opacity-0[data-ending-style]{opacity:0}.data-\[highlighted\]\:bg-accent[data-highlighted]{background-color:var(--accent)}.data-\[highlighted\]\:text-accent-foreground[data-highlighted]{color:var(--accent-foreground)}.data-\[selected\]\:bg-background[data-selected]{background-color:var(--background)}.data-\[selected\]\:bg-sidebar-accent[data-selected]{background-color:var(--sidebar-accent)}.data-\[selected\]\:font-medium[data-selected]{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.data-\[selected\]\:text-foreground[data-selected]{color:var(--foreground)}.data-\[selected\]\:text-sidebar-accent-foreground[data-selected]{color:var(--sidebar-accent-foreground)}.data-\[selected\]\:shadow-xs[data-selected]{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.data-\[starting-style\]\:scale-90[data-starting-style]{--tw-scale-x:90%;--tw-scale-y:90%;--tw-scale-z:90%;scale:var(--tw-scale-x) var(--tw-scale-y)}.data-\[starting-style\]\:scale-95[data-starting-style]{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.data-\[starting-style\]\:opacity-0[data-starting-style]{opacity:0}@media(min-width:40rem){.sm\:flex-row{flex-direction:row}.sm\:justify-end{justify-content:flex-end}}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:size-3\.5 svg{width:calc(var(--spacing) * 3.5);height:calc(var(--spacing) * 3.5)}.\[\&_svg\]\:size-4 svg{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&\:\:-webkit-search-cancel-button\]\:appearance-none::-webkit-search-cancel-button{appearance:none}}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}:root{--radius:0;--background:#fff;--foreground:#201f24;--card:#fff;--card-foreground:#201f24;--popover:#fff;--popover-foreground:#201f24;--primary:#8232ff;--primary-foreground:#fff;--secondary:#f9f8fd;--secondary-foreground:#201f24;--muted:#f9f8fd;--muted-foreground:#838289;--accent:#8232ff29;--accent-foreground:#201f24;--destructive:#fa7171;--destructive-foreground:#201f24;--border:#cfced5;--input:#cfced5;--ring:#8232ff;--sidebar:#fff;--sidebar-foreground:#201f24;--sidebar-primary:#8232ff;--sidebar-primary-foreground:#fff;--sidebar-accent:#8232ff29;--sidebar-accent-foreground:#201f24;--sidebar-border:#cfced5;--sidebar-ring:#8232ff}.dark{--background:#201f24;--foreground:#fff;--card:#201f24;--card-foreground:#fff;--popover:#2b2a2f;--popover-foreground:#fff;--primary:#8232ff;--primary-foreground:#fff;--secondary:#2b2a2f;--secondary-foreground:#fff;--muted:#2b2a2f;--muted-foreground:#cfced5;--accent:#8232ff33;--accent-foreground:#fff;--destructive:#fa7171;--destructive-foreground:#201f24;--border:#424145;--input:#424145;--ring:#8232ff;--sidebar:#201f24;--sidebar-foreground:#fff;--sidebar-primary:#8232ff;--sidebar-primary-foreground:#fff;--sidebar-accent:#8232ff33;--sidebar-accent-foreground:#fff;--sidebar-border:#424145;--sidebar-ring:#8232ff}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@keyframes spin{to{transform:rotate(360deg)}}