@jini-ai/ui 0.5.5 → 0.6.10
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.
- package/CHANGELOG.md +91 -2
- package/dist/core/theme/validation.js +40 -38
- package/dist/features/a2ui/renderer.js +15 -13
- package/dist/features/about/react/components/AboutTab.js +64 -34
- package/dist/features/about/rules.d.ts +2 -2
- package/dist/features/about/rules.js +156 -129
- package/dist/features/admin-widgets/components/ImagePreviewModal.d.ts +4 -3
- package/dist/features/admin-widgets/components/ImagePreviewModal.js +6 -10
- package/dist/features/admin-widgets/components/InfoTip.d.ts +3 -2
- package/dist/features/admin-widgets/components/InfoTip.hooks.js +2 -0
- package/dist/features/admin-widgets/components/InfoTip.js +34 -7
- package/dist/features/admin-widgets/components/InfoTip.rules.d.ts +13 -0
- package/dist/features/admin-widgets/components/InfoTip.rules.js +43 -10
- package/dist/features/admin-widgets/components/Lightbox/Lightbox.d.ts +30 -0
- package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.d.ts +40 -0
- package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.js +128 -0
- package/dist/features/admin-widgets/components/Lightbox/Lightbox.js +18 -0
- package/dist/features/admin-widgets/components/MiddleTruncate.d.ts +14 -0
- package/dist/features/admin-widgets/components/MiddleTruncate.js +12 -0
- package/dist/features/admin-widgets/components/MiddleTruncate.rules.d.ts +20 -0
- package/dist/features/admin-widgets/components/MiddleTruncate.rules.js +21 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.d.ts +18 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.d.ts +16 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.js +45 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.js +18 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.rules.d.ts +32 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.rules.js +26 -0
- package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.hooks.js +29 -14
- package/dist/features/admin-widgets/index.d.ts +14 -2
- package/dist/features/admin-widgets/index.js +7 -1
- package/dist/features/admin-widgets/styles/admin-widgets.css +1 -0
- package/dist/features/asset-grid/react/components/AssetCard.js +7 -1
- package/dist/features/asset-grid/react/components/AssetGrid.d.ts +1 -1
- package/dist/features/asset-grid/react/components/AssetGrid.js +38 -18
- package/dist/features/asset-grid/react/components/useAssetGridFilters.d.ts +21 -0
- package/dist/features/asset-grid/react/components/useAssetGridFilters.js +19 -0
- package/dist/features/asset-grid/react/hooks/useAssetGridKeyboardShortcuts.js +16 -11
- package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.d.ts +2 -0
- package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.js +35 -11
- package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.d.ts +6 -6
- package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.js +10 -129
- package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.d.ts +50 -0
- package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.js +95 -0
- package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.d.ts +56 -0
- package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.js +80 -0
- package/dist/features/asset-tree-browser/rules.js +40 -22
- package/dist/features/browser-chrome/rules.js +74 -70
- package/dist/features/charts/browser.js +11 -5
- package/dist/features/charts/react.js +2 -2
- package/dist/features/charts/table.js +2 -2
- package/dist/features/command-palette/react/hooks/useCommandPalette.js +3 -9
- package/dist/features/connectors/ConnectorsBrowser.d.ts +4 -4
- package/dist/features/connectors/ConnectorsBrowser.js +26 -14
- package/dist/features/connectors/components/ConnectorCard.js +67 -38
- package/dist/features/connectors/components/ConnectorDetailDrawer.js +62 -31
- package/dist/features/connectors/components/ConnectorGrid.js +11 -6
- package/dist/features/connectors/hooks/useConnectorAuthorization.js +6 -0
- package/dist/features/connectors/hooks/useConnectorCatalog.d.ts +2 -0
- package/dist/features/connectors/hooks/useConnectorCatalog.js +32 -13
- package/dist/features/connectors/hooks/useWiredConnectorsBrowser.d.ts +3 -3
- package/dist/features/connectors/rules.d.ts +8 -1
- package/dist/features/connectors/rules.js +42 -20
- package/dist/features/connectors/types.d.ts +11 -0
- package/dist/features/execution/constants.d.ts +4 -10
- package/dist/features/execution/constants.js +9 -94
- package/dist/features/execution/react/components/ByokProviderForm.d.ts +2 -4
- package/dist/features/execution/react/components/ByokProviderForm.js +140 -43
- package/dist/features/execution/react/components/ExecutionTab.d.ts +1 -2
- package/dist/features/execution/react/components/ExecutionTab.js +52 -30
- package/dist/features/execution/react/components/LocalCliAgentCard.js +12 -3
- package/dist/features/execution/react/components/LocalCliAgentList.js +14 -3
- package/dist/features/execution/react/components/LocalCliAgentSettings.js +14 -3
- package/dist/features/execution/react/hooks/useLocalCliAgentCard.js +54 -36
- package/dist/features/execution/react/hooks/useReasoningControl.js +114 -78
- package/dist/features/execution/rules.d.ts +2 -2
- package/dist/features/execution/rules.js +31 -15
- package/dist/features/execution/types.d.ts +4 -42
- package/dist/features/execution/types.js +0 -17
- package/dist/features/external-mcp/react/components/ExternalMcpTab.d.ts +6 -2
- package/dist/features/external-mcp/react/components/ExternalMcpTab.js +9 -2
- package/dist/features/file-dropzone/react/components/FileDropzone.js +24 -25
- package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.d.ts +2 -2
- package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.js +35 -30
- package/dist/features/file-dropzone/react/components/FileDropzoneThumbnailGrid.js +30 -24
- package/dist/features/file-dropzone/react/components/FileDropzoneZone.d.ts +14 -0
- package/dist/features/file-dropzone/react/components/FileDropzoneZone.js +26 -0
- package/dist/features/html-editor/canvas-content-wrapper.js +13 -13
- package/dist/features/html-editor/canvas-embed-placeholders.js +46 -37
- package/dist/features/html-editor/css.js +76 -69
- package/dist/features/html-editor/editor-blocks.js +47 -33
- package/dist/features/html-editor/editor-serialization.js +6 -5
- package/dist/features/html-editor/protected-elements.js +39 -28
- package/dist/features/html-editor/react/hooks/useInteractiveHtmlEditor.js +4 -4
- package/dist/features/html-editor/source-splice.js +51 -30
- package/dist/features/i18n/dictionaries/settings-dialog.ja.js +1 -0
- package/dist/features/i18n/dictionaries/settings-dialog.zh-CN.js +1 -0
- package/dist/features/i18n/dictionaries/settings-dialog.zh-TW.js +1 -0
- package/dist/features/i18n/locale.js +20 -16
- package/dist/features/iframe-pool/react/dom-sync.js +8 -6
- package/dist/features/integrations/react/components/CodexInstallToggleButton.js +19 -4
- package/dist/features/integrations/react/components/IntegrationsTab.js +60 -14
- package/dist/features/interactive-ui/interactive-ui.css +1 -1
- package/dist/features/interactive-ui/providers/shadcn/data-table.js +10 -7
- package/dist/features/lexical-rich-text-editor/mention-parser.js +60 -57
- package/dist/features/lexical-rich-text-editor/react/hooks/useKeyboardCommands.js +33 -38
- package/dist/features/lexical-rich-text-editor/rules.js +87 -74
- package/dist/features/lexical-rich-text-editor/serialize.js +27 -25
- package/dist/features/mcp-ui/secret-card.js +111 -61
- package/dist/features/mcp-ui/surfaces/confirmation.js +39 -21
- package/dist/features/mcp-ui/surfaces/document.d.ts +1 -1
- package/dist/features/mcp-ui/surfaces/document.js +10 -2
- package/dist/features/mcp-ui/surfaces/fields.js +30 -50
- package/dist/features/mcp-ui/surfaces/form.d.ts +6 -0
- package/dist/features/mcp-ui/surfaces/form.js +43 -21
- package/dist/features/mcp-ui/surfaces/outcome.js +30 -20
- package/dist/features/mcp-ui/surfaces/text-input.js +33 -21
- package/dist/features/mcp-ui/surfaces/tokens.d.ts +6 -1
- package/dist/features/mcp-ui/surfaces/tokens.js +5 -2
- package/dist/features/media-providers/constants.d.ts +1 -0
- package/dist/features/media-providers/constants.js +2 -81
- package/dist/features/media-providers/index.d.ts +1 -1
- package/dist/features/media-providers/ports.d.ts +2 -0
- package/dist/features/media-providers/react/components/MediaProviderCard.d.ts +22 -0
- package/dist/features/media-providers/react/components/MediaProviderCard.js +50 -0
- package/dist/features/media-providers/react/components/MediaProvidersTab.d.ts +5 -6
- package/dist/features/media-providers/react/components/MediaProvidersTab.js +40 -72
- package/dist/features/media-providers/react/components/media-providers-tab-labels.d.ts +11 -0
- package/dist/features/media-providers/react/components/media-providers-tab-labels.js +57 -0
- package/dist/features/media-providers/react/hooks/useMediaProvidersTab.d.ts +4 -2
- package/dist/features/media-providers/react/hooks/useMediaProvidersTab.js +37 -23
- package/dist/features/media-providers/rules.js +17 -10
- package/dist/features/media-providers/types.d.ts +4 -22
- package/dist/features/media-providers/types.js +3 -8
- package/dist/features/memory/formatters.js +127 -92
- package/dist/features/memory/react/components/MemoryConnectedPanel.js +92 -36
- package/dist/features/memory/react/components/MemoryList.js +37 -18
- package/dist/features/memory/react/hooks/useMemoryExtractions.store.js +42 -36
- package/dist/features/notifications/react/components/NotificationsTab.js +28 -28
- package/dist/features/observability/long-task.d.ts +0 -12
- package/dist/features/observability/long-task.js +15 -19
- package/dist/features/observability/resource-error.d.ts +0 -10
- package/dist/features/observability/resource-error.js +18 -13
- package/dist/features/observability/stuck-run.js +10 -6
- package/dist/features/observability/telemetry-url.d.ts +6 -0
- package/dist/features/observability/telemetry-url.js +16 -0
- package/dist/features/observability/white-screen.js +12 -24
- package/dist/features/panel-kit/fetch-query/adapter.react.d.ts +2 -2
- package/dist/features/panel-kit/fetch-query/adapter.react.js +4 -2
- package/dist/features/panel-kit/fetch-query/adapter.tanstack.js +46 -33
- package/dist/features/panel-kit/helpers/standing-draft-local-backup.js +3 -1
- package/dist/features/panel-kit/hooks/use-settings-slice.hooks.js +3 -1
- package/dist/features/privacy/react/components/PrivacyTab.js +24 -13
- package/dist/features/progress-card/components/ProgressCard.js +7 -3
- package/dist/features/progress-card/reference-adapters.js +59 -54
- package/dist/features/project-locations/react/components/ProjectLocationsTab.js +24 -15
- package/dist/features/project-locations/react/hooks/useProjectLocationsTab.d.ts +8 -0
- package/dist/features/project-locations/react/hooks/useProjectLocationsTab.js +31 -23
- package/dist/features/resource-dashboard/react/components/ResourceBoard.js +6 -47
- package/dist/features/resource-dashboard/react/components/ResourceBoardView.d.ts +2 -2
- package/dist/features/resource-dashboard/react/components/ResourceBoardView.js +3 -6
- package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.d.ts +15 -0
- package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.js +35 -0
- package/dist/features/resource-dashboard/react/components/ResourceCard.js +5 -16
- package/dist/features/resource-dashboard/react/components/ResourceCardParts.d.ts +34 -0
- package/dist/features/resource-dashboard/react/components/ResourceCardParts.js +34 -0
- package/dist/features/resource-dashboard/react/components/ResourceRowList.js +3 -1
- package/dist/features/resource-dashboard/react/components/ResourceRowListItem.js +2 -2
- package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.d.ts +19 -0
- package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.js +11 -0
- package/dist/features/resource-dashboard/react/components/ResourceRowListView.d.ts +1 -1
- package/dist/features/resource-dashboard/react/components/ResourceRowListView.js +4 -4
- package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.d.ts +10 -0
- package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.js +20 -0
- package/dist/features/resource-dashboard/react/components/resourceBoardProps.d.ts +48 -0
- package/dist/features/resource-dashboard/react/components/resourceBoardProps.js +70 -0
- package/dist/features/resource-dashboard/react/components/resourceRowListProps.d.ts +19 -0
- package/dist/features/resource-dashboard/react/components/resourceRowListProps.js +14 -0
- package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.d.ts +16 -0
- package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.js +13 -0
- package/dist/features/resource-dashboard/rules.js +2 -8
- package/dist/features/sketch-editor/dom.js +150 -99
- package/dist/features/sketch-editor/react/components/SketchEditor.js +26 -15
- package/dist/features/skills/react/components/SkillDraftForm.js +13 -19
- package/dist/features/skills/react/components/SkillDraftForm.labels.d.ts +31 -0
- package/dist/features/skills/react/components/SkillDraftForm.labels.js +54 -0
- package/dist/features/skills/react/components/SkillRow.js +38 -18
- package/dist/features/skills/react/components/SkillRow.labels.d.ts +7 -0
- package/dist/features/skills/react/components/SkillRow.labels.js +18 -0
- package/dist/features/skills/react/components/SkillsTab.js +24 -14
- package/dist/features/skills/react/components/SkillsTab.labels.d.ts +7 -0
- package/dist/features/skills/react/components/SkillsTab.labels.js +11 -0
- package/dist/features/skills/rules.js +32 -23
- package/dist/features/source-config-list/agent-handles.js +2 -1
- package/dist/features/source-config-list/dependencies.js +53 -49
- package/dist/features/source-config-list/index.d.ts +3 -3
- package/dist/features/source-config-list/react/components/SourceConfigAddForm.d.ts +6 -5
- package/dist/features/source-config-list/react/components/SourceConfigAddForm.js +38 -7
- package/dist/features/source-config-list/react/components/SourceConfigField.js +69 -25
- package/dist/features/source-config-list/react/components/SourceConfigItemCard.d.ts +6 -5
- package/dist/features/source-config-list/react/components/SourceConfigItemCard.js +76 -20
- package/dist/features/source-config-list/react/components/SourceConfigListView.d.ts +5 -3
- package/dist/features/source-config-list/react/components/SourceConfigListView.js +33 -3
- package/dist/features/source-config-list/react/components/SourceConfigTestControl.js +17 -1
- package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.d.ts +11 -0
- package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.js +12 -0
- package/dist/features/source-config-list/types.d.ts +14 -0
- package/dist/features/tab-launcher-menu/react/components/TabLauncherMenu.js +25 -1
- package/dist/features/tab-launcher-menu/react/hooks/useTabLauncherMenu.js +15 -16
- package/dist/features/tab-strip/react/components/TabBar.js +8 -1
- package/dist/features/tabbed-dialog/react/components/TabbedDialog.js +8 -30
- package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.d.ts +15 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.js +8 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.d.ts +12 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.js +6 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.d.ts +27 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.js +12 -0
- package/dist/features/tabbed-dialog/react/components/tabbedDialogView.d.ts +43 -0
- package/dist/features/tabbed-dialog/react/components/tabbedDialogView.js +59 -0
- package/dist/features/version-manager/dependencies.d.ts +3 -1
- package/dist/features/version-manager/dependencies.js +3 -2
- package/dist/features/version-manager/react/components/VersionManagerModal.js +10 -6
- package/dist/features/version-manager/react/hooks/useVersionManager.js +7 -4
- package/dist/features/viewer-shell/react/components/CommentSidePanel.js +103 -50
- package/dist/features/viewer-shell/react/components/MarkdownSplitPane.js +28 -8
- package/dist/features/viewer-shell/react/hooks/useCommentReorder.js +22 -16
- package/dist/features/viewer-shell/rules.js +19 -23
- package/dist/react/components/CustomSelect.d.ts +38 -1
- package/dist/react/components/CustomSelect.js +94 -38
- package/dist/react/components/OnboardingDropdown.js +20 -8
- package/dist/react/components/RemixIcon.stylesheet.js +3 -1
- package/dist/react/components/Toast.js +13 -2
- package/dist/react/components/TooltipLayer.js +5 -14
- package/dist/react/components/WorkingDirPicker.js +78 -50
- package/dist/react/components/remixicon-font-recovery.js +38 -30
- package/dist/react/components/select-popover.d.ts +8 -6
- package/dist/react/components/select-popover.js +33 -29
- package/dist/react/mcp-ui/McpUiHost.d.ts +1 -1
- package/dist/react/mcp-ui/McpUiHost.js +10 -11
- package/dist/react/mcp-ui/host-message-source.d.ts +14 -2
- package/dist/react/mcp-ui/host-message-source.js +15 -5
- package/dist/react/mcp-ui/mcp-ui-host-frame.d.ts +41 -0
- package/dist/react/mcp-ui/mcp-ui-host-frame.js +39 -0
- package/dist/renderers/annotation-canvas/react/components/AnnotationCanvas.js +97 -69
- package/dist/renderers/preview-modal-shell/react/components/PreviewModalShell.js +59 -30
- package/dist/renderers/react/components/ArtifactView.js +9 -6
- package/dist/renderers/srcdoc/build.d.ts +4 -1
- package/dist/renderers/srcdoc/build.js +62 -48
- package/dist/styles/admin.css +29 -14
- package/dist/styles/lightbox.css +142 -0
- package/dist/styles/variables.css +128 -0
- package/dist/theme/apply.js +9 -5
- package/dist/utils/auto-open-file.d.ts +2 -1
- package/dist/utils/auto-open-file.js +69 -85
- package/dist/utils/markdown-scroll-sync.js +108 -87
- package/dist/utils/polygon-selection.js +25 -26
- package/dist/utils/smooth-scroll-to-top.d.ts +1 -1
- package/dist/utils/smooth-scroll-to-top.js +2 -32
- package/dist/utils/sse.js +26 -26
- package/dist/utils/unit-bezier.d.ts +7 -0
- package/dist/utils/unit-bezier.js +38 -0
- package/package.json +9 -12
- package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.d.ts +0 -13
- package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.js +0 -46
|
@@ -205,6 +205,10 @@ function isUsableRect(r) {
|
|
|
205
205
|
function toCaretRect(r) {
|
|
206
206
|
return { top: r.top, bottom: r.bottom, left: r.left, right: r.right };
|
|
207
207
|
}
|
|
208
|
+
/** A caret rect pinned to a box's left edge (zero width). */
|
|
209
|
+
function leftEdgeRect(r) {
|
|
210
|
+
return { top: r.top, bottom: r.bottom, left: r.left, right: r.left };
|
|
211
|
+
}
|
|
208
212
|
/** Reads the caret's viewport rect via the native selection, with ordered
|
|
209
213
|
* fallbacks for the collapsed-caret 0×0 case: (1) range bounding rect,
|
|
210
214
|
* (2) range client-rects list, (3) a zero-width probe range cloned at the
|
|
@@ -213,99 +217,108 @@ export function readCaretRect(rootEl) {
|
|
|
213
217
|
if (typeof window === 'undefined')
|
|
214
218
|
return null;
|
|
215
219
|
const winSel = window.getSelection();
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
if (typeof probe.getClientRects === 'function') {
|
|
245
|
-
const prList = probe.getClientRects();
|
|
246
|
-
if (prList.length > 0 && isUsableRect(prList[0]))
|
|
247
|
-
return toCaretRect(prList[0]);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
catch {
|
|
251
|
-
/* fall through */
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
const el = anchorNode.nodeType === Node.ELEMENT_NODE
|
|
255
|
-
? anchorNode
|
|
256
|
-
: anchorNode.parentElement;
|
|
257
|
-
if (el) {
|
|
258
|
-
const er = el.getBoundingClientRect();
|
|
259
|
-
if (isUsableRect(er)) {
|
|
260
|
-
return { top: er.top, bottom: er.bottom, left: er.left, right: er.left };
|
|
261
|
-
}
|
|
262
|
-
}
|
|
220
|
+
const fromSelection = winSel && winSel.rangeCount > 0 ? caretRectFromRange(winSel.getRangeAt(0)) : null;
|
|
221
|
+
if (fromSelection)
|
|
222
|
+
return fromSelection;
|
|
223
|
+
return rootEl ? leftEdgeRect(rootEl.getBoundingClientRect()) : null;
|
|
224
|
+
}
|
|
225
|
+
/** Fallbacks (1)–(4) of `readCaretRect` for one live selection range. */
|
|
226
|
+
function caretRectFromRange(range) {
|
|
227
|
+
const fromRange = rangeGeometryRect(range);
|
|
228
|
+
if (fromRange)
|
|
229
|
+
return fromRange;
|
|
230
|
+
const anchorNode = range.startContainer;
|
|
231
|
+
if (anchorNode.nodeType === Node.TEXT_NODE) {
|
|
232
|
+
const probed = probeTextCaretRect({ text: anchorNode, offset: range.startOffset });
|
|
233
|
+
if (probed)
|
|
234
|
+
return probed;
|
|
235
|
+
}
|
|
236
|
+
return anchorElementRect(anchorNode);
|
|
237
|
+
}
|
|
238
|
+
/** Fallbacks (1) and (2): a range's bounding rect, then its first client rect. */
|
|
239
|
+
function rangeGeometryRect(range) {
|
|
240
|
+
// jsdom does not implement Range geometry — guard every native call so a
|
|
241
|
+
// missing method falls through to the element/root box instead of
|
|
242
|
+
// throwing.
|
|
243
|
+
if (typeof range.getBoundingClientRect === 'function') {
|
|
244
|
+
const r = range.getBoundingClientRect();
|
|
245
|
+
if (isUsableRect(r))
|
|
246
|
+
return toCaretRect(r);
|
|
263
247
|
}
|
|
264
|
-
if (
|
|
265
|
-
const
|
|
266
|
-
|
|
248
|
+
if (typeof range.getClientRects === 'function') {
|
|
249
|
+
const rects = range.getClientRects();
|
|
250
|
+
if (rects.length > 0 && isUsableRect(rects[0]))
|
|
251
|
+
return toCaretRect(rects[0]);
|
|
267
252
|
}
|
|
268
253
|
return null;
|
|
269
254
|
}
|
|
255
|
+
/** Fallback (3): geometry of a zero-width probe range at the anchor text offset. */
|
|
256
|
+
function probeTextCaretRect(at) {
|
|
257
|
+
try {
|
|
258
|
+
const probe = document.createRange();
|
|
259
|
+
const off = Math.min(at.offset, at.text.length);
|
|
260
|
+
probe.setStart(at.text, off);
|
|
261
|
+
probe.setEnd(at.text, off);
|
|
262
|
+
return rangeGeometryRect(probe);
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
/* fall through */
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/** Fallback (4): the anchor element's box (or the text anchor's parent's). */
|
|
270
|
+
function anchorElementRect(anchorNode) {
|
|
271
|
+
const el = anchorNode.nodeType === Node.ELEMENT_NODE ? anchorNode : anchorNode.parentElement;
|
|
272
|
+
if (!el)
|
|
273
|
+
return null;
|
|
274
|
+
const er = el.getBoundingClientRect();
|
|
275
|
+
return isUsableRect(er) ? leftEdgeRect(er) : null;
|
|
276
|
+
}
|
|
270
277
|
/** Default ABOVE the caret (a composer typically sits at a panel's bottom,
|
|
271
278
|
* so above never occludes the input line); flip BELOW only when above lacks
|
|
272
279
|
* room. Clamp horizontally into the viewport (or `boundary`, when given).
|
|
273
280
|
* Caps height; the caller's popover scrolls internally. */
|
|
274
281
|
export function computeCaretFloatingLayerPosition(caret, size, boundary, config) {
|
|
275
|
-
const {
|
|
282
|
+
const { margin } = config;
|
|
276
283
|
const vw = window.innerWidth;
|
|
277
|
-
const
|
|
278
|
-
const
|
|
279
|
-
|
|
284
|
+
const width = layerWidth({ vw, boundary, margin, preferredWidth: config.preferredWidth });
|
|
285
|
+
const vertical = verticalPlacement({
|
|
286
|
+
caret,
|
|
287
|
+
vh: window.innerHeight,
|
|
288
|
+
wantedH: size?.height ?? config.hardMaxHeight,
|
|
289
|
+
...config,
|
|
290
|
+
});
|
|
291
|
+
const left = clampLeft({ caretLeft: caret.left, vw, boundary, margin, width });
|
|
292
|
+
return { left, top: vertical.top, width, maxHeight: vertical.maxHeight, placement: vertical.placement };
|
|
293
|
+
}
|
|
294
|
+
function layerWidth(at) {
|
|
295
|
+
const viewportAvailableWidth = at.vw - at.margin * 2;
|
|
296
|
+
const boundaryAvailableWidth = at.boundary
|
|
297
|
+
? at.boundary.width - at.margin * 2
|
|
298
|
+
: viewportAvailableWidth;
|
|
280
299
|
const availableWidth = Math.max(240, Math.min(viewportAvailableWidth, boundaryAvailableWidth));
|
|
281
|
-
|
|
300
|
+
return Math.min(at.preferredWidth, availableWidth);
|
|
301
|
+
}
|
|
302
|
+
function verticalPlacement(at) {
|
|
303
|
+
const { caret, gap, margin, wantedH } = at;
|
|
282
304
|
const spaceAbove = caret.top - gap - margin;
|
|
283
|
-
const spaceBelow = vh - caret.bottom - gap - margin;
|
|
284
|
-
const wantedH = size?.height ?? hardMaxHeight;
|
|
305
|
+
const spaceBelow = at.vh - caret.bottom - gap - margin;
|
|
285
306
|
const aboveFits = spaceAbove >= Math.min(wantedH, 160);
|
|
286
307
|
const placement = aboveFits || spaceAbove >= spaceBelow ? 'above' : 'below';
|
|
287
308
|
const space = placement === 'above' ? spaceAbove : spaceBelow;
|
|
288
|
-
const maxHeight = Math.max(120, Math.min(hardMaxHeight, space));
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
296
|
-
else {
|
|
297
|
-
top = caret.bottom + gap;
|
|
298
|
-
}
|
|
309
|
+
const maxHeight = Math.max(120, Math.min(at.hardMaxHeight, space));
|
|
310
|
+
if (placement === 'below')
|
|
311
|
+
return { top: caret.bottom + gap, maxHeight, placement };
|
|
312
|
+
const h = Math.min(wantedH, maxHeight);
|
|
313
|
+
return { top: Math.max(margin, caret.top - gap - h), maxHeight, placement };
|
|
314
|
+
}
|
|
315
|
+
function clampLeft(at) {
|
|
316
|
+
const { vw, boundary, margin, width } = at;
|
|
299
317
|
const minLeft = boundary ? Math.max(margin, boundary.left + margin) : margin;
|
|
300
318
|
const maxLeft = boundary
|
|
301
319
|
? Math.max(minLeft, Math.min(vw - margin - width, boundary.right - margin - width))
|
|
302
320
|
: vw - margin - width;
|
|
303
|
-
|
|
304
|
-
if (left > maxLeft)
|
|
305
|
-
left = maxLeft;
|
|
306
|
-
if (left < minLeft)
|
|
307
|
-
left = minLeft;
|
|
308
|
-
return { left, top, width, maxHeight, placement };
|
|
321
|
+
return Math.max(minLeft, Math.min(at.caretLeft, maxLeft));
|
|
309
322
|
}
|
|
310
323
|
/** Combines detection + caret-rect reading into the shape `RichTextInput`
|
|
311
324
|
* reports upward on every editor update. */
|
|
@@ -9,32 +9,34 @@ import { $isMentionNode } from './mention-node.js';
|
|
|
9
9
|
export function serializeRichText(state) {
|
|
10
10
|
return state.read(() => {
|
|
11
11
|
const mentions = [];
|
|
12
|
-
const blocks =
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
let line = '';
|
|
20
|
-
for (const child of block.getChildren()) {
|
|
21
|
-
if ($isMentionNode(child)) {
|
|
22
|
-
line += child.getToken();
|
|
23
|
-
mentions.push(child.getEntity());
|
|
24
|
-
}
|
|
25
|
-
else if ($isLineBreakNode(child)) {
|
|
26
|
-
line += '\n';
|
|
27
|
-
}
|
|
28
|
-
else if ($isTextNode(child)) {
|
|
29
|
-
line += child.getTextContent();
|
|
30
|
-
}
|
|
31
|
-
else if ($isElementNode(child)) {
|
|
32
|
-
line += child.getTextContent();
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
blocks.push(line);
|
|
36
|
-
}
|
|
12
|
+
const blocks = $getRoot()
|
|
13
|
+
.getChildren()
|
|
14
|
+
.map((block) => $isParagraphNode(block)
|
|
15
|
+
? serializeParagraph({ paragraph: block, mentions })
|
|
16
|
+
: // Stray non-paragraph block (shouldn't happen): fall back to its text.
|
|
17
|
+
block.getTextContent());
|
|
37
18
|
return { text: blocks.join('\n'), mentions };
|
|
38
19
|
});
|
|
39
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* One paragraph's wire text; appends each atomic mention's entity to
|
|
23
|
+
* `mentions` in document order. A non-mention decorator child contributes
|
|
24
|
+
* nothing.
|
|
25
|
+
*/
|
|
26
|
+
function serializeParagraph(at) {
|
|
27
|
+
let line = '';
|
|
28
|
+
for (const child of at.paragraph.getChildren()) {
|
|
29
|
+
if ($isMentionNode(child)) {
|
|
30
|
+
line += child.getToken();
|
|
31
|
+
at.mentions.push(child.getEntity());
|
|
32
|
+
}
|
|
33
|
+
else if ($isLineBreakNode(child)) {
|
|
34
|
+
line += '\n';
|
|
35
|
+
}
|
|
36
|
+
else if ($isTextNode(child) || $isElementNode(child)) {
|
|
37
|
+
line += child.getTextContent();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return line;
|
|
41
|
+
}
|
|
40
42
|
//# sourceMappingURL=serialize.js.map
|
|
@@ -27,51 +27,131 @@ function checkedFields(form) {
|
|
|
27
27
|
return form.fields.map(field => ({ ...field }));
|
|
28
28
|
}
|
|
29
29
|
/** Classify before persistence; an abort wins over an already-buffered answer.
|
|
30
|
-
*
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (ctx.signal.aborted)
|
|
36
|
-
return { status: 'abandoned' };
|
|
30
|
+
* @complexity O(1). */
|
|
31
|
+
function answerStep(required) {
|
|
32
|
+
const { answer, signal } = required;
|
|
33
|
+
if (signal.aborted)
|
|
34
|
+
return { run: { status: 'abandoned' } };
|
|
37
35
|
if (answer.status !== 'received')
|
|
38
|
-
return { status: answer.status };
|
|
36
|
+
return { run: { status: answer.status } };
|
|
39
37
|
if (answer.params[SURFACE_DISMISSED_PARAM] === true)
|
|
40
|
-
return { status: 'cancelled' };
|
|
41
|
-
|
|
38
|
+
return { run: { status: 'cancelled' } };
|
|
39
|
+
return { params: answer.params };
|
|
40
|
+
}
|
|
41
|
+
/** The exact submitted secret, or undefined when it is blank or malformed.
|
|
42
|
+
* Trimming tests blankness only: rewriting a provider secret can corrupt intentional newlines.
|
|
43
|
+
* @complexity O(b) in the submitted secret bytes. */
|
|
44
|
+
function secretValue(required) {
|
|
45
|
+
const { field, value } = required;
|
|
46
|
+
if (value !== undefined && typeof value !== 'string')
|
|
47
|
+
return undefined;
|
|
48
|
+
const secret = typeof value === 'string' ? value : '';
|
|
49
|
+
if (secret.trim() === '' && (secret !== '' || field.allowBlank !== true))
|
|
50
|
+
return undefined;
|
|
51
|
+
return secret;
|
|
52
|
+
}
|
|
53
|
+
/** Project only declared names; undefined means a required secret was blank or malformed.
|
|
54
|
+
* @complexity O(n + b) in fields and submitted secret bytes. */
|
|
55
|
+
function collectValues(required) {
|
|
56
|
+
const { fields, params } = required;
|
|
57
|
+
const values = Object.fromEntries(fields.map(field => [field.name, params[field.name]]));
|
|
42
58
|
const secrets = [];
|
|
43
59
|
for (const field of fields) {
|
|
44
60
|
if (field.secret !== true)
|
|
45
61
|
continue;
|
|
46
|
-
const
|
|
47
|
-
if (
|
|
48
|
-
return
|
|
49
|
-
const secret = typeof value === 'string' ? value : '';
|
|
50
|
-
if (secret.trim() === '' && (secret !== '' || field.allowBlank !== true))
|
|
51
|
-
return { status: 'blank' };
|
|
62
|
+
const secret = secretValue({ field, value: values[field.name] });
|
|
63
|
+
if (secret === undefined)
|
|
64
|
+
return undefined;
|
|
52
65
|
values[field.name] = secret;
|
|
53
66
|
secrets.push(secret);
|
|
54
67
|
}
|
|
68
|
+
return { values, secrets };
|
|
69
|
+
}
|
|
70
|
+
/** Map a save exception to fixed, redacted text and log fixed metadata only.
|
|
71
|
+
* @complexity O(m + b): delegates to core's exact-secret redaction scan. */
|
|
72
|
+
function failedRun(required) {
|
|
73
|
+
const { error, secrets, signal, toolId, options, exchangeId } = required;
|
|
74
|
+
if (signal.aborted)
|
|
75
|
+
return { status: 'abandoned' };
|
|
76
|
+
let safeMessage = options.text?.saveFailure ?? 'Saving failed. Nothing was saved.';
|
|
77
|
+
try {
|
|
78
|
+
safeMessage = options.safeError?.(error) ?? safeMessage;
|
|
79
|
+
}
|
|
80
|
+
catch { /* An error mapper must not disclose a second exception containing the submitted value. */ }
|
|
81
|
+
// Even an accidentally permissive allowlist must not echo the submitted secret.
|
|
82
|
+
safeMessage = redactSecrets({ input: safeMessage }, { exactSecrets: secrets });
|
|
83
|
+
try {
|
|
84
|
+
options.logFailure?.({ toolId, exchangeId, saved: false });
|
|
85
|
+
}
|
|
86
|
+
catch { /* Logging must not replace the safe model result with an opaque logger exception. */ }
|
|
87
|
+
return { status: 'failed', safeMessage };
|
|
88
|
+
}
|
|
89
|
+
/** Classify, then persist; an abort wins over an already-buffered answer.
|
|
90
|
+
* @complexity O(n + b) before save in fields and submitted secret bytes; failure text delegates
|
|
91
|
+
* to core's exact-secret redaction scan. Host persistence cost is external. */
|
|
92
|
+
async function saveAnswer(required) {
|
|
93
|
+
const { answer, fields, prep, ctx, spec, options, exchangeId } = required;
|
|
94
|
+
const step = answerStep({ answer, signal: ctx.signal });
|
|
95
|
+
if ('run' in step)
|
|
96
|
+
return step.run;
|
|
97
|
+
const collected = collectValues({ fields, params: step.params });
|
|
98
|
+
if (collected === undefined)
|
|
99
|
+
return { status: 'blank' };
|
|
55
100
|
if (ctx.signal.aborted)
|
|
56
101
|
return { status: 'abandoned' };
|
|
102
|
+
const { values, secrets } = collected;
|
|
57
103
|
try {
|
|
58
104
|
return { status: 'saved', saved: await spec.save({ values, prep, signal: ctx.signal }) };
|
|
59
105
|
}
|
|
60
106
|
catch (error) {
|
|
107
|
+
return failedRun({ error, secrets, signal: ctx.signal, toolId: spec.toolId, options, exchangeId });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** Fail closed without an emitter. @complexity O(1). @throws ToolInputError with fixed text. */
|
|
111
|
+
function requireEmitter(required, optional) {
|
|
112
|
+
if (optional.emitSurface)
|
|
113
|
+
return optional.emitSurface;
|
|
114
|
+
throw new ToolInputError({ message: required.options.text?.noEmitter ?? `${required.toolId}: this execution context has no interactive form channel (no emitSurface), so this form cannot be shown here. Nothing was changed.` });
|
|
115
|
+
}
|
|
116
|
+
/** The form resource: engine-owned routing params and dismissal around the host's copy.
|
|
117
|
+
* @complexity O(n + h) in fields and rendered HTML. */
|
|
118
|
+
function buildCardForm(required) {
|
|
119
|
+
const { form, fields, uri, framing, toolId, exchangeId, options } = required;
|
|
120
|
+
return buildFormSurface({ ...form, fields, uri, ...framing, toolName: toolId,
|
|
121
|
+
baseParams: { [SURFACE_EXCHANGE_ID_PARAM]: exchangeId },
|
|
122
|
+
cancel: { label: form.cancelLabel ?? options.text?.cancelLabel ?? 'Cancel', toolName: toolId,
|
|
123
|
+
params: { [SURFACE_EXCHANGE_ID_PARAM]: exchangeId, [SURFACE_DISMISSED_PARAM]: true } },
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/** Open the exchange, emit the card and hold the call until the real save is reported.
|
|
127
|
+
* @complexity O(n + h + b) locally, plus host I/O. */
|
|
128
|
+
async function runCard(required) {
|
|
129
|
+
const { spec, options, deps, ctx, emit, prep, form, fields } = required;
|
|
130
|
+
const exchange = deps.surfaceExchanges.open({ binding: { toolId: spec.toolId, principalId: ctx.principal.id, channel: 'mcp-ui' }, emit });
|
|
131
|
+
const closeOnAbort = () => exchange.close({});
|
|
132
|
+
ctx.signal.addEventListener('abort', closeOnAbort, { once: true });
|
|
133
|
+
try {
|
|
134
|
+
// Close the open/register race as well as the prepare/abort race.
|
|
61
135
|
if (ctx.signal.aborted)
|
|
62
|
-
return { status: 'abandoned' };
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
136
|
+
return spec.result({ prep, run: { status: 'abandoned' } });
|
|
137
|
+
const uri = `ui://${options.uriHost ?? 'jini'}/secret-card/${encodeURIComponent(spec.toolId)}/${encodeURIComponent(exchange.id)}`;
|
|
138
|
+
const framing = options.frameSize === undefined ? {} : { preferredFrameSize: options.frameSize };
|
|
139
|
+
const resource = buildCardForm({ form, fields, uri, framing, toolId: spec.toolId, exchangeId: exchange.id, options });
|
|
140
|
+
return await deps.askThenReport({ exchange, confirmationEmission: { channel: 'mcp-ui', payload: { resource } },
|
|
141
|
+
handle: async (answer) => {
|
|
142
|
+
const run = await saveAnswer({ answer, fields, prep, ctx, spec, options, exchangeId: exchange.id });
|
|
143
|
+
const result = spec.result({ prep, run });
|
|
144
|
+
const outcome = spec.outcome({ prep, run });
|
|
145
|
+
if (outcome === undefined)
|
|
146
|
+
return { result };
|
|
147
|
+
return { result, outcome: { channel: 'mcp-ui', payload: { resource: buildOutcomeSurface({ ...outcome, uri, ...framing }) } } };
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
ctx.signal.removeEventListener('abort', closeOnAbort);
|
|
153
|
+
// Also covers rendering/projection failures before or inside askThenReport; close is idempotent.
|
|
154
|
+
exchange.close({});
|
|
75
155
|
}
|
|
76
156
|
}
|
|
77
157
|
/**
|
|
@@ -92,8 +172,7 @@ export function defineSecretCardTool(required, options = {}) {
|
|
|
92
172
|
toolId: spec.toolId,
|
|
93
173
|
secretFieldNames: prep => checkedFields(spec.form({ prep })).filter(field => field.secret === true).map(field => field.name),
|
|
94
174
|
handler: deps => async (ctx, optional = {}) => {
|
|
95
|
-
|
|
96
|
-
throw new ToolInputError({ message: options.text?.noEmitter ?? `${spec.toolId}: this execution context has no interactive form channel (no emitSurface), so this form cannot be shown here. Nothing was changed.` });
|
|
175
|
+
const emit = requireEmitter({ toolId: spec.toolId, options }, optional);
|
|
97
176
|
const prep = await spec.prepare({ ctx });
|
|
98
177
|
if (ctx.signal.aborted)
|
|
99
178
|
return spec.result({ prep, run: { status: 'abandoned' } });
|
|
@@ -101,36 +180,7 @@ export function defineSecretCardTool(required, options = {}) {
|
|
|
101
180
|
const fields = checkedFields(form);
|
|
102
181
|
if (ctx.signal.aborted)
|
|
103
182
|
return spec.result({ prep, run: { status: 'abandoned' } });
|
|
104
|
-
|
|
105
|
-
const closeOnAbort = () => exchange.close({});
|
|
106
|
-
ctx.signal.addEventListener('abort', closeOnAbort, { once: true });
|
|
107
|
-
try {
|
|
108
|
-
// Close the open/register race as well as the prepare/abort race.
|
|
109
|
-
if (ctx.signal.aborted)
|
|
110
|
-
return spec.result({ prep, run: { status: 'abandoned' } });
|
|
111
|
-
const uri = `ui://${options.uriHost ?? 'jini'}/secret-card/${encodeURIComponent(spec.toolId)}/${encodeURIComponent(exchange.id)}`;
|
|
112
|
-
const framing = options.frameSize === undefined ? {} : { preferredFrameSize: options.frameSize };
|
|
113
|
-
const resource = buildFormSurface({ ...form, fields, uri, ...framing, toolName: spec.toolId,
|
|
114
|
-
baseParams: { [SURFACE_EXCHANGE_ID_PARAM]: exchange.id },
|
|
115
|
-
cancel: { label: form.cancelLabel ?? options.text?.cancelLabel ?? 'Cancel', toolName: spec.toolId,
|
|
116
|
-
params: { [SURFACE_EXCHANGE_ID_PARAM]: exchange.id, [SURFACE_DISMISSED_PARAM]: true } },
|
|
117
|
-
});
|
|
118
|
-
return await deps.askThenReport({ exchange, confirmationEmission: { channel: 'mcp-ui', payload: { resource } },
|
|
119
|
-
handle: async (answer) => {
|
|
120
|
-
const run = await saveAnswer({ answer, fields, prep, ctx, spec, options, exchangeId: exchange.id });
|
|
121
|
-
const result = spec.result({ prep, run });
|
|
122
|
-
const outcome = spec.outcome({ prep, run });
|
|
123
|
-
if (outcome === undefined)
|
|
124
|
-
return { result };
|
|
125
|
-
return { result, outcome: { channel: 'mcp-ui', payload: { resource: buildOutcomeSurface({ ...outcome, uri, ...framing }) } } };
|
|
126
|
-
},
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
finally {
|
|
130
|
-
ctx.signal.removeEventListener('abort', closeOnAbort);
|
|
131
|
-
// Also covers rendering/projection failures before or inside askThenReport; close is idempotent.
|
|
132
|
-
exchange.close({});
|
|
133
|
-
}
|
|
183
|
+
return runCard({ spec, options, deps, ctx, emit, prep, form, fields });
|
|
134
184
|
},
|
|
135
185
|
};
|
|
136
186
|
}
|
|
@@ -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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if ((spec.choices ?? []).length
|
|
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
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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) {
|