@jini-ai/ui 0.6.15-rc.2 → 0.6.16

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 (227) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/browser/useDismissOnOutsideOrEscape.js +1 -1
  3. package/dist/browser/useGlobalKeydown.js +2 -2
  4. package/dist/browser/useModalWindowDragGuard.js +1 -1
  5. package/dist/core/theme/css-name.d.ts +3 -0
  6. package/dist/core/theme/css-name.js +5 -0
  7. package/dist/core/theme/stylesheet.js +2 -1
  8. package/dist/core/theme/validation.js +15 -4
  9. package/dist/core.d.ts +1 -1
  10. package/dist/core.js +1 -1
  11. package/dist/features/about/rules.js +2 -1
  12. package/dist/features/admin-widgets/components/InfoTip.hooks.js +22 -12
  13. package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.js +1 -1
  14. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.js +1 -1
  15. package/dist/features/admin-widgets/components/SeeMore/SeeMore.hooks.js +2 -2
  16. package/dist/features/admin-widgets/components/SeeMore/SeeMore.js +4 -2
  17. package/dist/features/admin-widgets/components/Select/Select.hooks.js +2 -1
  18. package/dist/features/admin-widgets/components/Select/Select.js +2 -1
  19. package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.js +4 -2
  20. package/dist/features/admin-widgets/components/useDebouncedHtmlPreview.hooks.js +3 -2
  21. package/dist/features/appearance/constants.d.ts +28 -1
  22. package/dist/features/appearance/constants.js +12 -9
  23. package/dist/features/appearance/react/components/AppearanceTab.js +4 -4
  24. package/dist/features/asset-grid/react/components/AssetGrid.js +8 -3
  25. package/dist/features/asset-grid/react/hooks/useAssetGridKeyboardShortcuts.js +7 -6
  26. package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.js +8 -10
  27. package/dist/features/asset-grid/react/hooks/useRubberBandDrag.js +1 -1
  28. package/dist/features/asset-tree-browser/react/components/AssetTreeFileRow.js +6 -8
  29. package/dist/features/asset-tree-browser/react/hooks/useAssetTreeRowMenu.js +1 -1
  30. package/dist/features/asset-tree-browser/rules.js +1 -3
  31. package/dist/features/browser-chrome/rules.js +10 -8
  32. package/dist/features/charts/browser.js +2 -1
  33. package/dist/features/charts/contract.d.ts +4 -4
  34. package/dist/features/charts/react.js +5 -3
  35. package/dist/features/charts/wire.d.ts +19 -0
  36. package/dist/features/charts/wire.js +20 -2
  37. package/dist/features/connectors/ConnectorsBrowser.js +3 -1
  38. package/dist/features/connectors/hooks/useConnectorAuthorization.js +1 -1
  39. package/dist/features/connectors/hooks/useConnectorCatalog.js +2 -2
  40. package/dist/features/connectors/rules.js +10 -14
  41. package/dist/features/execution/constants.js +0 -1
  42. package/dist/features/execution/react/components/AgentDiagnosticRow.js +2 -0
  43. package/dist/features/execution/react/components/ByokProviderForm.js +1 -3
  44. package/dist/features/execution/react/components/LocalCliAgentSettings.js +2 -1
  45. package/dist/features/execution/react/hooks/use-byok-field-validation.hooks.js +6 -2
  46. package/dist/features/execution/react/hooks/useExecutionTab.js +9 -5
  47. package/dist/features/execution/react/hooks/useLocalCliAgentCard.js +3 -6
  48. package/dist/features/execution/react/hooks/useReasoningControl.js +3 -3
  49. package/dist/features/execution/rules.js +23 -18
  50. package/dist/features/external-mcp/react/components/ExternalMcpTab.js +10 -1
  51. package/dist/features/file-dropzone/rules.js +3 -2
  52. package/dist/features/folder-path-drop/rules.d.ts +2 -3
  53. package/dist/features/folder-path-drop/rules.js +3 -4
  54. package/dist/features/html-editor/canvas-embed-placeholders.js +2 -3
  55. package/dist/features/html-editor/css.js +7 -5
  56. package/dist/features/html-editor/editor-blocks.js +60 -6
  57. package/dist/features/html-editor/protected-elements.js +5 -3
  58. package/dist/features/html-editor/react/hooks/useInteractiveHtmlEditor.js +3 -3
  59. package/dist/features/html-editor/source-splice.js +6 -4
  60. package/dist/features/i18n/dictionaries/settings-dialog.ar.js +8 -0
  61. package/dist/features/i18n/dictionaries/settings-dialog.bn.js +8 -0
  62. package/dist/features/i18n/dictionaries/settings-dialog.de.js +8 -0
  63. package/dist/features/i18n/dictionaries/settings-dialog.en.js +8 -0
  64. package/dist/features/i18n/dictionaries/settings-dialog.es.js +8 -0
  65. package/dist/features/i18n/dictionaries/settings-dialog.fa.js +8 -0
  66. package/dist/features/i18n/dictionaries/settings-dialog.fr.js +8 -0
  67. package/dist/features/i18n/dictionaries/settings-dialog.hi.js +8 -0
  68. package/dist/features/i18n/dictionaries/settings-dialog.hu.js +8 -0
  69. package/dist/features/i18n/dictionaries/settings-dialog.id.js +8 -0
  70. package/dist/features/i18n/dictionaries/settings-dialog.it.js +8 -0
  71. package/dist/features/i18n/dictionaries/settings-dialog.ja.js +8 -0
  72. package/dist/features/i18n/dictionaries/settings-dialog.ko.js +8 -0
  73. package/dist/features/i18n/dictionaries/settings-dialog.pl.js +8 -0
  74. package/dist/features/i18n/dictionaries/settings-dialog.pt-BR.js +8 -0
  75. package/dist/features/i18n/dictionaries/settings-dialog.ru.js +8 -0
  76. package/dist/features/i18n/dictionaries/settings-dialog.th.js +8 -0
  77. package/dist/features/i18n/dictionaries/settings-dialog.tr.js +8 -0
  78. package/dist/features/i18n/dictionaries/settings-dialog.uk.js +8 -0
  79. package/dist/features/i18n/dictionaries/settings-dialog.ur.js +8 -0
  80. package/dist/features/i18n/dictionaries/settings-dialog.zh-CN.js +8 -0
  81. package/dist/features/i18n/dictionaries/settings-dialog.zh-TW.js +8 -0
  82. package/dist/features/iframe-pool/react/components/IframeKeepAliveProvider.js +5 -5
  83. package/dist/features/iframe-pool/react/dom-sync.js +2 -2
  84. package/dist/features/integrations/react/components/IntegrationsTab.js +3 -1
  85. package/dist/features/integrations/rules.js +6 -4
  86. package/dist/features/interactive-ui/providers/shadcn/data-table.js +10 -5
  87. package/dist/features/interactive-ui/table-data.js +1 -1
  88. package/dist/features/lexical-rich-text-editor/deserialize.js +2 -2
  89. package/dist/features/lexical-rich-text-editor/mention-parser.js +4 -4
  90. package/dist/features/lexical-rich-text-editor/react/components/RichTextInput.js +2 -1
  91. package/dist/features/lexical-rich-text-editor/react/hooks/useCaretFloatingLayerPosition.js +1 -1
  92. package/dist/features/lexical-rich-text-editor/react/hooks/useMentionColorStamping.js +1 -1
  93. package/dist/features/lexical-rich-text-editor/serialize.js +13 -10
  94. package/dist/features/list-detail-panel/react/components/ListDetailPanel.js +1 -1
  95. package/dist/features/mcp-ui/confirmation-store.js +3 -6
  96. package/dist/features/mcp-ui/surfaces/checkbox.d.ts +2 -0
  97. package/dist/features/mcp-ui/surfaces/choice-group.d.ts +2 -0
  98. package/dist/features/mcp-ui/surfaces/choice-group.js +2 -2
  99. package/dist/features/mcp-ui/surfaces/confirmation.d.ts +5 -0
  100. package/dist/features/mcp-ui/surfaces/confirmation.js +1 -0
  101. package/dist/features/mcp-ui/surfaces/document.d.ts +14 -16
  102. package/dist/features/mcp-ui/surfaces/document.js +46 -7
  103. package/dist/features/mcp-ui/surfaces/fields.d.ts +7 -1
  104. package/dist/features/mcp-ui/surfaces/fields.js +14 -11
  105. package/dist/features/mcp-ui/surfaces/form.js +6 -1
  106. package/dist/features/mcp-ui/surfaces/host-theme.js +2 -1
  107. package/dist/features/mcp-ui/surfaces/index.d.ts +1 -0
  108. package/dist/features/mcp-ui/surfaces/index.js +1 -0
  109. package/dist/features/mcp-ui/surfaces/outcome.js +1 -1
  110. package/dist/features/mcp-ui/surfaces/select.d.ts +2 -0
  111. package/dist/features/mcp-ui/surfaces/select.js +2 -1
  112. package/dist/features/mcp-ui/surfaces/structured-details.d.ts +40 -0
  113. package/dist/features/mcp-ui/surfaces/structured-details.js +159 -0
  114. package/dist/features/mcp-ui/surfaces/text-input.d.ts +2 -0
  115. package/dist/features/mcp-ui/surfaces/text-input.js +1 -1
  116. package/dist/features/mcp-ui/surfaces/tool-approval.d.ts +21 -0
  117. package/dist/features/mcp-ui/surfaces/tool-approval.js +42 -0
  118. package/dist/features/media-providers/react/hooks/useMediaProvidersTab.js +2 -1
  119. package/dist/features/memory/async-commit-guard.js +4 -1
  120. package/dist/features/memory/formatters.js +5 -1
  121. package/dist/features/memory/react/components/MemoryConnectedPanel.js +3 -1
  122. package/dist/features/memory/react/components/MemoryEntryCard.js +1 -1
  123. package/dist/features/memory/react/components/MemorySettingsPanel.js +3 -1
  124. package/dist/features/memory/react/hooks/useMemoryConfig.hooks.js +12 -10
  125. package/dist/features/memory/react/hooks/useMemoryConnectors.hooks.js +2 -5
  126. package/dist/features/memory/react/hooks/useMemoryConnectors.rules.js +10 -9
  127. package/dist/features/memory/react/hooks/useMemoryEntries.hooks.js +4 -2
  128. package/dist/features/memory/react/hooks/useMemoryFlash.hooks.js +1 -1
  129. package/dist/features/memory/rules.js +2 -1
  130. package/dist/features/panel-kit/fetch-query/adapter.react.js +3 -2
  131. package/dist/features/panel-kit/fetch-query/adapter.tanstack.js +3 -2
  132. package/dist/features/panel-kit/fetch-query/binding.react.js +2 -1
  133. package/dist/features/panel-kit/helpers/standing-draft-recovery-message.js +13 -4
  134. package/dist/features/panel-kit/hooks/use-dirty-guard.hooks.js +1 -1
  135. package/dist/features/panel-kit/hooks/use-focus-trap.hooks.js +1 -1
  136. package/dist/features/panel-kit/hooks/use-settings-slice.hooks.js +14 -10
  137. package/dist/features/panel-kit/hooks/use-standing-draft-autosave.hooks.js +2 -2
  138. package/dist/features/progress-card/components/ProgressCard.d.ts +1 -1
  139. package/dist/features/progress-card/reference-adapters.js +23 -13
  140. package/dist/features/progress-card/rules.d.ts +0 -1
  141. package/dist/features/progress-card/rules.js +14 -20
  142. package/dist/features/progress-card/types.d.ts +1 -1
  143. package/dist/features/project-locations/dependencies.js +6 -1
  144. package/dist/features/project-locations/react/components/ProjectLocationsTab.js +2 -1
  145. package/dist/features/project-locations/rules.js +2 -1
  146. package/dist/features/resource-dashboard/react/components/ResourceBoardToolbar.js +8 -1
  147. package/dist/features/resource-dashboard/react/components/ResourceCardParts.js +2 -1
  148. package/dist/features/resource-dashboard/react/components/ResourceRowListItem.js +3 -1
  149. package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.js +2 -1
  150. package/dist/features/resource-dashboard/react/components/ResourceRowListView.js +1 -1
  151. package/dist/features/resource-dashboard/react/hooks/useResourceBoard.js +1 -1
  152. package/dist/features/schedule-picker/rules.js +13 -7
  153. package/dist/features/sketch-editor/constants.d.ts +0 -6
  154. package/dist/features/sketch-editor/constants.js +17 -15
  155. package/dist/features/sketch-editor/dom.js +4 -7
  156. package/dist/features/sketch-editor/react/components/SketchMainMenu.js +2 -1
  157. package/dist/features/sketch-editor/react/components/SketchSaveStateBadge.js +2 -1
  158. package/dist/features/sketch-editor/react/hooks/useSketchDomEnhancements.js +1 -1
  159. package/dist/features/sketch-editor/rules.js +13 -12
  160. package/dist/features/skills/dependencies.js +9 -4
  161. package/dist/features/skills/react/components/SkillRow.js +2 -1
  162. package/dist/features/skills/react/components/SkillsTab.js +2 -1
  163. package/dist/features/skills/rules.js +3 -3
  164. package/dist/features/source-config-list/agent-handles.js +4 -3
  165. package/dist/features/source-config-list/react/components/SourceConfigItemCard.js +3 -1
  166. package/dist/features/tab-strip/react/components/TabBar.hooks.js +4 -2
  167. package/dist/features/version-manager/react/components/VersionPreviewFrame.js +4 -1
  168. package/dist/features/version-manager/react/components/VersionSidebar.js +8 -6
  169. package/dist/features/version-manager/react/hooks/usePreviewCanvasSize.js +1 -1
  170. package/dist/features/version-manager/react/hooks/useVersionManager.js +5 -7
  171. package/dist/features/version-manager/rules.js +3 -5
  172. package/dist/features/viewer-shell/react/components/CommentSidePanel.js +2 -1
  173. package/dist/features/viewer-shell/react/components/SegmentedToggle.js +2 -1
  174. package/dist/features/viewer-shell/react/components/ViewerShell.js +2 -1
  175. package/dist/features/viewer-shell/rules.js +8 -7
  176. package/dist/react/components/AgentIcon.js +0 -1
  177. package/dist/react/components/BrandLogo.js +10 -2
  178. package/dist/react/components/CustomSelect.js +1 -1
  179. package/dist/react/components/EditorIcon.js +17 -14
  180. package/dist/react/components/Loading.js +2 -1
  181. package/dist/react/components/OnboardingChipField.js +5 -1
  182. package/dist/react/components/OnboardingDropdown.js +6 -4
  183. package/dist/react/components/PaletteTweaks.js +7 -6
  184. package/dist/react/components/PillButton.js +2 -1
  185. package/dist/react/components/PopoverMenu.js +2 -1
  186. package/dist/react/components/RemixIcon.js +2 -1
  187. package/dist/react/components/SecretInput.hooks.d.ts +2 -25
  188. package/dist/react/components/SecretInput.hooks.js +2 -24
  189. package/dist/react/components/SecretInput.js +5 -4
  190. package/dist/react/components/Toast.js +4 -2
  191. package/dist/react/components/TooltipLayer.js +4 -1
  192. package/dist/react/components/WorkingDirPicker.js +5 -4
  193. package/dist/react/components/agent-icon-data-uris.generated.js +0 -1
  194. package/dist/react/components/remixicon-font-recovery.js +9 -1
  195. package/dist/react/components/select-popover.js +2 -2
  196. package/dist/react/hooks/useBrandFonts.js +1 -1
  197. package/dist/react/hooks/useEdgeAutoScroll.js +1 -1
  198. package/dist/react/hooks/useInView.js +9 -9
  199. package/dist/react/hooks/useResizableSplitPane.js +12 -20
  200. package/dist/react/mcp-ui/useHostSurfaceTokens.js +1 -1
  201. package/dist/renderers/annotation-canvas/react/components/AnnotationCanvas.js +7 -1
  202. package/dist/renderers/annotation-canvas/react/hooks/useAnnotationCanvas.controller.js +4 -2
  203. package/dist/renderers/annotation-canvas/react/hooks/useAnnotationCanvas.js +9 -5
  204. package/dist/renderers/annotation-canvas/rules.js +9 -13
  205. package/dist/renderers/annotation-canvas/theme.js +5 -2
  206. package/dist/renderers/renderers/index.js +1 -1
  207. package/dist/renderers/renderers/markdown.js +4 -6
  208. package/dist/renderers/shiki.js +7 -1
  209. package/dist/theme/apply.js +7 -3
  210. package/dist/utils/class-names.d.ts +3 -0
  211. package/dist/utils/class-names.js +5 -0
  212. package/dist/utils/design-md.js +3 -3
  213. package/dist/utils/ime-composing.d.ts +1 -1
  214. package/dist/utils/ime-composing.js +1 -2
  215. package/dist/utils/localized-url.js +2 -11
  216. package/dist/utils/markdown-scroll-sync.js +1 -1
  217. package/dist/utils/omit-keys.d.ts +3 -0
  218. package/dist/utils/omit-keys.js +8 -0
  219. package/dist/utils/polygon-selection.js +1 -1
  220. package/dist/utils/sse.js +3 -0
  221. package/dist/utils/trim-chars.d.ts +13 -0
  222. package/dist/utils/trim-chars.js +30 -0
  223. package/dist/utils/unhandled-case.d.ts +10 -0
  224. package/dist/utils/unhandled-case.js +12 -0
  225. package/dist/utils/unit-bezier.js +1 -3
  226. package/dist/utils/zip.js +6 -10
  227. package/package.json +32 -29
@@ -54,10 +54,15 @@ function formActions(spec) {
54
54
  actions.push({ id: 'cancel', label: spec.cancel.label, variant: 'neutral' });
55
55
  return actions;
56
56
  }
57
+ /** Case- and whitespace-insensitive equality: a field label that only repeats the card title is the same question. */
58
+ function sameText(a, b) {
59
+ const norm = (s) => s.trim().replace(/\s+/g, ' ').toLowerCase();
60
+ return norm(a) === norm(b);
61
+ }
57
62
  /** Header, context, the `<form>` itself and the status region. @complexity O(n) in the rendered length. */
58
63
  function formBodyHtml(spec, text) {
59
64
  const controls = spec.fields
60
- .map((field) => renderFieldControl(field, { showSecret: text.showSecret, hideSecret: text.hideSecret }))
65
+ .map((field) => renderFieldControl(field, { showSecret: text.showSecret, hideSecret: text.hideSecret }, { labelHidden: sameText(field.label, spec.title) }))
61
66
  .join('\n');
62
67
  const warning = spec.warning === undefined ? '' : `<p class="mcpui-warning">${escapeHtml(spec.warning)}</p>`;
63
68
  return [
@@ -79,7 +79,8 @@ export function applyHostSurfaceTokens(html, tokens) {
79
79
  if (close === -1)
80
80
  return html;
81
81
  const at = close + '</style>'.length;
82
- const rule = `\n<style ${HOST_THEME_STYLE_ATTRIBUTE}>\n:root {\n${entries.map(([name, value]) => ` ${name}: ${value};`).join('\n')}\n}\n</style>`;
82
+ const declarations = entries.map(([name, value]) => ` ${name}: ${value};`).join('\n');
83
+ const rule = `\n<style ${HOST_THEME_STYLE_ATTRIBUTE}>\n:root {\n${declarations}\n}\n</style>`;
83
84
  return `${html.slice(0, at)}${rule}${html.slice(at)}`;
84
85
  }
85
86
  //# sourceMappingURL=host-theme.js.map
@@ -10,4 +10,5 @@ export * from './outcome.js';
10
10
  export * from './select.js';
11
11
  export * from './text-input.js';
12
12
  export * from './tokens.js';
13
+ export * from './tool-approval.js';
13
14
  //# sourceMappingURL=index.d.ts.map
@@ -10,4 +10,5 @@ export * from './outcome.js';
10
10
  export * from './select.js';
11
11
  export * from './text-input.js';
12
12
  export * from './tokens.js';
13
+ export * from './tool-approval.js';
13
14
  //# sourceMappingURL=index.js.map
@@ -44,7 +44,7 @@
44
44
  */
45
45
  import { createUIResource } from '../resource.js';
46
46
  import { escapeHtml, escapeJsValue } from '../escape.js';
47
- import { renderActions, renderDetailList, renderStatusRegion, renderSurfaceDocument, renderSurfaceHeader, SURFACE_SCRIPT_PRELUDE, SURFACE_STATUS_ELEMENT_ID, } from './document.js';
47
+ import { renderActions, renderDetailList, renderSurfaceDocument, renderSurfaceHeader, SURFACE_SCRIPT_PRELUDE, SURFACE_STATUS_ELEMENT_ID, } from './document.js';
48
48
  const DEFAULT_APP = { appName: 'jini-mcp-ui-outcome', appVersion: '1' };
49
49
  const DEFAULT_OPEN_LINK_LABEL = 'Open site';
50
50
  const OPEN_LINK_ACTION_ID = 'open-link';
@@ -23,6 +23,8 @@ export interface SelectProps {
23
23
  /** Label for that leading empty option. Defaults to an em-dash. */
24
24
  readonly placeholder?: string;
25
25
  readonly disabled?: boolean;
26
+ /** Visually hide the label (it repeats the card title); it stays the accessible name. */
27
+ readonly labelHidden?: boolean;
26
28
  }
27
29
  /**
28
30
  * Renders one enumerated field.
@@ -17,8 +17,9 @@ import { fieldDescribedBy, fieldElementId, renderFieldLabel } from './document.j
17
17
  */
18
18
  export function renderSelect(props) {
19
19
  const id = fieldElementId(props.name);
20
+ const placeholderSelected = props.value === undefined ? 'selected ' : '';
20
21
  const placeholderOption = props.required === true
21
- ? `\n <option value="" ${props.value === undefined ? 'selected ' : ''}disabled>${escapeHtml(props.placeholder ?? '—')}</option>`
22
+ ? `\n <option value="" ${placeholderSelected}disabled>${escapeHtml(props.placeholder ?? '—')}</option>`
22
23
  : '';
23
24
  const options = props.options
24
25
  .map((option) => {
@@ -0,0 +1,40 @@
1
+ /** One fact. Values and previous snapshots must be JSON-serializable; strings remain supported. */
2
+ export interface SurfaceDetail {
3
+ readonly label: string;
4
+ readonly value: unknown;
5
+ /** Presence enables a diff, including an explicitly absent previous override. */
6
+ readonly previous?: unknown;
7
+ /** Explicit, optionally translated labels keyed by dotted object path. */
8
+ readonly labels?: Readonly<Record<string, string>>;
9
+ /** Optional secondary context beneath the label, using the shared muted hint style. */
10
+ readonly description?: string;
11
+ /**
12
+ * `code` discloses a monospace `<pre>` that keeps line breaks and scrolls inside its own box
13
+ * past a fixed height, so long SQL or JSON remains readable without stretching the dialog past
14
+ * its frame. `status` reserves one line for a short outcome value. Omitted (or `text`) permits
15
+ * value-sensitive presentation, preserving plain inline strings for short prose.
16
+ */
17
+ readonly format?: 'text' | 'code' | 'status';
18
+ }
19
+ type ValueContext = {
20
+ value: unknown;
21
+ path: string;
22
+ labels: Readonly<Record<string, string>>;
23
+ format?: SurfaceDetail['format'];
24
+ };
25
+ /** Native details owns keyboard interaction; mirror its state for explicit ARIA consumers. */
26
+ export declare const DETAIL_DISCLOSURE_SCRIPT = "document.addEventListener(\"toggle\", function (event) {\n var disclosure = event.target;\n if (!disclosure.matches || !disclosure.matches(\"details.mcpui-detail-disclosure\")) return;\n disclosure.querySelector(\"summary\").setAttribute(\"aria-expanded\", disclosure.open ? \"true\" : \"false\");\n}, true);";
27
+ /** Humanized English fallback; caller labels remain verbatim. @complexity O(k) key length. */
28
+ export declare function surfaceDetailLabel({ key, path, labels }: {
29
+ key: string;
30
+ path: string;
31
+ labels: ValueContext['labels'];
32
+ }, _options?: {}): string;
33
+ /** One reusable detail body. Raw JSON is escaped text, never a script or HTML fragment.
34
+ * @returns HTML for JSON-compatible caller data; JSON.stringify errors propagate for invalid data.
35
+ * @complexity O(n * depth) with a previous snapshot, otherwise O(n), in serialized bytes. */
36
+ export declare function renderStructuredDetail({ detail }: {
37
+ detail: SurfaceDetail;
38
+ }, _options?: {}): string;
39
+ export {};
40
+ //# sourceMappingURL=structured-details.d.ts.map
@@ -0,0 +1,159 @@
1
+ /** Shared value presentation for confirmation, form and outcome details. Never interprets HTML. */
2
+ import { escapeHtml } from '../escape.js';
3
+ /** Native details owns keyboard interaction; mirror its state for explicit ARIA consumers. */
4
+ export const DETAIL_DISCLOSURE_SCRIPT = `document.addEventListener("toggle", function (event) {
5
+ var disclosure = event.target;
6
+ if (!disclosure.matches || !disclosure.matches("details.mcpui-detail-disclosure")) return;
7
+ disclosure.querySelector("summary").setAttribute("aria-expanded", disclosure.open ? "true" : "false");
8
+ }, true);`;
9
+ /** Humanized English fallback; caller labels remain verbatim. @complexity O(k) key length. */
10
+ export function surfaceDetailLabel({ key, path, labels }, _options = {}) {
11
+ if (Object.hasOwn(labels, path))
12
+ return labels[path];
13
+ const words = key.replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/([A-Z])([A-Z][a-z])/g, '$1 $2').replace(/[_-]+/g, ' ').toLowerCase();
14
+ const acronyms = words.replace(/\b(url|css|html|json|api|id)\b/g, word => word.toUpperCase());
15
+ return acronyms.charAt(0).toUpperCase() + acronyms.slice(1);
16
+ }
17
+ function isRecord(value) {
18
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
19
+ }
20
+ function disclosure({ summary, body, kind = '' }, _options = {}) {
21
+ return `<details class="mcpui-detail-disclosure ${kind}"><summary aria-expanded="false">${escapeHtml(summary)}</summary>${body}</details>`;
22
+ }
23
+ /** JSON scalar display; missing override values use the same empty marker as null. */
24
+ function scalarText({ value }, _options = {}) {
25
+ if (value === undefined || value === null || value === '')
26
+ return '—';
27
+ if (typeof value === 'boolean')
28
+ return value ? 'On' : 'Off';
29
+ return String(value);
30
+ }
31
+ /** Classifies content for a disclosure, preserving the original text bytes in the body. */
32
+ function codeKind({ value, path, format }, _options = {}) {
33
+ const hasDeclaration = value.split('}').slice(0, -1).some(part => {
34
+ const open = part.indexOf('{');
35
+ return open >= 0 && part.indexOf(':', open + 1) >= 0;
36
+ });
37
+ if (/css$/i.test(path) || (hasDeclaration && !/^\s*[\[{]/.test(value)))
38
+ return 'CSS';
39
+ if (/^\s*</.test(value))
40
+ return 'HTML';
41
+ if (/^\s*[\[{]/.test(value))
42
+ return 'JSON';
43
+ if (format === 'code')
44
+ return 'Code';
45
+ return /[\r\n]/.test(value) ? 'Text' : undefined;
46
+ }
47
+ /** Escapes all text once at the HTML sink. Long prose uses a three-line summary. */
48
+ function renderText({ value, path, format }, _options = {}) {
49
+ const text = escapeHtml(value);
50
+ const kind = codeKind({ value, path, format });
51
+ if (kind) {
52
+ const lines = value.split(/\r\n|\r|\n/).length;
53
+ return disclosure({ summary: `${kind} · ${lines} ${lines === 1 ? 'line' : 'lines'}`, body: `<pre class="mcpui-code">${text}</pre>`, kind: 'mcpui-detail-code' });
54
+ }
55
+ if (value.length <= 80)
56
+ return text;
57
+ return `<details class="mcpui-detail-disclosure mcpui-detail-long"><summary aria-expanded="false"><span class="mcpui-detail-preview">${text}</span><span class="mcpui-detail-more">Show more</span><span class="mcpui-detail-less">Show less</span></summary><div class="mcpui-detail-full">${text}</div></details>`;
58
+ }
59
+ /** Arrays keep scalar items compact; nested or long items use readable bullet rows. */
60
+ function renderArray({ value, ...context }, _options = {}) {
61
+ if (value.length === 0)
62
+ return '<span class="mcpui-detail-value">—</span>';
63
+ const compact = value.every(item => item === null || ['boolean', 'number', 'string'].includes(typeof item))
64
+ && value.every(item => String(item).length <= 80 && !/[\r\n<{[]/.test(String(item)));
65
+ if (compact)
66
+ return renderText({ ...context, value: value.map(item => scalarText({ value: item })).join(', ') });
67
+ const items = value.map((item, index) => {
68
+ const path = context.path ? `${context.path}.${index}` : String(index);
69
+ return `<li>${renderValue({ ...context, value: item, path })}</li>`;
70
+ });
71
+ const rest = items.length <= 5 ? '' : disclosure({ summary: `Show more (${items.length - 5} items)`, body: `<ul class="mcpui-detail-array">${items.slice(5).join('')}</ul>` });
72
+ return `<ul class="mcpui-detail-array">${items.slice(0, 5).join('')}</ul>${rest}`;
73
+ }
74
+ function objectRow({ key, context, body }, _options = {}) {
75
+ return `<div class="mcpui-detail-row"><dt>${escapeHtml(surfaceDetailLabel({ key, path: context.path, labels: context.labels }))}</dt><dd>${body}</dd></div>`;
76
+ }
77
+ /** Nested objects stay definition lists, indented once per actual group. @complexity O(n) rendered bytes. */
78
+ function renderObject({ value, ...context }, _options = {}) {
79
+ const entries = Object.entries(value);
80
+ if (entries.length === 0)
81
+ return '<span class="mcpui-detail-value">—</span>';
82
+ const rows = entries.map(([key, item]) => {
83
+ const child = { ...context, value: item, path: context.path ? `${context.path}.${key}` : key };
84
+ return objectRow({ key, context: child, body: renderValue(child) });
85
+ }).join('');
86
+ return `<dl class="mcpui-detail-group">${rows}</dl>`;
87
+ }
88
+ /** Pure recursive rendering, O(n) in output bytes and O(depth) traversal stack; no external I/O. */
89
+ function renderValue(context, _options = {}) {
90
+ const { value } = context;
91
+ if (Array.isArray(value))
92
+ return renderArray({ ...context, value });
93
+ if (isRecord(value))
94
+ return renderObject({ ...context, value });
95
+ if (typeof value === 'string' && value !== '')
96
+ return renderText({ ...context, value });
97
+ return escapeHtml(scalarText({ value }));
98
+ }
99
+ /** Object order is immaterial; array order and missing keys matter to an approval diff. */
100
+ function equalValue({ before, after }, _options = {}) {
101
+ if (Object.is(before, after))
102
+ return true;
103
+ if (Array.isArray(before) && Array.isArray(after)) {
104
+ return before.length === after.length && before.every((item, index) => equalValue({ before: item, after: after[index] }));
105
+ }
106
+ if (!isRecord(before) || !isRecord(after))
107
+ return false;
108
+ const keys = Object.keys(before);
109
+ return keys.length === Object.keys(after).length && keys.every(key => Object.hasOwn(after, key) && equalValue({ before: before[key], after: after[key] }));
110
+ }
111
+ /** A missing JSON key must never read an inherited property such as __proto__ or constructor. */
112
+ function ownValue({ record, key }, _options = {}) {
113
+ return Object.hasOwn(record, key) ? record[key] : undefined;
114
+ }
115
+ /** Presence needs its own label: null and missing otherwise both display an empty marker. */
116
+ function renderFieldDiff({ child, previous, value, key }, _options = {}) {
117
+ const body = renderDiff(child);
118
+ if (Object.hasOwn(previous, key) && Object.hasOwn(value, key))
119
+ return body;
120
+ const label = Object.hasOwn(value, key) ? 'Added' : 'Removed';
121
+ return `<span class="mcpui-hint">${label}</span>${body}`;
122
+ }
123
+ /** Partition by equality before rendering so unchanged fields never crowd out changes.
124
+ * @complexity O(n * depth) worst-case nested comparison work; O(n) markup space. No reads or mutation. */
125
+ function renderObjectDiff(context, _options = {}) {
126
+ const { previous, value } = context;
127
+ const keys = [...new Set([...Object.keys(value), ...Object.keys(previous)])];
128
+ const changed = [];
129
+ const unchanged = [];
130
+ for (const key of keys) {
131
+ const child = { ...context, previous: ownValue({ record: previous, key }), value: ownValue({ record: value, key }), path: context.path ? `${context.path}.${key}` : key };
132
+ const same = Object.hasOwn(previous, key) === Object.hasOwn(value, key) && equalValue({ before: child.previous, after: child.value });
133
+ const body = same ? renderValue(child) : renderFieldDiff({ child, previous, value, key });
134
+ (same ? unchanged : changed).push(objectRow({ key, context: child, body }));
135
+ }
136
+ const rest = unchanged.length === 0 ? '' : disclosure({ summary: `Unchanged fields (${unchanged.length})`, body: `<dl class="mcpui-detail-group">${unchanged.join('')}</dl>`, kind: 'mcpui-detail-unchanged' });
137
+ const changes = changed.length === 0 ? '' : `<dl class="mcpui-detail-group">${changed.join('')}</dl>`;
138
+ return changes + rest || '<span class="mcpui-detail-value">—</span>';
139
+ }
140
+ /** Recurse only through object pairs; atomic and array updates show explicit before/after values. */
141
+ function renderDiff(context, _options = {}) {
142
+ if (isRecord(context.previous) && isRecord(context.value))
143
+ return renderObjectDiff({ ...context, previous: context.previous, value: context.value });
144
+ return `<div class="mcpui-detail-change"><div><span class="mcpui-hint">Before</span>${renderValue({ ...context, value: context.previous })}</div><div><span class="mcpui-hint">After</span>${renderValue(context)}</div></div>`;
145
+ }
146
+ /** One reusable detail body. Raw JSON is escaped text, never a script or HTML fragment.
147
+ * @returns HTML for JSON-compatible caller data; JSON.stringify errors propagate for invalid data.
148
+ * @complexity O(n * depth) with a previous snapshot, otherwise O(n), in serialized bytes. */
149
+ export function renderStructuredDetail({ detail }, _options = {}) {
150
+ const context = { value: detail.value, path: '', labels: detail.labels ?? {}, format: detail.format };
151
+ const hasPrevious = Object.hasOwn(detail, 'previous');
152
+ const body = hasPrevious ? renderDiff({ ...context, previous: detail.previous }) : renderValue(context);
153
+ const raw = hasPrevious ? { previous: detail.previous, value: detail.value } : detail.value;
154
+ const showRaw = hasPrevious || (typeof detail.value === 'object' && detail.value !== null) || body.startsWith('<details ');
155
+ if (!showRaw)
156
+ return body;
157
+ return body + disclosure({ summary: 'View raw', body: `<pre class="mcpui-code">${escapeHtml(JSON.stringify(raw, null, 2))}</pre>`, kind: 'mcpui-detail-raw' });
158
+ }
159
+ //# sourceMappingURL=structured-details.js.map
@@ -16,6 +16,8 @@ export interface TextInputProps extends Partial<SecretInputText> {
16
16
  readonly hint?: string;
17
17
  readonly required?: boolean;
18
18
  readonly disabled?: boolean;
19
+ /** Visually hide the label (it repeats the card title); it stays the accessible name. */
20
+ readonly labelHidden?: boolean;
19
21
  /** `'number'` also constrains the on-screen keyboard on touch devices, which `'text'` would not. */
20
22
  readonly inputType?: 'text' | 'number';
21
23
  /** Renders as `<input type="password">`, or a masked textarea when multiline — either way with
@@ -13,7 +13,7 @@ function optionalAttribute(name, value) {
13
13
  return '';
14
14
  return ` ${name}="${escapeHtml(String(value))}"`;
15
15
  }
16
- function booleanAttribute(name, value) {
16
+ function booleanAttribute(name, value = false) {
17
17
  return value === true ? ` ${name}` : '';
18
18
  }
19
19
  /**
@@ -0,0 +1,21 @@
1
+ import { type ToolDescriptor } from '@jini-ai/core';
2
+ type ApprovalText = (required: {
3
+ key: string;
4
+ }) => string;
5
+ type ApprovalDescriptor = Pick<ToolDescriptor, 'id' | 'metadata' | 'inputSchema'>;
6
+ /** Derive approval copy from the registered catalog, with the shared humanized-key fallback.
7
+ * The host translates copy; supplied input is never evaluated as a template or action metadata.
8
+ * @returns A question title, declared summary, confirm label and structured detail labels.
9
+ * @complexity O(n * depth + k) time/space in schema paths and tool-name bytes; no effects. */
10
+ export declare function toolApprovalPresentation({ descriptor }: {
11
+ descriptor: ApprovalDescriptor;
12
+ }, { text }: {
13
+ text: ApprovalText;
14
+ }): {
15
+ title: string;
16
+ summary?: string;
17
+ confirmLabel: string;
18
+ labels: Readonly<Record<string, string>>;
19
+ };
20
+ export {};
21
+ //# sourceMappingURL=tool-approval.d.ts.map
@@ -0,0 +1,42 @@
1
+ import { isNonArrayRecord } from '@jini-ai/core';
2
+ import { surfaceDetailLabel } from './structured-details.js';
3
+ function nonBlank({ value }, _options = {}) {
4
+ return typeof value === 'string' ? value.trim() || undefined : undefined;
5
+ }
6
+ function record({ value }, _options = {}) {
7
+ const candidate = { value };
8
+ return isNonArrayRecord(candidate) ? candidate.value : {};
9
+ }
10
+ /** Project inline JSON-schema property titles into the existing dotted-path label contract.
11
+ * Only `title`: `description` is guidance written for the model (often a paragraph), not a label;
12
+ * untitled fields keep the renderer's humanized-key fallback. No reference resolution or validation.
13
+ * @complexity O(n * depth) time and space in property-path bytes; no effects or input mutation. */
14
+ function schemaLabels({ schema, path = '' }, { text }) {
15
+ const properties = record({ value: record({ value: schema }).properties });
16
+ return Object.fromEntries(Object.entries(properties).flatMap(([key, property]) => {
17
+ const field = record({ value: property });
18
+ const fieldPath = path ? `${path}.${key}` : key;
19
+ const label = nonBlank({ value: field.title });
20
+ return [
21
+ ...(label ? [[fieldPath, text({ key: label })]] : []),
22
+ ...Object.entries(schemaLabels({ schema: field, path: fieldPath }, { text })),
23
+ ];
24
+ }));
25
+ }
26
+ /** Derive approval copy from the registered catalog, with the shared humanized-key fallback.
27
+ * The host translates copy; supplied input is never evaluated as a template or action metadata.
28
+ * @returns A question title, declared summary, confirm label and structured detail labels.
29
+ * @complexity O(n * depth + k) time/space in schema paths and tool-name bytes; no effects. */
30
+ export function toolApprovalPresentation({ descriptor }, { text }) {
31
+ const fallback = surfaceDetailLabel({ key: descriptor.id.replace(/\./g, ' '), path: '', labels: {} });
32
+ const action = text({ key: nonBlank({ value: descriptor.metadata?.title }) ?? fallback });
33
+ const title = /[??؟]$/.test(action) ? action : text({ key: '{action}?' }).replace('{action}', () => action);
34
+ const summary = nonBlank({ value: descriptor.metadata?.summary });
35
+ return {
36
+ title,
37
+ ...(summary ? { summary: text({ key: summary }) } : {}),
38
+ confirmLabel: text({ key: nonBlank({ value: descriptor.metadata?.actionVerb }) ?? 'Confirm' }),
39
+ labels: schemaLabels({ schema: descriptor.inputSchema }, { text }),
40
+ };
41
+ }
42
+ //# sourceMappingURL=tool-approval.js.map
@@ -201,7 +201,8 @@ export function useMediaProvidersTab({ port, catalogPolicy, initialProviders })
201
201
  })();
202
202
  }, [flushNow]);
203
203
  const fetchAndReconcile = useCallback((migrateOnFirstUpload) => {
204
- const ticket = ++loadTicket.current;
204
+ loadTicket.current += 1;
205
+ const ticket = loadTicket.current;
205
206
  const isCurrent = () => alive.current && loadTicket.current === ticket;
206
207
  // Captured for the MIGRATION decision only — `shouldSyncLocalProvidersToDaemon` asks "did the
207
208
  // operator already have local data before this fetch answered", which is a question about the
@@ -1,7 +1,10 @@
1
1
  export function createAsyncCommitGuard() {
2
2
  let currentRevision = 0;
3
3
  return {
4
- begin: () => ++currentRevision,
4
+ begin: () => {
5
+ currentRevision += 1;
6
+ return currentRevision;
7
+ },
5
8
  capture: () => currentRevision,
6
9
  invalidate: () => {
7
10
  currentRevision += 1;
@@ -10,6 +10,10 @@
10
10
  // translated copy is product content); every `t(...)` call below uses a
11
11
  // plain-English default instead.
12
12
  import { connectorAppLabel } from './constants.js';
13
+ const KIND_LABEL_KEYS = {
14
+ heuristic: 'Heuristic',
15
+ connector: 'Connected apps',
16
+ };
13
17
  export function describeConnectorReadIssue(result) {
14
18
  const failed = result.connectors.filter((connector) => connector.status === 'failed');
15
19
  const skipped = result.connectors.filter((connector) => connector.status === 'skipped');
@@ -212,7 +216,7 @@ export function describeRecord(record, t) {
212
216
  // that was the only writer at the time, so labelling them as such
213
217
  // keeps the history list legible after upgrading.
214
218
  const kind = record.kind ?? 'llm';
215
- const kindLabel = kind === 'heuristic' ? t('Heuristic') : kind === 'connector' ? t('Connected apps') : t('Model');
219
+ const kindLabel = t(KIND_LABEL_KEYS[kind] ?? 'Model');
216
220
  return { phaseLabel, reasonLabel, kindLabel, tone };
217
221
  }
218
222
  export function formatRelativeTime(at, now) {
@@ -22,7 +22,9 @@ function connectorRowState({ connector, context }) {
22
22
  const connecting = context.connectingConnectorIds.has(connector.id);
23
23
  const authorizationPending = context.pendingConnectorAuthIds.has(connector.id);
24
24
  const connectError = context.connectorConnectErrors[connector.id];
25
- const checkingStatus = context.connectorsLoading && !isStatusResolved({ connector, context }) && !connected && !authorizationPending && !connectError && !connecting;
25
+ const statusUnresolved = context.connectorsLoading && !isStatusResolved({ connector, context });
26
+ const idle = !connected && !authorizationPending && !connectError && !connecting;
27
+ const checkingStatus = statusUnresolved && idle;
26
28
  return {
27
29
  connected,
28
30
  selected: context.selectedConnectorIds.has(connector.id) && connected,
@@ -13,6 +13,6 @@ import { useT } from '../../../i18n/index.js';
13
13
  import { renderMarkdown } from '../render-markdown.js';
14
14
  export function MemoryEntryCard({ entry, previewId, previewBody, onOpenPreview, onStartEdit, onDelete, agentHandle, }) {
15
15
  const t = useT();
16
- return (_jsxs("div", { className: "library-card", children: [_jsxs("div", { className: "library-card-info", children: [_jsx("div", { className: "library-card-title-row", children: _jsx("span", { className: "library-card-name", children: entry.name }) }), _jsx("div", { className: "library-card-desc", children: entry.description || '—' })] }), _jsxs("div", { className: "memory-card-actions", children: [_jsx("button", { type: "button", className: "library-card-expand", onClick: () => onOpenPreview(entry.id), title: t('Preview'), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'preview', role: 'button', label: t('Preview') }) }), children: _jsx(Icon, { name: previewId === entry.id ? 'chevron-down' : 'chevron-right', size: 14 }) }), _jsx("button", { type: "button", className: "ghost library-card-action", onClick: () => onStartEdit(entry.id), title: t('Edit'), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'edit', role: 'button', label: t('Edit') }) }), children: _jsx(Icon, { name: "edit", size: 14 }) }), _jsx("button", { type: "button", className: "ghost library-card-action", onClick: () => onDelete(entry.id), title: t('Delete'), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'delete', role: 'button', label: t('Delete') }) }), children: _jsx(Icon, { name: "close", size: 14 }) })] }), previewId === entry.id && (_jsx("div", { className: "library-preview", style: { width: '100%' }, children: previewBody === null ? (_jsx("p", { children: t('Loading…') })) : previewBody ? (_jsx("div", { className: "library-preview-body", children: renderMarkdown(previewBody) })) : (_jsx("p", { className: "hint", children: "\u2014" })) }))] }));
16
+ return (_jsxs("div", { className: "library-card", children: [_jsxs("div", { className: "library-card-info", children: [_jsx("div", { className: "library-card-title-row", children: _jsx("span", { className: "library-card-name", children: entry.name }) }), _jsx("div", { className: "library-card-desc", children: entry.description || '—' })] }), _jsxs("div", { className: "memory-card-actions", children: [_jsx("button", { type: "button", className: "library-card-expand", onClick: () => onOpenPreview(entry.id), title: t('Preview'), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'preview', role: 'button', label: t('Preview') }) }), children: _jsx(Icon, { name: previewId === entry.id ? 'chevron-down' : 'chevron-right', size: 14 }) }), _jsx("button", { type: "button", className: "ghost library-card-action", onClick: () => onStartEdit(entry.id), title: t('Edit'), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'edit', role: 'button', label: t('Edit') }) }), children: _jsx(Icon, { name: "edit", size: 14 }) }), _jsx("button", { type: "button", className: "ghost library-card-action", onClick: () => onDelete(entry.id), title: t('Delete'), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'delete', role: 'button', label: t('Delete') }) }), children: _jsx(Icon, { name: "close", size: 14 }) })] }), previewId === entry.id && (_jsxs("div", { className: "library-preview", style: { width: '100%' }, children: [previewBody === null ? _jsx("p", { children: t('Loading…') }) : null, previewBody ? _jsx("div", { className: "library-preview-body", children: renderMarkdown(previewBody) }) : null, previewBody === '' ? _jsx("p", { className: "hint", children: "\u2014" }) : null] }))] }));
17
17
  }
18
18
  //# sourceMappingURL=MemoryEntryCard.js.map
@@ -26,6 +26,8 @@ export function MemorySettingsPanel({ enabled, onToggleEnabled, topTab, onTopTab
26
26
  // Scroll target for the saved-memory section; nothing outside this panel
27
27
  // needs to reach it, so it's owned here rather than threaded through props.
28
28
  const sectionRef = useRef(null);
29
- return (_jsxs("div", { className: "memory-settings-panel", children: [_jsxs("section", { className: "memory-panel-head-card", children: [_jsxs("div", { className: "memory-panel-head-copy", children: [_jsx("h4", { children: t('Memory') }), _jsx("p", { className: "hint", children: t('Control whether saved facts, preferences, and project context are reused in future chats.') })] }), _jsxs("div", { className: "memory-panel-head-controls", children: [_jsxs("div", { className: "jini-seg-control memory-panel-seg", role: "tablist", "aria-label": t('Memory view'), style: { '--seg-cols': 2 }, children: [_jsx("button", { type: "button", role: "tab", "aria-selected": topTab === 'memories', className: 'jini-seg-btn' + (topTab === 'memories' ? ' active' : ''), onClick: () => onTopTabChange('memories'), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'view-memories', role: 'button', label: t('Memories') }) }), children: _jsx("span", { className: "jini-seg-title", children: t('Memories') }) }), _jsx("button", { type: "button", role: "tab", "aria-selected": topTab === 'how', className: 'jini-seg-btn' + (topTab === 'how' ? ' active' : ''), onClick: () => onTopTabChange('how'), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'view-how', role: 'button', label: t('How it works') }) }), children: _jsx("span", { className: "jini-seg-title", children: t('How it works') }) })] }), _jsx("button", { type: "button", className: "memory-icon-btn", onClick: onAdd, disabled: !onAdd, "aria-label": t('Add memory'), title: t('Add memory'), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'add', role: 'button', label: t('Add memory') }) }), children: _jsx(Icon, { name: "plus", size: 14 }) }), _jsx("button", { type: "button", className: "memory-icon-btn", onClick: onOpenAdvanced, disabled: !onOpenAdvanced, "aria-label": t('Advanced'), title: t('Advanced'), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'advanced', role: 'button', label: t('Advanced') }) }), children: _jsx(Icon, { name: "settings", size: 14 }) }), _jsxs("label", { className: "toggle-switch memory-panel-master-toggle", title: enabled ? t('On') : t('Off'), children: [_jsx("input", { type: "checkbox", "aria-label": t('Enable memory'), checked: enabled, onChange: (event) => onToggleEnabled(event.target.checked), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'enabled', role: 'checkbox', label: t('Enable memory') }) }) }), _jsx("span", { className: "toggle-slider" })] })] })] }), topTab === 'memories' ? (_jsx(MemoryList, { sectionRef: sectionRef, ...savedMemory, ...(agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'saved' }) } : {}) })) : (_jsx(MemoryHowPanel, { ...howItWorks, ...(agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'how' }) } : {}) }))] }));
29
+ const savedHandleProps = agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'saved' }) } : {};
30
+ const howHandleProps = agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'how' }) } : {};
31
+ return (_jsxs("div", { className: "memory-settings-panel", children: [_jsxs("section", { className: "memory-panel-head-card", children: [_jsxs("div", { className: "memory-panel-head-copy", children: [_jsx("h4", { children: t('Memory') }), _jsx("p", { className: "hint", children: t('Control whether saved facts, preferences, and project context are reused in future chats.') })] }), _jsxs("div", { className: "memory-panel-head-controls", children: [_jsxs("div", { className: "jini-seg-control memory-panel-seg", role: "tablist", "aria-label": t('Memory view'), style: { '--seg-cols': 2 }, children: [_jsx("button", { type: "button", role: "tab", "aria-selected": topTab === 'memories', className: 'jini-seg-btn' + (topTab === 'memories' ? ' active' : ''), onClick: () => onTopTabChange('memories'), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'view-memories', role: 'button', label: t('Memories') }) }), children: _jsx("span", { className: "jini-seg-title", children: t('Memories') }) }), _jsx("button", { type: "button", role: "tab", "aria-selected": topTab === 'how', className: 'jini-seg-btn' + (topTab === 'how' ? ' active' : ''), onClick: () => onTopTabChange('how'), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'view-how', role: 'button', label: t('How it works') }) }), children: _jsx("span", { className: "jini-seg-title", children: t('How it works') }) })] }), _jsx("button", { type: "button", className: "memory-icon-btn", onClick: onAdd, disabled: !onAdd, "aria-label": t('Add memory'), title: t('Add memory'), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'add', role: 'button', label: t('Add memory') }) }), children: _jsx(Icon, { name: "plus", size: 14 }) }), _jsx("button", { type: "button", className: "memory-icon-btn", onClick: onOpenAdvanced, disabled: !onOpenAdvanced, "aria-label": t('Advanced'), title: t('Advanced'), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'advanced', role: 'button', label: t('Advanced') }) }), children: _jsx(Icon, { name: "settings", size: 14 }) }), _jsxs("label", { className: "toggle-switch memory-panel-master-toggle", title: enabled ? t('On') : t('Off'), children: [_jsx("input", { type: "checkbox", "aria-label": t('Enable memory'), checked: enabled, onChange: (event) => onToggleEnabled(event.target.checked), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'enabled', role: 'checkbox', label: t('Enable memory') }) }) }), _jsx("span", { className: "toggle-slider" })] })] })] }), topTab === 'memories' ? (_jsx(MemoryList, { sectionRef: sectionRef, ...savedMemory, ...savedHandleProps })) : (_jsx(MemoryHowPanel, { ...howItWorks, ...howHandleProps }))] }));
30
32
  }
31
33
  //# sourceMappingURL=MemorySettingsPanel.js.map
@@ -61,12 +61,12 @@ function enqueueConfigWrite(queue, value, send, onSettled) {
61
61
  thrown = error;
62
62
  }
63
63
  onSettled(ok, desired, queue.pending !== null);
64
- for (const settler of settlers) {
64
+ settlers.forEach(settler => {
65
65
  if (didThrow)
66
66
  settler.reject(thrown);
67
67
  else
68
68
  settler.resolve();
69
- }
69
+ });
70
70
  }
71
71
  }
72
72
  finally {
@@ -159,11 +159,12 @@ export function useMemoryConfig(port) {
159
159
  enabledConfirmedRef.current = value;
160
160
  if (!hasNewerIntent)
161
161
  setError(null);
162
+ return;
162
163
  }
163
- else if (!hasNewerIntent) {
164
- setEnabled(enabledConfirmedRef.current);
165
- setError(CONFIG_SAVE_ERROR_MESSAGE);
166
- }
164
+ if (hasNewerIntent)
165
+ return;
166
+ setEnabled(enabledConfirmedRef.current);
167
+ setError(CONFIG_SAVE_ERROR_MESSAGE);
167
168
  });
168
169
  }, [port]);
169
170
  // Map each hook key to its setter so a single optimistic-set + rollback path
@@ -186,11 +187,12 @@ export function useMemoryConfig(port) {
186
187
  hookConfirmedRef.current[key] = value;
187
188
  if (!hasNewerIntent)
188
189
  setError(null);
190
+ return;
189
191
  }
190
- else if (!hasNewerIntent) {
191
- setter(() => hookConfirmedRef.current[key]);
192
- setError(CONFIG_SAVE_ERROR_MESSAGE);
193
- }
192
+ if (hasNewerIntent)
193
+ return;
194
+ setter(() => hookConfirmedRef.current[key]);
195
+ setError(CONFIG_SAVE_ERROR_MESSAGE);
194
196
  });
195
197
  }, [port, setters]);
196
198
  const hookFlags = useMemo(() => ({
@@ -246,11 +246,8 @@ export function useMemoryConnectors(port, coord) {
246
246
  const connectedMemoryConnectors = useMemo(() => memoryConnectors.filter((connector) => connector.status === 'connected'), [memoryConnectors]);
247
247
  const selectedConnectedConnectorIds = useMemo(() => [...selectedConnectorIds].filter((id) => connectedMemoryConnectors.some((connector) => connector.id === id)), [selectedConnectorIds, connectedMemoryConnectors]);
248
248
  const connectedCount = connectedMemoryConnectors.length;
249
- const connectorScanLabel = connectorExtracting
250
- ? 'Scanning apps'
251
- : selectedConnectedConnectorIds.length === 0
252
- ? 'Select apps to scan'
253
- : 'Scan selected apps';
249
+ const idleScanLabel = selectedConnectedConnectorIds.length === 0 ? 'Select apps to scan' : 'Scan selected apps';
250
+ const connectorScanLabel = connectorExtracting ? 'Scanning apps' : idleScanLabel;
254
251
  const selectedConnectorSuggestions = useMemo(() => connectorSuggestions.filter((suggestion) => selectedSuggestionIds.has(suggestion.id)), [connectorSuggestions, selectedSuggestionIds]);
255
252
  const connectorLoadError = connectorCatalogueLoadError ?? connectorStatusLoadError;
256
253
  // Prune selection down to still-connected apps: disconnecting an app should
@@ -68,14 +68,15 @@ export function isRecentFailedConnectorExtraction(record, scanStartedAt) {
68
68
  return record.kind === 'connector' && record.phase === 'failed' && record.startedAt >= scanStartedAt - 5_000;
69
69
  }
70
70
  export function describeConnectorSuggestionsSaveOutcome({ savedCount, totalCount, failure, }) {
71
- const status = savedCount > 0 ? `Saved ${savedCount} memor${savedCount === 1 ? 'y' : 'ies'} from connected apps.` : null;
72
- const error = failure
73
- ? failure instanceof Error
74
- ? failure.message
75
- : String(failure)
76
- : savedCount !== totalCount
77
- ? `Saved ${savedCount} of ${totalCount} selected memories. Please try the remaining items again.`
78
- : null;
79
- return { status, error };
71
+ const noun = savedCount === 1 ? 'memory' : 'memories';
72
+ const status = savedCount > 0 ? `Saved ${savedCount} ${noun} from connected apps.` : null;
73
+ return { status, error: connectorSaveError({ savedCount, totalCount, failure }) };
74
+ }
75
+ function connectorSaveError({ savedCount, totalCount, failure }) {
76
+ if (failure)
77
+ return failure instanceof Error ? failure.message : String(failure);
78
+ return savedCount !== totalCount
79
+ ? `Saved ${savedCount} of ${totalCount} selected memories. Please try the remaining items again.`
80
+ : null;
80
81
  }
81
82
  //# sourceMappingURL=useMemoryConnectors.rules.js.map
@@ -148,7 +148,8 @@ export function useMemoryEntries(port, coord) {
148
148
  setPreviewBody(null);
149
149
  return;
150
150
  }
151
- const token = ++previewRequestTokenRef.current;
151
+ previewRequestTokenRef.current += 1;
152
+ const token = previewRequestTokenRef.current;
152
153
  setPreviewId(id);
153
154
  setPreviewBody(null);
154
155
  let entry;
@@ -173,7 +174,8 @@ export function useMemoryEntries(port, coord) {
173
174
  setPreviewBody(entry?.body ?? '');
174
175
  }, [previewId, port]);
175
176
  const startEdit = useCallback(async (id) => {
176
- const token = ++editRequestTokenRef.current;
177
+ editRequestTokenRef.current += 1;
178
+ const token = editRequestTokenRef.current;
177
179
  let entry;
178
180
  try {
179
181
  entry = await port.fetchMemoryEntry(id);
@@ -15,7 +15,7 @@ export function useMemoryFlash() {
15
15
  }, []);
16
16
  useEffect(() => {
17
17
  if (!flash)
18
- return;
18
+ return undefined;
19
19
  const id = setTimeout(() => setFlash(null), 1800);
20
20
  return () => clearTimeout(id);
21
21
  }, [flash]);
@@ -16,6 +16,7 @@
16
16
  // stay here: the tool-cursor-aware single-connector merge this slice's
17
17
  // discovery hydration needs, and the "mark pending authorization" transform.
18
18
  import { applyConnectorStatuses, mergeConnectors } from '../connectors/rules.js';
19
+ import { omitKeys } from '../../utils/omit-keys.js';
19
20
  /** Patch body for flipping the master memory switch. */
20
21
  export function enabledPatch(enabled) {
21
22
  return { enabled };
@@ -64,7 +65,7 @@ export function applyMemoryConnectorStatus(connector, status) {
64
65
  * tracks pending state separately rather than mutating the connector's
65
66
  * displayed status, so this transform is genuinely local to this slice. */
66
67
  export function connectorWithPendingAuthorization(connector) {
67
- const { accountLabel: _accountLabel, lastError: _lastError, ...base } = connector;
68
+ const base = omitKeys(connector, ['accountLabel', 'lastError']);
68
69
  return {
69
70
  ...base,
70
71
  status: base.status === 'disabled' ? 'disabled' : 'available',
@@ -59,7 +59,7 @@ export function useFetchQuery(required, optional = {}) {
59
59
  const fetch = useCallback(() => latest.current.fetch(), []);
60
60
  useEffect(() => {
61
61
  if (!enabled)
62
- return;
62
+ return undefined;
63
63
  const release = cache.observe({ entry: entry, observer: { fetch, staleTime, focus: refetchOnWindowFocus } });
64
64
  void cache.load({ entry: entry, fetch: fetch, staleTime: staleTime });
65
65
  return release;
@@ -84,7 +84,8 @@ export function useFetchMutation(required, optional = {}) {
84
84
  useEffect(() => { mounted.current = true; return () => { mounted.current = false; }; }, []);
85
85
  const [state, setState] = useState({ status: 'idle', error: null });
86
86
  const mutate = useCallback(({ input }) => {
87
- const current = ++generation.current;
87
+ generation.current += 1;
88
+ const current = generation.current;
88
89
  const { run, invalidates } = latest.current;
89
90
  setState({ status: 'pending', error: null });
90
91
  // Surface a rejected write immediately instead of silently retrying authorization or