@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
@@ -81,30 +81,51 @@ export function createExtractionHistoryStore(onRowsChanged) {
81
81
  function tombstone(id) {
82
82
  tombstones.add(id);
83
83
  }
84
+ function applyClearedFrame() {
85
+ // A remote clear removed every attempt the daemon knew about, which
86
+ // covers everything this client has observed: visible rows, ids hidden
87
+ // by a pending local delete, and buffered frames. Tombstone them all
88
+ // (rule C) so delayed frames cannot recreate discarded attempts.
89
+ const owned = new Set([...rows.map((row) => row.id), ...pendingDeletes.keys(), ...deferred.keys()]);
90
+ for (const id of owned)
91
+ tombstone(id);
92
+ deferred.clear();
93
+ clearStamp = commit([]);
94
+ }
95
+ function applyDeletedFrame({ id }) {
96
+ deferred.delete(id);
97
+ tombstone(id);
98
+ commit(rows.filter((row) => row.id !== id));
99
+ }
100
+ function deferFrame(ev) {
101
+ const prior = deferred.get(ev.id);
102
+ deferred.set(ev.id, prior ? mergeSameAttempt(prior, ev) : ev);
103
+ }
104
+ function upsertFrame(ev) {
105
+ // Merge by id (rule B): phase transitions for an in-flight attempt
106
+ // collapse onto a single row instead of stacking N entries. New ids are
107
+ // unshifted so the latest appears at the top.
108
+ const at = rows.findIndex((row) => row.id === ev.id);
109
+ let next;
110
+ if (at >= 0) {
111
+ next = rows.slice();
112
+ next[at] = mergeSameAttempt(next[at], ev);
113
+ }
114
+ else {
115
+ next = [ev, ...rows].slice(0, MAX_ROWS);
116
+ }
117
+ rowStamp.set(ev.id, commit(next));
118
+ }
84
119
  function applyFrame(ev) {
85
120
  if (!ev || !ev.id)
86
121
  return;
87
122
  // Pseudo-phases: the daemon emits these synthetically when a row is
88
123
  // dropped from the buffer, by the per-row delete button or the "Clear"
89
124
  // affordance at the top (possibly in another client).
90
- if (ev.phase === 'cleared') {
91
- // A remote clear removed every attempt the daemon knew about, which
92
- // covers everything this client has observed: visible rows, ids hidden
93
- // by a pending local delete, and buffered frames. Tombstone them all
94
- // (rule C) so delayed frames cannot recreate discarded attempts.
95
- const owned = new Set([...rows.map((row) => row.id), ...pendingDeletes.keys(), ...deferred.keys()]);
96
- for (const id of owned)
97
- tombstone(id);
98
- deferred.clear();
99
- clearStamp = commit([]);
100
- return;
101
- }
102
- if (ev.phase === 'deleted') {
103
- deferred.delete(ev.id);
104
- tombstone(ev.id);
105
- commit(rows.filter((row) => row.id !== ev.id));
106
- return;
107
- }
125
+ if (ev.phase === 'cleared')
126
+ return applyClearedFrame();
127
+ if (ev.phase === 'deleted')
128
+ return applyDeletedFrame({ id: ev.id });
108
129
  // Rule C: a confirmed-removed id never comes back. A delayed frame on
109
130
  // the SSE connection may have been emitted before the DELETE response
110
131
  // won the fetch race.
@@ -115,24 +136,9 @@ export function createExtractionHistoryStore(onRowsChanged) {
115
136
  // operation for their id. Buffer them (content-merged, rule B) so they
116
137
  // cannot visibly undo the optimistic removal; the settle path replays
117
138
  // the best buffered frame on failure and tombstones it on success.
118
- if (pendingDeletes.has(ev.id) || pendingClears > 0) {
119
- const prior = deferred.get(ev.id);
120
- deferred.set(ev.id, prior ? mergeSameAttempt(prior, ev) : ev);
121
- return;
122
- }
123
- // Merge by id (rule B): phase transitions for an in-flight attempt
124
- // collapse onto a single row instead of stacking N entries. New ids are
125
- // unshifted so the latest appears at the top.
126
- const at = rows.findIndex((row) => row.id === ev.id);
127
- let next;
128
- if (at >= 0) {
129
- next = rows.slice();
130
- next[at] = mergeSameAttempt(next[at], ev);
131
- }
132
- else {
133
- next = [ev, ...rows].slice(0, MAX_ROWS);
134
- }
135
- rowStamp.set(ev.id, commit(next));
139
+ if (pendingDeletes.has(ev.id) || pendingClears > 0)
140
+ return deferFrame(ev);
141
+ upsertFrame(ev);
136
142
  }
137
143
  function commitSnapshot(confirmed, sinceClock) {
138
144
  // An authoritative clear younger than this read supersedes the whole
@@ -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
  }