@jini-ai/ui 0.5.5 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/core/theme/validation.js +40 -38
  3. package/dist/features/a2ui/renderer.js +15 -13
  4. package/dist/features/about/react/components/AboutTab.js +64 -34
  5. package/dist/features/about/rules.d.ts +2 -2
  6. package/dist/features/about/rules.js +156 -129
  7. package/dist/features/admin-widgets/components/ImagePreviewModal.d.ts +4 -3
  8. package/dist/features/admin-widgets/components/ImagePreviewModal.js +6 -10
  9. package/dist/features/admin-widgets/components/InfoTip.d.ts +3 -2
  10. package/dist/features/admin-widgets/components/InfoTip.hooks.js +2 -0
  11. package/dist/features/admin-widgets/components/InfoTip.js +34 -7
  12. package/dist/features/admin-widgets/components/InfoTip.rules.d.ts +13 -0
  13. package/dist/features/admin-widgets/components/InfoTip.rules.js +43 -10
  14. package/dist/features/admin-widgets/components/Lightbox/Lightbox.d.ts +30 -0
  15. package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.d.ts +40 -0
  16. package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.js +128 -0
  17. package/dist/features/admin-widgets/components/Lightbox/Lightbox.js +18 -0
  18. package/dist/features/admin-widgets/components/MiddleTruncate.d.ts +14 -0
  19. package/dist/features/admin-widgets/components/MiddleTruncate.js +12 -0
  20. package/dist/features/admin-widgets/components/MiddleTruncate.rules.d.ts +20 -0
  21. package/dist/features/admin-widgets/components/MiddleTruncate.rules.js +21 -0
  22. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.d.ts +18 -0
  23. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.d.ts +16 -0
  24. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.js +45 -0
  25. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.js +18 -0
  26. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.rules.d.ts +32 -0
  27. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.rules.js +26 -0
  28. package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.hooks.js +29 -14
  29. package/dist/features/admin-widgets/index.d.ts +14 -2
  30. package/dist/features/admin-widgets/index.js +7 -1
  31. package/dist/features/admin-widgets/styles/admin-widgets.css +1 -0
  32. package/dist/features/asset-grid/react/components/AssetCard.js +7 -1
  33. package/dist/features/asset-grid/react/components/AssetGrid.d.ts +1 -1
  34. package/dist/features/asset-grid/react/components/AssetGrid.js +38 -18
  35. package/dist/features/asset-grid/react/components/useAssetGridFilters.d.ts +21 -0
  36. package/dist/features/asset-grid/react/components/useAssetGridFilters.js +19 -0
  37. package/dist/features/asset-grid/react/hooks/useAssetGridKeyboardShortcuts.js +16 -11
  38. package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.d.ts +2 -0
  39. package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.js +35 -11
  40. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.d.ts +6 -6
  41. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.js +10 -129
  42. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.d.ts +50 -0
  43. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.js +95 -0
  44. package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.d.ts +56 -0
  45. package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.js +80 -0
  46. package/dist/features/asset-tree-browser/rules.js +40 -22
  47. package/dist/features/browser-chrome/rules.js +74 -70
  48. package/dist/features/charts/browser.js +11 -5
  49. package/dist/features/charts/react.js +2 -2
  50. package/dist/features/charts/table.js +2 -2
  51. package/dist/features/command-palette/react/hooks/useCommandPalette.js +3 -9
  52. package/dist/features/connectors/ConnectorsBrowser.d.ts +4 -4
  53. package/dist/features/connectors/ConnectorsBrowser.js +26 -14
  54. package/dist/features/connectors/components/ConnectorCard.js +67 -38
  55. package/dist/features/connectors/components/ConnectorDetailDrawer.js +62 -31
  56. package/dist/features/connectors/components/ConnectorGrid.js +11 -6
  57. package/dist/features/connectors/hooks/useConnectorAuthorization.js +6 -0
  58. package/dist/features/connectors/hooks/useConnectorCatalog.d.ts +2 -0
  59. package/dist/features/connectors/hooks/useConnectorCatalog.js +32 -13
  60. package/dist/features/connectors/hooks/useWiredConnectorsBrowser.d.ts +3 -3
  61. package/dist/features/connectors/rules.d.ts +8 -1
  62. package/dist/features/connectors/rules.js +42 -20
  63. package/dist/features/connectors/types.d.ts +11 -0
  64. package/dist/features/execution/constants.d.ts +4 -10
  65. package/dist/features/execution/constants.js +9 -94
  66. package/dist/features/execution/react/components/ByokProviderForm.d.ts +2 -4
  67. package/dist/features/execution/react/components/ByokProviderForm.js +140 -43
  68. package/dist/features/execution/react/components/ExecutionTab.d.ts +1 -2
  69. package/dist/features/execution/react/components/ExecutionTab.js +52 -30
  70. package/dist/features/execution/react/components/LocalCliAgentCard.js +12 -3
  71. package/dist/features/execution/react/components/LocalCliAgentList.js +14 -3
  72. package/dist/features/execution/react/components/LocalCliAgentSettings.js +14 -3
  73. package/dist/features/execution/react/hooks/useLocalCliAgentCard.js +54 -36
  74. package/dist/features/execution/react/hooks/useReasoningControl.js +114 -78
  75. package/dist/features/execution/rules.d.ts +2 -2
  76. package/dist/features/execution/rules.js +31 -15
  77. package/dist/features/execution/types.d.ts +4 -42
  78. package/dist/features/execution/types.js +0 -17
  79. package/dist/features/external-mcp/react/components/ExternalMcpTab.d.ts +6 -2
  80. package/dist/features/external-mcp/react/components/ExternalMcpTab.js +9 -2
  81. package/dist/features/file-dropzone/react/components/FileDropzone.js +24 -25
  82. package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.d.ts +2 -2
  83. package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.js +35 -30
  84. package/dist/features/file-dropzone/react/components/FileDropzoneThumbnailGrid.js +30 -24
  85. package/dist/features/file-dropzone/react/components/FileDropzoneZone.d.ts +14 -0
  86. package/dist/features/file-dropzone/react/components/FileDropzoneZone.js +26 -0
  87. package/dist/features/html-editor/canvas-content-wrapper.js +13 -13
  88. package/dist/features/html-editor/canvas-embed-placeholders.js +46 -37
  89. package/dist/features/html-editor/css.js +76 -69
  90. package/dist/features/html-editor/editor-blocks.js +47 -33
  91. package/dist/features/html-editor/editor-serialization.js +6 -5
  92. package/dist/features/html-editor/protected-elements.js +39 -28
  93. package/dist/features/html-editor/react/hooks/useInteractiveHtmlEditor.js +4 -4
  94. package/dist/features/html-editor/source-splice.js +51 -30
  95. package/dist/features/i18n/dictionaries/settings-dialog.ja.js +1 -0
  96. package/dist/features/i18n/dictionaries/settings-dialog.zh-CN.js +1 -0
  97. package/dist/features/i18n/dictionaries/settings-dialog.zh-TW.js +1 -0
  98. package/dist/features/i18n/locale.js +20 -16
  99. package/dist/features/iframe-pool/react/dom-sync.js +8 -6
  100. package/dist/features/integrations/react/components/CodexInstallToggleButton.js +19 -4
  101. package/dist/features/integrations/react/components/IntegrationsTab.js +60 -14
  102. package/dist/features/interactive-ui/interactive-ui.css +1 -1
  103. package/dist/features/interactive-ui/providers/shadcn/data-table.js +10 -7
  104. package/dist/features/lexical-rich-text-editor/mention-parser.js +60 -57
  105. package/dist/features/lexical-rich-text-editor/react/hooks/useKeyboardCommands.js +33 -38
  106. package/dist/features/lexical-rich-text-editor/rules.js +87 -74
  107. package/dist/features/lexical-rich-text-editor/serialize.js +27 -25
  108. package/dist/features/mcp-ui/secret-card.js +111 -61
  109. package/dist/features/mcp-ui/surfaces/confirmation.js +39 -21
  110. package/dist/features/mcp-ui/surfaces/document.d.ts +1 -1
  111. package/dist/features/mcp-ui/surfaces/document.js +10 -2
  112. package/dist/features/mcp-ui/surfaces/fields.js +30 -50
  113. package/dist/features/mcp-ui/surfaces/form.d.ts +6 -0
  114. package/dist/features/mcp-ui/surfaces/form.js +43 -21
  115. package/dist/features/mcp-ui/surfaces/outcome.js +30 -20
  116. package/dist/features/mcp-ui/surfaces/text-input.js +33 -21
  117. package/dist/features/mcp-ui/surfaces/tokens.d.ts +6 -1
  118. package/dist/features/mcp-ui/surfaces/tokens.js +5 -2
  119. package/dist/features/media-providers/constants.d.ts +1 -0
  120. package/dist/features/media-providers/constants.js +2 -81
  121. package/dist/features/media-providers/index.d.ts +1 -1
  122. package/dist/features/media-providers/ports.d.ts +2 -0
  123. package/dist/features/media-providers/react/components/MediaProviderCard.d.ts +22 -0
  124. package/dist/features/media-providers/react/components/MediaProviderCard.js +50 -0
  125. package/dist/features/media-providers/react/components/MediaProvidersTab.d.ts +5 -6
  126. package/dist/features/media-providers/react/components/MediaProvidersTab.js +40 -72
  127. package/dist/features/media-providers/react/components/media-providers-tab-labels.d.ts +11 -0
  128. package/dist/features/media-providers/react/components/media-providers-tab-labels.js +57 -0
  129. package/dist/features/media-providers/react/hooks/useMediaProvidersTab.d.ts +4 -2
  130. package/dist/features/media-providers/react/hooks/useMediaProvidersTab.js +37 -23
  131. package/dist/features/media-providers/rules.js +17 -10
  132. package/dist/features/media-providers/types.d.ts +4 -22
  133. package/dist/features/media-providers/types.js +3 -8
  134. package/dist/features/memory/formatters.js +127 -92
  135. package/dist/features/memory/react/components/MemoryConnectedPanel.js +92 -36
  136. package/dist/features/memory/react/components/MemoryList.js +37 -18
  137. package/dist/features/memory/react/hooks/useMemoryExtractions.store.js +42 -36
  138. package/dist/features/notifications/react/components/NotificationsTab.js +28 -28
  139. package/dist/features/observability/long-task.d.ts +0 -12
  140. package/dist/features/observability/long-task.js +15 -19
  141. package/dist/features/observability/resource-error.d.ts +0 -10
  142. package/dist/features/observability/resource-error.js +18 -13
  143. package/dist/features/observability/stuck-run.js +10 -6
  144. package/dist/features/observability/telemetry-url.d.ts +6 -0
  145. package/dist/features/observability/telemetry-url.js +16 -0
  146. package/dist/features/observability/white-screen.js +12 -24
  147. package/dist/features/panel-kit/fetch-query/adapter.react.d.ts +2 -2
  148. package/dist/features/panel-kit/fetch-query/adapter.react.js +4 -2
  149. package/dist/features/panel-kit/fetch-query/adapter.tanstack.js +46 -33
  150. package/dist/features/panel-kit/helpers/standing-draft-local-backup.js +3 -1
  151. package/dist/features/panel-kit/hooks/use-settings-slice.hooks.js +3 -1
  152. package/dist/features/privacy/react/components/PrivacyTab.js +24 -13
  153. package/dist/features/progress-card/components/ProgressCard.js +7 -3
  154. package/dist/features/progress-card/reference-adapters.js +59 -54
  155. package/dist/features/project-locations/react/components/ProjectLocationsTab.js +24 -15
  156. package/dist/features/project-locations/react/hooks/useProjectLocationsTab.d.ts +8 -0
  157. package/dist/features/project-locations/react/hooks/useProjectLocationsTab.js +31 -23
  158. package/dist/features/resource-dashboard/react/components/ResourceBoard.js +6 -47
  159. package/dist/features/resource-dashboard/react/components/ResourceBoardView.d.ts +2 -2
  160. package/dist/features/resource-dashboard/react/components/ResourceBoardView.js +3 -6
  161. package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.d.ts +15 -0
  162. package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.js +35 -0
  163. package/dist/features/resource-dashboard/react/components/ResourceCard.js +5 -16
  164. package/dist/features/resource-dashboard/react/components/ResourceCardParts.d.ts +34 -0
  165. package/dist/features/resource-dashboard/react/components/ResourceCardParts.js +34 -0
  166. package/dist/features/resource-dashboard/react/components/ResourceRowList.js +3 -1
  167. package/dist/features/resource-dashboard/react/components/ResourceRowListItem.js +2 -2
  168. package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.d.ts +19 -0
  169. package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.js +11 -0
  170. package/dist/features/resource-dashboard/react/components/ResourceRowListView.d.ts +1 -1
  171. package/dist/features/resource-dashboard/react/components/ResourceRowListView.js +4 -4
  172. package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.d.ts +10 -0
  173. package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.js +20 -0
  174. package/dist/features/resource-dashboard/react/components/resourceBoardProps.d.ts +48 -0
  175. package/dist/features/resource-dashboard/react/components/resourceBoardProps.js +70 -0
  176. package/dist/features/resource-dashboard/react/components/resourceRowListProps.d.ts +19 -0
  177. package/dist/features/resource-dashboard/react/components/resourceRowListProps.js +14 -0
  178. package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.d.ts +16 -0
  179. package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.js +13 -0
  180. package/dist/features/resource-dashboard/rules.js +2 -8
  181. package/dist/features/sketch-editor/dom.js +150 -99
  182. package/dist/features/sketch-editor/react/components/SketchEditor.js +26 -15
  183. package/dist/features/skills/react/components/SkillDraftForm.js +13 -19
  184. package/dist/features/skills/react/components/SkillDraftForm.labels.d.ts +31 -0
  185. package/dist/features/skills/react/components/SkillDraftForm.labels.js +54 -0
  186. package/dist/features/skills/react/components/SkillRow.js +38 -18
  187. package/dist/features/skills/react/components/SkillRow.labels.d.ts +7 -0
  188. package/dist/features/skills/react/components/SkillRow.labels.js +18 -0
  189. package/dist/features/skills/react/components/SkillsTab.js +24 -14
  190. package/dist/features/skills/react/components/SkillsTab.labels.d.ts +7 -0
  191. package/dist/features/skills/react/components/SkillsTab.labels.js +11 -0
  192. package/dist/features/skills/rules.js +32 -23
  193. package/dist/features/source-config-list/agent-handles.js +2 -1
  194. package/dist/features/source-config-list/dependencies.js +53 -49
  195. package/dist/features/source-config-list/index.d.ts +3 -3
  196. package/dist/features/source-config-list/react/components/SourceConfigAddForm.d.ts +6 -5
  197. package/dist/features/source-config-list/react/components/SourceConfigAddForm.js +38 -7
  198. package/dist/features/source-config-list/react/components/SourceConfigField.js +69 -25
  199. package/dist/features/source-config-list/react/components/SourceConfigItemCard.d.ts +6 -5
  200. package/dist/features/source-config-list/react/components/SourceConfigItemCard.js +76 -20
  201. package/dist/features/source-config-list/react/components/SourceConfigListView.d.ts +5 -3
  202. package/dist/features/source-config-list/react/components/SourceConfigListView.js +33 -3
  203. package/dist/features/source-config-list/react/components/SourceConfigTestControl.js +17 -1
  204. package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.d.ts +11 -0
  205. package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.js +12 -0
  206. package/dist/features/source-config-list/types.d.ts +14 -0
  207. package/dist/features/tab-launcher-menu/react/components/TabLauncherMenu.js +25 -1
  208. package/dist/features/tab-launcher-menu/react/hooks/useTabLauncherMenu.js +15 -16
  209. package/dist/features/tab-strip/react/components/TabBar.js +8 -1
  210. package/dist/features/tabbed-dialog/react/components/TabbedDialog.js +8 -30
  211. package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.d.ts +15 -0
  212. package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.js +8 -0
  213. package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.d.ts +12 -0
  214. package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.js +6 -0
  215. package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.d.ts +27 -0
  216. package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.js +12 -0
  217. package/dist/features/tabbed-dialog/react/components/tabbedDialogView.d.ts +43 -0
  218. package/dist/features/tabbed-dialog/react/components/tabbedDialogView.js +59 -0
  219. package/dist/features/version-manager/dependencies.d.ts +3 -1
  220. package/dist/features/version-manager/dependencies.js +3 -2
  221. package/dist/features/version-manager/react/components/VersionManagerModal.js +10 -6
  222. package/dist/features/version-manager/react/hooks/useVersionManager.js +7 -4
  223. package/dist/features/viewer-shell/react/components/CommentSidePanel.js +103 -50
  224. package/dist/features/viewer-shell/react/components/MarkdownSplitPane.js +28 -8
  225. package/dist/features/viewer-shell/react/hooks/useCommentReorder.js +22 -16
  226. package/dist/features/viewer-shell/rules.js +19 -23
  227. package/dist/react/components/CustomSelect.d.ts +38 -1
  228. package/dist/react/components/CustomSelect.js +94 -38
  229. package/dist/react/components/OnboardingDropdown.js +20 -8
  230. package/dist/react/components/RemixIcon.stylesheet.js +3 -1
  231. package/dist/react/components/Toast.js +13 -2
  232. package/dist/react/components/TooltipLayer.js +5 -14
  233. package/dist/react/components/WorkingDirPicker.js +78 -50
  234. package/dist/react/components/remixicon-font-recovery.js +38 -30
  235. package/dist/react/components/select-popover.d.ts +8 -6
  236. package/dist/react/components/select-popover.js +33 -29
  237. package/dist/react/mcp-ui/McpUiHost.d.ts +1 -1
  238. package/dist/react/mcp-ui/McpUiHost.js +10 -11
  239. package/dist/react/mcp-ui/host-message-source.d.ts +14 -2
  240. package/dist/react/mcp-ui/host-message-source.js +15 -5
  241. package/dist/react/mcp-ui/mcp-ui-host-frame.d.ts +41 -0
  242. package/dist/react/mcp-ui/mcp-ui-host-frame.js +39 -0
  243. package/dist/renderers/annotation-canvas/react/components/AnnotationCanvas.js +97 -69
  244. package/dist/renderers/preview-modal-shell/react/components/PreviewModalShell.js +59 -30
  245. package/dist/renderers/react/components/ArtifactView.js +9 -6
  246. package/dist/renderers/srcdoc/build.d.ts +4 -1
  247. package/dist/renderers/srcdoc/build.js +62 -48
  248. package/dist/styles/admin.css +29 -14
  249. package/dist/styles/lightbox.css +142 -0
  250. package/dist/styles/variables.css +128 -0
  251. package/dist/theme/apply.js +9 -5
  252. package/dist/utils/auto-open-file.d.ts +2 -1
  253. package/dist/utils/auto-open-file.js +69 -85
  254. package/dist/utils/markdown-scroll-sync.js +108 -87
  255. package/dist/utils/polygon-selection.js +25 -26
  256. package/dist/utils/smooth-scroll-to-top.d.ts +1 -1
  257. package/dist/utils/smooth-scroll-to-top.js +2 -32
  258. package/dist/utils/sse.js +26 -26
  259. package/dist/utils/unit-bezier.d.ts +7 -0
  260. package/dist/utils/unit-bezier.js +38 -0
  261. package/package.json +7 -10
  262. package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.d.ts +0 -13
  263. package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.js +0 -46
@@ -33,6 +33,16 @@ export function setTooltipAttribute(target, name, value) {
33
33
  return;
34
34
  target.setAttribute(name, value);
35
35
  }
36
+ function resolveTooltipTarget(trigger, entry) {
37
+ return entry.target === 'closest-label' ? trigger.closest('label') : trigger;
38
+ }
39
+ function decorateTooltipTarget({ target, trigger, label, placement, }) {
40
+ setTooltipAttribute(target, 'data-tooltip', label);
41
+ setTooltipAttribute(target, 'data-tooltip-placement', placement);
42
+ if (!target.classList.contains('jini-tooltip'))
43
+ target.classList.add('jini-tooltip');
44
+ setTooltipAttribute(trigger, 'aria-label', label);
45
+ }
36
46
  /**
37
47
  * Drives ONLY the shared `TooltipLayer` contract (`.jini-tooltip[data-tooltip]`):
38
48
  * it renders a single styled, viewport-aware bubble and suppresses the
@@ -42,47 +52,111 @@ export function setTooltipAttribute(target, name, value) {
42
52
  export function applySketchEditorTooltips(root, labels, targets) {
43
53
  const decorated = new Set();
44
54
  for (const entry of targets) {
45
- for (const trigger of Array.from(root.querySelectorAll(entry.selector))) {
46
- const target = entry.target === 'closest-label' ? trigger.closest('label') : trigger;
55
+ const triggers = Array.from(root.querySelectorAll(entry.selector));
56
+ const label = normalizeTooltipLabel(labels[entry.label]);
57
+ if (!label)
58
+ continue;
59
+ for (const trigger of triggers) {
60
+ const target = resolveTooltipTarget(trigger, entry);
47
61
  if (!target || decorated.has(target))
48
62
  continue;
49
- const label = normalizeTooltipLabel(labels[entry.label]);
50
- if (!label)
51
- continue;
52
- setTooltipAttribute(target, 'data-tooltip', label);
53
- setTooltipAttribute(target, 'data-tooltip-placement', entry.placement ?? 'bottom');
54
- if (!target.classList.contains('jini-tooltip'))
55
- target.classList.add('jini-tooltip');
56
- setTooltipAttribute(trigger, 'aria-label', label);
63
+ decorateTooltipTarget({ target, trigger, label, placement: entry.placement ?? 'bottom' });
57
64
  decorated.add(target);
58
65
  }
59
66
  }
60
67
  }
68
+ function readPopoverOrigin(popover, rect) {
69
+ const left = Number.parseFloat(popover.style.left || `${rect.left}`);
70
+ const top = Number.parseFloat(popover.style.top || `${rect.top}`);
71
+ return Number.isFinite(left) && Number.isFinite(top) ? { left, top } : null;
72
+ }
73
+ /** Shifts `value` so the `[start, end]` span fits inside `[min, max]`: the
74
+ * overflow past `max` is subtracted first, then the underflow before `min`
75
+ * is added, in that order. */
76
+ function clampAxis({ value, start, end }, { min, max }) {
77
+ let next = value;
78
+ if (end > max)
79
+ next -= end - max;
80
+ if (start < min)
81
+ next += min - start;
82
+ return next;
83
+ }
84
+ function writePopoverCoordinate({ popover, side, next }) {
85
+ if (next !== Number.parseFloat(popover.style[side] || 'NaN'))
86
+ popover.style[side] = `${Math.max(0, Math.round(next))}px`;
87
+ }
61
88
  export function clampSketchContextPopover(popover, viewportRoot) {
62
89
  const viewportRect = viewportRoot.getBoundingClientRect();
63
90
  const rect = popover.getBoundingClientRect();
64
91
  if (!rect.width || !rect.height)
65
92
  return;
66
- let nextLeft = Number.parseFloat(popover.style.left || `${rect.left}`);
67
- let nextTop = Number.parseFloat(popover.style.top || `${rect.top}`);
68
- if (!Number.isFinite(nextLeft) || !Number.isFinite(nextTop))
93
+ const origin = readPopoverOrigin(popover, rect);
94
+ if (!origin)
69
95
  return;
70
- const maxRight = viewportRect.right - SKETCH_CONTEXT_MENU_MARGIN;
71
- const minLeft = viewportRect.left + SKETCH_CONTEXT_MENU_MARGIN;
72
- const maxBottom = viewportRect.bottom - SKETCH_CONTEXT_MENU_MARGIN;
73
- const minTop = viewportRect.top + SKETCH_CONTEXT_MENU_MARGIN;
74
- if (rect.right > maxRight)
75
- nextLeft -= rect.right - maxRight;
76
- if (rect.left < minLeft)
77
- nextLeft += minLeft - rect.left;
78
- if (rect.bottom > maxBottom)
79
- nextTop -= rect.bottom - maxBottom;
80
- if (rect.top < minTop)
81
- nextTop += minTop - rect.top;
82
- if (nextLeft !== Number.parseFloat(popover.style.left || 'NaN'))
83
- popover.style.left = `${Math.max(0, Math.round(nextLeft))}px`;
84
- if (nextTop !== Number.parseFloat(popover.style.top || 'NaN'))
85
- popover.style.top = `${Math.max(0, Math.round(nextTop))}px`;
96
+ const nextLeft = clampAxis({ value: origin.left, start: rect.left, end: rect.right }, { min: viewportRect.left + SKETCH_CONTEXT_MENU_MARGIN, max: viewportRect.right - SKETCH_CONTEXT_MENU_MARGIN });
97
+ const nextTop = clampAxis({ value: origin.top, start: rect.top, end: rect.bottom }, { min: viewportRect.top + SKETCH_CONTEXT_MENU_MARGIN, max: viewportRect.bottom - SKETCH_CONTEXT_MENU_MARGIN });
98
+ writePopoverCoordinate({ popover, side: 'left', next: nextLeft });
99
+ writePopoverCoordinate({ popover, side: 'top', next: nextTop });
100
+ }
101
+ function hasRecognizedContextMenuAction(menu, recognized) {
102
+ // `li[data-testid]` is an attribute selector: every `item` it matches is
103
+ // guaranteed to carry the attribute, so `getAttribute` can't return null
104
+ // here — only typed nullable because `getAttribute` is nullable in general.
105
+ return Array.from(menu.querySelectorAll('li[data-testid]')).some((item) => recognized.has(item.getAttribute('data-testid')));
106
+ }
107
+ /** Removes every `<hr>` and every non-allow-listed `<li>` from `menu`,
108
+ * returning the surviving `<li>`s keyed by action name. */
109
+ function pruneContextMenuItems(menu, allowed) {
110
+ const byAction = new Map();
111
+ for (const child of Array.from(menu.children)) {
112
+ if (child instanceof HTMLHRElement) {
113
+ child.remove();
114
+ continue;
115
+ }
116
+ if (!(child instanceof HTMLLIElement))
117
+ continue;
118
+ const actionName = child.getAttribute('data-testid') ?? '';
119
+ if (!allowed.has(actionName)) {
120
+ child.remove();
121
+ continue;
122
+ }
123
+ byAction.set(actionName, child);
124
+ }
125
+ return byAction;
126
+ }
127
+ function isContextMenuOrdered(menu, orderedItems) {
128
+ const currentItems = Array.from(menu.children).filter((child) => child instanceof HTMLLIElement);
129
+ return currentItems.length === orderedItems.length && currentItems.every((item, index) => item === orderedItems[index]);
130
+ }
131
+ function simplifyContextMenu({ menu, viewportRoot, allowList }, { allowed, recognized }) {
132
+ // `menu` was found via `root.querySelectorAll(...)`, so it is always a
133
+ // strict descendant of `root` and therefore always has a parent element
134
+ // — `.parentElement` is only typed nullable because `Element` itself
135
+ // permits detached nodes, not because that's reachable here.
136
+ const popover = menu.closest('.popover') ?? menu.parentElement;
137
+ if (!hasRecognizedContextMenuAction(menu, recognized))
138
+ return;
139
+ menu.classList.add('jini-sketch-context-menu');
140
+ popover.classList.add('jini-sketch-context-popover');
141
+ const byAction = pruneContextMenuItems(menu, allowed);
142
+ // `orderContextMenuActions` only returns names present in `byAction`'s
143
+ // keys, so every lookup here is defined.
144
+ const orderedItems = orderContextMenuActions(Array.from(byAction.keys()), allowList).map((action) => byAction.get(action));
145
+ if (orderedItems.length === 0) {
146
+ popover.remove();
147
+ return;
148
+ }
149
+ // Only touch the DOM when the order is actually wrong. This runs on
150
+ // every MutationObserver tick, and re-appending nodes already in place
151
+ // detaches/re-inserts them each frame; because that re-triggers the
152
+ // observer, an unconditional reorder becomes a self-sustaining
153
+ // per-frame churn that cancels the in-flight pointer interaction,
154
+ // making the menu impossible to click.
155
+ if (!isContextMenuOrdered(menu, orderedItems)) {
156
+ for (const item of orderedItems)
157
+ menu.appendChild(item);
158
+ }
159
+ clampSketchContextPopover(popover, viewportRoot);
86
160
  }
87
161
  /**
88
162
  * Simplifies Excalidraw's built-in right-click context menu down to
@@ -91,67 +165,18 @@ export function clampSketchContextPopover(popover, viewportRoot) {
91
165
  * Excalidraw's own.
92
166
  */
93
167
  export function applySketchContextMenuSimplification(root, viewportRoot, allowList, recognizedActions) {
168
+ const sets = { allowed: new Set(allowList), recognized: new Set(recognizedActions) };
94
169
  for (const menu of Array.from(root.querySelectorAll('ul.context-menu'))) {
95
- // `menu` was found via `root.querySelectorAll(...)`, so it is always a
96
- // strict descendant of `root` and therefore always has a parent element
97
- // — `.parentElement` is only typed nullable because `Element` itself
98
- // permits detached nodes, not because that's reachable here.
99
- const popover = menu.closest('.popover') ?? menu.parentElement;
100
- const recognized = new Set(recognizedActions);
101
- // `li[data-testid]` is an attribute selector: every `item` it matches is
102
- // guaranteed to carry the attribute, so `getAttribute` can't return null
103
- // here — only typed nullable because `getAttribute` is nullable in general.
104
- const hasRecognizedAction = Array.from(menu.querySelectorAll('li[data-testid]')).some((item) => recognized.has(item.getAttribute('data-testid')));
105
- if (!hasRecognizedAction)
106
- continue;
107
- menu.classList.add('jini-sketch-context-menu');
108
- popover.classList.add('jini-sketch-context-popover');
109
- const allowed = new Set(allowList);
110
- const byAction = new Map();
111
- for (const child of Array.from(menu.children)) {
112
- if (child instanceof HTMLHRElement) {
113
- child.remove();
114
- continue;
115
- }
116
- if (!(child instanceof HTMLLIElement))
117
- continue;
118
- const actionName = child.getAttribute('data-testid') ?? '';
119
- if (!allowed.has(actionName)) {
120
- child.remove();
121
- continue;
122
- }
123
- byAction.set(actionName, child);
124
- }
125
- const orderedActions = orderContextMenuActions(Array.from(byAction.keys()), allowList);
126
- const orderedItems = orderedActions.map((action) => byAction.get(action)).filter(Boolean);
127
- if (orderedItems.length === 0) {
128
- popover.remove();
129
- continue;
130
- }
131
- // Only touch the DOM when the order is actually wrong. This runs on
132
- // every MutationObserver tick, and re-appending nodes already in place
133
- // detaches/re-inserts them each frame; because that re-triggers the
134
- // observer, an unconditional reorder becomes a self-sustaining
135
- // per-frame churn that cancels the in-flight pointer interaction,
136
- // making the menu impossible to click.
137
- const currentItems = Array.from(menu.children).filter((child) => child instanceof HTMLLIElement);
138
- const alreadyOrdered = currentItems.length === orderedItems.length && currentItems.every((item, index) => item === orderedItems[index]);
139
- if (!alreadyOrdered) {
140
- for (const item of orderedItems)
141
- menu.appendChild(item);
142
- }
143
- clampSketchContextPopover(popover, viewportRoot);
170
+ simplifyContextMenu({ menu, viewportRoot, allowList }, sets);
144
171
  }
145
172
  }
146
- /**
147
- * Walks `root`'s text nodes and a fixed set of text-bearing attributes,
148
- * rewriting any that match `overrides`. `overrides` is entirely
149
- * host-supplied — no translated copy ships as a default (see
150
- * `packages/ui/archived provenance ledger`).
151
- */
152
- export function applySketchDomTextOverrides(root, overrides) {
153
- if (!overrides || Object.keys(overrides).length === 0 || typeof document === 'undefined')
154
- return;
173
+ /** The override for `value`, or `null` when there is none or it would not
174
+ * change anything (so callers skip a no-op DOM write). */
175
+ function resolveTextOverride(value, overrides) {
176
+ const next = translateDomTextValue(value, overrides);
177
+ return next !== null && next !== value ? next : null;
178
+ }
179
+ function rewriteTextNodes(root, overrides) {
155
180
  const textWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
156
181
  let node = textWalker.nextNode();
157
182
  while (node) {
@@ -159,23 +184,38 @@ export function applySketchDomTextOverrides(root, overrides) {
159
184
  // `nodeValue` is always a string per spec (only `Document`/`DocumentType`
160
185
  // nodes ever have a null `nodeValue`) — `Node.nodeValue`'s broader type
161
186
  // is nullable only because it covers those other node kinds too.
162
- const next = translateDomTextValue(node.nodeValue, overrides);
163
- if (next !== null && next !== node.nodeValue)
187
+ const next = resolveTextOverride(node.nodeValue, overrides);
188
+ if (next !== null)
164
189
  node.nodeValue = next;
165
190
  node = textWalker.nextNode();
166
191
  }
167
- const elements = root instanceof Element ? [root, ...Array.from(root.querySelectorAll('*'))] : Array.from(root.querySelectorAll('*'));
192
+ }
193
+ function rewriteOverrideAttributes(root, overrides) {
194
+ const descendants = Array.from(root.querySelectorAll('*'));
195
+ const elements = root instanceof Element ? [root, ...descendants] : descendants;
168
196
  for (const element of elements) {
169
197
  for (const attr of SKETCH_TEXT_OVERRIDE_ATTRS) {
170
198
  const value = element.getAttribute(attr);
171
199
  if (!value)
172
200
  continue;
173
- const next = translateDomTextValue(value, overrides);
174
- if (next !== null && next !== value)
201
+ const next = resolveTextOverride(value, overrides);
202
+ if (next !== null)
175
203
  element.setAttribute(attr, next);
176
204
  }
177
205
  }
178
206
  }
207
+ /**
208
+ * Walks `root`'s text nodes and a fixed set of text-bearing attributes,
209
+ * rewriting any that match `overrides`. `overrides` is entirely
210
+ * host-supplied — no translated copy ships as a default (see
211
+ * `packages/ui/archived provenance ledger`).
212
+ */
213
+ export function applySketchDomTextOverrides(root, overrides) {
214
+ if (!overrides || Object.keys(overrides).length === 0 || typeof document === 'undefined')
215
+ return;
216
+ rewriteTextNodes(root, overrides);
217
+ rewriteOverrideAttributes(root, overrides);
218
+ }
179
219
  export function rewriteExcalidrawUnableToEmbedToasts(root, replacement, additionalPhrases = []) {
180
220
  const normalizedReplacement = normalizeTooltipLabel(replacement);
181
221
  if (!normalizedReplacement)
@@ -241,20 +281,31 @@ export function enhanceSketchExcalidrawPortals(closeLabel, onClose, domTextOverr
241
281
  }
242
282
  }
243
283
  }
244
- export function handleSketchPortalCommandEnter(event, insertLabelPattern) {
245
- if (event.key !== 'Enter' || (!event.metaKey && !event.ctrlKey) || event.altKey)
246
- return;
247
- const target = event.target;
284
+ function isCommandEnter(event) {
285
+ return event.key === 'Enter' && (event.metaKey || event.ctrlKey) && !event.altKey;
286
+ }
287
+ /** The `.Modal__content` around `target`, but only when it sits inside one
288
+ * of our marked `.jini-sketch-modal` portals and holds a `<textarea>`. */
289
+ function resolvePortalEditorContent(target) {
248
290
  if (!(target instanceof Element))
249
- return;
291
+ return null;
250
292
  const portal = target.closest('.jini-sketch-modal');
251
293
  const content = target.closest('.Modal__content');
252
294
  if (!portal || !content || !portal.contains(content))
295
+ return null;
296
+ return content.querySelector('textarea') ? content : null;
297
+ }
298
+ function isButtonEnabled(button) {
299
+ return !button.disabled && button.getAttribute('aria-disabled') !== 'true';
300
+ }
301
+ export function handleSketchPortalCommandEnter(event, insertLabelPattern) {
302
+ if (!isCommandEnter(event))
253
303
  return;
254
- if (!content.querySelector('textarea'))
304
+ const content = resolvePortalEditorContent(event.target);
305
+ if (!content)
255
306
  return;
256
307
  const insertButton = findSketchMermaidInsertButton(content, insertLabelPattern);
257
- if (!insertButton || insertButton.disabled || insertButton.getAttribute('aria-disabled') === 'true')
308
+ if (!insertButton || !isButtonEnabled(insertButton))
258
309
  return;
259
310
  event.preventDefault();
260
311
  event.stopPropagation();
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useMemo, useRef } from 'react';
2
+ import { useRef } from 'react';
3
3
  import { useI18n } from '../../../i18n/index.js';
4
4
  import { Toast } from '../../../../react/components/Toast.js';
5
5
  import { defaultSketchEditorDependencies } from '../../dependencies.js';
@@ -10,6 +10,29 @@ import { useSketchScene } from '../hooks/useSketchScene.js';
10
10
  import { useSketchTheme } from '../hooks/useSketchTheme.js';
11
11
  import { SketchMainMenu } from './SketchMainMenu.js';
12
12
  import { SketchSaveStateBadge } from './SketchSaveStateBadge.js';
13
+ const EXCALIDRAW_UI_OPTIONS = {
14
+ canvasActions: {
15
+ saveToActiveFile: false,
16
+ loadScene: false,
17
+ toggleTheme: false,
18
+ saveAsImage: false,
19
+ export: false,
20
+ },
21
+ tools: { image: true },
22
+ };
23
+ /** `saving` wins over `dirty`; only the active label is translated. */
24
+ function resolveSaveStatus({ saving, dirty, t }) {
25
+ if (saving)
26
+ return { state: 'saving', label: t('Saving…') };
27
+ if (dirty)
28
+ return { state: 'dirty', label: t('Unsaved changes') };
29
+ return { state: 'saved', label: t('Saved') };
30
+ }
31
+ /** An export toast carries a file to open: it lingers longer and offers an
32
+ * "Open file" action; every other toast is a short-lived notice. */
33
+ function sketchToastActionProps({ actionFileName, t, onAction, }) {
34
+ return actionFileName ? { ttlMs: 5000, actionLabel: t('Open file'), onAction } : { ttlMs: 2200 };
35
+ }
13
36
  /**
14
37
  * An Excalidraw-backed sketch surface: theme sync, dirty/save/export
15
38
  * orchestration with content-signature deduping, and a DOM-enhancement
@@ -53,22 +76,10 @@ export function SketchEditor({ scene, onSceneChange, onClear, onSave, onExportIm
53
76
  mermaidInsertLabelPattern,
54
77
  onCloseActiveDialog: closeActiveDialog,
55
78
  });
56
- const excalidrawUIOptions = useMemo(() => ({
57
- canvasActions: {
58
- saveToActiveFile: false,
59
- loadScene: false,
60
- toggleTheme: false,
61
- saveAsImage: false,
62
- export: false,
63
- },
64
- tools: { image: true },
65
- }), []);
66
79
  const hasContent = sketchSceneHasContent(scene);
67
- const canClear = hasContent;
68
80
  const canSave = dirty || hasContent;
69
- const saveState = saving ? 'saving' : dirty ? 'dirty' : 'saved';
70
- const saveStateLabel = saving ? t('Saving…') : dirty ? t('Unsaved changes') : t('Saved');
81
+ const saveStatus = resolveSaveStatus({ saving, dirty, t });
71
82
  const { Excalidraw, MainMenu } = engine;
72
- return (_jsxs("div", { className: "sketch-editor", children: [_jsx("div", { ref: canvasWrapRef, className: "sketch-canvas-wrap sketch-excalidraw-wrap", "data-testid": "sketch-excalidraw-editor", children: _jsx(Excalidraw, { initialData: initialData, excalidrawAPI: handleExcalidrawAPI, onChange: handleChange, langCode: excalidrawLangCode(locale), theme: theme, detectScroll: false, handleKeyboardGlobally: false, autoFocus: true, name: fileName, UIOptions: excalidrawUIOptions, renderTopRightUI: () => _jsx(SketchSaveStateBadge, { state: saveState, label: saveStateLabel }), validateEmbeddable: validateSketchEmbeddableUrl, children: _jsx(SketchMainMenu, { MainMenu: MainMenu, t: t, saving: saving, showSaved: showSaved, canSave: canSave, onSave: () => void handleSave(), exportAvailable: Boolean(onExportImage), exporting: exporting, canExport: hasContent, onExportImage: () => void handleExportImage(), canClear: canClear, onClear: handleClear }) }, editorInstanceKey) }), toast ? (_jsx(Toast, { message: toast.message, details: toast.details ?? null, tone: toast.tone, ttlMs: toast.actionFileName ? 5000 : 2200, onDismiss: dismissToast, ...(toast.actionFileName ? { actionLabel: t('Open file'), onAction: handleToastAction } : {}) })) : null] }));
83
+ return (_jsxs("div", { className: "sketch-editor", children: [_jsx("div", { ref: canvasWrapRef, className: "sketch-canvas-wrap sketch-excalidraw-wrap", "data-testid": "sketch-excalidraw-editor", children: _jsx(Excalidraw, { initialData: initialData, excalidrawAPI: handleExcalidrawAPI, onChange: handleChange, langCode: excalidrawLangCode(locale), theme: theme, detectScroll: false, handleKeyboardGlobally: false, autoFocus: true, name: fileName, UIOptions: EXCALIDRAW_UI_OPTIONS, renderTopRightUI: () => _jsx(SketchSaveStateBadge, { ...saveStatus }), validateEmbeddable: validateSketchEmbeddableUrl, children: _jsx(SketchMainMenu, { MainMenu: MainMenu, t: t, saving: saving, showSaved: showSaved, canSave: canSave, onSave: () => void handleSave(), exportAvailable: Boolean(onExportImage), exporting: exporting, canExport: hasContent, onExportImage: () => void handleExportImage(), canClear: hasContent, onClear: handleClear }) }, editorInstanceKey) }), toast ? (_jsx(Toast, { message: toast.message, details: toast.details ?? null, tone: toast.tone, onDismiss: dismissToast, ...sketchToastActionProps({ actionFileName: toast.actionFileName, t, onAction: handleToastAction }) })) : null] }));
73
84
  }
74
85
  //# sourceMappingURL=SketchEditor.js.map
@@ -1,30 +1,24 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { agentHandleProps } from '@jini-ai/agentic';
3
3
  import { useT } from '../../../i18n/index.js';
4
+ import { resolveSkillDraftFormLabels, skillDraftErrorText, skillDraftSubmitLabel } from './SkillDraftForm.labels.js';
4
5
  /**
5
6
  * The create/edit form for one skill — shared between the top-of-list
6
7
  * "New skill" panel and an expanded row's inline edit. Origin:
7
8
  * `SkillDraftForm` in `SkillsSection.tsx`.
8
9
  */
9
10
  export function SkillDraftForm({ heading, subheading, draft, onDraftChange, error, saving, isEdit, isBuiltInOverride = false, onCancel, onSubmit, labels, agentHandle, }) {
10
- const t = useT();
11
- const nameLabel = labels?.nameLabel ?? t('Name');
12
- const namePlaceholder = labels?.namePlaceholder ?? t('my-skill');
13
- const triggersLabel = labels?.triggersLabel ?? t('Triggers');
14
- const triggersPlaceholder = labels?.triggersPlaceholder ?? t('search the web, summarize');
15
- const descriptionLabel = labels?.descriptionLabel ?? t('Description');
16
- const descriptionPlaceholder = labels?.descriptionPlaceholder ?? t('What does this skill do? When should it be used?');
17
- const bodyLabel = labels?.bodyLabel ?? t('Body');
18
- const bodyPlaceholder = labels?.bodyPlaceholder ?? t('# My skill\n\n1. Explain the workflow.\n2. Describe the inputs and outputs.');
19
- const cancelLabel = labels?.cancelLabel ?? t('Cancel');
20
- const createLabel = labels?.createLabel ?? t('Create');
21
- const saveLabel = labels?.saveLabel ?? t('Save');
22
- const overrideSaveLabel = labels?.overrideSaveLabel ?? t('Save as override');
23
- const savingLabel = labels?.savingLabel ?? t('Saving…');
24
- const nameRequiredError = labels?.nameRequiredError ?? t('Name is required.');
25
- const bodyRequiredError = labels?.bodyRequiredError ?? t('Body is required.');
26
- const submitLabel = saving ? savingLabel : isEdit ? (isBuiltInOverride ? overrideSaveLabel : saveLabel) : createLabel;
27
- const errorText = error?.kind === 'name-required' ? nameRequiredError : error?.kind === 'body-required' ? bodyRequiredError : (error?.message ?? null);
28
- return (_jsxs("div", { className: "jini-settings-subsection jini-skills-draft", "data-testid": isEdit ? 'skills-edit-form' : 'skills-create-form', children: [_jsx("div", { className: "jini-section-head", children: _jsxs("div", { children: [_jsx("h4", { children: heading }), subheading ? _jsx("p", { className: "jini-hint", children: subheading }) : null] }) }), _jsx("div", { className: "jini-settings-field", children: _jsxs("label", { children: [_jsx("span", { children: nameLabel }), _jsx("input", { type: "text", value: draft.name, onChange: (event) => onDraftChange((current) => ({ ...current, name: event.target.value })), placeholder: namePlaceholder, disabled: isEdit, ...agentHandleProps({}, { base: agentHandle, ...({ action: 'name', role: 'field', label: nameLabel }) }) })] }) }), _jsx("div", { className: "jini-settings-field", children: _jsxs("label", { children: [_jsx("span", { children: triggersLabel }), _jsx("input", { type: "text", value: draft.triggers, onChange: (event) => onDraftChange((current) => ({ ...current, triggers: event.target.value })), placeholder: triggersPlaceholder, ...agentHandleProps({}, { base: agentHandle, ...({ action: 'triggers', role: 'field', label: triggersLabel }) }) })] }) }), _jsx("div", { className: "jini-settings-field", children: _jsxs("label", { children: [_jsx("span", { children: descriptionLabel }), _jsx("textarea", { rows: 2, value: draft.description, onChange: (event) => onDraftChange((current) => ({ ...current, description: event.target.value })), placeholder: descriptionPlaceholder, ...agentHandleProps({}, { base: agentHandle, ...({ action: 'description', role: 'field', label: descriptionLabel }) }) })] }) }), _jsx("div", { className: "jini-settings-field", children: _jsxs("label", { children: [_jsx("span", { children: bodyLabel }), _jsx("textarea", { rows: 14, value: draft.body, onChange: (event) => onDraftChange((current) => ({ ...current, body: event.target.value })), placeholder: bodyPlaceholder, ...agentHandleProps({}, { base: agentHandle, ...({ action: 'body', role: 'field', label: bodyLabel }) }) })] }) }), errorText ? (_jsx("p", { className: "jini-hint jini-hint-error", role: "alert", children: errorText })) : null, _jsxs("div", { className: "jini-skills-draft-actions", children: [_jsx("button", { type: "button", className: "jini-button jini-button-ghost", onClick: onCancel, disabled: saving, ...agentHandleProps({}, { base: agentHandle, ...({ action: 'cancel', role: 'button', label: cancelLabel }) }), children: cancelLabel }), _jsx("button", { type: "button", className: "jini-button jini-button-primary", onClick: onSubmit, disabled: saving, "data-testid": "skills-save", ...agentHandleProps({}, { base: agentHandle, ...({ action: 'submit', role: 'button', label: submitLabel }) }), children: submitLabel })] })] }));
11
+ const l = resolveSkillDraftFormLabels({ t: useT() }, { labels });
12
+ const submitLabel = skillDraftSubmitLabel({ labels: l, saving, isEdit, isBuiltInOverride });
13
+ const errorText = skillDraftErrorText({ labels: l, error });
14
+ const fieldProps = { draft, onDraftChange, agentHandle };
15
+ return (_jsxs("div", { className: "jini-settings-subsection jini-skills-draft", "data-testid": isEdit ? 'skills-edit-form' : 'skills-create-form', children: [_jsx("div", { className: "jini-section-head", children: _jsxs("div", { children: [_jsx("h4", { children: heading }), subheading ? _jsx("p", { className: "jini-hint", children: subheading }) : null] }) }), _jsx(SkillDraftField, { ...fieldProps, field: "name", label: l.nameLabel, placeholder: l.namePlaceholder, disabled: isEdit }), _jsx(SkillDraftField, { ...fieldProps, field: "triggers", label: l.triggersLabel, placeholder: l.triggersPlaceholder }), _jsx(SkillDraftField, { ...fieldProps, field: "description", label: l.descriptionLabel, placeholder: l.descriptionPlaceholder, rows: 2 }), _jsx(SkillDraftField, { ...fieldProps, field: "body", label: l.bodyLabel, placeholder: l.bodyPlaceholder, rows: 14 }), errorText ? (_jsx("p", { className: "jini-hint jini-hint-error", role: "alert", children: errorText })) : null, _jsxs("div", { className: "jini-skills-draft-actions", children: [_jsx("button", { type: "button", className: "jini-button jini-button-ghost", onClick: onCancel, disabled: saving, ...agentHandleProps({}, { base: agentHandle, action: 'cancel', role: 'button', label: l.cancelLabel }), children: l.cancelLabel }), _jsx("button", { type: "button", className: "jini-button jini-button-primary", onClick: onSubmit, disabled: saving, "data-testid": "skills-save", ...agentHandleProps({}, { base: agentHandle, action: 'submit', role: 'button', label: submitLabel }), children: submitLabel })] })] }));
16
+ }
17
+ /** One labelled draft field. Attribute order matches the original inline
18
+ * markup exactly so the rendered HTML is unchanged. */
19
+ function SkillDraftField({ draft, onDraftChange, agentHandle, field, label, placeholder, disabled, rows }) {
20
+ const onChange = (event) => onDraftChange((current) => ({ ...current, [field]: event.target.value }));
21
+ const handle = agentHandleProps({}, { base: agentHandle, action: field, role: 'field', label });
22
+ return (_jsx("div", { className: "jini-settings-field", children: _jsxs("label", { children: [_jsx("span", { children: label }), rows === undefined ? (_jsx("input", { type: "text", value: draft[field], onChange: onChange, placeholder: placeholder, disabled: disabled, ...handle })) : (_jsx("textarea", { rows: rows, value: draft[field], onChange: onChange, placeholder: placeholder, ...handle }))] }) }));
29
23
  }
30
24
  //# sourceMappingURL=SkillDraftForm.js.map
@@ -0,0 +1,31 @@
1
+ import type { SkillDraftError } from '../../types.js';
2
+ import type { SkillDraftFormLabels } from './SkillDraftForm.js';
3
+ /** `defaults` with each entry replaced by the host's label when it supplied
4
+ * one, else by the translated default. `t` runs only for the gaps. Shared by
5
+ * every skills component that takes a `labels` override bag. */
6
+ export declare function resolveLabels<K extends string>({ defaults, t }: {
7
+ defaults: Record<K, string>;
8
+ t: (key: string) => string;
9
+ }, { labels }: {
10
+ labels?: Partial<Record<NoInfer<K>, string | undefined>> | undefined;
11
+ }): Record<K, string>;
12
+ /** The form's labels with every gap filled from the translated defaults. */
13
+ export declare function resolveSkillDraftFormLabels({ t }: {
14
+ t: (key: string) => string;
15
+ }, { labels }: {
16
+ labels?: SkillDraftFormLabels | undefined;
17
+ }): Required<SkillDraftFormLabels>;
18
+ /** Saving wins over everything; then edit (override vs. plain save) vs. create. */
19
+ export declare function skillDraftSubmitLabel({ labels, saving, isEdit, isBuiltInOverride, }: {
20
+ labels: Required<SkillDraftFormLabels>;
21
+ saving: boolean;
22
+ isEdit: boolean;
23
+ isBuiltInOverride: boolean;
24
+ }): string;
25
+ /** The alert text for `error`: the two validation kinds use their labels, a
26
+ * submit failure shows the host's own message; `null` means no alert. */
27
+ export declare function skillDraftErrorText({ labels, error, }: {
28
+ labels: Required<SkillDraftFormLabels>;
29
+ error: SkillDraftError | null;
30
+ }): string | null;
31
+ //# sourceMappingURL=SkillDraftForm.labels.d.ts.map
@@ -0,0 +1,54 @@
1
+ /** Every `SkillDraftFormLabels` key with its untranslated default. A host
2
+ * label wins; otherwise the default is passed through `t` (lazily, only
3
+ * when the host left that label out). */
4
+ const DEFAULT_SKILL_DRAFT_FORM_LABELS = {
5
+ nameLabel: 'Name',
6
+ namePlaceholder: 'my-skill',
7
+ triggersLabel: 'Triggers',
8
+ triggersPlaceholder: 'search the web, summarize',
9
+ descriptionLabel: 'Description',
10
+ descriptionPlaceholder: 'What does this skill do? When should it be used?',
11
+ bodyLabel: 'Body',
12
+ bodyPlaceholder: '# My skill\n\n1. Explain the workflow.\n2. Describe the inputs and outputs.',
13
+ cancelLabel: 'Cancel',
14
+ createLabel: 'Create',
15
+ saveLabel: 'Save',
16
+ overrideSaveLabel: 'Save as override',
17
+ savingLabel: 'Saving…',
18
+ nameRequiredError: 'Name is required.',
19
+ bodyRequiredError: 'Body is required.',
20
+ };
21
+ /** `defaults` with each entry replaced by the host's label when it supplied
22
+ * one, else by the translated default. `t` runs only for the gaps. Shared by
23
+ * every skills component that takes a `labels` override bag. */
24
+ export function resolveLabels({ defaults, t }, { labels }) {
25
+ const resolved = { ...defaults };
26
+ for (const key of Object.keys(defaults)) {
27
+ resolved[key] = labels?.[key] ?? t(defaults[key]);
28
+ }
29
+ return resolved;
30
+ }
31
+ /** The form's labels with every gap filled from the translated defaults. */
32
+ export function resolveSkillDraftFormLabels({ t }, { labels }) {
33
+ return resolveLabels({ defaults: DEFAULT_SKILL_DRAFT_FORM_LABELS, t }, { labels });
34
+ }
35
+ /** Saving wins over everything; then edit (override vs. plain save) vs. create. */
36
+ export function skillDraftSubmitLabel({ labels, saving, isEdit, isBuiltInOverride, }) {
37
+ if (saving)
38
+ return labels.savingLabel;
39
+ if (!isEdit)
40
+ return labels.createLabel;
41
+ return isBuiltInOverride ? labels.overrideSaveLabel : labels.saveLabel;
42
+ }
43
+ /** The alert text for `error`: the two validation kinds use their labels, a
44
+ * submit failure shows the host's own message; `null` means no alert. */
45
+ export function skillDraftErrorText({ labels, error, }) {
46
+ if (error === null)
47
+ return null;
48
+ if (error.kind === 'name-required')
49
+ return labels.nameRequiredError;
50
+ if (error.kind === 'body-required')
51
+ return labels.bodyRequiredError;
52
+ return error.message;
53
+ }
54
+ //# sourceMappingURL=SkillDraftForm.labels.js.map
@@ -4,6 +4,8 @@ import { formatSkillFileSize, humanizeSkillCategory, isBuiltInSkill, isDeletable
4
4
  import { useT } from '../../../i18n/index.js';
5
5
  import { Icon } from '../../../../react/components/Icon.js';
6
6
  import { SkillDraftForm } from './SkillDraftForm.js';
7
+ import { resolveLabels } from './SkillDraftForm.labels.js';
8
+ import { DEFAULT_SKILL_ROW_LABELS } from './SkillRow.labels.js';
7
9
  /**
8
10
  * One collapsible skill row: header (enable toggle, name, mode/category/
9
11
  * source badges, edit/delete actions) always visible; body preview + file
@@ -11,25 +13,43 @@ import { SkillDraftForm } from './SkillDraftForm.js';
11
13
  * `SkillRow` in `SkillsSection.tsx`.
12
14
  */
13
15
  export function SkillRow({ skill, locale, enabled, expanded, editing, body, bodyLoading, files, filesLoading, confirmDelete, confirmBuiltInEdit, draft, draftError, draftSaving, onDraftChange, onToggleExpanded, onToggleEnabled, onStartEdit, onConfirmBuiltInEdit, onCancelBuiltInEdit, onArmDelete, onCancelDelete, onCommitDelete, onCancelEdit, onSubmitEdit, labels, agentHandle, }) {
14
- const t = useT();
16
+ const l = resolveLabels({ defaults: DEFAULT_SKILL_ROW_LABELS, t: useT() }, { labels });
17
+ const builtIn = isBuiltInSkill(skill);
18
+ // Built-in skills are never edited in place — "editing" one creates a
19
+ // user-owned override, so every edit affordance says so.
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] }));
22
+ }
23
+ function skillRowClassName({ enabled, expanded }) {
24
+ return `jini-skills-row${enabled ? '' : ' jini-skills-row-disabled'}${expanded ? ' jini-skills-row-expanded' : ''}`;
25
+ }
26
+ /** The always-visible header button: name, mode/category/source badges, description. */
27
+ function SkillRowSummaryButton({ skill, locale, expanded, onToggleExpanded, labels: l, agentHandle, }) {
15
28
  const name = localizedSkillName(skill, locale) || skill.id;
16
29
  const description = localizedSkillDescription(skill, locale);
17
- const canDelete = isDeletableSkill(skill);
18
- const builtIn = isBuiltInSkill(skill);
19
- const expandLabel = labels?.expandLabel ?? t('Expand');
20
- const collapseLabel = labels?.collapseLabel ?? t('Collapse');
21
- const editLabel = labels?.editLabel ?? t('Edit');
22
- const overrideCreateLabel = labels?.overrideCreateLabel ?? t('Create override');
23
- const deleteLabel = labels?.deleteLabel ?? t('Delete');
24
- const deleteConfirmLabel = labels?.deleteConfirmLabel ?? t('Confirm delete');
25
- const cancelLabel = labels?.cancelLabel ?? t('Cancel');
26
- const enableToggleLabel = labels?.enableToggleLabel ?? t('Enabled');
27
- const loadingLabel = labels?.loadingLabel ?? t('Loading…');
28
- const filesHeading = labels?.filesHeading ?? t('Files');
29
- const noFilesLabel = labels?.noFilesLabel ?? t('No files.');
30
- const bodyHeading = labels?.bodyHeading ?? t('SKILL.md');
31
- const userBadge = labels?.userBadge ?? t('user');
32
- const overrideWarning = labels?.overrideWarning ?? t('This skill ships with the host. Editing it creates your own copy — the original is unaffected.');
33
- return (_jsxs("div", { className: `jini-skills-row${enabled ? '' : ' jini-skills-row-disabled'}${expanded ? ' jini-skills-row-expanded' : ''}`, "data-testid": `skill-row-${skill.id}`, children: [_jsxs("div", { className: "jini-skills-row-head", children: [_jsxs("button", { type: "button", className: "jini-skills-row-summary-btn", onClick: onToggleExpanded, "aria-expanded": expanded, title: expanded ? collapseLabel : expandLabel, ...agentHandleProps({}, { base: agentHandle, ...({ action: 'expand', role: 'button', label: name }) }), children: [_jsx(Icon, { name: "puzzle", size: 14, "aria-hidden": "true" }), _jsxs("span", { className: "jini-skills-row-summary", children: [_jsxs("span", { className: "jini-skills-row-summary-line", children: [_jsx("strong", { children: name }), _jsx("span", { className: "jini-skills-row-summary-mode", children: skill.mode }), skill.category ? (_jsx("span", { className: "jini-skills-row-summary-category", title: humanizeSkillCategory(skill.category), children: humanizeSkillCategory(skill.category) })) : null, skill.source === 'user' ? _jsx("span", { className: "jini-skills-row-summary-source", children: userBadge }) : null] }), description ? _jsx("span", { className: "jini-hint", children: description }) : null] }), _jsx(Icon, { name: "chevron-down", size: 14, "aria-hidden": "true" })] }), _jsxs("div", { className: "jini-skills-row-actions", children: [canDelete && confirmDelete ? (_jsxs("span", { className: "jini-skills-delete-confirm", role: "group", children: [_jsx("button", { type: "button", className: "jini-button jini-button-danger", onClick: onCommitDelete, "data-testid": "skills-delete-confirm", ...agentHandleProps({}, { base: agentHandle, ...({ action: 'delete-confirm', role: 'button', label: deleteConfirmLabel }) }), children: deleteConfirmLabel }), _jsx("button", { type: "button", className: "jini-button jini-button-ghost", onClick: onCancelDelete, ...agentHandleProps({}, { base: agentHandle, ...({ action: 'delete-cancel', role: 'button', label: cancelLabel }) }), children: cancelLabel })] })) : (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "jini-button jini-button-ghost", onClick: onStartEdit, "aria-label": builtIn ? overrideCreateLabel : editLabel, title: builtIn ? overrideCreateLabel : editLabel, "data-testid": "skills-edit", ...agentHandleProps({}, { base: agentHandle, ...({ action: 'edit', role: 'button', label: builtIn ? overrideCreateLabel : editLabel }) }), children: _jsx(Icon, { name: "edit", size: 13 }) }), canDelete ? (_jsx("button", { type: "button", className: "jini-button jini-button-ghost", onClick: onArmDelete, "aria-label": deleteLabel, title: deleteLabel, "data-testid": "skills-delete", ...agentHandleProps({}, { base: agentHandle, ...({ action: 'delete', role: 'button', label: deleteLabel }) }), children: _jsx(Icon, { name: "close", size: 13 }) })) : null] })), _jsxs("label", { className: "toggle-switch toggle-switch-sm jini-skills-row-enable", title: enableToggleLabel, children: [_jsx("input", { type: "checkbox", checked: enabled, onChange: (event) => onToggleEnabled(event.target.checked), "aria-label": enableToggleLabel, ...agentHandleProps({}, { base: agentHandle, ...({ action: 'enabled', role: 'checkbox', label: enableToggleLabel }) }) }), _jsx("span", { className: "toggle-slider" })] })] })] }), confirmBuiltInEdit ? (_jsxs("div", { className: "jini-empty-card jini-empty-card-warning", role: "alert", "data-testid": "skills-edit-builtin-warning", children: [_jsx("p", { children: overrideWarning }), _jsxs("div", { className: "jini-skills-draft-actions", children: [_jsx("button", { type: "button", className: "jini-button jini-button-ghost", onClick: onCancelBuiltInEdit, "data-testid": "skills-edit-builtin-cancel", ...agentHandleProps({}, { base: agentHandle, ...({ action: 'builtin-edit-cancel', role: 'button', label: cancelLabel }) }), children: cancelLabel }), _jsx("button", { type: "button", className: "jini-button jini-button-primary", onClick: onConfirmBuiltInEdit, "data-testid": "skills-edit-builtin-confirm", ...agentHandleProps({}, { base: agentHandle, ...({ action: 'builtin-edit-confirm', role: 'button', label: overrideCreateLabel }) }), children: overrideCreateLabel })] })] })) : null, expanded && !editing ? (_jsxs("div", { className: "jini-skills-row-detail", children: [_jsxs("div", { className: "jini-settings-subsection", children: [_jsx("h5", { children: bodyHeading }), bodyLoading ? _jsx("p", { className: "jini-hint", children: loadingLabel }) : _jsx("pre", { className: "jini-skills-body-preview", children: body ?? '' })] }), _jsxs("div", { className: "jini-settings-subsection", children: [_jsx("h5", { children: filesHeading }), filesLoading ? (_jsx("p", { className: "jini-hint", children: loadingLabel })) : !files || files.length === 0 ? (_jsx("p", { className: "jini-hint", children: noFilesLabel })) : (_jsx("ul", { className: "jini-skills-file-tree", children: files.map((entry) => (_jsxs("li", { className: "jini-skills-file-entry", style: { paddingLeft: skillFileTreeIndent(entry.path) }, children: [_jsx(Icon, { name: entry.kind === 'directory' ? 'folder' : 'file', size: 12 }), _jsx("span", { children: skillFileLeafName(entry.path) }), entry.kind === 'file' && typeof entry.size === 'number' ? (_jsx("span", { className: "jini-hint", children: formatSkillFileSize(entry.size) })) : null] }, entry.path))) }))] })] })) : null, editing && draft ? (_jsx(SkillDraftForm, { heading: builtIn ? overrideCreateLabel : editLabel, 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] }));
30
+ return (_jsxs("button", { type: "button", className: "jini-skills-row-summary-btn", onClick: onToggleExpanded, "aria-expanded": expanded, title: expanded ? l.collapseLabel : l.expandLabel, ...agentHandleProps({}, { base: agentHandle, action: 'expand', role: 'button', label: name }), children: [_jsx(Icon, { name: "puzzle", size: 14, "aria-hidden": "true" }), _jsxs("span", { className: "jini-skills-row-summary", children: [_jsxs("span", { className: "jini-skills-row-summary-line", children: [_jsx("strong", { children: name }), _jsx("span", { className: "jini-skills-row-summary-mode", children: skill.mode }), skill.category ? (_jsx("span", { className: "jini-skills-row-summary-category", title: humanizeSkillCategory(skill.category), children: humanizeSkillCategory(skill.category) })) : null, skill.source === 'user' ? _jsx("span", { className: "jini-skills-row-summary-source", children: l.userBadge }) : null] }), description ? _jsx("span", { className: "jini-hint", children: description }) : null] }), _jsx(Icon, { name: "chevron-down", size: 14, "aria-hidden": "true" })] }));
31
+ }
32
+ /** The armed-delete confirm/cancel pair that replaces the row actions. */
33
+ function SkillRowDeleteConfirm({ onCommitDelete, onCancelDelete, labels: l, agentHandle, }) {
34
+ return (_jsxs("span", { className: "jini-skills-delete-confirm", role: "group", children: [_jsx("button", { type: "button", className: "jini-button jini-button-danger", onClick: onCommitDelete, "data-testid": "skills-delete-confirm", ...agentHandleProps({}, { base: agentHandle, action: 'delete-confirm', role: 'button', label: l.deleteConfirmLabel }), children: l.deleteConfirmLabel }), _jsx("button", { type: "button", className: "jini-button jini-button-ghost", onClick: onCancelDelete, ...agentHandleProps({}, { base: agentHandle, action: 'delete-cancel', role: 'button', label: l.cancelLabel }), children: l.cancelLabel })] }));
35
+ }
36
+ /** Edit (or create-override) plus, for a user-owned skill only, delete. */
37
+ function SkillRowEditDeleteActions({ skill, editActionLabel, onStartEdit, onArmDelete, labels: l, agentHandle, }) {
38
+ return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "jini-button jini-button-ghost", onClick: onStartEdit, "aria-label": editActionLabel, title: editActionLabel, "data-testid": "skills-edit", ...agentHandleProps({}, { base: agentHandle, action: 'edit', role: 'button', label: editActionLabel }), children: _jsx(Icon, { name: "edit", size: 13 }) }), isDeletableSkill(skill) ? (_jsx("button", { type: "button", className: "jini-button jini-button-ghost", onClick: onArmDelete, "aria-label": l.deleteLabel, title: l.deleteLabel, "data-testid": "skills-delete", ...agentHandleProps({}, { base: agentHandle, action: 'delete', role: 'button', label: l.deleteLabel }), children: _jsx(Icon, { name: "close", size: 13 }) })) : null] }));
39
+ }
40
+ /** The "editing a built-in creates an override" confirmation card. */
41
+ function SkillRowBuiltInEditWarning({ onCancelBuiltInEdit, onConfirmBuiltInEdit, labels: l, agentHandle, }) {
42
+ return (_jsxs("div", { className: "jini-empty-card jini-empty-card-warning", role: "alert", "data-testid": "skills-edit-builtin-warning", children: [_jsx("p", { children: l.overrideWarning }), _jsxs("div", { className: "jini-skills-draft-actions", children: [_jsx("button", { type: "button", className: "jini-button jini-button-ghost", onClick: onCancelBuiltInEdit, "data-testid": "skills-edit-builtin-cancel", ...agentHandleProps({}, { base: agentHandle, action: 'builtin-edit-cancel', role: 'button', label: l.cancelLabel }), children: l.cancelLabel }), _jsx("button", { type: "button", className: "jini-button jini-button-primary", onClick: onConfirmBuiltInEdit, "data-testid": "skills-edit-builtin-confirm", ...agentHandleProps({}, { base: agentHandle, action: 'builtin-edit-confirm', role: 'button', label: l.overrideCreateLabel }), children: l.overrideCreateLabel })] })] }));
43
+ }
44
+ /** The expanded, non-editing detail: SKILL.md preview plus the file tree. */
45
+ function SkillRowDetail({ body, bodyLoading, files, filesLoading, labels: l, }) {
46
+ return (_jsxs("div", { className: "jini-skills-row-detail", children: [_jsxs("div", { className: "jini-settings-subsection", children: [_jsx("h5", { children: l.bodyHeading }), bodyLoading ? _jsx("p", { className: "jini-hint", children: l.loadingLabel }) : _jsx("pre", { className: "jini-skills-body-preview", children: body ?? '' })] }), _jsxs("div", { className: "jini-settings-subsection", children: [_jsx("h5", { children: l.filesHeading }), _jsx(SkillFileTree, { files: files, filesLoading: filesLoading, labels: l })] })] }));
47
+ }
48
+ function SkillFileTree({ files, filesLoading, labels: l, }) {
49
+ if (filesLoading)
50
+ return _jsx("p", { className: "jini-hint", children: l.loadingLabel });
51
+ if (!files || files.length === 0)
52
+ return _jsx("p", { className: "jini-hint", children: l.noFilesLabel });
53
+ return (_jsx("ul", { className: "jini-skills-file-tree", children: files.map((entry) => (_jsxs("li", { className: "jini-skills-file-entry", style: { paddingLeft: skillFileTreeIndent(entry.path) }, children: [_jsx(Icon, { name: entry.kind === 'directory' ? 'folder' : 'file', size: 12 }), _jsx("span", { children: skillFileLeafName(entry.path) }), entry.kind === 'file' && typeof entry.size === 'number' ? (_jsx("span", { className: "jini-hint", children: formatSkillFileSize(entry.size) })) : null] }, entry.path))) }));
34
54
  }
35
55
  //# sourceMappingURL=SkillRow.js.map