@jini-ai/ui 0.6.15-rc.2 → 0.6.15

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 (183) hide show
  1. package/dist/browser/useDismissOnOutsideOrEscape.js +1 -1
  2. package/dist/browser/useGlobalKeydown.js +2 -2
  3. package/dist/browser/useModalWindowDragGuard.js +1 -1
  4. package/dist/core/theme/css-name.d.ts +3 -0
  5. package/dist/core/theme/css-name.js +5 -0
  6. package/dist/core/theme/stylesheet.js +2 -1
  7. package/dist/core/theme/validation.js +15 -4
  8. package/dist/features/about/rules.js +2 -1
  9. package/dist/features/admin-widgets/components/InfoTip.hooks.js +22 -12
  10. package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.js +1 -1
  11. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.js +1 -1
  12. package/dist/features/admin-widgets/components/SeeMore/SeeMore.hooks.js +2 -2
  13. package/dist/features/admin-widgets/components/SeeMore/SeeMore.js +4 -2
  14. package/dist/features/admin-widgets/components/Select/Select.hooks.js +2 -1
  15. package/dist/features/admin-widgets/components/Select/Select.js +2 -1
  16. package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.js +4 -2
  17. package/dist/features/admin-widgets/components/useDebouncedHtmlPreview.hooks.js +3 -2
  18. package/dist/features/asset-grid/react/components/AssetGrid.js +8 -3
  19. package/dist/features/asset-grid/react/hooks/useAssetGridKeyboardShortcuts.js +7 -6
  20. package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.js +8 -10
  21. package/dist/features/asset-grid/react/hooks/useRubberBandDrag.js +1 -1
  22. package/dist/features/asset-tree-browser/react/components/AssetTreeFileRow.js +6 -8
  23. package/dist/features/asset-tree-browser/react/hooks/useAssetTreeRowMenu.js +1 -1
  24. package/dist/features/asset-tree-browser/rules.js +1 -3
  25. package/dist/features/browser-chrome/rules.js +10 -8
  26. package/dist/features/charts/browser.js +2 -1
  27. package/dist/features/charts/react.js +3 -1
  28. package/dist/features/connectors/ConnectorsBrowser.js +3 -1
  29. package/dist/features/connectors/hooks/useConnectorAuthorization.js +1 -1
  30. package/dist/features/connectors/hooks/useConnectorCatalog.js +2 -2
  31. package/dist/features/connectors/rules.js +10 -14
  32. package/dist/features/execution/react/components/AgentDiagnosticRow.js +2 -0
  33. package/dist/features/execution/react/components/ByokProviderForm.js +1 -3
  34. package/dist/features/execution/react/components/LocalCliAgentSettings.js +2 -1
  35. package/dist/features/execution/react/hooks/use-byok-field-validation.hooks.js +6 -2
  36. package/dist/features/execution/react/hooks/useExecutionTab.js +9 -5
  37. package/dist/features/execution/react/hooks/useLocalCliAgentCard.js +3 -6
  38. package/dist/features/execution/react/hooks/useReasoningControl.js +3 -3
  39. package/dist/features/execution/rules.js +23 -18
  40. package/dist/features/external-mcp/react/components/ExternalMcpTab.js +10 -1
  41. package/dist/features/file-dropzone/rules.js +3 -2
  42. package/dist/features/html-editor/canvas-embed-placeholders.js +2 -3
  43. package/dist/features/html-editor/css.js +7 -5
  44. package/dist/features/html-editor/editor-blocks.js +60 -6
  45. package/dist/features/html-editor/protected-elements.js +5 -3
  46. package/dist/features/html-editor/react/hooks/useInteractiveHtmlEditor.js +3 -3
  47. package/dist/features/html-editor/source-splice.js +6 -4
  48. package/dist/features/iframe-pool/react/components/IframeKeepAliveProvider.js +5 -5
  49. package/dist/features/iframe-pool/react/dom-sync.js +2 -2
  50. package/dist/features/integrations/react/components/IntegrationsTab.js +3 -1
  51. package/dist/features/integrations/rules.js +6 -4
  52. package/dist/features/interactive-ui/providers/shadcn/data-table.js +10 -5
  53. package/dist/features/interactive-ui/table-data.js +1 -1
  54. package/dist/features/lexical-rich-text-editor/deserialize.js +2 -2
  55. package/dist/features/lexical-rich-text-editor/mention-parser.js +4 -4
  56. package/dist/features/lexical-rich-text-editor/react/components/RichTextInput.js +2 -1
  57. package/dist/features/lexical-rich-text-editor/react/hooks/useCaretFloatingLayerPosition.js +1 -1
  58. package/dist/features/lexical-rich-text-editor/react/hooks/useMentionColorStamping.js +1 -1
  59. package/dist/features/lexical-rich-text-editor/serialize.js +13 -10
  60. package/dist/features/list-detail-panel/react/components/ListDetailPanel.js +1 -1
  61. package/dist/features/mcp-ui/confirmation-store.js +3 -6
  62. package/dist/features/mcp-ui/surfaces/confirmation.d.ts +5 -0
  63. package/dist/features/mcp-ui/surfaces/confirmation.js +1 -0
  64. package/dist/features/mcp-ui/surfaces/document.d.ts +3 -15
  65. package/dist/features/mcp-ui/surfaces/document.js +37 -6
  66. package/dist/features/mcp-ui/surfaces/fields.js +2 -0
  67. package/dist/features/mcp-ui/surfaces/host-theme.js +2 -1
  68. package/dist/features/mcp-ui/surfaces/index.d.ts +1 -0
  69. package/dist/features/mcp-ui/surfaces/index.js +1 -0
  70. package/dist/features/mcp-ui/surfaces/outcome.js +1 -1
  71. package/dist/features/mcp-ui/surfaces/select.js +2 -1
  72. package/dist/features/mcp-ui/surfaces/structured-details.d.ts +40 -0
  73. package/dist/features/mcp-ui/surfaces/structured-details.js +159 -0
  74. package/dist/features/mcp-ui/surfaces/text-input.js +1 -1
  75. package/dist/features/mcp-ui/surfaces/tool-approval.d.ts +21 -0
  76. package/dist/features/mcp-ui/surfaces/tool-approval.js +41 -0
  77. package/dist/features/media-providers/react/hooks/useMediaProvidersTab.js +2 -1
  78. package/dist/features/memory/async-commit-guard.js +4 -1
  79. package/dist/features/memory/formatters.js +5 -1
  80. package/dist/features/memory/react/components/MemoryConnectedPanel.js +3 -1
  81. package/dist/features/memory/react/components/MemoryEntryCard.js +1 -1
  82. package/dist/features/memory/react/components/MemorySettingsPanel.js +3 -1
  83. package/dist/features/memory/react/hooks/useMemoryConfig.hooks.js +12 -10
  84. package/dist/features/memory/react/hooks/useMemoryConnectors.hooks.js +2 -5
  85. package/dist/features/memory/react/hooks/useMemoryConnectors.rules.js +10 -9
  86. package/dist/features/memory/react/hooks/useMemoryEntries.hooks.js +4 -2
  87. package/dist/features/memory/react/hooks/useMemoryFlash.hooks.js +1 -1
  88. package/dist/features/memory/rules.js +2 -1
  89. package/dist/features/panel-kit/fetch-query/adapter.react.js +3 -2
  90. package/dist/features/panel-kit/fetch-query/adapter.tanstack.js +3 -2
  91. package/dist/features/panel-kit/fetch-query/binding.react.js +2 -1
  92. package/dist/features/panel-kit/helpers/standing-draft-recovery-message.js +13 -4
  93. package/dist/features/panel-kit/hooks/use-dirty-guard.hooks.js +1 -1
  94. package/dist/features/panel-kit/hooks/use-focus-trap.hooks.js +1 -1
  95. package/dist/features/panel-kit/hooks/use-settings-slice.hooks.js +14 -10
  96. package/dist/features/panel-kit/hooks/use-standing-draft-autosave.hooks.js +2 -2
  97. package/dist/features/progress-card/components/ProgressCard.d.ts +1 -1
  98. package/dist/features/progress-card/reference-adapters.js +23 -13
  99. package/dist/features/progress-card/rules.d.ts +0 -1
  100. package/dist/features/progress-card/rules.js +14 -20
  101. package/dist/features/progress-card/types.d.ts +1 -1
  102. package/dist/features/project-locations/dependencies.js +6 -1
  103. package/dist/features/project-locations/react/components/ProjectLocationsTab.js +2 -1
  104. package/dist/features/project-locations/rules.js +2 -1
  105. package/dist/features/resource-dashboard/react/components/ResourceBoardToolbar.js +8 -1
  106. package/dist/features/resource-dashboard/react/components/ResourceCardParts.js +2 -1
  107. package/dist/features/resource-dashboard/react/components/ResourceRowListItem.js +3 -1
  108. package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.js +2 -1
  109. package/dist/features/resource-dashboard/react/components/ResourceRowListView.js +1 -1
  110. package/dist/features/resource-dashboard/react/hooks/useResourceBoard.js +1 -1
  111. package/dist/features/schedule-picker/rules.js +13 -7
  112. package/dist/features/sketch-editor/constants.d.ts +0 -6
  113. package/dist/features/sketch-editor/constants.js +17 -15
  114. package/dist/features/sketch-editor/dom.js +4 -7
  115. package/dist/features/sketch-editor/react/components/SketchMainMenu.js +2 -1
  116. package/dist/features/sketch-editor/react/components/SketchSaveStateBadge.js +2 -1
  117. package/dist/features/sketch-editor/react/hooks/useSketchDomEnhancements.js +1 -1
  118. package/dist/features/sketch-editor/rules.js +13 -12
  119. package/dist/features/skills/dependencies.js +9 -4
  120. package/dist/features/skills/react/components/SkillRow.js +2 -1
  121. package/dist/features/skills/react/components/SkillsTab.js +2 -1
  122. package/dist/features/skills/rules.js +3 -3
  123. package/dist/features/source-config-list/agent-handles.js +4 -3
  124. package/dist/features/source-config-list/react/components/SourceConfigItemCard.js +3 -1
  125. package/dist/features/tab-strip/react/components/TabBar.hooks.js +4 -2
  126. package/dist/features/version-manager/react/components/VersionPreviewFrame.js +4 -1
  127. package/dist/features/version-manager/react/components/VersionSidebar.js +8 -6
  128. package/dist/features/version-manager/react/hooks/usePreviewCanvasSize.js +1 -1
  129. package/dist/features/version-manager/react/hooks/useVersionManager.js +5 -7
  130. package/dist/features/version-manager/rules.js +3 -5
  131. package/dist/features/viewer-shell/react/components/CommentSidePanel.js +2 -1
  132. package/dist/features/viewer-shell/react/components/SegmentedToggle.js +2 -1
  133. package/dist/features/viewer-shell/react/components/ViewerShell.js +2 -1
  134. package/dist/features/viewer-shell/rules.js +8 -7
  135. package/dist/react/components/BrandLogo.js +10 -2
  136. package/dist/react/components/CustomSelect.js +1 -1
  137. package/dist/react/components/EditorIcon.js +17 -14
  138. package/dist/react/components/Loading.js +2 -1
  139. package/dist/react/components/OnboardingChipField.js +5 -1
  140. package/dist/react/components/OnboardingDropdown.js +6 -4
  141. package/dist/react/components/PaletteTweaks.js +7 -6
  142. package/dist/react/components/PillButton.js +2 -1
  143. package/dist/react/components/PopoverMenu.js +2 -1
  144. package/dist/react/components/RemixIcon.js +2 -1
  145. package/dist/react/components/SecretInput.hooks.d.ts +2 -25
  146. package/dist/react/components/SecretInput.hooks.js +2 -24
  147. package/dist/react/components/SecretInput.js +5 -4
  148. package/dist/react/components/Toast.js +4 -2
  149. package/dist/react/components/TooltipLayer.js +4 -1
  150. package/dist/react/components/WorkingDirPicker.js +5 -4
  151. package/dist/react/components/remixicon-font-recovery.js +9 -1
  152. package/dist/react/components/select-popover.js +2 -2
  153. package/dist/react/hooks/useBrandFonts.js +1 -1
  154. package/dist/react/hooks/useEdgeAutoScroll.js +1 -1
  155. package/dist/react/hooks/useInView.js +9 -9
  156. package/dist/react/hooks/useResizableSplitPane.js +12 -20
  157. package/dist/react/mcp-ui/useHostSurfaceTokens.js +1 -1
  158. package/dist/renderers/annotation-canvas/react/components/AnnotationCanvas.js +7 -1
  159. package/dist/renderers/annotation-canvas/react/hooks/useAnnotationCanvas.controller.js +4 -2
  160. package/dist/renderers/annotation-canvas/react/hooks/useAnnotationCanvas.js +9 -5
  161. package/dist/renderers/annotation-canvas/rules.js +9 -13
  162. package/dist/renderers/annotation-canvas/theme.js +5 -2
  163. package/dist/renderers/renderers/index.js +1 -1
  164. package/dist/renderers/renderers/markdown.js +4 -6
  165. package/dist/theme/apply.js +7 -3
  166. package/dist/utils/class-names.d.ts +3 -0
  167. package/dist/utils/class-names.js +5 -0
  168. package/dist/utils/design-md.js +3 -3
  169. package/dist/utils/ime-composing.d.ts +1 -1
  170. package/dist/utils/ime-composing.js +1 -2
  171. package/dist/utils/localized-url.js +2 -11
  172. package/dist/utils/markdown-scroll-sync.js +1 -1
  173. package/dist/utils/omit-keys.d.ts +3 -0
  174. package/dist/utils/omit-keys.js +8 -0
  175. package/dist/utils/polygon-selection.js +1 -1
  176. package/dist/utils/sse.js +3 -0
  177. package/dist/utils/trim-chars.d.ts +13 -0
  178. package/dist/utils/trim-chars.js +30 -0
  179. package/dist/utils/unhandled-case.d.ts +10 -0
  180. package/dist/utils/unhandled-case.js +12 -0
  181. package/dist/utils/unit-bezier.js +1 -3
  182. package/dist/utils/zip.js +6 -10
  183. package/package.json +10 -9
@@ -25,7 +25,7 @@ export interface ProgressCardData {
25
25
  * 'indeterminate' (both always compute a percentage) — it's included as a
26
26
  * forward-looking capability for Jini's own Run/Agent/Tool dashboards. */
27
27
  progress: number | 'indeterminate';
28
- /** The primary step/todo checklist. */
28
+ /** The primary step/to-do checklist. */
29
29
  steps: ProgressCardItem[];
30
30
  /** An optional secondary list, e.g. "files touched" by an agent run. */
31
31
  secondaryItems?: ProgressCardItem[];
@@ -6,6 +6,11 @@ const DEFAULT_BUILT_IN = {
6
6
  builtIn: true,
7
7
  };
8
8
  let nextFakeId = 1;
9
+ function takeFakeId() {
10
+ const id = `fake-location-${nextFakeId}`;
11
+ nextFakeId += 1;
12
+ return id;
13
+ }
9
14
  /**
10
15
  * An in-memory test/demo double. Per this package's established convention
11
16
  * (see `execution/dependencies.ts`, `integrations/dependencies.ts`), ships a
@@ -30,7 +35,7 @@ export function createFakeProjectLocationsPort(options = {}) {
30
35
  saveLocations(drafts) {
31
36
  const builtIn = locations.filter((location) => location.builtIn);
32
37
  const nextExternal = drafts.map((draft) => ({
33
- id: draft.id ?? `fake-location-${nextFakeId++}`,
38
+ id: draft.id ?? takeFakeId(),
34
39
  name: draft.path.split(/[\\/]/).filter(Boolean).pop() || draft.path,
35
40
  path: draft.path,
36
41
  }));
@@ -56,7 +56,8 @@ export function ProjectLocationsTab({ port, defaultLocationId, onDefaultLocation
56
56
  return null;
57
57
  }
58
58
  })();
59
- const errorNotice = result.status === 'save-error' ? saveErrorLabel : result.status === 'scan-error' ? scanErrorLabel : null;
59
+ const errorNoticeByStatus = { 'save-error': saveErrorLabel, 'scan-error': scanErrorLabel };
60
+ const errorNotice = errorNoticeByStatus[result.status] ?? null;
60
61
  return (_jsxs("section", { className: "jini-settings-section jini-settings-project-locations", children: [_jsx("div", { className: "jini-section-head", children: _jsxs("div", { children: [_jsx("h4", { children: title }), _jsx("p", { className: "jini-hint", children: description })] }) }), builtIn ? (_jsxs("div", { className: "jini-project-location-card jini-project-location-card-built-in", children: [_jsxs("div", { className: "jini-project-location-card-main", children: [_jsx("strong", { children: builtInLabel }), _jsx("code", { children: builtIn.path })] }), _jsxs("label", { className: "jini-project-location-default-control", children: [_jsx("input", { type: "radio", name: "jini-project-location-default", checked: effectiveDefaultLocationId === builtIn.id, onChange: () => setDefaultLocationId(builtIn.id) }), _jsx("span", { children: defaultControlLabel(effectiveDefaultLocationId === builtIn.id) })] })] })) : null, _jsx("div", { className: "jini-project-location-list", children: drafts.map((draft, index) => (_jsxs("div", { className: "jini-project-location-card", children: [_jsxs("div", { className: "jini-project-location-card-main", children: [_jsx("strong", { children: locationLabel(draft.path) }), _jsx("code", { children: draft.path }), _jsx("small", { className: "jini-hint", children: rowHint })] }), draft.id ? (_jsxs("label", { className: "jini-project-location-default-control", children: [_jsx("input", { type: "radio", name: "jini-project-location-default", checked: effectiveDefaultLocationId === draft.id, onChange: () => setDefaultLocationId(draft.id) }), _jsx("span", { children: defaultControlLabel(effectiveDefaultLocationId === draft.id) })] })) : null, _jsxs("button", { type: "button", className: "jini-button jini-button-ghost", onClick: () => removeDraft(index), disabled: saving, children: [_jsx(Icon, { name: "trash", size: 13 }), _jsx("span", { children: deleteLabel })] })] }, `${draft.id ?? 'new'}-${index}`))) }), _jsxs("button", { type: "button", className: "jini-button jini-button-ghost", onClick: addFolder, disabled: saving, children: [_jsx(Icon, { name: "plus", size: 12 }), _jsx("span", { children: addFolderLabel })] }), notice ? (_jsx("p", { className: "jini-hint", role: "status", children: notice })) : null, errorNotice ? (_jsx("p", { className: "jini-hint jini-hint-error", role: "alert", children: errorNotice })) : null] }));
61
62
  }
62
63
  //# sourceMappingURL=ProjectLocationsTab.js.map
@@ -1,3 +1,4 @@
1
+ import { trimTrailingChars } from '../../utils/trim-chars.js';
1
2
  /**
2
3
  * The id meaning "the host's own default root".
3
4
  *
@@ -77,6 +78,6 @@ function normalizePathForCompare(value) {
77
78
  const trimmed = value.trim();
78
79
  if (trimmed.length <= 1)
79
80
  return trimmed;
80
- return trimmed.replace(/[\\/]+$/, '') || trimmed;
81
+ return trimTrailingChars(trimmed, '\\/') || trimmed;
81
82
  }
82
83
  //# sourceMappingURL=rules.js.map
@@ -1,4 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** The select-mode bar while selecting, otherwise the button that enters select mode. */
3
+ function ResourceBoardSelectControls({ selectMode, selectedCount, bulkDeleteBusy, onEnterSelectMode, onExitSelectMode, onBulkDelete, selectModeLabel, selectedCountLabel, deleteSelectedLabel, cancelSelectLabel, }) {
4
+ if (!selectMode) {
5
+ return (_jsx("button", { type: "button", className: "resource-board-select-toggle", onClick: onEnterSelectMode, children: selectModeLabel }));
6
+ }
7
+ return (_jsxs("div", { className: "resource-board-select-bar", role: "group", children: [_jsx("span", { className: "resource-board-select-count", children: selectedCountLabel(selectedCount) }), _jsx("button", { type: "button", disabled: selectedCount === 0 || bulkDeleteBusy, onClick: onBulkDelete, children: deleteSelectedLabel }), _jsx("button", { type: "button", onClick: onExitSelectMode, children: cancelSelectLabel })] }));
8
+ }
2
9
  /**
3
10
  * DesignsTab's toolbar: sub-tab sort pills + a create button + search +
4
11
  * select-mode/bulk-delete bar (grid view only) + the grid/kanban view
@@ -9,6 +16,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
16
  */
10
17
  export function ResourceBoardToolbar({ sortOptions, activeSort, onSortChange, sortAriaLabel, query, onQueryChange, searchPlaceholder, viewMode, onViewModeChange, viewToggleAriaLabel, gridViewLabel, kanbanViewLabel, enableBulkDelete, selectMode, selectedCount, bulkDeleteBusy, onEnterSelectMode, onExitSelectMode, onBulkDelete, selectModeLabel, selectedCountLabel, deleteSelectedLabel, cancelSelectLabel, createLabel, onCreate, }) {
11
18
  const showSelectControls = enableBulkDelete && viewMode === 'grid';
12
- return (_jsxs("div", { className: "resource-board-toolbar", children: [_jsx("div", { className: "resource-board-toolbar-left", children: sortOptions.length > 0 ? (_jsx("div", { className: "resource-board-sort-pill", role: "group", "aria-label": sortAriaLabel, children: sortOptions.map((option) => (_jsx("button", { type: "button", "aria-pressed": activeSort === option.value, className: activeSort === option.value ? 'active' : '', onClick: () => onSortChange(option.value), children: option.label }, option.value))) })) : null }), _jsxs("div", { className: "resource-board-toolbar-right", children: [onCreate && createLabel ? (_jsx("button", { type: "button", className: "resource-board-create-button", onClick: onCreate, children: createLabel })) : null, _jsx("div", { className: "resource-board-search", children: _jsx("input", { placeholder: searchPlaceholder, value: query, onChange: (event) => onQueryChange(event.target.value) }) }), showSelectControls ? (selectMode ? (_jsxs("div", { className: "resource-board-select-bar", role: "group", children: [_jsx("span", { className: "resource-board-select-count", children: selectedCountLabel(selectedCount) }), _jsx("button", { type: "button", disabled: selectedCount === 0 || bulkDeleteBusy, onClick: onBulkDelete, children: deleteSelectedLabel }), _jsx("button", { type: "button", onClick: onExitSelectMode, children: cancelSelectLabel })] })) : (_jsx("button", { type: "button", className: "resource-board-select-toggle", onClick: onEnterSelectMode, children: selectModeLabel }))) : null, _jsxs("div", { className: "resource-board-view-pill", role: "group", "aria-label": viewToggleAriaLabel, children: [_jsx("button", { "aria-pressed": viewMode === 'grid', className: viewMode === 'grid' ? 'active' : '', title: gridViewLabel, onClick: () => onViewModeChange('grid'), children: gridViewLabel }), _jsx("button", { "aria-pressed": viewMode === 'kanban', className: viewMode === 'kanban' ? 'active' : '', title: kanbanViewLabel, onClick: () => onViewModeChange('kanban'), children: kanbanViewLabel })] })] })] }));
19
+ return (_jsxs("div", { className: "resource-board-toolbar", children: [_jsx("div", { className: "resource-board-toolbar-left", children: sortOptions.length > 0 ? (_jsx("div", { className: "resource-board-sort-pill", role: "group", "aria-label": sortAriaLabel, children: sortOptions.map((option) => (_jsx("button", { type: "button", "aria-pressed": activeSort === option.value, className: activeSort === option.value ? 'active' : '', onClick: () => onSortChange(option.value), children: option.label }, option.value))) })) : null }), _jsxs("div", { className: "resource-board-toolbar-right", children: [onCreate && createLabel ? (_jsx("button", { type: "button", className: "resource-board-create-button", onClick: onCreate, children: createLabel })) : null, _jsx("div", { className: "resource-board-search", children: _jsx("input", { placeholder: searchPlaceholder, value: query, onChange: (event) => onQueryChange(event.target.value) }) }), showSelectControls ? (_jsx(ResourceBoardSelectControls, { selectMode: selectMode, selectedCount: selectedCount, bulkDeleteBusy: bulkDeleteBusy, onEnterSelectMode: onEnterSelectMode, onExitSelectMode: onExitSelectMode, onBulkDelete: onBulkDelete, selectModeLabel: selectModeLabel, selectedCountLabel: selectedCountLabel, deleteSelectedLabel: deleteSelectedLabel, cancelSelectLabel: cancelSelectLabel })) : null, _jsxs("div", { className: "resource-board-view-pill", role: "group", "aria-label": viewToggleAriaLabel, children: [_jsx("button", { "aria-pressed": viewMode === 'grid', className: viewMode === 'grid' ? 'active' : '', title: gridViewLabel, onClick: () => onViewModeChange('grid'), children: gridViewLabel }), _jsx("button", { "aria-pressed": viewMode === 'kanban', className: viewMode === 'kanban' ? 'active' : '', title: kanbanViewLabel, onClick: () => onViewModeChange('kanban'), children: kanbanViewLabel })] })] })] }));
13
20
  }
14
21
  //# sourceMappingURL=ResourceBoardToolbar.js.map
@@ -29,6 +29,7 @@ export function ResourceCardMenu({ menuActions, menuOpen, busy, moreLabel, menuC
29
29
  }
30
30
  /** Title, optional subtitle, and the status badge (only when the item has a status AND a pre-translated label). */
31
31
  export function ResourceCardMeta({ item, statusLabel, toneMap, }) {
32
- return (_jsxs("div", { className: "resource-board-card-meta", children: [_jsx("div", { className: "resource-board-card-title", title: item.title, children: item.title }), item.subtitle ? _jsx("div", { className: "resource-board-card-subtitle", children: item.subtitle }) : null, item.status && statusLabel ? _jsx(StatusPill, { status: item.status, label: statusLabel, ...(toneMap ? { toneMap } : {}) }) : null] }));
32
+ const toneMapProps = toneMap ? { toneMap } : {};
33
+ return (_jsxs("div", { className: "resource-board-card-meta", children: [_jsx("div", { className: "resource-board-card-title", title: item.title, children: item.title }), item.subtitle ? _jsx("div", { className: "resource-board-card-subtitle", children: item.subtitle }) : null, item.status && statusLabel ? _jsx(StatusPill, { status: item.status, label: statusLabel, ...toneMapProps }) : null] }));
33
34
  }
34
35
  //# sourceMappingURL=ResourceCardParts.js.map
@@ -9,6 +9,8 @@ import { ResourceRunHistoryList } from './ResourceRunHistoryList.js';
9
9
  * `ResourceRunHistoryList` sublist.
10
10
  */
11
11
  export function ResourceRowListItem({ row, busy, statusLabel, toneMap, canExpandHistory, expanded, showHistoryLabel, hideHistoryLabel, historyItems, historyLoading, historyTitleLabel, historyLoadingLabel, historyEmptyLabel, onToggleExpand, onAction, onHistoryItemAction, }) {
12
- return (_jsxs("li", { className: `resource-row-list-item${row.paused ? ' is-paused' : ''}`, "data-testid": "resource-row-list-item", children: [_jsx("div", { className: "resource-row-list-item-main", children: _jsx(ResourceRowListItemContent, { row: row, statusLabel: statusLabel, toneMap: toneMap }) }), _jsx(ResourceRowListItemActions, { row: row, busy: busy, canExpandHistory: canExpandHistory, expanded: expanded, showHistoryLabel: showHistoryLabel, hideHistoryLabel: hideHistoryLabel, onToggleExpand: onToggleExpand, onAction: onAction }), expanded ? (_jsx(ResourceRunHistoryList, { items: historyItems, loading: historyLoading, titleLabel: historyTitleLabel, loadingLabel: historyLoadingLabel, emptyLabel: historyEmptyLabel, statusLabel: statusLabel, ...(toneMap ? { toneMap } : {}), ...(onHistoryItemAction ? { onAction: onHistoryItemAction } : {}) })) : null] }));
12
+ const toneMapProps = toneMap ? { toneMap } : {};
13
+ const historyActionProps = onHistoryItemAction ? { onAction: onHistoryItemAction } : {};
14
+ return (_jsxs("li", { className: `resource-row-list-item${row.paused ? ' is-paused' : ''}`, "data-testid": "resource-row-list-item", children: [_jsx("div", { className: "resource-row-list-item-main", children: _jsx(ResourceRowListItemContent, { row: row, statusLabel: statusLabel, toneMap: toneMap }) }), _jsx(ResourceRowListItemActions, { row: row, busy: busy, canExpandHistory: canExpandHistory, expanded: expanded, showHistoryLabel: showHistoryLabel, hideHistoryLabel: hideHistoryLabel, onToggleExpand: onToggleExpand, onAction: onAction }), expanded ? (_jsx(ResourceRunHistoryList, { items: historyItems, loading: historyLoading, titleLabel: historyTitleLabel, loadingLabel: historyLoadingLabel, emptyLabel: historyEmptyLabel, statusLabel: statusLabel, ...toneMapProps, ...historyActionProps })) : null] }));
13
15
  }
14
16
  //# sourceMappingURL=ResourceRowListItem.js.map
@@ -2,7 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { StatusPill } from './StatusPill.js';
3
3
  /** Title/meta/detail lines plus the last-run `StatusPill` + label of one `ResourceRowListItem`. */
4
4
  export function ResourceRowListItemContent({ row, statusLabel, toneMap, }) {
5
- return (_jsxs("div", { className: "resource-row-list-item-content", children: [_jsx("span", { className: "resource-row-list-item-title", children: row.title }), row.metaLine ? _jsx("span", { className: "resource-row-list-item-meta", children: row.metaLine }) : null, row.detailLine ? _jsx("span", { className: "resource-row-list-item-detail", children: row.detailLine }) : null, row.lastRunStatus ? (_jsxs("span", { className: "resource-row-list-item-last-run", children: [_jsx(StatusPill, { status: row.lastRunStatus, label: statusLabel(row.lastRunStatus), ...(toneMap ? { toneMap } : {}) }), row.lastRunLabel ? _jsx("span", { children: row.lastRunLabel }) : null] })) : null] }));
5
+ const toneMapProps = toneMap ? { toneMap } : {};
6
+ return (_jsxs("div", { className: "resource-row-list-item-content", children: [_jsx("span", { className: "resource-row-list-item-title", children: row.title }), row.metaLine ? _jsx("span", { className: "resource-row-list-item-meta", children: row.metaLine }) : null, row.detailLine ? _jsx("span", { className: "resource-row-list-item-detail", children: row.detailLine }) : null, row.lastRunStatus ? (_jsxs("span", { className: "resource-row-list-item-last-run", children: [_jsx(StatusPill, { status: row.lastRunStatus, label: statusLabel(row.lastRunStatus), ...toneMapProps }), row.lastRunLabel ? _jsx("span", { children: row.lastRunLabel }) : null] })) : null] }));
6
7
  }
7
8
  /** Always-visible inline action buttons (never a kebab menu, unlike `ResourceCard`) plus the optional history-expand toggle. */
8
9
  export function ResourceRowListItemActions({ row, busy, canExpandHistory, expanded, showHistoryLabel, hideHistoryLabel, onToggleExpand, onAction, }) {
@@ -13,6 +13,6 @@ import { ResourceRowListAlerts, ResourceRowListEmpty, ResourceRowListHero, Resou
13
13
  export function ResourceRowListView(props) {
14
14
  const { loading, rows, sectionLabel, loadingLabel } = props;
15
15
  const isEmpty = !loading && rows.length === 0;
16
- return (_jsxs("section", { className: "resource-row-list", children: [_jsx(ResourceRowListHero, { ...props }), _jsx(ResourceRowListAlerts, { ...props }), _jsxs("section", { className: "resource-row-list-saved", "aria-label": sectionLabel, children: [_jsxs("div", { className: "resource-row-list-section-head", children: [_jsx("h2", { children: sectionLabel }), loading ? _jsx("span", { className: "resource-row-list-section-meta", children: loadingLabel }) : null] }), isEmpty ? _jsx(ResourceRowListEmpty, { ...props }) : rows.length > 0 ? _jsx(ResourceRowListRows, { ...props }) : null] })] }));
16
+ return (_jsxs("section", { className: "resource-row-list", children: [_jsx(ResourceRowListHero, { ...props }), _jsx(ResourceRowListAlerts, { ...props }), _jsxs("section", { className: "resource-row-list-saved", "aria-label": sectionLabel, children: [_jsxs("div", { className: "resource-row-list-section-head", children: [_jsx("h2", { children: sectionLabel }), loading ? _jsx("span", { className: "resource-row-list-section-meta", children: loadingLabel }) : null] }), isEmpty ? _jsx(ResourceRowListEmpty, { ...props }) : null, rows.length > 0 ? _jsx(ResourceRowListRows, { ...props }) : null] })] }));
17
17
  }
18
18
  //# sourceMappingURL=ResourceRowListView.js.map
@@ -76,7 +76,7 @@ export function useResourceBoard(params) {
76
76
  // same guard DesignsTab's own `menuContainerRef`-based effect uses.
77
77
  useEffect(() => {
78
78
  if (!openMenuId)
79
- return;
79
+ return undefined;
80
80
  const onKey = (event) => {
81
81
  if (event.key === 'Escape')
82
82
  setOpenMenuId(null);
@@ -23,8 +23,12 @@ export function formatTime12h(time) {
23
23
  const h = Number(m[1]);
24
24
  const mm = m[2];
25
25
  const suffix = h >= 12 ? 'PM' : 'AM';
26
- const h12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
27
- return `${h12}:${mm} ${suffix}`;
26
+ return `${to12Hour(h)}:${mm} ${suffix}`;
27
+ }
28
+ function to12Hour(h) {
29
+ if (h === 0)
30
+ return 12;
31
+ return h > 12 ? h - 12 : h;
28
32
  }
29
33
  function weekdayLongName(weekday, weekdays) {
30
34
  return weekdays.find((w) => w.value === weekday)?.long ?? weekdays[0]?.long ?? 'Sunday';
@@ -39,11 +43,13 @@ export function decomposeSchedule(schedule, weekdays = DEFAULT_WEEKDAYS) {
39
43
  if (schedule.kind === 'hourly') {
40
44
  return { kind: 'hourly', minute: String(schedule.minute).padStart(2, '0') };
41
45
  }
42
- const freq = schedule.kind === 'daily'
43
- ? 'Daily'
44
- : schedule.kind === 'weekdays'
45
- ? 'Weekdays'
46
- : weekdayLongName(schedule.weekday, weekdays);
46
+ let freq;
47
+ if (schedule.kind === 'daily')
48
+ freq = 'Daily';
49
+ else if (schedule.kind === 'weekdays')
50
+ freq = 'Weekdays';
51
+ else
52
+ freq = weekdayLongName(schedule.weekday, weekdays);
47
53
  return { kind: 'timed', freq, time: formatTime12h(schedule.time), tz: schedule.timezone };
48
54
  }
49
55
  /** Plain-English one-line summary, e.g. `'Hourly at :05'` or
@@ -2,12 +2,6 @@ import type { SketchTooltipTarget } from './types.js';
2
2
  export declare const SAVED_VISIBLE_MS = 2000;
3
3
  export declare const EXPORTED_IMAGE_MIME_TYPE = "image/png";
4
4
  export declare const SKETCH_CONTEXT_MENU_MARGIN = 8;
5
- /**
6
- * Default tool/menu tooltip targets — these selectors and `data-testid`
7
- * values are Excalidraw's own toolbar markup, not host-product content.
8
- * Host-overridable via `SketchEditorProps.tooltipTargets` for a forked or
9
- * newer Excalidraw build with different markup.
10
- */
11
5
  export declare const DEFAULT_SKETCH_TOOLTIP_TARGETS: readonly SketchTooltipTarget[];
12
6
  /**
13
7
  * Default context-menu action allow-list + order. These are Excalidraw's
@@ -7,23 +7,25 @@ export const SKETCH_CONTEXT_MENU_MARGIN = 8;
7
7
  * Host-overridable via `SketchEditorProps.tooltipTargets` for a forked or
8
8
  * newer Excalidraw build with different markup.
9
9
  */
10
+ /** Anchor the tooltip on the toolbar button's wrapping `<label>`. */
11
+ const CLOSEST_LABEL = 'closest-label';
10
12
  export const DEFAULT_SKETCH_TOOLTIP_TARGETS = [
11
13
  { selector: '[data-testid="main-menu-trigger"]', label: 'mainMenu', placement: 'bottom' },
12
- { selector: '[data-testid="toolbar-lock"]', target: 'closest-label', label: 'lock', placement: 'bottom' },
13
- { selector: '[data-testid="toolbar-hand"]', target: 'closest-label', label: 'hand', placement: 'bottom' },
14
- { selector: '[data-testid="toolbar-selection"]', target: 'closest-label', label: 'selection', placement: 'bottom' },
15
- { selector: '[data-testid="toolbar-rectangle"]', target: 'closest-label', label: 'rectangle', placement: 'bottom' },
16
- { selector: '[data-testid="toolbar-diamond"]', target: 'closest-label', label: 'diamond', placement: 'bottom' },
17
- { selector: '[data-testid="toolbar-ellipse"]', target: 'closest-label', label: 'ellipse', placement: 'bottom' },
18
- { selector: '[data-testid="toolbar-arrow"]', target: 'closest-label', label: 'arrow', placement: 'bottom' },
19
- { selector: '[data-testid="toolbar-line"]', target: 'closest-label', label: 'line', placement: 'bottom' },
20
- { selector: '[data-testid="toolbar-freedraw"]', target: 'closest-label', label: 'freedraw', placement: 'bottom' },
21
- { selector: '[data-testid="toolbar-text"]', target: 'closest-label', label: 'text', placement: 'bottom' },
22
- { selector: '[data-testid="toolbar-image"]', target: 'closest-label', label: 'image', placement: 'bottom' },
23
- { selector: '[data-testid="toolbar-eraser"]', target: 'closest-label', label: 'eraser', placement: 'bottom' },
24
- { selector: '[data-testid="toolbar-frame"]', target: 'closest-label', label: 'frame', placement: 'bottom' },
25
- { selector: '[data-testid="toolbar-embeddable"]', target: 'closest-label', label: 'embeddable', placement: 'bottom' },
26
- { selector: '[data-testid="toolbar-laser"]', target: 'closest-label', label: 'laser', placement: 'bottom' },
14
+ { selector: '[data-testid="toolbar-lock"]', target: CLOSEST_LABEL, label: 'lock', placement: 'bottom' },
15
+ { selector: '[data-testid="toolbar-hand"]', target: CLOSEST_LABEL, label: 'hand', placement: 'bottom' },
16
+ { selector: '[data-testid="toolbar-selection"]', target: CLOSEST_LABEL, label: 'selection', placement: 'bottom' },
17
+ { selector: '[data-testid="toolbar-rectangle"]', target: CLOSEST_LABEL, label: 'rectangle', placement: 'bottom' },
18
+ { selector: '[data-testid="toolbar-diamond"]', target: CLOSEST_LABEL, label: 'diamond', placement: 'bottom' },
19
+ { selector: '[data-testid="toolbar-ellipse"]', target: CLOSEST_LABEL, label: 'ellipse', placement: 'bottom' },
20
+ { selector: '[data-testid="toolbar-arrow"]', target: CLOSEST_LABEL, label: 'arrow', placement: 'bottom' },
21
+ { selector: '[data-testid="toolbar-line"]', target: CLOSEST_LABEL, label: 'line', placement: 'bottom' },
22
+ { selector: '[data-testid="toolbar-freedraw"]', target: CLOSEST_LABEL, label: 'freedraw', placement: 'bottom' },
23
+ { selector: '[data-testid="toolbar-text"]', target: CLOSEST_LABEL, label: 'text', placement: 'bottom' },
24
+ { selector: '[data-testid="toolbar-image"]', target: CLOSEST_LABEL, label: 'image', placement: 'bottom' },
25
+ { selector: '[data-testid="toolbar-eraser"]', target: CLOSEST_LABEL, label: 'eraser', placement: 'bottom' },
26
+ { selector: '[data-testid="toolbar-frame"]', target: CLOSEST_LABEL, label: 'frame', placement: 'bottom' },
27
+ { selector: '[data-testid="toolbar-embeddable"]', target: CLOSEST_LABEL, label: 'embeddable', placement: 'bottom' },
28
+ { selector: '[data-testid="toolbar-laser"]', target: CLOSEST_LABEL, label: 'laser', placement: 'bottom' },
27
29
  { selector: '.App-toolbar__extra-tools-trigger', label: 'moreTools', placement: 'bottom' },
28
30
  ];
29
31
  /**
@@ -109,18 +109,15 @@ function hasRecognizedContextMenuAction(menu, recognized) {
109
109
  function pruneContextMenuItems(menu, allowed) {
110
110
  const byAction = new Map();
111
111
  for (const child of Array.from(menu.children)) {
112
- if (child instanceof HTMLHRElement) {
112
+ if (child instanceof HTMLHRElement)
113
113
  child.remove();
114
- continue;
115
- }
116
114
  if (!(child instanceof HTMLLIElement))
117
115
  continue;
118
116
  const actionName = child.getAttribute('data-testid') ?? '';
119
- if (!allowed.has(actionName)) {
117
+ if (allowed.has(actionName))
118
+ byAction.set(actionName, child);
119
+ else
120
120
  child.remove();
121
- continue;
122
- }
123
- byAction.set(actionName, child);
124
121
  }
125
122
  return byAction;
126
123
  }
@@ -2,7 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Icon } from '../../../../react/components/Icon.js';
3
3
  /** The composed `<MainMenu>` Excalidraw renders inside its own menu popover. */
4
4
  export function SketchMainMenu({ MainMenu, t, saving, showSaved, canSave, onSave, exportAvailable, exporting, canExport, onExportImage, canClear, onClear, }) {
5
- const saveLabel = saving ? t('Saving…') : showSaved ? t('Saved') : t('Save');
5
+ const idleSaveLabel = showSaved ? t('Saved') : t('Save');
6
+ const saveLabel = saving ? t('Saving…') : idleSaveLabel;
6
7
  const exportLabel = exporting ? t('Exporting image…') : t('Export image');
7
8
  return (_jsxs(MainMenu, { children: [_jsx(MainMenu.Item, { "data-testid": "sketch-menu-save", icon: showSaved ? _jsx(Icon, { name: "check", size: 16 }) : undefined, onClick: onSave, disabled: saving || !canSave, "aria-label": saveLabel, children: saveLabel }), exportAvailable ? (_jsx(MainMenu.Item, { "data-testid": "sketch-menu-export-image", icon: _jsx(Icon, { name: "download", size: 16 }), onClick: onExportImage, disabled: exporting || !canExport, "aria-label": exportLabel, children: exportLabel })) : null, _jsx(MainMenu.Separator, {}), _jsx(MainMenu.DefaultItems.SearchMenu, {}), _jsx(MainMenu.DefaultItems.Help, {}), _jsx(MainMenu.Item, { "data-testid": "sketch-menu-clear", icon: _jsx(Icon, { name: "trash", size: 16 }), onClick: onClear, disabled: !canClear, children: t('Clear canvas') }), _jsx(MainMenu.Separator, {}), _jsx(MainMenu.DefaultItems.ChangeCanvasBackground, {})] }));
8
9
  }
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Icon } from '../../../../react/components/Icon.js';
3
+ const STATE_ICONS = { saving: 'spinner', dirty: 'alert-triangle' };
3
4
  /** The small save-state indicator rendered in Excalidraw's own top-right UI slot. */
4
5
  export function SketchSaveStateBadge({ state, label }) {
5
- return (_jsx("div", { className: "sketch-excalidraw-actions", "data-testid": "sketch-save-state", children: _jsxs("span", { className: `sketch-save-state is-${state}`, role: "status", "aria-live": "polite", children: [_jsx(Icon, { name: state === 'saving' ? 'spinner' : state === 'dirty' ? 'alert-triangle' : 'check', size: 12, className: state === 'saving' ? 'icon-spin' : undefined }), _jsx("span", { children: label })] }) }));
6
+ return (_jsx("div", { className: "sketch-excalidraw-actions", "data-testid": "sketch-save-state", children: _jsxs("span", { className: `sketch-save-state is-${state}`, role: "status", "aria-live": "polite", children: [_jsx(Icon, { name: STATE_ICONS[state] ?? 'check', size: 12, className: state === 'saving' ? 'icon-spin' : undefined }), _jsx("span", { children: label })] }) }));
6
7
  }
7
8
  //# sourceMappingURL=SketchSaveStateBadge.js.map
@@ -16,7 +16,7 @@ export function useSketchDomEnhancements({ containerRef, t, domTextOverrides, to
16
16
  useEffect(() => {
17
17
  const root = containerRef.current;
18
18
  if (!root)
19
- return;
19
+ return undefined;
20
20
  let frame = null;
21
21
  const applyEnhancements = () => {
22
22
  frame = null;
@@ -136,12 +136,15 @@ export function exportedImageFileName(fileName, sourceExtension) {
136
136
  const slash = fileName.lastIndexOf('/');
137
137
  const baseName = slash >= 0 ? fileName.slice(slash + 1) : fileName;
138
138
  const stem = typeof sourceExtension === 'string'
139
- ? baseName.toLowerCase().endsWith(sourceExtension.toLowerCase())
140
- ? baseName.slice(0, baseName.length - sourceExtension.length)
141
- : baseName
139
+ ? stripKnownExtension(baseName, sourceExtension)
142
140
  : baseName.replace(/\.[^./]+$/, '');
143
141
  return `${stem || 'sketch'}.png`;
144
142
  }
143
+ function stripKnownExtension(baseName, extension) {
144
+ return baseName.toLowerCase().endsWith(extension.toLowerCase())
145
+ ? baseName.slice(0, baseName.length - extension.length)
146
+ : baseName;
147
+ }
145
148
  export function exportedImageResultFileName(result, fallback) {
146
149
  if (result && typeof result === 'object' && typeof result.fileName === 'string') {
147
150
  return result.fileName;
@@ -197,15 +200,13 @@ export function buildSketchTooltipLabels(t) {
197
200
  * in `overrides` matches (caller should leave the node untouched).
198
201
  */
199
202
  export function translateDomTextValue(value, overrides) {
200
- // Every group in this pattern is quantified with `*` (zero-or-more), so it
201
- // matches every string input, including `''` — `.match()` can never return
202
- // null here, and each capture group is always defined (possibly empty).
203
- // The nullable return/`| undefined` group types are just `RegExp.match`'s
204
- // and `RegExpMatchArray`'s general signatures, not a real runtime path.
205
- const match = value.match(/^(\s*)([\s\S]*?)(\s*)$/);
206
- const leading = match[1];
207
- const core = match[2];
208
- const trailing = match[3];
203
+ // Split into leading whitespace, core, trailing whitespace (an all-whitespace
204
+ // value is all leading). `trimStart`/`trimEnd` strip exactly regex `\s`.
205
+ const coreStart = value.length - value.trimStart().length;
206
+ const leading = value.slice(0, coreStart);
207
+ const rest = value.slice(coreStart);
208
+ const core = rest.trimEnd();
209
+ const trailing = rest.slice(core.length);
209
210
  const normalized = core.replace(/\s+/g, ' ').trim();
210
211
  if (!normalized)
211
212
  return null;
@@ -1,3 +1,4 @@
1
+ import { omitKeys } from '../../utils/omit-keys.js';
1
2
  const DEFAULT_FAKE_SKILLS = [
2
3
  {
3
4
  id: 'skill-summarize',
@@ -10,9 +11,13 @@ const DEFAULT_FAKE_SKILLS = [
10
11
  },
11
12
  ];
12
13
  let nextFakeId = 1;
14
+ function takeFakeSkillId() {
15
+ const id = `fake-skill-${nextFakeId}`;
16
+ nextFakeId += 1;
17
+ return id;
18
+ }
13
19
  function toSummary(detail) {
14
- const { body: _body, ...summary } = detail;
15
- return summary;
20
+ return omitKeys(detail, ['body']);
16
21
  }
17
22
  /**
18
23
  * An in-memory test/demo double. Per this package's established convention
@@ -39,7 +44,7 @@ export function createFakeSkillsPort(options = {}) {
39
44
  return delay([...(files.get(id) ?? [])]);
40
45
  },
41
46
  createSkill(payload) {
42
- const id = `fake-skill-${nextFakeId++}`;
47
+ const id = takeFakeSkillId();
43
48
  const detail = {
44
49
  id,
45
50
  name: payload.name,
@@ -58,7 +63,7 @@ export function createFakeSkillsPort(options = {}) {
58
63
  return Promise.reject(new Error(`Unknown skill: ${id}`));
59
64
  // A built-in edit writes a NEW user-owned shadow copy, same as the
60
65
  // origin — the requested id is never mutated in place.
61
- const targetId = existing.source === 'user' ? id : `fake-skill-${nextFakeId++}`;
66
+ const targetId = existing.source === 'user' ? id : takeFakeSkillId();
62
67
  const detail = {
63
68
  ...existing,
64
69
  id: targetId,
@@ -18,7 +18,8 @@ export function SkillRow({ skill, locale, enabled, expanded, editing, body, body
18
18
  // Built-in skills are never edited in place — "editing" one creates a
19
19
  // user-owned override, so every edit affordance says so.
20
20
  const editActionLabel = builtIn ? l.overrideCreateLabel : l.editLabel;
21
- return (_jsxs("div", { className: skillRowClassName({ enabled, expanded }), "data-testid": `skill-row-${skill.id}`, children: [_jsxs("div", { className: "jini-skills-row-head", children: [_jsx(SkillRowSummaryButton, { skill: skill, locale: locale, expanded: expanded, onToggleExpanded: onToggleExpanded, labels: l, agentHandle: agentHandle }), _jsxs("div", { className: "jini-skills-row-actions", children: [isDeletableSkill(skill) && confirmDelete ? (_jsx(SkillRowDeleteConfirm, { onCommitDelete: onCommitDelete, onCancelDelete: onCancelDelete, labels: l, agentHandle: agentHandle })) : (_jsx(SkillRowEditDeleteActions, { skill: skill, editActionLabel: editActionLabel, onStartEdit: onStartEdit, onArmDelete: onArmDelete, labels: l, agentHandle: agentHandle })), _jsxs("label", { className: "toggle-switch toggle-switch-sm jini-skills-row-enable", title: l.enableToggleLabel, children: [_jsx("input", { type: "checkbox", checked: enabled, onChange: (event) => onToggleEnabled(event.target.checked), "aria-label": l.enableToggleLabel, ...agentHandleProps({}, { base: agentHandle, action: 'enabled', role: 'checkbox', label: l.enableToggleLabel }) }), _jsx("span", { className: "toggle-slider" })] })] })] }), confirmBuiltInEdit ? (_jsx(SkillRowBuiltInEditWarning, { onCancelBuiltInEdit: onCancelBuiltInEdit, onConfirmBuiltInEdit: onConfirmBuiltInEdit, labels: l, agentHandle: agentHandle })) : null, expanded && !editing ? (_jsx(SkillRowDetail, { body: body, bodyLoading: bodyLoading, files: files, filesLoading: filesLoading, labels: l })) : null, editing && draft ? (_jsx(SkillDraftForm, { heading: editActionLabel, subheading: skill.id, draft: draft, onDraftChange: onDraftChange, error: draftError, saving: draftSaving, isEdit: true, isBuiltInOverride: builtIn, onCancel: onCancelEdit, onSubmit: onSubmitEdit, labels: labels, ...(agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'edit-form' }) } : {}) })) : null] }));
21
+ const editFormHandleProps = agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'edit-form' }) } : {};
22
+ return (_jsxs("div", { className: skillRowClassName({ enabled, expanded }), "data-testid": `skill-row-${skill.id}`, children: [_jsxs("div", { className: "jini-skills-row-head", children: [_jsx(SkillRowSummaryButton, { skill: skill, locale: locale, expanded: expanded, onToggleExpanded: onToggleExpanded, labels: l, agentHandle: agentHandle }), _jsxs("div", { className: "jini-skills-row-actions", children: [isDeletableSkill(skill) && confirmDelete ? (_jsx(SkillRowDeleteConfirm, { onCommitDelete: onCommitDelete, onCancelDelete: onCancelDelete, labels: l, agentHandle: agentHandle })) : (_jsx(SkillRowEditDeleteActions, { skill: skill, editActionLabel: editActionLabel, onStartEdit: onStartEdit, onArmDelete: onArmDelete, labels: l, agentHandle: agentHandle })), _jsxs("label", { className: "toggle-switch toggle-switch-sm jini-skills-row-enable", title: l.enableToggleLabel, children: [_jsx("input", { type: "checkbox", checked: enabled, onChange: (event) => onToggleEnabled(event.target.checked), "aria-label": l.enableToggleLabel, ...agentHandleProps({}, { base: agentHandle, action: 'enabled', role: 'checkbox', label: l.enableToggleLabel }) }), _jsx("span", { className: "toggle-slider" })] })] })] }), confirmBuiltInEdit ? (_jsx(SkillRowBuiltInEditWarning, { onCancelBuiltInEdit: onCancelBuiltInEdit, onConfirmBuiltInEdit: onConfirmBuiltInEdit, labels: l, agentHandle: agentHandle })) : null, expanded && !editing ? (_jsx(SkillRowDetail, { body: body, bodyLoading: bodyLoading, files: files, filesLoading: filesLoading, labels: l })) : null, editing && draft ? (_jsx(SkillDraftForm, { heading: editActionLabel, subheading: skill.id, draft: draft, onDraftChange: onDraftChange, error: draftError, saving: draftSaving, isEdit: true, isBuiltInOverride: builtIn, onCancel: onCancelEdit, onSubmit: onSubmitEdit, labels: labels, ...editFormHandleProps })) : null] }));
22
23
  }
23
24
  function skillRowClassName({ enabled, expanded }) {
24
25
  return `jini-skills-row${enabled ? '' : ' jini-skills-row-disabled'}${expanded ? ' jini-skills-row-expanded' : ''}`;
@@ -21,7 +21,8 @@ export function SkillsTab({ port, disabledSkillIds, onToggleEnabled, locale = 'e
21
21
  const tab = useSkillsTab({ port, locale });
22
22
  const l = resolveLabels({ defaults: DEFAULT_SKILLS_TAB_LABELS, t }, { labels });
23
23
  const loadErrorLabel = labels?.loadErrorLabel ?? t('Could not load skills: {error}', { error: tab.loadError ?? '' });
24
- return (_jsxs("section", { className: "jini-settings-section jini-settings-skills", children: [_jsx(SkillsTabToolbar, { tab: tab, labels: l, agentHandle: agentHandle }), tab.loadError ? (_jsx("div", { className: "jini-empty-card jini-empty-card-error", children: _jsx("strong", { children: loadErrorLabel }) })) : null, tab.creating ? (_jsx(SkillDraftForm, { heading: l.newSkillLabel, draft: tab.draft, onDraftChange: tab.setDraft, error: tab.draftError, saving: tab.draftSaving, isEdit: false, onCancel: tab.cancelDraft, onSubmit: tab.submitDraft, labels: labels, ...(agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'create-form' }) } : {}) })) : null, !tab.loading && tab.filteredSkills.length === 0 ? (_jsx("div", { className: "jini-empty-card", children: _jsx("strong", { children: l.noResultsLabel }) })) : (_jsx(SkillsTabRows, { tab: tab, disabledSkillIds: disabledSkillIds, onToggleEnabled: onToggleEnabled, locale: locale, labels: labels, agentHandle: agentHandle }))] }));
24
+ const createFormHandleProps = agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'create-form' }) } : {};
25
+ return (_jsxs("section", { className: "jini-settings-section jini-settings-skills", children: [_jsx(SkillsTabToolbar, { tab: tab, labels: l, agentHandle: agentHandle }), tab.loadError ? (_jsx("div", { className: "jini-empty-card jini-empty-card-error", children: _jsx("strong", { children: loadErrorLabel }) })) : null, tab.creating ? (_jsx(SkillDraftForm, { heading: l.newSkillLabel, draft: tab.draft, onDraftChange: tab.setDraft, error: tab.draftError, saving: tab.draftSaving, isEdit: false, onCancel: tab.cancelDraft, onSubmit: tab.submitDraft, labels: labels, ...createFormHandleProps })) : null, !tab.loading && tab.filteredSkills.length === 0 ? (_jsx("div", { className: "jini-empty-card", children: _jsx("strong", { children: l.noResultsLabel }) })) : (_jsx(SkillsTabRows, { tab: tab, disabledSkillIds: disabledSkillIds, onToggleEnabled: onToggleEnabled, locale: locale, labels: labels, agentHandle: agentHandle }))] }));
25
26
  }
26
27
  /** Search box, "New skill" button, and the source/mode/(category) selects. */
27
28
  function SkillsTabToolbar({ tab, labels: l, agentHandle, }) {
@@ -146,16 +146,16 @@ const modeValue = (skill) => skill.mode;
146
146
  const categoryValue = (skill) => typeof skill.category === 'string' && skill.category ? skill.category : null;
147
147
  /** The data-derived pills (`mode`, `category`): option SET from every skill,
148
148
  * COUNTS from `matching` — see `skillFilterOptions`. */
149
- function valueFilterOptions({ skills, matching, valueOf, }) {
149
+ function valueFilterOptions({ skills, matching, valueOf: valueFor, }) {
150
150
  const values = new Set();
151
151
  for (const skill of skills) {
152
- const value = valueOf(skill);
152
+ const value = valueFor(skill);
153
153
  if (value !== null)
154
154
  values.add(value);
155
155
  }
156
156
  const counts = new Map();
157
157
  for (const skill of matching) {
158
- const value = valueOf(skill);
158
+ const value = valueFor(skill);
159
159
  if (value === null)
160
160
  continue;
161
161
  counts.set(value, (counts.get(value) ?? 0) + 1);
@@ -39,6 +39,7 @@
39
39
  * why cards are keyed by id rather than list position.
40
40
  */
41
41
  import { agentHandle, buildAgentListHandles } from '@jini-ai/agentic';
42
+ import { trimChars } from '../../utils/trim-chars.js';
42
43
  /** Segments of a derived handle: `<base>-field-<key>` and `<base>-<action>` share one joiner. */
43
44
  const HANDLE_SEPARATOR = '-';
44
45
  /** The namespace every host-described field sits under — see this module's doc for why. */
@@ -65,11 +66,11 @@ const LIST_ITEM_NAMESPACE = 'item';
65
66
  * @complexity O(n) in `segment.length`.
66
67
  */
67
68
  function sanitizeHandleSegment(segment) {
68
- return segment
69
+ const separated = segment
69
70
  .replace(/([a-z0-9])([A-Z])/g, `$1${HANDLE_SEPARATOR}$2`)
70
71
  .toLowerCase()
71
- .replace(/[^a-z0-9]+/g, HANDLE_SEPARATOR)
72
- .replace(/^-+|-+$/g, '');
72
+ .replace(/[^a-z0-9]+/g, HANDLE_SEPARATOR);
73
+ return trimChars(separated, '-');
73
74
  }
74
75
  /**
75
76
  * The handle for one action or sub-control published under `base`.
@@ -122,7 +122,9 @@ function useItemCardEdit({ source, fieldSpecs, onUpdate }) {
122
122
  /** The expanded body: the Edit/Save/Cancel toggle, the editable or read-only fields, and the test control. */
123
123
  function ItemCardBody(props) {
124
124
  const { capabilities, testing, testResult, onTest, agentHandle, edit } = props;
125
- return (_jsxs("div", { className: "source-config-item-card-body", children: [capabilities.canUpdate ? _jsx(ItemCardEditToggle, { ...props, edit: edit }) : null, edit.editing ? _jsx(ItemCardEditFields, { ...props, edit: edit }) : _jsx(ItemCardReadOnlyFields, { ...props }), capabilities.canTest ? (_jsx(SourceConfigTestControl, { running: testing, disabled: isAnyActionPending(props), onTest: onTest, ...(testResult ? { result: testResult } : {}), ...(agentHandle ? { agentHandle: sourceConfigActionHandle(agentHandle, 'test') } : {}) })) : null] }));
125
+ const testResultProps = testResult ? { result: testResult } : {};
126
+ const testHandleProps = agentHandle ? { agentHandle: sourceConfigActionHandle(agentHandle, 'test') } : {};
127
+ return (_jsxs("div", { className: "source-config-item-card-body", children: [capabilities.canUpdate ? _jsx(ItemCardEditToggle, { ...props, edit: edit }) : null, edit.editing ? _jsx(ItemCardEditFields, { ...props, edit: edit }) : _jsx(ItemCardReadOnlyFields, { ...props }), capabilities.canTest ? (_jsx(SourceConfigTestControl, { running: testing, disabled: isAnyActionPending(props), onTest: onTest, ...testResultProps, ...testHandleProps })) : null] }));
126
128
  }
127
129
  function ItemCardEditToggle(props) {
128
130
  const { updating, agentHandle, edit } = props;
@@ -138,9 +138,11 @@ export function useActiveTabInView({ activeId }, _optional = {}) {
138
138
  const tabBox = tab.getBoundingClientRect();
139
139
  // A host's end padding (e.g. room for an edge fade) counts as off-screen.
140
140
  const visibleRight = stripBox.right - (parseFloat(getComputedStyle(strip).paddingRight) || 0);
141
- if (tabBox.left < stripBox.left)
141
+ if (tabBox.left < stripBox.left) {
142
142
  strip.scrollLeft += tabBox.left - stripBox.left;
143
- else if (tabBox.right > visibleRight)
143
+ return;
144
+ }
145
+ if (tabBox.right > visibleRight)
144
146
  strip.scrollLeft += tabBox.right - visibleRight;
145
147
  }
146
148
  scrollActiveTabIntoView();
@@ -11,6 +11,9 @@ export function VersionPreviewFrame({ previewDocument, frameReady, onFrameLoad,
11
11
  const [frameRef, canvasSize] = usePreviewCanvasSize();
12
12
  const preset = findViewportPreset(viewportPresets, viewport);
13
13
  const scale = effectivePreviewScale(preset, 1, canvasSize, DEFAULT_PREVIEW_CANVAS_PADDING);
14
- return (_jsx("div", { className: "jini-version-preview", ref: frameRef, children: error ? (_jsx("div", { className: "jini-viewer-empty", role: "alert", children: error })) : (_jsxs(_Fragment, { children: [previewDocument ? (_jsx("div", { className: "jini-preview-viewport", style: previewViewportStyle(preset, scale, 1), children: _jsx("div", { className: "jini-preview-frame-clip", children: _jsx("div", { style: previewScaleShellStyle(preset, 1), children: _jsx("iframe", { title: title, sandbox: "allow-scripts allow-downloads", srcDoc: previewDocument, onLoad: onFrameLoad }) }) }) })) : !loading && !loadingContent ? (_jsx("div", { className: "jini-viewer-empty", children: t('No preview available.') })) : null, loading || loadingContent || (previewDocument && !frameReady) ? (_jsx("div", { className: "jini-version-preview-overlay", role: "status", "aria-label": t('Loading preview…'), children: _jsx("span", { className: "jini-version-preview-spinner", "aria-hidden": "true" }) })) : null] })) }));
14
+ const showEmpty = !previewDocument && !loading && !loadingContent;
15
+ const showLoading = loading || loadingContent || (previewDocument && !frameReady);
16
+ const emptyState = showEmpty ? _jsx("div", { className: "jini-viewer-empty", children: t('No preview available.') }) : null;
17
+ return (_jsx("div", { className: "jini-version-preview", ref: frameRef, children: error ? (_jsx("div", { className: "jini-viewer-empty", role: "alert", children: error })) : (_jsxs(_Fragment, { children: [previewDocument ? (_jsx("div", { className: "jini-preview-viewport", style: previewViewportStyle(preset, scale, 1), children: _jsx("div", { className: "jini-preview-frame-clip", children: _jsx("div", { style: previewScaleShellStyle(preset, 1), children: _jsx("iframe", { title: title, sandbox: "allow-scripts allow-downloads", srcDoc: previewDocument, onLoad: onFrameLoad }) }) }) })) : null, emptyState, showLoading ? (_jsx("div", { className: "jini-version-preview-overlay", role: "status", "aria-label": t('Loading preview…'), children: _jsx("span", { className: "jini-version-preview-spinner", "aria-hidden": "true" }) })) : null] })) }));
15
18
  }
16
19
  //# sourceMappingURL=VersionPreviewFrame.js.map
@@ -4,11 +4,13 @@ import { useT } from '../../../i18n/index.js';
4
4
  * independent of whatever the preview pane is doing. */
5
5
  export function VersionSidebar({ countLabel, search, onSearchChange, showSearch, loading, versions, visibleVersions, selectedVersionId, onSelect, onPrefetch, formatDate, sourceLabel, sourceClassName, restoredFrom, }) {
6
6
  const t = useT();
7
- return (_jsxs("div", { className: "jini-version-sidebar", children: [_jsx("div", { className: "jini-version-sidebar-head", children: _jsx("span", { className: "jini-version-count", children: countLabel }) }), showSearch ? (_jsxs("div", { className: "jini-version-search", children: [_jsx("input", { type: "search", value: search, onChange: (event) => onSearchChange(event.target.value), placeholder: t('Search…'), "aria-label": t('Search…') }), search ? (_jsx("button", { type: "button", className: "jini-version-search-clear", "aria-label": t('Clear'), onClick: () => onSearchChange(''), children: t('Clear') })) : null] })) : null, _jsx("div", { className: "jini-version-list", role: "listbox", "aria-label": t('Version history'), children: loading ? (_jsx("div", { className: "jini-version-skeleton-list", role: "status", "aria-label": t('Loading versions…'), children: [0, 1, 2, 3].map((row) => (_jsx("div", { className: "jini-version-skeleton-item", "aria-hidden": "true" }, row))) })) : versions.length === 0 ? (_jsx("div", { className: "jini-version-empty", children: t('No versions yet.') })) : visibleVersions.length === 0 ? (_jsx("div", { className: "jini-version-empty", children: t('No results for “{query}”.', { query: search.trim() }) })) : (visibleVersions.map((version) => {
8
- const selected = version.id === selectedVersionId;
9
- const itemRestoredFrom = restoredFrom(version);
10
- const prefetch = () => onPrefetch(version.id);
11
- return (_jsxs("button", { type: "button", className: `jini-version-item${selected ? ' active' : ''}`, role: "option", "aria-selected": selected, onClick: () => onSelect(version.id), onMouseEnter: prefetch, onFocus: prefetch, children: [_jsxs("span", { className: "jini-version-item-top", children: [version.current ? _jsx("span", { className: "jini-version-current-badge", children: t('Current') }) : null, _jsx("span", { className: `jini-version-source-badge ${sourceClassName(version)}`, children: sourceLabel(version) }), _jsx("span", { className: "jini-version-time", children: formatDate(version) })] }), _jsx("span", { className: "jini-version-item-title", children: version.prompt || version.label || t('Version {version}', { version: version.version }) }), _jsxs("span", { className: "jini-version-item-meta", children: [t('Version {version}', { version: version.version }), itemRestoredFrom ? (_jsx("span", { className: "jini-version-item-restored", children: t('Restored from v{version}', { version: itemRestoredFrom.version }) })) : null] })] }, version.id));
12
- })) })] }));
7
+ const noResultsMessage = visibleVersions.length === 0 ? t('No results for “{query}”.', { query: search.trim() }) : null;
8
+ const emptyMessage = versions.length === 0 ? t('No versions yet.') : noResultsMessage;
9
+ return (_jsxs("div", { className: "jini-version-sidebar", children: [_jsx("div", { className: "jini-version-sidebar-head", children: _jsx("span", { className: "jini-version-count", children: countLabel }) }), showSearch ? (_jsxs("div", { className: "jini-version-search", children: [_jsx("input", { type: "search", value: search, onChange: (event) => onSearchChange(event.target.value), placeholder: t('Search…'), "aria-label": t('Search…') }), search ? (_jsx("button", { type: "button", className: "jini-version-search-clear", "aria-label": t('Clear'), onClick: () => onSearchChange(''), children: t('Clear') })) : null] })) : null, _jsxs("div", { className: "jini-version-list", role: "listbox", "aria-label": t('Version history'), children: [loading ? (_jsx("div", { className: "jini-version-skeleton-list", role: "status", "aria-label": t('Loading versions…'), children: [0, 1, 2, 3].map((row) => (_jsx("div", { className: "jini-version-skeleton-item", "aria-hidden": "true" }, row))) })) : null, !loading && emptyMessage ? _jsx("div", { className: "jini-version-empty", children: emptyMessage }) : null, !loading && !emptyMessage ? (visibleVersions.map((version) => {
10
+ const selected = version.id === selectedVersionId;
11
+ const itemRestoredFrom = restoredFrom(version);
12
+ const prefetch = () => onPrefetch(version.id);
13
+ return (_jsxs("button", { type: "button", className: `jini-version-item${selected ? ' active' : ''}`, role: "option", "aria-selected": selected, onClick: () => onSelect(version.id), onMouseEnter: prefetch, onFocus: prefetch, children: [_jsxs("span", { className: "jini-version-item-top", children: [version.current ? _jsx("span", { className: "jini-version-current-badge", children: t('Current') }) : null, _jsx("span", { className: `jini-version-source-badge ${sourceClassName(version)}`, children: sourceLabel(version) }), _jsx("span", { className: "jini-version-time", children: formatDate(version) })] }), _jsx("span", { className: "jini-version-item-title", children: version.prompt || version.label || t('Version {version}', { version: version.version }) }), _jsxs("span", { className: "jini-version-item-meta", children: [t('Version {version}', { version: version.version }), itemRestoredFrom ? (_jsx("span", { className: "jini-version-item-restored", children: t('Restored from v{version}', { version: itemRestoredFrom.version }) })) : null] })] }, version.id));
14
+ })) : null] })] }));
13
15
  }
14
16
  //# sourceMappingURL=VersionSidebar.js.map
@@ -16,7 +16,7 @@ export function usePreviewCanvasSize() {
16
16
  // dead-branch-gets-refactored-away rule.
17
17
  const el = ref.current;
18
18
  if (!el)
19
- return;
19
+ return undefined;
20
20
  const measure = () => {
21
21
  const rect = el.getBoundingClientRect();
22
22
  setSize({ width: rect.width, height: rect.height });
@@ -108,10 +108,8 @@ export function useVersionManager(dependencies, options) {
108
108
  setVersions(nextVersions);
109
109
  const currentVersion = nextVersions.find((version) => version.current);
110
110
  seedCurrentVersionContent({ currentVersion, currentContent, contentCache: contentCacheRef.current });
111
- const nextSelected = (preferredId ? nextVersions.find((version) => version.id === preferredId) : null) ??
112
- currentVersion ??
113
- nextVersions[0] ??
114
- null;
111
+ const preferredVersion = preferredId ? nextVersions.find((version) => version.id === preferredId) : null;
112
+ const nextSelected = preferredVersion ?? currentVersion ?? nextVersions[0] ?? null;
115
113
  setSelectedId(nextSelected?.id ?? null);
116
114
  setLoading(false);
117
115
  }, [port, fileRef, currentContent]);
@@ -125,7 +123,7 @@ export function useVersionManager(dependencies, options) {
125
123
  if (!selectedId) {
126
124
  setSelectedContent(null);
127
125
  setSelectedContentVersionId(null);
128
- return;
126
+ return undefined;
129
127
  }
130
128
  const cached = contentCacheRef.current.get(selectedId);
131
129
  if (cached !== undefined) {
@@ -138,7 +136,7 @@ export function useVersionManager(dependencies, options) {
138
136
  else {
139
137
  setError(null);
140
138
  }
141
- return;
139
+ return undefined;
142
140
  }
143
141
  // Cache miss: keep the previous preview mounted (don't clear
144
142
  // `selectedContent`) so switching versions never blanks to empty while
@@ -177,7 +175,7 @@ export function useVersionManager(dependencies, options) {
177
175
  // is ever missed.
178
176
  useEffect(() => {
179
177
  if (!previewDocument || loadedPreviewDocument === previewDocument)
180
- return;
178
+ return undefined;
181
179
  const fallback = setTimeout(() => setLoadedPreviewDocument(previewDocument), PREVIEW_LOAD_FALLBACK_MS);
182
180
  return () => clearTimeout(fallback);
183
181
  }, [previewDocument, loadedPreviewDocument]);