@workbench-kit/shell-react 0.0.2-prototype.0.2.32 → 0.0.2-prototype.0.2.34

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 (52) hide show
  1. package/README.md +32 -0
  2. package/package.json +9 -9
  3. package/src/commands/use-command-descriptors.ts +7 -3
  4. package/src/commands/use-extension-registry-command-descriptors.ts +23 -6
  5. package/src/devtools/use-workbench-devtools-snapshot.ts +39 -14
  6. package/src/devtools/workbench-devtools-snapshot.ts +32 -13
  7. package/src/editor/area.tsx +3 -2
  8. package/src/editor/state-storage.ts +31 -0
  9. package/src/editor/tab-context-menu.ts +8 -8
  10. package/src/editor/use-editor.ts +6 -6
  11. package/src/explorer/context-menu.ts +8 -4
  12. package/src/explorer/view.tsx +4 -3
  13. package/src/extensions/canonical-extension-descriptions.ts +58 -0
  14. package/src/extensions/context-menu.ts +9 -6
  15. package/src/extensions/extension-enablement-context.ts +15 -0
  16. package/src/extensions/extension-enablement-controller.ts +453 -0
  17. package/src/extensions/management-model.ts +111 -47
  18. package/src/extensions/theme-selection-protection.ts +98 -0
  19. package/src/extensions/uninstall-eligibility.ts +103 -0
  20. package/src/extensions/use-extension-management.ts +151 -67
  21. package/src/extensions/view.tsx +4 -0
  22. package/src/field-remap/chrome-labels.ts +87 -2
  23. package/src/field-remap/convert-palette.tsx +164 -18
  24. package/src/field-remap/demo.tsx +48 -3
  25. package/src/field-remap/detail-panel.tsx +6 -5
  26. package/src/field-remap/flow.tsx +341 -155
  27. package/src/field-remap/history.ts +99 -0
  28. package/src/field-remap/index.ts +9 -1
  29. package/src/field-remap/panel.tsx +388 -97
  30. package/src/field-remap/preview-controller.ts +122 -0
  31. package/src/field-remap/preview.tsx +171 -0
  32. package/src/field-remap/view.css +154 -2
  33. package/src/index.ts +16 -1
  34. package/src/management/keybinding-overrides-storage.ts +28 -0
  35. package/src/management/preference-settings-storage.ts +28 -0
  36. package/src/management/settings.tsx +2 -0
  37. package/src/management/use-command-management.ts +31 -26
  38. package/src/management/use-keybinding-management.ts +25 -12
  39. package/src/shell/focused-extension-services.ts +138 -0
  40. package/src/shell/host-shell.tsx +13 -10
  41. package/src/shell/persistence-diagnostic-context.ts +11 -0
  42. package/src/shell/provider.tsx +373 -69
  43. package/src/shell/settings.tsx +25 -16
  44. package/src/shell/shell.tsx +119 -78
  45. package/src/shell/view-host.tsx +23 -22
  46. package/src/storage/local-json-storage.ts +43 -29
  47. package/src/storage/persistence-diagnostics.ts +72 -0
  48. package/src/workbench/appearance-storage.ts +28 -0
  49. package/src/workbench/command-host.tsx +19 -22
  50. package/src/workbench/command-palette.ts +14 -13
  51. package/src/workbench/layout-storage.ts +52 -5
  52. package/src/workbench/use-persisted-appearance.ts +38 -12
@@ -1,10 +1,20 @@
1
1
  import {
2
2
  createBrowserWorkbenchStorage,
3
+ readWorkbenchStorageJsonResult,
4
+ writeWorkbenchStorageJsonResult,
5
+ type WorkbenchPersistenceDiagnosticOptions,
6
+ type WorkbenchPersistenceReadResult,
7
+ type WorkbenchPersistenceWriteResult,
3
8
  type WorkbenchStorageAdapter,
4
9
  type WorkbenchStorageReader,
5
10
  type WorkbenchStorageWriter,
6
11
  } from '@workbench-kit/workbench-core';
7
12
 
13
+ export interface WriteLocalJsonStorageOptions<T> extends WorkbenchPersistenceDiagnosticOptions {
14
+ readonly errorMode?: 'ignore' | 'throw';
15
+ readonly toStorageValue?: (value: T) => unknown;
16
+ }
17
+
8
18
  export function resolveLocalWorkbenchStorage<
9
19
  TStorage extends WorkbenchStorageReader | WorkbenchStorageWriter = WorkbenchStorageAdapter,
10
20
  >(storage?: TStorage): TStorage | WorkbenchStorageAdapter | undefined {
@@ -16,49 +26,53 @@ export function readLocalJsonStorage<T>(
16
26
  parse: (value: unknown) => T,
17
27
  fallback: () => T,
18
28
  storage?: WorkbenchStorageReader,
29
+ options: WorkbenchPersistenceDiagnosticOptions = {},
19
30
  ): T {
20
- const resolvedStorage = resolveLocalWorkbenchStorage(storage);
21
- if (!resolvedStorage) {
22
- return fallback();
23
- }
24
-
25
- try {
26
- const raw = resolvedStorage.getItem(storageKey);
27
- if (!raw) {
28
- return fallback();
29
- }
31
+ return readLocalJsonStorageResult(storageKey, parse, fallback, storage, options).value;
32
+ }
30
33
 
31
- return parse(JSON.parse(raw) as unknown);
32
- } catch {
33
- return fallback();
34
- }
34
+ export function readLocalJsonStorageResult<T>(
35
+ storageKey: string,
36
+ parse: (value: unknown) => T,
37
+ fallback: () => T,
38
+ storage?: WorkbenchStorageReader,
39
+ options: WorkbenchPersistenceDiagnosticOptions = {},
40
+ ): WorkbenchPersistenceReadResult<T> {
41
+ return readWorkbenchStorageJsonResult(storageKey, parse, fallback, storage, options);
35
42
  }
36
43
 
37
44
  export function writeLocalJsonStorage<T>(
38
45
  storageKey: string,
39
46
  value: T,
40
47
  storage?: WorkbenchStorageWriter,
41
- options: {
42
- readonly errorMode?: 'ignore' | 'throw';
43
- readonly toStorageValue?: (value: T) => unknown;
44
- } = {},
48
+ options: WriteLocalJsonStorageOptions<T> = {},
45
49
  ): void {
50
+ if (options.errorMode !== 'throw') {
51
+ writeLocalJsonStorageResult(storageKey, value, storage, options);
52
+ return;
53
+ }
54
+
46
55
  const resolvedStorage = resolveLocalWorkbenchStorage(storage);
47
56
  if (!resolvedStorage) {
48
57
  return;
49
58
  }
50
59
 
51
- try {
52
- resolvedStorage.setItem(
53
- storageKey,
54
- JSON.stringify((options.toStorageValue ?? identity)(value), null, 2),
55
- );
56
- } catch (error) {
57
- if (options.errorMode === 'throw') {
58
- throw error;
59
- }
60
- // Local storage is best-effort; quota, security, and serialization errors stay non-fatal.
61
- }
60
+ resolvedStorage.setItem(
61
+ storageKey,
62
+ JSON.stringify((options.toStorageValue ?? identity)(value), null, 2),
63
+ );
64
+ }
65
+
66
+ export function writeLocalJsonStorageResult<T>(
67
+ storageKey: string,
68
+ value: T,
69
+ storage?: WorkbenchStorageWriter,
70
+ options: WriteLocalJsonStorageOptions<T> = {},
71
+ ): WorkbenchPersistenceWriteResult {
72
+ return writeWorkbenchStorageJsonResult(storageKey, value, storage, {
73
+ onDiagnostic: options.onDiagnostic,
74
+ toStorageValue: options.toStorageValue,
75
+ });
62
76
  }
63
77
 
64
78
  function identity<T>(value: T): T {
@@ -0,0 +1,72 @@
1
+ import { useEffect, useRef, type MutableRefObject } from 'react';
2
+ import type {
3
+ WorkbenchPersistenceDiagnostic,
4
+ WorkbenchPersistenceDiagnosticHandler,
5
+ WorkbenchPersistenceWriteResult,
6
+ } from '@workbench-kit/workbench-core';
7
+
8
+ export type WorkbenchPersistenceDiagnosticHandlerRef = MutableRefObject<
9
+ WorkbenchPersistenceDiagnosticHandler | undefined
10
+ >;
11
+
12
+ export function usePersistenceDiagnosticHandlerRef(
13
+ onDiagnostic: WorkbenchPersistenceDiagnosticHandler | undefined,
14
+ ): WorkbenchPersistenceDiagnosticHandlerRef {
15
+ const handlerRef = useRef(onDiagnostic);
16
+
17
+ useEffect(() => {
18
+ handlerRef.current = onDiagnostic;
19
+ }, [onDiagnostic]);
20
+
21
+ return handlerRef;
22
+ }
23
+
24
+ export function useReportPersistenceReadDiagnostic(
25
+ diagnostic: WorkbenchPersistenceDiagnostic | undefined,
26
+ generation: readonly unknown[],
27
+ handlerRef: WorkbenchPersistenceDiagnosticHandlerRef,
28
+ ): void {
29
+ const lastGenerationRef = useRef<readonly unknown[] | undefined>(undefined);
30
+
31
+ useEffect(() => {
32
+ if (isSameGeneration(lastGenerationRef.current, generation)) {
33
+ return;
34
+ }
35
+
36
+ lastGenerationRef.current = [...generation];
37
+ if (diagnostic) {
38
+ reportPersistenceDiagnostic(diagnostic, handlerRef);
39
+ }
40
+ }, [diagnostic, generation, handlerRef]);
41
+ }
42
+
43
+ export function reportPersistenceWriteResult(
44
+ result: WorkbenchPersistenceWriteResult,
45
+ handlerRef: WorkbenchPersistenceDiagnosticHandlerRef,
46
+ ): void {
47
+ if (!result.committed) {
48
+ reportPersistenceDiagnostic(result.diagnostic, handlerRef);
49
+ }
50
+ }
51
+
52
+ export function reportPersistenceDiagnostic(
53
+ diagnostic: WorkbenchPersistenceDiagnostic,
54
+ handlerRef: WorkbenchPersistenceDiagnosticHandlerRef,
55
+ ): void {
56
+ try {
57
+ handlerRef.current?.(diagnostic);
58
+ } catch {
59
+ // Diagnostics are observational and must not change runtime state.
60
+ }
61
+ }
62
+
63
+ function isSameGeneration(
64
+ left: readonly unknown[] | undefined,
65
+ right: readonly unknown[],
66
+ ): boolean {
67
+ return (
68
+ left !== undefined &&
69
+ left.length === right.length &&
70
+ left.every((value, index) => Object.is(value, right[index]))
71
+ );
72
+ }
@@ -7,14 +7,19 @@ import {
7
7
  type WorkbenchColorSchemePreference,
8
8
  } from '@workbench-kit/react/workbench';
9
9
  import {
10
+ type WorkbenchPersistenceDiagnosticOptions,
11
+ type WorkbenchPersistenceReadResult,
12
+ type WorkbenchPersistenceWriteResult,
10
13
  type WorkbenchStorageReader,
11
14
  type WorkbenchStorageWriter,
12
15
  } from '@workbench-kit/workbench-core';
13
16
 
14
17
  import {
15
18
  readLocalJsonStorage,
19
+ readLocalJsonStorageResult,
16
20
  resolveLocalWorkbenchStorage,
17
21
  writeLocalJsonStorage,
22
+ writeLocalJsonStorageResult,
18
23
  } from '../storage/local-json-storage.js';
19
24
 
20
25
  export type { WorkbenchAppearanceSettings } from '@workbench-kit/react/workbench/themePresets';
@@ -44,6 +49,20 @@ export function readPersistedWorkbenchAppearance(
44
49
  );
45
50
  }
46
51
 
52
+ export function readPersistedWorkbenchAppearanceResult(
53
+ storageKey = DEFAULT_WORKBENCH_APPEARANCE_STORAGE_KEY,
54
+ storage?: WorkbenchStorageReader,
55
+ options: WorkbenchPersistenceDiagnosticOptions = {},
56
+ ): WorkbenchPersistenceReadResult<WorkbenchAppearanceSettings> {
57
+ return readLocalJsonStorageResult(
58
+ storageKey,
59
+ normalizeWorkbenchAppearance,
60
+ () => DEFAULT_WORKBENCH_APPEARANCE,
61
+ storage,
62
+ options,
63
+ );
64
+ }
65
+
47
66
  export function writePersistedWorkbenchAppearance(
48
67
  settings: WorkbenchAppearanceSettings,
49
68
  storageKey = DEFAULT_WORKBENCH_APPEARANCE_STORAGE_KEY,
@@ -52,6 +71,15 @@ export function writePersistedWorkbenchAppearance(
52
71
  writeLocalJsonStorage(storageKey, settings, storage);
53
72
  }
54
73
 
74
+ export function writePersistedWorkbenchAppearanceResult(
75
+ settings: WorkbenchAppearanceSettings,
76
+ storageKey = DEFAULT_WORKBENCH_APPEARANCE_STORAGE_KEY,
77
+ storage?: WorkbenchStorageWriter,
78
+ options: WorkbenchPersistenceDiagnosticOptions = {},
79
+ ): WorkbenchPersistenceWriteResult {
80
+ return writeLocalJsonStorageResult(storageKey, settings, storage, options);
81
+ }
82
+
55
83
  function normalizeWorkbenchAppearance(value: unknown): WorkbenchAppearanceSettings {
56
84
  if (typeof value !== 'object' || value === null) {
57
85
  return DEFAULT_WORKBENCH_APPEARANCE;
@@ -101,11 +101,15 @@ export function WorkbenchCommandHost({
101
101
  quickOpenTitle = 'Quick Open',
102
102
  }: WorkbenchCommandHostProps) {
103
103
  const {
104
+ activities,
105
+ commands,
104
106
  contextKeyService,
105
107
  executeCommand,
106
- extensionRegistry,
108
+ extensionCatalog,
109
+ keybindings,
107
110
  keybindingOverrides,
108
111
  layoutService,
112
+ views,
109
113
  workspaceHostPort,
110
114
  } = useWorkbench();
111
115
  const [paletteOpen, setPaletteOpen] = useState(false);
@@ -136,12 +140,12 @@ export function WorkbenchCommandHost({
136
140
  }, [layoutService]);
137
141
 
138
142
  const managedShellActivities = useMemo(
139
- () => resolveShellCommandActivities(extensionRegistry),
140
- [extensionRegistry],
143
+ () => resolveShellCommandActivities({ activities, views }),
144
+ [activities, views],
141
145
  );
142
146
  const visibleShellActivities = useMemo(
143
- () => resolveShellCommandActivities(extensionRegistry, contextKeySnapshot),
144
- [contextKeySnapshot, extensionRegistry],
147
+ () => resolveShellCommandActivities({ activities, views }, contextKeySnapshot),
148
+ [activities, contextKeySnapshot, views],
145
149
  );
146
150
  const visibleShellActivityIdSet = new Set(visibleShellActivities.map((activity) => activity.id));
147
151
  const visibleShellActivitySignature = managedShellActivities
@@ -203,7 +207,7 @@ export function WorkbenchCommandHost({
203
207
 
204
208
  useEffect(() => {
205
209
  const registration = registerWorkbenchShellCommandHandlers(
206
- extensionRegistry.commands,
210
+ commands,
207
211
  shellCommandDefinitions,
208
212
  () => {
209
213
  const context = shellContextRef.current;
@@ -218,14 +222,16 @@ export function WorkbenchCommandHost({
218
222
  return () => {
219
223
  registration.dispose();
220
224
  };
221
- }, [extensionRegistry.commands, shellCommandDefinitions]);
225
+ }, [commands, shellCommandDefinitions]);
222
226
 
223
227
  const paletteCommands = useMemo(
224
228
  () =>
225
229
  buildWorkbenchPaletteCommands({
226
230
  additionalCommands,
227
- extensionCommandFeaturesById: collectExtensionCommandFeaturesById(extensionRegistry),
228
- extensionCommands: extensionRegistry.commands
231
+ extensionCommandFeaturesById: collectExtensionCommandFeaturesById(
232
+ extensionCatalog.getFeatureSpecs(),
233
+ ),
234
+ extensionCommands: commands
229
235
  .getCommands()
230
236
  .filter((command) => !managedShellCommandIds.has(command.id)),
231
237
  shellCommands: shellCommandDefinitions,
@@ -233,7 +239,8 @@ export function WorkbenchCommandHost({
233
239
  }),
234
240
  [
235
241
  additionalCommands,
236
- extensionRegistry,
242
+ commands,
243
+ extensionCatalog,
237
244
  managedShellCommandIds,
238
245
  shellContext,
239
246
  shellCommandDefinitions,
@@ -356,12 +363,7 @@ export function WorkbenchCommandHost({
356
363
  return;
357
364
  }
358
365
 
359
- const match = resolveExtensionKeybindingCommand(
360
- extensionRegistry.keybindings,
361
- event,
362
- {},
363
- keybindingOverrides,
364
- );
366
+ const match = resolveExtensionKeybindingCommand(keybindings, event, {}, keybindingOverrides);
365
367
  if (!match) {
366
368
  return;
367
369
  }
@@ -374,12 +376,7 @@ export function WorkbenchCommandHost({
374
376
  return () => {
375
377
  window.removeEventListener('keydown', onKeyDown);
376
378
  };
377
- }, [
378
- enableExtensionKeybindings,
379
- executeCommand,
380
- extensionRegistry.keybindings,
381
- keybindingOverrides,
382
- ]);
379
+ }, [enableExtensionKeybindings, executeCommand, keybindings, keybindingOverrides]);
383
380
 
384
381
  return (
385
382
  <>
@@ -7,10 +7,13 @@ import type {
7
7
  import {
8
8
  filterActivitiesByWhenClause,
9
9
  type ExtensionCommandFeatureSpec,
10
- type ExtensionRegistry,
10
+ type ActivityRegistry,
11
+ type ExtensionFeatureSpec,
12
+ type ViewRegistry,
11
13
  } from '@workbench-kit/workbench-core';
14
+ import type { CommandRegistry } from '@workbench-kit/platform';
12
15
 
13
- type ExtensionCommand = ReturnType<ExtensionRegistry['commands']['getCommands']>[number];
16
+ type ExtensionCommand = ReturnType<CommandRegistry['getCommands']>[number];
14
17
 
15
18
  function resolveCommandValue<TContext, TValue>(
16
19
  value: CommandDefinition<TContext>[keyof CommandDefinition<TContext>],
@@ -44,10 +47,10 @@ export function resolveExtensionCommandIcon(icon: ExtensionCommand['icon']): str
44
47
  }
45
48
 
46
49
  export function resolveShellCommandActivities(
47
- extensionRegistry: ExtensionRegistry,
50
+ registries: { readonly activities: ActivityRegistry; readonly views: ViewRegistry },
48
51
  contextKeys?: object | undefined,
49
52
  ): WorkbenchShellCommandActivity[] {
50
- const activities = extensionRegistry.activities.getActivities();
53
+ const activities = registries.activities.getActivities();
51
54
  if (activities.length > 0) {
52
55
  const visibleActivities =
53
56
  contextKeys === undefined
@@ -63,13 +66,11 @@ export function resolveShellCommandActivities(
63
66
  .sort((left, right) => left.label.localeCompare(right.label));
64
67
  }
65
68
 
66
- const viewContainerIds = new Set(
67
- extensionRegistry.views.getViews().map((view) => view.containerId),
68
- );
69
+ const viewContainerIds = new Set(registries.views.getViews().map((view) => view.containerId));
69
70
 
70
71
  return [...viewContainerIds].map((containerId) => {
71
- const container = extensionRegistry.views.getViewContainer(containerId);
72
- const firstView = extensionRegistry.views.getViews(containerId)[0];
72
+ const container = registries.views.getViewContainer(containerId);
73
+ const firstView = registries.views.getViews(containerId)[0];
73
74
 
74
75
  return {
75
76
  icon: formatWorkbenchCommandIcon(container?.icon ?? 'files'),
@@ -125,12 +126,12 @@ export function extensionCommandToDescriptor(
125
126
  }
126
127
 
127
128
  export function collectExtensionCommandFeaturesById(
128
- extensionRegistry: ExtensionRegistry,
129
+ featureSpecs: readonly ExtensionFeatureSpec[],
129
130
  ): ReadonlyMap<string, ExtensionCommandFeatureSpec> {
130
131
  return new Map(
131
- extensionRegistry
132
- .getFeatureSpecs()
133
- .flatMap((feature) => feature.commands.map((command) => [command.id, command] as const)),
132
+ featureSpecs.flatMap((feature) =>
133
+ feature.commands.map((command) => [command.id, command] as const),
134
+ ),
134
135
  );
135
136
  }
136
137
 
@@ -7,6 +7,11 @@ import {
7
7
  type WorkbenchLayoutState,
8
8
  type WorkbenchLayoutStateInput,
9
9
  } from '@workbench-kit/workbench-core/layout';
10
+ import type {
11
+ WorkbenchPersistenceDiagnosticOptions,
12
+ WorkbenchPersistenceReadResult,
13
+ WorkbenchPersistenceWriteResult,
14
+ } from '@workbench-kit/workbench-core';
10
15
  import type {
11
16
  WorkbenchStorageReader,
12
17
  WorkbenchStorageWriter,
@@ -14,8 +19,10 @@ import type {
14
19
 
15
20
  import {
16
21
  readLocalJsonStorage,
22
+ readLocalJsonStorageResult,
17
23
  resolveLocalWorkbenchStorage,
18
24
  writeLocalJsonStorage,
25
+ writeLocalJsonStorageResult,
19
26
  } from '../storage/local-json-storage.js';
20
27
 
21
28
  export const DEFAULT_WORKBENCH_LAYOUT_STORAGE_KEY = 'workbench-kit/.workbench/layout';
@@ -96,6 +103,20 @@ export function readPersistedWorkbenchLayout(
96
103
  );
97
104
  }
98
105
 
106
+ export function readPersistedWorkbenchLayoutResult(
107
+ storageKey = DEFAULT_WORKBENCH_LAYOUT_STORAGE_KEY,
108
+ storage?: WorkbenchStorageReader,
109
+ options: WorkbenchPersistenceDiagnosticOptions = {},
110
+ ): WorkbenchPersistenceReadResult<WorkbenchLayoutStateInput | undefined> {
111
+ return readLocalJsonStorageResult(
112
+ storageKey,
113
+ (value) => workbenchLayoutConfigToInput(parseWorkbenchLayoutConfig(value)),
114
+ () => undefined,
115
+ storage,
116
+ options,
117
+ );
118
+ }
119
+
99
120
  export function writePersistedWorkbenchLayout(
100
121
  state: WorkbenchLayoutState,
101
122
  storageKey = DEFAULT_WORKBENCH_LAYOUT_STORAGE_KEY,
@@ -106,6 +127,18 @@ export function writePersistedWorkbenchLayout(
106
127
  });
107
128
  }
108
129
 
130
+ export function writePersistedWorkbenchLayoutResult(
131
+ state: WorkbenchLayoutState,
132
+ storageKey = DEFAULT_WORKBENCH_LAYOUT_STORAGE_KEY,
133
+ storage?: WorkbenchStorageWriter,
134
+ options: WorkbenchPersistenceDiagnosticOptions = {},
135
+ ): WorkbenchPersistenceWriteResult {
136
+ return writeLocalJsonStorageResult(storageKey, state, storage, {
137
+ ...options,
138
+ toStorageValue: workbenchLayoutStateToStorageValue,
139
+ });
140
+ }
141
+
109
142
  export function resolvePersistedWorkbenchLayout(
110
143
  initialLayout: WorkbenchLayoutStateInput | undefined,
111
144
  options: {
@@ -114,6 +147,17 @@ export function resolvePersistedWorkbenchLayout(
114
147
  storageKey?: string | undefined;
115
148
  } = {},
116
149
  ): WorkbenchLayoutStateInput | undefined {
150
+ return resolvePersistedWorkbenchLayoutResult(initialLayout, options).value;
151
+ }
152
+
153
+ export function resolvePersistedWorkbenchLayoutResult(
154
+ initialLayout: WorkbenchLayoutStateInput | undefined,
155
+ options: {
156
+ persistLayout?: boolean | undefined;
157
+ storage?: WorkbenchStorageReader | undefined;
158
+ storageKey?: string | undefined;
159
+ } = {},
160
+ ): WorkbenchPersistenceReadResult<WorkbenchLayoutStateInput | undefined> {
117
161
  const {
118
162
  initialLayout: baseLayout,
119
163
  persistLayout = options.storage !== undefined || isWorkbenchLayoutPersistenceAvailable(),
@@ -122,13 +166,16 @@ export function resolvePersistedWorkbenchLayout(
122
166
  } = { initialLayout, ...options };
123
167
 
124
168
  if (!persistLayout) {
125
- return baseLayout;
169
+ return { value: baseLayout };
126
170
  }
127
171
 
128
- const persisted = readPersistedWorkbenchLayout(storageKey, storage);
129
- if (!persisted) {
130
- return baseLayout;
172
+ const result = readPersistedWorkbenchLayoutResult(storageKey, storage);
173
+ if (!result.value) {
174
+ return { ...result, value: baseLayout };
131
175
  }
132
176
 
133
- return createWorkbenchLayoutState(persisted, createWorkbenchLayoutState(baseLayout ?? {}));
177
+ return {
178
+ ...result,
179
+ value: createWorkbenchLayoutState(result.value, createWorkbenchLayoutState(baseLayout ?? {})),
180
+ };
134
181
  }
@@ -1,33 +1,59 @@
1
- import { useEffect, useState } from 'react';
2
- import type { WorkbenchStorageAdapter } from '@workbench-kit/workbench-core';
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import type {
3
+ WorkbenchPersistenceDiagnosticHandler,
4
+ WorkbenchStorageAdapter,
5
+ } from '@workbench-kit/workbench-core';
3
6
 
4
7
  import {
5
- readPersistedWorkbenchAppearance,
6
- writePersistedWorkbenchAppearance,
8
+ readPersistedWorkbenchAppearanceResult,
9
+ writePersistedWorkbenchAppearanceResult,
7
10
  type WorkbenchAppearanceSettings,
8
11
  } from './appearance-storage.js';
12
+ import {
13
+ reportPersistenceWriteResult,
14
+ usePersistenceDiagnosticHandlerRef,
15
+ useReportPersistenceReadDiagnostic,
16
+ } from '../storage/persistence-diagnostics.js';
9
17
 
10
18
  export interface UsePersistedWorkbenchAppearanceOptions {
19
+ onPersistenceDiagnostic?: WorkbenchPersistenceDiagnosticHandler | undefined;
11
20
  persist?: boolean | undefined;
12
21
  storage?: WorkbenchStorageAdapter | undefined;
13
22
  storageKey?: string | undefined;
14
23
  }
15
24
 
25
+ interface PersistedWorkbenchAppearanceState {
26
+ readonly appearance: WorkbenchAppearanceSettings;
27
+ readonly writeEligible: boolean;
28
+ }
29
+
16
30
  export function usePersistedWorkbenchAppearance(
17
31
  options: UsePersistedWorkbenchAppearanceOptions = {},
18
32
  ): [WorkbenchAppearanceSettings, (settings: WorkbenchAppearanceSettings) => void] {
19
- const { persist = true, storage, storageKey } = options;
20
- const [appearance, setAppearance] = useState(() =>
21
- readPersistedWorkbenchAppearance(storageKey, storage),
22
- );
33
+ const { onPersistenceDiagnostic, persist = true, storage, storageKey } = options;
34
+ const [initialRead] = useState(() => readPersistedWorkbenchAppearanceResult(storageKey, storage));
35
+ const [state, setState] = useState<PersistedWorkbenchAppearanceState>(() => ({
36
+ appearance: initialRead.value,
37
+ writeEligible: initialRead.diagnostic === undefined,
38
+ }));
39
+ const diagnosticHandlerRef = usePersistenceDiagnosticHandlerRef(onPersistenceDiagnostic);
40
+
41
+ useReportPersistenceReadDiagnostic(initialRead.diagnostic, [], diagnosticHandlerRef);
23
42
 
24
43
  useEffect(() => {
25
- if (!persist) {
44
+ if (!persist || !state.writeEligible) {
26
45
  return;
27
46
  }
28
47
 
29
- writePersistedWorkbenchAppearance(appearance, storageKey, storage);
30
- }, [appearance, persist, storage, storageKey]);
48
+ reportPersistenceWriteResult(
49
+ writePersistedWorkbenchAppearanceResult(state.appearance, storageKey, storage),
50
+ diagnosticHandlerRef,
51
+ );
52
+ }, [diagnosticHandlerRef, persist, state, storage, storageKey]);
53
+
54
+ const setAppearance = useCallback((appearance: WorkbenchAppearanceSettings) => {
55
+ setState({ appearance, writeEligible: true });
56
+ }, []);
31
57
 
32
- return [appearance, setAppearance];
58
+ return [state.appearance, setAppearance];
33
59
  }