@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
@@ -0,0 +1,88 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ import type {
4
+ ShowWorkbenchNoticeInput,
5
+ WorkbenchNoticeController,
6
+ WorkbenchNoticeTone,
7
+ } from './WorkbenchNotice.js';
8
+
9
+ export interface WorkbenchNotificationAction {
10
+ readonly label: string;
11
+ readonly onAction: () => void;
12
+ }
13
+
14
+ export interface WorkbenchNotifyOptions {
15
+ readonly actions?: readonly WorkbenchNotificationAction[];
16
+ readonly dataAttributes?: ShowWorkbenchNoticeInput['dataAttributes'];
17
+ readonly durationMs?: number;
18
+ readonly id?: string;
19
+ }
20
+
21
+ /**
22
+ * NotificationService-shaped facade over {@link WorkbenchNoticeController}.
23
+ * Prefer this for command/install/save feedback; keep modal confirms separate.
24
+ */
25
+ export interface WorkbenchNotify {
26
+ info(message: ReactNode, options?: WorkbenchNotifyOptions): string;
27
+ error(message: ReactNode, options?: WorkbenchNotifyOptions): string;
28
+ success(message: ReactNode, options?: WorkbenchNotifyOptions): string;
29
+ warning(message: ReactNode, options?: WorkbenchNotifyOptions): string;
30
+ dismiss(id: string): void;
31
+ clear(): void;
32
+ }
33
+
34
+ function showTone(
35
+ controller: WorkbenchNoticeController,
36
+ tone: WorkbenchNoticeTone,
37
+ message: ReactNode,
38
+ options: WorkbenchNotifyOptions = {},
39
+ ): string {
40
+ const { actions, dataAttributes, durationMs, id } = options;
41
+ const noticeId = id ?? `workbench-notify-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
42
+
43
+ const noticeMessage =
44
+ actions && actions.length > 0 ? (
45
+ <span className="ui-workbench-notify-message">
46
+ <span className="ui-workbench-notify-message__text">{message}</span>
47
+ <span className="ui-workbench-notify-message__actions">
48
+ {actions.map((action) => (
49
+ <button
50
+ className="ui-workbench-notify-message__action"
51
+ key={action.label}
52
+ type="button"
53
+ onClick={() => {
54
+ action.onAction();
55
+ controller.dismissNotice(noticeId);
56
+ }}
57
+ >
58
+ {action.label}
59
+ </button>
60
+ ))}
61
+ </span>
62
+ </span>
63
+ ) : (
64
+ message
65
+ );
66
+
67
+ return controller.showNotice({
68
+ ...(dataAttributes === undefined ? {} : { dataAttributes }),
69
+ ...(durationMs === undefined ? {} : { durationMs }),
70
+ id: noticeId,
71
+ message: noticeMessage,
72
+ tone,
73
+ });
74
+ }
75
+
76
+ /**
77
+ * Create a `notify.info` / `notify.error` facade over an existing notice controller.
78
+ */
79
+ export function createWorkbenchNotify(controller: WorkbenchNoticeController): WorkbenchNotify {
80
+ return {
81
+ info: (message, options) => showTone(controller, 'info', message, options),
82
+ error: (message, options) => showTone(controller, 'error', message, options),
83
+ success: (message, options) => showTone(controller, 'success', message, options),
84
+ warning: (message, options) => showTone(controller, 'warning', message, options),
85
+ dismiss: (noticeId) => controller.dismissNotice(noticeId),
86
+ clear: () => controller.clearNotices(),
87
+ };
88
+ }
@@ -0,0 +1,72 @@
1
+ import type { ExtensionCatalogBrowseEntry, ExtensionInstallOptions } from './types.js';
2
+
3
+ export function extensionInstallRequiresApproval(entry: ExtensionCatalogBrowseEntry): boolean {
4
+ return Boolean(
5
+ entry.installPlan?.requiresApproval && !entry.installed && !entry.installPlan.blocked,
6
+ );
7
+ }
8
+
9
+ export function formatExtensionInstallApprovalMessage(entry: ExtensionCatalogBrowseEntry): string {
10
+ const permissions = entry.installPlan?.permissions ?? [];
11
+ const permissionLine =
12
+ permissions.length > 0
13
+ ? `Requested permissions: ${permissions.join(', ')}.`
14
+ : 'This package requests privileges that require approval.';
15
+
16
+ return [
17
+ `Install "${entry.displayName}"?`,
18
+ '',
19
+ permissionLine,
20
+ 'Continue only if you trust this package.',
21
+ ].join('\n');
22
+ }
23
+
24
+ export interface ResolveExtensionInstallOptionsInput {
25
+ readonly confirm?: ((message: string) => boolean) | undefined;
26
+ /**
27
+ * When true (or when the predicate returns true), skip the confirm prompt and
28
+ * return `{ approved: true }`. Hosts typically back this with a durable trust
29
+ * store keyed by extension id + permission fingerprint.
30
+ */
31
+ readonly isTrusted?: boolean | ((entry: ExtensionCatalogBrowseEntry) => boolean) | undefined;
32
+ /** Called after the user explicitly confirms trust for this install. */
33
+ readonly rememberTrust?: ((entry: ExtensionCatalogBrowseEntry) => void) | undefined;
34
+ }
35
+
36
+ /**
37
+ * Resolve install options for UI actions.
38
+ *
39
+ * Approval is required when the install plan requests permissions. A trusted
40
+ * predicate may skip the prompt; otherwise the host confirm dialog runs and
41
+ * successful consent may be persisted via `rememberTrust`.
42
+ */
43
+ export function resolveExtensionInstallOptions(
44
+ entry: ExtensionCatalogBrowseEntry,
45
+ confirmOrOptions: ((message: string) => boolean) | ResolveExtensionInstallOptionsInput = (
46
+ message,
47
+ ) => (typeof globalThis.confirm === 'function' ? globalThis.confirm(message) : false),
48
+ ): ExtensionInstallOptions | undefined {
49
+ if (!extensionInstallRequiresApproval(entry)) {
50
+ return {};
51
+ }
52
+
53
+ const options =
54
+ typeof confirmOrOptions === 'function' ? { confirm: confirmOrOptions } : confirmOrOptions;
55
+ const confirm =
56
+ options.confirm ??
57
+ ((message: string) =>
58
+ typeof globalThis.confirm === 'function' ? globalThis.confirm(message) : false);
59
+
60
+ const trusted =
61
+ typeof options.isTrusted === 'function' ? options.isTrusted(entry) : Boolean(options.isTrusted);
62
+ if (trusted) {
63
+ return { approved: true };
64
+ }
65
+
66
+ if (!confirm(formatExtensionInstallApprovalMessage(entry))) {
67
+ return undefined;
68
+ }
69
+
70
+ options.rememberTrust?.(entry);
71
+ return { approved: true };
72
+ }
@@ -0,0 +1,38 @@
1
+ import type { ExtensionCatalogBrowseEntry, ExtensionManagementEntry } from './types.js';
2
+
3
+ export function filterInstalledEntries(
4
+ entries: readonly ExtensionManagementEntry[],
5
+ query: string,
6
+ ): readonly ExtensionManagementEntry[] {
7
+ const normalized = query.trim().toLowerCase();
8
+ if (!normalized) {
9
+ return entries;
10
+ }
11
+
12
+ return entries.filter((entry) =>
13
+ [entry.displayName, entry.id, entry.category, entry.description ?? '']
14
+ .join(' ')
15
+ .toLowerCase()
16
+ .includes(normalized),
17
+ );
18
+ }
19
+
20
+ export function filterBrowseEntries(
21
+ entries: readonly ExtensionCatalogBrowseEntry[],
22
+ query: string,
23
+ category?: string,
24
+ ): readonly ExtensionCatalogBrowseEntry[] {
25
+ const normalized = query.trim().toLowerCase();
26
+ return entries.filter((entry) => {
27
+ if (category && entry.category !== category) {
28
+ return false;
29
+ }
30
+ if (!normalized) {
31
+ return true;
32
+ }
33
+ return [entry.displayName, entry.id, entry.category, entry.description]
34
+ .join(' ')
35
+ .toLowerCase()
36
+ .includes(normalized);
37
+ });
38
+ }
@@ -22,10 +22,10 @@ export type {
22
22
  ExtensionManagementPendingAction,
23
23
  } from './ExtensionManagementSidebar.js';
24
24
  export {
25
- createIntegrationCommandAction,
25
+ IntegrationActionRow,
26
+ IntegrationCommandButton,
26
27
  type IntegrationCommandAction,
27
- } from './integration-command-action.js';
28
- export { IntegrationActionRow, IntegrationCommandButton } from './IntegrationCommandActions.js';
28
+ } from './IntegrationCommandActions.js';
29
29
  export { IntegrationsShell } from './IntegrationsShell.js';
30
30
  export type { IntegrationsShellProps } from './IntegrationsShell.js';
31
31
  export {
@@ -75,6 +75,13 @@ export type {
75
75
  WorkbenchNoticePosition,
76
76
  WorkbenchNoticeTone,
77
77
  } from './WorkbenchNotice.js';
78
+ export { createWorkbenchNotify } from './createWorkbenchNotify.js';
79
+ export type {
80
+ WorkbenchNotificationAction,
81
+ WorkbenchNotify,
82
+ WorkbenchNotifyOptions,
83
+ } from './createWorkbenchNotify.js';
84
+ export { useWorkbenchNotify } from './useWorkbenchNotify.js';
78
85
  export { KeybindingCaptureField } from './KeybindingCaptureField.js';
79
86
  export type { KeybindingCaptureFieldProps } from './KeybindingCaptureField.js';
80
87
  export { KeybindingManagementPanel } from './KeybindingManagementPanel.js';
@@ -112,6 +119,12 @@ export {
112
119
  extensionCategoryIconTone,
113
120
  formatExtensionCategoryLabel,
114
121
  } from './extension-category-display.js';
122
+ export {
123
+ extensionInstallRequiresApproval,
124
+ formatExtensionInstallApprovalMessage,
125
+ resolveExtensionInstallOptions,
126
+ type ResolveExtensionInstallOptionsInput,
127
+ } from './extension-install-approval.js';
115
128
  export type {
116
129
  AccountManagementEntry,
117
130
  AccountManagementEntryStatus,
@@ -122,9 +135,11 @@ export type {
122
135
  CommandManagementPanelProps,
123
136
  CommandManagementRunState,
124
137
  ExtensionCatalogBrowseEntry,
138
+ ExtensionInstallOptions,
125
139
  ExtensionManagementDiagnosticSummary,
126
140
  ExtensionManagementEntry,
127
141
  ExtensionManagementFeatureItem,
128
142
  ExtensionManagementFeatureSummary,
129
143
  ExtensionManagementPanelProps,
144
+ ExtensionManagementTransition,
130
145
  } from './types.js';
@@ -2,3 +2,31 @@
2
2
  @import './library-catalog-picker-dialog.css';
3
3
  @import './library-facet-filter-dialog.css';
4
4
  @import './management-panel.css';
5
+
6
+ .ui-workbench-notify-message {
7
+ display: inline-flex;
8
+ flex-wrap: wrap;
9
+ gap: 8px;
10
+ align-items: center;
11
+ }
12
+
13
+ .ui-workbench-notify-message__actions {
14
+ display: inline-flex;
15
+ flex-wrap: wrap;
16
+ gap: 6px;
17
+ }
18
+
19
+ .ui-workbench-notify-message__action {
20
+ margin: 0;
21
+ padding: 2px 8px;
22
+ border: 1px solid var(--color-border, currentColor);
23
+ border-radius: var(--radius-sm, 4px);
24
+ background: transparent;
25
+ color: inherit;
26
+ font: inherit;
27
+ cursor: pointer;
28
+ }
29
+
30
+ .ui-workbench-notify-message__action:hover {
31
+ background: var(--color-surface-hover, rgba(255, 255, 255, 0.08));
32
+ }
@@ -82,6 +82,11 @@ export interface ExtensionManagementDiagnosticSummary {
82
82
  readonly severity: 'error' | 'warning';
83
83
  }
84
84
 
85
+ export interface ExtensionManagementTransition {
86
+ readonly kind: 'applied' | 'reloadRequired' | 'failed';
87
+ readonly message: string;
88
+ }
89
+
85
90
  export interface ExtensionInstallPlanSummary {
86
91
  readonly blocked: boolean;
87
92
  readonly diagnostics?: readonly ExtensionManagementDiagnosticSummary[] | undefined;
@@ -92,6 +97,8 @@ export interface ExtensionInstallPlanSummary {
92
97
  }
93
98
 
94
99
  export interface ExtensionManagementEntry {
100
+ /** True only when a persisted installed record currently owns this entry. */
101
+ readonly canUninstall?: boolean;
95
102
  readonly category: string;
96
103
  readonly description?: string | undefined;
97
104
  readonly diagnostics?: readonly ExtensionManagementDiagnosticSummary[] | undefined;
@@ -102,6 +109,7 @@ export interface ExtensionManagementEntry {
102
109
  readonly installedAt?: string | undefined;
103
110
  readonly manifestUrl?: string | undefined;
104
111
  readonly source: ExtensionManagementEntrySource;
112
+ readonly transition?: ExtensionManagementTransition;
105
113
  }
106
114
 
107
115
  export interface ExtensionCatalogBrowseEntry {
@@ -115,12 +123,26 @@ export interface ExtensionCatalogBrowseEntry {
115
123
  readonly manifestUrl: string;
116
124
  }
117
125
 
126
+ export interface ExtensionInstallOptions {
127
+ /** Required when the install plan sets `requiresApproval`. */
128
+ readonly approved?: boolean | undefined;
129
+ }
130
+
118
131
  export interface ExtensionManagementPanelProps {
119
132
  browseEntries: readonly ExtensionCatalogBrowseEntry[];
120
133
  catalogError?: string | undefined;
121
134
  catalogLoading?: boolean | undefined;
122
135
  className?: string | undefined;
123
136
  installedEntries: readonly ExtensionManagementEntry[];
124
- onInstall?: ((entry: ExtensionCatalogBrowseEntry) => void) | undefined;
137
+ onInstall?:
138
+ ((entry: ExtensionCatalogBrowseEntry, options?: ExtensionInstallOptions) => void) | undefined;
139
+ onUninstall?: (entry: ExtensionManagementEntry) => void;
125
140
  onToggleEnabled?: ((entry: ExtensionManagementEntry, enabled: boolean) => void) | undefined;
141
+ /**
142
+ * Host-owned durable trust check. When true for an entry, the install confirm
143
+ * prompt is skipped and `{ approved: true }` is used.
144
+ */
145
+ isInstallTrusted?: ((entry: ExtensionCatalogBrowseEntry) => boolean) | undefined;
146
+ /** Persist trust after the user confirms a permissioned install. */
147
+ onRememberInstallTrust?: ((entry: ExtensionCatalogBrowseEntry) => void) | undefined;
126
148
  }
@@ -0,0 +1,10 @@
1
+ import { useMemo } from 'react';
2
+
3
+ import { createWorkbenchNotify, type WorkbenchNotify } from './createWorkbenchNotify.js';
4
+ import { useWorkbenchNotice } from './WorkbenchNotice.js';
5
+
6
+ /** Convenience hook: NotificationService-shaped API inside a notice provider. */
7
+ export function useWorkbenchNotify(): WorkbenchNotify {
8
+ const controller = useWorkbenchNotice();
9
+ return useMemo(() => createWorkbenchNotify(controller), [controller]);
10
+ }
@@ -1,7 +1,11 @@
1
1
  import Markdown from 'react-markdown';
2
2
  import { Children, isValidElement, type ComponentPropsWithoutRef, type ReactNode } from 'react';
3
- import { cx } from '../utils/cx';
4
- import { workbenchMarkdownRemarkPlugins } from './markdownRemarkPlugins';
3
+ import { cx } from '../../utils/cx';
4
+ import {
5
+ workbenchMarkdownRemarkPlugins,
6
+ workbenchMarkdownRehypePlugins,
7
+ } from './markdownRemarkPlugins';
8
+ import { sanitizeMarkdownHref } from './sanitizeMarkdownHref';
5
9
 
6
10
  export interface WorkbenchMarkdownPreviewProps extends ComponentPropsWithoutRef<'article'> {
7
11
  source: string;
@@ -16,12 +20,23 @@ export function WorkbenchMarkdownPreview({
16
20
  <article className={cx('ui-workbench-markdown-preview', className)} {...props}>
17
21
  <Markdown
18
22
  remarkPlugins={workbenchMarkdownRemarkPlugins}
23
+ rehypePlugins={workbenchMarkdownRehypePlugins}
19
24
  components={{
20
- a: ({ children, href }) => (
21
- <a href={href} rel="noreferrer" target={href?.startsWith('#') ? undefined : '_blank'}>
22
- {children}
23
- </a>
24
- ),
25
+ a: ({ children, href }) => {
26
+ const safeHref = sanitizeMarkdownHref(href);
27
+ if (!safeHref) {
28
+ return <span>{children}</span>;
29
+ }
30
+ return (
31
+ <a
32
+ href={safeHref}
33
+ rel="noreferrer"
34
+ target={safeHref.startsWith('#') ? undefined : '_blank'}
35
+ >
36
+ {children}
37
+ </a>
38
+ );
39
+ },
25
40
  code: ({ children, className: codeClassName }) => (
26
41
  <code className={cx('ui-workbench-markdown-preview__code', codeClassName)}>
27
42
  {children}
@@ -0,0 +1,12 @@
1
+ import remarkGfm from 'remark-gfm';
2
+ import rehypeSanitize from 'rehype-sanitize';
3
+
4
+ /** Shared remark plugins for workbench markdown surfaces (preview, chat, etc.). */
5
+ export const workbenchMarkdownRemarkPlugins = [remarkGfm];
6
+
7
+ /**
8
+ * Default rehype pipeline for untrusted markdown (chat / preview).
9
+ * Strips script/event handlers and dangerous URLs via rehype-sanitize.
10
+ * Hosts that need raw HTML must render outside these kit surfaces.
11
+ */
12
+ export const workbenchMarkdownRehypePlugins = [rehypeSanitize];
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Allowlist markdown link targets for safe preview rendering.
3
+ * Blocks javascript:/data:/vbscript: and other non-navigation schemes.
4
+ */
5
+ export function sanitizeMarkdownHref(href: string | undefined): string | undefined {
6
+ if (href == null || href.trim() === '') {
7
+ return undefined;
8
+ }
9
+
10
+ const trimmed = href.trim();
11
+ if (trimmed.startsWith('#')) {
12
+ return trimmed;
13
+ }
14
+
15
+ try {
16
+ const base =
17
+ typeof globalThis.location?.href === 'string' && globalThis.location.href.length > 0
18
+ ? globalThis.location.href
19
+ : 'https://workbench.local/';
20
+ const url = new URL(trimmed, base);
21
+ const protocol = url.protocol.toLowerCase();
22
+ if (protocol === 'http:' || protocol === 'https:' || protocol === 'mailto:') {
23
+ return trimmed;
24
+ }
25
+ return undefined;
26
+ } catch {
27
+ return undefined;
28
+ }
29
+ }
@@ -219,56 +219,16 @@ function renderResizeHandle(
219
219
  );
220
220
  }
221
221
 
222
- const nodeRendererRegistry: Record<string, (params: NodeRendererProps) => ReactNode> = {
223
- frame: ({
224
- node,
225
- style,
226
- children,
227
- onClick,
228
- onPointerDown,
229
- onResizeHandlePointerDown,
230
- resizeHandleLabel,
231
- }) => (
232
- <div
233
- key={node.id}
234
- data-node-id={node.id}
235
- style={style}
236
- onClick={onClick}
237
- onPointerDown={onPointerDown}
238
- >
239
- <div style={{ position: 'relative', width: '100%', height: '100%' }}>{children}</div>
240
- {renderResizeHandle(resizeHandleLabel, onResizeHandlePointerDown)}
241
- </div>
242
- ),
243
- group: ({
244
- node,
245
- style,
246
- children,
247
- onClick,
248
- onPointerDown,
249
- onResizeHandlePointerDown,
250
- resizeHandleLabel,
251
- }) => (
252
- <div
253
- key={node.id}
254
- data-node-id={node.id}
255
- style={style}
256
- onClick={onClick}
257
- onPointerDown={onPointerDown}
258
- >
259
- <div style={{ position: 'relative', width: '100%', height: '100%' }}>{children}</div>
260
- {renderResizeHandle(resizeHandleLabel, onResizeHandlePointerDown)}
261
- </div>
262
- ),
263
- component: ({
264
- node,
265
- style,
266
- children,
267
- onClick,
268
- onPointerDown,
269
- onResizeHandlePointerDown,
270
- resizeHandleLabel,
271
- }) => (
222
+ function renderContainerNode({
223
+ node,
224
+ style,
225
+ children,
226
+ onClick,
227
+ onPointerDown,
228
+ onResizeHandlePointerDown,
229
+ resizeHandleLabel,
230
+ }: NodeRendererProps) {
231
+ return (
272
232
  <div
273
233
  key={node.id}
274
234
  data-node-id={node.id}
@@ -279,35 +239,39 @@ const nodeRendererRegistry: Record<string, (params: NodeRendererProps) => ReactN
279
239
  <div style={{ position: 'relative', width: '100%', height: '100%' }}>{children}</div>
280
240
  {renderResizeHandle(resizeHandleLabel, onResizeHandlePointerDown)}
281
241
  </div>
282
- ),
283
- instance: ({
284
- node,
285
- style,
286
- children,
287
- onClick,
288
- onPointerDown,
289
- onResizeHandlePointerDown,
290
- resizeHandleLabel,
291
- }) => (
242
+ );
243
+ }
244
+
245
+ function renderEmptyNode({
246
+ node,
247
+ style,
248
+ onClick,
249
+ onPointerDown,
250
+ onResizeHandlePointerDown,
251
+ resizeHandleLabel,
252
+ }: NodeRendererProps) {
253
+ return (
292
254
  <div
293
255
  key={node.id}
294
256
  data-node-id={node.id}
295
257
  style={style}
296
- onClick={onClick}
258
+ onClick={wrapNodeClick(onClick)}
297
259
  onPointerDown={onPointerDown}
298
260
  >
299
- <div style={{ position: 'relative', width: '100%', height: '100%' }}>{children}</div>
300
261
  {renderResizeHandle(resizeHandleLabel, onResizeHandlePointerDown)}
301
262
  </div>
302
- ),
303
- rectangle: ({
304
- node,
305
- style,
306
- onClick,
307
- onPointerDown,
308
- onResizeHandlePointerDown,
309
- resizeHandleLabel,
310
- }) => (
263
+ );
264
+ }
265
+
266
+ function renderContentNode({
267
+ node,
268
+ style,
269
+ onClick,
270
+ onPointerDown,
271
+ onResizeHandlePointerDown,
272
+ resizeHandleLabel,
273
+ }: NodeRendererProps) {
274
+ return (
311
275
  <div
312
276
  key={node.id}
313
277
  data-node-id={node.id}
@@ -315,9 +279,18 @@ const nodeRendererRegistry: Record<string, (params: NodeRendererProps) => ReactN
315
279
  onClick={wrapNodeClick(onClick)}
316
280
  onPointerDown={onPointerDown}
317
281
  >
282
+ {(node as { content?: string }).content ?? ''}
318
283
  {renderResizeHandle(resizeHandleLabel, onResizeHandlePointerDown)}
319
284
  </div>
320
- ),
285
+ );
286
+ }
287
+
288
+ const nodeRendererRegistry: Record<string, (params: NodeRendererProps) => ReactNode> = {
289
+ frame: renderContainerNode,
290
+ group: renderContainerNode,
291
+ component: renderContainerNode,
292
+ instance: renderContainerNode,
293
+ rectangle: renderEmptyNode,
321
294
  circle: ({
322
295
  node,
323
296
  style,
@@ -336,18 +309,7 @@ const nodeRendererRegistry: Record<string, (params: NodeRendererProps) => ReactN
336
309
  {renderResizeHandle(resizeHandleLabel, onResizeHandlePointerDown)}
337
310
  </div>
338
311
  ),
339
- text: ({ node, style, onClick, onPointerDown, onResizeHandlePointerDown, resizeHandleLabel }) => (
340
- <div
341
- key={node.id}
342
- data-node-id={node.id}
343
- style={style}
344
- onClick={wrapNodeClick(onClick)}
345
- onPointerDown={onPointerDown}
346
- >
347
- {(node as { content?: string }).content ?? ''}
348
- {renderResizeHandle(resizeHandleLabel, onResizeHandlePointerDown)}
349
- </div>
350
- ),
312
+ text: renderContentNode,
351
313
  image: ({
352
314
  node,
353
315
  style,
@@ -394,43 +356,8 @@ const nodeRendererRegistry: Record<string, (params: NodeRendererProps) => ReactN
394
356
  </div>
395
357
  );
396
358
  },
397
- vector: ({
398
- node,
399
- style,
400
- onClick,
401
- onPointerDown,
402
- onResizeHandlePointerDown,
403
- resizeHandleLabel,
404
- }) => (
405
- <div
406
- key={node.id}
407
- data-node-id={node.id}
408
- style={style}
409
- onClick={wrapNodeClick(onClick)}
410
- onPointerDown={onPointerDown}
411
- >
412
- {(node as { content?: string }).content ?? ''}
413
- {renderResizeHandle(resizeHandleLabel, onResizeHandlePointerDown)}
414
- </div>
415
- ),
416
- unknown: ({
417
- node,
418
- style,
419
- onClick,
420
- onPointerDown,
421
- onResizeHandlePointerDown,
422
- resizeHandleLabel,
423
- }) => (
424
- <div
425
- key={node.id}
426
- data-node-id={node.id}
427
- style={style}
428
- onClick={wrapNodeClick(onClick)}
429
- onPointerDown={onPointerDown}
430
- >
431
- {renderResizeHandle(resizeHandleLabel, onResizeHandlePointerDown)}
432
- </div>
433
- ),
359
+ vector: renderContentNode,
360
+ unknown: renderEmptyNode,
434
361
  };
435
362
 
436
363
  function renderNode(
@@ -37,6 +37,7 @@ import {
37
37
  type WorkbenchStructuredDataSchemaFieldInputProps,
38
38
  type WorkbenchStructuredDataTextArrayInputProps,
39
39
  } from './structuredDataSchema';
40
+ import { isRenderableWorkbenchStructuredDataFormError } from './structuredDataFormModel';
40
41
 
41
42
  export function WorkbenchStructuredDataForm({
42
43
  ariaLabel,
@@ -156,10 +157,6 @@ export function WorkbenchStructuredDataForm({
156
157
  );
157
158
  }
158
159
 
159
- function isRenderableWorkbenchStructuredDataFormError(error: ReactNode) {
160
- return error !== undefined && error !== null && error !== false && error !== '';
161
- }
162
-
163
160
  function StructuredDataSection({
164
161
  data,
165
162
  disabled,