@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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @jini-ai/ui
2
2
 
3
+ ## 0.5.6 — 2026-10-22
4
+
5
+ - 2026-10-22: MCP-UI question cards follow dark hosts, optional useCachedLoader fetch, trust options, html-editor fallbacks removed, coverage.
6
+
3
7
  ## 0.5.5 — 2026-10-16
4
8
 
5
9
  - `admin-widgets` Select paints group headings and per-option badges (dc770a41) and is pane-aware; image fields name the file (bc1dbf0d).
@@ -6,50 +6,52 @@ const safeValue = (value) => typeof value === 'string' && value.trim().length >
6
6
  const component = '(?:[+-]?(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:%|deg|rad|grad|turn)?|none)';
7
7
  const components = new RegExp(`^(?:rgb|rgba|hsl|hsla|oklch|oklab|lab|lch)\\(\\s*${component}(?:\\s*,\\s*|\\s+)${component}(?:\\s*,\\s*|\\s+)${component}(?:(?:\\s*[,/]\\s*)${component})?\\s*\\)$`, 'i');
8
8
  const colorValue = (value) => safeValue(value)
9
- && (/^#[\da-f]{3}(?:[\da-f]{1}|[\da-f]{3}|[\da-f]{5})?$/i.test(value)
9
+ && (/^#[\da-f]{3}(?:[\da-f]|[\da-f]{3}|[\da-f]{5})?$/i.test(value)
10
10
  || components.test(value)
11
11
  || /^(?:transparent|black|white|red|green|blue|gray|grey)$/i.test(value));
12
+ const fontStacksValid = ({ fonts }) => record(fonts) && safeValue(fonts.body) && safeValue(fonts.heading) && (fonts.mono === undefined || safeValue(fonts.mono));
13
+ const stylesheetUrlValid = ({ url }) => {
14
+ if (typeof url !== 'string' || url.length > 4096 || /[\x00-\x20<>]/.test(url))
15
+ return false;
16
+ try {
17
+ return new URL(url, 'https://font-base.invalid').protocol === 'https:';
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ };
23
+ const fontLoadValid = ({ load }) => load === undefined || (Array.isArray(load) && load.length <= 16 && load.every((url) => stylesheetUrlValid({ url })));
24
+ function fontErrors({ fonts }) {
25
+ const errors = [];
26
+ if (!fontStacksValid({ fonts }))
27
+ errors.push('fonts must provide body and heading font stacks');
28
+ if (record(fonts) && !fontLoadValid({ load: fonts.load }))
29
+ errors.push('fonts.load must contain relative or HTTPS stylesheet URLs');
30
+ return errors;
31
+ }
32
+ function paletteErrors({ scheme, colors }) {
33
+ if (!record(colors))
34
+ return [`${scheme} must be a complete palette`];
35
+ const known = adminThemeColorKeys;
36
+ const invalid = adminThemeColorKeys.filter((key) => !Object.hasOwn(colors, key) || !colorValue(colors[key]))
37
+ .map((key) => `${scheme}.${key} must be a concrete CSS color`);
38
+ const unknown = Object.keys(colors).filter((key) => !known.includes(key)).map((key) => `${scheme}.${key} is not a theme color`);
39
+ return [...invalid, ...unknown];
40
+ }
41
+ const radiusValid = ({ radius }) => radius === undefined || (typeof radius === 'string' && /^(?:0|\d+(?:\.\d+)?(?:px|rem|em))$/.test(radius));
42
+ const densityValid = ({ density }) => density === undefined || (typeof density === 'number' && Number.isFinite(density) && density >= 0.5 && density <= 2);
12
43
  /** Validate serializable theme data before any DOM or font-loading effect. */
13
44
  export function validateAdminTheme({ theme }) {
14
- const errors = [];
15
45
  if (!record(theme))
16
46
  return { valid: false, errors: ['Theme must be an object'] };
17
- if (!safeValue(theme.name))
18
- errors.push('name must be a nonempty safe string');
19
- const fonts = theme.fonts;
20
- if (!record(fonts) || !safeValue(fonts.body) || !safeValue(fonts.heading) || (fonts.mono !== undefined && !safeValue(fonts.mono))) {
21
- errors.push('fonts must provide body and heading font stacks');
22
- }
23
- if (record(fonts) && fonts.load !== undefined) {
24
- if (!Array.isArray(fonts.load) || fonts.load.length > 16 || !fonts.load.every((url) => {
25
- if (typeof url !== 'string' || url.length > 4096 || /[\x00-\x20<>]/.test(url))
26
- return false;
27
- try {
28
- return new URL(url, 'https://font-base.invalid').protocol === 'https:';
29
- }
30
- catch {
31
- return false;
32
- }
33
- }))
34
- errors.push('fonts.load must contain relative or HTTPS stylesheet URLs');
35
- }
36
- for (const scheme of ['light', 'dark']) {
37
- const colors = theme[scheme];
38
- if (!record(colors)) {
39
- errors.push(`${scheme} must be a complete palette`);
40
- continue;
41
- }
42
- for (const key of adminThemeColorKeys)
43
- if (!Object.hasOwn(colors, key) || !colorValue(colors[key]))
44
- errors.push(`${scheme}.${key} must be a concrete CSS color`);
45
- for (const key of Object.keys(colors))
46
- if (!adminThemeColorKeys.includes(key))
47
- errors.push(`${scheme}.${key} is not a theme color`);
48
- }
49
- if (theme.radius !== undefined && (typeof theme.radius !== 'string' || !/^(?:0|\d+(?:\.\d+)?(?:px|rem|em))$/.test(theme.radius)))
50
- errors.push('radius must be a nonnegative CSS length');
51
- if (theme.density !== undefined && (typeof theme.density !== 'number' || !Number.isFinite(theme.density) || theme.density < 0.5 || theme.density > 2))
52
- errors.push('density must be between 0.5 and 2');
47
+ const errors = [
48
+ ...(safeValue(theme.name) ? [] : ['name must be a nonempty safe string']),
49
+ ...fontErrors({ fonts: theme.fonts }),
50
+ ...paletteErrors({ scheme: 'light', colors: theme.light }),
51
+ ...paletteErrors({ scheme: 'dark', colors: theme.dark }),
52
+ ...(radiusValid({ radius: theme.radius }) ? [] : ['radius must be a nonnegative CSS length']),
53
+ ...(densityValid({ density: theme.density }) ? [] : ['density must be between 0.5 and 2']),
54
+ ];
53
55
  return errors.length ? { valid: false, errors } : { valid: true, theme: theme };
54
56
  }
55
57
  //# sourceMappingURL=validation.js.map
@@ -11,6 +11,19 @@ const PROVIDER_ACTION_PROPS = new Map([
11
11
  ['shadcn.text-input', 'onValueChange'],
12
12
  ['shadcn.select', 'onValueChange'],
13
13
  ]);
14
+ function renderRegistryComponent({ component, registry, componentId, onAction }, _options = {}) {
15
+ const entry = registry.resolveById({ id: component.component });
16
+ if (!entry) {
17
+ return (_jsxs("span", { className: "a2ui-placeholder", "data-a2ui-status": "unrenderable-type", "data-a2ui-component-id": componentId, children: ["\u26A0 no renderer yet for component type \"", component.component, "\""] }));
18
+ }
19
+ const RegistryComponent = entry.Component;
20
+ // `onRowClick` belongs to `native.data-table`/`shadcn.data-table`, not a generic
21
+ // per-component-type feedback convention — each non-table provider needs an explicit
22
+ // callback contract, supplied by the built-in map or its registry entry, not a guess.
23
+ // Unannotated custom entries retain the old table callback for compatibility.
24
+ const actionProp = entry.actionProp ?? PROVIDER_ACTION_PROPS.get(entry.id) ?? 'onRowClick';
25
+ return _jsx(RegistryComponent, { ...component.props, [actionProp]: () => onAction(componentId) });
26
+ }
14
27
  /**
15
28
  * Recursively walks a surface's component map from `componentId` — same shape as
16
29
  * `@jini-ai/chat/react`'s `A2uiSurfaceCard`'s `RenderComponent` (cycle guard via `ancestors`,
@@ -59,19 +72,8 @@ function RenderNode({ interpreter, surfaceId, registry, componentId, ancestors,
59
72
  return (_jsx("div", { className: "a2ui-card", "data-a2ui-component-id": componentId, children: _jsx(RenderNode, { ...childProps, componentId: String(component.props.child) }) }));
60
73
  case 'Button':
61
74
  return (_jsx("button", { type: "button", className: `a2ui-button a2ui-button-${String(component.props.variant)}`, "data-a2ui-component-id": componentId, onClick: () => onAction(componentId), children: _jsx(RenderNode, { ...childProps, componentId: String(component.props.child) }) }));
62
- default: {
63
- const entry = registry.resolveById({ id: component.component });
64
- if (!entry) {
65
- return (_jsxs("span", { className: "a2ui-placeholder", "data-a2ui-status": "unrenderable-type", "data-a2ui-component-id": componentId, children: ["\u26A0 no renderer yet for component type \"", component.component, "\""] }));
66
- }
67
- const RegistryComponent = entry.Component;
68
- // `onRowClick` belongs to `native.data-table`/`shadcn.data-table`, not a generic
69
- // per-component-type feedback convention — each non-table provider needs an explicit
70
- // callback contract, supplied by the built-in map or its registry entry, not a guess.
71
- // Unannotated custom entries retain the old table callback for compatibility.
72
- const actionProp = entry.actionProp ?? PROVIDER_ACTION_PROPS.get(entry.id) ?? 'onRowClick';
73
- return _jsx(RegistryComponent, { ...component.props, [actionProp]: () => onAction(componentId) });
74
- }
75
+ default:
76
+ return renderRegistryComponent({ component, registry, componentId, onAction });
75
77
  }
76
78
  }
77
79
  /**
@@ -3,6 +3,65 @@ import { useMemo, useState } from 'react';
3
3
  import { useT } from '../../../i18n/index.js';
4
4
  import { deriveAboutUpdateControl, isAboutUpdateActionDisabled } from '../../rules.js';
5
5
  import { useSilentUpdatesToggle } from '../hooks/useSilentUpdatesToggle.js';
6
+ /** Built-in (untranslated) copy for every label a host may override. */
7
+ const DEFAULT_LABELS = {
8
+ versionLabel: 'Version',
9
+ runtimeLabel: 'Runtime',
10
+ packagedLabel: 'Packaged',
11
+ developmentLabel: 'Development build',
12
+ channelLabel: 'Channel',
13
+ platformLabel: 'Platform',
14
+ archLabel: 'Architecture',
15
+ releaseLinkLabel: 'View releases',
16
+ actionFailedLabel: 'Something went wrong. Try again.',
17
+ versionUnavailableLabel: 'Version information is unavailable.',
18
+ silentUpdatesLabel: 'Install updates automatically',
19
+ silentUpdatesHint: 'Apply updates in the background without asking first.',
20
+ };
21
+ const LABEL_KEYS = Object.keys(DEFAULT_LABELS);
22
+ /** Each label is the host override when given, else the translated default. */
23
+ function resolveLabels({ t }, { labels }) {
24
+ const resolved = { ...DEFAULT_LABELS };
25
+ for (const key of LABEL_KEYS)
26
+ resolved[key] = labels?.[key] ?? t(DEFAULT_LABELS[key]);
27
+ return resolved;
28
+ }
29
+ /**
30
+ * The one thing that's genuinely render-local: whether an update action this
31
+ * tab itself kicked off is still in flight, and whether the last one failed.
32
+ */
33
+ function useUpdateActionRunner({ onPerformUpdateAction, }) {
34
+ const [actionBusy, setActionBusy] = useState(false);
35
+ const [actionFailed, setActionFailed] = useState(false);
36
+ const run = (action) => {
37
+ setActionBusy(true);
38
+ setActionFailed(false);
39
+ void onPerformUpdateAction(action).then(() => setActionBusy(false), () => {
40
+ setActionBusy(false);
41
+ setActionFailed(true);
42
+ });
43
+ };
44
+ return { actionBusy, actionFailed, run };
45
+ }
46
+ function UpdateActions({ control, primaryLabel, actionDisabled, onPrimaryAction, onOpenReleaseLink, releaseLinkLabel, }) {
47
+ // Origin styled 'download'/'install'/'quit' as primary and left 'check'
48
+ // plain; simplified to "any actionable state reads as primary, any inert
49
+ // one reads as ghost" — same visual intent, one fewer hardcoded branch,
50
+ // and it falls straight out of a field `deriveAboutUpdateControl` already
51
+ // computes rather than re-deriving OD's three-way action match.
52
+ const primaryButtonClassName = `jini-button${control.primaryAction ? ' jini-button-primary' : ' jini-button-ghost'}`;
53
+ return (_jsxs("div", { className: "jini-settings-about-actions", children: [primaryLabel ? (_jsx("button", { type: "button", className: primaryButtonClassName, disabled: actionDisabled, onClick: onPrimaryAction, "data-testid": "about-update-action", children: primaryLabel })) : null, control.showReleaseLink ? (_jsx("button", { type: "button", className: "jini-button jini-button-ghost", onClick: onOpenReleaseLink, "data-testid": "about-release-link", children: releaseLinkLabel })) : null] }));
54
+ }
55
+ /** A `<dt>/<dd>` row rendered only when the host supplied a value for it. */
56
+ function OptionalFactRow({ label, value }) {
57
+ return value ? (_jsxs("div", { children: [_jsx("dt", { children: label }), _jsx("dd", { children: value })] })) : null;
58
+ }
59
+ function VersionDetails({ appVersionInfo, labels, statusTone, statusText, actions, actionFailed }) {
60
+ return (_jsxs("dl", { className: "jini-settings-about-list", children: [_jsxs("div", { className: "jini-settings-about-version-row", children: [_jsxs("div", { className: "jini-settings-about-version-copy", children: [_jsx("dt", { children: labels.versionLabel }), _jsx("span", { className: "jini-settings-about-version-num", children: appVersionInfo.version }), _jsx("dd", { "aria-live": "polite", className: `jini-settings-about-status jini-settings-about-status--${statusTone}`, children: statusText })] }), actions] }), actionFailed ? (_jsx("p", { className: "jini-hint jini-settings-about-error", role: "status", children: labels.actionFailedLabel })) : null, _jsxs("div", { children: [_jsx("dt", { children: labels.runtimeLabel }), _jsx("dd", { children: appVersionInfo.packaged === false ? labels.developmentLabel : labels.packagedLabel })] }), _jsx(OptionalFactRow, { label: labels.channelLabel, value: appVersionInfo.channel }), _jsx(OptionalFactRow, { label: labels.platformLabel, value: appVersionInfo.platform }), _jsx(OptionalFactRow, { label: labels.archLabel, value: appVersionInfo.arch })] }));
61
+ }
62
+ function SilentUpdatesToggle({ silentUpdates, labels, }) {
63
+ return (_jsx("div", { className: "jini-settings-subsection jini-settings-about-silent-updates", children: _jsxs("label", { className: "jini-settings-about-toggle", children: [_jsx("input", { type: "checkbox", checked: silentUpdates.allowSilentUpdates, disabled: silentUpdates.busy, onChange: (event) => silentUpdates.toggle(event.currentTarget.checked), "data-testid": "about-silent-updates-toggle" }), _jsxs("span", { className: "jini-settings-about-toggle-copy", children: [_jsx("span", { children: labels.silentUpdatesLabel }), _jsx("span", { className: "jini-hint", children: labels.silentUpdatesHint })] })] }) }));
64
+ }
6
65
  /**
7
66
  * App version + auto-update panel, plus the "allow silent updates"
8
67
  * preference. Origin: the `activeSection === 'about'` block in
@@ -25,46 +84,17 @@ import { useSilentUpdatesToggle } from '../hooks/useSilentUpdatesToggle.js';
25
84
  */
26
85
  export function AboutTab({ appVersionInfo, updaterModel, onPerformUpdateAction, onOpenReleaseLink, allowSilentUpdates = false, onSilentUpdatePreferenceChange, labels, }) {
27
86
  const t = useT();
28
- const [actionBusy, setActionBusy] = useState(false);
29
- const [actionFailed, setActionFailed] = useState(false);
87
+ const { actionBusy, actionFailed, run } = useUpdateActionRunner({ onPerformUpdateAction });
30
88
  const silentUpdates = useSilentUpdatesToggle({ allowSilentUpdates, onSilentUpdatePreferenceChange });
31
- const control = useMemo(() => deriveAboutUpdateControl(updaterModel, appVersionInfo ?? null), [updaterModel, appVersionInfo]);
89
+ const control = useMemo(() => deriveAboutUpdateControl(updaterModel, appVersionInfo), [updaterModel, appVersionInfo]);
32
90
  const actionDisabled = isAboutUpdateActionDisabled(control, updaterModel, actionBusy);
33
- const versionLabel = labels?.versionLabel ?? t('Version');
34
- const runtimeLabel = labels?.runtimeLabel ?? t('Runtime');
35
- const packagedLabel = labels?.packagedLabel ?? t('Packaged');
36
- const developmentLabel = labels?.developmentLabel ?? t('Development build');
37
- const channelLabel = labels?.channelLabel ?? t('Channel');
38
- const platformLabel = labels?.platformLabel ?? t('Platform');
39
- const archLabel = labels?.archLabel ?? t('Architecture');
40
- const releaseLinkLabel = labels?.releaseLinkLabel ?? t('View releases');
41
- const actionFailedLabel = labels?.actionFailedLabel ?? t('Something went wrong. Try again.');
42
- const versionUnavailableLabel = labels?.versionUnavailableLabel ?? t('Version information is unavailable.');
43
- const silentUpdatesLabel = labels?.silentUpdatesLabel ?? t('Install updates automatically');
44
- const silentUpdatesHint = labels?.silentUpdatesHint ?? t('Apply updates in the background without asking first.');
45
- const statusText = t(control.statusKey, control.statusVars);
46
- const primaryLabel = control.primaryLabelKey ? t(control.primaryLabelKey) : null;
47
- // Origin styled 'download'/'install'/'quit' as primary and left 'check'
48
- // plain; simplified to "any actionable state reads as primary, any inert
49
- // one reads as ghost" — same visual intent, one fewer hardcoded branch,
50
- // and it falls straight out of a field `deriveAboutUpdateControl` already
51
- // computes rather than re-deriving OD's three-way action match.
52
- const primaryButtonClassName = `jini-button${control.primaryAction ? ' jini-button-primary' : ' jini-button-ghost'}`;
91
+ const resolvedLabels = resolveLabels({ t }, { labels });
53
92
  // `handlePrimaryAction` only exists when there is an action to run — it is
54
93
  // `undefined` in every state that renders the button disabled-and-inert
55
94
  // (e.g. "downloading"), so there is no dead "disabled but somehow clicked"
56
95
  // branch to guard against inside the handler itself.
57
96
  const { primaryAction } = control;
58
- const handlePrimaryAction = primaryAction == null
59
- ? undefined
60
- : () => {
61
- setActionBusy(true);
62
- setActionFailed(false);
63
- void onPerformUpdateAction(primaryAction).then(() => setActionBusy(false), () => {
64
- setActionBusy(false);
65
- setActionFailed(true);
66
- });
67
- };
68
- return (_jsxs("section", { className: "jini-settings-section jini-settings-about", children: [appVersionInfo ? (_jsxs("dl", { className: "jini-settings-about-list", children: [_jsxs("div", { className: "jini-settings-about-version-row", children: [_jsxs("div", { className: "jini-settings-about-version-copy", children: [_jsx("dt", { children: versionLabel }), _jsx("span", { className: "jini-settings-about-version-num", children: appVersionInfo.version }), _jsx("dd", { "aria-live": "polite", className: `jini-settings-about-status jini-settings-about-status--${control.statusTone}`, children: statusText })] }), _jsxs("div", { className: "jini-settings-about-actions", children: [primaryLabel ? (_jsx("button", { type: "button", className: primaryButtonClassName, disabled: actionDisabled, onClick: handlePrimaryAction, "data-testid": "about-update-action", children: primaryLabel })) : null, control.showReleaseLink ? (_jsx("button", { type: "button", className: "jini-button jini-button-ghost", onClick: onOpenReleaseLink, "data-testid": "about-release-link", children: releaseLinkLabel })) : null] })] }), actionFailed ? (_jsx("p", { className: "jini-hint jini-settings-about-error", role: "status", children: actionFailedLabel })) : null, _jsxs("div", { children: [_jsx("dt", { children: runtimeLabel }), _jsx("dd", { children: appVersionInfo.packaged === false ? developmentLabel : packagedLabel })] }), appVersionInfo.channel ? (_jsxs("div", { children: [_jsx("dt", { children: channelLabel }), _jsx("dd", { children: appVersionInfo.channel })] })) : null, appVersionInfo.platform ? (_jsxs("div", { children: [_jsx("dt", { children: platformLabel }), _jsx("dd", { children: appVersionInfo.platform })] })) : null, appVersionInfo.arch ? (_jsxs("div", { children: [_jsx("dt", { children: archLabel }), _jsx("dd", { children: appVersionInfo.arch })] })) : null] })) : (_jsx("div", { className: "jini-empty-card", children: versionUnavailableLabel })), onSilentUpdatePreferenceChange ? (_jsx("div", { className: "jini-settings-subsection jini-settings-about-silent-updates", children: _jsxs("label", { className: "jini-settings-about-toggle", children: [_jsx("input", { type: "checkbox", checked: silentUpdates.allowSilentUpdates, disabled: silentUpdates.busy, onChange: (event) => silentUpdates.toggle(event.currentTarget.checked), "data-testid": "about-silent-updates-toggle" }), _jsxs("span", { className: "jini-settings-about-toggle-copy", children: [_jsx("span", { children: silentUpdatesLabel }), _jsx("span", { className: "jini-hint", children: silentUpdatesHint })] })] }) })) : null] }));
97
+ const handlePrimaryAction = primaryAction == null ? undefined : () => run(primaryAction);
98
+ return (_jsxs("section", { className: "jini-settings-section jini-settings-about", children: [appVersionInfo ? (_jsx(VersionDetails, { appVersionInfo: appVersionInfo, labels: resolvedLabels, statusTone: control.statusTone, statusText: t(control.statusKey, control.statusVars), actionFailed: actionFailed, actions: _jsx(UpdateActions, { control: control, primaryLabel: control.primaryLabelKey ? t(control.primaryLabelKey) : null, actionDisabled: actionDisabled, onPrimaryAction: handlePrimaryAction, onOpenReleaseLink: onOpenReleaseLink, releaseLinkLabel: resolvedLabels.releaseLinkLabel }) })) : (_jsx("div", { className: "jini-empty-card", children: resolvedLabels.versionUnavailableLabel })), onSilentUpdatePreferenceChange ? _jsx(SilentUpdatesToggle, { silentUpdates: silentUpdates, labels: resolvedLabels }) : null] }));
69
99
  }
70
100
  //# sourceMappingURL=AboutTab.js.map
@@ -46,7 +46,7 @@ export declare const ABOUT_UPDATE_KEYS: {
46
46
  * build — one shared "unsupported" outcome, since the operator can do
47
47
  * nothing about any of them from this panel.
48
48
  *
49
- * After that it is a switch over the reported state. Two subtleties worth
49
+ * After that it is a lookup of the reported state in `STATE_DERIVERS`. Two subtleties worth
50
50
  * keeping when reading it:
51
51
  *
52
52
  * - `primaryLabelKey` can be set while `primaryAction` is `null`. That is the
@@ -56,7 +56,7 @@ export declare const ABOUT_UPDATE_KEYS: {
56
56
  * about to disappear (installing, or quitting to let an installer run).
57
57
  * Offering a navigation there invites a click that cannot complete.
58
58
  *
59
- * @complexity O(1) — a fixed switch, no iteration.
59
+ * @complexity O(1) — a fixed-size map lookup, no iteration.
60
60
  */
61
61
  export declare function deriveAboutUpdateControl(model: UpdaterModel, appVersionInfo: AppVersionInfo | null): AboutUpdateControl;
62
62
  /**
@@ -29,6 +29,154 @@ export const ABOUT_UPDATE_KEYS = {
29
29
  notChecked: 'settings.updateStatusNotChecked',
30
30
  check: 'settings.updateCheck',
31
31
  };
32
+ const K = ABOUT_UPDATE_KEYS;
33
+ /**
34
+ * Shared by the capability gate and the reported `unsupported` state — one
35
+ * outcome, since the operator can do nothing about either from this panel.
36
+ */
37
+ const UNSUPPORTED_CONTROL = {
38
+ primaryAction: null,
39
+ primaryLabelKey: null,
40
+ showReleaseLink: true,
41
+ statusKey: K.unsupported,
42
+ statusTone: 'warning',
43
+ };
44
+ const DEVELOPMENT_CONTROL = {
45
+ primaryAction: null,
46
+ primaryLabelKey: null,
47
+ showReleaseLink: true,
48
+ statusKey: K.development,
49
+ statusTone: 'neutral',
50
+ };
51
+ /** `{ statusVars: { version } }` when the version is known (truthy), else nothing. */
52
+ function versionVars(model) {
53
+ return model.availableVersion ? { statusVars: { version: model.availableVersion } } : {};
54
+ }
55
+ /** Whether either install route (hand off to an installer, or apply in place) is open. */
56
+ function canInstall(model) {
57
+ return Boolean(model.canOpenInstaller || model.canApplyInPlace);
58
+ }
59
+ function deriveAvailable(model) {
60
+ return {
61
+ primaryAction: model.canDownload ? 'download' : null,
62
+ primaryLabelKey: model.canDownload ? K.download : null,
63
+ showReleaseLink: true,
64
+ statusKey: model.availableVersion ? K.available : K.availableUnknown,
65
+ statusTone: 'warning',
66
+ ...versionVars(model),
67
+ };
68
+ }
69
+ function deriveDownloading(model) {
70
+ const percent = model.downloadProgress?.percent;
71
+ const known = typeof percent === 'number';
72
+ return {
73
+ primaryAction: null,
74
+ primaryLabelKey: K.downloadingLabel,
75
+ showReleaseLink: true,
76
+ statusKey: known ? K.downloadingPercent : K.downloading,
77
+ statusTone: 'neutral',
78
+ ...(known ? { statusVars: { percent } } : {}),
79
+ };
80
+ }
81
+ function deriveDownloaded(model) {
82
+ const payload = model.updateKind === 'payload';
83
+ // The installer is already running; the only useful act left is to get
84
+ // out of its way.
85
+ if (model.installerOpened && model.canQuitAfterInstallerOpen) {
86
+ return {
87
+ primaryAction: 'quit',
88
+ primaryLabelKey: K.quit,
89
+ showReleaseLink: false,
90
+ statusKey: payload ? K.installingRestart : K.opening,
91
+ statusTone: 'neutral',
92
+ };
93
+ }
94
+ const installable = canInstall(model);
95
+ return {
96
+ primaryAction: installable ? 'install' : null,
97
+ primaryLabelKey: installable ? (payload ? K.installRestart : K.installNow) : null,
98
+ showReleaseLink: true,
99
+ statusKey: model.availableVersion ? K.ready : K.readyUnknown,
100
+ statusTone: 'success',
101
+ ...versionVars(model),
102
+ };
103
+ }
104
+ /**
105
+ * Retry the furthest-along step that is still possible: re-install a
106
+ * download we already have, else re-download a version we know about,
107
+ * else start over. Sending the operator back to "check" when a finished
108
+ * download is sitting on disk would throw away the expensive part.
109
+ */
110
+ function errorRetryAction(model) {
111
+ if (model.status?.downloadPath != null && canInstall(model))
112
+ return 'install';
113
+ if (model.availableVersion != null && model.canDownload)
114
+ return 'download';
115
+ return 'check';
116
+ }
117
+ function deriveError(model) {
118
+ return {
119
+ primaryAction: errorRetryAction(model),
120
+ primaryLabelKey: K.retry,
121
+ showReleaseLink: true,
122
+ statusKey: K.failed,
123
+ statusTone: 'error',
124
+ };
125
+ }
126
+ /**
127
+ * `idle`, and any state a newer backend reports that this build predates.
128
+ * Falling through to "you can check" is the safe default: it offers the
129
+ * one action that is valid from any unknown state.
130
+ */
131
+ const IDLE_CONTROL = {
132
+ primaryAction: 'check',
133
+ primaryLabelKey: K.check,
134
+ showReleaseLink: true,
135
+ statusKey: K.notChecked,
136
+ statusTone: 'neutral',
137
+ };
138
+ /**
139
+ * One deriver per reported state. A `Map` (not an object literal) so a
140
+ * state string that happens to name an `Object.prototype` member can never
141
+ * resolve to anything but the idle fallback.
142
+ */
143
+ const STATE_DERIVERS = new Map([
144
+ [
145
+ 'checking',
146
+ () => ({
147
+ primaryAction: null,
148
+ primaryLabelKey: K.checkingLabel,
149
+ showReleaseLink: true,
150
+ statusKey: K.checking,
151
+ statusTone: 'neutral',
152
+ }),
153
+ ],
154
+ [
155
+ 'not-available',
156
+ () => ({
157
+ primaryAction: 'check',
158
+ primaryLabelKey: K.recheck,
159
+ showReleaseLink: true,
160
+ statusKey: K.upToDate,
161
+ statusTone: 'success',
162
+ }),
163
+ ],
164
+ ['available', deriveAvailable],
165
+ ['downloading', deriveDownloading],
166
+ ['downloaded', deriveDownloaded],
167
+ [
168
+ 'installing',
169
+ () => ({
170
+ primaryAction: null,
171
+ primaryLabelKey: K.installingRestart,
172
+ showReleaseLink: false,
173
+ statusKey: K.installing,
174
+ statusTone: 'neutral',
175
+ }),
176
+ ],
177
+ ['error', deriveError],
178
+ ['unsupported', () => ({ ...UNSUPPORTED_CONTROL })],
179
+ ]);
32
180
  /**
33
181
  * Derives what the About panel's update row should show.
34
182
  *
@@ -45,7 +193,7 @@ export const ABOUT_UPDATE_KEYS = {
45
193
  * build — one shared "unsupported" outcome, since the operator can do
46
194
  * nothing about any of them from this panel.
47
195
  *
48
- * After that it is a switch over the reported state. Two subtleties worth
196
+ * After that it is a lookup of the reported state in `STATE_DERIVERS`. Two subtleties worth
49
197
  * keeping when reading it:
50
198
  *
51
199
  * - `primaryLabelKey` can be set while `primaryAction` is `null`. That is the
@@ -55,136 +203,15 @@ export const ABOUT_UPDATE_KEYS = {
55
203
  * about to disappear (installing, or quitting to let an installer run).
56
204
  * Offering a navigation there invites a click that cannot complete.
57
205
  *
58
- * @complexity O(1) — a fixed switch, no iteration.
206
+ * @complexity O(1) — a fixed-size map lookup, no iteration.
59
207
  */
60
208
  export function deriveAboutUpdateControl(model, appVersionInfo) {
61
- const K = ABOUT_UPDATE_KEYS;
62
- if (appVersionInfo?.packaged === false) {
63
- return {
64
- primaryAction: null,
65
- primaryLabelKey: null,
66
- showReleaseLink: true,
67
- statusKey: K.development,
68
- statusTone: 'neutral',
69
- };
70
- }
71
- if (model.environment !== 'desktop' || !model.enabled || !model.supported) {
72
- return {
73
- primaryAction: null,
74
- primaryLabelKey: null,
75
- showReleaseLink: true,
76
- statusKey: K.unsupported,
77
- statusTone: 'warning',
78
- };
79
- }
80
- switch (model.status?.state) {
81
- case 'checking':
82
- return {
83
- primaryAction: null,
84
- primaryLabelKey: K.checkingLabel,
85
- showReleaseLink: true,
86
- statusKey: K.checking,
87
- statusTone: 'neutral',
88
- };
89
- case 'not-available':
90
- return {
91
- primaryAction: 'check',
92
- primaryLabelKey: K.recheck,
93
- showReleaseLink: true,
94
- statusKey: K.upToDate,
95
- statusTone: 'success',
96
- };
97
- case 'available':
98
- return {
99
- primaryAction: model.canDownload ? 'download' : null,
100
- primaryLabelKey: model.canDownload ? K.download : null,
101
- showReleaseLink: true,
102
- statusKey: model.availableVersion ? K.available : K.availableUnknown,
103
- statusTone: 'warning',
104
- ...(model.availableVersion ? { statusVars: { version: model.availableVersion } } : {}),
105
- };
106
- case 'downloading': {
107
- const percent = model.downloadProgress?.percent;
108
- const known = typeof percent === 'number';
109
- return {
110
- primaryAction: null,
111
- primaryLabelKey: K.downloadingLabel,
112
- showReleaseLink: true,
113
- statusKey: known ? K.downloadingPercent : K.downloading,
114
- statusTone: 'neutral',
115
- ...(known ? { statusVars: { percent } } : {}),
116
- };
117
- }
118
- case 'downloaded': {
119
- // The installer is already running; the only useful act left is to get
120
- // out of its way.
121
- if (model.installerOpened && model.canQuitAfterInstallerOpen) {
122
- return {
123
- primaryAction: 'quit',
124
- primaryLabelKey: K.quit,
125
- showReleaseLink: false,
126
- statusKey: model.updateKind === 'payload' ? K.installingRestart : K.opening,
127
- statusTone: 'neutral',
128
- };
129
- }
130
- const canInstall = Boolean(model.canOpenInstaller || model.canApplyInPlace);
131
- return {
132
- primaryAction: canInstall ? 'install' : null,
133
- primaryLabelKey: canInstall ? (model.updateKind === 'payload' ? K.installRestart : K.installNow) : null,
134
- showReleaseLink: true,
135
- statusKey: model.availableVersion ? K.ready : K.readyUnknown,
136
- statusTone: 'success',
137
- ...(model.availableVersion ? { statusVars: { version: model.availableVersion } } : {}),
138
- };
139
- }
140
- case 'installing':
141
- return {
142
- primaryAction: null,
143
- primaryLabelKey: K.installingRestart,
144
- showReleaseLink: false,
145
- statusKey: K.installing,
146
- statusTone: 'neutral',
147
- };
148
- case 'error': {
149
- // Retry the furthest-along step that is still possible: re-install a
150
- // download we already have, else re-download a version we know about,
151
- // else start over. Sending the operator back to "check" when a finished
152
- // download is sitting on disk would throw away the expensive part.
153
- const canRetryInstall = model.status?.downloadPath != null && Boolean(model.canOpenInstaller || model.canApplyInPlace);
154
- const primaryAction = canRetryInstall
155
- ? 'install'
156
- : model.availableVersion != null && model.canDownload
157
- ? 'download'
158
- : 'check';
159
- return {
160
- primaryAction,
161
- primaryLabelKey: K.retry,
162
- showReleaseLink: true,
163
- statusKey: K.failed,
164
- statusTone: 'error',
165
- };
166
- }
167
- case 'unsupported':
168
- return {
169
- primaryAction: null,
170
- primaryLabelKey: null,
171
- showReleaseLink: true,
172
- statusKey: K.unsupported,
173
- statusTone: 'warning',
174
- };
175
- // `idle`, and any state a newer backend reports that this build predates.
176
- // Falling through to "you can check" is the safe default: it offers the
177
- // one action that is valid from any unknown state.
178
- case 'idle':
179
- default:
180
- return {
181
- primaryAction: 'check',
182
- primaryLabelKey: K.check,
183
- showReleaseLink: true,
184
- statusKey: K.notChecked,
185
- statusTone: 'neutral',
186
- };
187
- }
209
+ if (appVersionInfo?.packaged === false)
210
+ return { ...DEVELOPMENT_CONTROL };
211
+ if (model.environment !== 'desktop' || !model.enabled || !model.supported)
212
+ return { ...UNSUPPORTED_CONTROL };
213
+ const derive = model.status ? STATE_DERIVERS.get(model.status.state) : undefined;
214
+ return derive ? derive(model) : { ...IDLE_CONTROL };
188
215
  }
189
216
  /**
190
217
  * Whether the primary button should be disabled.
@@ -1,16 +1,17 @@
1
- import { useImagePreviewModal } from "./ImagePreviewModal.hooks.js";
1
+ import type { useLightbox } from "./Lightbox/Lightbox.hooks.js";
2
2
  export interface ImagePreviewModalProps {
3
3
  open: boolean;
4
4
  src: string;
5
5
  alt: string;
6
6
  onClose: () => void;
7
7
  /** A fake controller can exercise rendering without a real native-dialog lifecycle. */
8
- useModal?: typeof useImagePreviewModal | undefined;
8
+ useModal?: typeof useLightbox | undefined;
9
9
  /** Optional tagging preserves existing untagged renders when no agent handle is supplied. */
10
10
  agentHandle?: string | undefined;
11
11
  /** No locale is owned here; a translated caller must pass its own accessible close label. */
12
12
  closeLabel?: string | undefined;
13
13
  }
14
- /** Controlled native-dialog lightbox. The host supplies image copy and a translated close label. */
14
+ /** One image in the shared `Lightbox`; the alt text doubles as its title. Kept as a named
15
+ * single-image entry point so callers need not assemble the lightbox for the common case. */
15
16
  export declare function ImagePreviewModal({ open, src, alt, onClose, useModal, agentHandle: handle, closeLabel, }: ImagePreviewModalProps): import("react").JSX.Element;
16
17
  //# sourceMappingURL=ImagePreviewModal.d.ts.map
@@ -1,13 +1,9 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { agentHandle } from "@jini-ai/agentic";
3
- import { useImagePreviewModal } from "./ImagePreviewModal.hooks.js";
4
- // Native dialog supplies focus trapping, Escape and a backdrop through the browser's top layer,
5
- // avoiding a hand-built overlay and z-index policy. A confirm/cancel dialog's two-action contract
6
- // does not fit a read-only image with one close affordance. The injectable hook owns lifecycle
7
- // and DOM-environment fallbacks so this component stays focused on rendering.
8
- /** Controlled native-dialog lightbox. The host supplies image copy and a translated close label. */
9
- export function ImagePreviewModal({ open, src, alt, onClose, useModal = useImagePreviewModal, agentHandle: handle, closeLabel = "Close preview", }) {
10
- const { dialogRef, handleNativeCancel, handleBackdropClick } = useModal({ open, onClose });
11
- return (_jsxs("dialog", { ref: dialogRef, className: "image-preview-modal", "aria-label": alt, onCancel: handleNativeCancel, onClick: handleBackdropClick, children: [_jsx("button", { type: "button", className: "image-preview-modal-close", onClick: onClose, "aria-label": closeLabel, ...(handle ? agentHandle({ handle: handle }, { role: "button", label: closeLabel }) : {}), children: "\u00D7" }), _jsx("img", { src: src, alt: alt })] }));
3
+ import { Lightbox } from "./Lightbox/Lightbox.js";
4
+ /** One image in the shared `Lightbox`; the alt text doubles as its title. Kept as a named
5
+ * single-image entry point so callers need not assemble the lightbox for the common case. */
6
+ export function ImagePreviewModal({ open, src, alt, onClose, useModal, agentHandle: handle, closeLabel = "Close preview", }) {
7
+ return (_jsx(Lightbox, { open: open, title: alt, onClose: onClose, className: "image-preview-modal", labels: { close: closeLabel }, useLightboxHook: useModal, partAttrs: handle ? { close: { ...agentHandle({ handle: handle }, { role: "button", label: closeLabel }) } } : undefined, children: _jsx("img", { src: src, alt: alt }) }));
12
8
  }
13
9
  //# sourceMappingURL=ImagePreviewModal.js.map