@jini-ai/ui 0.5.5 → 0.6.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/CHANGELOG.md +91 -2
  2. package/dist/core/theme/validation.js +40 -38
  3. package/dist/features/a2ui/renderer.js +15 -13
  4. package/dist/features/about/react/components/AboutTab.js +64 -34
  5. package/dist/features/about/rules.d.ts +2 -2
  6. package/dist/features/about/rules.js +156 -129
  7. package/dist/features/admin-widgets/components/ImagePreviewModal.d.ts +4 -3
  8. package/dist/features/admin-widgets/components/ImagePreviewModal.js +6 -10
  9. package/dist/features/admin-widgets/components/InfoTip.d.ts +3 -2
  10. package/dist/features/admin-widgets/components/InfoTip.hooks.js +2 -0
  11. package/dist/features/admin-widgets/components/InfoTip.js +34 -7
  12. package/dist/features/admin-widgets/components/InfoTip.rules.d.ts +13 -0
  13. package/dist/features/admin-widgets/components/InfoTip.rules.js +43 -10
  14. package/dist/features/admin-widgets/components/Lightbox/Lightbox.d.ts +30 -0
  15. package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.d.ts +40 -0
  16. package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.js +128 -0
  17. package/dist/features/admin-widgets/components/Lightbox/Lightbox.js +18 -0
  18. package/dist/features/admin-widgets/components/MiddleTruncate.d.ts +14 -0
  19. package/dist/features/admin-widgets/components/MiddleTruncate.js +12 -0
  20. package/dist/features/admin-widgets/components/MiddleTruncate.rules.d.ts +20 -0
  21. package/dist/features/admin-widgets/components/MiddleTruncate.rules.js +21 -0
  22. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.d.ts +18 -0
  23. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.d.ts +16 -0
  24. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.js +45 -0
  25. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.js +18 -0
  26. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.rules.d.ts +32 -0
  27. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.rules.js +26 -0
  28. package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.hooks.js +29 -14
  29. package/dist/features/admin-widgets/index.d.ts +14 -2
  30. package/dist/features/admin-widgets/index.js +7 -1
  31. package/dist/features/admin-widgets/styles/admin-widgets.css +1 -0
  32. package/dist/features/asset-grid/react/components/AssetCard.js +7 -1
  33. package/dist/features/asset-grid/react/components/AssetGrid.d.ts +1 -1
  34. package/dist/features/asset-grid/react/components/AssetGrid.js +38 -18
  35. package/dist/features/asset-grid/react/components/useAssetGridFilters.d.ts +21 -0
  36. package/dist/features/asset-grid/react/components/useAssetGridFilters.js +19 -0
  37. package/dist/features/asset-grid/react/hooks/useAssetGridKeyboardShortcuts.js +16 -11
  38. package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.d.ts +2 -0
  39. package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.js +35 -11
  40. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.d.ts +6 -6
  41. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.js +10 -129
  42. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.d.ts +50 -0
  43. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.js +95 -0
  44. package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.d.ts +56 -0
  45. package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.js +80 -0
  46. package/dist/features/asset-tree-browser/rules.js +40 -22
  47. package/dist/features/browser-chrome/rules.js +74 -70
  48. package/dist/features/charts/browser.js +11 -5
  49. package/dist/features/charts/react.js +2 -2
  50. package/dist/features/charts/table.js +2 -2
  51. package/dist/features/command-palette/react/hooks/useCommandPalette.js +3 -9
  52. package/dist/features/connectors/ConnectorsBrowser.d.ts +4 -4
  53. package/dist/features/connectors/ConnectorsBrowser.js +26 -14
  54. package/dist/features/connectors/components/ConnectorCard.js +67 -38
  55. package/dist/features/connectors/components/ConnectorDetailDrawer.js +62 -31
  56. package/dist/features/connectors/components/ConnectorGrid.js +11 -6
  57. package/dist/features/connectors/hooks/useConnectorAuthorization.js +6 -0
  58. package/dist/features/connectors/hooks/useConnectorCatalog.d.ts +2 -0
  59. package/dist/features/connectors/hooks/useConnectorCatalog.js +32 -13
  60. package/dist/features/connectors/hooks/useWiredConnectorsBrowser.d.ts +3 -3
  61. package/dist/features/connectors/rules.d.ts +8 -1
  62. package/dist/features/connectors/rules.js +42 -20
  63. package/dist/features/connectors/types.d.ts +11 -0
  64. package/dist/features/execution/constants.d.ts +4 -10
  65. package/dist/features/execution/constants.js +9 -94
  66. package/dist/features/execution/react/components/ByokProviderForm.d.ts +2 -4
  67. package/dist/features/execution/react/components/ByokProviderForm.js +140 -43
  68. package/dist/features/execution/react/components/ExecutionTab.d.ts +1 -2
  69. package/dist/features/execution/react/components/ExecutionTab.js +52 -30
  70. package/dist/features/execution/react/components/LocalCliAgentCard.js +12 -3
  71. package/dist/features/execution/react/components/LocalCliAgentList.js +14 -3
  72. package/dist/features/execution/react/components/LocalCliAgentSettings.js +14 -3
  73. package/dist/features/execution/react/hooks/useLocalCliAgentCard.js +54 -36
  74. package/dist/features/execution/react/hooks/useReasoningControl.js +114 -78
  75. package/dist/features/execution/rules.d.ts +2 -2
  76. package/dist/features/execution/rules.js +31 -15
  77. package/dist/features/execution/types.d.ts +4 -42
  78. package/dist/features/execution/types.js +0 -17
  79. package/dist/features/external-mcp/react/components/ExternalMcpTab.d.ts +6 -2
  80. package/dist/features/external-mcp/react/components/ExternalMcpTab.js +9 -2
  81. package/dist/features/file-dropzone/react/components/FileDropzone.js +24 -25
  82. package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.d.ts +2 -2
  83. package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.js +35 -30
  84. package/dist/features/file-dropzone/react/components/FileDropzoneThumbnailGrid.js +30 -24
  85. package/dist/features/file-dropzone/react/components/FileDropzoneZone.d.ts +14 -0
  86. package/dist/features/file-dropzone/react/components/FileDropzoneZone.js +26 -0
  87. package/dist/features/html-editor/canvas-content-wrapper.js +13 -13
  88. package/dist/features/html-editor/canvas-embed-placeholders.js +46 -37
  89. package/dist/features/html-editor/css.js +76 -69
  90. package/dist/features/html-editor/editor-blocks.js +47 -33
  91. package/dist/features/html-editor/editor-serialization.js +6 -5
  92. package/dist/features/html-editor/protected-elements.js +39 -28
  93. package/dist/features/html-editor/react/hooks/useInteractiveHtmlEditor.js +4 -4
  94. package/dist/features/html-editor/source-splice.js +51 -30
  95. package/dist/features/i18n/dictionaries/settings-dialog.ja.js +1 -0
  96. package/dist/features/i18n/dictionaries/settings-dialog.zh-CN.js +1 -0
  97. package/dist/features/i18n/dictionaries/settings-dialog.zh-TW.js +1 -0
  98. package/dist/features/i18n/locale.js +20 -16
  99. package/dist/features/iframe-pool/react/dom-sync.js +8 -6
  100. package/dist/features/integrations/react/components/CodexInstallToggleButton.js +19 -4
  101. package/dist/features/integrations/react/components/IntegrationsTab.js +60 -14
  102. package/dist/features/interactive-ui/interactive-ui.css +1 -1
  103. package/dist/features/interactive-ui/providers/shadcn/data-table.js +10 -7
  104. package/dist/features/lexical-rich-text-editor/mention-parser.js +60 -57
  105. package/dist/features/lexical-rich-text-editor/react/hooks/useKeyboardCommands.js +33 -38
  106. package/dist/features/lexical-rich-text-editor/rules.js +87 -74
  107. package/dist/features/lexical-rich-text-editor/serialize.js +27 -25
  108. package/dist/features/mcp-ui/secret-card.js +111 -61
  109. package/dist/features/mcp-ui/surfaces/confirmation.js +39 -21
  110. package/dist/features/mcp-ui/surfaces/document.d.ts +1 -1
  111. package/dist/features/mcp-ui/surfaces/document.js +10 -2
  112. package/dist/features/mcp-ui/surfaces/fields.js +30 -50
  113. package/dist/features/mcp-ui/surfaces/form.d.ts +6 -0
  114. package/dist/features/mcp-ui/surfaces/form.js +43 -21
  115. package/dist/features/mcp-ui/surfaces/outcome.js +30 -20
  116. package/dist/features/mcp-ui/surfaces/text-input.js +33 -21
  117. package/dist/features/mcp-ui/surfaces/tokens.d.ts +6 -1
  118. package/dist/features/mcp-ui/surfaces/tokens.js +5 -2
  119. package/dist/features/media-providers/constants.d.ts +1 -0
  120. package/dist/features/media-providers/constants.js +2 -81
  121. package/dist/features/media-providers/index.d.ts +1 -1
  122. package/dist/features/media-providers/ports.d.ts +2 -0
  123. package/dist/features/media-providers/react/components/MediaProviderCard.d.ts +22 -0
  124. package/dist/features/media-providers/react/components/MediaProviderCard.js +50 -0
  125. package/dist/features/media-providers/react/components/MediaProvidersTab.d.ts +5 -6
  126. package/dist/features/media-providers/react/components/MediaProvidersTab.js +40 -72
  127. package/dist/features/media-providers/react/components/media-providers-tab-labels.d.ts +11 -0
  128. package/dist/features/media-providers/react/components/media-providers-tab-labels.js +57 -0
  129. package/dist/features/media-providers/react/hooks/useMediaProvidersTab.d.ts +4 -2
  130. package/dist/features/media-providers/react/hooks/useMediaProvidersTab.js +37 -23
  131. package/dist/features/media-providers/rules.js +17 -10
  132. package/dist/features/media-providers/types.d.ts +4 -22
  133. package/dist/features/media-providers/types.js +3 -8
  134. package/dist/features/memory/formatters.js +127 -92
  135. package/dist/features/memory/react/components/MemoryConnectedPanel.js +92 -36
  136. package/dist/features/memory/react/components/MemoryList.js +37 -18
  137. package/dist/features/memory/react/hooks/useMemoryExtractions.store.js +42 -36
  138. package/dist/features/notifications/react/components/NotificationsTab.js +28 -28
  139. package/dist/features/observability/long-task.d.ts +0 -12
  140. package/dist/features/observability/long-task.js +15 -19
  141. package/dist/features/observability/resource-error.d.ts +0 -10
  142. package/dist/features/observability/resource-error.js +18 -13
  143. package/dist/features/observability/stuck-run.js +10 -6
  144. package/dist/features/observability/telemetry-url.d.ts +6 -0
  145. package/dist/features/observability/telemetry-url.js +16 -0
  146. package/dist/features/observability/white-screen.js +12 -24
  147. package/dist/features/panel-kit/fetch-query/adapter.react.d.ts +2 -2
  148. package/dist/features/panel-kit/fetch-query/adapter.react.js +4 -2
  149. package/dist/features/panel-kit/fetch-query/adapter.tanstack.js +46 -33
  150. package/dist/features/panel-kit/helpers/standing-draft-local-backup.js +3 -1
  151. package/dist/features/panel-kit/hooks/use-settings-slice.hooks.js +3 -1
  152. package/dist/features/privacy/react/components/PrivacyTab.js +24 -13
  153. package/dist/features/progress-card/components/ProgressCard.js +7 -3
  154. package/dist/features/progress-card/reference-adapters.js +59 -54
  155. package/dist/features/project-locations/react/components/ProjectLocationsTab.js +24 -15
  156. package/dist/features/project-locations/react/hooks/useProjectLocationsTab.d.ts +8 -0
  157. package/dist/features/project-locations/react/hooks/useProjectLocationsTab.js +31 -23
  158. package/dist/features/resource-dashboard/react/components/ResourceBoard.js +6 -47
  159. package/dist/features/resource-dashboard/react/components/ResourceBoardView.d.ts +2 -2
  160. package/dist/features/resource-dashboard/react/components/ResourceBoardView.js +3 -6
  161. package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.d.ts +15 -0
  162. package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.js +35 -0
  163. package/dist/features/resource-dashboard/react/components/ResourceCard.js +5 -16
  164. package/dist/features/resource-dashboard/react/components/ResourceCardParts.d.ts +34 -0
  165. package/dist/features/resource-dashboard/react/components/ResourceCardParts.js +34 -0
  166. package/dist/features/resource-dashboard/react/components/ResourceRowList.js +3 -1
  167. package/dist/features/resource-dashboard/react/components/ResourceRowListItem.js +2 -2
  168. package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.d.ts +19 -0
  169. package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.js +11 -0
  170. package/dist/features/resource-dashboard/react/components/ResourceRowListView.d.ts +1 -1
  171. package/dist/features/resource-dashboard/react/components/ResourceRowListView.js +4 -4
  172. package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.d.ts +10 -0
  173. package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.js +20 -0
  174. package/dist/features/resource-dashboard/react/components/resourceBoardProps.d.ts +48 -0
  175. package/dist/features/resource-dashboard/react/components/resourceBoardProps.js +70 -0
  176. package/dist/features/resource-dashboard/react/components/resourceRowListProps.d.ts +19 -0
  177. package/dist/features/resource-dashboard/react/components/resourceRowListProps.js +14 -0
  178. package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.d.ts +16 -0
  179. package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.js +13 -0
  180. package/dist/features/resource-dashboard/rules.js +2 -8
  181. package/dist/features/sketch-editor/dom.js +150 -99
  182. package/dist/features/sketch-editor/react/components/SketchEditor.js +26 -15
  183. package/dist/features/skills/react/components/SkillDraftForm.js +13 -19
  184. package/dist/features/skills/react/components/SkillDraftForm.labels.d.ts +31 -0
  185. package/dist/features/skills/react/components/SkillDraftForm.labels.js +54 -0
  186. package/dist/features/skills/react/components/SkillRow.js +38 -18
  187. package/dist/features/skills/react/components/SkillRow.labels.d.ts +7 -0
  188. package/dist/features/skills/react/components/SkillRow.labels.js +18 -0
  189. package/dist/features/skills/react/components/SkillsTab.js +24 -14
  190. package/dist/features/skills/react/components/SkillsTab.labels.d.ts +7 -0
  191. package/dist/features/skills/react/components/SkillsTab.labels.js +11 -0
  192. package/dist/features/skills/rules.js +32 -23
  193. package/dist/features/source-config-list/agent-handles.js +2 -1
  194. package/dist/features/source-config-list/dependencies.js +53 -49
  195. package/dist/features/source-config-list/index.d.ts +3 -3
  196. package/dist/features/source-config-list/react/components/SourceConfigAddForm.d.ts +6 -5
  197. package/dist/features/source-config-list/react/components/SourceConfigAddForm.js +38 -7
  198. package/dist/features/source-config-list/react/components/SourceConfigField.js +69 -25
  199. package/dist/features/source-config-list/react/components/SourceConfigItemCard.d.ts +6 -5
  200. package/dist/features/source-config-list/react/components/SourceConfigItemCard.js +76 -20
  201. package/dist/features/source-config-list/react/components/SourceConfigListView.d.ts +5 -3
  202. package/dist/features/source-config-list/react/components/SourceConfigListView.js +33 -3
  203. package/dist/features/source-config-list/react/components/SourceConfigTestControl.js +17 -1
  204. package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.d.ts +11 -0
  205. package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.js +12 -0
  206. package/dist/features/source-config-list/types.d.ts +14 -0
  207. package/dist/features/tab-launcher-menu/react/components/TabLauncherMenu.js +25 -1
  208. package/dist/features/tab-launcher-menu/react/hooks/useTabLauncherMenu.js +15 -16
  209. package/dist/features/tab-strip/react/components/TabBar.js +8 -1
  210. package/dist/features/tabbed-dialog/react/components/TabbedDialog.js +8 -30
  211. package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.d.ts +15 -0
  212. package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.js +8 -0
  213. package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.d.ts +12 -0
  214. package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.js +6 -0
  215. package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.d.ts +27 -0
  216. package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.js +12 -0
  217. package/dist/features/tabbed-dialog/react/components/tabbedDialogView.d.ts +43 -0
  218. package/dist/features/tabbed-dialog/react/components/tabbedDialogView.js +59 -0
  219. package/dist/features/version-manager/dependencies.d.ts +3 -1
  220. package/dist/features/version-manager/dependencies.js +3 -2
  221. package/dist/features/version-manager/react/components/VersionManagerModal.js +10 -6
  222. package/dist/features/version-manager/react/hooks/useVersionManager.js +7 -4
  223. package/dist/features/viewer-shell/react/components/CommentSidePanel.js +103 -50
  224. package/dist/features/viewer-shell/react/components/MarkdownSplitPane.js +28 -8
  225. package/dist/features/viewer-shell/react/hooks/useCommentReorder.js +22 -16
  226. package/dist/features/viewer-shell/rules.js +19 -23
  227. package/dist/react/components/CustomSelect.d.ts +38 -1
  228. package/dist/react/components/CustomSelect.js +94 -38
  229. package/dist/react/components/OnboardingDropdown.js +20 -8
  230. package/dist/react/components/RemixIcon.stylesheet.js +3 -1
  231. package/dist/react/components/Toast.js +13 -2
  232. package/dist/react/components/TooltipLayer.js +5 -14
  233. package/dist/react/components/WorkingDirPicker.js +78 -50
  234. package/dist/react/components/remixicon-font-recovery.js +38 -30
  235. package/dist/react/components/select-popover.d.ts +8 -6
  236. package/dist/react/components/select-popover.js +33 -29
  237. package/dist/react/mcp-ui/McpUiHost.d.ts +1 -1
  238. package/dist/react/mcp-ui/McpUiHost.js +10 -11
  239. package/dist/react/mcp-ui/host-message-source.d.ts +14 -2
  240. package/dist/react/mcp-ui/host-message-source.js +15 -5
  241. package/dist/react/mcp-ui/mcp-ui-host-frame.d.ts +41 -0
  242. package/dist/react/mcp-ui/mcp-ui-host-frame.js +39 -0
  243. package/dist/renderers/annotation-canvas/react/components/AnnotationCanvas.js +97 -69
  244. package/dist/renderers/preview-modal-shell/react/components/PreviewModalShell.js +59 -30
  245. package/dist/renderers/react/components/ArtifactView.js +9 -6
  246. package/dist/renderers/srcdoc/build.d.ts +4 -1
  247. package/dist/renderers/srcdoc/build.js +62 -48
  248. package/dist/styles/admin.css +29 -14
  249. package/dist/styles/lightbox.css +142 -0
  250. package/dist/styles/variables.css +128 -0
  251. package/dist/theme/apply.js +9 -5
  252. package/dist/utils/auto-open-file.d.ts +2 -1
  253. package/dist/utils/auto-open-file.js +69 -85
  254. package/dist/utils/markdown-scroll-sync.js +108 -87
  255. package/dist/utils/polygon-selection.js +25 -26
  256. package/dist/utils/smooth-scroll-to-top.d.ts +1 -1
  257. package/dist/utils/smooth-scroll-to-top.js +2 -32
  258. package/dist/utils/sse.js +26 -26
  259. package/dist/utils/unit-bezier.d.ts +7 -0
  260. package/dist/utils/unit-bezier.js +38 -0
  261. package/package.json +9 -12
  262. package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.d.ts +0 -13
  263. package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.js +0 -46
@@ -27,109 +27,116 @@ export function prettifyCss(css) {
27
27
  let atDeclStart = false;
28
28
  let i = 0;
29
29
  const n = css.length;
30
- const trimTrailingSpace = () => {
31
- out = out.replace(/[ \t]+$/, '');
32
- };
33
- const peekNextNonSpace = (from) => {
34
- let j = from;
35
- while (j < n && /\s/.test(css[j]))
36
- j++;
37
- return css[j];
30
+ const skipWhitespace = (_input, _options = {}) => {
31
+ while (i < n && /\s/.test(css[i]))
32
+ i++;
38
33
  };
39
- while (i < n) {
40
- const ch = css[i];
34
+ const consumeValue = ({ ch }, _options = {}) => {
41
35
  if (quote) {
42
36
  out += ch;
43
37
  if (ch === '\\' && i + 1 < n) {
44
38
  out += css[i + 1];
45
39
  i += 2;
46
- continue;
40
+ return true;
47
41
  }
48
42
  if (ch === quote)
49
43
  quote = null;
50
44
  i++;
51
- continue;
45
+ return true;
52
46
  }
53
47
  if (ch === '"' || ch === "'") {
54
48
  quote = ch;
55
49
  out += ch;
56
50
  i++;
57
- continue;
51
+ return true;
58
52
  }
59
53
  if (ch === '(') {
60
54
  parenDepth++;
61
55
  out += ch;
62
56
  i++;
63
- continue;
57
+ return true;
64
58
  }
65
59
  if (ch === ')') {
66
60
  parenDepth = Math.max(0, parenDepth - 1);
67
61
  out += ch;
68
62
  i++;
69
- continue;
63
+ return true;
70
64
  }
71
- if (parenDepth === 0 && ch === '{') {
72
- trimTrailingSpace();
73
- out += ' {';
74
- depth++;
75
- atDeclStart = true;
76
- i++;
77
- while (i < n && /\s/.test(css[i]))
78
- i++;
79
- if (peekNextNonSpace(i) !== '}')
80
- out += '\n' + INDENT.repeat(depth);
81
- continue;
65
+ return false;
66
+ };
67
+ const openRule = (_input, _options = {}) => {
68
+ out = out.replace(/[ \t]+$/, '');
69
+ out += ' {';
70
+ depth++;
71
+ atDeclStart = true;
72
+ i++;
73
+ skipWhitespace({}, {});
74
+ if (css[i] !== '}')
75
+ out += '\n' + INDENT.repeat(depth);
76
+ return true;
77
+ };
78
+ const closeRule = (_input, _options = {}) => {
79
+ depth = Math.max(0, depth - 1);
80
+ out = out.replace(/[ \t]+$/, '');
81
+ // A `}` immediately after `{` (an empty rule) needs no newline before it; otherwise close on
82
+ // its own indented line.
83
+ if (!out.endsWith('{'))
84
+ out += '\n' + INDENT.repeat(depth);
85
+ out += '}';
86
+ atDeclStart = false;
87
+ i++;
88
+ skipWhitespace({}, {});
89
+ const nextCh = css[i];
90
+ // A following `}` (closing a parent at-rule) manages its own leading newline via the same
91
+ // trailing-space trim above — adding one here too would double up into a stray blank line.
92
+ if (nextCh !== undefined && nextCh !== '}') {
93
+ out += depth === 0 ? '\n\n' : '\n' + INDENT.repeat(depth);
82
94
  }
83
- if (parenDepth === 0 && ch === '}') {
84
- depth = Math.max(0, depth - 1);
85
- trimTrailingSpace();
86
- // A `}` immediately after `{` (an empty rule) needs no newline before it; otherwise close on
87
- // its own indented line.
88
- if (!out.endsWith('{'))
89
- out += '\n' + INDENT.repeat(depth);
90
- out += '}';
91
- atDeclStart = false;
92
- i++;
93
- while (i < n && /\s/.test(css[i]))
94
- i++;
95
- const nextCh = css[i];
96
- // A following `}` (closing a parent at-rule) manages its own leading newline via the same
97
- // trimTrailingSpace() above — adding one here too would double up into a stray blank line.
98
- if (nextCh !== undefined && nextCh !== '}') {
99
- out += depth === 0 ? '\n\n' : '\n' + INDENT.repeat(depth);
100
- }
101
- continue;
95
+ return true;
96
+ };
97
+ const endDeclaration = (_input, _options = {}) => {
98
+ out += ';';
99
+ i++;
100
+ skipWhitespace({}, {});
101
+ const nextCh = css[i];
102
+ if (nextCh !== '}' && nextCh !== undefined) {
103
+ out += '\n' + INDENT.repeat(depth);
104
+ atDeclStart = true;
102
105
  }
103
- if (parenDepth === 0 && ch === ';') {
104
- out += ';';
106
+ return true;
107
+ };
108
+ // Only the FIRST colon of a declaration (the property/value separator) gets a space after it —
109
+ // a selector's own colon (`.gm-btn:hover`, matched at `depth === 0`, before any `{`) is left
110
+ // untouched, and only `atDeclStart` (cleared the moment this fires) keeps a later colon inside
111
+ // the same value (there are none in practice here, but the guard costs nothing) from being
112
+ // treated as another property separator.
113
+ const separateValue = (_input, _options = {}) => {
114
+ out += ': ';
115
+ atDeclStart = false;
116
+ i++;
117
+ while (i < n && css[i] === ' ')
105
118
  i++;
106
- const nextCh = peekNextNonSpace(i);
107
- while (i < n && /\s/.test(css[i]))
108
- i++;
109
- if (nextCh !== '}' && nextCh !== undefined) {
110
- out += '\n' + INDENT.repeat(depth);
111
- atDeclStart = true;
112
- }
113
- continue;
119
+ return true;
120
+ };
121
+ const consumeStructure = ({ ch }, _options = {}) => {
122
+ if (parenDepth !== 0)
123
+ return false;
124
+ switch (ch) {
125
+ case '{': return openRule({}, {});
126
+ case '}': return closeRule({}, {});
127
+ case ';': return endDeclaration({}, {});
128
+ case ':': return depth > 0 && atDeclStart ? separateValue({}, {}) : false;
129
+ default: return false;
114
130
  }
115
- // Only the FIRST colon of a declaration (the property/value separator) gets a space after it —
116
- // a selector's own colon (`.gm-btn:hover`, matched at `depth === 0`, before any `{`) is left
117
- // untouched, and only `atDeclStart` (cleared the moment this fires) keeps a later colon inside
118
- // the same value (there are none in practice here, but the guard costs nothing) from being
119
- // treated as another property separator.
120
- if (parenDepth === 0 && ch === ':' && depth > 0 && atDeclStart) {
121
- out += ': ';
122
- atDeclStart = false;
123
- i++;
124
- while (i < n && css[i] === ' ')
125
- i++;
131
+ };
132
+ while (i < n) {
133
+ const ch = css[i];
134
+ if (consumeValue({ ch }, {}) || consumeStructure({ ch }, {}))
126
135
  continue;
127
- }
128
136
  if (/\s/.test(ch)) {
129
137
  out += ' ';
130
138
  i++;
131
- while (i < n && /\s/.test(css[i]))
132
- i++;
139
+ skipWhitespace({}, {});
133
140
  continue;
134
141
  }
135
142
  out += ch;
@@ -48,6 +48,42 @@ const isEditableEffect = (el) => {
48
48
  return true;
49
49
  return (TEXT_EFFECT_TAGS.has(el.tagName) || PHRASING_TAGS.has(el.tagName)) && isPhrasingOnly(el);
50
50
  };
51
+ function runtimeBlockReason({ el, isLive }, _options = {}) {
52
+ if (isLive)
53
+ return undefined;
54
+ if (Object.keys(el.attributes).some(name => name.startsWith('data-kui')) && !isEditableEffect(el))
55
+ return 'kuinetic';
56
+ if (el.tagName === 'canvas')
57
+ return 'canvas';
58
+ if (el.tagName === 'video' && 'autoplay' in el.attributes)
59
+ return 'autoplay-video';
60
+ if (el.attributes['data-editor'] === 'block')
61
+ return 'scripted';
62
+ return undefined;
63
+ }
64
+ function describeBlock({ el, isLive, hasLiveDescendants }, options = {}) {
65
+ const view = sourceElementAttributeView(el, {});
66
+ const host = options.describeHostElement?.(view);
67
+ const embed = !!host || !!options.isProtectedElement?.(view);
68
+ let reason = runtimeBlockReason({ el, isLive }, {});
69
+ if (reason && hasLiveDescendants)
70
+ reason = undefined;
71
+ if (!reason && embed)
72
+ reason = 'embed';
73
+ return { reason, host };
74
+ }
75
+ function createEditorBlock({ el, reason, host }, options = {}) {
76
+ const t = options.translate ?? ((key) => key);
77
+ const id = el.attributes.id;
78
+ const identity = id ? `#${id}` : el.attributes.class ? `.${el.attributes.class.trim().split(/\s+/).slice(0, 2).join('.')}` : el.tagName;
79
+ return {
80
+ ...el.outerRange, tagName: el.tagName, ...(id ? { id } : {}), reason, element: el,
81
+ descriptor: reason === 'embed' && host ? host : {
82
+ kindLabel: t(LABELS[reason]), identityLabel: identity.slice(0, 64), presentation: reason === 'embed' ? 'embed' : 'runtime',
83
+ guidanceLabel: t('Locked · Edit in HTML · Preview plays it'), inline: INLINE_TAGS.has(el.tagName),
84
+ },
85
+ };
86
+ }
51
87
  /** Outermost disjoint roots; explicit live islands veto aggregation but never host embed safety. */
52
88
  export function detectEditorBlocks({ html }, options = {}) {
53
89
  const elements = indexSourceElements({ html }, {});
@@ -60,48 +96,22 @@ export function detectEditorBlocks({ html }, options = {}) {
60
96
  };
61
97
  elements.forEach(annotate);
62
98
  let unsupported = false;
63
- const walk = (el, live, invalidAncestor) => {
99
+ const walk = ({ el, live, invalidAncestor }, _options = {}) => {
64
100
  const mode = el.attributes['data-editor'];
65
101
  const isLive = mode === 'live' || (mode !== 'block' && live);
66
- const view = sourceElementAttributeView(el, {});
67
- const host = options.describeHostElement?.(view);
68
- const embed = !!host || !!options.isProtectedElement?.(view);
69
- let reason;
70
- if (!isLive) {
71
- if (Object.keys(el.attributes).some(name => name.startsWith('data-kui')) && !isEditableEffect(el))
72
- reason = 'kuinetic';
73
- else if (el.tagName === 'canvas')
74
- reason = 'canvas';
75
- else if (el.tagName === 'video' && 'autoplay' in el.attributes)
76
- reason = 'autoplay-video';
77
- else if (mode === 'block')
78
- reason = 'scripted';
79
- }
80
- if (reason && liveIslands.get(el))
81
- reason = undefined;
82
- if (!reason && embed)
83
- reason = 'embed';
102
+ const { reason, host } = describeBlock({ el, isLive, hasLiveDescendants: !!liveIslands.get(el) }, options);
84
103
  const invalid = invalidAncestor || !el.outerRange || UNSUPPORTED_CONTEXTS.has(el.tagName);
85
104
  if (reason) {
86
105
  if (invalid || !el.sourceSupported) {
87
106
  unsupported = true;
88
107
  return;
89
108
  }
90
- const t = options.translate ?? ((key) => key);
91
- const id = el.attributes.id;
92
- const identity = id ? `#${id}` : el.attributes.class ? `.${el.attributes.class.trim().split(/\s+/).slice(0, 2).join('.')}` : el.tagName;
93
- blocks.push({
94
- ...el.outerRange, tagName: el.tagName, ...(id ? { id } : {}), reason, element: el,
95
- descriptor: reason === 'embed' && host ? host : {
96
- kindLabel: t(LABELS[reason]), identityLabel: identity.slice(0, 64), presentation: reason === 'embed' ? 'embed' : 'runtime',
97
- guidanceLabel: t('Locked · Edit in HTML · Preview plays it'), inline: INLINE_TAGS.has(el.tagName),
98
- },
99
- });
109
+ blocks.push(createEditorBlock({ el, reason, host }, options));
100
110
  return;
101
111
  }
102
- el.children.forEach(child => walk(child, isLive, invalid));
112
+ el.children.forEach(child => walk({ el: child, live: isLive, invalidAncestor: invalid }, {}));
103
113
  };
104
- elements.forEach(el => walk(el, false, false));
114
+ elements.forEach(el => walk({ el, live: false, invalidAncestor: false }, {}));
105
115
  return unsupported || (blocks.length > 0 && elements.some(el => !el.sourceSupported))
106
116
  ? { ok: false, problem: "Can't edit this source visually. Use HTML." } : { ok: true, blocks, elements };
107
117
  }
@@ -147,8 +157,12 @@ export function prepareEditorSource({ html }, options = {}) {
147
157
  let namespace = 0;
148
158
  while (html.toLowerCase().includes(markerAttribute))
149
159
  markerAttribute = `data-jini-raw-source-${++namespace}`;
160
+ const prepared = renderSourceShells({ html, instances, blocks: detected.blocks, markerAttribute }, {});
161
+ return { ok: true, html: prepared, markerAttribute, instances, canvasCss: css.join('\n') };
162
+ }
163
+ function renderSourceShells({ html, instances, blocks, markerAttribute }, _options = {}) {
150
164
  let prepared = html;
151
- const roots = new Map(detected.blocks.map(block => [block.outerStart, block]));
165
+ const roots = new Map(blocks.map(block => [block.outerStart, block]));
152
166
  for (let i = instances.length - 1; i >= 0; i--) {
153
167
  const instance = instances[i];
154
168
  const block = roots.get(instance.outerStart);
@@ -164,7 +178,7 @@ export function prepareEditorSource({ html }, options = {}) {
164
178
  const shell = `<${tag} ${markerAttribute}="${i}"${layout}>${voidTag && !instance.nonvisual ? '' : `</${tag}>`}`;
165
179
  prepared = prepared.slice(0, instance.outerStart) + shell + prepared.slice(instance.outerEnd);
166
180
  }
167
- return { ok: true, html: prepared, markerAttribute, instances, canvasCss: css.join('\n') };
181
+ return prepared;
168
182
  }
169
183
  /** Exact authored opening tag and its source offsets; no regex-based HTML parsing or id guessing. */
170
184
  export function findEditorBlockOpening({ html, target }, _options = {}) {
@@ -32,10 +32,11 @@ function serializeCleaned(editor) {
32
32
  // GrapesJS's getCss typing omits its JSON return type; successful init supplies a wrapper.
33
33
  const rules = editor.getCss({ json: true, avoidProtected: true, keepUnusedStyles: true });
34
34
  const css = rules.map(rule => rule.toCSS()).join('');
35
- const wrapper = editor.getWrapper();
36
- // `wrapper` is undefined only before/without a successful init, which cannot be true once this
37
- // hook's `editor` has fired `update` — defensive only, mirroring `serializeCleaned`'s own doc.
38
- const html = wrapper ? wrapper.getInnerHTML() : editor.getHtml();
35
+ // `getWrapper()` is undefined only before/without a successful init, which cannot be true once this
36
+ // hook's `editor` has fired `update` — and `serializeEditorSource` has already walked the wrapper
37
+ // (`assertProtectedSourceMetadata`) before reaching here. The old `: editor.getHtml()` fallback for a
38
+ // missing wrapper was therefore unreachable and is gone.
39
+ const html = editor.getWrapper().getInnerHTML();
39
40
  return css ? `<style>${prettifyCss(css)}</style>${html}` : html;
40
41
  }
41
42
  /**
@@ -86,7 +87,7 @@ function serializeWithSplice(editor, original, dirty, structuralChange, sourcePl
86
87
  let result = original;
87
88
  for (const edit of ordered) {
88
89
  const range = originalIndex.get(pathKey(edit.path));
89
- if (range && sourcePlan?.instances.some(raw => range.start < raw.outerEnd && range.end > raw.outerStart)) {
90
+ if (range && sourcePlan.instances.some(raw => range.start < raw.outerEnd && range.end > raw.outerStart)) {
90
91
  warnFallback('text edit overlaps a protected source region');
91
92
  return serializeCleaned(editor);
92
93
  }
@@ -71,6 +71,38 @@ export function protectedCanvasDescriber({ editor }, _options = {}) {
71
71
  });
72
72
  return el => descriptors.get(el);
73
73
  }
74
+ function selectedSourceStart({ wrapper, selected, original, html }, _options = {}) {
75
+ // Search only exact authored slices. Copies share bytes, so count preceding equal instances
76
+ // in model order; no mapping export mode or persistent source tokens are needed.
77
+ let copiesBefore = 0;
78
+ let reached = false;
79
+ visit(wrapper, component => {
80
+ if (component === selected)
81
+ reached = true;
82
+ else if (!reached && component.is(PROTECTED_ELEMENT_TYPE) && protectedElementHtml({ component }, {}) === original)
83
+ copiesBefore++;
84
+ });
85
+ let start = -1;
86
+ for (let i = 0; i <= copiesBefore; i++)
87
+ start = html.indexOf(original, start + 1);
88
+ if (start < 0)
89
+ throw new EditorSourceError();
90
+ return start;
91
+ }
92
+ function selectedSourceTarget({ selected, html, start }, _options = {}) {
93
+ const tagName = selected.get('sourceTagName');
94
+ const id = selected.get('sourceId');
95
+ let occurrenceIndex = 0;
96
+ for (;;) {
97
+ const range = findEditorBlockOpening({ html, target: { occurrenceIndex, tagName, ...(id ? { id } : {}) } }, {});
98
+ if (!range || range.start > start)
99
+ throw new EditorSourceError();
100
+ if (range.start === start)
101
+ break;
102
+ occurrenceIndex++;
103
+ }
104
+ return { occurrenceIndex, tagName, ...(id ? { id } : {}) };
105
+ }
74
106
  /**
75
107
  * Registers the `protected-element` component type so any parsed element `isProtectedElement`
76
108
  * recognizes becomes one atomic block: its content can never be edited (excluded from RTE
@@ -104,33 +136,9 @@ export function registerProtectedElementType({ editor, sourcePlan }, options = {
104
136
  return;
105
137
  const original = protectedElementHtml({ component: selected }, {});
106
138
  const html = wrapper.getInnerHTML();
107
- // Search only exact authored slices. Copies share bytes, so count preceding equal instances
108
- // in model order; no mapping export mode or persistent source tokens are needed.
109
- let copiesBefore = 0;
110
- let reached = false;
111
- visit(wrapper, component => {
112
- if (component === selected)
113
- reached = true;
114
- else if (!reached && component.is(PROTECTED_ELEMENT_TYPE) && protectedElementHtml({ component }, {}) === original)
115
- copiesBefore++;
116
- });
117
- let start = -1;
118
- for (let i = 0; i <= copiesBefore; i++)
119
- start = html.indexOf(original, start + 1);
120
- if (start < 0)
121
- throw new EditorSourceError();
122
- const tagName = selected.get('sourceTagName');
123
- const id = selected.get('sourceId');
124
- let occurrenceIndex = 0;
125
- for (;;) {
126
- const range = findEditorBlockOpening({ html, target: { occurrenceIndex, tagName, ...(id ? { id } : {}) } }, {});
127
- if (!range || range.start > start)
128
- throw new EditorSourceError();
129
- if (range.start === start)
130
- break;
131
- occurrenceIndex++;
132
- }
133
- await requestEditHtml({ occurrenceIndex, tagName, ...(id ? { id } : {}) }, {});
139
+ const start = selectedSourceStart({ wrapper, selected, original, html }, {});
140
+ const target = selectedSourceTarget({ selected, html, start }, {});
141
+ await requestEditHtml(target, {});
134
142
  }
135
143
  catch (error) {
136
144
  options.onSerializationError?.(error instanceof EditorSourceError ? error : new EditorSourceError());
@@ -161,7 +169,10 @@ export function registerProtectedElementType({ editor, sourcePlan }, options = {
161
169
  init() {
162
170
  // Keep default move/copy/delete toolbar commands, then append the host navigation action.
163
171
  if (!this.get('sourceNonvisual') && options.onRequestEditHtml) {
164
- const toolbar = (this.get('toolbar') ?? []);
172
+ // GrapesJS's `Component.initToolbar` fills `toolbar` (even over an explicit `null`) before a
173
+ // type's `init` runs, and a component cannot be constructed without the editor model it
174
+ // needs for that — so the old `?? []` fallback here could not run.
175
+ const toolbar = this.get('toolbar');
165
176
  this.set('toolbar', [...toolbar.filter(item => item.command !== command), {
166
177
  label: options.editHtmlLabel ?? 'Edit HTML', command,
167
178
  // GrapesJS sizes every toolbar item as a 26px icon square; a text label overflowed past the
@@ -259,10 +259,10 @@ export function useInteractiveHtmlEditor(html, onChange, isProtectedElement, can
259
259
  };
260
260
  const serialize = () => {
261
261
  try {
262
- const next = serializeEditorSource({ editor, html, edits: dirty, sourcePlan }, { structuralChange });
263
- if (next.includes(sourcePlan.markerAttribute))
264
- throw new EditorSourceError();
265
- return next;
262
+ // No separate marker-leak check here: `serializeEditorSource` already throws an
263
+ // `EditorSourceError` when its result carries `sourcePlan.markerAttribute`, so a second
264
+ // identical check on the same result could never fire.
265
+ return serializeEditorSource({ editor, html, edits: dirty, sourcePlan }, { structuralChange });
266
266
  }
267
267
  catch (error) {
268
268
  const failure = error instanceof EditorSourceError ? error : new EditorSourceError();
@@ -11,6 +11,20 @@ function isElement(node) {
11
11
  function hasChildNodes(node) {
12
12
  return 'childNodes' in node;
13
13
  }
14
+ function recordInnerRange({ node, currentPath, out }, _options = {}) {
15
+ const location = node.sourceCodeLocation;
16
+ // parse5 attaches a location only to elements created from a start-tag token, so a located
17
+ // element always has `startTag` (implied elements — a `tbody`, a `</p>`- or `</br>`-created
18
+ // element — carry no location at all and are skipped). Requiring `startTag` here replaces the old
19
+ // `: location.endOffset` fallback, which no parsed element could reach.
20
+ if (location?.startTag) {
21
+ // Void elements (`<br>`, `<img>`) have no `endTag` and no inner content: their range is
22
+ // empty (`start === end`), anchored at the end of the start tag.
23
+ const start = location.startTag.endOffset;
24
+ const end = location.endTag ? location.endTag.startOffset : start;
25
+ out.set(pathKey(currentPath), { start, end, tagName: node.tagName });
26
+ }
27
+ }
14
28
  /**
15
29
  * Walks one level of element-only children (recursing into each), assigning each a path and
16
30
  * recording its source range. `style`/`script` children are skipped without consuming a path slot.
@@ -25,14 +39,7 @@ function indexChildren(children, parentPath, out) {
25
39
  if (UNINDEXED_TAGS.has(node.tagName))
26
40
  continue;
27
41
  const currentPath = [...parentPath, elementIndex];
28
- const location = node.sourceCodeLocation;
29
- if (location) {
30
- // Void elements (`<br>`, `<img>`) have no `endTag` and no inner content: their range is
31
- // empty (`start === end`), anchored at the end of the start tag.
32
- const start = location.startTag ? location.startTag.endOffset : location.endOffset;
33
- const end = location.endTag ? location.endTag.startOffset : start;
34
- out.set(pathKey(currentPath), { start, end, tagName: node.tagName });
35
- }
42
+ recordInnerRange({ node, currentPath, out }, {});
36
43
  if (hasChildNodes(node))
37
44
  indexChildren(node.childNodes, currentPath, out);
38
45
  elementIndex++;
@@ -87,6 +94,35 @@ export function spliceElementInner(html, path, expectedTagName, newInner) {
87
94
  return html.slice(0, range.start) + newInner + html.slice(range.end);
88
95
  }
89
96
  const VOID_TAGS = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr']);
97
+ function sourceOuterEnd({ node, html }, _options = {}) {
98
+ const loc = node.sourceCodeLocation;
99
+ const start = loc?.startTag;
100
+ const foreignSelfClosing = node.namespaceURI !== 'http://www.w3.org/1999/xhtml' && !!start
101
+ && html.slice(start.startOffset, start.endOffset).endsWith('/>');
102
+ if (VOID_TAGS.has(node.tagName) || foreignSelfClosing)
103
+ return start?.endOffset;
104
+ return loc?.endTag?.endOffset;
105
+ }
106
+ function sourceOuterRange({ node, html, parentStart, parentEnd }, _options = {}) {
107
+ const start = node.sourceCodeLocation?.startTag;
108
+ const outerEnd = sourceOuterEnd({ node, html }, {});
109
+ const boundsValid = !!start && outerEnd !== undefined && start.startOffset >= parentStart && outerEnd <= parentEnd;
110
+ // Template content is a separate fragment; it has no ordinary component path/shell here.
111
+ if (!boundsValid || node.tagName === 'template')
112
+ return null;
113
+ return { outerStart: start.startOffset, outerEnd: outerEnd, openingEnd: start.endOffset };
114
+ }
115
+ function invalidateOverlappingSiblings({ elements }, _options = {}) {
116
+ // Foster parenting can move a valid-looking node ahead of a source-earlier table. Neither
117
+ // sibling can be trusted as a model-addressable source owner in that recovered ordering.
118
+ for (let i = 1; i < elements.length; i++) {
119
+ const previous = elements[i - 1];
120
+ const current = elements[i];
121
+ if (previous.outerRange && current.outerRange && previous.outerRange.outerEnd > current.outerRange.outerStart) {
122
+ previous.sourceSupported = current.sourceSupported = false;
123
+ }
124
+ }
125
+ }
90
126
  /**
91
127
  * Extends the inner-splice index without changing its published range shape. Missing end tags,
92
128
  * implicit nodes and reordered/foster-parented children cannot establish an opaque source owner.
@@ -94,41 +130,26 @@ const VOID_TAGS = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'i
94
130
  */
95
131
  export function indexSourceElements({ html }, _options = {}) {
96
132
  const fragment = parseFragment(html, { sourceCodeLocationInfo: true });
97
- const walk = (nodes, parentPath, parentStart, parentEnd) => {
133
+ const walk = ({ nodes, parentPath, parentStart, parentEnd }, _options = {}) => {
98
134
  let slot = 0;
99
135
  const elements = nodes.filter(isElement).map(node => {
100
136
  const atom = UNINDEXED_TAGS.has(node.tagName);
101
137
  const path = atom ? null : [...parentPath, slot++];
102
138
  const loc = node.sourceCodeLocation;
103
- const start = loc?.startTag;
104
- const end = loc?.endTag;
105
- const voidTag = VOID_TAGS.has(node.tagName) || (node.namespaceURI !== 'http://www.w3.org/1999/xhtml'
106
- && !!start && html.slice(start.startOffset, start.endOffset).endsWith('/>'));
107
- const outerEnd = voidTag ? start?.endOffset : end?.endOffset;
108
- const boundsValid = !!start && outerEnd !== undefined && start.startOffset >= parentStart && outerEnd <= parentEnd;
109
- const children = walk(node.childNodes, path ?? parentPath, start?.endOffset ?? parentStart, end?.startOffset ?? parentEnd);
110
- // Template content is a separate fragment; it has no ordinary component path/shell here.
111
- const valid = boundsValid && node.tagName !== 'template';
139
+ const children = walk({ nodes: node.childNodes, parentPath: path ?? parentPath, parentStart: loc?.startTag?.endOffset ?? parentStart, parentEnd: loc?.endTag?.startOffset ?? parentEnd }, {});
140
+ const outerRange = sourceOuterRange({ node, html, parentStart, parentEnd }, {});
112
141
  return {
113
142
  tagName: node.tagName,
114
143
  attributes: Object.fromEntries(node.attrs.map(attr => [attr.name, attr.value])),
115
144
  path,
116
- outerRange: valid ? { outerStart: start.startOffset, outerEnd: outerEnd, openingEnd: start.endOffset } : null,
117
- sourceSupported: valid && children.every(child => child.sourceSupported),
145
+ outerRange,
146
+ sourceSupported: !!outerRange && children.every(child => child.sourceSupported),
118
147
  children,
119
148
  };
120
149
  });
121
- // Foster parenting can move a valid-looking node ahead of a source-earlier table. Neither
122
- // sibling can be trusted as a model-addressable source owner in that recovered ordering.
123
- for (let i = 1; i < elements.length; i++) {
124
- const previous = elements[i - 1];
125
- const current = elements[i];
126
- if (previous.outerRange && current.outerRange && previous.outerRange.outerEnd > current.outerRange.outerStart) {
127
- previous.sourceSupported = current.sourceSupported = false;
128
- }
129
- }
150
+ invalidateOverlappingSiblings({ elements }, {});
130
151
  return elements;
131
152
  };
132
- return walk(fragment.childNodes, [], 0, html.length);
153
+ return walk({ nodes: fragment.childNodes, parentPath: [], parentStart: 0, parentEnd: html.length }, {});
133
154
  }
134
155
  //# sourceMappingURL=source-splice.js.map
@@ -1,5 +1,6 @@
1
1
  export const SETTINGS_DIALOG_JA = {
2
2
  "I removed what looked like a key from your message. If it was real, rotate it. Use the secure form to save it.": "メッセージからキーと思われる文字列を削除しました。本物のキーだった場合は再発行してください。保存には安全なフォームを使ってください。",
3
+ // eslint-disable-next-line sonarjs/no-hardcoded-passwords -- Translation of user-facing credential-safety guidance; the value is prose, not a password.
3
4
  "Use the secure form to save keys, tokens, passwords, and secrets. Chat answers are sent to the assistant.": "キー、トークン、パスワード、シークレットの保存には安全なフォームを使ってください。チャットの回答はアシスタントに送信されます。",
4
5
  '# My skill\n\n1. Explain the workflow.\n2. Describe the inputs and outputs.': '# マイスキル\n\n1. ワークフローを説明してください。\n2. 入力と出力を説明してください。',
5
6
  'A custom path override did not work, so this used the automatically detected binary instead.': 'カスタムパスの上書きが機能しなかったため、自動検出されたバイナリを代わりに使用しました。',
@@ -1,5 +1,6 @@
1
1
  export const SETTINGS_DIALOG_ZH_CN = {
2
2
  "I removed what looked like a key from your message. If it was real, rotate it. Use the secure form to save it.": "我已从你的消息中移除疑似密钥的内容。如果是真实密钥,请更换它。请使用安全表单保存。",
3
+ // eslint-disable-next-line sonarjs/no-hardcoded-passwords -- Translation of user-facing credential-safety guidance; the value is prose, not a password.
3
4
  "Use the secure form to save keys, tokens, passwords, and secrets. Chat answers are sent to the assistant.": "请使用安全表单保存密钥、令牌、密码和机密信息。聊天回答会发送给助手。",
4
5
  '# My skill\n\n1. Explain the workflow.\n2. Describe the inputs and outputs.': '# 我的技能\n\n1. 说明工作流程。\n2. 描述输入和输出。',
5
6
  'A custom path override did not work, so this used the automatically detected binary instead.': '自定义路径覆盖未生效,因此改用自动检测到的二进制文件。',
@@ -1,5 +1,6 @@
1
1
  export const SETTINGS_DIALOG_ZH_TW = {
2
2
  "I removed what looked like a key from your message. If it was real, rotate it. Use the secure form to save it.": "我已從你的訊息中移除疑似金鑰的內容。如果是真實金鑰,請更換它。請使用安全表單儲存。",
3
+ // eslint-disable-next-line sonarjs/no-hardcoded-passwords -- Translation of user-facing credential-safety guidance; the value is prose, not a password.
3
4
  "Use the secure form to save keys, tokens, passwords, and secrets. Chat answers are sent to the assistant.": "請使用安全表單儲存金鑰、權杖、密碼和機密資訊。聊天回答會傳送給助理。",
4
5
  '# My skill\n\n1. Explain the workflow.\n2. Describe the inputs and outputs.': '# 我的技能\n\n1. 說明工作流程。\n2. 描述輸入與輸出。',
5
6
  'A custom path override did not work, so this used the automatically detected binary instead.': '自訂路徑覆寫未生效,因此改用自動偵測到的二進位檔。',
@@ -21,25 +21,29 @@ export function resolveSystemLocale(languages, supportedLocales) {
21
21
  const normalized = raw.trim();
22
22
  if (!normalized)
23
23
  continue;
24
- const exact = supportedLocales.find((locale) => locale.toLowerCase() === normalized.toLowerCase());
25
- if (exact)
26
- return exact;
27
- const [language, regionOrScript] = normalized.toLowerCase().split('-');
28
- if (language === 'zh') {
29
- const isTraditionalScript = regionOrScript === 'hant' ||
30
- regionOrScript === 'tw' ||
31
- regionOrScript === 'hk' ||
32
- regionOrScript === 'mo';
33
- const candidate = isTraditionalScript ? 'zh-TW' : 'zh-CN';
34
- if (supportedLocales.includes(candidate))
35
- return candidate;
36
- }
37
- const baseMatch = supportedLocales.find((locale) => locale.toLowerCase().split('-')[0] === language);
38
- if (baseMatch)
39
- return baseMatch;
24
+ const match = matchPreferredLocale({ normalized, supportedLocales });
25
+ if (match)
26
+ return match;
40
27
  }
41
28
  return null;
42
29
  }
30
+ function matchPreferredLocale({ normalized, supportedLocales }, _options = {}) {
31
+ const exact = supportedLocales.find((locale) => locale.toLowerCase() === normalized.toLowerCase());
32
+ if (exact)
33
+ return exact;
34
+ const [language, regionOrScript] = normalized.toLowerCase().split('-');
35
+ if (language === 'zh') {
36
+ const isTraditionalScript = regionOrScript === 'hant' ||
37
+ regionOrScript === 'tw' ||
38
+ regionOrScript === 'hk' ||
39
+ regionOrScript === 'mo';
40
+ const candidate = isTraditionalScript ? 'zh-TW' : 'zh-CN';
41
+ if (supportedLocales.includes(candidate))
42
+ return candidate;
43
+ }
44
+ const baseMatch = supportedLocales.find((locale) => locale.toLowerCase().split('-')[0] === language);
45
+ return baseMatch || null;
46
+ }
43
47
  /** Reads `navigator.languages` (falling back to `navigator.language`). */
44
48
  function browserPreferredLanguages() {
45
49
  if (typeof navigator === 'undefined')