@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
@@ -136,12 +136,15 @@ export function exportedImageFileName(fileName, sourceExtension) {
136
136
  const slash = fileName.lastIndexOf('/');
137
137
  const baseName = slash >= 0 ? fileName.slice(slash + 1) : fileName;
138
138
  const stem = typeof sourceExtension === 'string'
139
- ? baseName.toLowerCase().endsWith(sourceExtension.toLowerCase())
140
- ? baseName.slice(0, baseName.length - sourceExtension.length)
141
- : baseName
139
+ ? stripKnownExtension(baseName, sourceExtension)
142
140
  : baseName.replace(/\.[^./]+$/, '');
143
141
  return `${stem || 'sketch'}.png`;
144
142
  }
143
+ function stripKnownExtension(baseName, extension) {
144
+ return baseName.toLowerCase().endsWith(extension.toLowerCase())
145
+ ? baseName.slice(0, baseName.length - extension.length)
146
+ : baseName;
147
+ }
145
148
  export function exportedImageResultFileName(result, fallback) {
146
149
  if (result && typeof result === 'object' && typeof result.fileName === 'string') {
147
150
  return result.fileName;
@@ -197,15 +200,13 @@ export function buildSketchTooltipLabels(t) {
197
200
  * in `overrides` matches (caller should leave the node untouched).
198
201
  */
199
202
  export function translateDomTextValue(value, overrides) {
200
- // Every group in this pattern is quantified with `*` (zero-or-more), so it
201
- // matches every string input, including `''` — `.match()` can never return
202
- // null here, and each capture group is always defined (possibly empty).
203
- // The nullable return/`| undefined` group types are just `RegExp.match`'s
204
- // and `RegExpMatchArray`'s general signatures, not a real runtime path.
205
- const match = value.match(/^(\s*)([\s\S]*?)(\s*)$/);
206
- const leading = match[1];
207
- const core = match[2];
208
- const trailing = match[3];
203
+ // Split into leading whitespace, core, trailing whitespace (an all-whitespace
204
+ // value is all leading). `trimStart`/`trimEnd` strip exactly regex `\s`.
205
+ const coreStart = value.length - value.trimStart().length;
206
+ const leading = value.slice(0, coreStart);
207
+ const rest = value.slice(coreStart);
208
+ const core = rest.trimEnd();
209
+ const trailing = rest.slice(core.length);
209
210
  const normalized = core.replace(/\s+/g, ' ').trim();
210
211
  if (!normalized)
211
212
  return null;
@@ -1,3 +1,4 @@
1
+ import { omitKeys } from '../../utils/omit-keys.js';
1
2
  const DEFAULT_FAKE_SKILLS = [
2
3
  {
3
4
  id: 'skill-summarize',
@@ -10,9 +11,13 @@ const DEFAULT_FAKE_SKILLS = [
10
11
  },
11
12
  ];
12
13
  let nextFakeId = 1;
14
+ function takeFakeSkillId() {
15
+ const id = `fake-skill-${nextFakeId}`;
16
+ nextFakeId += 1;
17
+ return id;
18
+ }
13
19
  function toSummary(detail) {
14
- const { body: _body, ...summary } = detail;
15
- return summary;
20
+ return omitKeys(detail, ['body']);
16
21
  }
17
22
  /**
18
23
  * An in-memory test/demo double. Per this package's established convention
@@ -39,7 +44,7 @@ export function createFakeSkillsPort(options = {}) {
39
44
  return delay([...(files.get(id) ?? [])]);
40
45
  },
41
46
  createSkill(payload) {
42
- const id = `fake-skill-${nextFakeId++}`;
47
+ const id = takeFakeSkillId();
43
48
  const detail = {
44
49
  id,
45
50
  name: payload.name,
@@ -58,7 +63,7 @@ export function createFakeSkillsPort(options = {}) {
58
63
  return Promise.reject(new Error(`Unknown skill: ${id}`));
59
64
  // A built-in edit writes a NEW user-owned shadow copy, same as the
60
65
  // origin — the requested id is never mutated in place.
61
- const targetId = existing.source === 'user' ? id : `fake-skill-${nextFakeId++}`;
66
+ const targetId = existing.source === 'user' ? id : takeFakeSkillId();
62
67
  const detail = {
63
68
  ...existing,
64
69
  id: targetId,
@@ -18,7 +18,8 @@ export function SkillRow({ skill, locale, enabled, expanded, editing, body, body
18
18
  // Built-in skills are never edited in place — "editing" one creates a
19
19
  // user-owned override, so every edit affordance says so.
20
20
  const editActionLabel = builtIn ? l.overrideCreateLabel : l.editLabel;
21
- return (_jsxs("div", { className: skillRowClassName({ enabled, expanded }), "data-testid": `skill-row-${skill.id}`, children: [_jsxs("div", { className: "jini-skills-row-head", children: [_jsx(SkillRowSummaryButton, { skill: skill, locale: locale, expanded: expanded, onToggleExpanded: onToggleExpanded, labels: l, agentHandle: agentHandle }), _jsxs("div", { className: "jini-skills-row-actions", children: [isDeletableSkill(skill) && confirmDelete ? (_jsx(SkillRowDeleteConfirm, { onCommitDelete: onCommitDelete, onCancelDelete: onCancelDelete, labels: l, agentHandle: agentHandle })) : (_jsx(SkillRowEditDeleteActions, { skill: skill, editActionLabel: editActionLabel, onStartEdit: onStartEdit, onArmDelete: onArmDelete, labels: l, agentHandle: agentHandle })), _jsxs("label", { className: "toggle-switch toggle-switch-sm jini-skills-row-enable", title: l.enableToggleLabel, children: [_jsx("input", { type: "checkbox", checked: enabled, onChange: (event) => onToggleEnabled(event.target.checked), "aria-label": l.enableToggleLabel, ...agentHandleProps({}, { base: agentHandle, action: 'enabled', role: 'checkbox', label: l.enableToggleLabel }) }), _jsx("span", { className: "toggle-slider" })] })] })] }), confirmBuiltInEdit ? (_jsx(SkillRowBuiltInEditWarning, { onCancelBuiltInEdit: onCancelBuiltInEdit, onConfirmBuiltInEdit: onConfirmBuiltInEdit, labels: l, agentHandle: agentHandle })) : null, expanded && !editing ? (_jsx(SkillRowDetail, { body: body, bodyLoading: bodyLoading, files: files, filesLoading: filesLoading, labels: l })) : null, editing && draft ? (_jsx(SkillDraftForm, { heading: editActionLabel, subheading: skill.id, draft: draft, onDraftChange: onDraftChange, error: draftError, saving: draftSaving, isEdit: true, isBuiltInOverride: builtIn, onCancel: onCancelEdit, onSubmit: onSubmitEdit, labels: labels, ...(agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'edit-form' }) } : {}) })) : null] }));
21
+ const editFormHandleProps = agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'edit-form' }) } : {};
22
+ return (_jsxs("div", { className: skillRowClassName({ enabled, expanded }), "data-testid": `skill-row-${skill.id}`, children: [_jsxs("div", { className: "jini-skills-row-head", children: [_jsx(SkillRowSummaryButton, { skill: skill, locale: locale, expanded: expanded, onToggleExpanded: onToggleExpanded, labels: l, agentHandle: agentHandle }), _jsxs("div", { className: "jini-skills-row-actions", children: [isDeletableSkill(skill) && confirmDelete ? (_jsx(SkillRowDeleteConfirm, { onCommitDelete: onCommitDelete, onCancelDelete: onCancelDelete, labels: l, agentHandle: agentHandle })) : (_jsx(SkillRowEditDeleteActions, { skill: skill, editActionLabel: editActionLabel, onStartEdit: onStartEdit, onArmDelete: onArmDelete, labels: l, agentHandle: agentHandle })), _jsxs("label", { className: "toggle-switch toggle-switch-sm jini-skills-row-enable", title: l.enableToggleLabel, children: [_jsx("input", { type: "checkbox", checked: enabled, onChange: (event) => onToggleEnabled(event.target.checked), "aria-label": l.enableToggleLabel, ...agentHandleProps({}, { base: agentHandle, action: 'enabled', role: 'checkbox', label: l.enableToggleLabel }) }), _jsx("span", { className: "toggle-slider" })] })] })] }), confirmBuiltInEdit ? (_jsx(SkillRowBuiltInEditWarning, { onCancelBuiltInEdit: onCancelBuiltInEdit, onConfirmBuiltInEdit: onConfirmBuiltInEdit, labels: l, agentHandle: agentHandle })) : null, expanded && !editing ? (_jsx(SkillRowDetail, { body: body, bodyLoading: bodyLoading, files: files, filesLoading: filesLoading, labels: l })) : null, editing && draft ? (_jsx(SkillDraftForm, { heading: editActionLabel, subheading: skill.id, draft: draft, onDraftChange: onDraftChange, error: draftError, saving: draftSaving, isEdit: true, isBuiltInOverride: builtIn, onCancel: onCancelEdit, onSubmit: onSubmitEdit, labels: labels, ...editFormHandleProps })) : null] }));
22
23
  }
23
24
  function skillRowClassName({ enabled, expanded }) {
24
25
  return `jini-skills-row${enabled ? '' : ' jini-skills-row-disabled'}${expanded ? ' jini-skills-row-expanded' : ''}`;
@@ -21,7 +21,8 @@ export function SkillsTab({ port, disabledSkillIds, onToggleEnabled, locale = 'e
21
21
  const tab = useSkillsTab({ port, locale });
22
22
  const l = resolveLabels({ defaults: DEFAULT_SKILLS_TAB_LABELS, t }, { labels });
23
23
  const loadErrorLabel = labels?.loadErrorLabel ?? t('Could not load skills: {error}', { error: tab.loadError ?? '' });
24
- return (_jsxs("section", { className: "jini-settings-section jini-settings-skills", children: [_jsx(SkillsTabToolbar, { tab: tab, labels: l, agentHandle: agentHandle }), tab.loadError ? (_jsx("div", { className: "jini-empty-card jini-empty-card-error", children: _jsx("strong", { children: loadErrorLabel }) })) : null, tab.creating ? (_jsx(SkillDraftForm, { heading: l.newSkillLabel, draft: tab.draft, onDraftChange: tab.setDraft, error: tab.draftError, saving: tab.draftSaving, isEdit: false, onCancel: tab.cancelDraft, onSubmit: tab.submitDraft, labels: labels, ...(agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'create-form' }) } : {}) })) : null, !tab.loading && tab.filteredSkills.length === 0 ? (_jsx("div", { className: "jini-empty-card", children: _jsx("strong", { children: l.noResultsLabel }) })) : (_jsx(SkillsTabRows, { tab: tab, disabledSkillIds: disabledSkillIds, onToggleEnabled: onToggleEnabled, locale: locale, labels: labels, agentHandle: agentHandle }))] }));
24
+ const createFormHandleProps = agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'create-form' }) } : {};
25
+ return (_jsxs("section", { className: "jini-settings-section jini-settings-skills", children: [_jsx(SkillsTabToolbar, { tab: tab, labels: l, agentHandle: agentHandle }), tab.loadError ? (_jsx("div", { className: "jini-empty-card jini-empty-card-error", children: _jsx("strong", { children: loadErrorLabel }) })) : null, tab.creating ? (_jsx(SkillDraftForm, { heading: l.newSkillLabel, draft: tab.draft, onDraftChange: tab.setDraft, error: tab.draftError, saving: tab.draftSaving, isEdit: false, onCancel: tab.cancelDraft, onSubmit: tab.submitDraft, labels: labels, ...createFormHandleProps })) : null, !tab.loading && tab.filteredSkills.length === 0 ? (_jsx("div", { className: "jini-empty-card", children: _jsx("strong", { children: l.noResultsLabel }) })) : (_jsx(SkillsTabRows, { tab: tab, disabledSkillIds: disabledSkillIds, onToggleEnabled: onToggleEnabled, locale: locale, labels: labels, agentHandle: agentHandle }))] }));
25
26
  }
26
27
  /** Search box, "New skill" button, and the source/mode/(category) selects. */
27
28
  function SkillsTabToolbar({ tab, labels: l, agentHandle, }) {
@@ -146,16 +146,16 @@ const modeValue = (skill) => skill.mode;
146
146
  const categoryValue = (skill) => typeof skill.category === 'string' && skill.category ? skill.category : null;
147
147
  /** The data-derived pills (`mode`, `category`): option SET from every skill,
148
148
  * COUNTS from `matching` — see `skillFilterOptions`. */
149
- function valueFilterOptions({ skills, matching, valueOf, }) {
149
+ function valueFilterOptions({ skills, matching, valueOf: valueFor, }) {
150
150
  const values = new Set();
151
151
  for (const skill of skills) {
152
- const value = valueOf(skill);
152
+ const value = valueFor(skill);
153
153
  if (value !== null)
154
154
  values.add(value);
155
155
  }
156
156
  const counts = new Map();
157
157
  for (const skill of matching) {
158
- const value = valueOf(skill);
158
+ const value = valueFor(skill);
159
159
  if (value === null)
160
160
  continue;
161
161
  counts.set(value, (counts.get(value) ?? 0) + 1);
@@ -39,6 +39,7 @@
39
39
  * why cards are keyed by id rather than list position.
40
40
  */
41
41
  import { agentHandle, buildAgentListHandles } from '@jini-ai/agentic';
42
+ import { trimChars } from '../../utils/trim-chars.js';
42
43
  /** Segments of a derived handle: `<base>-field-<key>` and `<base>-<action>` share one joiner. */
43
44
  const HANDLE_SEPARATOR = '-';
44
45
  /** The namespace every host-described field sits under — see this module's doc for why. */
@@ -65,11 +66,11 @@ const LIST_ITEM_NAMESPACE = 'item';
65
66
  * @complexity O(n) in `segment.length`.
66
67
  */
67
68
  function sanitizeHandleSegment(segment) {
68
- return segment
69
+ const separated = segment
69
70
  .replace(/([a-z0-9])([A-Z])/g, `$1${HANDLE_SEPARATOR}$2`)
70
71
  .toLowerCase()
71
- .replace(/[^a-z0-9]+/g, HANDLE_SEPARATOR)
72
- .replace(/^-+|-+$/g, '');
72
+ .replace(/[^a-z0-9]+/g, HANDLE_SEPARATOR);
73
+ return trimChars(separated, '-');
73
74
  }
74
75
  /**
75
76
  * The handle for one action or sub-control published under `base`.
@@ -122,7 +122,9 @@ function useItemCardEdit({ source, fieldSpecs, onUpdate }) {
122
122
  /** The expanded body: the Edit/Save/Cancel toggle, the editable or read-only fields, and the test control. */
123
123
  function ItemCardBody(props) {
124
124
  const { capabilities, testing, testResult, onTest, agentHandle, edit } = props;
125
- return (_jsxs("div", { className: "source-config-item-card-body", children: [capabilities.canUpdate ? _jsx(ItemCardEditToggle, { ...props, edit: edit }) : null, edit.editing ? _jsx(ItemCardEditFields, { ...props, edit: edit }) : _jsx(ItemCardReadOnlyFields, { ...props }), capabilities.canTest ? (_jsx(SourceConfigTestControl, { running: testing, disabled: isAnyActionPending(props), onTest: onTest, ...(testResult ? { result: testResult } : {}), ...(agentHandle ? { agentHandle: sourceConfigActionHandle(agentHandle, 'test') } : {}) })) : null] }));
125
+ const testResultProps = testResult ? { result: testResult } : {};
126
+ const testHandleProps = agentHandle ? { agentHandle: sourceConfigActionHandle(agentHandle, 'test') } : {};
127
+ return (_jsxs("div", { className: "source-config-item-card-body", children: [capabilities.canUpdate ? _jsx(ItemCardEditToggle, { ...props, edit: edit }) : null, edit.editing ? _jsx(ItemCardEditFields, { ...props, edit: edit }) : _jsx(ItemCardReadOnlyFields, { ...props }), capabilities.canTest ? (_jsx(SourceConfigTestControl, { running: testing, disabled: isAnyActionPending(props), onTest: onTest, ...testResultProps, ...testHandleProps })) : null] }));
126
128
  }
127
129
  function ItemCardEditToggle(props) {
128
130
  const { updating, agentHandle, edit } = props;
@@ -138,9 +138,11 @@ export function useActiveTabInView({ activeId }, _optional = {}) {
138
138
  const tabBox = tab.getBoundingClientRect();
139
139
  // A host's end padding (e.g. room for an edge fade) counts as off-screen.
140
140
  const visibleRight = stripBox.right - (parseFloat(getComputedStyle(strip).paddingRight) || 0);
141
- if (tabBox.left < stripBox.left)
141
+ if (tabBox.left < stripBox.left) {
142
142
  strip.scrollLeft += tabBox.left - stripBox.left;
143
- else if (tabBox.right > visibleRight)
143
+ return;
144
+ }
145
+ if (tabBox.right > visibleRight)
144
146
  strip.scrollLeft += tabBox.right - visibleRight;
145
147
  }
146
148
  scrollActiveTabIntoView();
@@ -11,6 +11,9 @@ export function VersionPreviewFrame({ previewDocument, frameReady, onFrameLoad,
11
11
  const [frameRef, canvasSize] = usePreviewCanvasSize();
12
12
  const preset = findViewportPreset(viewportPresets, viewport);
13
13
  const scale = effectivePreviewScale(preset, 1, canvasSize, DEFAULT_PREVIEW_CANVAS_PADDING);
14
- return (_jsx("div", { className: "jini-version-preview", ref: frameRef, children: error ? (_jsx("div", { className: "jini-viewer-empty", role: "alert", children: error })) : (_jsxs(_Fragment, { children: [previewDocument ? (_jsx("div", { className: "jini-preview-viewport", style: previewViewportStyle(preset, scale, 1), children: _jsx("div", { className: "jini-preview-frame-clip", children: _jsx("div", { style: previewScaleShellStyle(preset, 1), children: _jsx("iframe", { title: title, sandbox: "allow-scripts allow-downloads", srcDoc: previewDocument, onLoad: onFrameLoad }) }) }) })) : !loading && !loadingContent ? (_jsx("div", { className: "jini-viewer-empty", children: t('No preview available.') })) : null, loading || loadingContent || (previewDocument && !frameReady) ? (_jsx("div", { className: "jini-version-preview-overlay", role: "status", "aria-label": t('Loading preview…'), children: _jsx("span", { className: "jini-version-preview-spinner", "aria-hidden": "true" }) })) : null] })) }));
14
+ const showEmpty = !previewDocument && !loading && !loadingContent;
15
+ const showLoading = loading || loadingContent || (previewDocument && !frameReady);
16
+ const emptyState = showEmpty ? _jsx("div", { className: "jini-viewer-empty", children: t('No preview available.') }) : null;
17
+ return (_jsx("div", { className: "jini-version-preview", ref: frameRef, children: error ? (_jsx("div", { className: "jini-viewer-empty", role: "alert", children: error })) : (_jsxs(_Fragment, { children: [previewDocument ? (_jsx("div", { className: "jini-preview-viewport", style: previewViewportStyle(preset, scale, 1), children: _jsx("div", { className: "jini-preview-frame-clip", children: _jsx("div", { style: previewScaleShellStyle(preset, 1), children: _jsx("iframe", { title: title, sandbox: "allow-scripts allow-downloads", srcDoc: previewDocument, onLoad: onFrameLoad }) }) }) })) : null, emptyState, showLoading ? (_jsx("div", { className: "jini-version-preview-overlay", role: "status", "aria-label": t('Loading preview…'), children: _jsx("span", { className: "jini-version-preview-spinner", "aria-hidden": "true" }) })) : null] })) }));
15
18
  }
16
19
  //# sourceMappingURL=VersionPreviewFrame.js.map
@@ -4,11 +4,13 @@ import { useT } from '../../../i18n/index.js';
4
4
  * independent of whatever the preview pane is doing. */
5
5
  export function VersionSidebar({ countLabel, search, onSearchChange, showSearch, loading, versions, visibleVersions, selectedVersionId, onSelect, onPrefetch, formatDate, sourceLabel, sourceClassName, restoredFrom, }) {
6
6
  const t = useT();
7
- return (_jsxs("div", { className: "jini-version-sidebar", children: [_jsx("div", { className: "jini-version-sidebar-head", children: _jsx("span", { className: "jini-version-count", children: countLabel }) }), showSearch ? (_jsxs("div", { className: "jini-version-search", children: [_jsx("input", { type: "search", value: search, onChange: (event) => onSearchChange(event.target.value), placeholder: t('Search…'), "aria-label": t('Search…') }), search ? (_jsx("button", { type: "button", className: "jini-version-search-clear", "aria-label": t('Clear'), onClick: () => onSearchChange(''), children: t('Clear') })) : null] })) : null, _jsx("div", { className: "jini-version-list", role: "listbox", "aria-label": t('Version history'), children: loading ? (_jsx("div", { className: "jini-version-skeleton-list", role: "status", "aria-label": t('Loading versions…'), children: [0, 1, 2, 3].map((row) => (_jsx("div", { className: "jini-version-skeleton-item", "aria-hidden": "true" }, row))) })) : versions.length === 0 ? (_jsx("div", { className: "jini-version-empty", children: t('No versions yet.') })) : visibleVersions.length === 0 ? (_jsx("div", { className: "jini-version-empty", children: t('No results for “{query}”.', { query: search.trim() }) })) : (visibleVersions.map((version) => {
8
- const selected = version.id === selectedVersionId;
9
- const itemRestoredFrom = restoredFrom(version);
10
- const prefetch = () => onPrefetch(version.id);
11
- return (_jsxs("button", { type: "button", className: `jini-version-item${selected ? ' active' : ''}`, role: "option", "aria-selected": selected, onClick: () => onSelect(version.id), onMouseEnter: prefetch, onFocus: prefetch, children: [_jsxs("span", { className: "jini-version-item-top", children: [version.current ? _jsx("span", { className: "jini-version-current-badge", children: t('Current') }) : null, _jsx("span", { className: `jini-version-source-badge ${sourceClassName(version)}`, children: sourceLabel(version) }), _jsx("span", { className: "jini-version-time", children: formatDate(version) })] }), _jsx("span", { className: "jini-version-item-title", children: version.prompt || version.label || t('Version {version}', { version: version.version }) }), _jsxs("span", { className: "jini-version-item-meta", children: [t('Version {version}', { version: version.version }), itemRestoredFrom ? (_jsx("span", { className: "jini-version-item-restored", children: t('Restored from v{version}', { version: itemRestoredFrom.version }) })) : null] })] }, version.id));
12
- })) })] }));
7
+ const noResultsMessage = visibleVersions.length === 0 ? t('No results for “{query}”.', { query: search.trim() }) : null;
8
+ const emptyMessage = versions.length === 0 ? t('No versions yet.') : noResultsMessage;
9
+ return (_jsxs("div", { className: "jini-version-sidebar", children: [_jsx("div", { className: "jini-version-sidebar-head", children: _jsx("span", { className: "jini-version-count", children: countLabel }) }), showSearch ? (_jsxs("div", { className: "jini-version-search", children: [_jsx("input", { type: "search", value: search, onChange: (event) => onSearchChange(event.target.value), placeholder: t('Search…'), "aria-label": t('Search…') }), search ? (_jsx("button", { type: "button", className: "jini-version-search-clear", "aria-label": t('Clear'), onClick: () => onSearchChange(''), children: t('Clear') })) : null] })) : null, _jsxs("div", { className: "jini-version-list", role: "listbox", "aria-label": t('Version history'), children: [loading ? (_jsx("div", { className: "jini-version-skeleton-list", role: "status", "aria-label": t('Loading versions…'), children: [0, 1, 2, 3].map((row) => (_jsx("div", { className: "jini-version-skeleton-item", "aria-hidden": "true" }, row))) })) : null, !loading && emptyMessage ? _jsx("div", { className: "jini-version-empty", children: emptyMessage }) : null, !loading && !emptyMessage ? (visibleVersions.map((version) => {
10
+ const selected = version.id === selectedVersionId;
11
+ const itemRestoredFrom = restoredFrom(version);
12
+ const prefetch = () => onPrefetch(version.id);
13
+ return (_jsxs("button", { type: "button", className: `jini-version-item${selected ? ' active' : ''}`, role: "option", "aria-selected": selected, onClick: () => onSelect(version.id), onMouseEnter: prefetch, onFocus: prefetch, children: [_jsxs("span", { className: "jini-version-item-top", children: [version.current ? _jsx("span", { className: "jini-version-current-badge", children: t('Current') }) : null, _jsx("span", { className: `jini-version-source-badge ${sourceClassName(version)}`, children: sourceLabel(version) }), _jsx("span", { className: "jini-version-time", children: formatDate(version) })] }), _jsx("span", { className: "jini-version-item-title", children: version.prompt || version.label || t('Version {version}', { version: version.version }) }), _jsxs("span", { className: "jini-version-item-meta", children: [t('Version {version}', { version: version.version }), itemRestoredFrom ? (_jsx("span", { className: "jini-version-item-restored", children: t('Restored from v{version}', { version: itemRestoredFrom.version }) })) : null] })] }, version.id));
14
+ })) : null] })] }));
13
15
  }
14
16
  //# sourceMappingURL=VersionSidebar.js.map
@@ -16,7 +16,7 @@ export function usePreviewCanvasSize() {
16
16
  // dead-branch-gets-refactored-away rule.
17
17
  const el = ref.current;
18
18
  if (!el)
19
- return;
19
+ return undefined;
20
20
  const measure = () => {
21
21
  const rect = el.getBoundingClientRect();
22
22
  setSize({ width: rect.width, height: rect.height });
@@ -108,10 +108,8 @@ export function useVersionManager(dependencies, options) {
108
108
  setVersions(nextVersions);
109
109
  const currentVersion = nextVersions.find((version) => version.current);
110
110
  seedCurrentVersionContent({ currentVersion, currentContent, contentCache: contentCacheRef.current });
111
- const nextSelected = (preferredId ? nextVersions.find((version) => version.id === preferredId) : null) ??
112
- currentVersion ??
113
- nextVersions[0] ??
114
- null;
111
+ const preferredVersion = preferredId ? nextVersions.find((version) => version.id === preferredId) : null;
112
+ const nextSelected = preferredVersion ?? currentVersion ?? nextVersions[0] ?? null;
115
113
  setSelectedId(nextSelected?.id ?? null);
116
114
  setLoading(false);
117
115
  }, [port, fileRef, currentContent]);
@@ -125,7 +123,7 @@ export function useVersionManager(dependencies, options) {
125
123
  if (!selectedId) {
126
124
  setSelectedContent(null);
127
125
  setSelectedContentVersionId(null);
128
- return;
126
+ return undefined;
129
127
  }
130
128
  const cached = contentCacheRef.current.get(selectedId);
131
129
  if (cached !== undefined) {
@@ -138,7 +136,7 @@ export function useVersionManager(dependencies, options) {
138
136
  else {
139
137
  setError(null);
140
138
  }
141
- return;
139
+ return undefined;
142
140
  }
143
141
  // Cache miss: keep the previous preview mounted (don't clear
144
142
  // `selectedContent`) so switching versions never blanks to empty while
@@ -177,7 +175,7 @@ export function useVersionManager(dependencies, options) {
177
175
  // is ever missed.
178
176
  useEffect(() => {
179
177
  if (!previewDocument || loadedPreviewDocument === previewDocument)
180
- return;
178
+ return undefined;
181
179
  const fallback = setTimeout(() => setLoadedPreviewDocument(previewDocument), PREVIEW_LOAD_FALLBACK_MS);
182
180
  return () => clearTimeout(fallback);
183
181
  }, [previewDocument, loadedPreviewDocument]);
@@ -89,11 +89,9 @@ export function filterVersionsBySearch(versions, search, describe) {
89
89
  * current, no restoring mid-fetch, no restoring stale/mismatched content). */
90
90
  export function isRestoreDisabled(input) {
91
91
  const { selectedVersion, restoring, loadingContent, selectedContentMatchesVersion } = input;
92
- return (!selectedVersion ||
93
- selectedVersion.current ||
94
- restoring ||
95
- loadingContent ||
96
- !selectedContentMatchesVersion);
92
+ if (!selectedVersion || selectedVersion.current)
93
+ return true;
94
+ return restoring || loadingContent || !selectedContentMatchesVersion;
97
95
  }
98
96
  /** Whether the currently-loaded preview content actually corresponds to the
99
97
  * currently-selected version (guards against acting on stale content
@@ -113,7 +113,8 @@ function CommentSideSelectBar({ selectedCount, sending, sendDisabled, queueOnSen
113
113
  const t = useT();
114
114
  if (selectedCount === 0)
115
115
  return null;
116
- return (_jsxs("div", { className: "comment-side-selectbar", "data-testid": "comment-side-selectbar", children: [_jsx("span", { className: "comment-side-selectcount", children: t('{n} selected', { n: selectedCount }) }), _jsx("button", { type: "button", className: "comment-side-clear", onClick: onClearSelection, children: t('Clear') }), _jsx("button", { type: "button", className: "comment-side-send", "data-testid": "comment-side-send-claude", disabled: sending || sendDisabled, onClick: () => void onSendSelected(), children: sending ? t('Sending…') : queueOnSend ? t('Queue') : t('Send to chat') })] }));
116
+ const idleSendLabel = queueOnSend ? t('Queue') : t('Send to chat');
117
+ return (_jsxs("div", { className: "comment-side-selectbar", "data-testid": "comment-side-selectbar", children: [_jsx("span", { className: "comment-side-selectcount", children: t('{n} selected', { n: selectedCount }) }), _jsx("button", { type: "button", className: "comment-side-clear", onClick: onClearSelection, children: t('Clear') }), _jsx("button", { type: "button", className: "comment-side-send", "data-testid": "comment-side-send-claude", disabled: sending || sendDisabled, onClick: () => void onSendSelected(), children: sending ? t('Sending…') : idleSendLabel })] }));
117
118
  }
118
119
  function CommentSideCreateForm({ renderCreateForm, draft, onDraftChange, onCreateComment, sending, sendDisabled, }) {
119
120
  const t = useT();
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { RemixIcon } from '../../../../react/components/RemixIcon.js';
3
+ import { withClassName } from '../../../../utils/class-names.js';
3
4
  /**
4
5
  * A small, always-visible group of toggle buttons, one active at a time.
5
6
  * This is the shared primitive behind two call sites that turned out to be
@@ -14,7 +15,7 @@ import { RemixIcon } from '../../../../react/components/RemixIcon.js';
14
15
  * primitives for a minor ARIA-pattern difference.
15
16
  */
16
17
  export function SegmentedToggle({ options, value, onChange, ariaLabel, className, }) {
17
- return (_jsx("div", { className: `viewer-segmented-toggle${className ? ` ${className}` : ''}`, role: "group", "aria-label": ariaLabel, children: options.map((option) => {
18
+ return (_jsx("div", { className: withClassName('viewer-segmented-toggle', className), role: "group", "aria-label": ariaLabel, children: options.map((option) => {
18
19
  const selected = option.value === value;
19
20
  const label = option.title ?? option.label;
20
21
  return (_jsxs("button", { type: "button", className: `viewer-segmented-toggle-button${selected ? ' active' : ''}`, "aria-pressed": selected, "aria-label": label, title: label, onClick: () => onChange(option.value), children: [option.icon ? _jsx(RemixIcon, { name: option.icon, size: 14 }) : null, _jsx("span", { children: option.label })] }, option.value));
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { withClassName } from '../../../../utils/class-names.js';
2
3
  /**
3
4
  * The "viewer-toolbar + viewer-body" chrome independently repeated across
4
5
  * 8 media-viewer components in the source file (`BinaryViewer`,
@@ -9,7 +10,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
10
  * layout wrapper.
10
11
  */
11
12
  export function ViewerShell({ kindClassName, toolbarLeft, toolbarActions, bodyClassName, children }) {
12
- return (_jsxs("div", { className: `viewer${kindClassName ? ` ${kindClassName}` : ''}`, children: [_jsxs("div", { className: "viewer-toolbar", children: [_jsx("div", { className: "viewer-toolbar-left", children: toolbarLeft }), toolbarActions ? _jsx("div", { className: "viewer-toolbar-actions", children: toolbarActions }) : null] }), _jsx("div", { className: `viewer-body${bodyClassName ? ` ${bodyClassName}` : ''}`, children: children })] }));
13
+ return (_jsxs("div", { className: withClassName('viewer', kindClassName), children: [_jsxs("div", { className: "viewer-toolbar", children: [_jsx("div", { className: "viewer-toolbar-left", children: toolbarLeft }), toolbarActions ? _jsx("div", { className: "viewer-toolbar-actions", children: toolbarActions }) : null] }), _jsx("div", { className: withClassName('viewer-body', bodyClassName), children: children })] }));
13
14
  }
14
15
  /** Trivial "nothing to show" placeholder shared by every viewer body. */
15
16
  export function ViewerEmptyState({ children }) {
@@ -97,13 +97,14 @@ export function hasPrecisionSensitiveJsonNumberText(text) {
97
97
  }
98
98
  numberTokenPattern.lastIndex = i;
99
99
  const match = numberTokenPattern.exec(text);
100
- if (!match) {
100
+ if (match) {
101
+ if (isPrecisionSensitiveNumberLiteral(match[0]))
102
+ return true;
103
+ i = numberTokenPattern.lastIndex;
104
+ }
105
+ else {
101
106
  i += 1;
102
- continue;
103
107
  }
104
- if (isPrecisionSensitiveNumberLiteral(match[0]))
105
- return true;
106
- i = numberTokenPattern.lastIndex;
107
108
  }
108
109
  return false;
109
110
  }
@@ -111,10 +112,10 @@ export function hasPrecisionSensitiveJsonNumberText(text) {
111
112
  * `start` (honouring `\` escapes), or `text.length` when it is unterminated. */
112
113
  function indexAfterJsonString(text, start) {
113
114
  for (let i = start; i < text.length; i += 1) {
115
+ if (text[i] === '"')
116
+ return i + 1;
114
117
  if (text[i] === '\\')
115
118
  i += 1;
116
- else if (text[i] === '"')
117
- return i + 1;
118
119
  }
119
120
  return text.length;
120
121
  }
@@ -12,7 +12,6 @@ const ICON_EXT = {
12
12
  gemini: 'svg',
13
13
  opencode: 'svg',
14
14
  'cursor-agent': 'svg',
15
- copilot: 'svg',
16
15
  qwen: 'svg',
17
16
  qoder: 'svg',
18
17
  deepseek: 'svg',
@@ -18,12 +18,20 @@ export function brandMonogram(name) {
18
18
  }
19
19
  /** The stage to start at, given which sources are available. */
20
20
  export function firstLogoStage(opts) {
21
- return opts.canUseBrandStage ? 'brand' : opts.logoSrc ? 'custom' : opts.host ? 'favicon' : 'letter';
21
+ if (opts.canUseBrandStage)
22
+ return 'brand';
23
+ return fallbackLogoStage(opts);
24
+ }
25
+ /** The first non-brand stage whose source is available. */
26
+ function fallbackLogoStage(opts) {
27
+ if (opts.logoSrc)
28
+ return 'custom';
29
+ return opts.host ? 'favicon' : 'letter';
22
30
  }
23
31
  /** The next stage to try after the current one fails to load. */
24
32
  export function advanceLogoStage(stage, opts) {
25
33
  if (stage === 'brand')
26
- return opts.logoSrc ? 'custom' : opts.host ? 'favicon' : 'letter';
34
+ return fallbackLogoStage(opts);
27
35
  if (stage === 'custom')
28
36
  return 'favicon';
29
37
  return 'letter';
@@ -233,7 +233,7 @@ export function useCustomSelectOriginTheme({ buttonRef, open, portal }, _optiona
233
233
  const [attributes, setAttributes] = useState({});
234
234
  useLayoutEffect(() => {
235
235
  if (!open || !portal)
236
- return;
236
+ return undefined;
237
237
  const update = () => {
238
238
  const next = resolveCustomSelectOriginTheme({ trigger: buttonRef.current });
239
239
  setAttributes((current) => current['data-admin-theme'] === next['data-admin-theme']
@@ -36,21 +36,24 @@ const xcodePath = 'M19.06 5.3327c.4517-.1936.7744-.2581 1.097-.1936.5163.1291.77
36
36
  const webstormPath = 'M0 0v24h24V0H0zm17.889 2.889c1.444 0 2.667.444 3.667 1.278l-1.111 1.667c-.889-.611-1.722-1-2.556-1s-1.278.389-1.278.889v.056c0 .667.444.889 2.111 1.333 2 .556 3.111 1.278 3.111 3v.056c0 2-1.5 3.111-3.611 3.111-1.5-.056-3-.611-4.167-1.667l1.278-1.556c.889.722 1.833 1.222 2.944 1.222.889 0 1.389-.333 1.389-.944v-.056c0-.556-.333-.833-2-1.278-2-.5-3.222-1.056-3.222-3.056v-.056c0-1.833 1.444-3 3.444-3zm-16.111.222h2.278l1.5 5.778 1.722-5.778h1.667l1.667 5.778 1.5-5.778h2.333l-2.833 9.944H9.723L8.112 7.277l-1.667 5.778H4.612L1.779 3.111zm.5 16.389h9V21h-9v-1.5z';
37
37
  const ideaPath = 'M0 0v24h24V0zm3.723 3.111h5v1.834h-1.39v6.277h1.39v1.834h-5v-1.834h1.444V4.945H3.723zm11.055 0H17v6.5c0 .612-.055 1.111-.222 1.556-.167.444-.39.777-.723 1.11-.277.279-.666.557-1.11.668a3.933 3.933 0 0 1-1.445.278c-.778 0-1.444-.167-1.944-.445a4.81 4.81 0 0 1-1.279-1.056l1.39-1.555c.277.334.555.555.833.722.277.167.611.278.945.278.389 0 .721-.111 1-.389.221-.278.333-.667.333-1.278zM2.222 19.5h9V21h-9z';
38
38
  const warpPath = 'M12.035 2.723h9.253A2.712 2.712 0 0 1 24 5.435v10.529a2.712 2.712 0 0 1-2.712 2.713H8.047Zm-1.681 2.6L6.766 19.677h5.598l-.399 1.6H2.712A2.712 2.712 0 0 1 0 18.565V8.036a2.712 2.712 0 0 1 2.712-2.712Z';
39
+ const PRIMARY = 'var(--jini-primary)';
40
+ const BG = 'var(--jini-bg)';
41
+ const TEXT = 'var(--jini-text)';
39
42
  const EDITORS = {
40
- vscode: { bg: 'var(--jini-primary)', fg: 'var(--jini-bg)', glyph: vscodeLogo },
41
- cursor: { bg: 'var(--jini-text)', fg: 'var(--jini-bg)', glyph: simplePath(cursorPath) },
42
- windsurf: { bg: 'var(--jini-bg)', fg: 'var(--jini-text)', glyph: simplePath(windsurfPath) },
43
- zed: { bg: 'var(--jini-primary)', fg: 'var(--jini-bg)', glyph: simplePath(zedPath) },
44
- qoder: { bg: 'var(--jini-warning)', fg: 'var(--jini-text)', glyph: qoderLogo },
45
- antigravity: { bg: 'var(--jini-bg)', fg: 'var(--jini-text)', glyph: antigravityLogo },
46
- webstorm: { bg: 'var(--jini-text)', fg: 'var(--jini-bg)', glyph: simplePath(webstormPath) },
47
- idea: { bg: 'var(--jini-text)', fg: 'var(--jini-bg)', glyph: simplePath(ideaPath) },
48
- xcode: { bg: 'var(--jini-primary)', fg: 'var(--jini-bg)', glyph: simplePath(xcodePath) },
49
- finder: { bg: 'var(--jini-primary)', fg: 'var(--jini-bg)', glyph: finderLogo },
50
- explorer: { bg: 'var(--jini-warning)', fg: 'var(--jini-text)', glyph: folderLogo },
51
- 'file-manager': { bg: 'var(--jini-muted)', fg: 'var(--jini-bg)', glyph: folderLogo },
52
- terminal: { bg: 'var(--jini-text)', fg: 'var(--jini-success)', glyph: terminalLogo },
53
- warp: { bg: 'var(--jini-primary)', fg: 'var(--jini-bg)', glyph: simplePath(warpPath) },
43
+ vscode: { bg: PRIMARY, fg: BG, glyph: vscodeLogo },
44
+ cursor: { bg: TEXT, fg: BG, glyph: simplePath(cursorPath) },
45
+ windsurf: { bg: BG, fg: TEXT, glyph: simplePath(windsurfPath) },
46
+ zed: { bg: PRIMARY, fg: BG, glyph: simplePath(zedPath) },
47
+ qoder: { bg: 'var(--jini-warning)', fg: TEXT, glyph: qoderLogo },
48
+ antigravity: { bg: BG, fg: TEXT, glyph: antigravityLogo },
49
+ webstorm: { bg: TEXT, fg: BG, glyph: simplePath(webstormPath) },
50
+ idea: { bg: TEXT, fg: BG, glyph: simplePath(ideaPath) },
51
+ xcode: { bg: PRIMARY, fg: BG, glyph: simplePath(xcodePath) },
52
+ finder: { bg: PRIMARY, fg: BG, glyph: finderLogo },
53
+ explorer: { bg: 'var(--jini-warning)', fg: TEXT, glyph: folderLogo },
54
+ 'file-manager': { bg: 'var(--jini-muted)', fg: BG, glyph: folderLogo },
55
+ terminal: { bg: TEXT, fg: 'var(--jini-success)', glyph: terminalLogo },
56
+ warp: { bg: PRIMARY, fg: BG, glyph: simplePath(warpPath) },
54
57
  };
55
58
  /**
56
59
  * Renders an editor/target glyph by string key using the host's theme, with a
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Icon } from './Icon.js';
3
+ import { withClassName } from '../../utils/class-names.js';
3
4
  export function Spinner({ size = 14, label }) {
4
5
  return (_jsxs("span", { className: "loading-spinner", role: "status", "aria-live": "polite", children: [_jsx(Icon, { name: "spinner", size: size }), label ? _jsx("span", { className: "loading-spinner-label", children: label }) : null] }));
5
6
  }
6
7
  export function Skeleton({ width, height = 14, radius = 6, className }) {
7
- return (_jsx("span", { className: `skeleton-block${className ? ` ${className}` : ''}`, style: { width, height, borderRadius: radius }, "aria-hidden": true }));
8
+ return (_jsx("span", { className: withClassName('skeleton-block', className), style: { width, height, borderRadius: radius }, "aria-hidden": true }));
8
9
  }
9
10
  /**
10
11
  * Card-shaped skeleton for grid layouts (thumbnail area over a row of meta
@@ -1,7 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** A single-select value as a selection list; an empty string selects nothing. */
3
+ function singleSelection(value) {
4
+ return value ? [value] : [];
5
+ }
2
6
  export function OnboardingChipField(props) {
3
7
  const { label, options, className } = props;
4
- const selected = props.multiple ? props.value : props.value ? [props.value] : [];
8
+ const selected = props.multiple ? props.value : singleSelection(props.value);
5
9
  return (_jsxs("div", { className: ['onboarding-chip-field', className].filter(Boolean).join(' '), children: [_jsx("span", { className: "onboarding-chip-field__label", children: label }), _jsx("div", { className: "onboarding-chip-field__chips", children: options.map((option) => {
6
10
  const active = selected.includes(option.value);
7
11
  return (_jsx("button", { type: "button", className: `onboarding-chip${active ? ' is-selected' : ''}`, "aria-pressed": active, onClick: () => {
@@ -51,7 +51,9 @@ export const ONBOARDING_MENU_FALLBACK_VIEWPORT = 720;
51
51
  * string (or otherwise falsy value) becomes an empty selection.
52
52
  */
53
53
  export function resolveOnboardingSelectedValues(value) {
54
- return Array.isArray(value) ? value : value ? [value] : [];
54
+ if (Array.isArray(value))
55
+ return value;
56
+ return value ? [value] : [];
55
57
  }
56
58
  /**
57
59
  * Compute the trigger's visible label. In multiple mode it joins every
@@ -121,7 +123,7 @@ export function useOnboardingDropdownPlacement(rootRef, open, placement, optionC
121
123
  });
122
124
  useLayoutEffect(() => {
123
125
  if (!open)
124
- return;
126
+ return undefined;
125
127
  function measureMenu() {
126
128
  const node = rootRef.current;
127
129
  if (!node)
@@ -153,11 +155,11 @@ export function useOnboardingDropdown(props) {
153
155
  const [open, setOpen] = useState(false);
154
156
  const [query, setQuery] = useState('');
155
157
  const rootRef = useRef(null);
156
- const dropdownIdRef = useRef(`onboarding-dropdown-${Math.random().toString(36).slice(2)}`);
158
+ const dropdownIdRef = useRef(`onboarding-dropdown-${globalThis.crypto.getRandomValues(new Uint32Array(1))[0].toString(36)}`);
157
159
  const { placement: resolvedPlacement, maxHeight: menuMaxHeight } = useOnboardingDropdownPlacement(rootRef, open, placement, options.length);
158
160
  useEffect(() => {
159
161
  if (!open)
160
- return;
162
+ return undefined;
161
163
  function handlePointerDown(event) {
162
164
  if (!rootRef.current?.contains(event.target)) {
163
165
  setOpen(false);
@@ -10,12 +10,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
10
  // hook-testable in isolation.
11
11
  import { useCallback, useEffect, useRef, useState } from 'react';
12
12
  import { Icon } from './Icon.js';
13
+ const PRIMARY = 'var(--jini-primary)';
13
14
  export const PALETTE_TWEAKS_SWATCHES = [
14
- { id: 'coral', label: 'Coral - default', stripe: ['var(--jini-primary)', 'var(--jini-primary)', 'var(--jini-primary)', 'var(--jini-primary)'] },
15
- { id: 'electric', label: 'Electric', stripe: ['var(--jini-primary)', 'var(--jini-primary)', 'var(--jini-primary)', 'var(--jini-primary)'] },
16
- { id: 'acid-forest', label: 'Acid forest', stripe: ['var(--jini-primary)', 'var(--jini-primary)', 'var(--jini-primary)', 'var(--jini-primary)'] },
17
- { id: 'risograph', label: 'Risograph', stripe: ['var(--jini-primary)', 'var(--jini-primary)', 'var(--jini-primary)', 'var(--jini-primary)'] },
18
- { id: 'mono-noir', label: 'Mono noir', stripe: ['var(--jini-text)', 'var(--jini-primary)', 'var(--jini-primary)', 'var(--jini-primary)'] },
15
+ { id: 'coral', label: 'Coral - default', stripe: [PRIMARY, PRIMARY, PRIMARY, PRIMARY] },
16
+ { id: 'electric', label: 'Electric', stripe: [PRIMARY, PRIMARY, PRIMARY, PRIMARY] },
17
+ { id: 'acid-forest', label: 'Acid forest', stripe: [PRIMARY, PRIMARY, PRIMARY, PRIMARY] },
18
+ { id: 'risograph', label: 'Risograph', stripe: [PRIMARY, PRIMARY, PRIMARY, PRIMARY] },
19
+ { id: 'mono-noir', label: 'Mono noir', stripe: ['var(--jini-text)', PRIMARY, PRIMARY, PRIMARY] },
19
20
  ];
20
21
  // --- Pure derivations (module-level, directly unit-testable) --------------
21
22
  /**
@@ -71,7 +72,7 @@ export function usePaletteDismiss(options) {
71
72
  const rootRef = useRef(null);
72
73
  useEffect(() => {
73
74
  if (!open)
74
- return;
75
+ return undefined;
75
76
  function onDoc(ev) {
76
77
  if (!rootRef.current)
77
78
  return;
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { withClassName } from '../../utils/class-names.js';
2
3
  /**
3
4
  * Generic labeled trigger pill: icon + label (+ optional trailing icon),
4
5
  * togglable `active` state, and an optional anchored children slot for a
@@ -9,6 +10,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
10
  * (recon r6 §1.19) — generic popover-trigger chrome, no product types.
10
11
  */
11
12
  export function PillButton({ icon, label, active = false, 'aria-label': ariaLabel, trailingIcon, onClick, disabled = false, className, children, }) {
12
- return (_jsxs("div", { className: `jini-pill-wrap${className ? ` ${className}` : ''}`, children: [_jsxs("button", { type: "button", className: `jini-pill${active ? ' is-active' : ''}`, "aria-label": ariaLabel, "aria-expanded": children ? active : undefined, onClick: onClick, disabled: disabled, children: [icon, _jsx("span", { className: "jini-pill__label", children: label }), trailingIcon] }), children] }));
13
+ return (_jsxs("div", { className: withClassName('jini-pill-wrap', className), children: [_jsxs("button", { type: "button", className: `jini-pill${active ? ' is-active' : ''}`, "aria-label": ariaLabel, "aria-expanded": children ? active : undefined, onClick: onClick, disabled: disabled, children: [icon, _jsx("span", { className: "jini-pill__label", children: label }), trailingIcon] }), children] }));
13
14
  }
14
15
  //# sourceMappingURL=PillButton.js.map
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { withClassName } from '../../utils/class-names.js';
2
3
  /**
3
4
  * Generic popover panel shell: a plain positioned wrapper around a list of
4
5
  * `PopoverItem`s (or any other content). No open/closed state of its own —
@@ -8,6 +9,6 @@ import { jsx as _jsx } from "react/jsx-runtime";
8
9
  * (recon r6 §1.19) — generic, no product types.
9
10
  */
10
11
  export function PopoverMenu({ children, className }) {
11
- return _jsx("div", { className: `jini-popover${className ? ` ${className}` : ''}`, children: children });
12
+ return _jsx("div", { className: withClassName('jini-popover', className), children: children });
12
13
  }
13
14
  //# sourceMappingURL=PopoverMenu.js.map
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect } from 'react';
3
3
  import { ensureRemixIconStylesheet } from './RemixIcon.stylesheet.js';
4
+ import { withClassName } from '../../utils/class-names.js';
4
5
  export { installRemixIconStylesheet, REMIXICON_STYLESHEET_MARKER } from './RemixIcon.stylesheet.js';
5
6
  /**
6
7
  * Thin wrapper around a RemixIcon webfont glyph (`ri-<name>`). Loads this package's own default
@@ -12,7 +13,7 @@ export function RemixIcon({ name, size = 14, className, style }) {
12
13
  useEffect(() => {
13
14
  ensureRemixIconStylesheet({});
14
15
  }, []);
15
- return (_jsx("i", { className: `ri-${name}${className ? ` ${className}` : ''}`, "aria-hidden": "true", style: {
16
+ return (_jsx("i", { className: withClassName(`ri-${name}`, className), "aria-hidden": "true", style: {
16
17
  fontSize: size,
17
18
  lineHeight: 1,
18
19
  width: size,