@jini-ai/ui 0.5.5 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/core/theme/validation.js +40 -38
  3. package/dist/features/a2ui/renderer.js +15 -13
  4. package/dist/features/about/react/components/AboutTab.js +64 -34
  5. package/dist/features/about/rules.d.ts +2 -2
  6. package/dist/features/about/rules.js +156 -129
  7. package/dist/features/admin-widgets/components/ImagePreviewModal.d.ts +4 -3
  8. package/dist/features/admin-widgets/components/ImagePreviewModal.js +6 -10
  9. package/dist/features/admin-widgets/components/InfoTip.d.ts +3 -2
  10. package/dist/features/admin-widgets/components/InfoTip.hooks.js +2 -0
  11. package/dist/features/admin-widgets/components/InfoTip.js +34 -7
  12. package/dist/features/admin-widgets/components/InfoTip.rules.d.ts +13 -0
  13. package/dist/features/admin-widgets/components/InfoTip.rules.js +43 -10
  14. package/dist/features/admin-widgets/components/Lightbox/Lightbox.d.ts +30 -0
  15. package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.d.ts +40 -0
  16. package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.js +128 -0
  17. package/dist/features/admin-widgets/components/Lightbox/Lightbox.js +18 -0
  18. package/dist/features/admin-widgets/components/MiddleTruncate.d.ts +14 -0
  19. package/dist/features/admin-widgets/components/MiddleTruncate.js +12 -0
  20. package/dist/features/admin-widgets/components/MiddleTruncate.rules.d.ts +20 -0
  21. package/dist/features/admin-widgets/components/MiddleTruncate.rules.js +21 -0
  22. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.d.ts +18 -0
  23. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.d.ts +16 -0
  24. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.js +45 -0
  25. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.js +18 -0
  26. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.rules.d.ts +32 -0
  27. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.rules.js +26 -0
  28. package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.hooks.js +29 -14
  29. package/dist/features/admin-widgets/index.d.ts +14 -2
  30. package/dist/features/admin-widgets/index.js +7 -1
  31. package/dist/features/admin-widgets/styles/admin-widgets.css +1 -0
  32. package/dist/features/asset-grid/react/components/AssetCard.js +7 -1
  33. package/dist/features/asset-grid/react/components/AssetGrid.d.ts +1 -1
  34. package/dist/features/asset-grid/react/components/AssetGrid.js +38 -18
  35. package/dist/features/asset-grid/react/components/useAssetGridFilters.d.ts +21 -0
  36. package/dist/features/asset-grid/react/components/useAssetGridFilters.js +19 -0
  37. package/dist/features/asset-grid/react/hooks/useAssetGridKeyboardShortcuts.js +16 -11
  38. package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.d.ts +2 -0
  39. package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.js +35 -11
  40. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.d.ts +6 -6
  41. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.js +10 -129
  42. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.d.ts +50 -0
  43. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.js +95 -0
  44. package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.d.ts +56 -0
  45. package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.js +80 -0
  46. package/dist/features/asset-tree-browser/rules.js +40 -22
  47. package/dist/features/browser-chrome/rules.js +74 -70
  48. package/dist/features/charts/browser.js +11 -5
  49. package/dist/features/charts/react.js +2 -2
  50. package/dist/features/charts/table.js +2 -2
  51. package/dist/features/command-palette/react/hooks/useCommandPalette.js +3 -9
  52. package/dist/features/connectors/ConnectorsBrowser.d.ts +4 -4
  53. package/dist/features/connectors/ConnectorsBrowser.js +26 -14
  54. package/dist/features/connectors/components/ConnectorCard.js +67 -38
  55. package/dist/features/connectors/components/ConnectorDetailDrawer.js +62 -31
  56. package/dist/features/connectors/components/ConnectorGrid.js +11 -6
  57. package/dist/features/connectors/hooks/useConnectorAuthorization.js +6 -0
  58. package/dist/features/connectors/hooks/useConnectorCatalog.d.ts +2 -0
  59. package/dist/features/connectors/hooks/useConnectorCatalog.js +32 -13
  60. package/dist/features/connectors/hooks/useWiredConnectorsBrowser.d.ts +3 -3
  61. package/dist/features/connectors/rules.d.ts +8 -1
  62. package/dist/features/connectors/rules.js +42 -20
  63. package/dist/features/connectors/types.d.ts +11 -0
  64. package/dist/features/execution/constants.d.ts +4 -10
  65. package/dist/features/execution/constants.js +9 -94
  66. package/dist/features/execution/react/components/ByokProviderForm.d.ts +2 -4
  67. package/dist/features/execution/react/components/ByokProviderForm.js +140 -43
  68. package/dist/features/execution/react/components/ExecutionTab.d.ts +1 -2
  69. package/dist/features/execution/react/components/ExecutionTab.js +52 -30
  70. package/dist/features/execution/react/components/LocalCliAgentCard.js +12 -3
  71. package/dist/features/execution/react/components/LocalCliAgentList.js +14 -3
  72. package/dist/features/execution/react/components/LocalCliAgentSettings.js +14 -3
  73. package/dist/features/execution/react/hooks/useLocalCliAgentCard.js +54 -36
  74. package/dist/features/execution/react/hooks/useReasoningControl.js +114 -78
  75. package/dist/features/execution/rules.d.ts +2 -2
  76. package/dist/features/execution/rules.js +31 -15
  77. package/dist/features/execution/types.d.ts +4 -42
  78. package/dist/features/execution/types.js +0 -17
  79. package/dist/features/external-mcp/react/components/ExternalMcpTab.d.ts +6 -2
  80. package/dist/features/external-mcp/react/components/ExternalMcpTab.js +9 -2
  81. package/dist/features/file-dropzone/react/components/FileDropzone.js +24 -25
  82. package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.d.ts +2 -2
  83. package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.js +35 -30
  84. package/dist/features/file-dropzone/react/components/FileDropzoneThumbnailGrid.js +30 -24
  85. package/dist/features/file-dropzone/react/components/FileDropzoneZone.d.ts +14 -0
  86. package/dist/features/file-dropzone/react/components/FileDropzoneZone.js +26 -0
  87. package/dist/features/html-editor/canvas-content-wrapper.js +13 -13
  88. package/dist/features/html-editor/canvas-embed-placeholders.js +46 -37
  89. package/dist/features/html-editor/css.js +76 -69
  90. package/dist/features/html-editor/editor-blocks.js +47 -33
  91. package/dist/features/html-editor/editor-serialization.js +6 -5
  92. package/dist/features/html-editor/protected-elements.js +39 -28
  93. package/dist/features/html-editor/react/hooks/useInteractiveHtmlEditor.js +4 -4
  94. package/dist/features/html-editor/source-splice.js +51 -30
  95. package/dist/features/i18n/dictionaries/settings-dialog.ja.js +1 -0
  96. package/dist/features/i18n/dictionaries/settings-dialog.zh-CN.js +1 -0
  97. package/dist/features/i18n/dictionaries/settings-dialog.zh-TW.js +1 -0
  98. package/dist/features/i18n/locale.js +20 -16
  99. package/dist/features/iframe-pool/react/dom-sync.js +8 -6
  100. package/dist/features/integrations/react/components/CodexInstallToggleButton.js +19 -4
  101. package/dist/features/integrations/react/components/IntegrationsTab.js +60 -14
  102. package/dist/features/interactive-ui/interactive-ui.css +1 -1
  103. package/dist/features/interactive-ui/providers/shadcn/data-table.js +10 -7
  104. package/dist/features/lexical-rich-text-editor/mention-parser.js +60 -57
  105. package/dist/features/lexical-rich-text-editor/react/hooks/useKeyboardCommands.js +33 -38
  106. package/dist/features/lexical-rich-text-editor/rules.js +87 -74
  107. package/dist/features/lexical-rich-text-editor/serialize.js +27 -25
  108. package/dist/features/mcp-ui/secret-card.js +111 -61
  109. package/dist/features/mcp-ui/surfaces/confirmation.js +39 -21
  110. package/dist/features/mcp-ui/surfaces/document.d.ts +1 -1
  111. package/dist/features/mcp-ui/surfaces/document.js +10 -2
  112. package/dist/features/mcp-ui/surfaces/fields.js +30 -50
  113. package/dist/features/mcp-ui/surfaces/form.d.ts +6 -0
  114. package/dist/features/mcp-ui/surfaces/form.js +43 -21
  115. package/dist/features/mcp-ui/surfaces/outcome.js +30 -20
  116. package/dist/features/mcp-ui/surfaces/text-input.js +33 -21
  117. package/dist/features/mcp-ui/surfaces/tokens.d.ts +6 -1
  118. package/dist/features/mcp-ui/surfaces/tokens.js +5 -2
  119. package/dist/features/media-providers/constants.d.ts +1 -0
  120. package/dist/features/media-providers/constants.js +2 -81
  121. package/dist/features/media-providers/index.d.ts +1 -1
  122. package/dist/features/media-providers/ports.d.ts +2 -0
  123. package/dist/features/media-providers/react/components/MediaProviderCard.d.ts +22 -0
  124. package/dist/features/media-providers/react/components/MediaProviderCard.js +50 -0
  125. package/dist/features/media-providers/react/components/MediaProvidersTab.d.ts +5 -6
  126. package/dist/features/media-providers/react/components/MediaProvidersTab.js +40 -72
  127. package/dist/features/media-providers/react/components/media-providers-tab-labels.d.ts +11 -0
  128. package/dist/features/media-providers/react/components/media-providers-tab-labels.js +57 -0
  129. package/dist/features/media-providers/react/hooks/useMediaProvidersTab.d.ts +4 -2
  130. package/dist/features/media-providers/react/hooks/useMediaProvidersTab.js +37 -23
  131. package/dist/features/media-providers/rules.js +17 -10
  132. package/dist/features/media-providers/types.d.ts +4 -22
  133. package/dist/features/media-providers/types.js +3 -8
  134. package/dist/features/memory/formatters.js +127 -92
  135. package/dist/features/memory/react/components/MemoryConnectedPanel.js +92 -36
  136. package/dist/features/memory/react/components/MemoryList.js +37 -18
  137. package/dist/features/memory/react/hooks/useMemoryExtractions.store.js +42 -36
  138. package/dist/features/notifications/react/components/NotificationsTab.js +28 -28
  139. package/dist/features/observability/long-task.d.ts +0 -12
  140. package/dist/features/observability/long-task.js +15 -19
  141. package/dist/features/observability/resource-error.d.ts +0 -10
  142. package/dist/features/observability/resource-error.js +18 -13
  143. package/dist/features/observability/stuck-run.js +10 -6
  144. package/dist/features/observability/telemetry-url.d.ts +6 -0
  145. package/dist/features/observability/telemetry-url.js +16 -0
  146. package/dist/features/observability/white-screen.js +12 -24
  147. package/dist/features/panel-kit/fetch-query/adapter.react.d.ts +2 -2
  148. package/dist/features/panel-kit/fetch-query/adapter.react.js +4 -2
  149. package/dist/features/panel-kit/fetch-query/adapter.tanstack.js +46 -33
  150. package/dist/features/panel-kit/helpers/standing-draft-local-backup.js +3 -1
  151. package/dist/features/panel-kit/hooks/use-settings-slice.hooks.js +3 -1
  152. package/dist/features/privacy/react/components/PrivacyTab.js +24 -13
  153. package/dist/features/progress-card/components/ProgressCard.js +7 -3
  154. package/dist/features/progress-card/reference-adapters.js +59 -54
  155. package/dist/features/project-locations/react/components/ProjectLocationsTab.js +24 -15
  156. package/dist/features/project-locations/react/hooks/useProjectLocationsTab.d.ts +8 -0
  157. package/dist/features/project-locations/react/hooks/useProjectLocationsTab.js +31 -23
  158. package/dist/features/resource-dashboard/react/components/ResourceBoard.js +6 -47
  159. package/dist/features/resource-dashboard/react/components/ResourceBoardView.d.ts +2 -2
  160. package/dist/features/resource-dashboard/react/components/ResourceBoardView.js +3 -6
  161. package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.d.ts +15 -0
  162. package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.js +35 -0
  163. package/dist/features/resource-dashboard/react/components/ResourceCard.js +5 -16
  164. package/dist/features/resource-dashboard/react/components/ResourceCardParts.d.ts +34 -0
  165. package/dist/features/resource-dashboard/react/components/ResourceCardParts.js +34 -0
  166. package/dist/features/resource-dashboard/react/components/ResourceRowList.js +3 -1
  167. package/dist/features/resource-dashboard/react/components/ResourceRowListItem.js +2 -2
  168. package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.d.ts +19 -0
  169. package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.js +11 -0
  170. package/dist/features/resource-dashboard/react/components/ResourceRowListView.d.ts +1 -1
  171. package/dist/features/resource-dashboard/react/components/ResourceRowListView.js +4 -4
  172. package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.d.ts +10 -0
  173. package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.js +20 -0
  174. package/dist/features/resource-dashboard/react/components/resourceBoardProps.d.ts +48 -0
  175. package/dist/features/resource-dashboard/react/components/resourceBoardProps.js +70 -0
  176. package/dist/features/resource-dashboard/react/components/resourceRowListProps.d.ts +19 -0
  177. package/dist/features/resource-dashboard/react/components/resourceRowListProps.js +14 -0
  178. package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.d.ts +16 -0
  179. package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.js +13 -0
  180. package/dist/features/resource-dashboard/rules.js +2 -8
  181. package/dist/features/sketch-editor/dom.js +150 -99
  182. package/dist/features/sketch-editor/react/components/SketchEditor.js +26 -15
  183. package/dist/features/skills/react/components/SkillDraftForm.js +13 -19
  184. package/dist/features/skills/react/components/SkillDraftForm.labels.d.ts +31 -0
  185. package/dist/features/skills/react/components/SkillDraftForm.labels.js +54 -0
  186. package/dist/features/skills/react/components/SkillRow.js +38 -18
  187. package/dist/features/skills/react/components/SkillRow.labels.d.ts +7 -0
  188. package/dist/features/skills/react/components/SkillRow.labels.js +18 -0
  189. package/dist/features/skills/react/components/SkillsTab.js +24 -14
  190. package/dist/features/skills/react/components/SkillsTab.labels.d.ts +7 -0
  191. package/dist/features/skills/react/components/SkillsTab.labels.js +11 -0
  192. package/dist/features/skills/rules.js +32 -23
  193. package/dist/features/source-config-list/agent-handles.js +2 -1
  194. package/dist/features/source-config-list/dependencies.js +53 -49
  195. package/dist/features/source-config-list/index.d.ts +3 -3
  196. package/dist/features/source-config-list/react/components/SourceConfigAddForm.d.ts +6 -5
  197. package/dist/features/source-config-list/react/components/SourceConfigAddForm.js +38 -7
  198. package/dist/features/source-config-list/react/components/SourceConfigField.js +69 -25
  199. package/dist/features/source-config-list/react/components/SourceConfigItemCard.d.ts +6 -5
  200. package/dist/features/source-config-list/react/components/SourceConfigItemCard.js +76 -20
  201. package/dist/features/source-config-list/react/components/SourceConfigListView.d.ts +5 -3
  202. package/dist/features/source-config-list/react/components/SourceConfigListView.js +33 -3
  203. package/dist/features/source-config-list/react/components/SourceConfigTestControl.js +17 -1
  204. package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.d.ts +11 -0
  205. package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.js +12 -0
  206. package/dist/features/source-config-list/types.d.ts +14 -0
  207. package/dist/features/tab-launcher-menu/react/components/TabLauncherMenu.js +25 -1
  208. package/dist/features/tab-launcher-menu/react/hooks/useTabLauncherMenu.js +15 -16
  209. package/dist/features/tab-strip/react/components/TabBar.js +8 -1
  210. package/dist/features/tabbed-dialog/react/components/TabbedDialog.js +8 -30
  211. package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.d.ts +15 -0
  212. package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.js +8 -0
  213. package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.d.ts +12 -0
  214. package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.js +6 -0
  215. package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.d.ts +27 -0
  216. package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.js +12 -0
  217. package/dist/features/tabbed-dialog/react/components/tabbedDialogView.d.ts +43 -0
  218. package/dist/features/tabbed-dialog/react/components/tabbedDialogView.js +59 -0
  219. package/dist/features/version-manager/dependencies.d.ts +3 -1
  220. package/dist/features/version-manager/dependencies.js +3 -2
  221. package/dist/features/version-manager/react/components/VersionManagerModal.js +10 -6
  222. package/dist/features/version-manager/react/hooks/useVersionManager.js +7 -4
  223. package/dist/features/viewer-shell/react/components/CommentSidePanel.js +103 -50
  224. package/dist/features/viewer-shell/react/components/MarkdownSplitPane.js +28 -8
  225. package/dist/features/viewer-shell/react/hooks/useCommentReorder.js +22 -16
  226. package/dist/features/viewer-shell/rules.js +19 -23
  227. package/dist/react/components/CustomSelect.d.ts +38 -1
  228. package/dist/react/components/CustomSelect.js +94 -38
  229. package/dist/react/components/OnboardingDropdown.js +20 -8
  230. package/dist/react/components/RemixIcon.stylesheet.js +3 -1
  231. package/dist/react/components/Toast.js +13 -2
  232. package/dist/react/components/TooltipLayer.js +5 -14
  233. package/dist/react/components/WorkingDirPicker.js +78 -50
  234. package/dist/react/components/remixicon-font-recovery.js +38 -30
  235. package/dist/react/components/select-popover.d.ts +8 -6
  236. package/dist/react/components/select-popover.js +33 -29
  237. package/dist/react/mcp-ui/McpUiHost.d.ts +1 -1
  238. package/dist/react/mcp-ui/McpUiHost.js +10 -11
  239. package/dist/react/mcp-ui/host-message-source.d.ts +14 -2
  240. package/dist/react/mcp-ui/host-message-source.js +15 -5
  241. package/dist/react/mcp-ui/mcp-ui-host-frame.d.ts +41 -0
  242. package/dist/react/mcp-ui/mcp-ui-host-frame.js +39 -0
  243. package/dist/renderers/annotation-canvas/react/components/AnnotationCanvas.js +97 -69
  244. package/dist/renderers/preview-modal-shell/react/components/PreviewModalShell.js +59 -30
  245. package/dist/renderers/react/components/ArtifactView.js +9 -6
  246. package/dist/renderers/srcdoc/build.d.ts +4 -1
  247. package/dist/renderers/srcdoc/build.js +62 -48
  248. package/dist/styles/admin.css +29 -14
  249. package/dist/styles/lightbox.css +142 -0
  250. package/dist/styles/variables.css +128 -0
  251. package/dist/theme/apply.js +9 -5
  252. package/dist/utils/auto-open-file.d.ts +2 -1
  253. package/dist/utils/auto-open-file.js +69 -85
  254. package/dist/utils/markdown-scroll-sync.js +108 -87
  255. package/dist/utils/polygon-selection.js +25 -26
  256. package/dist/utils/smooth-scroll-to-top.d.ts +1 -1
  257. package/dist/utils/smooth-scroll-to-top.js +2 -32
  258. package/dist/utils/sse.js +26 -26
  259. package/dist/utils/unit-bezier.d.ts +7 -0
  260. package/dist/utils/unit-bezier.js +38 -0
  261. package/package.json +7 -10
  262. package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.d.ts +0 -13
  263. package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.js +0 -46
@@ -45,84 +45,101 @@ export function providerDisplayName(provider) {
45
45
  return 'Memory model';
46
46
  }
47
47
  }
48
- export function parseProviderError(raw) {
48
+ function isString(value) {
49
+ return typeof value === 'string';
50
+ }
51
+ function isNumber(value) {
52
+ return typeof value === 'number';
53
+ }
54
+ // Reads message/code/status out of the first `{...}` in the raw provider
55
+ // error. The nested `error.*` field wins for message/code; the top-level
56
+ // field wins for status.
57
+ function parseJsonErrorFields(raw) {
49
58
  const jsonStart = raw.indexOf('{');
50
- let message = raw.trim();
51
- let code = '';
52
- let status = null;
53
- if (jsonStart >= 0) {
54
- try {
55
- const parsed = JSON.parse(raw.slice(jsonStart));
56
- const error = parsed?.error;
57
- if (typeof error?.message === 'string')
58
- message = error.message;
59
- else if (typeof parsed?.message === 'string')
60
- message = parsed.message;
61
- if (typeof error?.code === 'string')
62
- code = error.code;
63
- else if (typeof parsed?.code === 'string')
64
- code = parsed.code;
65
- if (typeof parsed?.status === 'number')
66
- status = parsed.status;
67
- else if (typeof error?.status === 'number')
68
- status = error.status;
69
- }
70
- catch {
71
- // Fall through to regex parsing below.
72
- }
59
+ if (jsonStart < 0)
60
+ return {};
61
+ try {
62
+ // Text starting at `{` can only parse to an object, so `parsed` is never null.
63
+ const parsed = JSON.parse(raw.slice(jsonStart));
64
+ const error = parsed.error;
65
+ return {
66
+ message: [error?.message, parsed.message].find(isString),
67
+ code: [error?.code, parsed.code].find(isString),
68
+ status: [parsed.status, error?.status].find(isNumber),
69
+ };
70
+ }
71
+ catch {
72
+ // Fall through to regex parsing below.
73
+ return {};
73
74
  }
75
+ }
76
+ export function parseProviderError(raw) {
77
+ const fields = parseJsonErrorFields(raw);
74
78
  const statusMatch = /\b(4\d\d|5\d\d)\b/.exec(raw);
75
- if (status === null && statusMatch?.[1])
76
- status = Number(statusMatch[1]);
77
79
  return {
78
- message: message.replace(/\s+/g, ' ').trim(),
79
- code,
80
- status,
80
+ message: (fields.message ?? raw.trim()).replace(/\s+/g, ' ').trim(),
81
+ code: fields.code ?? '',
82
+ status: fields.status ?? (statusMatch ? Number(statusMatch[1]) : null),
81
83
  };
82
84
  }
85
+ function classifyFailure({ status, haystack }) {
86
+ if (status === 401 || /token[_ -]?expired|authentication token has expired|invalid[_ -]?api[_ -]?key|unauthorized/.test(haystack)) {
87
+ return 'auth';
88
+ }
89
+ if (status === 429 || /rate limit|quota|too many requests|insufficient_quota/.test(haystack))
90
+ return 'quota';
91
+ if (/network|fetch failed|timeout|timed out|econnreset|enotfound/.test(haystack))
92
+ return 'network';
93
+ return 'other';
94
+ }
95
+ function failureCopy({ category, providerName, usesChatCli, source, message, }) {
96
+ switch (category) {
97
+ case 'auth':
98
+ return {
99
+ title: `${providerName} authentication expired`,
100
+ detail: source,
101
+ action: usesChatCli
102
+ ? 'Sign in to the selected local CLI or choose a different memory model.'
103
+ : 'Update the memory extraction model key or sign in again.',
104
+ };
105
+ case 'quota':
106
+ return {
107
+ title: `${providerName} quota or rate limit hit`,
108
+ detail: source,
109
+ action: 'Try again later or switch the memory extraction model.',
110
+ };
111
+ case 'network':
112
+ return {
113
+ title: `${providerName} request failed`,
114
+ detail: source,
115
+ action: usesChatCli
116
+ ? 'Check the selected local CLI and try again.'
117
+ : 'Check the model provider connection and try again.',
118
+ };
119
+ default:
120
+ return {
121
+ title: 'Memory extraction failed',
122
+ detail: message || source,
123
+ action: usesChatCli
124
+ ? 'Try again after checking the selected local CLI.'
125
+ : 'Try again after checking the memory extraction model settings.',
126
+ };
127
+ }
128
+ }
83
129
  export function describeExtractionFailure(record) {
84
130
  if (record.phase !== 'failed' || !record.error)
85
131
  return null;
86
- const providerName = providerDisplayName(record.provider);
87
- const usesChatCli = record.provider?.credentialSource === 'chat-cli';
88
132
  const parsed = parseProviderError(record.error);
89
133
  const haystack = `${parsed.message} ${parsed.code} ${record.error}`.toLowerCase();
90
- const source = record.kind === 'connector'
91
- ? 'Connected apps were read, but the assistant could not turn that context into memory.'
92
- : 'Memory extraction could not run for this chat.';
93
- if (parsed.status === 401
94
- || /token[_ -]?expired|authentication token has expired|invalid[_ -]?api[_ -]?key|unauthorized/.test(haystack)) {
95
- return {
96
- title: `${providerName} authentication expired`,
97
- detail: source,
98
- action: usesChatCli
99
- ? 'Sign in to the selected local CLI or choose a different memory model.'
100
- : 'Update the memory extraction model key or sign in again.',
101
- };
102
- }
103
- if (parsed.status === 429 || /rate limit|quota|too many requests|insufficient_quota/.test(haystack)) {
104
- return {
105
- title: `${providerName} quota or rate limit hit`,
106
- detail: source,
107
- action: 'Try again later or switch the memory extraction model.',
108
- };
109
- }
110
- if (/network|fetch failed|timeout|timed out|econnreset|enotfound/.test(haystack)) {
111
- return {
112
- title: `${providerName} request failed`,
113
- detail: source,
114
- action: usesChatCli
115
- ? 'Check the selected local CLI and try again.'
116
- : 'Check the model provider connection and try again.',
117
- };
118
- }
119
- return {
120
- title: 'Memory extraction failed',
121
- detail: parsed.message || source,
122
- action: usesChatCli
123
- ? 'Try again after checking the selected local CLI.'
124
- : 'Try again after checking the memory extraction model settings.',
125
- };
134
+ return failureCopy({
135
+ category: classifyFailure({ status: parsed.status, haystack }),
136
+ providerName: providerDisplayName(record.provider),
137
+ usesChatCli: record.provider?.credentialSource === 'chat-cli',
138
+ source: record.kind === 'connector'
139
+ ? 'Connected apps were read, but the assistant could not turn that context into memory.'
140
+ : 'Memory extraction could not run for this chat.',
141
+ message: parsed.message,
142
+ });
126
143
  }
127
144
  export function formatConnectorContextBytes(bytes) {
128
145
  if (!Number.isFinite(bytes) || bytes <= 0)
@@ -248,43 +265,55 @@ export function formatRelativeTimeAgo(at, now) {
248
265
  export function memoryCountLabel(count) {
249
266
  return count === 1 ? 'memory' : 'memories';
250
267
  }
268
+ // The record's written count when it is a positive number, else 0.
269
+ function positiveWrittenCount(record) {
270
+ const count = record.writtenCount;
271
+ return typeof count === 'number' && count > 0 ? count : 0;
272
+ }
273
+ function connectorCardTitle({ record, t }) {
274
+ switch (record.phase) {
275
+ case 'running':
276
+ return t('Scanning connected apps');
277
+ case 'failed':
278
+ return t('Connected app scan failed');
279
+ case 'skipped':
280
+ return t('Connected app scan skipped');
281
+ case 'success': {
282
+ const writtenCount = positiveWrittenCount(record);
283
+ if (writtenCount > 0) {
284
+ return t('Saved {count} {label}', { count: writtenCount, label: memoryCountLabel(writtenCount) });
285
+ }
286
+ return t('No new memories found');
287
+ }
288
+ default:
289
+ return t('Connected app scan');
290
+ }
291
+ }
251
292
  export function extractionCardTitle(record, t) {
252
293
  const kind = record.kind ?? 'llm';
253
294
  if (kind !== 'connector') {
254
295
  return record.userMessagePreview || t('Extraction');
255
296
  }
256
- if (record.phase === 'running')
257
- return t('Scanning connected apps');
258
- if (record.phase === 'failed')
259
- return t('Connected app scan failed');
260
- if (record.phase === 'skipped')
261
- return t('Connected app scan skipped');
262
- if (record.phase === 'success') {
263
- const writtenCount = typeof record.writtenCount === 'number' ? record.writtenCount : null;
264
- if (writtenCount && writtenCount > 0) {
265
- return t('Saved {count} {label}', { count: writtenCount, label: memoryCountLabel(writtenCount) });
266
- }
267
- return t('No new memories found');
268
- }
269
- return t('Connected app scan');
297
+ return connectorCardTitle({ record, t });
270
298
  }
271
- export function extractionCardMeta(record, now, t) {
272
- const kind = record.kind ?? 'llm';
299
+ function connectorCardMeta({ record, now, t }) {
300
+ if (record.phase === 'running')
301
+ return t('Checking selected apps');
273
302
  const age = formatRelativeTimeAgo(record.startedAt, now);
274
- if (kind === 'connector') {
275
- if (record.phase === 'running')
276
- return t('Checking selected apps');
277
- if (record.phase === 'failed')
303
+ switch (record.phase) {
304
+ case 'failed':
278
305
  return `${t('Needs attention')} · ${age}`;
279
- if (record.phase === 'skipped')
306
+ case 'skipped':
280
307
  return `${t('Skipped')} · ${age}`;
281
- if (record.phase === 'success') {
282
- const writtenCount = typeof record.writtenCount === 'number' ? record.writtenCount : null;
283
- const result = writtenCount && writtenCount > 0 ? t('From connected apps') : t('Checked selected apps');
308
+ case 'success': {
309
+ const result = positiveWrittenCount(record) > 0 ? t('From connected apps') : t('Checked selected apps');
284
310
  return `${result} · ${age}`;
285
311
  }
286
- return `${t('Connected apps')} · ${age}`;
312
+ default:
313
+ return `${t('Connected apps')} · ${age}`;
287
314
  }
315
+ }
316
+ function modelCardMeta({ record, now, t }) {
288
317
  const duration = formatDuration(record);
289
318
  const parts = [formatAbsoluteTime(record.startedAt, now), formatRelativeTime(record.startedAt, now)];
290
319
  if (duration)
@@ -294,6 +323,12 @@ export function extractionCardMeta(record, now, t) {
294
323
  }
295
324
  return parts.join(' · ');
296
325
  }
326
+ export function extractionCardMeta(record, now, t) {
327
+ const kind = record.kind ?? 'llm';
328
+ if (kind === 'connector')
329
+ return connectorCardMeta({ record, now, t });
330
+ return modelCardMeta({ record, now, t });
331
+ }
297
332
  /** Localized labels for each memory type badge. `t`-only, so callers memoize on
298
333
  * `t` at the call site instead of drilling the map through props. */
299
334
  export function memoryTypeLabels(t) {
@@ -14,43 +14,99 @@ import { ConnectorLogo } from '../../../connectors/index.js';
14
14
  import { Icon } from '../../../../react/components/Icon.js';
15
15
  import { useT } from '../../../i18n/index.js';
16
16
  import { MemoryExtractionCard } from './MemoryExtractionCard.js';
17
- export function MemoryConnectedPanel({ enabled, onOpenConnectors, connectorStatuses, connectorsLoading, connectedCount, selectedConnectorIds, selectedConnectedConnectorIds, connectingConnectorIds, pendingConnectorAuthIds, connectorConnectErrors, connectorIdsWithDetails, connectorExtracting, connectorSaving, connectorScanLabel, connectorSuggestions, selectedSuggestionIds, selectedConnectorSuggestions, connectorStatus, connectorError, connectorLoadError, connectorAttempts, connectorContextBytes, connectorExtractions, memoryConnectors, toggleConnectorSelection, onConnectMemoryConnector, toggleConnectorSuggestion, onSuggestConnectorMemory, onSaveConnectorSuggestions, onDiscardConnectorSuggestions, nowClock, onOpenPreview, onDeleteExtraction, }) {
17
+ function isStatusResolved({ connector, context }) {
18
+ return context.connectorIdsWithDetails.has(connector.id) || context.connectorStatuses[connector.id] !== undefined;
19
+ }
20
+ function connectorRowState({ connector, context }) {
21
+ const connected = connector.status === 'connected';
22
+ const connecting = context.connectingConnectorIds.has(connector.id);
23
+ const authorizationPending = context.pendingConnectorAuthIds.has(connector.id);
24
+ const connectError = context.connectorConnectErrors[connector.id];
25
+ const checkingStatus = context.connectorsLoading && !isStatusResolved({ connector, context }) && !connected && !authorizationPending && !connectError && !connecting;
26
+ return {
27
+ connected,
28
+ selected: context.selectedConnectorIds.has(connector.id) && connected,
29
+ connecting,
30
+ authorizationPending,
31
+ connectError,
32
+ checkingStatus,
33
+ reconnecting: connector.status === 'error',
34
+ busy: connecting || authorizationPending || checkingStatus,
35
+ };
36
+ }
37
+ function connectorHint({ connector, state, t }) {
38
+ if (state.connected)
39
+ return connector.accountLabel || `${connector.tools.length} ${t('read tools')}`;
40
+ if (state.checkingStatus)
41
+ return t('Checking connection status…');
42
+ if (state.authorizationPending)
43
+ return t('Finish authorization in your browser, then return here');
44
+ return connector.lastError?.trim() || state.connectError || t('Connect this app before extraction');
45
+ }
46
+ function connectButtonLabel({ state, t }) {
47
+ if (state.checkingStatus)
48
+ return t('Checking');
49
+ if (state.authorizationPending)
50
+ return t('Waiting');
51
+ if (state.connecting)
52
+ return t('Connecting');
53
+ return state.reconnecting ? t('Reconnect') : t('Connect');
54
+ }
55
+ function MemoryConnectButton({ connector, state, onConnectMemoryConnector, }) {
56
+ const t = useT();
57
+ return (_jsxs("button", { type: "button", className: `memory-connector-connect-button${state.busy ? ' is-loading' : ''}`, disabled: state.busy, "aria-busy": state.busy || undefined, "aria-label": `${state.reconnecting ? t('Reconnect') : t('Connect')} ${connector.name}`, onClick: (event) => {
58
+ event.preventDefault();
59
+ event.stopPropagation();
60
+ void onConnectMemoryConnector(connector.id);
61
+ }, children: [_jsx(Icon, { name: state.busy ? 'refresh' : 'plus', size: 12, className: state.busy ? 'icon-spin' : '' }), _jsx("span", { children: connectButtonLabel({ state, t }) })] }));
62
+ }
63
+ function MemoryConnectorPicker({ selected }) {
64
+ const t = useT();
65
+ return (_jsxs("span", { className: `memory-connector-picker${selected ? ' is-selected' : ''}`, children: [_jsx("span", { className: "memory-connector-picker-box", "aria-hidden": "true", children: selected ? _jsx(Icon, { name: "check", size: 12 }) : null }), _jsx("span", { children: selected ? t('Selected') : t('Select') })] }));
66
+ }
67
+ function MemoryConnectorRow({ connector, context, toggleConnectorSelection, onConnectMemoryConnector, }) {
68
+ const t = useT();
69
+ const state = connectorRowState({ connector, context });
70
+ const { connected, selected } = state;
71
+ return (_jsxs("label", { className: `memory-connector-row${connected ? '' : ' is-disabled'}${selected ? ' is-selected' : ''}`, "data-memory-connector-id": connector.id, children: [_jsx("input", { className: "memory-connector-input", type: "checkbox", checked: selected, disabled: !connected, "aria-label": `${t('Use')} ${connector.name} ${t('for memory extraction')}`, onChange: () => toggleConnectorSelection(connector.id) }), _jsxs("span", { className: `memory-connector-brand${selected ? ' is-selected' : ''}`, children: [_jsx(ConnectorLogo, { connectorId: connector.id, connectorName: connector.name, logoUrl: connector.logoUrl, size: "sm" }), _jsx("span", { className: "memory-connector-selected-mark", "aria-hidden": "true", children: selected ? _jsx(Icon, { name: "check", size: 13 }) : null })] }), _jsxs("span", { className: "memory-connector-copy", children: [_jsx("strong", { children: connector.name }), _jsx("small", { children: connectorHint({ connector, state, t }) })] }), connected ? (_jsx(MemoryConnectorPicker, { selected: selected })) : (_jsx(MemoryConnectButton, { connector: connector, state: state, onConnectMemoryConnector: onConnectMemoryConnector }))] }));
72
+ }
73
+ function MemoryConnectedSummary({ connectorsLoading, connectedCount, onOpenConnectors, }) {
74
+ const t = useT();
75
+ return (_jsxs("div", { className: "memory-source-summary memory-connected-summary", children: [_jsx("span", { className: "memory-block-icon", children: _jsx(Icon, { name: "link", size: 15 }) }), _jsxs("div", { children: [_jsx("h4", { children: t('Import from apps') }), _jsx("p", { className: "hint", children: t('Choose apps to scan for design preferences, project context, and visual references. Nothing is scanned until you select an app.') })] }), _jsx("span", { className: "memory-source-badge", children: connectorsLoading ? t('Loading') : `${connectedCount} ${t('connected')}` }), _jsx("button", { type: "button", className: "ghost memory-source-action", onClick: onOpenConnectors, disabled: !onOpenConnectors, children: t('Manage') })] }));
76
+ }
77
+ function MemoryConnectorRunBar({ enabled, connectorExtracting, connectorSaving, connectedCount, selectedCount, connectorScanLabel, onSuggestConnectorMemory, }) {
78
+ const t = useT();
79
+ return (_jsxs("div", { className: "memory-connector-actions memory-connector-runbar", children: [_jsxs("span", { className: "hint", children: [t('Selected'), " ", selectedCount, " ", t('of'), " ", connectedCount, " ", t('connected'), ' ', connectedCount === 1 ? t('app') : t('apps'), "."] }), _jsxs("button", { type: "button", className: "primary memory-source-action", onClick: () => void onSuggestConnectorMemory(), disabled: !enabled || connectorExtracting || connectorSaving || selectedCount === 0, children: [_jsx(Icon, { name: connectorExtracting ? 'refresh' : 'sparkles', size: 14, className: connectorExtracting ? 'icon-spin' : '' }), _jsx("span", { children: t(connectorScanLabel) })] })] }));
80
+ }
81
+ function MemorySuggestionCard({ suggestion, selected, typeLabel, toggleConnectorSuggestion, }) {
82
+ const t = useT();
83
+ const sourceLabel = suggestion.source?.connectorName || suggestion.source?.toolTitle || t('Connected apps');
84
+ return (_jsxs("label", { className: `memory-suggestion-card${selected ? ' is-selected' : ''}`, children: [_jsxs("span", { className: "memory-connector-check", children: [_jsx("input", { type: "checkbox", checked: selected, onChange: () => toggleConnectorSuggestion(suggestion.id) }), _jsx("span", { "aria-hidden": "true", children: selected ? _jsx(Icon, { name: "check", size: 13 }) : null })] }), _jsxs("span", { className: "memory-suggestion-copy", children: [_jsxs("span", { className: "memory-suggestion-title", children: [_jsx("strong", { children: suggestion.name }), _jsx("span", { className: "memory-type-badge", children: typeLabel })] }), suggestion.description ? _jsx("small", { children: suggestion.description }) : null, _jsx("span", { className: "memory-suggestion-body", children: suggestion.body })] }), _jsx("span", { className: "memory-connector-state is-connected", children: sourceLabel })] }));
85
+ }
86
+ function MemorySuggestionPanel({ connectorSuggestions, selectedSuggestionIds, selectedConnectorSuggestions, connectorSaving, toggleConnectorSuggestion, onSaveConnectorSuggestions, onDiscardConnectorSuggestions, }) {
18
87
  const t = useT();
19
88
  const typeLabel = useMemo(() => memoryTypeLabels(t), [t]);
20
- return (_jsxs("div", { className: "memory-tab-panel memory-connected-panel", children: [_jsxs("div", { className: "memory-source-summary memory-connected-summary", children: [_jsx("span", { className: "memory-block-icon", children: _jsx(Icon, { name: "link", size: 15 }) }), _jsxs("div", { children: [_jsx("h4", { children: t('Import from apps') }), _jsx("p", { className: "hint", children: t('Choose apps to scan for design preferences, project context, and visual references. Nothing is scanned until you select an app.') })] }), _jsx("span", { className: "memory-source-badge", children: connectorsLoading ? t('Loading') : `${connectedCount} ${t('connected')}` }), _jsx("button", { type: "button", className: "ghost memory-source-action", onClick: onOpenConnectors, disabled: !onOpenConnectors, children: t('Manage') })] }), _jsxs("div", { className: "memory-connector-workbench", children: [connectorLoadError ? (_jsx("div", { role: "alert", className: "memory-connector-result is-error", children: connectorLoadError })) : null, _jsxs("div", { className: "memory-connector-picker-head", children: [_jsxs("div", { children: [_jsx("h4", { children: t('Choose sources') }), _jsx("p", { className: "hint", children: t('Select connected apps first. Only the apps you choose are scanned.') })] }), _jsxs("span", { className: "memory-source-badge", children: [selectedConnectedConnectorIds.length, " ", t('selected')] })] }), _jsx("div", { className: "memory-connector-list", "aria-label": t('Connected memory apps'), children: memoryConnectors.map((connector) => {
21
- const connected = connector.status === 'connected';
22
- const selected = selectedConnectorIds.has(connector.id) && connected;
23
- const connecting = connectingConnectorIds.has(connector.id);
24
- const authorizationPending = pendingConnectorAuthIds.has(connector.id);
25
- const connectError = connectorConnectErrors[connector.id];
26
- const statusResolved = connectorIdsWithDetails.has(connector.id) || connectorStatuses[connector.id] !== undefined;
27
- const checkingStatus = connectorsLoading && !statusResolved && !connected && !authorizationPending && !connectError && !connecting;
28
- const connectorLastError = connector.lastError?.trim();
29
- const reconnecting = connector.status === 'error';
30
- const connectorHint = connected
31
- ? connector.accountLabel || `${connector.tools.length} ${t('read tools')}`
32
- : checkingStatus
33
- ? t('Checking connection status…')
34
- : authorizationPending
35
- ? t('Finish authorization in your browser, then return here')
36
- : connectorLastError || connectError || t('Connect this app before extraction');
37
- return (_jsxs("label", { className: `memory-connector-row${connected ? '' : ' is-disabled'}${selected ? ' is-selected' : ''}`, "data-memory-connector-id": connector.id, children: [_jsx("input", { className: "memory-connector-input", type: "checkbox", checked: selected, disabled: !connected, "aria-label": `${t('Use')} ${connector.name} ${t('for memory extraction')}`, onChange: () => toggleConnectorSelection(connector.id) }), _jsxs("span", { className: `memory-connector-brand${selected ? ' is-selected' : ''}`, children: [_jsx(ConnectorLogo, { connectorId: connector.id, connectorName: connector.name, logoUrl: connector.logoUrl, size: "sm" }), _jsx("span", { className: "memory-connector-selected-mark", "aria-hidden": "true", children: selected ? _jsx(Icon, { name: "check", size: 13 }) : null })] }), _jsxs("span", { className: "memory-connector-copy", children: [_jsx("strong", { children: connector.name }), _jsx("small", { children: connectorHint })] }), connected ? (_jsxs("span", { className: `memory-connector-picker${selected ? ' is-selected' : ''}`, children: [_jsx("span", { className: "memory-connector-picker-box", "aria-hidden": "true", children: selected ? _jsx(Icon, { name: "check", size: 12 }) : null }), _jsx("span", { children: selected ? t('Selected') : t('Select') })] })) : (_jsxs("button", { type: "button", className: `memory-connector-connect-button${connecting || authorizationPending || checkingStatus ? ' is-loading' : ''}`, disabled: connecting || authorizationPending || checkingStatus, "aria-busy": connecting || authorizationPending || checkingStatus || undefined, "aria-label": `${reconnecting ? t('Reconnect') : t('Connect')} ${connector.name}`, onClick: (event) => {
38
- event.preventDefault();
39
- event.stopPropagation();
40
- void onConnectMemoryConnector(connector.id);
41
- }, children: [_jsx(Icon, { name: connecting || authorizationPending || checkingStatus ? 'refresh' : 'plus', size: 12, className: connecting || authorizationPending || checkingStatus ? 'icon-spin' : '' }), _jsx("span", { children: checkingStatus
42
- ? t('Checking')
43
- : authorizationPending
44
- ? t('Waiting')
45
- : connecting
46
- ? t('Connecting')
47
- : reconnecting
48
- ? t('Reconnect')
49
- : t('Connect') })] }))] }, connector.id));
50
- }) }), _jsxs("div", { className: "memory-connector-actions memory-connector-runbar", children: [_jsxs("span", { className: "hint", children: [t('Selected'), " ", selectedConnectedConnectorIds.length, " ", t('of'), " ", connectedCount, " ", t('connected'), ' ', connectedCount === 1 ? t('app') : t('apps'), "."] }), _jsxs("button", { type: "button", className: "primary memory-source-action", onClick: () => void onSuggestConnectorMemory(), disabled: !enabled || connectorExtracting || connectorSaving || selectedConnectedConnectorIds.length === 0, children: [_jsx(Icon, { name: connectorExtracting ? 'refresh' : 'sparkles', size: 14, className: connectorExtracting ? 'icon-spin' : '' }), _jsx("span", { children: t(connectorScanLabel) })] })] })] }), connectorSuggestions.length > 0 ? (_jsxs("div", { className: "memory-suggestion-panel", children: [_jsxs("div", { className: "memory-subsection-head", children: [_jsxs("div", { children: [_jsx("h4", { children: t('Suggested memories') }), _jsx("p", { className: "hint", children: t('Review design-related memories before saving them.') })] }), _jsxs("span", { className: "memory-source-badge", children: [selectedConnectorSuggestions.length, " ", t('selected')] })] }), _jsx("div", { className: "memory-suggestion-list", children: connectorSuggestions.map((suggestion) => {
51
- const selected = selectedSuggestionIds.has(suggestion.id);
52
- const sourceLabel = suggestion.source?.connectorName || suggestion.source?.toolTitle || t('Connected apps');
53
- return (_jsxs("label", { className: `memory-suggestion-card${selected ? ' is-selected' : ''}`, children: [_jsxs("span", { className: "memory-connector-check", children: [_jsx("input", { type: "checkbox", checked: selected, onChange: () => toggleConnectorSuggestion(suggestion.id) }), _jsx("span", { "aria-hidden": "true", children: selected ? _jsx(Icon, { name: "check", size: 13 }) : null })] }), _jsxs("span", { className: "memory-suggestion-copy", children: [_jsxs("span", { className: "memory-suggestion-title", children: [_jsx("strong", { children: suggestion.name }), _jsx("span", { className: "memory-type-badge", children: typeLabel[suggestion.type] })] }), suggestion.description ? _jsx("small", { children: suggestion.description }) : null, _jsx("span", { className: "memory-suggestion-body", children: suggestion.body })] }), _jsx("span", { className: "memory-connector-state is-connected", children: sourceLabel })] }, suggestion.id));
54
- }) }), _jsxs("div", { className: "memory-connector-actions", children: [_jsxs("button", { type: "button", className: "primary memory-source-action", onClick: () => void onSaveConnectorSuggestions(), disabled: connectorSaving || selectedConnectorSuggestions.length === 0, children: [_jsx(Icon, { name: connectorSaving ? 'refresh' : 'check', size: 14, className: connectorSaving ? 'icon-spin' : '' }), _jsx("span", { children: connectorSaving ? t('Saving') : t('Save selected') })] }), _jsx("button", { type: "button", className: "ghost memory-source-action", onClick: onDiscardConnectorSuggestions, disabled: connectorSaving, children: t('Discard') })] })] })) : null, connectorStatus ? (_jsx("div", { role: "status", className: "memory-connector-result is-success", children: connectorStatus })) : null, connectorError ? (_jsx("div", { role: "alert", className: "memory-connector-result is-error", children: connectorError })) : null, connectorAttempts.length > 0 ? (_jsxs("div", { className: "memory-connector-diagnostics", "aria-label": t('Connected app read status'), children: [_jsxs("div", { className: "memory-connector-diagnostics-head", children: [_jsx("strong", { children: t('Last scan') }), _jsxs("span", { children: [formatConnectorContextBytes(connectorContextBytes), " ", t('read')] })] }), _jsx("div", { className: "memory-connector-diagnostics-list", children: connectorAttempts.map((attempt) => (_jsxs("div", { className: `memory-connector-diagnostic-row is-${attempt.status}`, children: [_jsx("span", { className: "memory-connector-diagnostic-dot", "aria-hidden": "true" }), _jsxs("span", { className: "memory-connector-diagnostic-copy", children: [_jsx("strong", { children: connectorAttemptTitle(attempt) }), _jsx("small", { children: connectorAttemptDetail(attempt) })] })] }, `${attempt.connectorId}-${attempt.status}-${attempt.toolName ?? 'none'}`))) })] })) : null, connectorExtractions.length > 0 ? (_jsxs("details", { className: "memory-scan-history", children: [_jsxs("summary", { children: [_jsx("span", { children: t('Recent scans') }), _jsx("span", { children: connectorExtractions.length })] }), _jsx("div", { className: "memory-connector-run-history", "aria-label": t('Connected app memory run status'), children: connectorExtractions.slice(0, 4).map((record) => (_jsx(MemoryExtractionCard, { record: record, nowClock: nowClock, onOpenPreview: onOpenPreview, onDelete: onDeleteExtraction }, record.id))) })] })) : null] }));
89
+ return (_jsxs("div", { className: "memory-suggestion-panel", children: [_jsxs("div", { className: "memory-subsection-head", children: [_jsxs("div", { children: [_jsx("h4", { children: t('Suggested memories') }), _jsx("p", { className: "hint", children: t('Review design-related memories before saving them.') })] }), _jsxs("span", { className: "memory-source-badge", children: [selectedConnectorSuggestions.length, " ", t('selected')] })] }), _jsx("div", { className: "memory-suggestion-list", children: connectorSuggestions.map((suggestion) => (_jsx(MemorySuggestionCard, { suggestion: suggestion, selected: selectedSuggestionIds.has(suggestion.id), typeLabel: typeLabel[suggestion.type], toggleConnectorSuggestion: toggleConnectorSuggestion }, suggestion.id))) }), _jsxs("div", { className: "memory-connector-actions", children: [_jsxs("button", { type: "button", className: "primary memory-source-action", onClick: () => void onSaveConnectorSuggestions(), disabled: connectorSaving || selectedConnectorSuggestions.length === 0, children: [_jsx(Icon, { name: connectorSaving ? 'refresh' : 'check', size: 14, className: connectorSaving ? 'icon-spin' : '' }), _jsx("span", { children: connectorSaving ? t('Saving') : t('Save selected') })] }), _jsx("button", { type: "button", className: "ghost memory-source-action", onClick: onDiscardConnectorSuggestions, disabled: connectorSaving, children: t('Discard') })] })] }));
90
+ }
91
+ function MemoryConnectorDiagnostics({ connectorAttempts, connectorContextBytes, }) {
92
+ const t = useT();
93
+ return (_jsxs("div", { className: "memory-connector-diagnostics", "aria-label": t('Connected app read status'), children: [_jsxs("div", { className: "memory-connector-diagnostics-head", children: [_jsx("strong", { children: t('Last scan') }), _jsxs("span", { children: [formatConnectorContextBytes(connectorContextBytes), " ", t('read')] })] }), _jsx("div", { className: "memory-connector-diagnostics-list", children: connectorAttempts.map((attempt) => (_jsxs("div", { className: `memory-connector-diagnostic-row is-${attempt.status}`, children: [_jsx("span", { className: "memory-connector-diagnostic-dot", "aria-hidden": "true" }), _jsxs("span", { className: "memory-connector-diagnostic-copy", children: [_jsx("strong", { children: connectorAttemptTitle(attempt) }), _jsx("small", { children: connectorAttemptDetail(attempt) })] })] }, `${attempt.connectorId}-${attempt.status}-${attempt.toolName ?? 'none'}`))) })] }));
94
+ }
95
+ function MemoryScanHistory({ connectorExtractions, nowClock, onOpenPreview, onDeleteExtraction, }) {
96
+ const t = useT();
97
+ return (_jsxs("details", { className: "memory-scan-history", children: [_jsxs("summary", { children: [_jsx("span", { children: t('Recent scans') }), _jsx("span", { children: connectorExtractions.length })] }), _jsx("div", { className: "memory-connector-run-history", "aria-label": t('Connected app memory run status'), children: connectorExtractions.slice(0, 4).map((record) => (_jsx(MemoryExtractionCard, { record: record, nowClock: nowClock, onOpenPreview: onOpenPreview, onDelete: onDeleteExtraction }, record.id))) })] }));
98
+ }
99
+ export function MemoryConnectedPanel({ enabled, onOpenConnectors, connectorStatuses, connectorsLoading, connectedCount, selectedConnectorIds, selectedConnectedConnectorIds, connectingConnectorIds, pendingConnectorAuthIds, connectorConnectErrors, connectorIdsWithDetails, connectorExtracting, connectorSaving, connectorScanLabel, connectorSuggestions, selectedSuggestionIds, selectedConnectorSuggestions, connectorStatus, connectorError, connectorLoadError, connectorAttempts, connectorContextBytes, connectorExtractions, memoryConnectors, toggleConnectorSelection, onConnectMemoryConnector, toggleConnectorSuggestion, onSuggestConnectorMemory, onSaveConnectorSuggestions, onDiscardConnectorSuggestions, nowClock, onOpenPreview, onDeleteExtraction, }) {
100
+ const t = useT();
101
+ const rowContext = {
102
+ connectorStatuses,
103
+ connectorsLoading,
104
+ selectedConnectorIds,
105
+ connectingConnectorIds,
106
+ pendingConnectorAuthIds,
107
+ connectorConnectErrors,
108
+ connectorIdsWithDetails,
109
+ };
110
+ return (_jsxs("div", { className: "memory-tab-panel memory-connected-panel", children: [_jsx(MemoryConnectedSummary, { connectorsLoading: connectorsLoading, connectedCount: connectedCount, onOpenConnectors: onOpenConnectors }), _jsxs("div", { className: "memory-connector-workbench", children: [connectorLoadError ? (_jsx("div", { role: "alert", className: "memory-connector-result is-error", children: connectorLoadError })) : null, _jsxs("div", { className: "memory-connector-picker-head", children: [_jsxs("div", { children: [_jsx("h4", { children: t('Choose sources') }), _jsx("p", { className: "hint", children: t('Select connected apps first. Only the apps you choose are scanned.') })] }), _jsxs("span", { className: "memory-source-badge", children: [selectedConnectedConnectorIds.length, " ", t('selected')] })] }), _jsx("div", { className: "memory-connector-list", "aria-label": t('Connected memory apps'), children: memoryConnectors.map((connector) => (_jsx(MemoryConnectorRow, { connector: connector, context: rowContext, toggleConnectorSelection: toggleConnectorSelection, onConnectMemoryConnector: onConnectMemoryConnector }, connector.id))) }), _jsx(MemoryConnectorRunBar, { enabled: enabled, connectorExtracting: connectorExtracting, connectorSaving: connectorSaving, connectedCount: connectedCount, selectedCount: selectedConnectedConnectorIds.length, connectorScanLabel: connectorScanLabel, onSuggestConnectorMemory: onSuggestConnectorMemory })] }), connectorSuggestions.length > 0 ? (_jsx(MemorySuggestionPanel, { connectorSuggestions: connectorSuggestions, selectedSuggestionIds: selectedSuggestionIds, selectedConnectorSuggestions: selectedConnectorSuggestions, connectorSaving: connectorSaving, toggleConnectorSuggestion: toggleConnectorSuggestion, onSaveConnectorSuggestions: onSaveConnectorSuggestions, onDiscardConnectorSuggestions: onDiscardConnectorSuggestions })) : null, connectorStatus ? (_jsx("div", { role: "status", className: "memory-connector-result is-success", children: connectorStatus })) : null, connectorError ? (_jsx("div", { role: "alert", className: "memory-connector-result is-error", children: connectorError })) : null, connectorAttempts.length > 0 ? (_jsx(MemoryConnectorDiagnostics, { connectorAttempts: connectorAttempts, connectorContextBytes: connectorContextBytes })) : null, connectorExtractions.length > 0 ? (_jsx(MemoryScanHistory, { connectorExtractions: connectorExtractions, nowClock: nowClock, onOpenPreview: onOpenPreview, onDeleteExtraction: onDeleteExtraction })) : null] }));
55
111
  }
56
112
  //# sourceMappingURL=MemoryConnectedPanel.js.map
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  // Dumb panel for the saved-memory records: counts, the type-filter pills, the
3
3
  // extraction clear/refresh toolbar, and the unified list (saved entries + the
4
4
  // visible extraction rows). Rendering only — every list, count, and handler is
@@ -11,24 +11,43 @@ import { TYPES } from '../../constants.js';
11
11
  import { memoryTypeLabels } from '../../formatters.js';
12
12
  import { MemoryEntryCard } from './MemoryEntryCard.js';
13
13
  import { MemoryExtractionCard } from './MemoryExtractionCard.js';
14
- export function MemoryList({ sectionRef, entries, filtered, visibleExtractions, filter, onFilterChange, unifiedMemoryCount, onClearExtractions, onRefreshExtractions, isRefreshing, previewId, previewBody, nowClock, onOpenPreview, onStartEdit, onDeleteEntry, onDeleteExtraction, agentHandle, }) {
14
+ /** Per-row agent handles under `<agentHandle>-<action>`, or undefined when unpublished. */
15
+ function rowHandles({ agentHandle, action, ids }) {
16
+ return agentHandle ? buildAgentListHandles({ prefix: agentSubHandle({ base: agentHandle, action }), ids }) : undefined;
17
+ }
18
+ function MemoryListCounts({ savedCount, extractionCount }) {
19
+ const t = useT();
20
+ return (_jsxs("div", { className: "memory-management-counts", children: [_jsxs("span", { className: "memory-source-badge", children: [savedCount, " ", t('saved')] }), extractionCount > 0 ? (_jsxs("span", { className: "memory-source-badge", children: [extractionCount, " ", extractionCount === 1 ? t('extraction') : t('extractions')] })) : null] }));
21
+ }
22
+ function MemoryFilterPills({ entries, extractionCount, filter, onFilterChange, agentHandle, }) {
15
23
  const t = useT();
16
24
  const typeLabel = useMemo(() => memoryTypeLabels(t), [t]);
17
- const entryHandles = agentHandle
18
- ? buildAgentListHandles({ prefix: agentSubHandle({ base: agentHandle, action: 'entry' }), ids: filtered.map((e) => e.id) })
19
- : undefined;
20
- const extractionHandles = agentHandle
21
- ? buildAgentListHandles({ prefix: agentSubHandle({ base: agentHandle, action: 'extraction' }), ids: visibleExtractions.map((r) => r.id) })
22
- : undefined;
23
- return (_jsx("section", { ref: sectionRef, className: "settings-section settings-section-card memory-records-section", children: _jsxs("div", { className: "memory-management-panel", children: [_jsxs("div", { className: "memory-subsection-head", children: [_jsxs("div", { children: [_jsx("h4", { children: t('Saved memory') }), _jsx("p", { className: "hint", children: t('Saved facts, preferences, and project context available to future chats.') })] }), _jsxs("div", { className: "memory-management-counts", children: [_jsxs("span", { className: "memory-source-badge", children: [entries.length, " ", t('saved')] }), visibleExtractions.length > 0 ? (_jsxs("span", { className: "memory-source-badge", children: [visibleExtractions.length, " ", visibleExtractions.length === 1 ? t('extraction') : t('extractions')] })) : null] })] }), _jsxs("div", { className: "library-toolbar is-row", children: [_jsxs("div", { className: "library-filters", children: [_jsxs("button", { type: "button", className: `filter-pill${filter === 'all' ? ' active' : ''}`, onClick: () => onFilterChange('all'), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'filter-all', role: 'button', label: t('All') }) }), children: [t('All'), _jsx("span", { className: "filter-pill-count", children: entries.length + visibleExtractions.length })] }), TYPES.map((type) => {
24
- const count = entries.filter((e) => e.type === type).length;
25
- if (count === 0 && filter !== type)
26
- return null;
27
- return (_jsxs("button", { type: "button", className: `filter-pill${filter === type ? ' active' : ''}`, onClick: () => onFilterChange(type), ...agentHandleProps({}, { base: agentHandle, ...({ action: `filter-${type}`, role: 'button', label: typeLabel[type] }) }), children: [typeLabel[type], _jsx("span", { className: "filter-pill-count", children: count })] }, type));
28
- })] }), _jsxs("div", { className: "memory-management-actions", children: [visibleExtractions.length > 0 ? (_jsxs("button", { type: "button", className: "ghost memory-clear-extractions", onClick: () => onClearExtractions(), title: t('Clear extraction history'), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'clear-extractions', role: 'button', label: t('Clear extraction history') }) }), children: [_jsx(Icon, { name: "close", size: 12 }), _jsx("span", { children: t('Clear') })] })) : null, visibleExtractions.length > 0 ? (_jsxs("button", { type: "button", className: "ghost memory-refresh-extractions", onClick: () => onRefreshExtractions(), disabled: isRefreshing, title: t('Refresh'), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'refresh-extractions', role: 'button', label: t('Refresh') }) }), children: [_jsx(Icon, { name: "refresh", size: 12, className: isRefreshing ? 'icon-spin' : '' }), _jsx("span", { children: isRefreshing ? t('Refreshing') : t('Refresh') })] })) : null] })] }), _jsx("div", { className: "library-content memory-unified-list", children: unifiedMemoryCount === 0 ? (
29
- // Empty state: one clear "no rows yet" line and a one-sentence
30
- // primer that explains the mechanism (talk in chat, fact gets
31
- // extracted) with a single example.
32
- _jsxs("div", { className: "library-empty", children: [_jsx("p", { className: "library-empty-title", children: t('No saved memories yet') }), _jsxs("p", { className: "library-empty-hint", children: [t('Tell the assistant a fact in chat — e.g.'), " ", _jsx("code", { children: t('I prefer dark mode') }), ' ', t('— and it will be saved here automatically.')] })] })) : (_jsxs(_Fragment, { children: [filtered.map((entry, index) => (_jsx(MemoryEntryCard, { entry: entry, previewId: previewId, previewBody: previewBody, onOpenPreview: onOpenPreview, onStartEdit: onStartEdit, onDelete: onDeleteEntry, ...(entryHandles?.[index] ? { agentHandle: entryHandles[index] } : {}) }, entry.id))), visibleExtractions.map((record, index) => (_jsx(MemoryExtractionCard, { record: record, nowClock: nowClock, onOpenPreview: onOpenPreview, onDelete: onDeleteExtraction, ...(extractionHandles?.[index] ? { agentHandle: extractionHandles[index] } : {}) }, record.id)))] })) })] }) }));
25
+ return (_jsxs("div", { className: "library-filters", children: [_jsxs("button", { type: "button", className: `filter-pill${filter === 'all' ? ' active' : ''}`, onClick: () => onFilterChange('all'), ...agentHandleProps({}, { base: agentHandle, action: 'filter-all', role: 'button', label: t('All') }), children: [t('All'), _jsx("span", { className: "filter-pill-count", children: entries.length + extractionCount })] }), TYPES.map((type) => {
26
+ const count = entries.filter((e) => e.type === type).length;
27
+ if (count === 0 && filter !== type)
28
+ return null;
29
+ return (_jsxs("button", { type: "button", className: `filter-pill${filter === type ? ' active' : ''}`, onClick: () => onFilterChange(type), ...agentHandleProps({}, { base: agentHandle, action: `filter-${type}`, role: 'button', label: typeLabel[type] }), children: [typeLabel[type], _jsx("span", { className: "filter-pill-count", children: count })] }, type));
30
+ })] }));
31
+ }
32
+ function MemoryExtractionActions({ onClearExtractions, onRefreshExtractions, isRefreshing, agentHandle, }) {
33
+ const t = useT();
34
+ return (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "ghost memory-clear-extractions", onClick: () => onClearExtractions(), title: t('Clear extraction history'), ...agentHandleProps({}, { base: agentHandle, action: 'clear-extractions', role: 'button', label: t('Clear extraction history') }), children: [_jsx(Icon, { name: "close", size: 12 }), _jsx("span", { children: t('Clear') })] }), _jsxs("button", { type: "button", className: "ghost memory-refresh-extractions", onClick: () => onRefreshExtractions(), disabled: isRefreshing, title: t('Refresh'), ...agentHandleProps({}, { base: agentHandle, action: 'refresh-extractions', role: 'button', label: t('Refresh') }), children: [_jsx(Icon, { name: "refresh", size: 12, className: isRefreshing ? 'icon-spin' : '' }), _jsx("span", { children: isRefreshing ? t('Refreshing') : t('Refresh') })] })] }));
35
+ }
36
+ function MemoryListEmpty() {
37
+ const t = useT();
38
+ return (
39
+ // Empty state: one clear "no rows yet" line and a one-sentence
40
+ // primer that explains the mechanism (talk in chat, fact gets
41
+ // extracted) with a single example.
42
+ _jsxs("div", { className: "library-empty", children: [_jsx("p", { className: "library-empty-title", children: t('No saved memories yet') }), _jsxs("p", { className: "library-empty-hint", children: [t('Tell the assistant a fact in chat — e.g.'), " ", _jsx("code", { children: t('I prefer dark mode') }), ' ', t('— and it will be saved here automatically.')] })] }));
43
+ }
44
+ function MemoryListRows({ filtered, visibleExtractions, previewId, previewBody, nowClock, onOpenPreview, onStartEdit, onDeleteEntry, onDeleteExtraction, agentHandle, }) {
45
+ const entryHandles = rowHandles({ agentHandle, action: 'entry', ids: filtered.map((e) => e.id) });
46
+ const extractionHandles = rowHandles({ agentHandle, action: 'extraction', ids: visibleExtractions.map((r) => r.id) });
47
+ return (_jsxs(_Fragment, { children: [filtered.map((entry, index) => (_jsx(MemoryEntryCard, { entry: entry, previewId: previewId, previewBody: previewBody, onOpenPreview: onOpenPreview, onStartEdit: onStartEdit, onDelete: onDeleteEntry, ...(entryHandles?.[index] ? { agentHandle: entryHandles[index] } : {}) }, entry.id))), visibleExtractions.map((record, index) => (_jsx(MemoryExtractionCard, { record: record, nowClock: nowClock, onOpenPreview: onOpenPreview, onDelete: onDeleteExtraction, ...(extractionHandles?.[index] ? { agentHandle: extractionHandles[index] } : {}) }, record.id)))] }));
48
+ }
49
+ export function MemoryList({ sectionRef, entries, filtered, visibleExtractions, filter, onFilterChange, unifiedMemoryCount, onClearExtractions, onRefreshExtractions, isRefreshing, previewId, previewBody, nowClock, onOpenPreview, onStartEdit, onDeleteEntry, onDeleteExtraction, agentHandle, }) {
50
+ const t = useT();
51
+ return (_jsx("section", { ref: sectionRef, className: "settings-section settings-section-card memory-records-section", children: _jsxs("div", { className: "memory-management-panel", children: [_jsxs("div", { className: "memory-subsection-head", children: [_jsxs("div", { children: [_jsx("h4", { children: t('Saved memory') }), _jsx("p", { className: "hint", children: t('Saved facts, preferences, and project context available to future chats.') })] }), _jsx(MemoryListCounts, { savedCount: entries.length, extractionCount: visibleExtractions.length })] }), _jsxs("div", { className: "library-toolbar is-row", children: [_jsx(MemoryFilterPills, { entries: entries, extractionCount: visibleExtractions.length, filter: filter, onFilterChange: onFilterChange, agentHandle: agentHandle }), _jsx("div", { className: "memory-management-actions", children: visibleExtractions.length > 0 ? (_jsx(MemoryExtractionActions, { onClearExtractions: onClearExtractions, onRefreshExtractions: onRefreshExtractions, isRefreshing: isRefreshing, agentHandle: agentHandle })) : null })] }), _jsx("div", { className: "library-content memory-unified-list", children: unifiedMemoryCount === 0 ? (_jsx(MemoryListEmpty, {})) : (_jsx(MemoryListRows, { filtered: filtered, visibleExtractions: visibleExtractions, previewId: previewId, previewBody: previewBody, nowClock: nowClock, onOpenPreview: onOpenPreview, onStartEdit: onStartEdit, onDeleteEntry: onDeleteEntry, onDeleteExtraction: onDeleteExtraction, agentHandle: agentHandle })) })] }) }));
33
52
  }
34
53
  //# sourceMappingURL=MemoryList.js.map
@@ -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