@jini-ai/ui 0.5.5 → 0.6.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/CHANGELOG.md +91 -2
  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 +9 -12
  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
@@ -0,0 +1,95 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useT } from '../../../i18n/index.js';
3
+ import { EMPTY_TOOLBAR_ACTIONS } from '../../constants.js';
4
+ import { basenameForRename, canCopyLocalPath, countFilesUnderDir, resolveKindConfig } from '../../rules.js';
5
+ import { AssetTreeBreadcrumbs } from './AssetTreeBreadcrumbs.js';
6
+ import { AssetTreeEmptyState } from './AssetTreeEmptyState.js';
7
+ import { AssetTreeFileRow } from './AssetTreeFileRow.js';
8
+ import { AssetTreeFolderRow } from './AssetTreeFolderRow.js';
9
+ import { AssetTreeRowMenu } from './AssetTreeRowMenu.js';
10
+ import { AssetTreeSelectionBar } from './AssetTreeSelectionBar.js';
11
+ import { AssetTreeToolbar } from './AssetTreeToolbar.js';
12
+ import { AssetTreeUploadErrorBanner } from './AssetTreeUploadErrorBanner.js';
13
+ import { FilePreviewPane } from './FilePreviewPane.js';
14
+ /** Breadcrumbs (root label defaults to `t('Files')`) plus the host toolbar. */
15
+ export function AssetTreeBrowserTopbar({ currentDir, rootLabel, onNavigate, toolbarActions = EMPTY_TOOLBAR_ACTIONS, }) {
16
+ const t = useT();
17
+ return (_jsxs("div", { className: "asset-tree-topbar", children: [_jsx("div", { className: "asset-tree-topbar-left", children: _jsx(AssetTreeBreadcrumbs, { currentDir: currentDir, rootLabel: rootLabel ?? t('Files'), onNavigate: onNavigate }) }), _jsx("div", { className: "asset-tree-topbar-right", children: _jsx(AssetTreeToolbar, { actions: toolbarActions }) })] }));
18
+ }
19
+ /** The drop-target listing area: upload-error banner, selection bar, folders + kind sections (or the empty state), footer. */
20
+ export function AssetTreeBrowserBody(props) {
21
+ const { controller, downloadFiles, footer } = props;
22
+ const { dragUpload, preview } = controller;
23
+ return (_jsxs("div", { className: "asset-tree-body", "data-testid": "asset-tree-body", onDragEnter: dragUpload.onDragEnter, onDragOver: dragUpload.onDragOver, onDragLeave: dragUpload.onDragLeave, onDrop: dragUpload.onDrop, children: [dragUpload.dropReadError && !preview.previewPath ? (_jsx(AssetTreeUploadErrorBanner, { message: dragUpload.dropReadError, onDismiss: dragUpload.clearDropReadError })) : null, _jsx(AssetTreeBrowserSelectionBar, { controller: controller, downloadFiles: downloadFiles }), _jsx(AssetTreeBrowserListing, { ...props }), footer] }));
24
+ }
25
+ function AssetTreeBrowserSelectionBar({ controller, downloadFiles, }) {
26
+ const { selection, batchActions } = controller;
27
+ if (selection.selected.size === 0)
28
+ return null;
29
+ return (_jsx(AssetTreeSelectionBar, { count: selection.selected.size, onClear: selection.clearSelection, onDelete: () => void batchActions.deleteSelected(), deleting: batchActions.deleting, onDownload: downloadFiles ? () => void batchActions.downloadSelected() : undefined, downloading: batchActions.downloading, downloadError: batchActions.downloadError }));
30
+ }
31
+ function AssetTreeBrowserListing({ controller, files, folders, selectors, kindConfig, emptyStateActions = EMPTY_TOOLBAR_ACTIONS, }) {
32
+ const { navigation } = controller;
33
+ const isEmpty = files.length === 0 && (folders?.length ?? 0) === 0;
34
+ if (isEmpty)
35
+ return _jsx(AssetTreeEmptyState, { actions: emptyStateActions });
36
+ return (_jsxs(_Fragment, { children: [_jsx(AssetTreeFoldersSection, { files: files, dirs: navigation.dirsAtCurrentDir, currentDir: navigation.currentDir, onNavigate: navigation.setCurrentDir }, "folders"), navigation.sections.map((section) => (_jsx(AssetTreeKindSection, { controller: controller, section: section, selectors: selectors, kindConfig: kindConfig }, `kind:${section.kind}`)))] }));
37
+ }
38
+ function AssetTreeFoldersSection({ files, dirs, currentDir, onNavigate, }) {
39
+ const t = useT();
40
+ if (dirs.length === 0)
41
+ return null;
42
+ return (_jsxs("div", { className: "asset-tree-section", children: [_jsxs("div", { className: "asset-tree-section-label", children: [t('Folders'), _jsx("span", { className: "asset-tree-section-count", children: dirs.length })] }), dirs.map((dirName) => {
43
+ const fullPath = currentDir === '' ? dirName : `${currentDir}/${dirName}`;
44
+ return (_jsx(AssetTreeFolderRow, { name: dirName, path: fullPath, fileCount: countFilesUnderDir(files, fullPath), onNavigate: onNavigate }, fullPath));
45
+ })] }));
46
+ }
47
+ function AssetTreeKindSection({ controller, section, selectors, kindConfig, }) {
48
+ const t = useT();
49
+ const { navigation, preview, selection, rename, rowMenu, openPath } = controller;
50
+ const config = resolveKindConfig(section.kind, kindConfig);
51
+ return (_jsxs("div", { className: "asset-tree-section", children: [_jsxs("div", { className: "asset-tree-section-label", children: [t(config.label), _jsx("span", { className: "asset-tree-section-count", children: section.files.length })] }), section.files.map((f) => (_jsx(AssetTreeFileRow, { path: f.path, displayName: basenameForRename(f.path, navigation.currentDir), active: preview.previewPath === f.path, selected: selection.selected.has(f.path), kindLabel: t(config.label), kindGlyph: config.glyph, size: selectors.getSize(f), modifiedAt: selectors.getModifiedAt(f), renaming: rename.renaming?.path === f.path ? rename.renaming : null, onSelectPreview: preview.setPreviewPath, onOpen: openPath, onToggleSelect: selection.toggleSelect, onOpenMenu: rowMenu.openMenuFor, onRenameDraftChange: rename.updateDraft, onCommitRename: () => void rename.commitRename(), onCancelRename: rename.cancelRename }, f.path)))] }));
52
+ }
53
+ /** `FilePreviewPane` for the previewed `file`, with its kind display config resolved. */
54
+ export function AssetTreeBrowserPreview({ file, selectors, kindConfig, onOpenFile, onClose, getFileUrl, renderPreviewThumbnail, thumbnailIsInteractive, }) {
55
+ const t = useT();
56
+ const previewKindConfig = resolveKindConfig(selectors.getKind(file), kindConfig);
57
+ return (_jsx(FilePreviewPane, { file: file, path: file.path, kindLabel: t(previewKindConfig.label), kindGlyph: previewKindConfig.glyph, size: selectors.getSize(file), modifiedAt: selectors.getModifiedAt(file), onOpen: () => onOpenFile(file), onClose: onClose, downloadHref: getFileUrl?.(file), renderThumbnail: renderPreviewThumbnail, thumbnailIsInteractive: thumbnailIsInteractive }));
58
+ }
59
+ /** The open row context menu for `menuPos.path`, with every action wired. */
60
+ export function AssetTreeBrowserRowMenu({ controller, menuPos, selectors, getFileUrl, onDeleteFile, }) {
61
+ const { navigation, rowMenu, rename, copyLocalPath, findFile, openPath } = controller;
62
+ const { path } = menuPos;
63
+ const menuFile = findFile(path) ?? null;
64
+ return (_jsx(AssetTreeRowMenu, { path: path, displayName: basenameForRename(path, navigation.currentDir), top: menuPos.top, left: menuPos.left, containerRef: rowMenu.containerRef, canCopyLocalPath: !!menuFile && canCopyLocalPath(menuFile, selectors), copied: copyLocalPath.copiedPath === path, download: getFileUrl && menuFile ? { href: getFileUrl(menuFile), onClick: rowMenu.closeMenu } : undefined, onOpen: () => {
65
+ rowMenu.closeMenu();
66
+ openPath(path);
67
+ }, onRename: () => {
68
+ rowMenu.closeMenu();
69
+ rename.startRename(path);
70
+ },
71
+ // Deliberately does NOT close the menu (unlike every other action
72
+ // here) — the origin `DesignFilesPanel`'s equivalent handler did
73
+ // call `setMenuPos(null)` before copying, which meant its
74
+ // "Copied" confirmation label could never actually become visible
75
+ // to a user (it only ever renders inside the now-closed popover).
76
+ // Since the whole point of `copiedPath`/the transient-confirmation
77
+ // UI is to be seen, this keeps the menu open so it can be; the
78
+ // existing outside-dismiss/Escape handling still closes it
79
+ // normally afterward.
80
+ onCopyLocalPath: () => {
81
+ // `canCopyLocalPath` (passed below) already gates the button's
82
+ // `disabled` state on `!!menuFile`, so a click can only reach
83
+ // this handler when `menuFile` is non-null — asserting that
84
+ // instead of re-guarding it here avoids an unreachable `: null`
85
+ // ternary arm (a real click can never fire on a disabled
86
+ // button, so that arm was dead code, not defensive coding).
87
+ const localPath = selectors.getLocalPath?.(menuFile);
88
+ if (localPath)
89
+ void copyLocalPath.copyLocalPath(path, localPath);
90
+ }, onDelete: () => {
91
+ rowMenu.closeMenu();
92
+ onDeleteFile(path);
93
+ } }));
94
+ }
95
+ //# sourceMappingURL=AssetTreeBrowserParts.js.map
@@ -0,0 +1,56 @@
1
+ import type { AssetTreeDependencies } from '../../ports.js';
2
+ import type { AssetTreeFileItem, AssetTreeFolderItem, AssetTreeNavState, AssetTreeSelectors } from '../../types.js';
3
+ import { type UseAssetTreeBatchActionsResult } from '../hooks/useAssetTreeBatchActions.js';
4
+ import { type UseAssetTreeCopyLocalPathResult } from '../hooks/useAssetTreeCopyLocalPath.js';
5
+ import { type UseAssetTreeDragUploadResult } from '../hooks/useAssetTreeDragUpload.js';
6
+ import { useAssetTreeNavigation, type UseAssetTreeNavigationResult } from '../hooks/useAssetTreeNavigation.js';
7
+ import { useAssetTreePreview, type UseAssetTreePreviewResult } from '../hooks/useAssetTreePreview.js';
8
+ import { useAssetTreeRename, type UseAssetTreeRenameResult } from '../hooks/useAssetTreeRename.js';
9
+ import { type UseAssetTreeRowMenuResult } from '../hooks/useAssetTreeRowMenu.js';
10
+ import { useAssetTreeSelection, type UseAssetTreeSelectionResult } from '../hooks/useAssetTreeSelection.js';
11
+ /** The `AssetTreeBrowser` props every controller run needs. */
12
+ export interface AssetTreeBrowserControllerInput<TFile extends AssetTreeFileItem> {
13
+ files: TFile[];
14
+ selectors: AssetTreeSelectors<TFile>;
15
+ onOpenFile: (file: TFile) => void;
16
+ onRenameFile: (path: string, nextPath: string) => Promise<TFile | null> | TFile | null;
17
+ onDeleteFiles: (paths: string[]) => Promise<void> | void;
18
+ onUploadFiles: (files: File[]) => void;
19
+ }
20
+ /** The optional `AssetTreeBrowser` props the controller consumes, with the same defaults the component documents. */
21
+ export interface AssetTreeBrowserControllerOptions<TFile extends AssetTreeFileItem> {
22
+ folders?: AssetTreeFolderItem[];
23
+ sectionOrder?: readonly string[];
24
+ dependencies?: AssetTreeDependencies;
25
+ navState?: AssetTreeNavState;
26
+ onNavStateChange?: (state: AssetTreeNavState) => void;
27
+ onSelectionChange?: (paths: Set<string>) => void;
28
+ downloadFiles?: (paths: string[]) => Promise<{
29
+ blob: Blob;
30
+ filename: string;
31
+ }>;
32
+ selectInitialPreviewFile?: (files: TFile[]) => TFile | null;
33
+ useAssetTreeNavigation?: typeof useAssetTreeNavigation;
34
+ useAssetTreePreview?: typeof useAssetTreePreview;
35
+ useAssetTreeRename?: typeof useAssetTreeRename;
36
+ useAssetTreeSelection?: typeof useAssetTreeSelection;
37
+ }
38
+ export interface AssetTreeBrowserController<TFile> {
39
+ navigation: UseAssetTreeNavigationResult<TFile>;
40
+ preview: UseAssetTreePreviewResult<TFile>;
41
+ rename: UseAssetTreeRenameResult;
42
+ selection: UseAssetTreeSelectionResult;
43
+ rowMenu: UseAssetTreeRowMenuResult;
44
+ dragUpload: UseAssetTreeDragUploadResult;
45
+ batchActions: UseAssetTreeBatchActionsResult;
46
+ copyLocalPath: UseAssetTreeCopyLocalPathResult;
47
+ findFile: (path: string) => TFile | undefined;
48
+ /** Resolves `path` against `files` and opens it; a path no longer in `files` is a no-op. */
49
+ openPath: (path: string) => void;
50
+ }
51
+ /**
52
+ * Every hook `AssetTreeBrowser` composes, wired together in the one order
53
+ * their cross-references need, so the component itself is pure rendering.
54
+ */
55
+ export declare function useAssetTreeBrowserController<TFile extends AssetTreeFileItem>(input: AssetTreeBrowserControllerInput<TFile>, { folders, sectionOrder, dependencies, navState, onNavStateChange, onSelectionChange, downloadFiles, selectInitialPreviewFile, useAssetTreeNavigation: useAssetTreeNavigationHook, useAssetTreePreview: useAssetTreePreviewHook, useAssetTreeRename: useAssetTreeRenameHook, useAssetTreeSelection: useAssetTreeSelectionHook, }: AssetTreeBrowserControllerOptions<TFile>): AssetTreeBrowserController<TFile>;
56
+ //# sourceMappingURL=useAssetTreeBrowserController.d.ts.map
@@ -0,0 +1,80 @@
1
+ import { useEffect, useMemo } from 'react';
2
+ import { DEFAULT_SECTION_ORDER } from '../../constants.js';
3
+ import { createBrowserAssetTreeDependencies } from '../../dependencies.js';
4
+ import { useAssetTreeBatchActions } from '../hooks/useAssetTreeBatchActions.js';
5
+ import { useAssetTreeClipboardPasteUpload } from '../hooks/useAssetTreeClipboardPasteUpload.js';
6
+ import { useAssetTreeCopyLocalPath } from '../hooks/useAssetTreeCopyLocalPath.js';
7
+ import { useAssetTreeDragUpload } from '../hooks/useAssetTreeDragUpload.js';
8
+ import { useAssetTreeNavigation } from '../hooks/useAssetTreeNavigation.js';
9
+ import { useAssetTreePreview } from '../hooks/useAssetTreePreview.js';
10
+ import { useAssetTreeRename } from '../hooks/useAssetTreeRename.js';
11
+ import { useAssetTreeRowMenu } from '../hooks/useAssetTreeRowMenu.js';
12
+ import { useAssetTreeSelection } from '../hooks/useAssetTreeSelection.js';
13
+ /**
14
+ * Every hook `AssetTreeBrowser` composes, wired together in the one order
15
+ * their cross-references need, so the component itself is pure rendering.
16
+ */
17
+ export function useAssetTreeBrowserController(input, { folders, sectionOrder = DEFAULT_SECTION_ORDER, dependencies, navState, onNavStateChange, onSelectionChange, downloadFiles, selectInitialPreviewFile, useAssetTreeNavigation: useAssetTreeNavigationHook = useAssetTreeNavigation, useAssetTreePreview: useAssetTreePreviewHook = useAssetTreePreview, useAssetTreeRename: useAssetTreeRenameHook = useAssetTreeRename, useAssetTreeSelection: useAssetTreeSelectionHook = useAssetTreeSelection, }) {
18
+ const { files, selectors, onOpenFile, onRenameFile, onDeleteFiles, onUploadFiles } = input;
19
+ // KNOWN FOLLOW-UP (flagged by the 2026-07-22 verification pass, not yet
20
+ // fixed): this has the same useWiredX gap ConnectorsBrowser had — `deps`
21
+ // resolution is still inline instead of behind a `useWiredAssetTreeBrowser`
22
+ // wrapper. Deliberately not fixed here: unlike ConnectorsBrowser's `deps`
23
+ // (fed to exactly the 3 already-overridable sub-hooks), this `deps` also
24
+ // feeds `useAssetTreeRowMenu`, `useAssetTreeClipboardPasteUpload`, and
25
+ // `useAssetTreeCopyLocalPath` below — none of which currently have their
26
+ // own override props — so collapsing it into one wired hook is a real
27
+ // design decision (what belongs in the wired hook vs. stays overridable)
28
+ // rather than a mechanical port of the ConnectorsBrowser pattern. Lower
29
+ // severity than that one too: these are DOM/clipboard adapters, not
30
+ // remote data fetching.
31
+ const deps = useMemo(() => dependencies ?? createBrowserAssetTreeDependencies(), [dependencies]);
32
+ const navigation = useAssetTreeNavigationHook({
33
+ files,
34
+ folders,
35
+ sectionOrder,
36
+ getKind: selectors.getKind,
37
+ getModifiedAt: selectors.getModifiedAt,
38
+ navState,
39
+ onNavStateChange,
40
+ });
41
+ const preview = useAssetTreePreviewHook(files, selectInitialPreviewFile);
42
+ // Declared before `useAssetTreeSelection` so its in-flight `renaming.path`
43
+ // (if any) can be threaded into selection's prune logic as
44
+ // `pendingRenamePath` — see that hook's doc comment for the race this
45
+ // closes. `onRenamed` referencing `selection` here (declared below) is
46
+ // safe: it's a closure that only actually runs later, once `selection` is
47
+ // fully bound.
48
+ const rename = useAssetTreeRenameHook({
49
+ currentDir: navigation.currentDir,
50
+ onRenameFile,
51
+ onRenamed: (oldPath, renamedFile) => {
52
+ if (preview.previewPath === oldPath)
53
+ preview.setPreviewPath(renamedFile.path);
54
+ selection.renamePath(oldPath, renamedFile.path);
55
+ },
56
+ });
57
+ const selection = useAssetTreeSelectionHook(navigation.filesAtCurrentDir, navigation.currentDir, rename.renaming?.path ?? null);
58
+ const rowMenu = useAssetTreeRowMenu(deps.dom);
59
+ const dragUpload = useAssetTreeDragUpload(onUploadFiles);
60
+ useAssetTreeClipboardPasteUpload({
61
+ dom: deps.dom,
62
+ onUploadFiles,
63
+ onBeforeUpload: dragUpload.clearDropReadError,
64
+ });
65
+ const batchActions = useAssetTreeBatchActions({ selected: selection.selected, onDeleteFiles, downloadFiles });
66
+ const copyLocalPath = useAssetTreeCopyLocalPath(deps.clipboard);
67
+ useEffect(() => {
68
+ onSelectionChange?.(selection.selected);
69
+ }, [selection.selected, onSelectionChange]);
70
+ function findFile(path) {
71
+ return files.find((f) => f.path === path);
72
+ }
73
+ function openPath(path) {
74
+ const target = findFile(path);
75
+ if (target)
76
+ onOpenFile(target);
77
+ }
78
+ return { navigation, preview, rename, selection, rowMenu, dragUpload, batchActions, copyLocalPath, findFile, openPath };
79
+ }
80
+ //# sourceMappingURL=useAssetTreeBrowserController.js.map
@@ -25,36 +25,54 @@ import { DEFAULT_KIND_GLYPH, DOUBLE_ACTIVATION_WINDOW_MS, ROW_MENU_ESTIMATED_HEI
25
25
  * this asymmetry looks surprising but is exactly what the source does.
26
26
  */
27
27
  export function deriveTreeChildren(files, folders, currentDir) {
28
- const prefix = currentDir === '' ? '' : `${currentDir}/`;
28
+ const isRoot = currentDir === '';
29
+ const prefix = isRoot ? '' : `${currentDir}/`;
29
30
  const dirs = new Set();
31
+ const localFiles = collectFileChildren({ files, prefix, isRoot, dirs });
32
+ collectFolderChildren({ folders, prefix, dirs });
33
+ return {
34
+ dirsAtCurrentDir: [...dirs].sort((a, b) => a.localeCompare(b)),
35
+ filesAtCurrentDir: localFiles,
36
+ };
37
+ }
38
+ /** The first path component of `path` below `prefix`, or `null` when `path` is outside `prefix`. */
39
+ function childSegment(path, prefix) {
40
+ if (!path.startsWith(prefix))
41
+ return null;
42
+ const remainder = path.slice(prefix.length);
43
+ const slashIdx = remainder.indexOf('/');
44
+ if (slashIdx === -1)
45
+ return { head: remainder, nested: false };
46
+ return { head: remainder.slice(0, slashIdx), nested: true };
47
+ }
48
+ /**
49
+ * File half of {@link deriveTreeChildren}: adds each nested file's top
50
+ * segment to `dirs` and returns the files listed at this level (every
51
+ * in-prefix file at the root, only one-level-deep files elsewhere).
52
+ */
53
+ function collectFileChildren(input) {
30
54
  const localFiles = [];
31
- for (const f of files) {
32
- if (!f.path.startsWith(prefix))
55
+ for (const f of input.files) {
56
+ const segment = childSegment(f.path, input.prefix);
57
+ if (!segment)
33
58
  continue;
34
- const remainder = f.path.slice(prefix.length);
35
- const slashIdx = remainder.indexOf('/');
36
- if (slashIdx === -1) {
59
+ if (segment.nested)
60
+ input.dirs.add(segment.head);
61
+ if (!segment.nested || input.isRoot)
37
62
  localFiles.push(f);
38
- }
39
- else {
40
- dirs.add(remainder.slice(0, slashIdx));
41
- if (currentDir === '')
42
- localFiles.push(f);
43
- }
44
63
  }
45
- for (const folder of folders) {
46
- if (!folder.path.startsWith(prefix))
64
+ return localFiles;
65
+ }
66
+ /** Folder half of {@link deriveTreeChildren}: adds each persisted folder's top segment below `prefix` to `dirs`. */
67
+ function collectFolderChildren(input) {
68
+ for (const folder of input.folders) {
69
+ const segment = childSegment(folder.path, input.prefix);
70
+ if (!segment)
47
71
  continue;
48
- const remainder = folder.path.slice(prefix.length);
49
- if (!remainder)
72
+ if (!segment.nested && !segment.head)
50
73
  continue; // the current directory itself
51
- const slashIdx = remainder.indexOf('/');
52
- dirs.add(slashIdx === -1 ? remainder : remainder.slice(0, slashIdx));
74
+ input.dirs.add(segment.head);
53
75
  }
54
- return {
55
- dirsAtCurrentDir: [...dirs].sort((a, b) => a.localeCompare(b)),
56
- filesAtCurrentDir: localFiles,
57
- };
58
76
  }
59
77
  /** Total files (at every depth, not just the immediate level) under `dirPath` — what a folder row's "N files" subtitle counts. */
60
78
  export function countFilesUnderDir(files, dirPath) {
@@ -2,27 +2,26 @@ import { DEFAULT_HOME_NAVIGATION_ENTRY, EMPTY_URL, HISTORY_LIMIT } from './const
2
2
  function escapeRegExp(value) {
3
3
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
4
4
  }
5
+ /** Resolves a `/`-rooted address: against `appOrigin` when it matches a configured app-route prefix, else as a local `file://` path. */
6
+ function resolveAbsolutePath(value, options) {
7
+ const { appRoutePrefixes = [], appOrigin } = options;
8
+ if (appOrigin && appRoutePrefixes.length > 0) {
9
+ const prefixPattern = new RegExp(`^/(${appRoutePrefixes.map(escapeRegExp).join('|')})(/|$)`);
10
+ if (prefixPattern.test(value))
11
+ return new URL(value, appOrigin).toString();
12
+ }
13
+ return `file://${encodeURI(value)}`;
14
+ }
5
15
  export function normalizeBrowserAddress(rawAddress, options = {}) {
6
16
  const value = rawAddress.trim();
7
- if (!value)
8
- return EMPTY_URL;
9
- if (value === EMPTY_URL)
17
+ if (!value || value === EMPTY_URL)
10
18
  return EMPTY_URL;
11
19
  if (/^(https?|file):\/\//i.test(value))
12
20
  return value;
13
- if (/^localhost(:\d+)?(\/.*)?$/i.test(value))
21
+ if (/^(localhost|127\.0\.0\.1|0\.0\.0\.0)(:\d+)?(\/.*)?$/i.test(value))
14
22
  return `http://${value}`;
15
- if (/^(127\.0\.0\.1|0\.0\.0\.0)(:\d+)?(\/.*)?$/i.test(value))
16
- return `http://${value}`;
17
- if (value.startsWith('/')) {
18
- const { appRoutePrefixes = [], appOrigin } = options;
19
- if (appOrigin && appRoutePrefixes.length > 0) {
20
- const prefixPattern = new RegExp(`^/(${appRoutePrefixes.map(escapeRegExp).join('|')})(/|$)`);
21
- if (prefixPattern.test(value))
22
- return new URL(value, appOrigin).toString();
23
- }
24
- return `file://${encodeURI(value)}`;
25
- }
23
+ if (value.startsWith('/'))
24
+ return resolveAbsolutePath(value, options);
26
25
  if (/^[\w.-]+\.[a-z]{2,}(:\d+)?(\/.*)?$/i.test(value))
27
26
  return `https://${value}`;
28
27
  return `https://www.google.com/search?q=${encodeURIComponent(value)}`;
@@ -137,37 +136,37 @@ export function mergeHistoryEntry(history, url, meta = {}, options = {}, now = D
137
136
  if (!isHistoryUrl(url))
138
137
  return history;
139
138
  const existing = history.find((entry) => sameUrl(entry.url, url));
140
- // `isHistoryUrl` above already excludes `EMPTY_URL`, so `labelFromUrl`'s
141
- // home-label branch can never trigger here — no `homeLabel` option to pass.
142
- const nextTitle = meta.title && meta.title.trim()
143
- ? meta.title.trim()
144
- : existing?.title || labelFromUrl(url);
145
- const nextIconUrl = cleanIconUrl(meta.iconUrl) || existing?.iconUrl || faviconUrl(url);
146
- const visitIncrement = options.countVisit === false ? 0 : 1;
147
- const entry = existing
148
- ? {
149
- ...existing,
150
- ...(nextIconUrl !== undefined ? { iconUrl: nextIconUrl } : {}),
151
- title: nextTitle,
152
- lastVisitedAt: visitIncrement > 0 ? now : existing.lastVisitedAt,
153
- visitCount: existing.visitCount + visitIncrement,
154
- }
155
- : {
156
- ...(nextIconUrl !== undefined ? { iconUrl: nextIconUrl } : {}),
157
- title: nextTitle,
158
- url,
159
- lastVisitedAt: now,
160
- visitCount: 1,
161
- };
162
- if (existing
163
- && existing.title === entry.title
164
- && existing.iconUrl === entry.iconUrl
165
- && existing.lastVisitedAt === entry.lastVisitedAt
166
- && existing.visitCount === entry.visitCount) {
139
+ const entry = buildHistoryEntry({ url, meta, now, countVisit: options.countVisit !== false }, { existing });
140
+ if (existing && sameHistoryEntryState(existing, entry))
167
141
  return history;
168
- }
169
142
  return [entry, ...history.filter((item) => !sameUrl(item.url, url))].slice(0, HISTORY_LIMIT);
170
143
  }
144
+ /** Builds the merged entry for `url`: an update of `existing` when present, otherwise a fresh first-visit entry. */
145
+ function buildHistoryEntry({ url, meta, now, countVisit }, { existing }) {
146
+ const resolved = resolveHistoryMeta({ url, meta }, { existing });
147
+ if (!existing)
148
+ return { ...resolved, url, lastVisitedAt: now, visitCount: 1 };
149
+ return {
150
+ ...existing,
151
+ ...resolved,
152
+ lastVisitedAt: countVisit ? now : existing.lastVisitedAt,
153
+ visitCount: existing.visitCount + (countVisit ? 1 : 0),
154
+ };
155
+ }
156
+ /** Title and icon for a merged entry: the caller's meta wins, then the existing entry's, then values derived from `url`. */
157
+ function resolveHistoryMeta({ url, meta }, { existing }) {
158
+ // `isHistoryUrl` above already excludes `EMPTY_URL`, so `labelFromUrl`'s
159
+ // home-label branch can never trigger here — no `homeLabel` option to pass.
160
+ const title = meta.title?.trim() || existing?.title || labelFromUrl(url);
161
+ const iconUrl = cleanIconUrl(meta.iconUrl) || existing?.iconUrl || faviconUrl(url);
162
+ return iconUrl !== undefined ? { iconUrl, title } : { title };
163
+ }
164
+ function sameHistoryEntryState(left, right) {
165
+ return (left.title === right.title
166
+ && left.iconUrl === right.iconUrl
167
+ && left.lastVisitedAt === right.lastVisitedAt
168
+ && left.visitCount === right.visitCount);
169
+ }
171
170
  // ---------------------------------------------------------------------------
172
171
  // Navigation stack
173
172
  // ---------------------------------------------------------------------------
@@ -201,6 +200,20 @@ export function recordNavigation(state, url, title, options = {}) {
201
200
  if (url !== EMPTY_URL && !isHistoryUrl(url))
202
201
  return state;
203
202
  const { navigationStack: stack, navigationIndex: index } = state;
203
+ const nextTitle = navigationTitle({ url }, { title, homeLabel: options.homeLabel });
204
+ const rejoin = findRejoinTarget({ state, url }, options);
205
+ if (rejoin) {
206
+ const next = stack.slice();
207
+ // `nextTitle` is only ever '' for EMPTY_URL with an explicit '' homeLabel;
208
+ // keep the rejoined entry's own title rather than blanking it.
209
+ next[rejoin.index] = { title: nextTitle || rejoin.entry.title, url };
210
+ return { navigationStack: next, navigationIndex: rejoin.index };
211
+ }
212
+ const base = index >= 0 ? stack.slice(0, index + 1) : [];
213
+ const nextStack = [...base, { title: nextTitle, url }].slice(-HISTORY_LIMIT);
214
+ return { navigationStack: nextStack, navigationIndex: nextStack.length - 1 };
215
+ }
216
+ function navigationTitle({ url }, { title, homeLabel }) {
204
217
  // Bug fix: this used to hardcode DEFAULT_HOME_NAVIGATION_ENTRY.title here,
205
218
  // ignoring a caller-supplied `options.homeLabel` — so a host configuring a
206
219
  // custom `homeEntry` (via useBrowserNavigationStack) would see the entry's
@@ -208,34 +221,25 @@ export function recordNavigation(state, url, title, options = {}) {
208
221
  // even though `initialNavigationState`/`labelFromUrl` both honor the
209
222
  // custom label correctly. Falls back to the generic default only when no
210
223
  // homeLabel is supplied.
211
- const nextTitle = url === EMPTY_URL
212
- ? (options.homeLabel ?? DEFAULT_HOME_NAVIGATION_ENTRY.title)
213
- : title && title.trim()
214
- ? title.trim()
215
- : labelFromUrl(url, options.homeLabel);
216
- const nextEntry = { title: nextTitle, url };
217
- const updateEntry = (entries, entryIndex) => {
218
- const existing = entries[entryIndex];
219
- const next = entries.slice();
220
- next[entryIndex] = { title: nextTitle || existing?.title || labelFromUrl(url, options.homeLabel), url };
221
- return next;
222
- };
223
- const currentEntry = index >= 0 ? stack[index] : undefined;
224
- const shouldReplacePending = Boolean(options.replacePendingTarget && options.pendingTarget && currentEntry && sameUrl(currentEntry.url, options.pendingTarget));
225
- if (currentEntry && (sameUrl(currentEntry.url, url) || shouldReplacePending)) {
226
- return { navigationStack: updateEntry(stack, index), navigationIndex: index };
227
- }
228
- const previousIndex = index - 1;
229
- if (previousIndex >= 0 && sameUrl(stack[previousIndex]?.url ?? '', url)) {
230
- return { navigationStack: updateEntry(stack, previousIndex), navigationIndex: previousIndex };
231
- }
232
- const nextIndex = index + 1;
233
- if (nextIndex < stack.length && sameUrl(stack[nextIndex]?.url ?? '', url)) {
234
- return { navigationStack: updateEntry(stack, nextIndex), navigationIndex: nextIndex };
224
+ if (url === EMPTY_URL)
225
+ return homeLabel ?? DEFAULT_HOME_NAVIGATION_ENTRY.title;
226
+ return title?.trim() || labelFromUrl(url, homeLabel);
227
+ }
228
+ /**
229
+ * The stack entry `url` should update in place instead of appending: the
230
+ * current entry (same url, or the pending-load target being replaced), else
231
+ * the adjacent back entry, else the adjacent forward entry. An out-of-range
232
+ * index reads as `undefined`, which never matches.
233
+ */
234
+ function findRejoinTarget({ state, url }, { replacePendingTarget, pendingTarget }) {
235
+ const { navigationStack: stack, navigationIndex: index } = state;
236
+ const current = stack[index];
237
+ if (current && (sameUrl(current.url, url) || (replacePendingTarget && pendingTarget && sameUrl(current.url, pendingTarget)))) {
238
+ return { index, entry: current };
235
239
  }
236
- const base = index >= 0 ? stack.slice(0, index + 1) : [];
237
- const nextStack = [...base, nextEntry].slice(-HISTORY_LIMIT);
238
- return { navigationStack: nextStack, navigationIndex: nextStack.length - 1 };
240
+ return [index - 1, index + 1]
241
+ .map((candidate) => ({ index: candidate, entry: stack[candidate] }))
242
+ .find((target) => target.entry !== undefined && sameUrl(target.entry.url, url));
239
243
  }
240
244
  export function updateCurrentNavigationTitle(state, title) {
241
245
  const trimmedTitle = title?.trim();
@@ -59,6 +59,12 @@ export function mountChart({ element, spec, registry }, options = {}) {
59
59
  function candidates() {
60
60
  return selectChartProviders({ registry, spec: input.spec }, { siteProvider: options.siteProvider }).filter(({ id }) => !failed.has(id));
61
61
  }
62
+ function isStaleMount({ token }, _options = {}) {
63
+ return destroyed || token !== generation;
64
+ }
65
+ function isCurrentUpdate({ current, updateRevision }, _options = {}) {
66
+ return !destroyed && active === current && updateRevision === revision;
67
+ }
62
68
  /** Async renderer errors arrive after mount; defer teardown outside the provider's commit. */
63
69
  function renderError(entry, error, token) {
64
70
  queueMicrotask(() => {
@@ -76,7 +82,7 @@ export function mountChart({ element, spec, registry }, options = {}) {
76
82
  let renderRevision = revision;
77
83
  try {
78
84
  const provider = await entry.load({});
79
- if (destroyed || token !== generation)
85
+ if (isStaleMount({ token }))
80
86
  return;
81
87
  const frame = element.ownerDocument.createElement('div');
82
88
  renderer.replaceChildren(frame);
@@ -84,13 +90,13 @@ export function mountChart({ element, spec, registry }, options = {}) {
84
90
  const instance = provider.mount({ element: frame, ...input }, { onError: (error) => renderError(entry, error, token) });
85
91
  active = { entry, instance };
86
92
  await instance.ready;
87
- if (destroyed || token !== generation || renderRevision !== revision)
93
+ if (isStaleMount({ token }) || renderRevision !== revision)
88
94
  return;
89
95
  fallback.hidden = true;
90
96
  return;
91
97
  }
92
98
  catch (error) {
93
- if (destroyed || token !== generation || renderRevision !== revision)
99
+ if (isStaleMount({ token }) || renderRevision !== revision)
94
100
  return;
95
101
  failed.add(entry.id);
96
102
  report(entry, error);
@@ -113,12 +119,12 @@ export function mountChart({ element, spec, registry }, options = {}) {
113
119
  const current = active;
114
120
  try {
115
121
  await current.instance.update(input);
116
- if (!destroyed && active === current && updateRevision === revision)
122
+ if (isCurrentUpdate({ current, updateRevision }))
117
123
  fallback.hidden = true;
118
124
  return;
119
125
  }
120
126
  catch (error) {
121
- if (destroyed || active !== current || updateRevision !== revision)
127
+ if (!isCurrentUpdate({ current, updateRevision }))
122
128
  return;
123
129
  failed.add(current.entry.id);
124
130
  report(current.entry, error);
@@ -13,8 +13,8 @@ export function ChartHost({ spec, registry, theme = DEFAULT_CHART_THEME, sitePro
13
13
  const instance = useRef(undefined);
14
14
  const previous = useRef({ spec, theme });
15
15
  useEffect(() => {
16
- if (!element.current)
17
- return;
16
+ // The container `<div>` below is rendered unconditionally, so React has always attached the ref
17
+ // by the time this effect runs; the old `if (!element.current) return` could never fire.
18
18
  const mounted = mountChart({ element: element.current, spec, registry }, {
19
19
  theme, siteProvider, ...(onProviderError ? { onProviderError } : {}),
20
20
  });
@@ -41,8 +41,8 @@ export function renderChartTable({ element, spec }, _options = {}) {
41
41
  const tr = body.insertRow();
42
42
  for (const { key } of columns) {
43
43
  const cell = tr.insertCell();
44
- const value = row[key];
45
- cell.textContent = value == null ? '' : typeof value === 'object' ? JSON.stringify(value) : String(value);
44
+ const value = row[key] ?? '';
45
+ cell.textContent = typeof value === 'object' ? JSON.stringify(value) : String(value);
46
46
  cell.dataset.numeric = String(numeric.has(key));
47
47
  }
48
48
  }
@@ -30,18 +30,12 @@ export function useCommandPalette(options, dependencies) {
30
30
  onClose();
31
31
  return;
32
32
  }
33
- if (event.key === 'ArrowDown') {
33
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
34
34
  event.preventDefault();
35
35
  if (results.length === 0)
36
36
  return;
37
- setCursor((current) => nextCursor(current, results.length, 1));
38
- return;
39
- }
40
- if (event.key === 'ArrowUp') {
41
- event.preventDefault();
42
- if (results.length === 0)
43
- return;
44
- setCursor((current) => nextCursor(current, results.length, -1));
37
+ const direction = event.key === 'ArrowDown' ? 1 : -1;
38
+ setCursor((current) => nextCursor(current, results.length, direction));
45
39
  return;
46
40
  }
47
41
  if (event.key === 'Enter') {
@@ -1,8 +1,8 @@
1
1
  import type { ConnectorsDependencies } from './ports.js';
2
2
  import type { Connector, ConnectorAuthResultEvent, ProviderTab } from './types.js';
3
- import { useConnectorCatalog } from './hooks/useConnectorCatalog.js';
4
- import { useConnectorAuthorization } from './hooks/useConnectorAuthorization.js';
5
- import { useConnectorDetail } from './hooks/useConnectorDetail.js';
3
+ import type { useConnectorCatalog } from './hooks/useConnectorCatalog.js';
4
+ import type { useConnectorAuthorization } from './hooks/useConnectorAuthorization.js';
5
+ import type { useConnectorDetail } from './hooks/useConnectorDetail.js';
6
6
  import type { ConnectorGateProps } from './components/ConnectorGate.js';
7
7
  export interface ConnectorsBrowserProps {
8
8
  /** Whether the host's provider is configured (e.g. an API key is saved) — gates enrichment + unmasks the grid. */
@@ -36,5 +36,5 @@ export interface ConnectorsBrowserProps {
36
36
  * messages. Ported from OD's `ConnectorsBrowser.tsx` — see
37
37
  * `packages/ui/source-map.md` for full provenance.
38
38
  */
39
- export declare function ConnectorsBrowser({ unlocked, providerTabs, catalogRefreshKey, dependencies, getCategoryLabel, getDisplayableAccountLabel, gate, onProviderTabClick, onConnectorAuthResult, onConnectorsChanged, useConnectorCatalog: useConnectorCatalogHook, useConnectorAuthorization: useConnectorAuthorizationHook, useConnectorDetail: useConnectorDetailHook, agentHandle, }: ConnectorsBrowserProps): import("react").JSX.Element;
39
+ export declare function ConnectorsBrowser({ unlocked, providerTabs, catalogRefreshKey, dependencies, getCategoryLabel, getDisplayableAccountLabel, gate, onProviderTabClick, onConnectorAuthResult, onConnectorsChanged, useConnectorCatalog, useConnectorAuthorization, useConnectorDetail, agentHandle, }: ConnectorsBrowserProps): import("react").JSX.Element;
40
40
  //# sourceMappingURL=ConnectorsBrowser.d.ts.map