@jini-ai/ui 0.5.4 → 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 (270) hide show
  1. package/CHANGELOG.md +10 -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/Select/Select.hooks.d.ts +21 -0
  29. package/dist/features/admin-widgets/components/Select/Select.hooks.js +24 -2
  30. package/dist/features/admin-widgets/components/Select/Select.js +4 -3
  31. package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.hooks.js +29 -14
  32. package/dist/features/admin-widgets/index.d.ts +14 -2
  33. package/dist/features/admin-widgets/index.js +7 -1
  34. package/dist/features/admin-widgets/styles/admin-widgets.css +1 -0
  35. package/dist/features/asset-grid/react/components/AssetCard.js +7 -1
  36. package/dist/features/asset-grid/react/components/AssetGrid.d.ts +1 -1
  37. package/dist/features/asset-grid/react/components/AssetGrid.js +38 -18
  38. package/dist/features/asset-grid/react/components/useAssetGridFilters.d.ts +21 -0
  39. package/dist/features/asset-grid/react/components/useAssetGridFilters.js +19 -0
  40. package/dist/features/asset-grid/react/hooks/useAssetGridKeyboardShortcuts.js +16 -11
  41. package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.d.ts +2 -0
  42. package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.js +35 -11
  43. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.d.ts +6 -6
  44. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.js +10 -129
  45. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.d.ts +50 -0
  46. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.js +95 -0
  47. package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.d.ts +56 -0
  48. package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.js +80 -0
  49. package/dist/features/asset-tree-browser/rules.js +40 -22
  50. package/dist/features/browser-chrome/rules.js +74 -70
  51. package/dist/features/charts/browser.js +11 -5
  52. package/dist/features/charts/react.js +2 -2
  53. package/dist/features/charts/table.js +2 -2
  54. package/dist/features/command-palette/react/hooks/useCommandPalette.js +3 -9
  55. package/dist/features/connectors/ConnectorsBrowser.d.ts +4 -4
  56. package/dist/features/connectors/ConnectorsBrowser.js +26 -14
  57. package/dist/features/connectors/components/ConnectorCard.js +67 -38
  58. package/dist/features/connectors/components/ConnectorDetailDrawer.js +62 -31
  59. package/dist/features/connectors/components/ConnectorGrid.js +11 -6
  60. package/dist/features/connectors/hooks/useConnectorAuthorization.js +6 -0
  61. package/dist/features/connectors/hooks/useConnectorCatalog.d.ts +2 -0
  62. package/dist/features/connectors/hooks/useConnectorCatalog.js +32 -13
  63. package/dist/features/connectors/hooks/useWiredConnectorsBrowser.d.ts +3 -3
  64. package/dist/features/connectors/rules.d.ts +8 -1
  65. package/dist/features/connectors/rules.js +42 -20
  66. package/dist/features/connectors/types.d.ts +11 -0
  67. package/dist/features/execution/constants.d.ts +4 -10
  68. package/dist/features/execution/constants.js +9 -94
  69. package/dist/features/execution/react/components/ByokProviderForm.d.ts +2 -4
  70. package/dist/features/execution/react/components/ByokProviderForm.js +140 -43
  71. package/dist/features/execution/react/components/ExecutionTab.d.ts +1 -2
  72. package/dist/features/execution/react/components/ExecutionTab.js +52 -30
  73. package/dist/features/execution/react/components/LocalCliAgentCard.js +12 -3
  74. package/dist/features/execution/react/components/LocalCliAgentList.js +14 -3
  75. package/dist/features/execution/react/components/LocalCliAgentSettings.js +14 -3
  76. package/dist/features/execution/react/hooks/useLocalCliAgentCard.js +54 -36
  77. package/dist/features/execution/react/hooks/useReasoningControl.d.ts +1 -1
  78. package/dist/features/execution/react/hooks/useReasoningControl.js +116 -75
  79. package/dist/features/execution/rules.d.ts +13 -2
  80. package/dist/features/execution/rules.js +51 -11
  81. package/dist/features/execution/types.d.ts +6 -42
  82. package/dist/features/execution/types.js +0 -17
  83. package/dist/features/external-mcp/react/components/ExternalMcpTab.d.ts +6 -2
  84. package/dist/features/external-mcp/react/components/ExternalMcpTab.js +9 -2
  85. package/dist/features/file-dropzone/react/components/FileDropzone.js +24 -25
  86. package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.d.ts +2 -2
  87. package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.js +35 -30
  88. package/dist/features/file-dropzone/react/components/FileDropzoneThumbnailGrid.js +30 -24
  89. package/dist/features/file-dropzone/react/components/FileDropzoneZone.d.ts +14 -0
  90. package/dist/features/file-dropzone/react/components/FileDropzoneZone.js +26 -0
  91. package/dist/features/html-editor/canvas-content-wrapper.js +13 -13
  92. package/dist/features/html-editor/canvas-embed-placeholders.js +46 -37
  93. package/dist/features/html-editor/css.js +76 -69
  94. package/dist/features/html-editor/editor-blocks.js +47 -33
  95. package/dist/features/html-editor/editor-serialization.js +6 -5
  96. package/dist/features/html-editor/protected-elements.js +39 -28
  97. package/dist/features/html-editor/react/hooks/useInteractiveHtmlEditor.js +4 -4
  98. package/dist/features/html-editor/source-splice.js +51 -30
  99. package/dist/features/i18n/dictionaries/settings-dialog.ja.js +1 -0
  100. package/dist/features/i18n/dictionaries/settings-dialog.zh-CN.js +1 -0
  101. package/dist/features/i18n/dictionaries/settings-dialog.zh-TW.js +1 -0
  102. package/dist/features/i18n/locale.js +20 -16
  103. package/dist/features/iframe-pool/react/dom-sync.js +8 -6
  104. package/dist/features/integrations/react/components/CodexInstallToggleButton.js +19 -4
  105. package/dist/features/integrations/react/components/IntegrationsTab.js +60 -14
  106. package/dist/features/interactive-ui/interactive-ui.css +1 -1
  107. package/dist/features/interactive-ui/providers/native/data-table.manifest.d.ts +6 -6
  108. package/dist/features/interactive-ui/providers/shadcn/data-table.js +10 -7
  109. package/dist/features/interactive-ui/providers/shadcn/data-table.manifest.d.ts +6 -6
  110. package/dist/features/lexical-rich-text-editor/mention-parser.js +60 -57
  111. package/dist/features/lexical-rich-text-editor/react/hooks/useKeyboardCommands.js +33 -38
  112. package/dist/features/lexical-rich-text-editor/rules.js +87 -74
  113. package/dist/features/lexical-rich-text-editor/serialize.js +27 -25
  114. package/dist/features/mcp-ui/secret-card.js +111 -61
  115. package/dist/features/mcp-ui/surfaces/confirmation.js +39 -21
  116. package/dist/features/mcp-ui/surfaces/document.d.ts +1 -1
  117. package/dist/features/mcp-ui/surfaces/document.js +10 -2
  118. package/dist/features/mcp-ui/surfaces/fields.js +30 -50
  119. package/dist/features/mcp-ui/surfaces/form.d.ts +6 -0
  120. package/dist/features/mcp-ui/surfaces/form.js +43 -21
  121. package/dist/features/mcp-ui/surfaces/outcome.js +30 -20
  122. package/dist/features/mcp-ui/surfaces/text-input.js +33 -21
  123. package/dist/features/mcp-ui/surfaces/tokens.d.ts +6 -1
  124. package/dist/features/mcp-ui/surfaces/tokens.js +5 -2
  125. package/dist/features/media-providers/constants.d.ts +1 -0
  126. package/dist/features/media-providers/constants.js +2 -81
  127. package/dist/features/media-providers/index.d.ts +1 -1
  128. package/dist/features/media-providers/ports.d.ts +2 -0
  129. package/dist/features/media-providers/react/components/MediaProviderCard.d.ts +22 -0
  130. package/dist/features/media-providers/react/components/MediaProviderCard.js +50 -0
  131. package/dist/features/media-providers/react/components/MediaProvidersTab.d.ts +5 -6
  132. package/dist/features/media-providers/react/components/MediaProvidersTab.js +40 -72
  133. package/dist/features/media-providers/react/components/media-providers-tab-labels.d.ts +11 -0
  134. package/dist/features/media-providers/react/components/media-providers-tab-labels.js +57 -0
  135. package/dist/features/media-providers/react/hooks/useMediaProvidersTab.d.ts +4 -2
  136. package/dist/features/media-providers/react/hooks/useMediaProvidersTab.js +37 -23
  137. package/dist/features/media-providers/rules.js +17 -10
  138. package/dist/features/media-providers/types.d.ts +4 -22
  139. package/dist/features/media-providers/types.js +3 -8
  140. package/dist/features/memory/formatters.js +127 -92
  141. package/dist/features/memory/react/components/MemoryConnectedPanel.js +92 -36
  142. package/dist/features/memory/react/components/MemoryList.js +37 -18
  143. package/dist/features/memory/react/hooks/useMemoryExtractions.store.js +42 -36
  144. package/dist/features/notifications/react/components/NotificationsTab.js +28 -28
  145. package/dist/features/observability/long-task.d.ts +0 -12
  146. package/dist/features/observability/long-task.js +15 -19
  147. package/dist/features/observability/resource-error.d.ts +0 -10
  148. package/dist/features/observability/resource-error.js +18 -13
  149. package/dist/features/observability/stuck-run.js +10 -6
  150. package/dist/features/observability/telemetry-url.d.ts +6 -0
  151. package/dist/features/observability/telemetry-url.js +16 -0
  152. package/dist/features/observability/white-screen.js +12 -24
  153. package/dist/features/panel-kit/fetch-query/adapter.react.d.ts +2 -2
  154. package/dist/features/panel-kit/fetch-query/adapter.react.js +4 -2
  155. package/dist/features/panel-kit/fetch-query/adapter.tanstack.js +46 -33
  156. package/dist/features/panel-kit/helpers/standing-draft-local-backup.js +3 -1
  157. package/dist/features/panel-kit/hooks/use-settings-slice.hooks.js +3 -1
  158. package/dist/features/privacy/react/components/PrivacyTab.js +24 -13
  159. package/dist/features/progress-card/components/ProgressCard.js +7 -3
  160. package/dist/features/progress-card/reference-adapters.js +59 -54
  161. package/dist/features/project-locations/react/components/ProjectLocationsTab.js +24 -15
  162. package/dist/features/project-locations/react/hooks/useProjectLocationsTab.d.ts +8 -0
  163. package/dist/features/project-locations/react/hooks/useProjectLocationsTab.js +31 -23
  164. package/dist/features/resource-dashboard/react/components/ResourceBoard.js +6 -47
  165. package/dist/features/resource-dashboard/react/components/ResourceBoardView.d.ts +2 -2
  166. package/dist/features/resource-dashboard/react/components/ResourceBoardView.js +3 -6
  167. package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.d.ts +15 -0
  168. package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.js +35 -0
  169. package/dist/features/resource-dashboard/react/components/ResourceCard.js +5 -16
  170. package/dist/features/resource-dashboard/react/components/ResourceCardParts.d.ts +34 -0
  171. package/dist/features/resource-dashboard/react/components/ResourceCardParts.js +34 -0
  172. package/dist/features/resource-dashboard/react/components/ResourceRowList.js +3 -1
  173. package/dist/features/resource-dashboard/react/components/ResourceRowListItem.js +2 -2
  174. package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.d.ts +19 -0
  175. package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.js +11 -0
  176. package/dist/features/resource-dashboard/react/components/ResourceRowListView.d.ts +1 -1
  177. package/dist/features/resource-dashboard/react/components/ResourceRowListView.js +4 -4
  178. package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.d.ts +10 -0
  179. package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.js +20 -0
  180. package/dist/features/resource-dashboard/react/components/resourceBoardProps.d.ts +48 -0
  181. package/dist/features/resource-dashboard/react/components/resourceBoardProps.js +70 -0
  182. package/dist/features/resource-dashboard/react/components/resourceRowListProps.d.ts +19 -0
  183. package/dist/features/resource-dashboard/react/components/resourceRowListProps.js +14 -0
  184. package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.d.ts +16 -0
  185. package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.js +13 -0
  186. package/dist/features/resource-dashboard/rules.js +2 -8
  187. package/dist/features/sketch-editor/dom.js +150 -99
  188. package/dist/features/sketch-editor/react/components/SketchEditor.js +26 -15
  189. package/dist/features/skills/react/components/SkillDraftForm.js +13 -19
  190. package/dist/features/skills/react/components/SkillDraftForm.labels.d.ts +31 -0
  191. package/dist/features/skills/react/components/SkillDraftForm.labels.js +54 -0
  192. package/dist/features/skills/react/components/SkillRow.js +38 -18
  193. package/dist/features/skills/react/components/SkillRow.labels.d.ts +7 -0
  194. package/dist/features/skills/react/components/SkillRow.labels.js +18 -0
  195. package/dist/features/skills/react/components/SkillsTab.js +24 -14
  196. package/dist/features/skills/react/components/SkillsTab.labels.d.ts +7 -0
  197. package/dist/features/skills/react/components/SkillsTab.labels.js +11 -0
  198. package/dist/features/skills/rules.js +32 -23
  199. package/dist/features/source-config-list/agent-handles.js +2 -1
  200. package/dist/features/source-config-list/dependencies.js +53 -49
  201. package/dist/features/source-config-list/index.d.ts +3 -3
  202. package/dist/features/source-config-list/react/components/SourceConfigAddForm.d.ts +6 -5
  203. package/dist/features/source-config-list/react/components/SourceConfigAddForm.js +38 -7
  204. package/dist/features/source-config-list/react/components/SourceConfigField.js +69 -25
  205. package/dist/features/source-config-list/react/components/SourceConfigItemCard.d.ts +6 -5
  206. package/dist/features/source-config-list/react/components/SourceConfigItemCard.js +76 -20
  207. package/dist/features/source-config-list/react/components/SourceConfigListView.d.ts +5 -3
  208. package/dist/features/source-config-list/react/components/SourceConfigListView.js +33 -3
  209. package/dist/features/source-config-list/react/components/SourceConfigTestControl.js +17 -1
  210. package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.d.ts +11 -0
  211. package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.js +12 -0
  212. package/dist/features/source-config-list/types.d.ts +14 -0
  213. package/dist/features/tab-launcher-menu/react/components/TabLauncherMenu.js +25 -1
  214. package/dist/features/tab-launcher-menu/react/hooks/useTabLauncherMenu.js +15 -16
  215. package/dist/features/tab-strip/react/components/TabBar.js +8 -1
  216. package/dist/features/tabbed-dialog/react/components/TabbedDialog.js +8 -30
  217. package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.d.ts +15 -0
  218. package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.js +8 -0
  219. package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.d.ts +12 -0
  220. package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.js +6 -0
  221. package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.d.ts +27 -0
  222. package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.js +12 -0
  223. package/dist/features/tabbed-dialog/react/components/tabbedDialogView.d.ts +43 -0
  224. package/dist/features/tabbed-dialog/react/components/tabbedDialogView.js +59 -0
  225. package/dist/features/version-manager/dependencies.d.ts +3 -1
  226. package/dist/features/version-manager/dependencies.js +3 -2
  227. package/dist/features/version-manager/react/components/VersionManagerModal.js +10 -6
  228. package/dist/features/version-manager/react/hooks/useVersionManager.js +7 -4
  229. package/dist/features/viewer-shell/react/components/CommentSidePanel.js +103 -50
  230. package/dist/features/viewer-shell/react/components/MarkdownSplitPane.js +28 -8
  231. package/dist/features/viewer-shell/react/hooks/useCommentReorder.js +22 -16
  232. package/dist/features/viewer-shell/rules.js +19 -23
  233. package/dist/react/components/CustomSelect.d.ts +38 -1
  234. package/dist/react/components/CustomSelect.js +94 -38
  235. package/dist/react/components/OnboardingDropdown.js +20 -8
  236. package/dist/react/components/RemixIcon.stylesheet.js +3 -1
  237. package/dist/react/components/Toast.js +13 -2
  238. package/dist/react/components/TooltipLayer.js +5 -14
  239. package/dist/react/components/WorkingDirPicker.js +78 -50
  240. package/dist/react/components/remixicon-font-recovery.js +38 -30
  241. package/dist/react/components/select-popover.d.ts +8 -6
  242. package/dist/react/components/select-popover.js +33 -29
  243. package/dist/react/mcp-ui/McpUiHost.d.ts +1 -1
  244. package/dist/react/mcp-ui/McpUiHost.js +10 -11
  245. package/dist/react/mcp-ui/host-message-source.d.ts +14 -2
  246. package/dist/react/mcp-ui/host-message-source.js +15 -5
  247. package/dist/react/mcp-ui/mcp-ui-host-frame.d.ts +41 -0
  248. package/dist/react/mcp-ui/mcp-ui-host-frame.js +39 -0
  249. package/dist/renderers/annotation-canvas/react/components/AnnotationCanvas.js +97 -69
  250. package/dist/renderers/preview-modal-shell/react/components/PreviewModalShell.js +59 -30
  251. package/dist/renderers/preview-modal-shell/react/components/icons.js +4 -1
  252. package/dist/renderers/react/components/ArtifactView.js +9 -6
  253. package/dist/renderers/srcdoc/build.d.ts +4 -1
  254. package/dist/renderers/srcdoc/build.js +62 -48
  255. package/dist/styles/admin.css +29 -14
  256. package/dist/styles/lightbox.css +142 -0
  257. package/dist/styles/variables.css +128 -0
  258. package/dist/theme/apply.js +9 -5
  259. package/dist/utils/auto-open-file.d.ts +2 -1
  260. package/dist/utils/auto-open-file.js +69 -85
  261. package/dist/utils/markdown-scroll-sync.js +108 -87
  262. package/dist/utils/polygon-selection.js +25 -26
  263. package/dist/utils/smooth-scroll-to-top.d.ts +1 -1
  264. package/dist/utils/smooth-scroll-to-top.js +2 -32
  265. package/dist/utils/sse.js +26 -26
  266. package/dist/utils/unit-bezier.d.ts +7 -0
  267. package/dist/utils/unit-bezier.js +38 -0
  268. package/package.json +7 -10
  269. package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.d.ts +0 -13
  270. package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.js +0 -46
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  /** Optional TanStack translation layer. No library types escape the fetch-query contract. */
3
3
  import { createContext, useCallback, useContext, useEffect, useMemo, useSyncExternalStore } from 'react';
4
4
  import { isCancelledError, replaceEqualDeep, QueryClient, QueryClientProvider, useMutation, useQuery } from '@tanstack/react-query';
5
- import { DEFAULT_STALE_TIME, FetchQueryCache, IDLE_RETENTION, toQueryError } from '@jini-ai/core/fetch';
5
+ import { DEFAULT_STALE_TIME, FetchQueryCache, IDLE_RETENTION, toQueryError, withCachedLoaderFetch } from '@jini-ai/core/fetch';
6
6
  import { browserFetchQueryEnvironment } from './environment.browser.js';
7
7
  import { resolveFetchQueryError, resolveFetchQueryStatus } from './adapter.react.js';
8
8
  import { FetchQueryBinding } from './binding.react.js';
@@ -42,12 +42,12 @@ export function FetchQueryProvider({ children, environment = browserFetchQueryEn
42
42
  }, [scope, environment]);
43
43
  return _jsx(QueryClientProvider, { client: scope.client, children: _jsx(ScopeContext.Provider, { value: scope, children: _jsx(FetchQueryBinding, { adapter: adapter, children: children }) }) });
44
44
  }
45
- /** Fail closed when an adapter hook is used without its provider. */
45
+ /** The provider's scope. This adapter's hooks are not exported: the only route to them is the
46
+ * `adapter` object this module's own `FetchQueryProvider` binds INSIDE its `ScopeContext`, and the
47
+ * public hooks already fail closed without a provider (`binding.react.tsx`'s `useAdapter`). So the
48
+ * old "no scope" throw here was unreachable and is gone; the guarantee is that binding order. */
46
49
  function useScope() {
47
- const scope = useContext(ScopeContext);
48
- if (!scope)
49
- throw new Error('Fetch-query hooks require FetchQueryProvider');
50
- return scope;
50
+ return useContext(ScopeContext);
51
51
  }
52
52
  /** Observe only this provider's connectivity; paused reads do not claim network activity. */
53
53
  function useOnline({ environment }) {
@@ -174,40 +174,53 @@ function useFetchMutation({ run }, { invalidates } = {}) {
174
174
  }, [mutateAsync]);
175
175
  return { mutate, status: mutation.status, error: mutation.status === 'error' ? toQueryError(mutation.error) : null, reset };
176
176
  }
177
+ /**
178
+ * One loader read of `required.key`, unwrapped to its value. Lifted out of `useCachedLoader`'s `load`
179
+ * verbatim so the missing-fetch guard (`withCachedLoaderFetch`) wrapping it does not push its
180
+ * callbacks past the function-nesting limit.
181
+ */
182
+ function loadLoaderQuery({ scope, options, required, staleTime }) {
183
+ const selected = { ...options, staleTime, queryKey: required.key, queryFn: () => read({ scope, key: required.key, fetch: required.fetch }),
184
+ structuralSharing: (previous, incoming) => saveResult({ scope, key: required.key, previous, incoming }) };
185
+ return scope.client.fetchQuery(selected).catch(error => {
186
+ // A forced refresh can supersede a loader's request without aborting its transport.
187
+ // Follow the new request instead of exposing TanStack's cancellation vocabulary.
188
+ if (!isCancelledError(error))
189
+ throw error;
190
+ // Invalidation also supersedes loader-only reads. Join (or start) the post-write
191
+ // request rather than returning an obsolete value while the key is invalidated.
192
+ const query = scope.client.getQueryCache().find({ queryKey: required.key, exact: true });
193
+ if (query.state.isInvalidated)
194
+ return scope.client.fetchQuery(selected);
195
+ return (query.promise ?? scope.client.getQueryData(required.key));
196
+ }).then(result => {
197
+ const query = scope.client.getQueryCache().find({ queryKey: required.key, exact: true });
198
+ const history = historyFor({ scope, query });
199
+ // Query.fetch returns the transport envelope, not structuralSharing's accepted value.
200
+ // Unwrap against the current generation so awaiting loaders agree with observers.
201
+ if (result.generation !== history.replacement)
202
+ return scope.client.getQueryData(required.key).value;
203
+ if ('error' in result)
204
+ throw result.error;
205
+ return result.value;
206
+ });
207
+ }
177
208
  /** Loader shares reactive reads, retains fresh idle values, and protects replacement writes. */
178
209
  function useCachedLoader({ key, fetch }, { staleTime = DEFAULT_STALE_TIME } = {}) {
179
210
  const scope = useScope();
180
211
  return useMemo(() => {
181
- const options = { queryKey: key, queryFn: () => read({ scope, key, fetch }),
212
+ // The template every loader query starts from. It carries no `queryFn`: `load` always supplies
213
+ // its own (for `required.key`), an observer that later mounts on a `replace`-created query
214
+ // installs the observer's, and an unobserved query is never refetched — so a template `queryFn`
215
+ // here could never run. Its `structuralSharing` does run: `replace` on a cold key builds the query
216
+ // from this template and publishes through it.
217
+ const options = { queryKey: key,
182
218
  structuralSharing: (previous, incoming) => saveResult({ scope, key, previous, incoming }), staleTime, gcTime: gcTimeFor({ staleTime }) };
183
219
  return {
184
220
  peek: () => scope.client.getQueryData(key)?.value,
185
- load: (required = { key, fetch }) => {
186
- const selected = { ...options, queryKey: required.key, queryFn: () => read({ scope, key: required.key, fetch: required.fetch }),
187
- structuralSharing: (previous, incoming) => saveResult({ scope, key: required.key, previous, incoming }) };
188
- return scope.client.fetchQuery(selected).catch(error => {
189
- // A forced refresh can supersede a loader's request without aborting its transport.
190
- // Follow the new request instead of exposing TanStack's cancellation vocabulary.
191
- if (!isCancelledError(error))
192
- throw error;
193
- // Invalidation also supersedes loader-only reads. Join (or start) the post-write
194
- // request rather than returning an obsolete value while the key is invalidated.
195
- const query = scope.client.getQueryCache().find({ queryKey: required.key, exact: true });
196
- if (query.state.isInvalidated)
197
- return scope.client.fetchQuery(selected);
198
- return (query.promise ?? scope.client.getQueryData(required.key));
199
- }).then(result => {
200
- const query = scope.client.getQueryCache().find({ queryKey: required.key, exact: true });
201
- const history = historyFor({ scope, query });
202
- // Query.fetch returns the transport envelope, not structuralSharing's accepted value.
203
- // Unwrap against the current generation so awaiting loaders agree with observers.
204
- if (result.generation !== history.replacement)
205
- return scope.client.getQueryData(required.key).value;
206
- if ('error' in result)
207
- throw result.error;
208
- return result.value;
209
- });
210
- },
221
+ // `fetch` is the loader's optional default; `withCachedLoaderFetch` rejects an argument-less read
222
+ // that has none before any query is built (see `CachedLoaderDefaults`).
223
+ load: (requested = { key, fetch }, { staleTime: readStaleTime = staleTime } = {}) => withCachedLoaderFetch(requested, required => loadLoaderQuery({ scope, options, required, staleTime: readStaleTime })),
211
224
  replace: ({ value }) => {
212
225
  const query = scope.client.getQueryCache().build(scope.client, options);
213
226
  const history = historyFor({ scope, query });
@@ -26,8 +26,10 @@ export const LOCAL_BACKUP_PRINCIPAL_ID = "local-tab-backup";
26
26
  * @complexity O(draft size) for reads/writes; O(1) for clears.
27
27
  */
28
28
  export function createStandingDraftLocalBackup({ keyPrefix: KEY_PREFIX, getStorage }, _options = {}) {
29
+ // Never sees a `null` principal: write/read/clear each return before reaching storage for one, so
30
+ // the old `principalId ?? ""` fallback here could not run.
29
31
  function keyFor(entryId, principalId) {
30
- return principalId === undefined ? `${KEY_PREFIX}${entryId}` : `${KEY_PREFIX}${encodeURIComponent(principalId ?? "")}.${encodeURIComponent(entryId)}`;
32
+ return principalId === undefined ? `${KEY_PREFIX}${entryId}` : `${KEY_PREFIX}${encodeURIComponent(principalId)}.${encodeURIComponent(entryId)}`;
31
33
  }
32
34
  /** Mirrors a refused draft for this browser profile. Silent no-op on any storage failure.
33
35
  * @complexity Time/space: O(size of the draft) for the one `JSON.stringify`. */
@@ -261,7 +261,9 @@ export function useSettingsSlice(options, _optional = {}) {
261
261
  // left to paint. The write itself still has to happen — through the same
262
262
  // lane as the debounce path, so it diffs against the base that is
263
263
  // current when it RUNS, not the one that was current when it was queued.
264
- void writes.run({ task: () => runSave().then(() => { }, () => { }) });
264
+ void writes.run({ task: () => runSave().then(() => { },
265
+ // No view remains to show the failure; settings and native errors may contain secrets.
266
+ () => { console.error('[settings] Final save failed after unmount.'); }) });
265
267
  }, [runSave, writes]);
266
268
  const onChange = useCallback((next) => {
267
269
  setValue(next);
@@ -6,6 +6,27 @@ import { hasMadeConsentDecision, isSharingEnabled, nextStateForDeclineAll, nextS
6
6
  function ToggleRow({ label, hint, checked, onChange, agentHandle }) {
7
7
  return (_jsxs("button", { type: "button", className: `jini-toggle-row${checked ? ' on' : ''}`, onClick: () => onChange(!checked), "aria-pressed": checked, ...agentHandleProps({}, { base: agentHandle, ...({ role: 'checkbox', label }) }), children: [_jsxs("div", { className: "jini-toggle-row-text", children: [_jsx("span", { className: "jini-toggle-row-label", children: label }), _jsx("span", { className: "jini-toggle-row-hint", children: hint })] }), _jsx("span", { className: "jini-toggle-row-switch", "aria-hidden": true })] }));
8
8
  }
9
+ function resolveConsentLabels({ labels }, { t }) {
10
+ return {
11
+ consentKicker: labels.consentKicker ?? t('Help improve this product'),
12
+ consentLead: labels.consentLead ?? t('Choose what you share. You can change this anytime.'),
13
+ consentFooter: labels.consentFooter ?? t('Nothing is shared unless you opt in.'),
14
+ declineLabel: labels.declineLabel ?? t("Don't share"),
15
+ shareLabel: labels.shareLabel ?? t('Share usage'),
16
+ metricsLabel: labels.metricsLabel ?? t('Anonymous metrics'),
17
+ metricsHint: labels.metricsHint ?? t('Feature usage and performance data, with no personal content.'),
18
+ contentLabel: labels.contentLabel ?? t('Conversation and tool content'),
19
+ contentHint: labels.contentHint ?? t('Helps improve model quality. Off by default.'),
20
+ };
21
+ }
22
+ function resolveInstallationLabels({ labels }, { t }) {
23
+ return {
24
+ installationIdLabel: labels.installationIdLabel ?? t('Installation ID'),
25
+ installationIdHint: labels.installationIdHint ?? t('An anonymous id used to group your shared data.'),
26
+ optedOutLabel: labels.optedOutLabel ?? t('Not sharing'),
27
+ deleteMyDataLabel: labels.deleteMyDataLabel ?? t('Delete my data'),
28
+ };
29
+ }
9
30
  /**
10
31
  * Telemetry consent card (share/decline + two per-category toggles) over a
11
32
  * generic `{ metrics, content }` shape, plus an installation-id
@@ -19,19 +40,9 @@ export function PrivacyTab({ state, onChange, labels, now = Date.now, agentHandl
19
40
  const t = useT();
20
41
  const decided = hasMadeConsentDecision(state);
21
42
  const sharing = decided ? isSharingEnabled(state.telemetry) : undefined;
22
- const consentKicker = labels?.consentKicker ?? t('Help improve this product');
23
- const consentLead = labels?.consentLead ?? t('Choose what you share. You can change this anytime.');
24
- const consentFooter = labels?.consentFooter ?? t('Nothing is shared unless you opt in.');
25
- const declineLabel = labels?.declineLabel ?? t("Don't share");
26
- const shareLabel = labels?.shareLabel ?? t('Share usage');
27
- const metricsLabel = labels?.metricsLabel ?? t('Anonymous metrics');
28
- const metricsHint = labels?.metricsHint ?? t('Feature usage and performance data, with no personal content.');
29
- const contentLabel = labels?.contentLabel ?? t('Conversation and tool content');
30
- const contentHint = labels?.contentHint ?? t('Helps improve model quality. Off by default.');
31
- const installationIdLabel = labels?.installationIdLabel ?? t('Installation ID');
32
- const installationIdHint = labels?.installationIdHint ?? t('An anonymous id used to group your shared data.');
33
- const optedOutLabel = labels?.optedOutLabel ?? t('Not sharing');
34
- const deleteMyDataLabel = labels?.deleteMyDataLabel ?? t('Delete my data');
43
+ const labelOverrides = labels ?? {};
44
+ const { consentKicker, consentLead, consentFooter, declineLabel, shareLabel, metricsLabel, metricsHint, contentLabel, contentHint, } = resolveConsentLabels({ labels: labelOverrides }, { t });
45
+ const { installationIdLabel, installationIdHint, optedOutLabel, deleteMyDataLabel, } = resolveInstallationLabels({ labels: labelOverrides }, { t });
35
46
  return (_jsxs("section", { className: "jini-settings-section jini-settings-privacy", children: [_jsxs("div", { className: "jini-settings-subsection", children: [_jsx("div", { className: "jini-section-head", children: _jsxs("div", { children: [_jsx("h4", { children: consentKicker }), _jsx("p", { className: "jini-hint", children: consentLead })] }) }), _jsxs("dl", { className: "jini-settings-privacy-disclosure", children: [_jsxs("div", { children: [_jsx("dt", { children: metricsLabel }), _jsx("dd", { children: metricsHint })] }), _jsxs("div", { children: [_jsx("dt", { children: contentLabel }), _jsx("dd", { children: contentHint })] })] }), _jsx("p", { className: "jini-hint", children: consentFooter }), _jsxs("div", { className: "jini-privacy-consent-actions", role: "group", "aria-label": consentKicker, children: [_jsx("button", { type: "button", className: `jini-privacy-consent-action${sharing === false ? ' is-active' : ''}`, "aria-pressed": sharing === false, onClick: () => onChange(nextStateForDeclineAll(state, now())), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'decline', role: 'button', label: declineLabel }) }), children: declineLabel }), _jsx("button", { type: "button", className: `jini-privacy-consent-action jini-privacy-consent-action--primary${sharing === true ? ' is-active' : ''}`, "aria-pressed": sharing === true, onClick: () => onChange(nextStateForShareAll(state, now())), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'share', role: 'button', label: shareLabel }) }), children: shareLabel })] })] }), decided ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "jini-settings-privacy-toggles", children: [_jsx(ToggleRow, { label: metricsLabel, hint: metricsHint, checked: state.telemetry.metrics === true, onChange: (v) => onChange(nextStateForTelemetryPatch(state, { metrics: v }, now())), ...(agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'metrics-toggle' }) } : {}) }), _jsx(ToggleRow, { label: contentLabel, hint: contentHint, checked: state.telemetry.content === true, onChange: (v) => onChange(nextStateForTelemetryPatch(state, { content: v }, now())), ...(agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'content-toggle' }) } : {}) })] }), _jsxs("div", { className: "jini-settings-subsection", children: [_jsx("div", { className: "jini-section-head", children: _jsxs("div", { children: [_jsx("h4", { children: installationIdLabel }), _jsx("p", { className: "jini-hint", children: installationIdHint })] }) }), _jsx("div", { className: "jini-settings-field", children: _jsx("input", { type: "text", readOnly: true, value: state.installationId ?? optedOutLabel, "aria-label": installationIdLabel, ...agentHandleProps({}, { base: agentHandle, ...({ action: 'installation-id', role: 'field', label: installationIdLabel }) }) }) }), _jsxs("button", { type: "button", className: "jini-button jini-button-ghost", onClick: () => onChange(nextStateForDeleteMyData(state, now())), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'delete-my-data', role: 'button', label: deleteMyDataLabel }) }), children: [_jsx(Icon, { name: "trash", size: 13 }), _jsx("span", { children: deleteMyDataLabel })] })] })] })) : null] }));
36
47
  }
37
48
  //# sourceMappingURL=PrivacyTab.js.map
@@ -14,11 +14,15 @@ export function ProgressCard({ data, maxSteps = 6, maxSecondaryItems = 5 }) {
14
14
  const title = data.title ?? t(defaultProgressCardTitle(data.status));
15
15
  const detail = data.detail ?? t(defaultProgressCardDetail(data.status));
16
16
  const secondaryHeading = t(data.secondaryItemsLabel ?? 'Files touched');
17
- const widthPercent = progressBarWidthPercent(data.progress);
18
- const ariaValueNow = progressBarAriaValueNow(data.progress);
19
17
  const visibleSteps = data.steps.slice(0, maxSteps);
20
18
  const visibleSecondaryItems = (data.secondaryItems ?? []).slice(0, maxSecondaryItems);
21
- return (_jsxs("section", { className: `progress-card is-${data.status}`, "data-progress-card-id": data.id, children: [_jsxs("div", { className: "progress-card-head", children: [_jsx(Icon, { name: progressCardStatusIcon(data.status) }), _jsxs("span", { children: [_jsx("strong", { children: title }), _jsx("small", { children: detail })] })] }), _jsx("div", { className: `progress-card-bar${widthPercent === null ? ' is-indeterminate' : ''}`, role: "progressbar", "aria-label": t('{title} progress', { title }), "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": ariaValueNow, children: _jsx("span", { style: widthPercent === null ? undefined : { width: `${widthPercent}%` } }) }), visibleSteps.length > 0 ? _jsx(ProgressCardItemList, { items: visibleSteps, className: "progress-card-steps" }) : null, visibleSecondaryItems.length > 0 ? (_jsxs("div", { className: "progress-card-secondary-items", children: [_jsx("span", { children: secondaryHeading }), _jsx(ProgressCardItemList, { items: visibleSecondaryItems, className: "progress-card-secondary-item-list", asCode: true })] })) : null] }));
19
+ return (_jsxs("section", { className: `progress-card is-${data.status}`, "data-progress-card-id": data.id, children: [_jsxs("div", { className: "progress-card-head", children: [_jsx(Icon, { name: progressCardStatusIcon(data.status) }), _jsxs("span", { children: [_jsx("strong", { children: title }), _jsx("small", { children: detail })] })] }), _jsx(ProgressCardBar, { progress: data.progress, label: t('{title} progress', { title }) }), visibleSteps.length > 0 ? _jsx(ProgressCardItemList, { items: visibleSteps, className: "progress-card-steps" }) : null, visibleSecondaryItems.length > 0 ? (_jsxs("div", { className: "progress-card-secondary-items", children: [_jsx("span", { children: secondaryHeading }), _jsx(ProgressCardItemList, { items: visibleSecondaryItems, className: "progress-card-secondary-item-list", asCode: true })] })) : null] }));
20
+ }
21
+ /** Presents the existing progress rules as a determinate or indeterminate accessible bar. */
22
+ function ProgressCardBar({ progress, label }) {
23
+ const widthPercent = progressBarWidthPercent(progress);
24
+ const ariaValueNow = progressBarAriaValueNow(progress);
25
+ return (_jsx("div", { className: `progress-card-bar${widthPercent === null ? ' is-indeterminate' : ''}`, role: "progressbar", "aria-label": label, "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": ariaValueNow, children: _jsx("span", { style: widthPercent === null ? undefined : { width: `${widthPercent}%` } }) }));
22
26
  }
23
27
  function ProgressCardItemList({ items, className, asCode, }) {
24
28
  const Tag = asCode ? 'code' : 'span';
@@ -29,17 +29,13 @@ export function parseTodoWriteInput(input) {
29
29
  if (!todo || typeof todo !== 'object')
30
30
  return null;
31
31
  const todoRecord = todo;
32
- const content = typeof todoRecord.content === 'string'
33
- ? todoRecord.content
34
- : typeof todoRecord.step === 'string'
35
- ? todoRecord.step
36
- : typeof todoRecord.description === 'string'
37
- ? todoRecord.description
38
- : typeof todoRecord.label === 'string'
39
- ? todoRecord.label
40
- : typeof todoRecord.text === 'string'
41
- ? todoRecord.text
42
- : '';
32
+ let content = '';
33
+ for (const key of ['content', 'step', 'description', 'label', 'text']) {
34
+ if (typeof todoRecord[key] === 'string') {
35
+ content = todoRecord[key];
36
+ break;
37
+ }
38
+ }
43
39
  if (!content)
44
40
  return null;
45
41
  return { content, status: normalizeTodoStatus(todoRecord.status) };
@@ -118,48 +114,55 @@ export function dedupeToolUsesById(events) {
118
114
  let deduped = null;
119
115
  for (let index = 0; index < events.length; index += 1) {
120
116
  const event = events[index];
121
- if (isToolUseEvent(event)) {
122
- if (seen.has(event.id)) {
123
- if (!deduped)
124
- deduped = events.slice(0, index);
125
- continue;
126
- }
117
+ if (!isToolUseEvent(event)) {
118
+ deduped?.push(event);
119
+ }
120
+ else if (seen.has(event.id)) {
121
+ deduped ??= events.slice(0, index);
122
+ }
123
+ else {
127
124
  seen.add(event.id);
125
+ deduped?.push(event);
128
126
  }
129
- if (deduped)
130
- deduped.push(event);
131
127
  }
132
128
  return deduped ?? events;
133
129
  }
130
+ function fileOpFromAgentEvent({ event, resultByToolUseId }, _options = {}) {
131
+ if (!isToolUseEvent(event))
132
+ return null;
133
+ const kind = classifyFileOp(event.name);
134
+ if (!kind)
135
+ return null;
136
+ const fullPath = extractFileOpPath(event.input);
137
+ if (!fullPath)
138
+ return null;
139
+ const result = resultByToolUseId.get(event.id);
140
+ const status = result === undefined ? 'running' : result.isError ? 'error' : 'done';
141
+ return { fullPath, kind, status };
142
+ }
134
143
  export function deriveFileOpsFromAgentEvents(events) {
135
144
  if (!events || events.length === 0)
136
145
  return [];
137
146
  const dedupedEvents = dedupeToolUsesById(events);
138
- const resultByToolUseId = new Map();
139
- for (const event of dedupedEvents) {
140
- if (isToolResultEvent(event))
141
- resultByToolUseId.set(event.toolUseId, event);
142
- }
147
+ const resultByToolUseId = new Map(dedupedEvents.filter(isToolResultEvent).map((event) => [event.toolUseId, event]));
143
148
  const byPath = new Map();
144
149
  for (const event of dedupedEvents) {
145
- if (!isToolUseEvent(event))
146
- continue;
147
- const kind = classifyFileOp(event.name);
148
- if (!kind)
149
- continue;
150
- const fullPath = extractFileOpPath(event.input);
151
- if (!fullPath)
150
+ const entry = fileOpFromAgentEvent({ event, resultByToolUseId });
151
+ if (!entry)
152
152
  continue;
153
- const result = resultByToolUseId.get(event.id);
154
- const status = result === undefined ? 'running' : result.isError ? 'error' : 'done';
155
- const existing = byPath.get(fullPath);
153
+ const existing = byPath.get(entry.fullPath);
156
154
  if (existing) {
157
- if (!existing.ops.includes(kind))
158
- existing.ops.push(kind);
159
- existing.status = mergeFileOpStatus(existing.status, status);
155
+ if (!existing.ops.includes(entry.kind))
156
+ existing.ops.push(entry.kind);
157
+ existing.status = mergeFileOpStatus(existing.status, entry.status);
160
158
  continue;
161
159
  }
162
- byPath.set(fullPath, { path: basename(fullPath), fullPath, ops: [kind], status });
160
+ byPath.set(entry.fullPath, {
161
+ path: basename(entry.fullPath),
162
+ fullPath: entry.fullPath,
163
+ ops: [entry.kind],
164
+ status: entry.status,
165
+ });
163
166
  }
164
167
  return Array.from(byPath.values());
165
168
  }
@@ -204,15 +207,7 @@ function fallbackChatActivitySteps(status, fileOps) {
204
207
  {
205
208
  id: 'update-files',
206
209
  label: 'Update files',
207
- status: hasError
208
- ? 'failed'
209
- : hasMutation
210
- ? fileOps.some((entry) => entry.status === 'running')
211
- ? 'running'
212
- : 'succeeded'
213
- : status === 'running'
214
- ? 'pending'
215
- : 'succeeded',
210
+ status: fallbackUpdateStatus({ status, fileOps, hasError, hasMutation }),
216
211
  },
217
212
  {
218
213
  id: 'finalize',
@@ -221,6 +216,22 @@ function fallbackChatActivitySteps(status, fileOps) {
221
216
  },
222
217
  ];
223
218
  }
219
+ function fallbackUpdateStatus({ status, fileOps, hasError, hasMutation }, _options = {}) {
220
+ if (hasError)
221
+ return 'failed';
222
+ if (hasMutation)
223
+ return fileOps.some((entry) => entry.status === 'running') ? 'running' : 'succeeded';
224
+ return status === 'running' ? 'pending' : 'succeeded';
225
+ }
226
+ function chatActivitySteps({ todos, status, fileOps }, _options = {}) {
227
+ if (todos.length === 0)
228
+ return fallbackChatActivitySteps(status, fileOps);
229
+ return todos.map((todo, index) => ({
230
+ id: `${todo.content}-${index}`,
231
+ label: todo.content,
232
+ status: todoStatusToProgressStatus(todo.status),
233
+ }));
234
+ }
224
235
  /** Maps a chat message's agent-event stream onto `ProgressCardData`. Returns
225
236
  * `null` when there's nothing worth showing (mirrors the source's own
226
237
  * `hasActivity` early-return — a message with no run, no todos, no file
@@ -234,13 +245,7 @@ export function chatActivityToProgressCard(message, options) {
234
245
  const hasActivity = options.active || todos.length > 0 || fileOps.length > 0 || statusDetail !== null || status === 'failed';
235
246
  if (!hasActivity)
236
247
  return null;
237
- const steps = todos.length > 0
238
- ? todos.map((todo, index) => ({
239
- id: `${todo.content}-${index}`,
240
- label: todo.content,
241
- status: todoStatusToProgressStatus(todo.status),
242
- }))
243
- : fallbackChatActivitySteps(status, fileOps);
248
+ const steps = chatActivitySteps({ todos, status, fileOps });
244
249
  const secondaryItems = fileOps.map((entry) => ({
245
250
  id: entry.fullPath,
246
251
  label: entry.path,
@@ -3,6 +3,29 @@ import { useT } from '../../../i18n/index.js';
3
3
  import { Icon } from '../../../../react/components/Icon.js';
4
4
  import { locationLabel } from '../../rules.js';
5
5
  import { useProjectLocationsTab } from '../hooks/useProjectLocationsTab.js';
6
+ function resolveLabels({ t }, labels = {}) {
7
+ const copy = {
8
+ title: 'Project locations',
9
+ description: 'Where new projects are created and existing ones are found.',
10
+ builtInLabel: 'Default location',
11
+ defaultBadge: 'Default',
12
+ makeDefaultLabel: 'Make default',
13
+ deleteLabel: 'Delete',
14
+ addFolderLabel: 'Add folder',
15
+ rowHint: 'Used as a project root.',
16
+ noFolderSelectedLabel: 'No folder selected.',
17
+ duplicateLabel: 'That folder is already a project location.',
18
+ savedLabel: 'Saved.',
19
+ defaultSavedLabel: 'Default location updated.',
20
+ saveErrorLabel: 'Could not save project locations.',
21
+ scanErrorLabel: 'Could not scan the new folder for existing projects.',
22
+ scanCompleteTemplate: 'Imported {imported}, {existing} already tracked.',
23
+ };
24
+ for (const key of Object.keys(copy)) {
25
+ copy[key] = labels?.[key] ?? t(copy[key]);
26
+ }
27
+ return copy;
28
+ }
6
29
  /**
7
30
  * Configured project roots: the host's built-in default plus operator-added
8
31
  * folders, each markable as the new-project default and removable. Origin:
@@ -13,21 +36,7 @@ import { useProjectLocationsTab } from '../hooks/useProjectLocationsTab.js';
13
36
  export function ProjectLocationsTab({ port, defaultLocationId, onDefaultLocationIdChange, labels }) {
14
37
  const t = useT();
15
38
  const { drafts, builtIn, effectiveDefaultLocationId, saving, result, addFolder, removeDraft, setDefaultLocationId } = useProjectLocationsTab({ port, defaultLocationId, onDefaultLocationIdChange });
16
- const title = labels?.title ?? t('Project locations');
17
- const description = labels?.description ?? t('Where new projects are created and existing ones are found.');
18
- const builtInLabel = labels?.builtInLabel ?? t('Default location');
19
- const defaultBadge = labels?.defaultBadge ?? t('Default');
20
- const makeDefaultLabel = labels?.makeDefaultLabel ?? t('Make default');
21
- const deleteLabel = labels?.deleteLabel ?? t('Delete');
22
- const addFolderLabel = labels?.addFolderLabel ?? t('Add folder');
23
- const rowHint = labels?.rowHint ?? t('Used as a project root.');
24
- const noFolderSelectedLabel = labels?.noFolderSelectedLabel ?? t('No folder selected.');
25
- const duplicateLabel = labels?.duplicateLabel ?? t('That folder is already a project location.');
26
- const savedLabel = labels?.savedLabel ?? t('Saved.');
27
- const defaultSavedLabel = labels?.defaultSavedLabel ?? t('Default location updated.');
28
- const saveErrorLabel = labels?.saveErrorLabel ?? t('Could not save project locations.');
29
- const scanErrorLabel = labels?.scanErrorLabel ?? t('Could not scan the new folder for existing projects.');
30
- const scanCompleteTemplate = labels?.scanCompleteTemplate ?? t('Imported {imported}, {existing} already tracked.');
39
+ const { title, description, builtInLabel, defaultBadge, makeDefaultLabel, deleteLabel, addFolderLabel, rowHint, noFolderSelectedLabel, duplicateLabel, savedLabel, defaultSavedLabel, saveErrorLabel, scanErrorLabel, scanCompleteTemplate, } = resolveLabels({ t }, labels);
31
40
  function defaultControlLabel(active) {
32
41
  return active ? defaultBadge : makeDefaultLabel;
33
42
  }
@@ -26,6 +26,14 @@ export interface UseProjectLocationsTabResult {
26
26
  removeDraft: (index: number) => void;
27
27
  setDefaultLocationId: (id: string) => void;
28
28
  }
29
+ /** Scan a saved location; disposed callers cannot publish success or failure. */
30
+ export declare function scanAddedLocation({ port, alive, setResult }: {
31
+ port: ProjectLocationsPort;
32
+ alive: {
33
+ readonly current: boolean;
34
+ };
35
+ setResult: (result: ProjectLocationsActionResult) => void;
36
+ }, _options?: {}): Promise<void>;
29
37
  /**
30
38
  * Owns the tab's async edges: the initial locations fetch, the add-folder ->
31
39
  * duplicate-check -> save -> (optional) scan sequence, and removal. Origin:
@@ -1,5 +1,22 @@
1
1
  import { useCallback, useEffect, useRef, useState } from 'react';
2
2
  import { DEFAULT_LOCATION_ID, externalLocations, isDuplicatePath, resolveDefaultLocationId, saveableDrafts, } from '../../rules.js';
3
+ /** Scan a saved location; disposed callers cannot publish success or failure. */
4
+ export async function scanAddedLocation({ port, alive, setResult }, _options = {}) {
5
+ const scan = port.scanLocations;
6
+ if (!scan)
7
+ return;
8
+ try {
9
+ const scanResult = await scan.call(port);
10
+ if (!alive.current)
11
+ return;
12
+ setResult({ status: 'scan-complete', imported: scanResult.imported.length, existing: scanResult.existing.length });
13
+ }
14
+ catch (error) {
15
+ if (!alive.current)
16
+ return;
17
+ setResult({ status: 'scan-error', message: error instanceof Error ? error.message : String(error) });
18
+ }
19
+ }
3
20
  /**
4
21
  * Owns the tab's async edges: the initial locations fetch, the add-folder ->
5
22
  * duplicate-check -> save -> (optional) scan sequence, and removal. Origin:
@@ -56,11 +73,7 @@ export function useProjectLocationsTab({ port, defaultLocationId, onDefaultLocat
56
73
  };
57
74
  }, [port]);
58
75
  const persist = useCallback(async (nextDrafts) => {
59
- setSaving(true);
60
- try {
61
- const saved = await portRef.current.saveLocations(saveableDrafts(nextDrafts));
62
- if (!alive.current)
63
- return null;
76
+ const applySaved = (saved) => {
64
77
  setLocations(saved);
65
78
  setDrafts(externalLocations(saved));
66
79
  setResult({ status: 'saved' });
@@ -72,17 +85,25 @@ export function useProjectLocationsTab({ port, defaultLocationId, onDefaultLocat
72
85
  if (effective !== configured)
73
86
  onDefaultLocationIdChangeRef.current?.(effective);
74
87
  return saved;
88
+ };
89
+ setSaving(true);
90
+ // One return after the try statement (null unless a still-mounted save succeeded), so no
91
+ // unreachable continuation follows a try and a catch that both return.
92
+ let persisted = null;
93
+ try {
94
+ const saved = await portRef.current.saveLocations(saveableDrafts(nextDrafts));
95
+ if (alive.current)
96
+ persisted = applySaved(saved);
75
97
  }
76
98
  catch (error) {
77
- if (!alive.current)
78
- return null;
79
- setResult({ status: 'save-error', message: error instanceof Error ? error.message : String(error) });
80
- return null;
99
+ if (alive.current)
100
+ setResult({ status: 'save-error', message: error instanceof Error ? error.message : String(error) });
81
101
  }
82
102
  finally {
83
103
  if (alive.current)
84
104
  setSaving(false);
85
105
  }
106
+ return persisted;
86
107
  }, []);
87
108
  const addFolder = useCallback(() => {
88
109
  void (async () => {
@@ -111,20 +132,7 @@ export function useProjectLocationsTab({ port, defaultLocationId, onDefaultLocat
111
132
  setDrafts(previous);
112
133
  return;
113
134
  }
114
- const scan = portRef.current.scanLocations;
115
- if (!scan)
116
- return;
117
- try {
118
- const scanResult = await scan.call(portRef.current);
119
- if (!alive.current)
120
- return;
121
- setResult({ status: 'scan-complete', imported: scanResult.imported.length, existing: scanResult.existing.length });
122
- }
123
- catch (error) {
124
- if (!alive.current)
125
- return;
126
- setResult({ status: 'scan-error', message: error instanceof Error ? error.message : String(error) });
127
- }
135
+ await scanAddedLocation({ port: portRef.current, alive, setResult });
128
136
  })();
129
137
  }, [persist]);
130
138
  const removeDraft = useCallback((index) => {
@@ -2,7 +2,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useT } from '../../../i18n/index.js';
3
3
  import { useWiredResourceBoard } from '../hooks/useResourceBoard.js';
4
4
  import { ResourceBoardView } from './ResourceBoardView.js';
5
- import { UNMATCHED_STATUS_BUCKET } from '../../constants.js';
5
+ import { boardCreateProps, buildKanbanColumns, createItemActionHandler, resourceBoardHookParams, statusLabelFor } from './resourceBoardProps.js';
6
+ import { translatedErrorLabelProps } from './translatedErrorLabelProps.js';
6
7
  /**
7
8
  * The DesignsTab-shaped orchestrator: wires `useWiredResourceBoard` +
8
9
  * `useT()` + `ResourceBoardView`. `rename`/`duplicate`/`delete` are the
@@ -14,51 +15,9 @@ import { UNMATCHED_STATUS_BUCKET } from '../../constants.js';
14
15
  */
15
16
  export function ResourceBoard({ dependencies, statusOptions, toneMap, defaultStatus, normalizeStatus, sortOptions = [], initialSort, storageScopeKey, defaultViewMode, enableBulkDelete = true, onOpenItem, onCreate, createLabel, onRenameRequest, onCustomItemAction, refreshToken, renderBody, useWiredResourceBoard: useWiredResourceBoardHook = useWiredResourceBoard, }) {
16
17
  const t = useT();
17
- const board = useWiredResourceBoardHook({
18
- dependencies,
19
- statusOrder: statusOptions.map((option) => option.value),
20
- ...(defaultStatus !== undefined ? { defaultStatus } : {}),
21
- ...(normalizeStatus !== undefined ? { normalizeStatus } : {}),
22
- ...(initialSort !== undefined ? { initialSort } : {}),
23
- ...(storageScopeKey !== undefined ? { storageScopeKey } : {}),
24
- ...(defaultViewMode !== undefined ? { defaultViewMode } : {}),
25
- ...(refreshToken !== undefined ? { refreshToken } : {}),
26
- });
27
- const kanbanColumns = statusOptions.map((option) => ({
28
- status: option.value,
29
- label: t(option.label),
30
- // Non-null: `useWiredResourceBoard` above is called with `statusOrder:
31
- // statusOptions.map(option => option.value)`, and `groupItemsByStatus`
32
- // (rules.ts) always seeds one Map entry per `statusOrder` value before
33
- // placing any item — so every `option.value` here is guaranteed present.
34
- items: board.kanbanColumns.get(option.value),
35
- }));
36
- const unmatched = board.kanbanColumns.get(UNMATCHED_STATUS_BUCKET);
37
- if (unmatched && unmatched.length > 0) {
38
- kanbanColumns.push({ status: UNMATCHED_STATUS_BUCKET, label: t('Other'), items: unmatched });
39
- }
40
- const handleItemAction = (id, kind) => {
41
- if (kind === 'delete') {
42
- void board.remove(id);
43
- return;
44
- }
45
- if (kind === 'duplicate') {
46
- void board.duplicate(id);
47
- return;
48
- }
49
- if (kind === 'rename') {
50
- board.closeMenu();
51
- // Non-null: `id` only ever reaches this handler via a menu-action
52
- // click closure `ResourceBoardView` builds per rendered item (from
53
- // this SAME render's `board.visibleItems`), so the item is always
54
- // still present in the array at lookup time.
55
- const current = board.visibleItems.find((item) => item.id === id);
56
- onRenameRequest?.(id, current.title);
57
- return;
58
- }
59
- board.closeMenu();
60
- onCustomItemAction?.(id, kind);
61
- };
62
- return (_jsx(ResourceBoardView, { loading: board.loading, error: board.error, ...(board.error ? { errorLabel: t(board.error) } : {}), actionError: board.actionError, ...(board.actionError ? { actionErrorLabel: t(board.actionError) } : {}), hasAnyItems: board.totalCount > 0, items: board.visibleItems, kanbanColumns: kanbanColumns, sortOptions: sortOptions, ...(board.sort !== undefined ? { activeSort: board.sort } : {}), onSortChange: board.setSort, sortAriaLabel: t('Sort'), query: board.query, onQueryChange: board.setQuery, searchPlaceholder: t('Search…'), viewMode: board.viewMode, onViewModeChange: board.setViewMode, viewToggleAriaLabel: t('View'), gridViewLabel: t('Grid view'), kanbanViewLabel: t('Kanban view'), enableBulkDelete: enableBulkDelete, selectMode: board.selectMode, selected: board.selected, bulkDeleteBusy: board.bulkDeleteBusy, onEnterSelectMode: board.enterSelectMode, onExitSelectMode: board.exitSelectMode, onBulkDelete: () => void board.bulkDelete(), selectModeLabel: t('Select'), selectedCountLabel: (n) => t('{n} selected', { n }), deleteSelectedLabel: t('Delete selected'), cancelSelectLabel: t('Cancel'), ...(createLabel !== undefined || onCreate ? { createLabel: createLabel ?? t('New') } : {}), ...(onCreate ? { onCreate } : {}), openMenuId: board.openMenuId, menuContainerRef: board.menuContainerRef, onToggleMenu: board.toggleMenu, onItemAction: handleItemAction, isItemBusy: board.isItemBusy, onOpenItem: onOpenItem, onToggleSelected: board.toggleSelected, onKanbanDelete: (id) => void board.remove(id), statusLabel: (status) => t(statusOptions.find((option) => option.value === status)?.label ?? status), ...(toneMap ? { toneMap } : {}), menuActionLabel: (_kind, fallback) => t(fallback), moreLabel: t('More'), deleteLabel: t('Delete'), deleteAriaLabel: (title) => t('Delete {name}', { name: title }), emptyColumnLabel: t('No items in this column.'), emptyStateTitle: t('No items yet.'), emptyStateNoMatch: t('No items match your search.'), ...(renderBody ? { renderBody } : {}) }));
18
+ const board = useWiredResourceBoardHook(resourceBoardHookParams({ dependencies, statusOptions }, { defaultStatus, normalizeStatus, initialSort, storageScopeKey, defaultViewMode, refreshToken }));
19
+ const kanbanColumns = buildKanbanColumns({ statusOptions, columns: board.kanbanColumns, t });
20
+ const handleItemAction = createItemActionHandler({ board }, { onRenameRequest, onCustomItemAction });
21
+ return (_jsx(ResourceBoardView, { loading: board.loading, error: board.error, actionError: board.actionError, ...translatedErrorLabelProps({ t, error: board.error, actionError: board.actionError }), hasAnyItems: board.totalCount > 0, items: board.visibleItems, kanbanColumns: kanbanColumns, sortOptions: sortOptions, ...(board.sort !== undefined ? { activeSort: board.sort } : {}), onSortChange: board.setSort, sortAriaLabel: t('Sort'), query: board.query, onQueryChange: board.setQuery, searchPlaceholder: t('Search…'), viewMode: board.viewMode, onViewModeChange: board.setViewMode, viewToggleAriaLabel: t('View'), gridViewLabel: t('Grid view'), kanbanViewLabel: t('Kanban view'), enableBulkDelete: enableBulkDelete, selectMode: board.selectMode, selected: board.selected, bulkDeleteBusy: board.bulkDeleteBusy, onEnterSelectMode: board.enterSelectMode, onExitSelectMode: board.exitSelectMode, onBulkDelete: () => void board.bulkDelete(), selectModeLabel: t('Select'), selectedCountLabel: (n) => t('{n} selected', { n }), deleteSelectedLabel: t('Delete selected'), cancelSelectLabel: t('Cancel'), ...boardCreateProps({ t }, { createLabel, onCreate }), openMenuId: board.openMenuId, menuContainerRef: board.menuContainerRef, onToggleMenu: board.toggleMenu, onItemAction: handleItemAction, isItemBusy: board.isItemBusy, onOpenItem: onOpenItem, onToggleSelected: board.toggleSelected, onKanbanDelete: (id) => void board.remove(id), statusLabel: statusLabelFor({ t, statusOptions }), ...(toneMap ? { toneMap } : {}), menuActionLabel: (_kind, fallback) => t(fallback), moreLabel: t('More'), deleteLabel: t('Delete'), deleteAriaLabel: (title) => t('Delete {name}', { name: title }), emptyColumnLabel: t('No items in this column.'), emptyStateTitle: t('No items yet.'), emptyStateNoMatch: t('No items match your search.'), ...(renderBody ? { renderBody } : {}) }));
63
22
  }
64
23
  //# sourceMappingURL=ResourceBoard.js.map
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode, RefObject } from 'react';
2
- import { type ResourceKanbanColumn } from './ResourceKanbanBoard.js';
2
+ import type { ResourceKanbanColumn } from './ResourceKanbanBoard.js';
3
3
  import type { ResourceBoardItem, ResourceBoardViewMode, ResourceSortOption, ResourceStatusToneMap } from '../../types.js';
4
4
  export interface ResourceBoardViewProps<TBody = unknown> {
5
5
  loading: boolean;
@@ -61,5 +61,5 @@ export interface ResourceBoardViewProps<TBody = unknown> {
61
61
  * state, the card grid, or the kanban board — DesignsTab's own
62
62
  * `filtered.length === 0 ? empty : view === "grid" ? grid : kanban` branch.
63
63
  */
64
- export declare function ResourceBoardView<TBody = unknown>({ loading, error, errorLabel, actionError, actionErrorLabel, hasAnyItems, items, kanbanColumns, sortOptions, activeSort, onSortChange, sortAriaLabel, query, onQueryChange, searchPlaceholder, viewMode, onViewModeChange, viewToggleAriaLabel, gridViewLabel, kanbanViewLabel, enableBulkDelete, selectMode, selected, bulkDeleteBusy, onEnterSelectMode, onExitSelectMode, onBulkDelete, selectModeLabel, selectedCountLabel, deleteSelectedLabel, cancelSelectLabel, createLabel, onCreate, openMenuId, menuContainerRef, onToggleMenu, onItemAction, isItemBusy, onOpenItem, onToggleSelected, onKanbanDelete, statusLabel, toneMap, menuActionLabel, moreLabel, deleteLabel, deleteAriaLabel, emptyColumnLabel, emptyStateTitle, emptyStateNoMatch, renderBody, }: ResourceBoardViewProps<TBody>): import("react").JSX.Element;
64
+ export declare function ResourceBoardView<TBody = unknown>(props: ResourceBoardViewProps<TBody>): import("react").JSX.Element;
65
65
  //# sourceMappingURL=ResourceBoardView.d.ts.map