@workbench-kit/react 0.0.2-prototype.0.2.4 → 0.0.2-prototype.0.2.40

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 (221) hide show
  1. package/README.md +172 -0
  2. package/package.json +38 -21
  3. package/src/index.ts +74 -34
  4. package/src/jdw/JsonCodeEditorPane.tsx +7 -6
  5. package/src/jdw/builtins/renderBuiltinWidgetLeaf.tsx +1 -3
  6. package/src/jdw/createBuiltinJdwRegistry.ts +1 -4
  7. package/src/jdw/cssRenderBackend.tsx +1 -4
  8. package/src/jdw/fixtures/jdw-fixtures.ts +0 -15
  9. package/src/json-config/JsonConfigWorkbench.tsx +1 -1
  10. package/src/layout/index.ts +11 -2
  11. package/src/layout/panel/Panel.tsx +0 -3
  12. package/src/layout/panel/index.ts +0 -2
  13. package/src/layout/sidebar/SideBarTree.tsx +301 -0
  14. package/src/layout/sidebar/SidebarActionIconBar.tsx +3 -3
  15. package/src/layout/sidebar/WorkbenchSidebarActions.tsx +1 -1
  16. package/src/layout/sidebar/index.ts +13 -0
  17. package/src/modal/Modal.tsx +55 -25
  18. package/src/modal/ModalTitlebar.tsx +8 -8
  19. package/src/modal/modalPosition.ts +3 -6
  20. package/src/modal/useModalFocusTrap.ts +142 -0
  21. package/src/modal/useModalWindowFrame.ts +2 -2
  22. package/src/overlay/ContextMenu.tsx +133 -21
  23. package/src/overlay/context-menu-density.css +22 -0
  24. package/src/overlay/context-menu-item.ts +17 -0
  25. package/src/overlay/context-menu.css +6 -2
  26. package/src/overlay/index.ts +6 -1
  27. package/src/overlay/measureAnchoredOverlayPanel.ts +4 -6
  28. package/src/overlay/pointerPassthroughRegion.ts +17 -1
  29. package/src/overlay/usePointerPassthroughRegion.ts +35 -6
  30. package/src/primitives/absolute-box/AbsoluteBox.tsx +4 -4
  31. package/src/primitives/app-icon/AppIcon.tsx +33 -0
  32. package/src/primitives/app-icon/app-icon.css +28 -0
  33. package/src/primitives/button/Button.tsx +4 -4
  34. package/src/primitives/catalog-browse-card/CatalogBrowseCard.tsx +6 -6
  35. package/src/primitives/catalog-browse-pane/CatalogBrowsePane.tsx +39 -32
  36. package/src/primitives/catalog-browse-pane/catalog-browse-pane.css +4 -0
  37. package/src/primitives/catalog-filter-overlay/CatalogFilterOverlay.tsx +2 -2
  38. package/src/primitives/checkbox/Checkbox.tsx +1 -1
  39. package/src/primitives/chip/Chip.tsx +3 -12
  40. package/src/primitives/chip/index.ts +2 -2
  41. package/src/primitives/codicon/Codicon.tsx +1 -1
  42. package/src/primitives/file-icon/FileIcon.tsx +1 -1
  43. package/src/primitives/icon-button/IconButton.tsx +1 -1
  44. package/src/primitives/index.ts +7 -2
  45. package/src/primitives/library-detail-layout/LibraryDetailLayout.tsx +123 -9
  46. package/src/primitives/library-detail-layout/library-detail-layout.css +57 -0
  47. package/src/primitives/library-detail-layout/resolve-hero-cover-media.ts +80 -0
  48. package/src/primitives/library-facet-filter-panel/LibraryFacetFilterPanel.tsx +12 -12
  49. package/src/primitives/library-facet-filter-strip/LibraryFacetFilterStrip.tsx +4 -4
  50. package/src/primitives/number-input/NumberInput.tsx +5 -5
  51. package/src/primitives/primitives.css +1 -0
  52. package/src/primitives/record-media-hero/RecordMediaHero.tsx +10 -6
  53. package/src/primitives/scroll-area/ScrollArea.tsx +5 -5
  54. package/src/primitives/scroll-area-infinite-load/useScrollAreaInfiniteLoad.ts +5 -5
  55. package/src/primitives/searchable-multi-select/SearchableMultiSelect.tsx +7 -7
  56. package/src/primitives/select/Select.tsx +1 -13
  57. package/src/primitives/text-area/TextArea.tsx +1 -1
  58. package/src/primitives/text-input/TextInput.tsx +1 -1
  59. package/src/primitives/workbench-media-slot/WorkbenchMediaPlaceholder.tsx +2 -2
  60. package/src/primitives/workbench-media-slot/WorkbenchMediaSlot.tsx +16 -10
  61. package/src/primitives/workbench-thumbnail/WorkbenchThumbnail.tsx +4 -4
  62. package/src/scrollbars.css +2 -6
  63. package/src/styles/core.css +16 -0
  64. package/src/styles/foundation.css +4 -0
  65. package/src/styles/overlay.css +2 -0
  66. package/src/styles.css +1 -15
  67. package/src/utils/clamp.ts +4 -0
  68. package/src/utils/normalizeKeyToken.ts +13 -0
  69. package/src/utils/readNumber.ts +4 -0
  70. package/src/widget-tree/WidgetSourceEditor.tsx +0 -14
  71. package/src/widget-tree/WidgetTreeCanvasPreview.tsx +3 -10
  72. package/src/widget-tree/WidgetTreeLab.tsx +2 -23
  73. package/src/widget-tree/WidgetTreeSidePanel.tsx +0 -5
  74. package/src/widget-tree/WidgetTreeWorkspaceShell.tsx +2 -2
  75. package/src/widget-tree/index.ts +1 -5
  76. package/src/workbench/auth/WorkbenchLoginView.tsx +1 -2
  77. package/src/workbench/auth/auth-flows.css +14 -0
  78. package/src/workbench/chat/ChatCommandProposalCard.tsx +4 -4
  79. package/src/workbench/chat/ChatConversationBar.tsx +3 -3
  80. package/src/workbench/chat/ChatHistoryMenu.tsx +1 -1
  81. package/src/workbench/chat/ChatMessageCollapsible.tsx +4 -0
  82. package/src/workbench/chat/ChatMessageItem.tsx +68 -10
  83. package/src/workbench/chat/ChatMessageList.tsx +1 -1
  84. package/src/workbench/chat/ChatPanel.tsx +32 -23
  85. package/src/workbench/chat/ChatPhasedRunProgress.tsx +86 -13
  86. package/src/workbench/chat/ChatRenameDialog.tsx +1 -1
  87. package/src/workbench/chat/chat-conversation.css +2 -2
  88. package/src/workbench/chat/chat-ui.css +8 -0
  89. package/src/workbench/chat/chatConversation.ts +2 -2
  90. package/src/workbench/chat/chatMessageMeta.tsx +4 -4
  91. package/src/workbench/chat/index.ts +1 -0
  92. package/src/workbench/chat/types.ts +7 -7
  93. package/src/workbench/chat/useCancelRuntimeOnUnmount.ts +4 -4
  94. package/src/workbench/chat/useChatPrependPagination.ts +10 -10
  95. package/src/workbench/chat/useSlashCommandSuggest.ts +1 -1
  96. package/src/workbench/{WorkbenchModalPortal.tsx → chrome/WorkbenchModalPortal.tsx} +1 -1
  97. package/src/workbench/chrome/workbench-chrome.css +4 -0
  98. package/src/workbench/chrome/workbench-layout-regions.css +13 -0
  99. package/src/workbench/chrome/workbench-shell-titlebar.css +0 -8
  100. package/src/workbench/{CommandGroupShell.tsx → commands/CommandGroupShell.tsx} +2 -2
  101. package/src/workbench/{CommandList.tsx → commands/CommandList.tsx} +6 -5
  102. package/src/workbench/{CommandPalette.tsx → commands/CommandPalette.tsx} +16 -42
  103. package/src/workbench/{ConfirmationFlow.tsx → commands/ConfirmationFlow.tsx} +5 -5
  104. package/src/workbench/{ShortcutCommandBridge.tsx → commands/ShortcutCommandBridge.tsx} +17 -13
  105. package/src/workbench/commands/WorkbenchQuickOpen.tsx +462 -0
  106. package/src/workbench/{command-model.ts → commands/command-model.ts} +5 -1
  107. package/src/workbench/commands/command-ui.ts +3 -0
  108. package/src/workbench/{commands.ts → commands/commands.ts} +45 -1
  109. package/src/workbench/commands/createWorkspaceFilesQuickOpenProvider.ts +86 -0
  110. package/src/workbench/commands/quick-open-model.ts +66 -0
  111. package/src/workbench/{WorkbenchEditorTabs.tsx → editor/WorkbenchEditorTabs.tsx} +13 -3
  112. package/src/workbench/{editor-tabs-dnd.ts → editor/editor-tabs-dnd.ts} +1 -1
  113. package/src/workbench/{editorTabContextMenu.ts → editor/editorTabContextMenu.ts} +43 -4
  114. package/src/workbench/{useEditorTabsStripDnd.ts → editor/useEditorTabsStripDnd.ts} +1 -1
  115. package/src/workbench/{useWorkbenchEditorTabContextMenu.tsx → editor/useWorkbenchEditorTabContextMenu.tsx} +16 -2
  116. package/src/workbench/editor-tabs/index.ts +10 -9
  117. package/src/workbench/index.ts +114 -74
  118. package/src/workbench/management/CommandManagementSidebar.tsx +16 -30
  119. package/src/workbench/management/ExtensionManagementPanel.tsx +84 -46
  120. package/src/workbench/management/ExtensionManagementSidebar.tsx +79 -45
  121. package/src/workbench/management/IntegrationCommandActions.tsx +8 -1
  122. package/src/workbench/management/IntegrationSettingsSurface.tsx +2 -5
  123. package/src/workbench/management/IntegrationsShell.tsx +1 -1
  124. package/src/workbench/management/KeybindingCaptureField.tsx +1 -9
  125. package/src/workbench/management/WorkbenchDialogFrame.tsx +2 -2
  126. package/src/workbench/management/WorkbenchNotice.tsx +17 -12
  127. package/src/workbench/management/createWorkbenchNotify.tsx +88 -0
  128. package/src/workbench/management/extension-install-approval.ts +72 -0
  129. package/src/workbench/management/extension-management-filters.ts +38 -0
  130. package/src/workbench/management/index.ts +18 -3
  131. package/src/workbench/management/management.css +28 -0
  132. package/src/workbench/management/types.ts +23 -1
  133. package/src/workbench/management/useWorkbenchNotify.ts +10 -0
  134. package/src/workbench/{MarkdownPreview.tsx → markdown/MarkdownPreview.tsx} +22 -7
  135. package/src/workbench/markdown/markdownRemarkPlugins.ts +12 -0
  136. package/src/workbench/markdown/sanitizeMarkdownHref.ts +29 -0
  137. package/src/workbench/schema/workbenchDocumentRenderer.tsx +48 -121
  138. package/src/workbench/settings/StructuredDataForm.tsx +1 -4
  139. package/src/workbench/settings/StructuredDataSchemaPanel.tsx +39 -44
  140. package/src/workbench/settings/sectionedPanelScrollSpy.ts +0 -81
  141. package/src/workbench/settings/structuredDataFormModel.ts +2 -1
  142. package/src/workbench/settings/structuredDataSchema.ts +24 -13
  143. package/src/workbench/settings/structuredDataSchemaSection.ts +4 -0
  144. package/src/workbench/settings/structuredDataSchemaTable.ts +5 -2
  145. package/src/workbench/{ActivityBar.tsx → shell/ActivityBar.tsx} +8 -12
  146. package/src/workbench/{ArtifactShell.tsx → shell/ArtifactShell.tsx} +25 -25
  147. package/src/workbench/{SplitView.tsx → shell/SplitView.tsx} +3 -3
  148. package/src/workbench/{StatusBar.tsx → shell/StatusBar.tsx} +8 -8
  149. package/src/workbench/{StructuredArtifactEditor.tsx → shell/StructuredArtifactEditor.tsx} +9 -9
  150. package/src/workbench/{Timeline.tsx → shell/Timeline.tsx} +77 -20
  151. package/src/workbench/{WorkbenchCanvasShell.defaults.ts → shell/WorkbenchCanvasShell.defaults.ts} +1 -1
  152. package/src/workbench/{WorkbenchCanvasShell.model.ts → shell/WorkbenchCanvasShell.model.ts} +5 -5
  153. package/src/workbench/{WorkbenchCanvasShell.tsx → shell/WorkbenchCanvasShell.tsx} +6 -6
  154. package/src/workbench/{WorkbenchDesktopTitleBar.tsx → shell/WorkbenchDesktopTitleBar.tsx} +15 -15
  155. package/src/workbench/{WorkbenchShell.tsx → shell/WorkbenchShell.tsx} +45 -6
  156. package/src/workbench/{WorkbenchShellTitleBarLayoutControls.tsx → shell/WorkbenchShellTitleBarLayoutControls.tsx} +16 -19
  157. package/src/workbench/{WorkbenchStandaloneShell.tsx → shell/WorkbenchStandaloneShell.tsx} +15 -22
  158. package/src/workbench/{WorkbenchViewEditor.tsx → shell/WorkbenchViewEditor.tsx} +7 -7
  159. package/src/workbench/{WorkbenchViewSidebar.tsx → shell/WorkbenchViewSidebar.tsx} +7 -7
  160. package/src/workbench/{WorkbenchWindowChromeControls.tsx → shell/WorkbenchWindowChromeControls.tsx} +18 -15
  161. package/src/workbench/shell/activity-bar.css +47 -0
  162. package/src/workbench/{activityBarOrder.ts → shell/activityBarOrder.ts} +2 -1
  163. package/src/workbench/{shell.ts → shell/shell.ts} +20 -13
  164. package/src/workbench/{shellViewModel.ts → shell/shellViewModel.ts} +3 -3
  165. package/src/workbench/{standalone.ts → shell/standalone.ts} +14 -2
  166. package/src/workbench/{useDeferredWorkbenchMount.ts → shell/useDeferredWorkbenchMount.ts} +3 -3
  167. package/src/workbench/{useWorkbenchAppearanceDocumentSync.tsx → shell/useWorkbenchAppearanceDocumentSync.tsx} +1 -1
  168. package/src/workbench/{useWorkbenchSidebarActionBarDnd.ts → shell/useWorkbenchSidebarActionBarDnd.ts} +1 -1
  169. package/src/workbench/{useWorkbenchSidebarViewPlacementDropZone.ts → shell/useWorkbenchSidebarViewPlacementDropZone.ts} +2 -2
  170. package/src/workbench/{workbenchSidebarLayoutContext.ts → shell/workbenchSidebarLayoutContext.ts} +0 -6
  171. package/src/workbench/{workbenchViewRouteState.ts → shell/workbenchViewRouteState.ts} +4 -4
  172. package/src/workbench/{WorkbenchThemeProvider.tsx → theme/WorkbenchThemeProvider.tsx} +4 -4
  173. package/src/workbench/{theme.ts → theme/theme.ts} +1 -1
  174. package/src/workbench/{themePresets.ts → theme/themePresets.ts} +3 -2
  175. package/src/workbench/views/command-list-density.css +36 -0
  176. package/src/workbench/views/command-list.css +2 -0
  177. package/src/workbench/views/extensions-sidebar.css +2 -0
  178. package/src/workbench/views/view-hosts.css +3 -3
  179. package/src/workbench/workspace/WorkspaceEditorPanel.tsx +1 -1
  180. package/src/workbench/workspace/WorkspaceExplorer.tsx +105 -4
  181. package/src/workbench/workspace/createVirtualWorkspaceExplorerPort.ts +1 -14
  182. package/src/workbench/workspace/workspaceExplorerKeyboard.ts +109 -0
  183. package/src/workbench/workspace/workspaceJsonDiagnostics.ts +6 -5
  184. package/src/workbench-menu-surfaces.css +3 -57
  185. package/src/brand/TilepaperAppIcon.tsx +0 -181
  186. package/src/brand/index.ts +0 -2
  187. package/src/brand/tilepaper-app-icon-geometry.ts +0 -201
  188. package/src/primitives/absolute-box/index.ts +0 -1
  189. package/src/primitives/catalog-browse-card/index.ts +0 -1
  190. package/src/primitives/catalog-browse-pane/index.ts +0 -3
  191. package/src/primitives/catalog-filter-overlay/index.ts +0 -1
  192. package/src/primitives/codicon/index.ts +0 -1
  193. package/src/primitives/external-link-button/index.ts +0 -1
  194. package/src/primitives/library-detail-layout/index.ts +0 -1
  195. package/src/primitives/library-facet-filter-panel/index.ts +0 -1
  196. package/src/primitives/library-facet-filter-strip/index.ts +0 -1
  197. package/src/primitives/panel-loading/index.ts +0 -1
  198. package/src/primitives/scroll-area-infinite-load/index.ts +0 -2
  199. package/src/primitives/workbench-thumbnail/index.ts +0 -1
  200. package/src/test-utils/workbenchMonacoMock.tsx +0 -37
  201. package/src/workbench/jdw/jdw-editors.css +0 -4
  202. package/src/workbench/jdw/jdw.css +0 -2
  203. package/src/workbench/management/integration-command-action.ts +0 -25
  204. package/src/workbench/markdownRemarkPlugins.ts +0 -4
  205. package/src/workbench/workbench-chrome.css +0 -4
  206. /package/src/workbench/{WorkbenchPlatformContext.tsx → chrome/WorkbenchPlatformContext.tsx} +0 -0
  207. /package/src/workbench/{workbenchModalViewState.ts → chrome/workbenchModalViewState.ts} +0 -0
  208. /package/src/workbench/{workbenchOverlaysContext.tsx → chrome/workbenchOverlaysContext.tsx} +0 -0
  209. /package/src/workbench/{workbenchPlatformChrome.ts → chrome/workbenchPlatformChrome.ts} +0 -0
  210. /package/src/workbench/{command-execution-policy.ts → commands/command-execution-policy.ts} +0 -0
  211. /package/src/workbench/{keyboard.ts → commands/keyboard.ts} +0 -0
  212. /package/src/workbench/{workbenchContextMenu.ts → commands/workbenchContextMenu.ts} +0 -0
  213. /package/src/workbench/{appearanceLabels.ts → shell/appearanceLabels.ts} +0 -0
  214. /package/src/workbench/{hardReset.ts → shell/hardReset.ts} +0 -0
  215. /package/src/workbench/{shellPresets.ts → shell/shellPresets.ts} +0 -0
  216. /package/src/workbench/{shellState.ts → shell/shellState.ts} +0 -0
  217. /package/src/workbench/{sidebarViewPlacementDnd.ts → shell/sidebarViewPlacementDnd.ts} +0 -0
  218. /package/src/workbench/{status.ts → shell/status.ts} +0 -0
  219. /package/src/workbench/{useWorkbenchSidebarLayout.tsx → shell/useWorkbenchSidebarLayout.tsx} +0 -0
  220. /package/src/workbench/{useWorkbenchStandaloneShellStateSync.tsx → shell/useWorkbenchStandaloneShellStateSync.tsx} +0 -0
  221. /package/src/workbench/{workbenchStandaloneShellReactContext.tsx → shell/workbenchStandaloneShellReactContext.tsx} +0 -0
@@ -7,6 +7,8 @@ import {
7
7
  extensionCategoryIconTone,
8
8
  formatExtensionCategoryLabel,
9
9
  } from './extension-category-display.js';
10
+ import { resolveExtensionInstallOptions } from './extension-install-approval.js';
11
+ import { filterBrowseEntries, filterInstalledEntries } from './extension-management-filters.js';
10
12
  import { ManagementFilterChips } from './ManagementFilterChips.js';
11
13
  import { ManagementCard, ManagementCardList } from './ManagementCard.js';
12
14
  import { ManagementGroup, ManagementGroups } from './ManagementGroup.js';
@@ -22,6 +24,7 @@ import type {
22
24
  ExtensionManagementEntry,
23
25
  ExtensionManagementFeatureSummary,
24
26
  ExtensionManagementPanelProps,
27
+ ExtensionManagementTransition,
25
28
  } from './types.js';
26
29
 
27
30
  const BROWSE_CATEGORIES = ['all', 'feature', 'editor', 'theme', 'language'] as const;
@@ -40,8 +43,11 @@ export function ExtensionManagementPanel({
40
43
  catalogLoading = false,
41
44
  className,
42
45
  installedEntries,
46
+ isInstallTrusted,
43
47
  onInstall,
48
+ onRememberInstallTrust,
44
49
  onToggleEnabled,
50
+ onUninstall,
45
51
  }: ExtensionManagementPanelProps) {
46
52
  const [activeTab, setActiveTab] = useState<'installed' | 'browse'>(
47
53
  EXTENSION_MANAGEMENT_DEFAULT_TAB,
@@ -89,6 +95,7 @@ export function ExtensionManagementPanel({
89
95
  summary={`${filteredInstalled.length} of ${installedEntries.length} visible`}
90
96
  onQueryChange={setInstalledQuery}
91
97
  onToggleEnabled={onToggleEnabled}
98
+ onUninstall={onUninstall}
92
99
  />
93
100
  ),
94
101
  },
@@ -103,12 +110,14 @@ export function ExtensionManagementPanel({
103
110
  emptyLabel="No catalog extensions match the current filter."
104
111
  entries={filteredBrowse}
105
112
  groups={browseGroups}
113
+ isInstallTrusted={isInstallTrusted}
106
114
  query={browseQuery}
107
115
  showGroups={showBrowseGroups}
108
116
  summary={`${filteredBrowse.length} of ${browseEntries.length} in catalog`}
109
117
  onCategoryChange={setCategoryFilter}
110
118
  onInstall={onInstall}
111
119
  onQueryChange={setBrowseQuery}
120
+ onRememberInstallTrust={onRememberInstallTrust}
112
121
  />
113
122
  ),
114
123
  },
@@ -116,7 +125,8 @@ export function ExtensionManagementPanel({
116
125
  onSelect={(tabId) => setActiveTab(tabId as 'installed' | 'browse')}
117
126
  />
118
127
  <ManagementPanelNotice>
119
- Installing or toggling extensions reloads the workbench to apply contributions.
128
+ Eligible unselected theme packs apply immediately. Other extension changes reload the
129
+ workbench.
120
130
  </ManagementPanelNotice>
121
131
  </ManagementPanelFrame>
122
132
  );
@@ -127,6 +137,7 @@ function InstalledExtensionsTab({
127
137
  entries,
128
138
  onQueryChange,
129
139
  onToggleEnabled,
140
+ onUninstall,
130
141
  query,
131
142
  summary,
132
143
  }: {
@@ -134,6 +145,7 @@ function InstalledExtensionsTab({
134
145
  entries: readonly ExtensionManagementEntry[];
135
146
  onQueryChange: (query: string) => void;
136
147
  onToggleEnabled?: ExtensionManagementPanelProps['onToggleEnabled'];
148
+ onUninstall?: ExtensionManagementPanelProps['onUninstall'];
137
149
  query: string;
138
150
  summary: string;
139
151
  }) {
@@ -154,15 +166,27 @@ function InstalledExtensionsTab({
154
166
  <li key={entry.id}>
155
167
  <ManagementCard
156
168
  actions={
157
- <Button
158
- compact
159
- disabled={!onToggleEnabled || entry.source === 'bundled'}
160
- type="button"
161
- variant={entry.enabled ? 'default' : 'primary'}
162
- onClick={() => onToggleEnabled?.(entry, !entry.enabled)}
163
- >
164
- {entry.enabled ? 'Disable' : 'Enable'}
165
- </Button>
169
+ <>
170
+ <Button
171
+ compact
172
+ disabled={!onToggleEnabled || entry.source === 'bundled'}
173
+ type="button"
174
+ variant={entry.enabled ? 'default' : 'primary'}
175
+ onClick={() => onToggleEnabled?.(entry, !entry.enabled)}
176
+ >
177
+ {entry.enabled ? 'Disable' : 'Enable'}
178
+ </Button>
179
+ {entry.canUninstall === true && onUninstall ? (
180
+ <Button
181
+ compact
182
+ type="button"
183
+ variant="danger"
184
+ onClick={() => onUninstall(entry)}
185
+ >
186
+ Uninstall
187
+ </Button>
188
+ ) : null}
189
+ </>
166
190
  }
167
191
  badges={
168
192
  <>
@@ -174,6 +198,14 @@ function InstalledExtensionsTab({
174
198
  {entry.enabled ? 'Enabled' : 'Disabled'}
175
199
  </Badge>
176
200
  )}
201
+ {entry.transition ? (
202
+ <Badge
203
+ title={entry.transition.message}
204
+ variant={extensionTransitionBadgeVariant(entry.transition.kind)}
205
+ >
206
+ {extensionTransitionLabel(entry.transition.kind)}
207
+ </Badge>
208
+ ) : null}
177
209
  </>
178
210
  }
179
211
  description={entry.description}
@@ -192,6 +224,22 @@ function InstalledExtensionsTab({
192
224
  );
193
225
  }
194
226
 
227
+ function extensionTransitionLabel(kind: ExtensionManagementTransition['kind']): string {
228
+ if (kind === 'reloadRequired') {
229
+ return 'Reload required';
230
+ }
231
+ return kind === 'failed' ? 'Failed' : 'Applied';
232
+ }
233
+
234
+ function extensionTransitionBadgeVariant(
235
+ kind: ExtensionManagementTransition['kind'],
236
+ ): 'accent' | 'danger' | 'muted' {
237
+ if (kind === 'failed') {
238
+ return 'danger';
239
+ }
240
+ return kind === 'applied' ? 'accent' : 'muted';
241
+ }
242
+
195
243
  function ExtensionFeatureDetails({ entry }: { entry: ExtensionManagementEntry }) {
196
244
  const rows = getExtensionFeatureDetailRows(entry.features);
197
245
  const diagnostics = entry.diagnostics ?? [];
@@ -273,9 +321,11 @@ function BrowseExtensionsTab({
273
321
  emptyLabel,
274
322
  entries,
275
323
  groups,
324
+ isInstallTrusted,
276
325
  onCategoryChange,
277
326
  onInstall,
278
327
  onQueryChange,
328
+ onRememberInstallTrust,
279
329
  query,
280
330
  showGroups,
281
331
  summary,
@@ -286,9 +336,11 @@ function BrowseExtensionsTab({
286
336
  emptyLabel: string;
287
337
  entries: readonly ExtensionCatalogBrowseEntry[];
288
338
  groups: ReadonlyArray<{ category: string; entries: readonly ExtensionCatalogBrowseEntry[] }>;
339
+ isInstallTrusted?: ExtensionManagementPanelProps['isInstallTrusted'];
289
340
  onCategoryChange: (category: BrowseCategoryFilter) => void;
290
341
  onInstall?: ExtensionManagementPanelProps['onInstall'];
291
342
  onQueryChange: (query: string) => void;
343
+ onRememberInstallTrust?: ExtensionManagementPanelProps['onRememberInstallTrust'];
292
344
  query: string;
293
345
  showGroups: boolean;
294
346
  summary: string;
@@ -335,7 +387,9 @@ function BrowseExtensionsTab({
335
387
  <li key={entry.id}>
336
388
  <ExtensionCatalogCard
337
389
  entry={entry}
390
+ isInstallTrusted={isInstallTrusted}
338
391
  onInstall={onInstall}
392
+ onRememberInstallTrust={onRememberInstallTrust}
339
393
  showCategory={false}
340
394
  />
341
395
  </li>
@@ -349,7 +403,13 @@ function BrowseExtensionsTab({
349
403
  <ManagementCardList>
350
404
  {flatEntries.map((entry) => (
351
405
  <li key={entry.id}>
352
- <ExtensionCatalogCard entry={entry} onInstall={onInstall} showCategory />
406
+ <ExtensionCatalogCard
407
+ entry={entry}
408
+ isInstallTrusted={isInstallTrusted}
409
+ onInstall={onInstall}
410
+ onRememberInstallTrust={onRememberInstallTrust}
411
+ showCategory
412
+ />
353
413
  </li>
354
414
  ))}
355
415
  </ManagementCardList>
@@ -360,11 +420,15 @@ function BrowseExtensionsTab({
360
420
 
361
421
  function ExtensionCatalogCard({
362
422
  entry,
423
+ isInstallTrusted,
363
424
  onInstall,
425
+ onRememberInstallTrust,
364
426
  showCategory,
365
427
  }: {
366
428
  entry: ExtensionCatalogBrowseEntry;
429
+ isInstallTrusted?: ExtensionManagementPanelProps['isInstallTrusted'];
367
430
  onInstall?: ExtensionManagementPanelProps['onInstall'];
431
+ onRememberInstallTrust?: ExtensionManagementPanelProps['onRememberInstallTrust'];
368
432
  showCategory: boolean;
369
433
  }) {
370
434
  return (
@@ -378,7 +442,15 @@ function ExtensionCatalogCard({
378
442
  }
379
443
  type="button"
380
444
  variant={entry.installed ? 'default' : 'primary'}
381
- onClick={() => onInstall?.(entry)}
445
+ onClick={() => {
446
+ if (!onInstall) return;
447
+ const options = resolveExtensionInstallOptions(entry, {
448
+ isTrusted: isInstallTrusted,
449
+ rememberTrust: onRememberInstallTrust,
450
+ });
451
+ if (!options) return;
452
+ onInstall(entry, options);
453
+ }}
382
454
  >
383
455
  {entry.installed ? 'Installed' : entry.installPlan?.blocked ? 'Blocked' : 'Install'}
384
456
  </Button>
@@ -462,37 +534,3 @@ function groupBrowseEntries(entries: readonly ExtensionCatalogBrowseEntry[]) {
462
534
  })
463
535
  .map(([category, categoryEntries]) => ({ category, entries: categoryEntries }));
464
536
  }
465
-
466
- function filterInstalledEntries(entries: readonly ExtensionManagementEntry[], query: string) {
467
- const normalized = query.trim().toLowerCase();
468
- if (!normalized) {
469
- return entries;
470
- }
471
-
472
- return entries.filter((entry) =>
473
- [entry.displayName, entry.id, entry.category, entry.description ?? '']
474
- .join(' ')
475
- .toLowerCase()
476
- .includes(normalized),
477
- );
478
- }
479
-
480
- function filterBrowseEntries(
481
- entries: readonly ExtensionCatalogBrowseEntry[],
482
- query: string,
483
- category?: string,
484
- ) {
485
- const normalized = query.trim().toLowerCase();
486
- return entries.filter((entry) => {
487
- if (category && entry.category !== category) {
488
- return false;
489
- }
490
- if (!normalized) {
491
- return true;
492
- }
493
- return [entry.displayName, entry.id, entry.category, entry.description]
494
- .join(' ')
495
- .toLowerCase()
496
- .includes(normalized);
497
- });
498
- }
@@ -17,6 +17,8 @@ import {
17
17
  extensionCategoryIconTone,
18
18
  formatExtensionCategoryLabel,
19
19
  } from './extension-category-display.js';
20
+ import { resolveExtensionInstallOptions } from './extension-install-approval.js';
21
+ import { filterBrowseEntries, filterInstalledEntries } from './extension-management-filters.js';
20
22
  import { ManagementFilterChips } from './ManagementFilterChips.js';
21
23
  import type {
22
24
  ExtensionCatalogBrowseEntry,
@@ -24,6 +26,7 @@ import type {
24
26
  ExtensionManagementEntry,
25
27
  ExtensionManagementFeatureSummary,
26
28
  ExtensionManagementPanelProps,
29
+ ExtensionManagementTransition,
27
30
  } from './types.js';
28
31
 
29
32
  const BROWSE_CATEGORIES = ['all', 'feature', 'editor', 'theme', 'language'] as const;
@@ -41,6 +44,7 @@ export interface ExtensionManagementSidebarProps extends ExtensionManagementPane
41
44
  emptyMarketplaceLabel?: string | undefined;
42
45
  missingExtensionIds?: readonly string[] | undefined;
43
46
  pendingAction?: ExtensionManagementPendingAction | undefined;
47
+ pendingUninstallEntryId?: string | undefined;
44
48
  }
45
49
 
46
50
  export interface ExtensionManagementPendingAction {
@@ -57,10 +61,14 @@ export function ExtensionManagementSidebar({
57
61
  emptyInstalledLabel = 'No installed extensions match the filter.',
58
62
  emptyMarketplaceLabel = 'No marketplace extensions match the filter.',
59
63
  installedEntries,
64
+ isInstallTrusted,
60
65
  missingExtensionIds = [],
61
66
  onInstall,
67
+ onRememberInstallTrust,
62
68
  onToggleEnabled,
69
+ onUninstall,
63
70
  pendingAction,
71
+ pendingUninstallEntryId,
64
72
  }: ExtensionManagementSidebarProps) {
65
73
  const [activeTab, setActiveTab] = useState<'installed' | 'marketplace'>(defaultTab);
66
74
  const [query, setQuery] = useState('');
@@ -98,7 +106,8 @@ export function ExtensionManagementSidebar({
98
106
  footer={
99
107
  <p className="workbench-extensions-sidebar__notice" role="note">
100
108
  <i aria-hidden className={cxCodicon('codicon-info')} />
101
- Installing or enabling extensions reloads the workbench to apply contributions.
109
+ Eligible unselected theme packs apply immediately. Other extension changes reload the
110
+ workbench.
102
111
  </p>
103
112
  }
104
113
  footerPlacement="overlay"
@@ -153,15 +162,19 @@ export function ExtensionManagementSidebar({
153
162
  catalogLoading={catalogLoading}
154
163
  emptyLabel={emptyMarketplaceLabel}
155
164
  entries={filteredBrowse}
165
+ isInstallTrusted={isInstallTrusted}
156
166
  pendingAction={pendingAction}
157
167
  onInstall={onInstall}
168
+ onRememberInstallTrust={onRememberInstallTrust}
158
169
  />
159
170
  ) : (
160
171
  <InstalledExtensionList
161
172
  emptyLabel={emptyInstalledLabel}
162
173
  entries={filteredInstalled}
163
174
  pendingAction={pendingAction}
175
+ pendingUninstallEntryId={pendingUninstallEntryId}
164
176
  onToggleEnabled={onToggleEnabled}
177
+ onUninstall={onUninstall}
165
178
  />
166
179
  )}
167
180
  </SideBarViewFrame>
@@ -172,12 +185,16 @@ function InstalledExtensionList({
172
185
  emptyLabel,
173
186
  entries,
174
187
  onToggleEnabled,
188
+ onUninstall,
175
189
  pendingAction,
190
+ pendingUninstallEntryId,
176
191
  }: {
177
192
  emptyLabel: string;
178
193
  entries: readonly ExtensionManagementEntry[];
179
194
  onToggleEnabled?: ExtensionManagementPanelProps['onToggleEnabled'];
195
+ onUninstall?: ExtensionManagementPanelProps['onUninstall'];
180
196
  pendingAction?: ExtensionManagementPendingAction | undefined;
197
+ pendingUninstallEntryId?: string | undefined;
181
198
  }) {
182
199
  if (entries.length === 0) {
183
200
  return (
@@ -192,6 +209,8 @@ function InstalledExtensionList({
192
209
  {entries.map((entry) => {
193
210
  const isPendingToggle =
194
211
  pendingAction?.kind === 'toggle' && pendingAction.entryId === entry.id;
212
+ const isPendingUninstall = pendingUninstallEntryId === entry.id;
213
+ const isPending = isPendingToggle || isPendingUninstall;
195
214
  const errorDiagnostics = (entry.diagnostics ?? []).filter(
196
215
  (diagnostic) => diagnostic.severity === 'error',
197
216
  );
@@ -203,15 +222,28 @@ function InstalledExtensionList({
203
222
  <ExtensionSidebarListItem
204
223
  key={entry.id}
205
224
  action={
206
- <Button
207
- compact
208
- disabled={!onToggleEnabled || entry.source === 'bundled' || isPendingToggle}
209
- type="button"
210
- variant={entry.enabled ? 'default' : 'primary'}
211
- onClick={() => onToggleEnabled?.(entry, !entry.enabled)}
212
- >
213
- {isPendingToggle ? 'Reloading…' : entry.enabled ? 'Disable' : 'Enable'}
214
- </Button>
225
+ <>
226
+ <Button
227
+ compact
228
+ disabled={!onToggleEnabled || entry.source === 'bundled' || isPending}
229
+ type="button"
230
+ variant={entry.enabled ? 'default' : 'primary'}
231
+ onClick={() => onToggleEnabled?.(entry, !entry.enabled)}
232
+ >
233
+ {isPendingToggle ? 'Reloading…' : entry.enabled ? 'Disable' : 'Enable'}
234
+ </Button>
235
+ {entry.canUninstall === true && onUninstall ? (
236
+ <Button
237
+ compact
238
+ disabled={isPending}
239
+ type="button"
240
+ variant="danger"
241
+ onClick={() => onUninstall(entry)}
242
+ >
243
+ {isPendingUninstall ? 'Reloading…' : 'Uninstall'}
244
+ </Button>
245
+ ) : null}
246
+ </>
215
247
  }
216
248
  category={entry.category}
217
249
  description={entry.description}
@@ -242,6 +274,14 @@ function InstalledExtensionList({
242
274
  {warningDiagnostics.length} warning{warningDiagnostics.length === 1 ? '' : 's'}
243
275
  </Badge>
244
276
  ) : null}
277
+ {entry.transition ? (
278
+ <Badge
279
+ title={entry.transition.message}
280
+ variant={extensionTransitionBadgeVariant(entry.transition.kind)}
281
+ >
282
+ {extensionTransitionLabel(entry.transition.kind)}
283
+ </Badge>
284
+ ) : null}
245
285
  </>
246
286
  }
247
287
  title={entry.displayName}
@@ -252,19 +292,39 @@ function InstalledExtensionList({
252
292
  );
253
293
  }
254
294
 
295
+ function extensionTransitionLabel(kind: ExtensionManagementTransition['kind']): string {
296
+ if (kind === 'reloadRequired') {
297
+ return 'Reload required';
298
+ }
299
+ return kind === 'failed' ? 'Failed' : 'Applied';
300
+ }
301
+
302
+ function extensionTransitionBadgeVariant(
303
+ kind: ExtensionManagementTransition['kind'],
304
+ ): 'accent' | 'danger' | 'muted' {
305
+ if (kind === 'failed') {
306
+ return 'danger';
307
+ }
308
+ return kind === 'applied' ? 'accent' : 'muted';
309
+ }
310
+
255
311
  function MarketplaceExtensionList({
256
312
  catalogError,
257
313
  catalogLoading,
258
314
  emptyLabel,
259
315
  entries,
316
+ isInstallTrusted,
260
317
  onInstall,
318
+ onRememberInstallTrust,
261
319
  pendingAction,
262
320
  }: {
263
321
  catalogError?: string | undefined;
264
322
  catalogLoading?: boolean | undefined;
265
323
  emptyLabel: string;
266
324
  entries: readonly ExtensionCatalogBrowseEntry[];
325
+ isInstallTrusted?: ExtensionManagementPanelProps['isInstallTrusted'];
267
326
  onInstall?: ExtensionManagementPanelProps['onInstall'];
327
+ onRememberInstallTrust?: ExtensionManagementPanelProps['onRememberInstallTrust'];
268
328
  pendingAction?: ExtensionManagementPendingAction | undefined;
269
329
  }) {
270
330
  if (catalogLoading) {
@@ -318,7 +378,15 @@ function MarketplaceExtensionList({
318
378
  }
319
379
  type="button"
320
380
  variant={entry.installed ? 'default' : 'primary'}
321
- onClick={() => onInstall?.(entry)}
381
+ onClick={() => {
382
+ if (!onInstall) return;
383
+ const options = resolveExtensionInstallOptions(entry, {
384
+ isTrusted: isInstallTrusted,
385
+ rememberTrust: onRememberInstallTrust,
386
+ });
387
+ if (!options) return;
388
+ onInstall(entry, options);
389
+ }}
322
390
  >
323
391
  {entry.installed
324
392
  ? 'Installed'
@@ -456,37 +524,3 @@ function formatFeatureBadge(label: string, count: number) {
456
524
 
457
525
  return `${label} ${count}`;
458
526
  }
459
-
460
- function filterInstalledEntries(entries: readonly ExtensionManagementEntry[], query: string) {
461
- const normalized = query.trim().toLowerCase();
462
- if (!normalized) {
463
- return entries;
464
- }
465
-
466
- return entries.filter((entry) =>
467
- [entry.displayName, entry.id, entry.category, entry.description ?? '']
468
- .join(' ')
469
- .toLowerCase()
470
- .includes(normalized),
471
- );
472
- }
473
-
474
- function filterBrowseEntries(
475
- entries: readonly ExtensionCatalogBrowseEntry[],
476
- query: string,
477
- category?: string,
478
- ) {
479
- const normalized = query.trim().toLowerCase();
480
- return entries.filter((entry) => {
481
- if (category && entry.category !== category) {
482
- return false;
483
- }
484
- if (!normalized) {
485
- return true;
486
- }
487
- return [entry.displayName, entry.id, entry.category, entry.description]
488
- .join(' ')
489
- .toLowerCase()
490
- .includes(normalized);
491
- });
492
- }
@@ -1,7 +1,14 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import { Button } from '../../primitives/button';
3
3
  import { WorkbenchPropertyInline } from '../../layout/WorkbenchPropertyPanel';
4
- import type { IntegrationCommandAction } from './integration-command-action.js';
4
+
5
+ export interface IntegrationCommandAction<TCommandId extends string = string> {
6
+ readonly enabled: boolean;
7
+ readonly execute: () => Promise<void> | void;
8
+ readonly id: TCommandId;
9
+ readonly label: string;
10
+ readonly visible?: boolean | undefined;
11
+ }
5
12
 
6
13
  type IntegrationActionRowAlign = 'center' | 'start';
7
14
  type IntegrationActionRowJustify = 'between' | 'end' | 'start';
@@ -7,6 +7,7 @@ import {
7
7
  WorkbenchPropertySection,
8
8
  WorkbenchPropertyStack,
9
9
  } from '../../primitives/index';
10
+ import { cx } from '../../utils/cx';
10
11
 
11
12
  type IntegrationSettingsDensity = 'compact' | 'default';
12
13
  type IntegrationSettingsInset = 'default' | 'flush';
@@ -206,7 +207,7 @@ export function IntegrationAccountRowEditor({
206
207
  export function IntegrationBodyText({ children, size = 'default' }: IntegrationBodyTextProps) {
207
208
  return (
208
209
  <WorkbenchPropertyHint
209
- className={joinClasses('block', size === 'compact' ? 'text-xs' : undefined)}
210
+ className={cx('block', size === 'compact' ? 'text-xs' : undefined)}
210
211
  data-size={size}
211
212
  >
212
213
  {children}
@@ -284,7 +285,3 @@ function IntegrationEditorDescription({ children }: { children?: ReactNode }) {
284
285
  function hasRenderableNode(node: ReactNode | undefined): boolean {
285
286
  return node !== undefined && node !== null && node !== false;
286
287
  }
287
-
288
- function joinClasses(...classes: Array<string | undefined>): string {
289
- return classes.filter(Boolean).join(' ');
290
- }
@@ -2,7 +2,7 @@ import type { ReactNode } from 'react';
2
2
  import { PanelHeader } from '../../layout/panel';
3
3
  import { WorkbenchEditorFrame, WorkbenchPanelScroll } from '../../layout/WorkbenchLayoutBase';
4
4
  import { WorkbenchPropertyHint } from '../../layout/WorkbenchPropertyPanel';
5
- import { SplitView } from '../SplitView';
5
+ import { SplitView } from '../shell/SplitView';
6
6
 
7
7
  export interface IntegrationsShellProps {
8
8
  children: ReactNode;
@@ -2,6 +2,7 @@ import { useEffect, useId, useRef, useState, type KeyboardEvent } from 'react';
2
2
  import { formatKeybindingLabel } from '@workbench-kit/platform';
3
3
  import { Button } from '../../primitives/button';
4
4
  import { cx } from '../../utils/cx';
5
+ import { normalizeKeyToken } from '../../utils/normalizeKeyToken';
5
6
 
6
7
  export interface KeybindingCaptureFieldProps {
7
8
  ariaLabel?: string | undefined;
@@ -131,12 +132,3 @@ function normalizeKeybindingKeyFromEvent(
131
132
  parts.push(normalizeKeyToken(event.key));
132
133
  return parts.join('+');
133
134
  }
134
-
135
- function normalizeKeyToken(token: string): string {
136
- const key = token.trim().toLowerCase();
137
- if (key === 'del') return 'delete';
138
- if (key === 'esc') return 'escape';
139
- if (key === 'return') return 'enter';
140
- if (key === 'spacebar' || key === 'space') return 'space';
141
- return key.length === 1 ? key : key;
142
- }
@@ -1,7 +1,7 @@
1
1
  import type { FormEventHandler, ReactNode } from 'react';
2
2
  import { Modal, type ModalBodyLayout, type ModalBodyPadding } from '../../modal/Modal';
3
- import type { WorkbenchWindowChromeMode } from '../workbenchPlatformChrome';
4
- import { WorkbenchModalPortal } from '../WorkbenchModalPortal';
3
+ import type { WorkbenchWindowChromeMode } from '../chrome/workbenchPlatformChrome';
4
+ import { WorkbenchModalPortal } from '../chrome/WorkbenchModalPortal';
5
5
 
6
6
  export type WorkbenchDialogBodyLayout = 'column-fill' | 'default' | 'padded-fill';
7
7
  export type WorkbenchDialogFrameSize =
@@ -149,16 +149,16 @@ export function WorkbenchNoticeViewport({
149
149
 
150
150
  return (
151
151
  <div
152
- aria-live="polite"
153
- className={resolveNoticeViewportClassName(position)}
152
+ className="ui-workbench-notice-viewport"
154
153
  data-position={position}
154
+ style={resolveNoticeViewportStyle(position)}
155
155
  {...(dataAttributes ?? {})}
156
156
  >
157
157
  {visibleNotices.map((notice) => (
158
158
  <WorkbenchBanner
159
- className="pointer-events-auto"
160
159
  data-tone={notice.tone}
161
160
  key={notice.id}
161
+ role={notice.tone === 'error' ? 'alert' : 'status'}
162
162
  style={resolveNoticeBannerStyle(notice.tone)}
163
163
  tone={notice.tone === 'warning' ? 'warning' : 'default'}
164
164
  {...(notice.dataAttributes ?? {})}
@@ -186,13 +186,6 @@ export function useWorkbenchNotice(): WorkbenchNoticeController {
186
186
  return controller;
187
187
  }
188
188
 
189
- function resolveNoticeViewportClassName(position: WorkbenchNoticePosition): string {
190
- return joinClasses(
191
- 'pointer-events-none fixed inset-x-0 bottom-4 z-50 flex flex-col gap-2 px-4',
192
- position === 'bottom-center' ? 'items-center' : 'items-end',
193
- );
194
- }
195
-
196
189
  function resolveNoticeBannerStyle(tone: WorkbenchNoticeTone): CSSProperties {
197
190
  return {
198
191
  borderColor:
@@ -203,9 +196,21 @@ function resolveNoticeBannerStyle(tone: WorkbenchNoticeTone): CSSProperties {
203
196
  : undefined,
204
197
  maxWidth: 'min(520px, calc(100vw - 32px))',
205
198
  minWidth: 'min(420px, calc(100vw - 32px))',
199
+ pointerEvents: 'auto',
206
200
  };
207
201
  }
208
202
 
209
- function joinClasses(...classes: Array<string | undefined>): string {
210
- return classes.filter(Boolean).join(' ');
203
+ function resolveNoticeViewportStyle(position: WorkbenchNoticePosition): CSSProperties {
204
+ return {
205
+ alignItems: position === 'bottom-center' ? 'center' : 'flex-end',
206
+ bottom: 16,
207
+ display: 'flex',
208
+ flexDirection: 'column',
209
+ gap: 8,
210
+ insetInline: 0,
211
+ paddingInline: 16,
212
+ pointerEvents: 'none',
213
+ position: 'fixed',
214
+ zIndex: 50,
215
+ };
211
216
  }