@workbench-kit/shell-react 0.0.2-prototype.0.2.33 → 0.0.2-prototype.0.2.35

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 (51) 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 +58 -2
  23. package/src/field-remap/convert-palette.tsx +151 -6
  24. package/src/field-remap/demo.tsx +31 -2
  25. package/src/field-remap/flow.tsx +57 -13
  26. package/src/field-remap/history.ts +99 -0
  27. package/src/field-remap/index.ts +9 -1
  28. package/src/field-remap/panel.tsx +371 -97
  29. package/src/field-remap/preview-controller.ts +122 -0
  30. package/src/field-remap/preview.tsx +171 -0
  31. package/src/field-remap/view.css +72 -0
  32. package/src/index.ts +16 -1
  33. package/src/management/keybinding-overrides-storage.ts +28 -0
  34. package/src/management/preference-settings-storage.ts +28 -0
  35. package/src/management/settings.tsx +2 -0
  36. package/src/management/use-command-management.ts +31 -26
  37. package/src/management/use-keybinding-management.ts +25 -12
  38. package/src/shell/focused-extension-services.ts +138 -0
  39. package/src/shell/host-shell.tsx +13 -10
  40. package/src/shell/persistence-diagnostic-context.ts +11 -0
  41. package/src/shell/provider.tsx +373 -69
  42. package/src/shell/settings.tsx +25 -16
  43. package/src/shell/shell.tsx +119 -78
  44. package/src/shell/view-host.tsx +23 -22
  45. package/src/storage/local-json-storage.ts +43 -29
  46. package/src/storage/persistence-diagnostics.ts +72 -0
  47. package/src/workbench/appearance-storage.ts +28 -0
  48. package/src/workbench/command-host.tsx +19 -22
  49. package/src/workbench/command-palette.ts +14 -13
  50. package/src/workbench/layout-storage.ts +52 -5
  51. package/src/workbench/use-persisted-appearance.ts +38 -12
@@ -4,7 +4,11 @@ import { Button } from '@workbench-kit/react/primitives';
4
4
  import type { FieldRemapChromeLabels, FieldRemapTranslate } from './chrome-labels.js';
5
5
  import type { FieldRemapFlowActions, FieldRemapFlowMapperProps } from './flow.js';
6
6
  import type { FieldRemapIoChrome } from './io-class-browse.js';
7
- import { FieldRemapPanel } from './panel.js';
7
+ import {
8
+ FieldRemapPanel,
9
+ type FieldRemapHistoryActions,
10
+ type FieldRemapHistoryAvailability,
11
+ } from './panel.js';
8
12
  import {
9
13
  getFieldRemapBrowseDemoShapes,
10
14
  getFieldRemapSample,
@@ -21,7 +25,7 @@ export interface SampleFieldRemapDemoProps {
21
25
  readonly showBindingsList?: FieldRemapFlowMapperProps['showBindingsList'];
22
26
  readonly showConvertPalette?: FieldRemapFlowMapperProps['showConvertPalette'];
23
27
  readonly emptyDetail?: FieldRemapFlowMapperProps['emptyDetail'];
24
- /** When true, show host-chrome demo controls (minimap toggle + fit view). */
28
+ /** When true, show host-chrome demo controls (history + fit view). */
25
29
  readonly showHostChromeDemo?: boolean | undefined;
26
30
  /** Prefer `browse` for I/O class/field inspection demos. */
27
31
  readonly ioChrome?: FieldRemapIoChrome | undefined;
@@ -51,7 +55,12 @@ export function SampleFieldRemapDemo({
51
55
  const sample = getFieldRemapSample(sampleId);
52
56
  const browseShapes = browseSeedShapes ? getFieldRemapBrowseDemoShapes() : null;
53
57
  const flowActionsRef = useRef<FieldRemapFlowActions | null>(null);
58
+ const historyActionsRef = useRef<FieldRemapHistoryActions | null>(null);
54
59
  const [showMinimap, setShowMinimap] = useState(showMinimapProp ?? true);
60
+ const [historyAvailability, setHistoryAvailability] = useState<FieldRemapHistoryAvailability>({
61
+ canUndo: false,
62
+ canRedo: false,
63
+ });
55
64
  const [lastPaneMenu, setLastPaneMenu] = useState<string | null>(null);
56
65
 
57
66
  useEffect(() => {
@@ -76,6 +85,24 @@ export function SampleFieldRemapDemo({
76
85
  >
77
86
  Fit view
78
87
  </Button>
88
+ <Button
89
+ compact
90
+ type="button"
91
+ data-testid="field-remap-undo"
92
+ disabled={!historyAvailability.canUndo}
93
+ onClick={() => historyActionsRef.current?.undo()}
94
+ >
95
+ Undo
96
+ </Button>
97
+ <Button
98
+ compact
99
+ type="button"
100
+ data-testid="field-remap-redo"
101
+ disabled={!historyAvailability.canRedo}
102
+ onClick={() => historyActionsRef.current?.redo()}
103
+ >
104
+ Redo
105
+ </Button>
79
106
  {lastPaneMenu ? (
80
107
  <span data-testid="field-remap-pane-menu-log" role="status">
81
108
  Pane menu: {lastPaneMenu}
@@ -100,6 +127,8 @@ export function SampleFieldRemapDemo({
100
127
  labels={labels}
101
128
  t={t}
102
129
  flowActionsRef={showHostChromeDemo ? flowActionsRef : undefined}
130
+ historyActionsRef={showHostChromeDemo ? historyActionsRef : undefined}
131
+ onHistoryAvailabilityChange={showHostChromeDemo ? setHistoryAvailability : undefined}
103
132
  onPaneContextMenu={
104
133
  showHostChromeDemo
105
134
  ? (event) => {
@@ -84,6 +84,7 @@ import {
84
84
  type FieldRemapDraftTransform,
85
85
  type FieldRemapSelection,
86
86
  } from './flow-ops.js';
87
+ import { FieldRemapPreviewRail, type FieldRemapPreviewState } from './preview.js';
87
88
  import './view.css';
88
89
 
89
90
  function TypeBadge({ dataType }: { readonly dataType?: string }): JSX.Element | null {
@@ -349,6 +350,10 @@ export interface FieldRemapFlowMapperProps {
349
350
  readonly targetTitle?: string | undefined;
350
351
  readonly selection?: FieldRemapSelection | undefined;
351
352
  readonly onSelectionChange?: ((next: FieldRemapSelection) => void) | undefined;
353
+ /** Precomputed runtime-only preview. Flow never evaluates mappings itself. */
354
+ readonly preview?: FieldRemapPreviewState;
355
+ /** Explicitly hide an injected preview without reserving a splitter track. */
356
+ readonly showPreview?: boolean;
352
357
  /**
353
358
  * When false, MiniMap is not mounted (not CSS-hidden). Defaults to true for
354
359
  * backward compatibility with existing samples.
@@ -421,36 +426,58 @@ function FieldRemapSplitWorkspace({
421
426
  }: FieldRemapSplitWorkspaceProps): JSX.Element {
422
427
  const [palette, canvas, detail] = Children.toArray(children);
423
428
  const isNarrow = layout === 'narrow';
424
- const canvasWithDetail = showDetail ? (
429
+ const [paletteSizeByLayout, setPaletteSizeByLayout] = useState({
430
+ wide: 240,
431
+ medium: 240,
432
+ narrow: 192,
433
+ });
434
+ const [detailSizeByLayout, setDetailSizeByLayout] = useState({
435
+ wide: 320,
436
+ medium: 320,
437
+ narrow: 220,
438
+ });
439
+ const paletteSizePx = paletteSizeByLayout[layout];
440
+ const detailSizePx = detailSizeByLayout[layout];
441
+ const canvasWithDetail = (
425
442
  <SplitView
426
- className="workbench-field-remap-flow__canvas-detail-split"
427
- defaultSecondarySizePx={isNarrow ? 220 : 320}
443
+ className={
444
+ showDetail
445
+ ? 'workbench-field-remap-flow__canvas-detail-split'
446
+ : 'workbench-field-remap-flow__canvas-detail-split ui-workbench-split-view--secondary-collapsed'
447
+ }
428
448
  layoutMode="secondary-fixed"
429
449
  maxSecondarySizePx={isNarrow ? 320 : 480}
430
450
  minPrimarySizePx={isNarrow ? 200 : 280}
431
451
  minSecondarySizePx={isNarrow ? 160 : 256}
452
+ onSecondarySizePxChange={(nextSize) => {
453
+ setDetailSizeByLayout((current) => ({ ...current, [layout]: nextSize }));
454
+ }}
432
455
  orientation={isNarrow ? 'vertical' : 'horizontal'}
433
456
  primary={canvas}
434
457
  secondary={detail}
458
+ secondarySizePx={detailSizePx}
435
459
  />
436
- ) : (
437
- canvas
438
460
  );
439
461
 
440
- const content = showConvertPalette ? (
462
+ const content = (
441
463
  <SplitView
442
- className="workbench-field-remap-flow__palette-split"
443
- defaultPrimarySizePx={isNarrow ? 192 : 240}
464
+ className={
465
+ showConvertPalette
466
+ ? 'workbench-field-remap-flow__palette-split'
467
+ : 'workbench-field-remap-flow__palette-split ui-workbench-split-view--primary-collapsed'
468
+ }
444
469
  maxPrimarySizePx={isNarrow ? 288 : 320}
445
470
  minPrimarySizePx={isNarrow ? 160 : 192}
446
471
  minSecondarySizePx={isNarrow ? 200 : 280}
472
+ onPrimarySizePxChange={(nextSize) => {
473
+ setPaletteSizeByLayout((current) => ({ ...current, [layout]: nextSize }));
474
+ }}
447
475
  orientation={layout === 'wide' ? 'horizontal' : 'vertical'}
448
476
  primary={palette}
477
+ primarySizePx={paletteSizePx}
449
478
  primarySizeUnit="pixels"
450
479
  secondary={canvasWithDetail}
451
480
  />
452
- ) : (
453
- canvasWithDetail
454
481
  );
455
482
 
456
483
  return (
@@ -486,6 +513,8 @@ function FieldRemapFlowCanvas({
486
513
  targetTitle,
487
514
  selection: selectionProp,
488
515
  onSelectionChange: onSelectionChangeProp,
516
+ preview,
517
+ showPreview = true,
489
518
  showMinimap = true,
490
519
  onShowMinimapChange,
491
520
  includeHidden = false,
@@ -509,6 +538,7 @@ function FieldRemapFlowCanvas({
509
538
  const [internalSelection, setInternalSelection] = useState<FieldRemapSelection>(null);
510
539
  const selection = selectionProp !== undefined ? selectionProp : internalSelection;
511
540
  const setSelection = onSelectionChangeProp ?? setInternalSelection;
541
+ const previewVisible = showPreview && preview !== undefined && preview.status !== 'unavailable';
512
542
  const [drafts, setDrafts] = useState<readonly FieldRemapDraftTransform[]>([]);
513
543
  const [placeTransformId, setPlaceTransformId] = useState(() => {
514
544
  const first = transforms.list().find((definition) => definition.id !== 'identity');
@@ -878,6 +908,7 @@ function FieldRemapFlowCanvas({
878
908
  data-empty-detail={emptyDetail}
879
909
  data-minimap={showMinimap ? 'on' : 'off'}
880
910
  data-hidden-fields={includeHidden ? 'on' : 'off'}
911
+ data-preview={previewVisible ? 'on' : 'off'}
881
912
  onKeyDown={onKeyDown}
882
913
  >
883
914
  {showFlowHint ? (
@@ -892,7 +923,7 @@ function FieldRemapFlowCanvas({
892
923
  <FieldRemapSplitWorkspace
893
924
  layout={workspaceLayout}
894
925
  showConvertPalette={showConvertPalette}
895
- showDetail={emptyDetail === 'hint' || selection !== null}
926
+ showDetail={emptyDetail === 'hint' || selection !== null || previewVisible}
896
927
  surface={
897
928
  selection?.kind === 'transformStep'
898
929
  ? 'convert-note'
@@ -1062,7 +1093,7 @@ function FieldRemapFlowCanvas({
1062
1093
  </ReactFlow>
1063
1094
  </div>
1064
1095
 
1065
- <>
1096
+ <div className="workbench-field-remap-flow__side-rail">
1066
1097
  {emptyDetail === 'hint' || selection !== null ? (
1067
1098
  <FieldRemapDetailPanel
1068
1099
  selection={selection}
@@ -1082,7 +1113,20 @@ function FieldRemapFlowCanvas({
1082
1113
  emptyDetailDescription={chromeLabels.emptyDetailDescription}
1083
1114
  />
1084
1115
  ) : null}
1085
- </>
1116
+ {previewVisible && preview ? (
1117
+ <FieldRemapPreviewRail
1118
+ preview={preview}
1119
+ selection={selection}
1120
+ edges={edges}
1121
+ operatorExists={
1122
+ selection?.kind === 'operator'
1123
+ ? operators.some((operator) => operator.id === selection.operatorId)
1124
+ : false
1125
+ }
1126
+ labels={chromeLabels}
1127
+ />
1128
+ ) : null}
1129
+ </div>
1086
1130
  </FieldRemapSplitWorkspace>
1087
1131
 
1088
1132
  {showBindingsList ? (
@@ -0,0 +1,99 @@
1
+ import type { MappingEdge, MappingOperator } from '@workbench-kit/field-remap';
2
+
3
+ export interface FieldRemapHistorySnapshot {
4
+ readonly edges: readonly MappingEdge[];
5
+ readonly operators: readonly MappingOperator[];
6
+ }
7
+
8
+ export interface FieldRemapHistoryState {
9
+ readonly past: readonly FieldRemapHistorySnapshot[];
10
+ readonly future: readonly FieldRemapHistorySnapshot[];
11
+ }
12
+
13
+ const FIELD_REMAP_HISTORY_LIMIT = 100;
14
+
15
+ export function createFieldRemapHistorySnapshot(
16
+ edges: readonly MappingEdge[],
17
+ operators: readonly MappingOperator[],
18
+ ): FieldRemapHistorySnapshot {
19
+ return Object.freeze({
20
+ edges: Object.freeze([...edges]),
21
+ operators: Object.freeze([...operators]),
22
+ });
23
+ }
24
+
25
+ export function createFieldRemapHistoryState(): FieldRemapHistoryState {
26
+ return { past: [], future: [] };
27
+ }
28
+
29
+ function sameItems<T>(left: readonly T[], right: readonly T[]): boolean {
30
+ return left.length === right.length && left.every((item, index) => item === right[index]);
31
+ }
32
+
33
+ export function areFieldRemapHistorySnapshotsEqual(
34
+ left: FieldRemapHistorySnapshot,
35
+ right: FieldRemapHistorySnapshot,
36
+ ): boolean {
37
+ return sameItems(left.edges, right.edges) && sameItems(left.operators, right.operators);
38
+ }
39
+
40
+ function appendBounded(
41
+ snapshots: readonly FieldRemapHistorySnapshot[],
42
+ snapshot: FieldRemapHistorySnapshot,
43
+ ): readonly FieldRemapHistorySnapshot[] {
44
+ return [...snapshots.slice(-(FIELD_REMAP_HISTORY_LIMIT - 1)), snapshot];
45
+ }
46
+
47
+ export function recordFieldRemapHistory(
48
+ state: FieldRemapHistoryState,
49
+ current: FieldRemapHistorySnapshot,
50
+ next: FieldRemapHistorySnapshot,
51
+ ): FieldRemapHistoryState {
52
+ if (areFieldRemapHistorySnapshotsEqual(current, next)) {
53
+ return state;
54
+ }
55
+ return {
56
+ past: appendBounded(
57
+ state.past,
58
+ createFieldRemapHistorySnapshot(current.edges, current.operators),
59
+ ),
60
+ future: [],
61
+ };
62
+ }
63
+
64
+ export function undoFieldRemapHistory(
65
+ state: FieldRemapHistoryState,
66
+ current: FieldRemapHistorySnapshot,
67
+ ): { readonly state: FieldRemapHistoryState; readonly snapshot: FieldRemapHistorySnapshot } | null {
68
+ const snapshot = state.past[state.past.length - 1];
69
+ if (!snapshot) {
70
+ return null;
71
+ }
72
+ return {
73
+ state: {
74
+ past: state.past.slice(0, -1),
75
+ future: [createFieldRemapHistorySnapshot(current.edges, current.operators), ...state.future],
76
+ },
77
+ snapshot,
78
+ };
79
+ }
80
+
81
+ export function redoFieldRemapHistory(
82
+ state: FieldRemapHistoryState,
83
+ current: FieldRemapHistorySnapshot,
84
+ ): { readonly state: FieldRemapHistoryState; readonly snapshot: FieldRemapHistorySnapshot } | null {
85
+ const snapshot = state.future[0];
86
+ if (!snapshot) {
87
+ return null;
88
+ }
89
+ return {
90
+ state: {
91
+ past: appendBounded(
92
+ state.past,
93
+ createFieldRemapHistorySnapshot(current.edges, current.operators),
94
+ ),
95
+ future: state.future.slice(1),
96
+ },
97
+ snapshot,
98
+ };
99
+ }
@@ -6,12 +6,20 @@
6
6
  * (Flow also side-imports the same stylesheet).
7
7
  */
8
8
 
9
- export { FieldRemapPanel, type FieldRemapPanelProps } from './panel.js';
9
+ export {
10
+ FieldRemapPanel,
11
+ type FieldRemapHistoryActions,
12
+ type FieldRemapHistoryAvailability,
13
+ type FieldRemapHistoryOwner,
14
+ type FieldRemapHistorySnapshot,
15
+ type FieldRemapPanelProps,
16
+ } from './panel.js';
10
17
  export {
11
18
  FieldRemapFlowMapper,
12
19
  type FieldRemapFlowActions,
13
20
  type FieldRemapFlowMapperProps,
14
21
  } from './flow.js';
22
+ export type { FieldRemapPreviewState } from './preview.js';
15
23
  export {
16
24
  defaultFieldRemapChromeLabels,
17
25
  fieldRemapChromeLabelKeys,