@jini-ai/ui 0.5.4 → 0.5.6

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 (270) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/core/theme/validation.js +40 -38
  3. package/dist/features/a2ui/renderer.js +15 -13
  4. package/dist/features/about/react/components/AboutTab.js +64 -34
  5. package/dist/features/about/rules.d.ts +2 -2
  6. package/dist/features/about/rules.js +156 -129
  7. package/dist/features/admin-widgets/components/ImagePreviewModal.d.ts +4 -3
  8. package/dist/features/admin-widgets/components/ImagePreviewModal.js +6 -10
  9. package/dist/features/admin-widgets/components/InfoTip.d.ts +3 -2
  10. package/dist/features/admin-widgets/components/InfoTip.hooks.js +2 -0
  11. package/dist/features/admin-widgets/components/InfoTip.js +34 -7
  12. package/dist/features/admin-widgets/components/InfoTip.rules.d.ts +13 -0
  13. package/dist/features/admin-widgets/components/InfoTip.rules.js +43 -10
  14. package/dist/features/admin-widgets/components/Lightbox/Lightbox.d.ts +30 -0
  15. package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.d.ts +40 -0
  16. package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.js +128 -0
  17. package/dist/features/admin-widgets/components/Lightbox/Lightbox.js +18 -0
  18. package/dist/features/admin-widgets/components/MiddleTruncate.d.ts +14 -0
  19. package/dist/features/admin-widgets/components/MiddleTruncate.js +12 -0
  20. package/dist/features/admin-widgets/components/MiddleTruncate.rules.d.ts +20 -0
  21. package/dist/features/admin-widgets/components/MiddleTruncate.rules.js +21 -0
  22. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.d.ts +18 -0
  23. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.d.ts +16 -0
  24. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.js +45 -0
  25. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.js +18 -0
  26. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.rules.d.ts +32 -0
  27. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.rules.js +26 -0
  28. package/dist/features/admin-widgets/components/Select/Select.hooks.d.ts +21 -0
  29. package/dist/features/admin-widgets/components/Select/Select.hooks.js +24 -2
  30. package/dist/features/admin-widgets/components/Select/Select.js +4 -3
  31. package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.hooks.js +29 -14
  32. package/dist/features/admin-widgets/index.d.ts +14 -2
  33. package/dist/features/admin-widgets/index.js +7 -1
  34. package/dist/features/admin-widgets/styles/admin-widgets.css +1 -0
  35. package/dist/features/asset-grid/react/components/AssetCard.js +7 -1
  36. package/dist/features/asset-grid/react/components/AssetGrid.d.ts +1 -1
  37. package/dist/features/asset-grid/react/components/AssetGrid.js +38 -18
  38. package/dist/features/asset-grid/react/components/useAssetGridFilters.d.ts +21 -0
  39. package/dist/features/asset-grid/react/components/useAssetGridFilters.js +19 -0
  40. package/dist/features/asset-grid/react/hooks/useAssetGridKeyboardShortcuts.js +16 -11
  41. package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.d.ts +2 -0
  42. package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.js +35 -11
  43. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.d.ts +6 -6
  44. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.js +10 -129
  45. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.d.ts +50 -0
  46. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.js +95 -0
  47. package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.d.ts +56 -0
  48. package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.js +80 -0
  49. package/dist/features/asset-tree-browser/rules.js +40 -22
  50. package/dist/features/browser-chrome/rules.js +74 -70
  51. package/dist/features/charts/browser.js +11 -5
  52. package/dist/features/charts/react.js +2 -2
  53. package/dist/features/charts/table.js +2 -2
  54. package/dist/features/command-palette/react/hooks/useCommandPalette.js +3 -9
  55. package/dist/features/connectors/ConnectorsBrowser.d.ts +4 -4
  56. package/dist/features/connectors/ConnectorsBrowser.js +26 -14
  57. package/dist/features/connectors/components/ConnectorCard.js +67 -38
  58. package/dist/features/connectors/components/ConnectorDetailDrawer.js +62 -31
  59. package/dist/features/connectors/components/ConnectorGrid.js +11 -6
  60. package/dist/features/connectors/hooks/useConnectorAuthorization.js +6 -0
  61. package/dist/features/connectors/hooks/useConnectorCatalog.d.ts +2 -0
  62. package/dist/features/connectors/hooks/useConnectorCatalog.js +32 -13
  63. package/dist/features/connectors/hooks/useWiredConnectorsBrowser.d.ts +3 -3
  64. package/dist/features/connectors/rules.d.ts +8 -1
  65. package/dist/features/connectors/rules.js +42 -20
  66. package/dist/features/connectors/types.d.ts +11 -0
  67. package/dist/features/execution/constants.d.ts +4 -10
  68. package/dist/features/execution/constants.js +9 -94
  69. package/dist/features/execution/react/components/ByokProviderForm.d.ts +2 -4
  70. package/dist/features/execution/react/components/ByokProviderForm.js +140 -43
  71. package/dist/features/execution/react/components/ExecutionTab.d.ts +1 -2
  72. package/dist/features/execution/react/components/ExecutionTab.js +52 -30
  73. package/dist/features/execution/react/components/LocalCliAgentCard.js +12 -3
  74. package/dist/features/execution/react/components/LocalCliAgentList.js +14 -3
  75. package/dist/features/execution/react/components/LocalCliAgentSettings.js +14 -3
  76. package/dist/features/execution/react/hooks/useLocalCliAgentCard.js +54 -36
  77. package/dist/features/execution/react/hooks/useReasoningControl.d.ts +1 -1
  78. package/dist/features/execution/react/hooks/useReasoningControl.js +116 -75
  79. package/dist/features/execution/rules.d.ts +13 -2
  80. package/dist/features/execution/rules.js +51 -11
  81. package/dist/features/execution/types.d.ts +6 -42
  82. package/dist/features/execution/types.js +0 -17
  83. package/dist/features/external-mcp/react/components/ExternalMcpTab.d.ts +6 -2
  84. package/dist/features/external-mcp/react/components/ExternalMcpTab.js +9 -2
  85. package/dist/features/file-dropzone/react/components/FileDropzone.js +24 -25
  86. package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.d.ts +2 -2
  87. package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.js +35 -30
  88. package/dist/features/file-dropzone/react/components/FileDropzoneThumbnailGrid.js +30 -24
  89. package/dist/features/file-dropzone/react/components/FileDropzoneZone.d.ts +14 -0
  90. package/dist/features/file-dropzone/react/components/FileDropzoneZone.js +26 -0
  91. package/dist/features/html-editor/canvas-content-wrapper.js +13 -13
  92. package/dist/features/html-editor/canvas-embed-placeholders.js +46 -37
  93. package/dist/features/html-editor/css.js +76 -69
  94. package/dist/features/html-editor/editor-blocks.js +47 -33
  95. package/dist/features/html-editor/editor-serialization.js +6 -5
  96. package/dist/features/html-editor/protected-elements.js +39 -28
  97. package/dist/features/html-editor/react/hooks/useInteractiveHtmlEditor.js +4 -4
  98. package/dist/features/html-editor/source-splice.js +51 -30
  99. package/dist/features/i18n/dictionaries/settings-dialog.ja.js +1 -0
  100. package/dist/features/i18n/dictionaries/settings-dialog.zh-CN.js +1 -0
  101. package/dist/features/i18n/dictionaries/settings-dialog.zh-TW.js +1 -0
  102. package/dist/features/i18n/locale.js +20 -16
  103. package/dist/features/iframe-pool/react/dom-sync.js +8 -6
  104. package/dist/features/integrations/react/components/CodexInstallToggleButton.js +19 -4
  105. package/dist/features/integrations/react/components/IntegrationsTab.js +60 -14
  106. package/dist/features/interactive-ui/interactive-ui.css +1 -1
  107. package/dist/features/interactive-ui/providers/native/data-table.manifest.d.ts +6 -6
  108. package/dist/features/interactive-ui/providers/shadcn/data-table.js +10 -7
  109. package/dist/features/interactive-ui/providers/shadcn/data-table.manifest.d.ts +6 -6
  110. package/dist/features/lexical-rich-text-editor/mention-parser.js +60 -57
  111. package/dist/features/lexical-rich-text-editor/react/hooks/useKeyboardCommands.js +33 -38
  112. package/dist/features/lexical-rich-text-editor/rules.js +87 -74
  113. package/dist/features/lexical-rich-text-editor/serialize.js +27 -25
  114. package/dist/features/mcp-ui/secret-card.js +111 -61
  115. package/dist/features/mcp-ui/surfaces/confirmation.js +39 -21
  116. package/dist/features/mcp-ui/surfaces/document.d.ts +1 -1
  117. package/dist/features/mcp-ui/surfaces/document.js +10 -2
  118. package/dist/features/mcp-ui/surfaces/fields.js +30 -50
  119. package/dist/features/mcp-ui/surfaces/form.d.ts +6 -0
  120. package/dist/features/mcp-ui/surfaces/form.js +43 -21
  121. package/dist/features/mcp-ui/surfaces/outcome.js +30 -20
  122. package/dist/features/mcp-ui/surfaces/text-input.js +33 -21
  123. package/dist/features/mcp-ui/surfaces/tokens.d.ts +6 -1
  124. package/dist/features/mcp-ui/surfaces/tokens.js +5 -2
  125. package/dist/features/media-providers/constants.d.ts +1 -0
  126. package/dist/features/media-providers/constants.js +2 -81
  127. package/dist/features/media-providers/index.d.ts +1 -1
  128. package/dist/features/media-providers/ports.d.ts +2 -0
  129. package/dist/features/media-providers/react/components/MediaProviderCard.d.ts +22 -0
  130. package/dist/features/media-providers/react/components/MediaProviderCard.js +50 -0
  131. package/dist/features/media-providers/react/components/MediaProvidersTab.d.ts +5 -6
  132. package/dist/features/media-providers/react/components/MediaProvidersTab.js +40 -72
  133. package/dist/features/media-providers/react/components/media-providers-tab-labels.d.ts +11 -0
  134. package/dist/features/media-providers/react/components/media-providers-tab-labels.js +57 -0
  135. package/dist/features/media-providers/react/hooks/useMediaProvidersTab.d.ts +4 -2
  136. package/dist/features/media-providers/react/hooks/useMediaProvidersTab.js +37 -23
  137. package/dist/features/media-providers/rules.js +17 -10
  138. package/dist/features/media-providers/types.d.ts +4 -22
  139. package/dist/features/media-providers/types.js +3 -8
  140. package/dist/features/memory/formatters.js +127 -92
  141. package/dist/features/memory/react/components/MemoryConnectedPanel.js +92 -36
  142. package/dist/features/memory/react/components/MemoryList.js +37 -18
  143. package/dist/features/memory/react/hooks/useMemoryExtractions.store.js +42 -36
  144. package/dist/features/notifications/react/components/NotificationsTab.js +28 -28
  145. package/dist/features/observability/long-task.d.ts +0 -12
  146. package/dist/features/observability/long-task.js +15 -19
  147. package/dist/features/observability/resource-error.d.ts +0 -10
  148. package/dist/features/observability/resource-error.js +18 -13
  149. package/dist/features/observability/stuck-run.js +10 -6
  150. package/dist/features/observability/telemetry-url.d.ts +6 -0
  151. package/dist/features/observability/telemetry-url.js +16 -0
  152. package/dist/features/observability/white-screen.js +12 -24
  153. package/dist/features/panel-kit/fetch-query/adapter.react.d.ts +2 -2
  154. package/dist/features/panel-kit/fetch-query/adapter.react.js +4 -2
  155. package/dist/features/panel-kit/fetch-query/adapter.tanstack.js +46 -33
  156. package/dist/features/panel-kit/helpers/standing-draft-local-backup.js +3 -1
  157. package/dist/features/panel-kit/hooks/use-settings-slice.hooks.js +3 -1
  158. package/dist/features/privacy/react/components/PrivacyTab.js +24 -13
  159. package/dist/features/progress-card/components/ProgressCard.js +7 -3
  160. package/dist/features/progress-card/reference-adapters.js +59 -54
  161. package/dist/features/project-locations/react/components/ProjectLocationsTab.js +24 -15
  162. package/dist/features/project-locations/react/hooks/useProjectLocationsTab.d.ts +8 -0
  163. package/dist/features/project-locations/react/hooks/useProjectLocationsTab.js +31 -23
  164. package/dist/features/resource-dashboard/react/components/ResourceBoard.js +6 -47
  165. package/dist/features/resource-dashboard/react/components/ResourceBoardView.d.ts +2 -2
  166. package/dist/features/resource-dashboard/react/components/ResourceBoardView.js +3 -6
  167. package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.d.ts +15 -0
  168. package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.js +35 -0
  169. package/dist/features/resource-dashboard/react/components/ResourceCard.js +5 -16
  170. package/dist/features/resource-dashboard/react/components/ResourceCardParts.d.ts +34 -0
  171. package/dist/features/resource-dashboard/react/components/ResourceCardParts.js +34 -0
  172. package/dist/features/resource-dashboard/react/components/ResourceRowList.js +3 -1
  173. package/dist/features/resource-dashboard/react/components/ResourceRowListItem.js +2 -2
  174. package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.d.ts +19 -0
  175. package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.js +11 -0
  176. package/dist/features/resource-dashboard/react/components/ResourceRowListView.d.ts +1 -1
  177. package/dist/features/resource-dashboard/react/components/ResourceRowListView.js +4 -4
  178. package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.d.ts +10 -0
  179. package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.js +20 -0
  180. package/dist/features/resource-dashboard/react/components/resourceBoardProps.d.ts +48 -0
  181. package/dist/features/resource-dashboard/react/components/resourceBoardProps.js +70 -0
  182. package/dist/features/resource-dashboard/react/components/resourceRowListProps.d.ts +19 -0
  183. package/dist/features/resource-dashboard/react/components/resourceRowListProps.js +14 -0
  184. package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.d.ts +16 -0
  185. package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.js +13 -0
  186. package/dist/features/resource-dashboard/rules.js +2 -8
  187. package/dist/features/sketch-editor/dom.js +150 -99
  188. package/dist/features/sketch-editor/react/components/SketchEditor.js +26 -15
  189. package/dist/features/skills/react/components/SkillDraftForm.js +13 -19
  190. package/dist/features/skills/react/components/SkillDraftForm.labels.d.ts +31 -0
  191. package/dist/features/skills/react/components/SkillDraftForm.labels.js +54 -0
  192. package/dist/features/skills/react/components/SkillRow.js +38 -18
  193. package/dist/features/skills/react/components/SkillRow.labels.d.ts +7 -0
  194. package/dist/features/skills/react/components/SkillRow.labels.js +18 -0
  195. package/dist/features/skills/react/components/SkillsTab.js +24 -14
  196. package/dist/features/skills/react/components/SkillsTab.labels.d.ts +7 -0
  197. package/dist/features/skills/react/components/SkillsTab.labels.js +11 -0
  198. package/dist/features/skills/rules.js +32 -23
  199. package/dist/features/source-config-list/agent-handles.js +2 -1
  200. package/dist/features/source-config-list/dependencies.js +53 -49
  201. package/dist/features/source-config-list/index.d.ts +3 -3
  202. package/dist/features/source-config-list/react/components/SourceConfigAddForm.d.ts +6 -5
  203. package/dist/features/source-config-list/react/components/SourceConfigAddForm.js +38 -7
  204. package/dist/features/source-config-list/react/components/SourceConfigField.js +69 -25
  205. package/dist/features/source-config-list/react/components/SourceConfigItemCard.d.ts +6 -5
  206. package/dist/features/source-config-list/react/components/SourceConfigItemCard.js +76 -20
  207. package/dist/features/source-config-list/react/components/SourceConfigListView.d.ts +5 -3
  208. package/dist/features/source-config-list/react/components/SourceConfigListView.js +33 -3
  209. package/dist/features/source-config-list/react/components/SourceConfigTestControl.js +17 -1
  210. package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.d.ts +11 -0
  211. package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.js +12 -0
  212. package/dist/features/source-config-list/types.d.ts +14 -0
  213. package/dist/features/tab-launcher-menu/react/components/TabLauncherMenu.js +25 -1
  214. package/dist/features/tab-launcher-menu/react/hooks/useTabLauncherMenu.js +15 -16
  215. package/dist/features/tab-strip/react/components/TabBar.js +8 -1
  216. package/dist/features/tabbed-dialog/react/components/TabbedDialog.js +8 -30
  217. package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.d.ts +15 -0
  218. package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.js +8 -0
  219. package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.d.ts +12 -0
  220. package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.js +6 -0
  221. package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.d.ts +27 -0
  222. package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.js +12 -0
  223. package/dist/features/tabbed-dialog/react/components/tabbedDialogView.d.ts +43 -0
  224. package/dist/features/tabbed-dialog/react/components/tabbedDialogView.js +59 -0
  225. package/dist/features/version-manager/dependencies.d.ts +3 -1
  226. package/dist/features/version-manager/dependencies.js +3 -2
  227. package/dist/features/version-manager/react/components/VersionManagerModal.js +10 -6
  228. package/dist/features/version-manager/react/hooks/useVersionManager.js +7 -4
  229. package/dist/features/viewer-shell/react/components/CommentSidePanel.js +103 -50
  230. package/dist/features/viewer-shell/react/components/MarkdownSplitPane.js +28 -8
  231. package/dist/features/viewer-shell/react/hooks/useCommentReorder.js +22 -16
  232. package/dist/features/viewer-shell/rules.js +19 -23
  233. package/dist/react/components/CustomSelect.d.ts +38 -1
  234. package/dist/react/components/CustomSelect.js +94 -38
  235. package/dist/react/components/OnboardingDropdown.js +20 -8
  236. package/dist/react/components/RemixIcon.stylesheet.js +3 -1
  237. package/dist/react/components/Toast.js +13 -2
  238. package/dist/react/components/TooltipLayer.js +5 -14
  239. package/dist/react/components/WorkingDirPicker.js +78 -50
  240. package/dist/react/components/remixicon-font-recovery.js +38 -30
  241. package/dist/react/components/select-popover.d.ts +8 -6
  242. package/dist/react/components/select-popover.js +33 -29
  243. package/dist/react/mcp-ui/McpUiHost.d.ts +1 -1
  244. package/dist/react/mcp-ui/McpUiHost.js +10 -11
  245. package/dist/react/mcp-ui/host-message-source.d.ts +14 -2
  246. package/dist/react/mcp-ui/host-message-source.js +15 -5
  247. package/dist/react/mcp-ui/mcp-ui-host-frame.d.ts +41 -0
  248. package/dist/react/mcp-ui/mcp-ui-host-frame.js +39 -0
  249. package/dist/renderers/annotation-canvas/react/components/AnnotationCanvas.js +97 -69
  250. package/dist/renderers/preview-modal-shell/react/components/PreviewModalShell.js +59 -30
  251. package/dist/renderers/preview-modal-shell/react/components/icons.js +4 -1
  252. package/dist/renderers/react/components/ArtifactView.js +9 -6
  253. package/dist/renderers/srcdoc/build.d.ts +4 -1
  254. package/dist/renderers/srcdoc/build.js +62 -48
  255. package/dist/styles/admin.css +29 -14
  256. package/dist/styles/lightbox.css +142 -0
  257. package/dist/styles/variables.css +128 -0
  258. package/dist/theme/apply.js +9 -5
  259. package/dist/utils/auto-open-file.d.ts +2 -1
  260. package/dist/utils/auto-open-file.js +69 -85
  261. package/dist/utils/markdown-scroll-sync.js +108 -87
  262. package/dist/utils/polygon-selection.js +25 -26
  263. package/dist/utils/smooth-scroll-to-top.d.ts +1 -1
  264. package/dist/utils/smooth-scroll-to-top.js +2 -32
  265. package/dist/utils/sse.js +26 -26
  266. package/dist/utils/unit-bezier.d.ts +7 -0
  267. package/dist/utils/unit-bezier.js +38 -0
  268. package/package.json +7 -10
  269. package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.d.ts +0 -13
  270. package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.js +0 -46
@@ -110,43 +110,47 @@ function confirmationActions(spec) {
110
110
  actions.push({ id: 'cancel', label: spec.cancel.label, variant: 'neutral' });
111
111
  return actions;
112
112
  }
113
- export function renderConfirmationDocument(spec) {
114
- const text = { ...DEFAULT_SURFACE_STATUS_TEXT, ...spec.text };
115
- const choicesParam = spec.choicesParam ?? 'overwrite';
116
- // The ticked ids are merged into confirm.params at click time; a key already there (the token,
117
- // say) would be silently replaced by an array.
118
- if ((spec.choices ?? []).length > 0 && Object.prototype.hasOwnProperty.call(spec.confirm.params, choicesParam)) {
113
+ /** The ticked ids are merged into confirm.params at click time; a key already there (the token,
114
+ * say) would be silently replaced by an array.
115
+ * @complexity O(a) in the alternative count. @throws Error naming the colliding param (and alternative). */
116
+ function assertChoicesParamFree(spec, required) {
117
+ const { choicesParam } = required;
118
+ if ((spec.choices ?? []).length === 0)
119
+ return;
120
+ if (Object.prototype.hasOwnProperty.call(spec.confirm.params, choicesParam)) {
119
121
  throw new Error(`choicesParam ${JSON.stringify(choicesParam)} collides with a key already in confirm.params.`);
120
122
  }
121
- for (const alternative of (spec.choices ?? []).length > 0 ? (spec.alternatives ?? []) : []) {
123
+ for (const alternative of spec.alternatives ?? []) {
122
124
  if (Object.prototype.hasOwnProperty.call(alternative.params, choicesParam)) {
123
125
  throw new Error(`choicesParam ${JSON.stringify(choicesParam)} collides with a key already in the params of alternative ${JSON.stringify(alternative.id)}.`);
124
126
  }
125
127
  }
126
- const alternatives = spec.alternatives ?? [];
127
- assertAlternativeIds(alternatives);
128
- const actions = confirmationActions(spec);
129
- // Every affirmative button starts disabled and waits out the dwell; only cancel is live at once.
130
- const affirmative = new Set(['confirm', ...alternatives.map((alternative) => alternative.id)]);
128
+ }
129
+ /** Header, context, choices, warning, actions and the status region. @complexity O(n) in the rendered length. */
130
+ function confirmationBodyHtml(spec, required) {
131
+ const { affirmative } = required;
131
132
  const warning = spec.warning === undefined ? '' : `<p class="mcpui-warning">${escapeHtml(spec.warning)}</p>`;
132
- const bodyHtml = [
133
+ return [
133
134
  renderSurfaceHeader(spec.description === undefined ? { title: spec.title } : { title: spec.title, description: spec.description }),
134
135
  renderDetailList(spec.details ?? []),
135
136
  renderChoices(spec.choices ?? []),
136
137
  warning,
137
138
  // Confirm starts disabled; the script enables it once the dwell has run out.
138
- renderActions(actions.map((action) => (affirmative.has(action.id) ? { ...action, disabled: true } : action))),
139
+ renderActions(confirmationActions(spec).map((action) => (affirmative.has(action.id) ? { ...action, disabled: true } : action))),
139
140
  renderStatusRegion(),
140
141
  ]
141
142
  .filter((fragment) => fragment !== '')
142
143
  .join('\n');
143
- // A map from button id to what that button does. `null` means "dismiss locally, call nothing" —
144
- // the shape a cancel-without-a-tool takes, kept as an explicit null rather than an absent key so
145
- // the script can tell "this button dismisses" from "this button is unknown to me".
144
+ }
145
+ /** A map from button id to what that button does. `null` means "dismiss locally, call nothing" —
146
+ * the shape a cancel-without-a-tool takes, kept as an explicit null rather than an absent key so
147
+ * the script can tell "this button dismisses" from "this button is unknown to me".
148
+ * @complexity O(a) in the alternative count. */
149
+ function confirmationPlan(spec, required) {
146
150
  const plan = {
147
151
  confirm: { toolName: spec.confirm.toolName, params: spec.confirm.params },
148
152
  };
149
- for (const alternative of alternatives) {
153
+ for (const alternative of required.alternatives) {
150
154
  plan[alternative.id] = { toolName: alternative.toolName, params: alternative.params };
151
155
  }
152
156
  if (spec.cancel !== undefined) {
@@ -154,7 +158,12 @@ export function renderConfirmationDocument(spec) {
154
158
  ? { toolName: spec.cancel.toolName, params: spec.cancel.params }
155
159
  : null;
156
160
  }
157
- const script = `(function () {
161
+ return plan;
162
+ }
163
+ /** The inline runtime script: dwell, visibility, locking and tool calls. @complexity O(s) in the script length. */
164
+ function confirmationScript(required) {
165
+ const { plan, text, choicesParam, affirmative } = required;
166
+ return `(function () {
158
167
  "use strict";
159
168
  ${SURFACE_SCRIPT_PRELUDE}
160
169
  var PLAN = ${escapeJsValue(plan)};
@@ -303,10 +312,19 @@ ${SURFACE_SCRIPT_PRELUDE}
303
312
  });
304
313
  }
305
314
  }());`;
315
+ }
316
+ export function renderConfirmationDocument(spec) {
317
+ const text = { ...DEFAULT_SURFACE_STATUS_TEXT, ...spec.text };
318
+ const choicesParam = spec.choicesParam ?? 'overwrite';
319
+ assertChoicesParamFree(spec, { choicesParam });
320
+ const alternatives = spec.alternatives ?? [];
321
+ assertAlternativeIds(alternatives);
322
+ // Every affirmative button starts disabled and waits out the dwell; only cancel is live at once.
323
+ const affirmative = new Set(['confirm', ...alternatives.map((alternative) => alternative.id)]);
306
324
  return renderSurfaceDocument({
307
325
  title: spec.title,
308
- bodyHtml,
309
- script,
326
+ bodyHtml: confirmationBodyHtml(spec, { affirmative }),
327
+ script: confirmationScript({ plan: confirmationPlan(spec, { alternatives }), text, choicesParam, affirmative }),
310
328
  app: spec.app ?? DEFAULT_APP,
311
329
  ...(spec.lang === undefined ? {} : { lang: spec.lang }),
312
330
  ...(spec.tokens === undefined ? {} : { tokens: spec.tokens }),
@@ -8,7 +8,7 @@ import { type SurfaceTokenName } from './tokens.js';
8
8
  * be a color no caller could reskin. `var(--x, fallback)` appears only where a value is genuinely
9
9
  * optional.
10
10
  */
11
- export declare const SURFACE_BASE_CSS = "*, *::before, *::after { box-sizing: border-box; }\nhtml, body { margin: 0; padding: 0; }\nbody {\n /* Transparent, not var(--jini-mcpui-bg): this document has no page of its own to paint. It is an\n allow-scripts-only sandboxed iframe embedded directly in a host's chat transcript, and html's\n background is left at its (also transparent) initial value, so the host's own pane shows\n straight through to .mcpui-surface below rather than through a second, unrelated rectangle of\n fill color sitting between the two. --jini-mcpui-bg stays declared for a caller styling\n something else via extraCss -- nothing in this stylesheet reads it anymore. */\n background: transparent;\n color: var(--jini-mcpui-text);\n font-family: var(--jini-mcpui-font);\n font-size: 14px;\n line-height: 1.5;\n padding: 16px;\n -webkit-font-smoothing: antialiased;\n}\n.mcpui-surface {\n background: linear-gradient(135deg,\n color-mix(in srgb, var(--jini-mcpui-accent) 5%, var(--jini-mcpui-panel)) 0%,\n var(--jini-mcpui-panel) 60%);\n border: 1px solid color-mix(in srgb, var(--jini-mcpui-accent) 18%, var(--jini-mcpui-border));\n border-radius: var(--jini-mcpui-radius-md);\n box-shadow: var(--jini-mcpui-surface-shadow);\n padding: 18px 20px 16px;\n max-width: 640px;\n margin: 0 auto;\n}\n.mcpui-title {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n letter-spacing: -0.01em;\n color: var(--jini-mcpui-text-strong);\n}\n.mcpui-description { margin: 6px 0 0; color: var(--jini-mcpui-text-muted); }\n.mcpui-details {\n display: grid;\n grid-template-columns: max-content minmax(0, 1fr);\n gap: 4px 14px;\n margin: 14px 0 0;\n}\n.mcpui-details dt { color: var(--jini-mcpui-text-soft); font-weight: 500; }\n.mcpui-details dd { margin: 0; overflow-wrap: anywhere; color: var(--jini-mcpui-text); }\n.mcpui-details-outcome { display: flex; flex-direction: column; gap: 8px; }\n.mcpui-outcome-row { display: flex; align-items: flex-start; gap: 14px; }\n.mcpui-outcome-row dt { flex: 1; min-width: 0; overflow-wrap: anywhere; }\n.mcpui-outcome-row dt .mcpui-hint { display: block; font-weight: 400; }\n.mcpui-outcome-row dd { flex: 1; min-width: 0; }\n/* Capped so a long status cannot squeeze the label back to one character per line; the full text\n stays in the cell's title. */\n.mcpui-outcome-row dd.mcpui-detail-status {\n flex: 0 1 auto; max-width: 60%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n}\n/* The iframe has 16px body padding on each side: 352px viewport = 320px card. Stacked, the value\n has the full width, so it wraps between words instead of truncating. */\n@media (max-width: 352px) {\n .mcpui-outcome-row { flex-direction: column; gap: 2px; }\n .mcpui-outcome-row dd.mcpui-detail-status { max-width: 100%; white-space: normal; overflow-wrap: normal; }\n}\n.mcpui-code {\n margin: 0;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n max-height: 16em;\n overflow: auto;\n font-family: var(--jini-mcpui-font-mono);\n font-size: 12.5px;\n line-height: 1.45;\n padding: 6px 8px;\n border: 1px solid var(--jini-mcpui-border);\n border-radius: var(--jini-mcpui-radius-sm);\n background: var(--jini-mcpui-bg);\n}\n.mcpui-warning {\n margin: 14px 0 0;\n padding: 9px 11px;\n border-radius: var(--jini-mcpui-radius-md);\n border: 1px solid var(--jini-mcpui-danger);\n background: var(--jini-mcpui-danger-tint);\n color: var(--jini-mcpui-text-strong);\n}\n.mcpui-fields { display: flex; flex-direction: column; gap: 12px; margin: 16px 0 0; border: 0; padding: 0; }\n.mcpui-field { display: flex; flex-direction: column; gap: 4px; }\n.mcpui-field-inline { flex-direction: row; align-items: flex-start; gap: 8px; }\n.mcpui-label { font-weight: 500; color: var(--jini-mcpui-text-strong); }\n.mcpui-required { color: var(--jini-mcpui-accent); margin-left: 2px; }\n.mcpui-hint { color: var(--jini-mcpui-text-soft); font-size: 13px; }\n.mcpui-input, .mcpui-select, .mcpui-textarea {\n font: inherit;\n color: var(--jini-mcpui-text);\n background: var(--jini-mcpui-panel);\n border: 1px solid color-mix(in srgb, var(--jini-mcpui-accent) 14%, var(--jini-mcpui-border-strong));\n border-radius: var(--jini-mcpui-radius-sm);\n padding: 7px 9px;\n width: 100%;\n min-width: 0;\n}\n.mcpui-textarea { resize: vertical; min-height: 72px; font-family: var(--jini-mcpui-font); }\n.mcpui-checkbox { margin: 3px 0 0; accent-color: var(--jini-mcpui-accent); }\n.mcpui-choice-group { border: 0; padding: 0; margin: 0; min-width: 0; }\n.mcpui-choice { display: flex; flex-direction: row; align-items: flex-start; gap: 8px; margin: 6px 0 0; }\n.mcpui-choice-label { color: var(--jini-mcpui-text); font-weight: 400; }\n.mcpui-input:focus-visible, .mcpui-select:focus-visible, .mcpui-textarea:focus-visible,\n.mcpui-checkbox:focus-visible, .mcpui-button:focus-visible {\n outline: 2px solid var(--jini-mcpui-accent-ring);\n outline-offset: 1px;\n border-color: var(--jini-mcpui-accent);\n}\n.mcpui-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 18px 0 0; }\n.mcpui-button {\n font: inherit;\n font-weight: 500;\n padding: 7px 15px;\n border-radius: var(--jini-mcpui-radius-pill);\n border: 1px solid color-mix(in srgb, var(--jini-mcpui-accent) 14%, var(--jini-mcpui-border-strong));\n background: var(--jini-mcpui-panel);\n color: var(--jini-mcpui-text);\n cursor: pointer;\n}\n.mcpui-button:hover:not([disabled]) { background: var(--jini-mcpui-accent-tint); }\n.mcpui-button-primary {\n background: var(--jini-mcpui-accent);\n border-color: var(--jini-mcpui-accent);\n color: var(--jini-mcpui-accent-ink);\n}\n.mcpui-button-primary:hover:not([disabled]) { background: var(--jini-mcpui-accent-hover); border-color: var(--jini-mcpui-accent-hover); }\n.mcpui-button-danger {\n background: var(--jini-mcpui-danger);\n border-color: var(--jini-mcpui-danger);\n color: var(--jini-mcpui-panel);\n}\n.mcpui-button-danger:hover:not([disabled]) { background: var(--jini-mcpui-danger-hover); border-color: var(--jini-mcpui-danger-hover); }\n.mcpui-button[disabled] { opacity: 0.55; cursor: default; }\n.mcpui-status { margin: 12px 0 0; min-height: 1.4em; color: var(--jini-mcpui-text-muted); }\n.mcpui-status[data-state=\"failed\"], .mcpui-status[data-state=\"invalid\"], .mcpui-status[data-state=\"expired\"] { color: var(--jini-mcpui-danger); }\n.mcpui-status[data-state=\"done\"] { color: var(--jini-mcpui-text-strong); }\n/* Neither a plain success nor a failure \u2014 e.g. an outcome surface reporting \"uploaded, not yet\n reachable\" (outcome.ts's own state: partial). Distinct from both --jini-mcpui-danger (would\n wrongly read as \"this failed\") and --jini-mcpui-text-strong (would wrongly read as \"this is\n fully done\"); the accent token reads as \"worth a second look\" without asserting either. */\n.mcpui-status[data-state=\"partial\"] { color: var(--jini-mcpui-accent); }\n.mcpui-code { font-family: var(--jini-mcpui-font-mono); font-size: 13px; color: var(--jini-mcpui-text-faint); }";
11
+ export declare const SURFACE_BASE_CSS = "*, *::before, *::after { box-sizing: border-box; }\nhtml, body { margin: 0; padding: 0; }\nbody {\n /* Transparent, not var(--jini-mcpui-bg): this document has no page of its own to paint. It is an\n allow-scripts-only sandboxed iframe embedded directly in a host's chat transcript, and html's\n background is left at its (also transparent) initial value, so the host's own pane shows\n straight through to .mcpui-surface below rather than through a second, unrelated rectangle of\n fill color sitting between the two. --jini-mcpui-bg stays declared for a caller styling\n something else via extraCss -- nothing in this stylesheet reads it anymore. */\n background: transparent;\n color: var(--jini-mcpui-text);\n font-family: var(--jini-mcpui-font);\n font-size: 14px;\n line-height: 1.5;\n padding: 16px;\n -webkit-font-smoothing: antialiased;\n}\n.mcpui-surface {\n background: linear-gradient(135deg,\n color-mix(in srgb, var(--jini-mcpui-accent) 5%, var(--jini-mcpui-panel)) 0%,\n var(--jini-mcpui-panel) 60%);\n border: 1px solid color-mix(in srgb, var(--jini-mcpui-accent) 18%, var(--jini-mcpui-border));\n border-radius: var(--jini-mcpui-radius-md);\n box-shadow: var(--jini-mcpui-surface-shadow);\n padding: 18px 20px 16px;\n max-width: 640px;\n margin: 0 auto;\n}\n.mcpui-title {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n letter-spacing: -0.01em;\n color: var(--jini-mcpui-text-strong);\n}\n.mcpui-description { margin: 6px 0 0; color: var(--jini-mcpui-text-muted); }\n/* fit-content(40%), not max-content: one long label used to take the whole row and squeeze every\n value to one character per line. Labels wrap at 40% instead. */\n.mcpui-details {\n display: grid;\n grid-template-columns: fit-content(40%) minmax(0, 1fr);\n gap: 4px 14px;\n margin: 14px 0 0;\n}\n.mcpui-details dt { color: var(--jini-mcpui-text-soft); font-weight: 500; overflow-wrap: anywhere; }\n.mcpui-details dd { margin: 0; overflow-wrap: anywhere; color: var(--jini-mcpui-text); }\n.mcpui-details-outcome { display: flex; flex-direction: column; gap: 8px; }\n.mcpui-outcome-row { display: flex; align-items: flex-start; gap: 14px; }\n.mcpui-outcome-row dt { flex: 1; min-width: 0; overflow-wrap: anywhere; }\n.mcpui-outcome-row dt .mcpui-hint { display: block; font-weight: 400; }\n.mcpui-outcome-row dd { flex: 1; min-width: 0; }\n/* Capped so a long status cannot squeeze the label back to one character per line; the full text\n stays in the cell's title. */\n.mcpui-outcome-row dd.mcpui-detail-status {\n flex: 0 1 auto; max-width: 60%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n}\n/* Frames up to 480px: stack each label above its value so the value gets the full width.\n Outcome lists keep their own row layout. */\n@media (max-width: 480px) {\n .mcpui-details:not(.mcpui-details-outcome) { grid-template-columns: minmax(0, 1fr); row-gap: 0; }\n .mcpui-details:not(.mcpui-details-outcome) dd { margin-bottom: 8px; }\n}\n/* The iframe has 16px body padding on each side: 352px viewport = 320px card. Stacked, the value\n has the full width, so it wraps between words instead of truncating. */\n@media (max-width: 352px) {\n .mcpui-outcome-row { flex-direction: column; gap: 2px; }\n .mcpui-outcome-row dd.mcpui-detail-status { max-width: 100%; white-space: normal; overflow-wrap: normal; }\n}\n.mcpui-code {\n margin: 0;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n max-height: 16em;\n overflow: auto;\n font-family: var(--jini-mcpui-font-mono);\n font-size: 12.5px;\n line-height: 1.45;\n padding: 6px 8px;\n border: 1px solid var(--jini-mcpui-border);\n border-radius: var(--jini-mcpui-radius-sm);\n background: var(--jini-mcpui-bg);\n}\n.mcpui-warning {\n margin: 14px 0 0;\n padding: 9px 11px;\n border-radius: var(--jini-mcpui-radius-md);\n border: 1px solid var(--jini-mcpui-danger);\n background: var(--jini-mcpui-danger-tint);\n color: var(--jini-mcpui-text-strong);\n}\n.mcpui-fields { display: flex; flex-direction: column; gap: 12px; margin: 16px 0 0; border: 0; padding: 0; }\n.mcpui-field { display: flex; flex-direction: column; gap: 4px; }\n.mcpui-field-inline { flex-direction: row; align-items: flex-start; gap: 8px; }\n.mcpui-label { font-weight: 500; color: var(--jini-mcpui-text-strong); }\n.mcpui-required { color: var(--jini-mcpui-accent); margin-left: 2px; }\n.mcpui-hint { color: var(--jini-mcpui-text-soft); font-size: 13px; }\n.mcpui-input, .mcpui-select, .mcpui-textarea {\n font: inherit;\n color: var(--jini-mcpui-text);\n background: var(--jini-mcpui-panel);\n border: 1px solid color-mix(in srgb, var(--jini-mcpui-accent) 14%, var(--jini-mcpui-border-strong));\n border-radius: var(--jini-mcpui-radius-sm);\n padding: 7px 9px;\n width: 100%;\n min-width: 0;\n}\n.mcpui-textarea { resize: vertical; min-height: 72px; font-family: var(--jini-mcpui-font); }\n.mcpui-checkbox { margin: 3px 0 0; accent-color: var(--jini-mcpui-accent); }\n.mcpui-choice-group { border: 0; padding: 0; margin: 0; min-width: 0; }\n.mcpui-choice { display: flex; flex-direction: row; align-items: flex-start; gap: 8px; margin: 6px 0 0; }\n.mcpui-choice-label { color: var(--jini-mcpui-text); font-weight: 400; }\n.mcpui-input:focus-visible, .mcpui-select:focus-visible, .mcpui-textarea:focus-visible,\n.mcpui-checkbox:focus-visible, .mcpui-button:focus-visible {\n outline: 2px solid var(--jini-mcpui-accent-ring);\n outline-offset: 1px;\n border-color: var(--jini-mcpui-accent);\n}\n.mcpui-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 18px 0 0; }\n.mcpui-button {\n font: inherit;\n font-weight: 500;\n padding: 7px 15px;\n border-radius: var(--jini-mcpui-radius-pill);\n border: 1px solid color-mix(in srgb, var(--jini-mcpui-accent) 14%, var(--jini-mcpui-border-strong));\n background: var(--jini-mcpui-panel);\n color: var(--jini-mcpui-text);\n cursor: pointer;\n}\n.mcpui-button:hover:not([disabled]) { background: var(--jini-mcpui-accent-tint); }\n.mcpui-button-primary {\n background: var(--jini-mcpui-accent);\n border-color: var(--jini-mcpui-accent);\n color: var(--jini-mcpui-accent-ink);\n}\n.mcpui-button-primary:hover:not([disabled]) { background: var(--jini-mcpui-accent-hover); border-color: var(--jini-mcpui-accent-hover); }\n.mcpui-button-danger {\n background: var(--jini-mcpui-danger);\n border-color: var(--jini-mcpui-danger);\n color: var(--jini-mcpui-panel);\n}\n.mcpui-button-danger:hover:not([disabled]) { background: var(--jini-mcpui-danger-hover); border-color: var(--jini-mcpui-danger-hover); }\n.mcpui-button[disabled] { opacity: 0.55; cursor: default; }\n.mcpui-status { margin: 12px 0 0; min-height: 1.4em; color: var(--jini-mcpui-text-muted); }\n.mcpui-status[data-state=\"failed\"], .mcpui-status[data-state=\"invalid\"], .mcpui-status[data-state=\"expired\"] { color: var(--jini-mcpui-danger); }\n.mcpui-status[data-state=\"done\"] { color: var(--jini-mcpui-text-strong); }\n/* Neither a plain success nor a failure \u2014 e.g. an outcome surface reporting \"uploaded, not yet\n reachable\" (outcome.ts's own state: partial). Distinct from both --jini-mcpui-danger (would\n wrongly read as \"this failed\") and --jini-mcpui-text-strong (would wrongly read as \"this is\n fully done\"); the accent token reads as \"worth a second look\" without asserting either. */\n.mcpui-status[data-state=\"partial\"] { color: var(--jini-mcpui-accent); }\n.mcpui-code { font-family: var(--jini-mcpui-font-mono); font-size: 13px; color: var(--jini-mcpui-text-faint); }";
12
12
  /**
13
13
  * The Content-Security-Policy every generated surface declares in its own `<head>`.
14
14
  *
@@ -59,13 +59,15 @@ body {
59
59
  color: var(--jini-mcpui-text-strong);
60
60
  }
61
61
  .mcpui-description { margin: 6px 0 0; color: var(--jini-mcpui-text-muted); }
62
+ /* fit-content(40%), not max-content: one long label used to take the whole row and squeeze every
63
+ value to one character per line. Labels wrap at 40% instead. */
62
64
  .mcpui-details {
63
65
  display: grid;
64
- grid-template-columns: max-content minmax(0, 1fr);
66
+ grid-template-columns: fit-content(40%) minmax(0, 1fr);
65
67
  gap: 4px 14px;
66
68
  margin: 14px 0 0;
67
69
  }
68
- .mcpui-details dt { color: var(--jini-mcpui-text-soft); font-weight: 500; }
70
+ .mcpui-details dt { color: var(--jini-mcpui-text-soft); font-weight: 500; overflow-wrap: anywhere; }
69
71
  .mcpui-details dd { margin: 0; overflow-wrap: anywhere; color: var(--jini-mcpui-text); }
70
72
  .mcpui-details-outcome { display: flex; flex-direction: column; gap: 8px; }
71
73
  .mcpui-outcome-row { display: flex; align-items: flex-start; gap: 14px; }
@@ -77,6 +79,12 @@ body {
77
79
  .mcpui-outcome-row dd.mcpui-detail-status {
78
80
  flex: 0 1 auto; max-width: 60%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
79
81
  }
82
+ /* Frames up to 480px: stack each label above its value so the value gets the full width.
83
+ Outcome lists keep their own row layout. */
84
+ @media (max-width: 480px) {
85
+ .mcpui-details:not(.mcpui-details-outcome) { grid-template-columns: minmax(0, 1fr); row-gap: 0; }
86
+ .mcpui-details:not(.mcpui-details-outcome) dd { margin-bottom: 8px; }
87
+ }
80
88
  /* The iframe has 16px body padding on each side: 352px viewport = 320px card. Stacked, the value
81
89
  has the full width, so it wraps between words instead of truncating. */
82
90
  @media (max-width: 352px) {
@@ -18,15 +18,32 @@ import { renderCheckbox } from './checkbox.js';
18
18
  import { renderChoiceGroup } from './choice-group.js';
19
19
  import { renderSelect } from './select.js';
20
20
  import { renderTextInput } from './text-input.js';
21
- /** Only the properties present on a given field are forwarded, because `exactOptionalPropertyTypes` makes `{ hint: undefined }` and `{}` different types. */
21
+ /** The listed properties that are present on `source`, in listed order.
22
+ * Only the properties present on a given field are forwarded, because `exactOptionalPropertyTypes` makes `{ hint: undefined }` and `{}` different types.
23
+ * @complexity O(k) in the key count. */
24
+ function present(source, keys) {
25
+ const picked = {};
26
+ for (const key of keys) {
27
+ const value = source[key];
28
+ if (value !== undefined)
29
+ picked[key] = value;
30
+ }
31
+ return picked;
32
+ }
22
33
  function base(field) {
23
- return {
24
- name: field.name,
25
- label: field.label,
26
- ...(field.hint === undefined ? {} : { hint: field.hint }),
27
- ...(field.required === undefined ? {} : { required: field.required }),
28
- ...(field.disabled === undefined ? {} : { disabled: field.disabled }),
29
- };
34
+ return { name: field.name, label: field.label, ...present(field, ['hint', 'required', 'disabled']) };
35
+ }
36
+ /** One-of-N, drawn as radios or a select per {@link EnumField.presentation}. @complexity O(n) in the rendered length. */
37
+ function renderEnumField(field) {
38
+ if (field.presentation === 'radio') {
39
+ return renderChoiceGroup({
40
+ ...base(field),
41
+ selection: 'single',
42
+ options: field.options,
43
+ ...(field.value === undefined ? {} : { value: [field.value] }),
44
+ });
45
+ }
46
+ return renderSelect({ ...base(field), options: field.options, ...present(field, ['value', 'placeholder']) });
30
47
  }
31
48
  /**
32
49
  * Renders the control fragment for one field, dispatching on its kind.
@@ -39,52 +56,15 @@ function base(field) {
39
56
  export function renderFieldControl(field, text) {
40
57
  switch (field.kind) {
41
58
  case 'string':
42
- return renderTextInput({
43
- ...base(field),
44
- ...text,
45
- ...(field.value === undefined ? {} : { value: field.value }),
46
- ...(field.placeholder === undefined ? {} : { placeholder: field.placeholder }),
47
- ...(field.multiline === undefined ? {} : { multiline: field.multiline }),
48
- ...(field.rows === undefined ? {} : { rows: field.rows }),
49
- ...(field.secret === undefined ? {} : { secret: field.secret }),
50
- });
59
+ return renderTextInput({ ...base(field), ...text, ...present(field, ['value', 'placeholder', 'multiline', 'rows', 'secret']) });
51
60
  case 'number':
52
- return renderTextInput({
53
- ...base(field),
54
- inputType: 'number',
55
- ...(field.value === undefined ? {} : { value: field.value }),
56
- ...(field.placeholder === undefined ? {} : { placeholder: field.placeholder }),
57
- ...(field.min === undefined ? {} : { min: field.min }),
58
- ...(field.max === undefined ? {} : { max: field.max }),
59
- ...(field.step === undefined ? {} : { step: field.step }),
60
- });
61
+ return renderTextInput({ ...base(field), inputType: 'number', ...present(field, ['value', 'placeholder', 'min', 'max', 'step']) });
61
62
  case 'boolean':
62
- return renderCheckbox({
63
- ...base(field),
64
- ...(field.value === undefined ? {} : { value: field.value }),
65
- });
63
+ return renderCheckbox({ ...base(field), ...present(field, ['value']) });
66
64
  case 'enum':
67
- if (field.presentation === 'radio') {
68
- return renderChoiceGroup({
69
- ...base(field),
70
- selection: 'single',
71
- options: field.options,
72
- ...(field.value === undefined ? {} : { value: [field.value] }),
73
- });
74
- }
75
- return renderSelect({
76
- ...base(field),
77
- options: field.options,
78
- ...(field.value === undefined ? {} : { value: field.value }),
79
- ...(field.placeholder === undefined ? {} : { placeholder: field.placeholder }),
80
- });
65
+ return renderEnumField(field);
81
66
  case 'multi-enum':
82
- return renderChoiceGroup({
83
- ...base(field),
84
- selection: 'multiple',
85
- options: field.options,
86
- ...(field.value === undefined ? {} : { value: field.value }),
87
- });
67
+ return renderChoiceGroup({ ...base(field), selection: 'multiple', options: field.options, ...present(field, ['value']) });
88
68
  }
89
69
  }
90
70
  /** Projects fields down to what the inline script needs, so the generated JS carries no labels, hints, or option lists it will never read. */
@@ -42,6 +42,12 @@ export interface FormSurfaceSpec {
42
42
  /** Styles the submit button as destructive. */
43
43
  readonly danger?: boolean;
44
44
  readonly submitLabel: string;
45
+ /** Alternative submit buttons collect the same fields, with explicit action params. */
46
+ readonly additionalSubmitActions?: readonly {
47
+ readonly id: string;
48
+ readonly label: string;
49
+ readonly params: Readonly<Record<string, unknown>>;
50
+ }[];
45
51
  /** The tool the collected values are sent to. */
46
52
  readonly toolName: string;
47
53
  /**
@@ -31,51 +31,61 @@ import { DEFAULT_SECRET_INPUT_TEXT } from './text-input.js';
31
31
  const DEFAULT_APP = { appName: 'jini-mcp-ui-form', appVersion: '1' };
32
32
  /** The form element's id, fixed so the emitted script can find it without a fragile selector. */
33
33
  const FORM_ELEMENT_ID = 'mcpui-form';
34
- /**
35
- * Renders the form dialog as a complete, self-contained HTML document.
36
- *
37
- * @param spec - See {@link FormSurfaceSpec}.
38
- * @returns The full HTML string.
39
- * @complexity O(n) in the number of fields plus the rendered length.
40
- */
41
- export function renderFormDocument(spec) {
42
- // A base param with a secret field's name would leak the same value through the emitted script,
43
- // even though the collected value overrides it at submit time. Reject that hidden prefill too.
34
+ /** A base param with a secret field's name would leak the same value through the emitted script,
35
+ * even though the collected value overrides it at submit time. Reject that hidden prefill too.
36
+ * @complexity O(n) in the field count. @throws Error with fixed text, never the field name or value. */
37
+ function assertNoSecretBaseParam(spec) {
44
38
  for (const field of spec.fields) {
45
39
  if (field.kind === 'string' && field.secret === true && spec.baseParams?.[field.name] !== undefined) {
46
40
  throw new Error('Secret fields cannot have a pre-filled value. Omit value.');
47
41
  }
48
42
  }
49
- const text = { ...DEFAULT_SURFACE_STATUS_TEXT, ...DEFAULT_SECRET_INPUT_TEXT, ...spec.text };
43
+ }
44
+ /** Submit, any alternative submits, then cancel. @complexity O(a) in the action count. */
45
+ function formActions(spec) {
50
46
  // Not `type: 'submit'` — see the runtime script below for why a submit-typed button inside this
51
47
  // particular `<form>` cannot work at all in production.
52
48
  const actions = [
53
49
  { id: 'submit', label: spec.submitLabel, variant: spec.danger === true ? 'danger' : 'primary' },
54
50
  ];
51
+ for (const action of spec.additionalSubmitActions ?? [])
52
+ actions.push({ id: `submit:${action.id}`, label: action.label, variant: 'neutral' });
55
53
  if (spec.cancel !== undefined)
56
54
  actions.push({ id: 'cancel', label: spec.cancel.label, variant: 'neutral' });
55
+ return actions;
56
+ }
57
+ /** Header, context, the `<form>` itself and the status region. @complexity O(n) in the rendered length. */
58
+ function formBodyHtml(spec, text) {
57
59
  const controls = spec.fields
58
60
  .map((field) => renderFieldControl(field, { showSecret: text.showSecret, hideSecret: text.hideSecret }))
59
61
  .join('\n');
60
62
  const warning = spec.warning === undefined ? '' : `<p class="mcpui-warning">${escapeHtml(spec.warning)}</p>`;
61
- const bodyHtml = [
63
+ return [
62
64
  renderSurfaceHeader(spec.description === undefined ? { title: spec.title } : { title: spec.title, description: spec.description }),
63
65
  renderDetailList(spec.details ?? []),
64
- `<form id="${FORM_ELEMENT_ID}" novalidate>\n<fieldset class="mcpui-fields">\n${controls}\n</fieldset>\n${warning}\n${renderActions(actions)}\n</form>`,
66
+ `<form id="${FORM_ELEMENT_ID}" novalidate>\n<fieldset class="mcpui-fields">\n${controls}\n</fieldset>\n${warning}\n${renderActions(formActions(spec))}\n</form>`,
65
67
  renderStatusRegion(),
66
68
  ]
67
69
  .filter((fragment) => fragment !== '')
68
70
  .join('\n');
69
- const cancelPlan = spec.cancel?.toolName === undefined
71
+ }
72
+ /** The cancel tool call, or null for a cancel that only tears the surface down. @complexity O(1). */
73
+ function cancelPlanOf(spec) {
74
+ return spec.cancel?.toolName === undefined
70
75
  ? null
71
76
  : { toolName: spec.cancel.toolName, params: spec.cancel.params ?? {} };
72
- const script = `(function () {
77
+ }
78
+ /** The inline runtime script that reads, validates and submits the fields. @complexity O(n) in the field count plus the script length. */
79
+ function formScript(spec, text) {
80
+ const cancelPlan = cancelPlanOf(spec);
81
+ return `(function () {
73
82
  "use strict";
74
83
  ${SURFACE_SCRIPT_PRELUDE}
75
84
  var FIELDS = ${escapeJsValue(toFieldReadSpecs(spec.fields))};
76
85
  var TOOL = ${escapeJsValue(spec.toolName)};
77
86
  var BASE_PARAMS = ${escapeJsValue(spec.baseParams ?? {})};
78
87
  var CANCEL = ${escapeJsValue(cancelPlan)};
88
+ var SUBMIT_ACTIONS = ${escapeJsValue(Object.fromEntries((spec.additionalSubmitActions ?? []).map(action => [`submit:${action.id}`, action.params])))};
79
89
  var TEXT = ${escapeJsValue(text)};
80
90
  var form = document.getElementById(${escapeJsValue(FORM_ELEMENT_ID)});
81
91
 
@@ -186,7 +196,7 @@ ${SURFACE_SCRIPT_PRELUDE}
186
196
  // says the dialog is no longer pending, where a retry cannot succeed (see reportCallFailure).
187
197
  var pending = false;
188
198
 
189
- function runSubmit() {
199
+ function runSubmit(actionParams) {
190
200
  if (pending) return;
191
201
  var params = {};
192
202
  var key;
@@ -201,6 +211,7 @@ ${SURFACE_SCRIPT_PRELUDE}
201
211
  setStatus(TEXT.missingPrefix + missing.join(", "), "invalid");
202
212
  return;
203
213
  }
214
+ for (key in actionParams) if (Object.prototype.hasOwnProperty.call(actionParams, key)) params[key] = actionParams[key];
204
215
  pending = true;
205
216
  setBusy(true);
206
217
  setStatus(TEXT.working, "pending");
@@ -225,14 +236,14 @@ ${SURFACE_SCRIPT_PRELUDE}
225
236
  var type = (target.getAttribute("type") || "text").toLowerCase();
226
237
  if (type === "checkbox" || type === "radio") return;
227
238
  event.preventDefault();
228
- runSubmit();
239
+ runSubmit({});
229
240
  });
230
241
 
231
242
  for (var b = 0; b < actionButtons.length; b++) {
232
243
  var action = actionButtons[b].getAttribute("data-mcpui-action");
233
- if (action === "submit") {
244
+ if (action === "submit" || Object.prototype.hasOwnProperty.call(SUBMIT_ACTIONS, action)) {
234
245
  actionButtons[b].addEventListener("click", function (event) {
235
- if (event.isTrusted === true) runSubmit();
246
+ if (event.isTrusted === true) runSubmit(SUBMIT_ACTIONS[event.currentTarget.getAttribute("data-mcpui-action")] || {});
236
247
  });
237
248
  continue;
238
249
  }
@@ -256,10 +267,21 @@ ${SURFACE_SCRIPT_PRELUDE}
256
267
  });
257
268
  }
258
269
  }());`;
270
+ }
271
+ /**
272
+ * Renders the form dialog as a complete, self-contained HTML document.
273
+ *
274
+ * @param spec - See {@link FormSurfaceSpec}.
275
+ * @returns The full HTML string.
276
+ * @complexity O(n) in the number of fields plus the rendered length.
277
+ */
278
+ export function renderFormDocument(spec) {
279
+ assertNoSecretBaseParam(spec);
280
+ const text = { ...DEFAULT_SURFACE_STATUS_TEXT, ...DEFAULT_SECRET_INPUT_TEXT, ...spec.text };
259
281
  return renderSurfaceDocument({
260
282
  title: spec.title,
261
- bodyHtml,
262
- script,
283
+ bodyHtml: formBodyHtml(spec, text),
284
+ script: formScript(spec, text),
263
285
  app: spec.app ?? DEFAULT_APP,
264
286
  ...(spec.lang === undefined ? {} : { lang: spec.lang }),
265
287
  ...(spec.tokens === undefined ? {} : { tokens: spec.tokens }),
@@ -48,6 +48,33 @@ import { renderActions, renderDetailList, renderStatusRegion, renderSurfaceDocum
48
48
  const DEFAULT_APP = { appName: 'jini-mcp-ui-outcome', appVersion: '1' };
49
49
  const DEFAULT_OPEN_LINK_LABEL = 'Open site';
50
50
  const OPEN_LINK_ACTION_ID = 'open-link';
51
+ /** The status region's `data-state` for an outcome state. @complexity O(1). */
52
+ function statusStateOf(state) {
53
+ if (state === 'success')
54
+ return 'done';
55
+ return state === 'partial' ? 'partial' : 'failed';
56
+ }
57
+ /** The open-link button, or nothing without a link. @complexity O(n) in the label length. */
58
+ function renderOpenLinkActions(spec) {
59
+ if (spec.openLinkUrl === undefined)
60
+ return '';
61
+ return renderActions([{ id: OPEN_LINK_ACTION_ID, label: spec.openLinkLabel ?? DEFAULT_OPEN_LINK_LABEL, variant: spec.state === 'success' ? 'primary' : 'neutral' }]);
62
+ }
63
+ /** The inline script: wires the open-link button, or nothing at all. @complexity O(n) in the URL length. */
64
+ function outcomeScript(openLinkUrl) {
65
+ // No prelude at all when there is nothing to wire up — a pure-display result needs no bridge
66
+ // calls and no `[data-mcpui-action]` scan.
67
+ if (openLinkUrl === undefined)
68
+ return '(function () { "use strict"; }());';
69
+ return `(function () {
70
+ "use strict";
71
+ ${SURFACE_SCRIPT_PRELUDE}
72
+ var OPEN_URL = ${escapeJsValue(openLinkUrl)};
73
+ for (var i = 0; i < actionButtons.length; i++) {
74
+ actionButtons[i].addEventListener("click", function () { api.openLink(OPEN_URL); });
75
+ }
76
+ }());`;
77
+ }
51
78
  /**
52
79
  * Renders the outcome document as a complete, self-contained HTML document.
53
80
  *
@@ -56,40 +83,23 @@ const OPEN_LINK_ACTION_ID = 'open-link';
56
83
  * @complexity O(n) in the rendered length.
57
84
  */
58
85
  export function renderOutcomeDocument(spec) {
59
- const statusState = spec.state === 'success' ? 'done' : spec.state === 'partial' ? 'partial' : 'failed';
60
86
  // Rendered directly into the status region rather than left for the script to set — this document
61
87
  // has nothing further to wait on (unlike a confirmation, which starts idle and only reaches "done"
62
88
  // after a click), so the true outcome should be visible from the FIRST paint, not a moment after
63
89
  // the script runs.
64
- const statusHtml = `<p class="mcpui-status" id="${SURFACE_STATUS_ELEMENT_ID}" role="status" aria-live="polite" data-state="${statusState}">${escapeHtml(spec.message)}</p>`;
65
- const hasOpenLink = spec.openLinkUrl !== undefined;
66
- const actionsHtml = hasOpenLink
67
- ? renderActions([{ id: OPEN_LINK_ACTION_ID, label: spec.openLinkLabel ?? DEFAULT_OPEN_LINK_LABEL, variant: spec.state === 'success' ? 'primary' : 'neutral' }])
68
- : '';
90
+ const statusHtml = `<p class="mcpui-status" id="${SURFACE_STATUS_ELEMENT_ID}" role="status" aria-live="polite" data-state="${statusStateOf(spec.state)}">${escapeHtml(spec.message)}</p>`;
69
91
  const bodyHtml = [
70
92
  renderSurfaceHeader(spec.description === undefined ? { title: spec.title } : { title: spec.title, description: spec.description }),
71
93
  renderDetailList(spec.details ?? [], { layout: 'outcome' }),
72
94
  statusHtml,
73
- actionsHtml,
95
+ renderOpenLinkActions(spec),
74
96
  ]
75
97
  .filter((fragment) => fragment !== '')
76
98
  .join('\n');
77
- const script = hasOpenLink
78
- ? `(function () {
79
- "use strict";
80
- ${SURFACE_SCRIPT_PRELUDE}
81
- var OPEN_URL = ${escapeJsValue(spec.openLinkUrl)};
82
- for (var i = 0; i < actionButtons.length; i++) {
83
- actionButtons[i].addEventListener("click", function () { api.openLink(OPEN_URL); });
84
- }
85
- }());`
86
- // No prelude at all when there is nothing to wire up — a pure-display result needs no bridge
87
- // calls and no `[data-mcpui-action]` scan.
88
- : '(function () { "use strict"; }());';
89
99
  return renderSurfaceDocument({
90
100
  title: spec.title,
91
101
  bodyHtml,
92
- script,
102
+ script: outcomeScript(spec.openLinkUrl),
93
103
  app: spec.app ?? DEFAULT_APP,
94
104
  ...(spec.lang === undefined ? {} : { lang: spec.lang }),
95
105
  ...(spec.tokens === undefined ? {} : { tokens: spec.tokens }),
@@ -29,6 +29,36 @@ function resolveInputType(props) {
29
29
  return 'number';
30
30
  return props.secret === true ? 'password' : 'text';
31
31
  }
32
+ /** The reveal toggle a multiline secret carries. @complexity O(n) in the label lengths. */
33
+ function renderSecretToggle(props, id) {
34
+ const showSecret = props.showSecret ?? DEFAULT_SECRET_INPUT_TEXT.showSecret;
35
+ const hideSecret = props.hideSecret ?? DEFAULT_SECRET_INPUT_TEXT.hideSecret;
36
+ return `<button type="button" class="mcpui-button" data-mcpui-secret-toggle="${escapeHtml(id)}" data-mcpui-show-secret="${escapeHtml(showSecret)}" data-mcpui-hide-secret="${escapeHtml(hideSecret)}" aria-controls="${escapeHtml(id)}" aria-pressed="false"${booleanAttribute('disabled', props.disabled)}>${escapeHtml(showSecret)}</button>`;
37
+ }
38
+ /** The `<textarea>` control, masked with a reveal toggle when secret. @complexity O(n) in the rendered length. */
39
+ function renderTextarea(props, required) {
40
+ const secret = props.secret === true;
41
+ return `<textarea class="mcpui-textarea"${required.common}${optionalAttribute('rows', props.rows)}` +
42
+ // Password inputs strip line breaks. CSS masking keeps the textarea's actual value intact,
43
+ // and disables browser text assistance that could retain or alter a pasted credential.
44
+ (secret ? ' data-mcpui-secret style="-webkit-text-security: disc;" autocomplete="off" spellcheck="false"' : '') +
45
+ `>${props.value === undefined ? '' : escapeHtml(String(props.value))}</textarea>` +
46
+ (secret ? renderSecretToggle(props, required.id) : '');
47
+ }
48
+ /** The single-line `<input>` control. @complexity O(n) in the rendered length. */
49
+ function renderInput(props, required) {
50
+ const { type, common } = required;
51
+ return `<input class="mcpui-input" type="${type}"${common}` +
52
+ optionalAttribute('value', props.value) +
53
+ // A stored secret (e.g. a cloud provider's access key or a database password) must never be
54
+ // autofilled with a saved website login. `new-password`, not `off`: Chrome ignores `off` on
55
+ // password inputs by design and fills the saved login anyway.
56
+ (type === 'password' ? optionalAttribute('autocomplete', 'new-password') : '') +
57
+ (type === 'number'
58
+ ? optionalAttribute('min', props.min) + optionalAttribute('max', props.max) + optionalAttribute('step', props.step)
59
+ : '') +
60
+ '>';
61
+ }
32
62
  /**
33
63
  * Renders one free-text field: its label, hint, and control, wrapped in `.mcpui-field`.
34
64
  *
@@ -49,27 +79,9 @@ export function renderTextInput(props) {
49
79
  booleanAttribute('disabled', props.disabled) +
50
80
  fieldDescribedBy(props);
51
81
  const type = resolveInputType(props);
52
- const isNumber = type === 'number';
53
- const secretMultiline = props.secret === true && props.multiline === true && !isNumber;
54
- const control = props.multiline === true && !isNumber
55
- ? `<textarea class="mcpui-textarea"${common}${optionalAttribute('rows', props.rows)}` +
56
- // Password inputs strip line breaks. CSS masking keeps the textarea's actual value intact,
57
- // and disables browser text assistance that could retain or alter a pasted credential.
58
- (secretMultiline ? ' data-mcpui-secret style="-webkit-text-security: disc;" autocomplete="off" spellcheck="false"' : '') +
59
- `>${props.value === undefined ? '' : escapeHtml(String(props.value))}</textarea>` +
60
- (secretMultiline
61
- ? `<button type="button" class="mcpui-button" data-mcpui-secret-toggle="${escapeHtml(id)}" data-mcpui-show-secret="${escapeHtml(props.showSecret ?? DEFAULT_SECRET_INPUT_TEXT.showSecret)}" data-mcpui-hide-secret="${escapeHtml(props.hideSecret ?? DEFAULT_SECRET_INPUT_TEXT.hideSecret)}" aria-controls="${escapeHtml(id)}" aria-pressed="false"${booleanAttribute('disabled', props.disabled)}>${escapeHtml(props.showSecret ?? DEFAULT_SECRET_INPUT_TEXT.showSecret)}</button>`
62
- : '')
63
- : `<input class="mcpui-input" type="${type}"${common}` +
64
- optionalAttribute('value', props.value) +
65
- // A stored secret (e.g. a cloud provider's access key or a database password) must never be
66
- // autofilled with a saved website login. `new-password`, not `off`: Chrome ignores `off` on
67
- // password inputs by design and fills the saved login anyway.
68
- (type === 'password' ? optionalAttribute('autocomplete', 'new-password') : '') +
69
- (isNumber
70
- ? optionalAttribute('min', props.min) + optionalAttribute('max', props.max) + optionalAttribute('step', props.step)
71
- : '') +
72
- '>';
82
+ const control = props.multiline === true && type !== 'number'
83
+ ? renderTextarea(props, { id, common })
84
+ : renderInput(props, { type, common });
73
85
  return `<div class="mcpui-field">\n${renderFieldLabel(props)}\n${control}\n</div>`;
74
86
  }
75
87
  //# sourceMappingURL=text-input.js.map
@@ -100,7 +100,12 @@ export declare const FORCE_LIGHT_SURFACE_THEME = true;
100
100
  * @returns A `:root { … }` block, then a `:root:root { … }` block holding the overrides (when any),
101
101
  * followed by a `@media (prefers-color-scheme: dark) { :root { … } }` block unless {@link FORCE_LIGHT_SURFACE_THEME} suppresses it, ready to paste into the first
102
102
  * `<style>` tag.
103
+ * @param options.forceLight - Defaults to {@link FORCE_LIGHT_SURFACE_THEME}. Exposed so the dark
104
+ * variant this function still knows how to emit is reachable (and verified) rather than dead code
105
+ * behind a module constant; every production caller keeps the default.
103
106
  * @complexity O(n) in the number of tokens.
104
107
  */
105
- export declare function renderTokenBlock(overrides?: Partial<Record<SurfaceTokenName, string>>): string;
108
+ export declare function renderTokenBlock(overrides?: Partial<Record<SurfaceTokenName, string>>, { forceLight }?: {
109
+ forceLight?: boolean;
110
+ }): string;
106
111
  //# sourceMappingURL=tokens.d.ts.map
@@ -148,9 +148,12 @@ export const FORCE_LIGHT_SURFACE_THEME = true;
148
148
  * @returns A `:root { … }` block, then a `:root:root { … }` block holding the overrides (when any),
149
149
  * followed by a `@media (prefers-color-scheme: dark) { :root { … } }` block unless {@link FORCE_LIGHT_SURFACE_THEME} suppresses it, ready to paste into the first
150
150
  * `<style>` tag.
151
+ * @param options.forceLight - Defaults to {@link FORCE_LIGHT_SURFACE_THEME}. Exposed so the dark
152
+ * variant this function still knows how to emit is reachable (and verified) rather than dead code
153
+ * behind a module constant; every production caller keeps the default.
151
154
  * @complexity O(n) in the number of tokens.
152
155
  */
153
- export function renderTokenBlock(overrides = {}) {
156
+ export function renderTokenBlock(overrides = {}, { forceLight = FORCE_LIGHT_SURFACE_THEME } = {}) {
154
157
  const declare = (entries) => entries.map(([name, value]) => ` ${name}: ${value};`).join('\n');
155
158
  const notOverridden = ([name]) => !(name in overrides);
156
159
  const light = declare([
@@ -162,7 +165,7 @@ export function renderTokenBlock(overrides = {}) {
162
165
  const overrideEntries = Object.entries(overrides);
163
166
  const overrideBlock = overrideEntries.length === 0 ? '' : `\n:root:root {\n${declare(overrideEntries)}\n}`;
164
167
  const lightBlock = `:root {\n${light}\n}${overrideBlock}`;
165
- if (FORCE_LIGHT_SURFACE_THEME)
168
+ if (forceLight)
166
169
  return lightBlock;
167
170
  const dark = declare([
168
171
  ...Object.entries(SURFACE_TOKENS_DARK).filter(notOverridden),
@@ -1,3 +1,4 @@
1
1
  import type { MediaProviderOption } from './types.js';
2
+ /** @deprecated Supply a host catalog. The former starter set is exported by integrations/media-providers/catalog. */
2
3
  export declare const DEFAULT_MEDIA_PROVIDER_CATALOG: readonly MediaProviderOption[];
3
4
  //# sourceMappingURL=constants.d.ts.map