@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
@@ -27,27 +27,22 @@ import { testStatusLabel } from '../../rules.js';
27
27
  */
28
28
  export function NotificationsTab({ preferences, onChange, testNotificationTitle, testNotificationBody, labels, agentHandle, }) {
29
29
  const t = useT();
30
- const successSoundHandles = agentHandle
31
- ? buildAgentListHandles({ prefix: agentSubHandle({ base: agentHandle, action: 'success-sound' }), ids: SUCCESS_SOUNDS.map((s) => s.id) })
32
- : undefined;
33
- const failureSoundHandles = agentHandle
34
- ? buildAgentListHandles({ prefix: agentSubHandle({ base: agentHandle, action: 'failure-sound' }), ids: FAILURE_SOUNDS.map((s) => s.id) })
35
- : undefined;
36
30
  const [permission, setPermission] = useState(() => notificationPermission());
37
31
  const [testStatus, setTestStatus] = useState(null);
38
- const completionSoundTitle = labels?.completionSoundTitle ?? t('Completion sound');
39
- const completionSoundHint = labels?.completionSoundHint ?? t('Play a sound when a task finishes.');
40
- const successSoundLabel = labels?.successSoundLabel ?? t('Success sound');
41
- const failureSoundLabel = labels?.failureSoundLabel ?? t('Failure sound');
42
- const desktopTitle = labels?.desktopTitle ?? t('Desktop notifications');
43
- const desktopHint = labels?.desktopHint ?? t('Show a system notification when a task finishes.');
44
- const desktopUnsupported = labels?.desktopUnsupported ?? t('Desktop notifications are not supported in this browser.');
45
- const desktopBlocked = labels?.desktopBlocked ?? t('Desktop notifications are blocked. Enable them in your browser settings.');
46
- const sendTestLabel = labels?.sendTestLabel ?? t('Send test notification');
47
- const testSentLabel = labels?.testSentLabel ?? t('Test notification sent.');
48
- const testFailedLabel = labels?.testFailedLabel ?? t('Could not send a test notification.');
49
- const activeLabel = labels?.activeLabel ?? t('On');
50
- const offLabel = labels?.offLabel ?? t('Off');
32
+ const label = ({ key, fallback }, _options = {}) => labels?.[key] ?? t(fallback);
33
+ const completionSoundTitle = label({ key: 'completionSoundTitle', fallback: 'Completion sound' });
34
+ const completionSoundHint = label({ key: 'completionSoundHint', fallback: 'Play a sound when a task finishes.' });
35
+ const successSoundLabel = label({ key: 'successSoundLabel', fallback: 'Success sound' });
36
+ const failureSoundLabel = label({ key: 'failureSoundLabel', fallback: 'Failure sound' });
37
+ const desktopTitle = label({ key: 'desktopTitle', fallback: 'Desktop notifications' });
38
+ const desktopHint = label({ key: 'desktopHint', fallback: 'Show a system notification when a task finishes.' });
39
+ const desktopUnsupported = label({ key: 'desktopUnsupported', fallback: 'Desktop notifications are not supported in this browser.' });
40
+ const desktopBlocked = label({ key: 'desktopBlocked', fallback: 'Desktop notifications are blocked. Enable them in your browser settings.' });
41
+ const sendTestLabel = label({ key: 'sendTestLabel', fallback: 'Send test notification' });
42
+ const testSentLabel = label({ key: 'testSentLabel', fallback: 'Test notification sent.' });
43
+ const testFailedLabel = label({ key: 'testFailedLabel', fallback: 'Could not send a test notification.' });
44
+ const activeLabel = label({ key: 'activeLabel', fallback: 'On' });
45
+ const offLabel = label({ key: 'offLabel', fallback: 'Off' });
51
46
  const toggleSound = () => {
52
47
  const next = !preferences.soundEnabled;
53
48
  onChange({ soundEnabled: next });
@@ -76,16 +71,21 @@ export function NotificationsTab({ preferences, onChange, testNotificationTitle,
76
71
  // outcome here rather than getting their own dead states.
77
72
  setTestStatus(result === 'shown' ? 'sent' : 'failed');
78
73
  };
79
- return (_jsxs("section", { className: "jini-settings-section jini-settings-notifications", children: [_jsxs("div", { className: "jini-settings-subsection", children: [_jsxs("div", { className: "jini-settings-notify-card", children: [_jsxs("div", { className: "jini-settings-notify-card-header", children: [_jsx("h4", { children: completionSoundTitle }), _jsx("div", { className: "jini-seg-control", role: "group", "aria-label": completionSoundTitle, style: { '--seg-cols': 1 }, children: _jsx("button", { type: "button", className: 'jini-seg-btn' + (preferences.soundEnabled ? ' active' : ''), "aria-pressed": preferences.soundEnabled, "aria-label": completionSoundTitle, onClick: toggleSound, ...agentHandleProps({}, { base: agentHandle, ...({ action: 'sound-toggle', role: 'button', label: completionSoundTitle }) }), children: _jsx("span", { className: "jini-seg-title", children: preferences.soundEnabled ? activeLabel : offLabel }) }) })] }), _jsx("p", { className: "jini-hint", children: completionSoundHint })] }), preferences.soundEnabled ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "jini-settings-field", children: [_jsx("label", { children: successSoundLabel }), _jsx("div", { className: "jini-seg-control", role: "group", "aria-label": successSoundLabel, style: { '--seg-cols': SUCCESS_SOUNDS.length }, children: SUCCESS_SOUNDS.map((sound, index) => (_jsx("button", { type: "button", className: 'jini-seg-btn' + (preferences.successSoundId === sound.id ? ' active' : ''), "aria-pressed": preferences.successSoundId === sound.id, onClick: () => {
80
- onChange({ successSoundId: sound.id });
81
- playSound(sound.id);
82
- }, ...agentHandleProps({}, { base: successSoundHandles?.[index], ...({ role: 'button', label: t(sound.labelKey) }) }), children: _jsx("span", { className: "jini-seg-title", children: t(sound.labelKey) }) }, sound.id))) })] }), _jsxs("div", { className: "jini-settings-field", children: [_jsx("label", { children: failureSoundLabel }), _jsx("div", { className: "jini-seg-control", role: "group", "aria-label": failureSoundLabel, style: { '--seg-cols': FAILURE_SOUNDS.length }, children: FAILURE_SOUNDS.map((sound, index) => (_jsx("button", { type: "button", className: 'jini-seg-btn' + (preferences.failureSoundId === sound.id ? ' active' : ''), "aria-pressed": preferences.failureSoundId === sound.id, onClick: () => {
83
- onChange({ failureSoundId: sound.id });
84
- playSound(sound.id);
85
- }, ...agentHandleProps({}, { base: failureSoundHandles?.[index], ...({ role: 'button', label: t(sound.labelKey) }) }), children: _jsx("span", { className: "jini-seg-title", children: t(sound.labelKey) }) }, sound.id))) })] })] })) : null] }), _jsxs("div", { className: "jini-settings-subsection", children: [_jsxs("div", { className: "jini-settings-notify-card", children: [_jsxs("div", { className: "jini-settings-notify-card-header", children: [_jsx("h4", { children: desktopTitle }), _jsx("div", { className: "jini-seg-control", role: "group", "aria-label": desktopTitle, style: { '--seg-cols': 1 }, children: _jsx("button", { type: "button", className: 'jini-seg-btn' + (preferences.desktopEnabled ? ' active' : ''), "aria-pressed": preferences.desktopEnabled, "aria-label": desktopTitle, disabled: permission === 'unsupported', onClick: () => {
86
- void toggleDesktop();
87
- }, ...agentHandleProps({}, { base: agentHandle, ...({ action: 'desktop-toggle', role: 'button', label: desktopTitle }) }), children: _jsx("span", { className: "jini-seg-title", children: preferences.desktopEnabled ? activeLabel : offLabel }) }) })] }), _jsx("p", { className: "jini-hint", children: desktopHint })] }), permission === 'unsupported' ? _jsx("p", { className: "jini-hint", children: desktopUnsupported }) : null, permission === 'denied' ? _jsx("p", { className: "jini-hint", children: desktopBlocked }) : null, preferences.desktopEnabled && permission === 'granted' ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "jini-button jini-button-ghost", onClick: () => {
74
+ return (_jsxs("section", { className: "jini-settings-section jini-settings-notifications", children: [_jsxs("div", { className: "jini-settings-subsection", children: [_jsxs("div", { className: "jini-settings-notify-card", children: [_jsxs("div", { className: "jini-settings-notify-card-header", children: [_jsx("h4", { children: completionSoundTitle }), _jsx(NotificationToggle, { title: completionSoundTitle, enabled: preferences.soundEnabled, onToggle: toggleSound, activeLabel: activeLabel, offLabel: offLabel, agentHandle: agentHandle, action: "sound-toggle" })] }), _jsx("p", { className: "jini-hint", children: completionSoundHint })] }), preferences.soundEnabled ? (_jsxs(_Fragment, { children: [_jsx(SoundPicker, { label: successSoundLabel, sounds: SUCCESS_SOUNDS, selectedSoundId: preferences.successSoundId, preferenceKey: "successSoundId", onChange: onChange, previewSound: playSound, agentHandle: agentHandle, action: "success-sound" }), _jsx(SoundPicker, { label: failureSoundLabel, sounds: FAILURE_SOUNDS, selectedSoundId: preferences.failureSoundId, preferenceKey: "failureSoundId", onChange: onChange, previewSound: playSound, agentHandle: agentHandle, action: "failure-sound" })] })) : null] }), _jsxs("div", { className: "jini-settings-subsection", children: [_jsxs("div", { className: "jini-settings-notify-card", children: [_jsxs("div", { className: "jini-settings-notify-card-header", children: [_jsx("h4", { children: desktopTitle }), _jsx(NotificationToggle, { title: desktopTitle, enabled: preferences.desktopEnabled, onToggle: toggleDesktop, activeLabel: activeLabel, offLabel: offLabel, agentHandle: agentHandle, action: "desktop-toggle", disabled: permission === 'unsupported' })] }), _jsx("p", { className: "jini-hint", children: desktopHint })] }), permission === 'unsupported' ? _jsx("p", { className: "jini-hint", children: desktopUnsupported }) : null, permission === 'denied' ? _jsx("p", { className: "jini-hint", children: desktopBlocked }) : null, preferences.desktopEnabled && permission === 'granted' ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "jini-button jini-button-ghost", onClick: () => {
88
75
  void sendTestNotification();
89
76
  }, ...agentHandleProps({}, { base: agentHandle, ...({ action: 'send-test', role: 'button', label: sendTestLabel }) }), children: sendTestLabel }), testStatus ? (_jsx("p", { className: "jini-hint", role: "status", children: testStatusLabel(testStatus, { testSentLabel, testFailedLabel }) })) : null] })) : null] })] }));
90
77
  }
78
+ function NotificationToggle({ title, enabled, onToggle, activeLabel, offLabel, agentHandle, action, disabled, }) {
79
+ return (_jsx("div", { className: "jini-seg-control", role: "group", "aria-label": title, style: { '--seg-cols': 1 }, children: _jsx("button", { type: "button", className: 'jini-seg-btn' + (enabled ? ' active' : ''), "aria-pressed": enabled, "aria-label": title, disabled: disabled, onClick: () => { void onToggle(); }, ...agentHandleProps({}, { base: agentHandle, action, role: 'button', label: title }), children: _jsx("span", { className: "jini-seg-title", children: enabled ? activeLabel : offLabel }) }) }));
80
+ }
81
+ function SoundPicker({ label, sounds, selectedSoundId, preferenceKey, onChange, previewSound, agentHandle, action, }) {
82
+ const t = useT();
83
+ const handles = agentHandle
84
+ ? buildAgentListHandles({ prefix: agentSubHandle({ base: agentHandle, action }), ids: sounds.map((sound) => sound.id) })
85
+ : undefined;
86
+ return (_jsxs("div", { className: "jini-settings-field", children: [_jsx("label", { children: label }), _jsx("div", { className: "jini-seg-control", role: "group", "aria-label": label, style: { '--seg-cols': sounds.length }, children: sounds.map((sound, index) => (_jsx("button", { type: "button", className: 'jini-seg-btn' + (selectedSoundId === sound.id ? ' active' : ''), "aria-pressed": selectedSoundId === sound.id, onClick: () => {
87
+ onChange({ [preferenceKey]: sound.id });
88
+ previewSound(sound.id);
89
+ }, ...agentHandleProps({}, { base: handles?.[index], role: 'button', label: t(sound.labelKey) }), children: _jsx("span", { className: "jini-seg-title", children: t(sound.labelKey) }) }, sound.id))) })] }));
90
+ }
91
91
  //# sourceMappingURL=NotificationsTab.js.map
@@ -1,15 +1,3 @@
1
- /**
2
- * Long task observer.
3
- *
4
- * Reports a `client_long_task` event whenever the main thread is blocked
5
- * for more than `minDurationMs`. Long tasks are a strong proxy for
6
- * perceived UI lag — a 500 ms task drops 30 consecutive frames at 60 fps,
7
- * which the user reads as "stuck".
8
- *
9
- * Threshold defaults to 100 ms rather than the W3C 50 ms minimum because
10
- * the browser already filters at 50 ms; bumping to 100 ms here halves the
11
- * event volume while still catching every task a human notices.
12
- */
13
1
  import { type SafetyEventReporter } from './ports.js';
14
2
  export interface LongTaskObserverOptions {
15
3
  reporter?: SafetyEventReporter | undefined;
@@ -10,6 +10,7 @@
10
10
  * the browser already filters at 50 ms; bumping to 100 ms here halves the
11
11
  * event volume while still catching every task a human notices.
12
12
  */
13
+ import { telemetryUrl } from './telemetry-url.js';
13
14
  import { noopSafetyEventReporter } from './ports.js';
14
15
  const DEFAULT_MIN_DURATION_MS = 100;
15
16
  let observer = null;
@@ -46,37 +47,32 @@ export function installLongTaskObserver(options = {}) {
46
47
  container_type: attribution?.containerType,
47
48
  container_name: attribution?.containerName,
48
49
  // containerSrc can carry a query string; trimmed to origin+pathname.
49
- container_src_origin: stripUrlQuery(attribution?.containerSrc),
50
+ container_src_origin: telemetryUrl({ value: attribution?.containerSrc }),
50
51
  });
51
52
  }
52
53
  });
53
- try {
54
- observer.observe({ type: 'longtask', buffered: true });
55
- }
56
- catch {
57
- // Older engines sometimes throw when `buffered` is requested.
58
- try {
59
- observer.observe({ type: 'longtask' });
60
- }
61
- catch {
62
- observer = null;
63
- return () => undefined;
64
- }
54
+ if (!observeLongTasks({ observe: (options) => observer.observe(options) })) {
55
+ observer = null;
56
+ return () => undefined;
65
57
  }
66
58
  return () => {
67
59
  observer?.disconnect();
68
60
  observer = null;
69
61
  };
70
62
  }
71
- function stripUrlQuery(value) {
72
- if (typeof value !== 'string' || value.length === 0)
73
- return undefined;
63
+ function observeLongTasks({ observe }, _options = {}) {
74
64
  try {
75
- const parsed = new URL(value);
76
- return `${parsed.origin}${parsed.pathname}`;
65
+ observe({ type: 'longtask', buffered: true });
77
66
  }
78
67
  catch {
79
- return value;
68
+ // Older engines sometimes throw when `buffered` is requested.
69
+ try {
70
+ observe({ type: 'longtask' });
71
+ }
72
+ catch {
73
+ return false;
74
+ }
80
75
  }
76
+ return true;
81
77
  }
82
78
  //# sourceMappingURL=long-task.js.map
@@ -1,13 +1,3 @@
1
- /**
2
- * Resource-loading error observer.
3
- *
4
- * The bubbling `error` event (registered with capture=false) catches
5
- * thrown JS errors but NOT failed resource loads — `<script>`, `<link>`,
6
- * `<img>`, etc. emit an `error` event that does not propagate. To pick
7
- * those up we register a *capturing* listener on `window`. This is the
8
- * canonical pattern for chunk-load failures: a missing static bundle
9
- * chunk results in a non-functional app with no JS exception.
10
- */
11
1
  import { type SafetyEventReporter } from './ports.js';
12
2
  export interface ResourceErrorObserverOptions {
13
3
  reporter?: SafetyEventReporter | undefined;
@@ -8,6 +8,7 @@
8
8
  * canonical pattern for chunk-load failures: a missing static bundle
9
9
  * chunk results in a non-functional app with no JS exception.
10
10
  */
11
+ import { telemetryUrl } from './telemetry-url.js';
11
12
  import { noopSafetyEventReporter } from './ports.js';
12
13
  const RESOURCE_TAGS = new Set(['SCRIPT', 'LINK', 'IMG', 'IFRAME', 'AUDIO', 'VIDEO', 'SOURCE', 'TRACK']);
13
14
  let installed = false;
@@ -29,8 +30,8 @@ export function installResourceErrorObserver(options = {}) {
29
30
  return;
30
31
  if (!RESOURCE_TAGS.has(target.tagName))
31
32
  return;
32
- const src = readSrc(target);
33
- if (src == null)
33
+ const src = telemetryUrl({ value: readSrc(target) });
34
+ if (typeof src !== 'string' || src.length === 0)
34
35
  return;
35
36
  reporter('client_resource_error', {
36
37
  tag: target.tagName.toLowerCase(),
@@ -50,16 +51,20 @@ export function installResourceErrorObserver(options = {}) {
50
51
  };
51
52
  }
52
53
  function readSrc(el) {
53
- const value = el instanceof HTMLLinkElement ? el.href :
54
- el instanceof HTMLScriptElement ? el.src :
55
- el instanceof HTMLImageElement ? el.src :
56
- el instanceof HTMLIFrameElement ? el.src :
57
- el instanceof HTMLSourceElement ? el.src :
58
- el instanceof HTMLTrackElement ? el.src :
59
- el instanceof HTMLMediaElement ? el.src :
60
- (el.getAttribute('src') ?? el.getAttribute('href'));
61
- if (typeof value !== 'string' || value.length === 0)
62
- return null;
63
- return value;
54
+ if (el instanceof HTMLLinkElement)
55
+ return el.href;
56
+ if (el instanceof HTMLScriptElement)
57
+ return el.src;
58
+ if (el instanceof HTMLImageElement)
59
+ return el.src;
60
+ if (el instanceof HTMLIFrameElement)
61
+ return el.src;
62
+ if (el instanceof HTMLSourceElement)
63
+ return el.src;
64
+ if (el instanceof HTMLTrackElement)
65
+ return el.src;
66
+ if (el instanceof HTMLMediaElement)
67
+ return el.src;
68
+ return el.getAttribute('src') ?? el.getAttribute('href');
64
69
  }
65
70
  //# sourceMappingURL=resource-error.js.map
@@ -79,8 +79,8 @@ function scheduleEmit(runId, stuckAfterMs) {
79
79
  return setTimeout(() => emitStuck(runId), stuckAfterMs);
80
80
  }
81
81
  function emitStuck(runId) {
82
- const entry = runs.get(runId);
83
- // `!entry || entry.emitted` is a defensive invariant guard, empirically
82
+ // Asserted present: there is no `!entry || entry.emitted` guard here (removed in the 2026-10-21
83
+ // coverage pass). Such a guard was a defensive invariant check, empirically
84
84
  // and structurally unreachable through this module's real call graph
85
85
  // today: `emitStuck` only ever runs as a `setTimeout` callback created by
86
86
  // `scheduleEmit`, and every path that could remove or re-flag an entry
@@ -88,13 +88,17 @@ function emitStuck(runId) {
88
88
  // `clearTimeout`s that exact timer first — in this single-threaded
89
89
  // runtime, a cleared timer's callback can never still fire. So by the
90
90
  // time this callback runs, its entry is guaranteed to both exist and
91
- // have `emitted === false`. Left in place (not stripped) as a guard
91
+ // have `emitted === false`. Unreachable code is refactored away rather than fake-tested — see
92
+ // packages/ui/archived provenance ledger's 2026-07-22 dated entry for the full proof.
93
+ //
94
+ // Original rationale for the removed guard, kept verbatim (moved, not deleted) so the trade-off
95
+ // stays visible to whoever revisits it: "Left in place (not stripped) as a guard
92
96
  // against a future refactor silently breaking that invariant — see
93
97
  // packages/ui/archived provenance ledger's 2026-07-22 dated entry for the full proof,
94
98
  // matching this task's own standard for documenting provable
95
- // unreachability instead of forcing a test.
96
- if (!entry || entry.emitted)
97
- return;
99
+ // unreachability instead of forcing a test." A refactor that lets a cleared or re-flagged
100
+ // entry reach this callback must therefore restore that guard here.
101
+ const entry = runs.get(runId);
98
102
  entry.emitted = true;
99
103
  entry.reporter('client_run_stuck', {
100
104
  run_id: runId,
@@ -0,0 +1,6 @@
1
+ /** Returns an HTTP(S) URL's origin/path, or a relative pathname, with no credentials/query/fragment.
2
+ * Invalid and opaque URLs are omitted. O(n) time/space in input length. */
3
+ export declare function telemetryUrl({ value }: {
4
+ value: unknown;
5
+ }, _options?: {}): string | undefined;
6
+ //# sourceMappingURL=telemetry-url.d.ts.map
@@ -0,0 +1,16 @@
1
+ /** Returns an HTTP(S) URL's origin/path, or a relative pathname, with no credentials/query/fragment.
2
+ * Invalid and opaque URLs are omitted. O(n) time/space in input length. */
3
+ export function telemetryUrl({ value }, _options = {}) {
4
+ if (typeof value !== 'string' || value.length === 0 || /\s/.test(value))
5
+ return undefined;
6
+ try {
7
+ const parsed = new URL(value, 'https://telemetry.invalid/');
8
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')
9
+ return undefined;
10
+ return /^[a-z][a-z0-9+.-]*:|^\/\//i.test(value) ? `${parsed.origin}${parsed.pathname}` : parsed.pathname;
11
+ }
12
+ catch {
13
+ return undefined;
14
+ }
15
+ }
16
+ //# sourceMappingURL=telemetry-url.js.map
@@ -55,26 +55,23 @@ export function installWhiteScreenDetector(options = {}) {
55
55
  minVisibleText,
56
56
  rootElementId,
57
57
  });
58
- let cancelled = false;
59
- // The `cancelled` guards at the top of this timer callback and inside the
60
- // `monitorMount` completion callback just below are defensive invariant
61
- // checks, empirically and structurally unreachable through this
62
- // function's real call graph: both places that ever set
58
+ // There is no `cancelled` flag guarding the timer callback or the
59
+ // `monitorMount` completion callback just below. Such guards were defensive
60
+ // invariant checks, empirically and structurally unreachable through this
61
+ // function's real call graph: both places that ever set the old
63
62
  // `cancelled = true` (this closure's returned teardown, and the
64
63
  // `monitorMount` completion callback itself) always call
65
64
  // `window.clearTimeout(timer)` / `observer.disconnect()` synchronously in
66
65
  // that same turn — and in this single-threaded runtime, a timer that's
67
66
  // been cleared (or a `MutationObserver` that's been disconnected, which
68
67
  // also discards its pending record queue per spec) can never still fire
69
- // its callback afterward. So by the time either callback runs at all,
70
- // `cancelled` is guaranteed to still be its initial `false`. Left in
71
- // place (not stripped) as a guard against a future refactor silently
72
- // breaking that invariant — see packages/ui/archived provenance ledger's 2026-07-22
73
- // dated entry for the full proof, same pattern as this package's
74
- // `stuck-run.ts` `emitStuck` precedent.
68
+ // its callback afterward. So neither callback can run after teardown or
69
+ // after the other has finished. The guards (and the then write-only flag)
70
+ // were removed in the 2026-10-21 coverage pass — unreachable code is
71
+ // refactored away rather than fake-tested; see packages/ui/archived
72
+ // provenance ledger's 2026-07-22 dated entry for the full proof, same
73
+ // pattern as this package's `stuck-run.ts` `emitStuck`.
75
74
  const timer = window.setTimeout(() => {
76
- if (cancelled)
77
- return;
78
75
  if (isAppMounted())
79
76
  return;
80
77
  reporter('client_white_screen', {
@@ -89,13 +86,9 @@ export function installWhiteScreenDetector(options = {}) {
89
86
  });
90
87
  }, timeoutMs);
91
88
  const stopMonitor = monitorMount(isAppMounted, () => {
92
- if (cancelled)
93
- return;
94
- cancelled = true;
95
89
  window.clearTimeout(timer);
96
90
  });
97
91
  return () => {
98
- cancelled = true;
99
92
  window.clearTimeout(timer);
100
93
  stopMonitor();
101
94
  };
@@ -141,8 +134,8 @@ function isLoadingShell(el, loadingShellClasses) {
141
134
  return false;
142
135
  }
143
136
  function monitorMount(isAppMounted, onMounted) {
144
- let stopped = false;
145
- // The `stopped` guard at the top of this callback is a defensive
137
+ // There is no `stopped` flag or guard at the top of this callback (both
138
+ // removed in the 2026-10-21 coverage pass) — the guard was a defensive
146
139
  // invariant check, empirically confirmed unreachable: a `MutationObserver`
147
140
  // callback fires at most once per batch of queued mutation records
148
141
  // (never re-entrantly mid-callback), `stopped` only ever flips to `true`
@@ -157,10 +150,7 @@ function monitorMount(isAppMounted, onMounted) {
157
150
  // zero more. See packages/ui/archived provenance ledger's 2026-07-22 dated entry for
158
151
  // the full record.
159
152
  const observer = new MutationObserver(() => {
160
- if (stopped)
161
- return;
162
153
  if (isAppMounted()) {
163
- stopped = true;
164
154
  observer.disconnect();
165
155
  onMounted();
166
156
  }
@@ -178,12 +168,10 @@ function monitorMount(isAppMounted, onMounted) {
178
168
  // Best-effort short-circuit: if the app is already mounted by the time
179
169
  // this runs (HMR, slow tab, etc.), fire immediately.
180
170
  if (isAppMounted()) {
181
- stopped = true;
182
171
  observer.disconnect();
183
172
  onMounted();
184
173
  }
185
174
  return () => {
186
- stopped = true;
187
175
  observer.disconnect();
188
176
  };
189
177
  }
@@ -1,6 +1,6 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import { type FetchQueryEnvironmentPort } from '@jini-ai/core/fetch';
3
- import type { CachedLoader, CachedLoaderOptions, FetchMutationOptions, FetchQueryOptions, MutationResult, QueryKey, QueryResult } from '@jini-ai/core/fetch';
3
+ import type { CachedLoader, CachedLoaderDefaults, CachedLoaderOptions, FetchMutationOptions, FetchQueryOptions, MutationResult, QueryKey, QueryResult } from '@jini-ai/core/fetch';
4
4
  /** Isolated cache scope; no external provider or query-library singleton is needed. */
5
5
  export declare function FetchQueryProvider({ children, environment }: {
6
6
  children: ReactNode;
@@ -27,7 +27,7 @@ export declare function useFetchQuery<T>(required: Pick<FetchQueryOptions<T>, "k
27
27
  /** A write returns its rejection to awaiting callers and also handles discarded promises. */
28
28
  export declare function useFetchMutation<TInput, TOutput>(required: Pick<FetchMutationOptions<TInput, TOutput>, "run">, optional?: Omit<FetchMutationOptions<TInput, TOutput>, "run">): MutationResult<TInput, TOutput>;
29
29
  /** Promise loader sharing the provider cache; replace wins over an older in-flight read. */
30
- export declare function useCachedLoader<T>({ key, fetch }: Pick<CachedLoaderOptions<T>, "key" | "fetch">, { staleTime }?: Omit<CachedLoaderOptions<T>, "key" | "fetch">): CachedLoader<T>;
30
+ export declare function useCachedLoader<T>({ key, fetch }: CachedLoaderDefaults<T>, { staleTime }?: Omit<CachedLoaderOptions<T>, "key" | "fetch">): CachedLoader<T>;
31
31
  /** Prefix invalidation refreshes every currently enabled read under that key. */
32
32
  export declare function useInvalidate(): (required: {
33
33
  key: QueryKey;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
3
- import { DEFAULT_STALE_TIME, FetchQueryCache, toQueryError } from '@jini-ai/core/fetch';
3
+ import { DEFAULT_STALE_TIME, FetchQueryCache, toQueryError, withCachedLoaderFetch } from '@jini-ai/core/fetch';
4
4
  import { browserFetchQueryEnvironment } from './environment.browser.js';
5
5
  import { FetchQueryBinding } from './binding.react.js';
6
6
  const CacheContext = createContext(null);
@@ -109,7 +109,9 @@ export function useCachedLoader({ key, fetch }, { staleTime = DEFAULT_STALE_TIME
109
109
  const cache = useCache();
110
110
  return useMemo(() => ({
111
111
  peek: () => cache.entry({ key: key }, { staleTime: staleTime }).state.data,
112
- load: (required = { key, fetch }) => cache.load({ entry: cache.entry({ key: required.key }, { staleTime }), fetch: required.fetch, staleTime }),
112
+ // `fetch` is the loader's optional default; `withCachedLoaderFetch` rejects an argument-less read
113
+ // that has none before the cache is touched (see `CachedLoaderDefaults`).
114
+ load: (required = { key, fetch }, { staleTime: readStaleTime = staleTime } = {}) => withCachedLoaderFetch(required, read => cache.load({ entry: cache.entry({ key: read.key }, { staleTime }), fetch: read.fetch, staleTime: readStaleTime })),
113
115
  replace: ({ value }) => cache.replace({ entry: cache.entry({ key: key }, { staleTime: staleTime }), data: value }),
114
116
  }), [cache, key, fetch, staleTime]);
115
117
  }
@@ -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);