@liminis/editor 0.1.0-rc.1

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 (219) hide show
  1. package/LICENSE +34 -0
  2. package/README.md +376 -0
  3. package/dist/annotations/anchor-align.d.ts +50 -0
  4. package/dist/annotations/anchor-align.js +74 -0
  5. package/dist/annotations/anchor-model.d.ts +81 -0
  6. package/dist/annotations/anchor-model.js +80 -0
  7. package/dist/annotations/anchor-resolver.d.ts +92 -0
  8. package/dist/annotations/anchor-resolver.js +231 -0
  9. package/dist/annotations/block-structure.d.ts +65 -0
  10. package/dist/annotations/block-structure.js +113 -0
  11. package/dist/annotations/types.d.ts +159 -0
  12. package/dist/annotations/types.js +50 -0
  13. package/dist/annotations.d.ts +25 -0
  14. package/dist/annotations.js +27 -0
  15. package/dist/app/App.d.ts +64 -0
  16. package/dist/app/App.js +180 -0
  17. package/dist/app/editor/AmbientCorrectionPlugin.d.ts +18 -0
  18. package/dist/app/editor/AmbientCorrectionPlugin.js +185 -0
  19. package/dist/app/editor/AnchorScrollPlugin.d.ts +16 -0
  20. package/dist/app/editor/AnchorScrollPlugin.js +134 -0
  21. package/dist/app/editor/AnnotationMarkerPlugin.d.ts +24 -0
  22. package/dist/app/editor/AnnotationMarkerPlugin.js +224 -0
  23. package/dist/app/editor/AnnotationPlugin.d.ts +42 -0
  24. package/dist/app/editor/AnnotationPlugin.js +135 -0
  25. package/dist/app/editor/AnnotationSurface.d.ts +34 -0
  26. package/dist/app/editor/AnnotationSurface.js +115 -0
  27. package/dist/app/editor/AssetContext.d.ts +20 -0
  28. package/dist/app/editor/AssetContext.js +81 -0
  29. package/dist/app/editor/BlockClickPlugin.d.ts +8 -0
  30. package/dist/app/editor/BlockClickPlugin.js +126 -0
  31. package/dist/app/editor/CodeBlockPlugin.d.ts +1 -0
  32. package/dist/app/editor/CodeBlockPlugin.js +219 -0
  33. package/dist/app/editor/CorrectionPanelPlugin.d.ts +14 -0
  34. package/dist/app/editor/CorrectionPanelPlugin.js +333 -0
  35. package/dist/app/editor/DragHandlePlugin.d.ts +1 -0
  36. package/dist/app/editor/DragHandlePlugin.js +300 -0
  37. package/dist/app/editor/Editor.d.ts +84 -0
  38. package/dist/app/editor/Editor.js +539 -0
  39. package/dist/app/editor/FrontmatterPlugin.d.ts +19 -0
  40. package/dist/app/editor/FrontmatterPlugin.js +301 -0
  41. package/dist/app/editor/ImageModal.d.ts +13 -0
  42. package/dist/app/editor/ImageModal.js +267 -0
  43. package/dist/app/editor/ImagePlugin.d.ts +3 -0
  44. package/dist/app/editor/ImagePlugin.js +187 -0
  45. package/dist/app/editor/LinkClickPlugin.d.ts +23 -0
  46. package/dist/app/editor/LinkClickPlugin.js +90 -0
  47. package/dist/app/editor/MarkdownShortcutsPlugin.d.ts +49 -0
  48. package/dist/app/editor/MarkdownShortcutsPlugin.js +373 -0
  49. package/dist/app/editor/OrderedTaskListPlugin.d.ts +21 -0
  50. package/dist/app/editor/OrderedTaskListPlugin.js +78 -0
  51. package/dist/app/editor/SearchPlugin.d.ts +1 -0
  52. package/dist/app/editor/SearchPlugin.js +379 -0
  53. package/dist/app/editor/SelectionContextMenuPlugin.d.ts +29 -0
  54. package/dist/app/editor/SelectionContextMenuPlugin.js +160 -0
  55. package/dist/app/editor/SlashMenu.d.ts +11 -0
  56. package/dist/app/editor/SlashMenu.js +494 -0
  57. package/dist/app/editor/SlashMenuPlugin.d.ts +1 -0
  58. package/dist/app/editor/SlashMenuPlugin.js +168 -0
  59. package/dist/app/editor/TableActionsPlugin.d.ts +1 -0
  60. package/dist/app/editor/TableActionsPlugin.js +398 -0
  61. package/dist/app/editor/TogglePlugin.d.ts +1 -0
  62. package/dist/app/editor/TogglePlugin.js +159 -0
  63. package/dist/app/editor/Toolbar.d.ts +10 -0
  64. package/dist/app/editor/Toolbar.js +263 -0
  65. package/dist/app/editor/WikiLinkExistencePlugin.d.ts +17 -0
  66. package/dist/app/editor/WikiLinkExistencePlugin.js +104 -0
  67. package/dist/app/editor/WikiLinkFormatPlugin.d.ts +17 -0
  68. package/dist/app/editor/WikiLinkFormatPlugin.js +203 -0
  69. package/dist/app/editor/annotation-marks.d.ts +177 -0
  70. package/dist/app/editor/annotation-marks.js +750 -0
  71. package/dist/app/editor/annotationCommands.d.ts +13 -0
  72. package/dist/app/editor/annotationCommands.js +11 -0
  73. package/dist/app/editor/c4/C4InteractiveRenderer.d.ts +35 -0
  74. package/dist/app/editor/c4/C4InteractiveRenderer.js +299 -0
  75. package/dist/app/editor/c4/edge-clipping.d.ts +24 -0
  76. package/dist/app/editor/c4/edge-clipping.js +139 -0
  77. package/dist/app/editor/c4/hooks/useC4DiagramDrag.d.ts +38 -0
  78. package/dist/app/editor/c4/hooks/useC4DiagramDrag.js +112 -0
  79. package/dist/app/editor/c4/layout.d.ts +25 -0
  80. package/dist/app/editor/c4/layout.js +839 -0
  81. package/dist/app/editor/c4/parser.d.ts +19 -0
  82. package/dist/app/editor/c4/parser.js +410 -0
  83. package/dist/app/editor/c4/render-to-string.d.ts +24 -0
  84. package/dist/app/editor/c4/render-to-string.js +34 -0
  85. package/dist/app/editor/c4/renderer.d.ts +64 -0
  86. package/dist/app/editor/c4/renderer.js +569 -0
  87. package/dist/app/editor/c4/types.d.ts +203 -0
  88. package/dist/app/editor/c4/types.js +43 -0
  89. package/dist/app/editor/correction-yaml.d.ts +10 -0
  90. package/dist/app/editor/correction-yaml.js +40 -0
  91. package/dist/app/editor/editorNodes.d.ts +9 -0
  92. package/dist/app/editor/editorNodes.js +36 -0
  93. package/dist/app/editor/index.d.ts +1 -0
  94. package/dist/app/editor/index.js +1 -0
  95. package/dist/app/editor/mark-overlap-resolver.d.ts +6 -0
  96. package/dist/app/editor/mark-overlap-resolver.js +45 -0
  97. package/dist/app/editor/nodes/C4Component.d.ts +14 -0
  98. package/dist/app/editor/nodes/C4Component.js +284 -0
  99. package/dist/app/editor/nodes/C4Node.d.ts +32 -0
  100. package/dist/app/editor/nodes/C4Node.js +112 -0
  101. package/dist/app/editor/nodes/CalloutNode.d.ts +23 -0
  102. package/dist/app/editor/nodes/CalloutNode.js +94 -0
  103. package/dist/app/editor/nodes/CustomLinkNode.d.ts +57 -0
  104. package/dist/app/editor/nodes/CustomLinkNode.js +209 -0
  105. package/dist/app/editor/nodes/CustomListItemNode.d.ts +46 -0
  106. package/dist/app/editor/nodes/CustomListItemNode.js +120 -0
  107. package/dist/app/editor/nodes/CustomListNode.d.ts +27 -0
  108. package/dist/app/editor/nodes/CustomListNode.js +54 -0
  109. package/dist/app/editor/nodes/DefinitionListNode.d.ts +44 -0
  110. package/dist/app/editor/nodes/DefinitionListNode.js +151 -0
  111. package/dist/app/editor/nodes/DiagramContextMenu.d.ts +45 -0
  112. package/dist/app/editor/nodes/DiagramContextMenu.js +116 -0
  113. package/dist/app/editor/nodes/EquationComponent.d.ts +17 -0
  114. package/dist/app/editor/nodes/EquationComponent.js +130 -0
  115. package/dist/app/editor/nodes/EquationNode.d.ts +43 -0
  116. package/dist/app/editor/nodes/EquationNode.js +179 -0
  117. package/dist/app/editor/nodes/FootnoteNode.d.ts +40 -0
  118. package/dist/app/editor/nodes/FootnoteNode.js +151 -0
  119. package/dist/app/editor/nodes/FrontmatterNode.d.ts +24 -0
  120. package/dist/app/editor/nodes/FrontmatterNode.js +88 -0
  121. package/dist/app/editor/nodes/HorizontalRuleNode.d.ts +17 -0
  122. package/dist/app/editor/nodes/HorizontalRuleNode.js +57 -0
  123. package/dist/app/editor/nodes/HtmlNode.d.ts +32 -0
  124. package/dist/app/editor/nodes/HtmlNode.js +121 -0
  125. package/dist/app/editor/nodes/ImageComponent.d.ts +11 -0
  126. package/dist/app/editor/nodes/ImageComponent.js +228 -0
  127. package/dist/app/editor/nodes/ImageNode.d.ts +53 -0
  128. package/dist/app/editor/nodes/ImageNode.js +183 -0
  129. package/dist/app/editor/nodes/ListItemParagraphBreakNode.d.ts +42 -0
  130. package/dist/app/editor/nodes/ListItemParagraphBreakNode.js +95 -0
  131. package/dist/app/editor/nodes/MermaidComponent.d.ts +11 -0
  132. package/dist/app/editor/nodes/MermaidComponent.js +216 -0
  133. package/dist/app/editor/nodes/MermaidNode.d.ts +27 -0
  134. package/dist/app/editor/nodes/MermaidNode.js +96 -0
  135. package/dist/app/editor/nodes/ToggleNode.d.ts +56 -0
  136. package/dist/app/editor/nodes/ToggleNode.js +237 -0
  137. package/dist/app/editor/nodes/diagram-context-menu.d.ts +11 -0
  138. package/dist/app/editor/nodes/diagram-context-menu.js +70 -0
  139. package/dist/app/editor/nodes/diagram-utils.d.ts +13 -0
  140. package/dist/app/editor/nodes/diagram-utils.js +29 -0
  141. package/dist/app/editor/nodes/index.d.ts +30 -0
  142. package/dist/app/editor/nodes/index.js +17 -0
  143. package/dist/app/editor/utils.d.ts +2 -0
  144. package/dist/app/editor/utils.js +17 -0
  145. package/dist/app/mapper/index.d.ts +3 -0
  146. package/dist/app/mapper/index.js +2 -0
  147. package/dist/app/mapper/lexicalToMdast.d.ts +62 -0
  148. package/dist/app/mapper/lexicalToMdast.js +1937 -0
  149. package/dist/app/mapper/mdastToLexical.d.ts +31 -0
  150. package/dist/app/mapper/mdastToLexical.js +1075 -0
  151. package/dist/contract.d.ts +8 -0
  152. package/dist/contract.js +8 -0
  153. package/dist/headless.d.ts +40 -0
  154. package/dist/headless.js +45 -0
  155. package/dist/host/context.d.ts +20 -0
  156. package/dist/host/context.js +24 -0
  157. package/dist/host/defaults.d.ts +21 -0
  158. package/dist/host/defaults.js +47 -0
  159. package/dist/host/messages.d.ts +26 -0
  160. package/dist/host/messages.js +47 -0
  161. package/dist/host/types.d.ts +75 -0
  162. package/dist/host/types.js +13 -0
  163. package/dist/index.d.ts +35 -0
  164. package/dist/index.js +51 -0
  165. package/dist/markdown/annotate-sentinels.d.ts +42 -0
  166. package/dist/markdown/annotate-sentinels.js +86 -0
  167. package/dist/markdown/parse.d.ts +56 -0
  168. package/dist/markdown/parse.js +516 -0
  169. package/dist/markdown/stringify.d.ts +7 -0
  170. package/dist/markdown/stringify.js +439 -0
  171. package/dist/markdown/vendor/mdast-util-wiki-link/LICENSE +21 -0
  172. package/dist/markdown/vendor/mdast-util-wiki-link/README.md +41 -0
  173. package/dist/markdown/vendor/mdast-util-wiki-link/from-markdown.d.ts +72 -0
  174. package/dist/markdown/vendor/mdast-util-wiki-link/from-markdown.js +127 -0
  175. package/dist/markdown/vendor/mdast-util-wiki-link/index.d.ts +8 -0
  176. package/dist/markdown/vendor/mdast-util-wiki-link/index.js +6 -0
  177. package/dist/markdown/vendor/mdast-util-wiki-link/to-markdown.d.ts +17 -0
  178. package/dist/markdown/vendor/mdast-util-wiki-link/to-markdown.js +33 -0
  179. package/dist/markdown.d.ts +28 -0
  180. package/dist/markdown.js +44 -0
  181. package/dist/mathjax-config.d.ts +89 -0
  182. package/dist/mathjax-config.js +133 -0
  183. package/dist/nodes.d.ts +34 -0
  184. package/dist/nodes.js +34 -0
  185. package/dist/stores/correctionStore.d.ts +15 -0
  186. package/dist/stores/correctionStore.js +12 -0
  187. package/dist/stores/editorStore.d.ts +16 -0
  188. package/dist/stores/editorStore.js +27 -0
  189. package/dist/styles.css +2477 -0
  190. package/dist/types.d.ts +296 -0
  191. package/dist/types.js +91 -0
  192. package/dist/utils/file-types.d.ts +10 -0
  193. package/dist/utils/file-types.js +46 -0
  194. package/docs/annotations.md +134 -0
  195. package/docs/decisions/README.md +72 -0
  196. package/docs/decisions/adr-002.md +27 -0
  197. package/docs/decisions/adr-007.md +27 -0
  198. package/docs/decisions/adr-010.md +26 -0
  199. package/docs/decisions/adr-024.md +124 -0
  200. package/docs/decisions/adr-024b.md +0 -0
  201. package/docs/decisions/adr-025.md +103 -0
  202. package/docs/decisions/adr-027.md +66 -0
  203. package/docs/decisions/adr-057.md +67 -0
  204. package/docs/decisions/adr-070.md +131 -0
  205. package/docs/decisions/adr-075.md +410 -0
  206. package/docs/decisions/adr-076.md +183 -0
  207. package/docs/decisions/adr-077.md +410 -0
  208. package/docs/decisions/adr-078.md +243 -0
  209. package/docs/decisions/adr-079.md +167 -0
  210. package/docs/decisions/adr-080.md +325 -0
  211. package/docs/decisions/adr-081.md +108 -0
  212. package/docs/decisions/adr-082.md +123 -0
  213. package/docs/decisions/adr-083.md +204 -0
  214. package/docs/decisions/adr-084.md +179 -0
  215. package/docs/editor-api.md +106 -0
  216. package/docs/markdown-pipeline.md +282 -0
  217. package/docs/provenance.md +106 -0
  218. package/docs/zusammen-editor-capability-map.md +232 -0
  219. package/package.json +175 -0
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `@liminis/editor/contract` — the host-message contract, and nothing else.
3
+ *
4
+ * A DOM- and React-free entry point so a preload script (or any other boundary
5
+ * that must not pull renderer code into its bundle) can `import type` the
6
+ * message shapes without dragging in Lexical.
7
+ */
8
+ export * from './types.js';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `@liminis/editor/contract` — the host-message contract, and nothing else.
3
+ *
4
+ * A DOM- and React-free entry point so a preload script (or any other boundary
5
+ * that must not pull renderer code into its bundle) can `import type` the
6
+ * message shapes without dragging in Lexical.
7
+ */
8
+ export * from './types.js';
@@ -0,0 +1,40 @@
1
+ /**
2
+ * `@liminis/editor/headless` — the DOM-free surface.
3
+ *
4
+ * Consumed by the Electron **main** process, which declares `lib: ["ES2024"]`
5
+ * and no DOM libs. Nothing exported from here may reference a DOM global or
6
+ * transitively import Lexical, `react-dom` (client), or anything that touches
7
+ * `document`/`window`. `renderC4DiagramToSVG` uses `react-dom/server`, which is
8
+ * DOM-free; the MathJax *lite* adaptor is typed over `LiteElement`, not
9
+ * `HTMLElement`, which is why only the lite factory appears here.
10
+ *
11
+ * **Known exception, carried over unchanged: `./mathjax-config`.** Its
12
+ * `createLiteAdaptorDocument` export is DOM-free, but the module statically
13
+ * imports `browserAdaptor` alongside `liteAdaptor` at top level, so anything
14
+ * that loads this entry evaluates a DOM-adaptor module. That is *not* a
15
+ * regression introduced by the extraction — before it, `main/remote-session/
16
+ * routes.ts` imported `shared/mathjax-config` directly and pulled in the very
17
+ * same graph; main's runtime imports are byte-for-byte what they were. It is
18
+ * called out here because this entry's contract would otherwise read as
19
+ * stronger than it is. Splitting the module into lite and browser halves is the
20
+ * real fix and belongs with #940's build work, where the entry graphs get
21
+ * enforced rather than documented.
22
+ *
23
+ * **This contract is by convention, not by compiler enforcement.** Main's
24
+ * `lib: ["ES2024"]` does *not* currently make `Document`/`HTMLElement` unresolvable
25
+ * — some dependency already in `src/main`'s own graph pulls the DOM lib in (this
26
+ * predates the package extraction; `@types/node` alone does not do it). So a
27
+ * DOM-typed export added here would compile silently rather than failing main's
28
+ * typecheck. Widening this entry therefore requires reading the addition's import
29
+ * graph yourself. The fix for a violation is always to prune that graph, never to
30
+ * widen main's `lib`.
31
+ */
32
+ export { renderC4DiagramToSVG } from './app/editor/c4/render-to-string.js';
33
+ export { parseC4, validateC4 } from './app/editor/c4/parser.js';
34
+ export { layoutC4Diagram } from './app/editor/c4/layout.js';
35
+ export { isSystem, isContainer, isComponent, isPerson, isExternal, isBoundary, } from './app/editor/c4/types.js';
36
+ export type { C4ElementType, C4Shape, C4Direction, C4Style, C4Properties, C4Element, C4Relationship, C4Diagram, Point, LayoutNode, LayoutEdge, LayoutResult, LayoutOptions, ParseError, ParseResult, ManualLayout, } from './app/editor/c4/types.js';
37
+ export { createLiteAdaptorDocument, TEX_PACKAGES } from './mathjax-config.js';
38
+ export type { TeXPackage, MathDocument, LiteMathJaxInstance } from './mathjax-config.js';
39
+ export { getFileType } from './utils/file-types.js';
40
+ export type { FileType } from './utils/file-types.js';
@@ -0,0 +1,45 @@
1
+ /**
2
+ * `@liminis/editor/headless` — the DOM-free surface.
3
+ *
4
+ * Consumed by the Electron **main** process, which declares `lib: ["ES2024"]`
5
+ * and no DOM libs. Nothing exported from here may reference a DOM global or
6
+ * transitively import Lexical, `react-dom` (client), or anything that touches
7
+ * `document`/`window`. `renderC4DiagramToSVG` uses `react-dom/server`, which is
8
+ * DOM-free; the MathJax *lite* adaptor is typed over `LiteElement`, not
9
+ * `HTMLElement`, which is why only the lite factory appears here.
10
+ *
11
+ * **Known exception, carried over unchanged: `./mathjax-config`.** Its
12
+ * `createLiteAdaptorDocument` export is DOM-free, but the module statically
13
+ * imports `browserAdaptor` alongside `liteAdaptor` at top level, so anything
14
+ * that loads this entry evaluates a DOM-adaptor module. That is *not* a
15
+ * regression introduced by the extraction — before it, `main/remote-session/
16
+ * routes.ts` imported `shared/mathjax-config` directly and pulled in the very
17
+ * same graph; main's runtime imports are byte-for-byte what they were. It is
18
+ * called out here because this entry's contract would otherwise read as
19
+ * stronger than it is. Splitting the module into lite and browser halves is the
20
+ * real fix and belongs with #940's build work, where the entry graphs get
21
+ * enforced rather than documented.
22
+ *
23
+ * **This contract is by convention, not by compiler enforcement.** Main's
24
+ * `lib: ["ES2024"]` does *not* currently make `Document`/`HTMLElement` unresolvable
25
+ * — some dependency already in `src/main`'s own graph pulls the DOM lib in (this
26
+ * predates the package extraction; `@types/node` alone does not do it). So a
27
+ * DOM-typed export added here would compile silently rather than failing main's
28
+ * typecheck. Widening this entry therefore requires reading the addition's import
29
+ * graph yourself. The fix for a violation is always to prune that graph, never to
30
+ * widen main's `lib`.
31
+ */
32
+ // C4 diagram subsystem — parse, layout, and server-side SVG rendering
33
+ export { renderC4DiagramToSVG } from './app/editor/c4/render-to-string.js';
34
+ export { parseC4, validateC4 } from './app/editor/c4/parser.js';
35
+ export { layoutC4Diagram } from './app/editor/c4/layout.js';
36
+ export { isSystem, isContainer, isComponent, isPerson, isExternal, isBoundary, } from './app/editor/c4/types.js';
37
+ // MathJax lite-adaptor factory (equation rendering, server-side export).
38
+ //
39
+ // `createBrowserAdaptorDocument` / `BrowserMathJaxInstance` are deliberately NOT
40
+ // re-exported here: they are typed over `Document` and `HTMLElement`, which would
41
+ // contradict this entry's DOM-free contract. Their only consumer is
42
+ // `EquationComponent.tsx` inside the package, which imports them directly.
43
+ export { createLiteAdaptorDocument, TEX_PACKAGES } from './mathjax-config.js';
44
+ // File-type detection, used by both the mdast mapper and app-side callers
45
+ export { getFileType } from './utils/file-types.js';
@@ -0,0 +1,20 @@
1
+ /**
2
+ * React context carrying the injected host services.
3
+ *
4
+ * Context rather than prop drilling: five separate mount sites in liminis-app
5
+ * plus ~20 deeply nested plugins would otherwise have to thread a services
6
+ * object through `App` → `Editor` → every plugin (see ADR-075).
7
+ */
8
+ import { type ReactNode } from 'react';
9
+ import type { EditorHostServices, ResolvedEditorHostServices } from './types.js';
10
+ export interface EditorHostProviderProps {
11
+ /** Host services. Any omitted member falls back to a safe default. */
12
+ services?: EditorHostServices;
13
+ children: ReactNode;
14
+ }
15
+ export declare function EditorHostProvider({ services, children }: EditorHostProviderProps): import("react").JSX.Element;
16
+ /**
17
+ * Access the host services. Usable without a provider — an absent provider
18
+ * yields the same all-defaults object a provider with no services would.
19
+ */
20
+ export declare function useEditorHost(): ResolvedEditorHostServices;
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * React context carrying the injected host services.
4
+ *
5
+ * Context rather than prop drilling: five separate mount sites in liminis-app
6
+ * plus ~20 deeply nested plugins would otherwise have to thread a services
7
+ * object through `App` → `Editor` → every plugin (see ADR-075).
8
+ */
9
+ import { createContext, useContext, useMemo } from 'react';
10
+ import { resolveHostServices } from './defaults.js';
11
+ const EditorHostContext = createContext(null);
12
+ export function EditorHostProvider({ services, children }) {
13
+ const resolved = useMemo(() => resolveHostServices(services), [services]);
14
+ return _jsx(EditorHostContext.Provider, { value: resolved, children: children });
15
+ }
16
+ /**
17
+ * Access the host services. Usable without a provider — an absent provider
18
+ * yields the same all-defaults object a provider with no services would.
19
+ */
20
+ export function useEditorHost() {
21
+ const value = useContext(EditorHostContext);
22
+ return value ?? FALLBACK_SERVICES;
23
+ }
24
+ const FALLBACK_SERVICES = resolveHostServices(undefined);
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Safe defaults for every host service.
3
+ *
4
+ * FR-003 / US2-AC2: a host that supplies no logger, no bridge and no resolvers
5
+ * must still get a working `<Editor>`. Nothing here throws, and nothing here
6
+ * touches a global that only exists inside Electron.
7
+ */
8
+ import type { EditorHostBridge, EditorHostServices, EditorLoggerFactory, ResolvedEditorHostServices } from './types.js';
9
+ /** Console-backed logger namespaced the same way liminis-app's logger is. */
10
+ export declare const defaultLoggerFactory: EditorLoggerFactory;
11
+ /**
12
+ * Bridge that drops outbound messages and never delivers inbound ones.
13
+ *
14
+ * This is the correct standalone behaviour: with no host there is nothing to
15
+ * apply edits to, so the editor simply behaves as an uncontrolled component.
16
+ */
17
+ export declare const noopBridge: EditorHostBridge;
18
+ /** Last-resort error surface when the host provides no toast mechanism. */
19
+ export declare const defaultNotifyError: (message: string, description?: string) => void;
20
+ /** Fill in every unsupplied service with its safe default. */
21
+ export declare function resolveHostServices(services: EditorHostServices | undefined): ResolvedEditorHostServices;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Safe defaults for every host service.
3
+ *
4
+ * FR-003 / US2-AC2: a host that supplies no logger, no bridge and no resolvers
5
+ * must still get a working `<Editor>`. Nothing here throws, and nothing here
6
+ * touches a global that only exists inside Electron.
7
+ */
8
+ /** Console-backed logger namespaced the same way liminis-app's logger is. */
9
+ export const defaultLoggerFactory = (namespace) => ({
10
+ debug: (...args) => console.debug(`[${namespace}]`, ...args),
11
+ info: (...args) => console.info(`[${namespace}]`, ...args),
12
+ warn: (...args) => console.warn(`[${namespace}]`, ...args),
13
+ error: (...args) => console.error(`[${namespace}]`, ...args),
14
+ });
15
+ /**
16
+ * Bridge that drops outbound messages and never delivers inbound ones.
17
+ *
18
+ * This is the correct standalone behaviour: with no host there is nothing to
19
+ * apply edits to, so the editor simply behaves as an uncontrolled component.
20
+ */
21
+ export const noopBridge = {
22
+ postMessage: () => { },
23
+ addMessageHandler: () => () => { },
24
+ };
25
+ /** Last-resort error surface when the host provides no toast mechanism. */
26
+ export const defaultNotifyError = (message, description) => {
27
+ if (description) {
28
+ console.error(message, description);
29
+ }
30
+ else {
31
+ console.error(message);
32
+ }
33
+ };
34
+ /** Fill in every unsupplied service with its safe default. */
35
+ export function resolveHostServices(services) {
36
+ return {
37
+ bridge: services?.bridge ?? noopBridge,
38
+ logger: services?.logger ?? defaultLoggerFactory,
39
+ notifyError: services?.notifyError ?? defaultNotifyError,
40
+ // Left undefined on purpose when absent: the plugins that use these treat
41
+ // "service missing" as "feature unavailable, do nothing" — exactly what the
42
+ // pre-extraction host-API presence guards did.
43
+ resolveWikiLinks: services?.resolveWikiLinks,
44
+ onScrollToAnchor: services?.onScrollToAnchor,
45
+ corrections: services?.corrections,
46
+ };
47
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Host message helpers, rebuilt on top of `EditorHostBridge.postMessage`.
3
+ *
4
+ * These used to live in `liminis-app/src/editor/messaging-electron.ts`. Keeping
5
+ * them package-side means every host adapter only has to implement
6
+ * `postMessage` + `addMessageHandler`, and the wire payloads are identical by
7
+ * construction rather than by convention.
8
+ */
9
+ import type { TextEdit, UIToHostMessage } from '../types.js';
10
+ import type { EditorHostBridge, EditorLogger } from './types.js';
11
+ /**
12
+ * Members are declared as function-typed properties rather than methods on
13
+ * purpose: callers destructure them (`const { openLink } = useHostMessages()`),
14
+ * which the `unbound-method` lint rule rightly flags for real methods.
15
+ */
16
+ export interface HostMessageApi {
17
+ postMessage: (message: UIToHostMessage) => void;
18
+ requestInit: () => void;
19
+ requestSettings: () => void;
20
+ applyTextEdits: (edits: TextEdit[], reason: 'typing' | 'drag' | 'paste' | 'format') => void;
21
+ writeAsset: (dataUri: string, suggestedName?: string) => void;
22
+ openLink: (url: string) => void;
23
+ }
24
+ export declare function createHostMessageApi(bridge: EditorHostBridge, log: EditorLogger): HostMessageApi;
25
+ /** Hook form of {@link createHostMessageApi}, bound to the ambient host services. */
26
+ export declare function useHostMessages(): HostMessageApi;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Host message helpers, rebuilt on top of `EditorHostBridge.postMessage`.
3
+ *
4
+ * These used to live in `liminis-app/src/editor/messaging-electron.ts`. Keeping
5
+ * them package-side means every host adapter only has to implement
6
+ * `postMessage` + `addMessageHandler`, and the wire payloads are identical by
7
+ * construction rather than by convention.
8
+ */
9
+ import { useMemo } from 'react';
10
+ import { useEditorHost } from './context.js';
11
+ export function createHostMessageApi(bridge, log) {
12
+ const postMessage = (message) => {
13
+ bridge.postMessage(message);
14
+ };
15
+ return {
16
+ postMessage,
17
+ requestInit: () => {
18
+ log.info('Requesting init from host');
19
+ postMessage({ type: 'REQUEST_INIT' });
20
+ },
21
+ requestSettings: () => {
22
+ postMessage({ type: 'REQUEST_SETTINGS' });
23
+ },
24
+ applyTextEdits: (edits, reason) => {
25
+ log.debug('applyTextEdits', {
26
+ reason,
27
+ edits: edits.map((e) => ({ start: e.start, end: e.end, newTextLen: e.newText.length })),
28
+ });
29
+ postMessage({ type: 'APPLY_TEXT_EDITS', edits, reason });
30
+ },
31
+ writeAsset: (dataUri, suggestedName) => {
32
+ postMessage({ type: 'WRITE_ASSET', dataUri, suggestedName });
33
+ },
34
+ openLink: (url) => {
35
+ log.debug('openLink', { url });
36
+ postMessage({ type: 'OPEN_LINK', url });
37
+ },
38
+ };
39
+ }
40
+ /** Hook form of {@link createHostMessageApi}, bound to the ambient host services. */
41
+ export function useHostMessages() {
42
+ const { bridge, logger } = useEditorHost();
43
+ return useMemo(() => createHostMessageApi(bridge, makeLog(logger)), [bridge, logger]);
44
+ }
45
+ function makeLog(logger) {
46
+ return logger('slashmd/messaging');
47
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * The host seam for `@liminis/editor`.
3
+ *
4
+ * The package boundary is drawn at **persistence**: anything about text ranges,
5
+ * marks, rendering and in-document UX lives in this package; storage, lifecycle,
6
+ * identity and higher-level panels live in the embedding application. Everything
7
+ * the package needs from its host crosses that seam through the interfaces below
8
+ * (see ADR-075).
9
+ *
10
+ * Every member is optional and has a safe default (see `./defaults.ts`), so
11
+ * `<Editor>` renders in a host that supplies nothing at all.
12
+ */
13
+ import type { HostToUIMessage, UIToHostMessage } from '../types.js';
14
+ /** Minimal logging surface. Structurally compatible with liminis-app's `Logger`. */
15
+ export interface EditorLogger {
16
+ debug: (...args: unknown[]) => void;
17
+ info: (...args: unknown[]) => void;
18
+ warn: (...args: unknown[]) => void;
19
+ error: (...args: unknown[]) => void;
20
+ }
21
+ /** Factory producing a namespaced logger, e.g. `createLogger('slashmd/App')`. */
22
+ export type EditorLoggerFactory = (namespace: string) => EditorLogger;
23
+ /**
24
+ * The channel through which the package talks to its embedding environment.
25
+ *
26
+ * Deliberately two methods: all higher-level helpers (`requestInit`,
27
+ * `applyTextEdits`, `writeAsset`, `openLink`) are built on `postMessage`
28
+ * inside the package, so every host adapter emits byte-identical payloads.
29
+ */
30
+ export interface EditorHostBridge {
31
+ postMessage(message: UIToHostMessage): void;
32
+ /** Subscribe to host → UI messages. Returns an unsubscribe function. */
33
+ addMessageHandler(handler: (message: HostToUIMessage) => void): () => void;
34
+ }
35
+ /**
36
+ * Persistence and knowledge-graph services backing the correction feature.
37
+ *
38
+ * The in-editor correction UI is package-side; reading and writing the
39
+ * corrections document and driving the knowledge tools stays host-side. That
40
+ * split is the package boundary drawn at persistence (`docs/decisions/adr-075.md`):
41
+ * this package renders and edits, and never decides where bytes live or which
42
+ * knowledge pipeline consumes them.
43
+ */
44
+ export interface CorrectionHostServices {
45
+ /** Raw YAML text of the corrections file, or `null` when it does not exist. */
46
+ readCorrections(): Promise<string | null>;
47
+ /** Persist the corrections file. The host owns mkdir and atomic-write semantics. */
48
+ writeCorrections(yaml: string): Promise<void>;
49
+ /** Entity-name suggestions for an autocomplete query. */
50
+ suggestEntities(query: string, numResults: number): Promise<string[]>;
51
+ /** Passage/source-name suggestions for an autocomplete query. */
52
+ suggestPassages(query: string, numResults: number, minScore: number): Promise<string[]>;
53
+ /** Apply pending corrections to the knowledge graph. Resolves true on success. */
54
+ applyCorrections(): Promise<boolean>;
55
+ }
56
+ /** Everything the package may ask of its host. */
57
+ export interface EditorHostServices {
58
+ /** Host message channel (Electron IPC in liminis-app). */
59
+ bridge?: EditorHostBridge;
60
+ /** Namespaced logger factory. */
61
+ logger?: EditorLoggerFactory;
62
+ /**
63
+ * Resolve wiki-link targets to existing paths. Returns a map of target →
64
+ * resolved path, or `null` for targets that do not resolve.
65
+ */
66
+ resolveWikiLinks?: (targets: string[]) => Promise<Record<string, string | null>>;
67
+ /** Subscribe to host-driven "scroll to this anchor" requests. */
68
+ onScrollToAnchor?: (callback: (anchor: string) => void) => () => void;
69
+ /** Surface a user-visible error (a toast in liminis-app). */
70
+ notifyError?: (message: string, description?: string) => void;
71
+ /** Correction persistence + knowledge-graph services. */
72
+ corrections?: CorrectionHostServices;
73
+ }
74
+ /** `EditorHostServices` with every member resolved to a concrete implementation. */
75
+ export type ResolvedEditorHostServices = Required<Pick<EditorHostServices, 'bridge' | 'logger' | 'notifyError'>> & Pick<EditorHostServices, 'resolveWikiLinks' | 'onScrollToAnchor' | 'corrections'>;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The host seam for `@liminis/editor`.
3
+ *
4
+ * The package boundary is drawn at **persistence**: anything about text ranges,
5
+ * marks, rendering and in-document UX lives in this package; storage, lifecycle,
6
+ * identity and higher-level panels live in the embedding application. Everything
7
+ * the package needs from its host crosses that seam through the interfaces below
8
+ * (see ADR-075).
9
+ *
10
+ * Every member is optional and has a safe default (see `./defaults.ts`), so
11
+ * `<Editor>` renders in a host that supplies nothing at all.
12
+ */
13
+ export {};
@@ -0,0 +1,35 @@
1
+ /**
2
+ * `@liminis/editor` — the curated public entry point.
3
+ *
4
+ * Consumers import from here (or from one of the other three declared
5
+ * subpaths: `./headless`, `./contract`, `./styles.css`) and never from a deep
6
+ * path into package internals (FR-002 / SC-002). If a consumer needs something
7
+ * that is not exported here, the answer is to add a considered export — never a
8
+ * deep import, and never a re-exported internals barrel.
9
+ */
10
+ export { EditorHostProvider, useEditorHost } from './host/context.js';
11
+ export type { EditorHostProviderProps } from './host/context.js';
12
+ export { resolveHostServices, defaultLoggerFactory, defaultNotifyError, noopBridge, } from './host/defaults.js';
13
+ export { createHostMessageApi, useHostMessages } from './host/messages.js';
14
+ export type { HostMessageApi } from './host/messages.js';
15
+ export type { CorrectionHostServices, EditorHostBridge, EditorHostServices, EditorLogger, EditorLoggerFactory, ResolvedEditorHostServices, } from './host/types.js';
16
+ export { App } from './app/App.js';
17
+ export type { CursorState } from './app/App.js';
18
+ export { Editor } from './app/editor/index.js';
19
+ export type { SweepFn } from './app/editor/AmbientCorrectionPlugin.js';
20
+ export type { SelectionContextMenuEvent } from './app/editor/SelectionContextMenuPlugin.js';
21
+ export { OPEN_ANNOTATION_COMPOSER_COMMAND } from './app/editor/annotationCommands.js';
22
+ export type { AnnotationCreateEvent } from './app/editor/AnnotationPlugin.js';
23
+ export type { Annotation, AnnotationKind, AnnotationKindConfig, AnnotationKindConfigs, AnnotationCreateAffordance, AnnotationMarkerStyle, AnnotationPresentation, AnnotationEditorHandle, MarkerTarget, } from './annotations/types.js';
24
+ export { parseMarkdown } from './markdown/parse.js';
25
+ export type { ParseOptions, ParseResult } from './markdown/parse.js';
26
+ export { isParagraph, isHeading, isList, isListItem, isBlockquote, isCode, isThematicBreak, isTable, isImage, isLink, isHtml, isText, isStrong, isEmphasis, isInlineCode, isDelete, } from './markdown/parse.js';
27
+ export { stringifyMarkdown } from './markdown/stringify.js';
28
+ export type { StringifyOptions } from './markdown/stringify.js';
29
+ export { importMarkdownToLexical, exportLexicalToMdast } from './app/mapper/index.js';
30
+ export type { ExportOptions, WikiLinkPromotionMode } from './app/mapper/index.js';
31
+ export * from './types.js';
32
+ export { useEditorStore } from './stores/editorStore.js';
33
+ export { findSvgElement } from './app/editor/nodes/diagram-utils.js';
34
+ export * from './headless.js';
35
+ export type { ParseResult as C4ParseResult, ParseError as C4ParseError, } from './app/editor/c4/types.js';
package/dist/index.js ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * `@liminis/editor` — the curated public entry point.
3
+ *
4
+ * Consumers import from here (or from one of the other three declared
5
+ * subpaths: `./headless`, `./contract`, `./styles.css`) and never from a deep
6
+ * path into package internals (FR-002 / SC-002). If a consumer needs something
7
+ * that is not exported here, the answer is to add a considered export — never a
8
+ * deep import, and never a re-exported internals barrel.
9
+ */
10
+ // --- Host seam -------------------------------------------------------------
11
+ // Injected services are how the package reaches its embedding environment.
12
+ export { EditorHostProvider, useEditorHost } from './host/context.js';
13
+ export { resolveHostServices, defaultLoggerFactory, defaultNotifyError, noopBridge, } from './host/defaults.js';
14
+ export { createHostMessageApi, useHostMessages } from './host/messages.js';
15
+ // --- Components ------------------------------------------------------------
16
+ export { App } from './app/App.js';
17
+ export { Editor } from './app/editor/index.js';
18
+ // --- Annotations, React surface (ADR-077) ---------------------------------
19
+ // The kind-configuration types a host needs to turn the mechanism on, plus the
20
+ // create-event shape. The DOM-free anchor model, resolver and marker-target
21
+ // helpers live on the `./annotations` subpath instead, so they stay callable
22
+ // outside a rendered editor.
23
+ export { OPEN_ANNOTATION_COMPOSER_COMMAND } from './app/editor/annotationCommands.js';
24
+ // --- Markdown pipeline -----------------------------------------------------
25
+ export { parseMarkdown } from './markdown/parse.js';
26
+ export { isParagraph, isHeading, isList, isListItem, isBlockquote, isCode, isThematicBreak, isTable, isImage, isLink, isHtml, isText, isStrong, isEmphasis, isInlineCode, isDelete, } from './markdown/parse.js';
27
+ export { stringifyMarkdown } from './markdown/stringify.js';
28
+ export { importMarkdownToLexical, exportLexicalToMdast } from './app/mapper/index.js';
29
+ // --- Host message contract -------------------------------------------------
30
+ export * from './types.js';
31
+ // --- Editor-owned stores ---------------------------------------------------
32
+ // `useEditorStore` is public: `EditorColumn` drives the frontmatter tray from
33
+ // the app toolbar.
34
+ //
35
+ // `useCorrectionStore` is deliberately NOT exported. ADR-075 justifies keeping
36
+ // it package-side on the grounds that it has no consumer outside the two
37
+ // editor plugins; publishing it would invite exactly the app-side consumer that
38
+ // invalidates that reasoning, with nothing at the boundary to catch it.
39
+ export { useEditorStore } from './stores/editorStore.js';
40
+ // --- Utilities the host also needs ----------------------------------------
41
+ export { findSvgElement } from './app/editor/nodes/diagram-utils.js';
42
+ // --- DOM-free surface, re-exported for renderer-side convenience ----------
43
+ // Renderer consumers may import these from here; the Electron main process must
44
+ // use '@liminis/editor/headless' instead (it has no DOM libs).
45
+ //
46
+ // NOTE: `./headless` also exports a `ParseResult` (the C4 parser's `{diagram,
47
+ // errors}`), which this entry's explicit markdown `ParseResult` above shadows —
48
+ // explicit exports win over `export *` in both ES and TypeScript. The C4 shape is
49
+ // therefore reachable from here only under the alias below. `./headless` itself
50
+ // exports it unaliased, where there is no collision.
51
+ export * from './headless.js';
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The Unicode Private-Use-Area sentinel tokens annotated-serialize mode
3
+ * brackets a live annotation mark's content with, plus the one helper that
4
+ * removes them again.
5
+ *
6
+ * They live here — below both the mapper and the stringifier — because the
7
+ * *invariant* they exist to serve reaches into both. Annotate mode must differ
8
+ * from a plain export by the tokens and nothing else (see
9
+ * `lexicalToMdast.ts`'s annotated-serialize header): the whole recovered-range
10
+ * calculation in `annotation-marks.ts` is offset arithmetic against a plain
11
+ * export of the same state. So every *decision* either module makes from a
12
+ * piece of text — "does this list item already carry an explicit `[ ]`
13
+ * marker?", "does this paragraph end in a colon?" — has to be made against the
14
+ * sentinel-free form, or enabling annotate mode changes output somewhere the
15
+ * caller never looks (Liminis #970).
16
+ */
17
+ export declare const SENTINEL_OPEN_START = "\uE000";
18
+ export declare const SENTINEL_OPEN_END = "\uE001";
19
+ export declare const SENTINEL_CLOSE_START = "\uE002";
20
+ export declare const SENTINEL_CLOSE_END = "\uE003";
21
+ /**
22
+ * `text` with every annotate-mode sentinel token removed. A no-op — and cheap
23
+ * — for the overwhelmingly common case of a string that contains none, which
24
+ * is every string on the disk-write path.
25
+ */
26
+ export declare function stripAnnotateSentinels(text: string): string;
27
+ export interface SentinelSplitPart {
28
+ text: string;
29
+ isSentinel: boolean;
30
+ }
31
+ /**
32
+ * Split `text` into alternating sentinel-token and plain-text parts.
33
+ *
34
+ * Used by `stringify.ts`'s force-escape handling (#17): a force-escaped
35
+ * text node's `getTextContent()` can have a sentinel token spliced onto
36
+ * either end by `sentinelAugmentedText` (annotate-serialize mode, see the
37
+ * module doc comment above) before the force-escape data is read, so
38
+ * treating the whole string as "one force-escaped run" would wrap the
39
+ * token's own characters in escape placeholders too. Splitting first lets
40
+ * the caller escape only the real content and leave token parts verbatim.
41
+ */
42
+ export declare function splitOnSentinelTokens(text: string): SentinelSplitPart[];
@@ -0,0 +1,86 @@
1
+ /**
2
+ * The Unicode Private-Use-Area sentinel tokens annotated-serialize mode
3
+ * brackets a live annotation mark's content with, plus the one helper that
4
+ * removes them again.
5
+ *
6
+ * They live here — below both the mapper and the stringifier — because the
7
+ * *invariant* they exist to serve reaches into both. Annotate mode must differ
8
+ * from a plain export by the tokens and nothing else (see
9
+ * `lexicalToMdast.ts`'s annotated-serialize header): the whole recovered-range
10
+ * calculation in `annotation-marks.ts` is offset arithmetic against a plain
11
+ * export of the same state. So every *decision* either module makes from a
12
+ * piece of text — "does this list item already carry an explicit `[ ]`
13
+ * marker?", "does this paragraph end in a colon?" — has to be made against the
14
+ * sentinel-free form, or enabling annotate mode changes output somewhere the
15
+ * caller never looks (Liminis #970).
16
+ */
17
+ export const SENTINEL_OPEN_START = '\u{E000}';
18
+ export const SENTINEL_OPEN_END = '\u{E001}';
19
+ export const SENTINEL_CLOSE_START = '\u{E002}';
20
+ export const SENTINEL_CLOSE_END = '\u{E003}';
21
+ /**
22
+ * Matches one whole sentinel token — an open pair or a close pair, with an
23
+ * annotation id between them.
24
+ *
25
+ * Two things keep this from eating real content, and both are load-bearing:
26
+ *
27
+ * 1. **The id is a negated character class** excluding all four delimiters, so
28
+ * the run cannot cross a delimiter and the match is forced to end at the
29
+ * first one it meets. Greediness is irrelevant for that reason — do not
30
+ * "simplify" this to `.*?`. A lazy dot matches delimiters happily, so on
31
+ * `…␀b␀id␁…` it would span from the *stray* opener to the first closer and
32
+ * delete the real text `b` along with the token.
33
+ * 2. **Each alternative pairs its own delimiters** — open-with-open,
34
+ * close-with-close. Choosing the two ends from independent classes would
35
+ * also match an open *paired with a close*, which the emitters never
36
+ * produce (see `openToken`/`closeToken` in `lexicalToMdast.ts`); on
37
+ * `…␀real text␃…` that mismatched span would be deleted, taking the real
38
+ * text with it. Only well-formed tokens should ever be removed, because
39
+ * this function feeds *decisions* — the explicit task-marker test and the
40
+ * ends-with-a-colon join rule — where a wrong strip silently changes
41
+ * output.
42
+ */
43
+ const SENTINEL_ID_RUN = `[^${SENTINEL_OPEN_START}${SENTINEL_OPEN_END}${SENTINEL_CLOSE_START}${SENTINEL_CLOSE_END}]*`;
44
+ const SENTINEL_TOKEN = new RegExp(`${SENTINEL_OPEN_START}${SENTINEL_ID_RUN}${SENTINEL_OPEN_END}` +
45
+ `|${SENTINEL_CLOSE_START}${SENTINEL_ID_RUN}${SENTINEL_CLOSE_END}`, 'gu');
46
+ /**
47
+ * `text` with every annotate-mode sentinel token removed. A no-op — and cheap
48
+ * — for the overwhelmingly common case of a string that contains none, which
49
+ * is every string on the disk-write path.
50
+ */
51
+ export function stripAnnotateSentinels(text) {
52
+ if (!text.includes(SENTINEL_OPEN_START) && !text.includes(SENTINEL_CLOSE_START))
53
+ return text;
54
+ return text.replace(SENTINEL_TOKEN, '');
55
+ }
56
+ /**
57
+ * Split `text` into alternating sentinel-token and plain-text parts.
58
+ *
59
+ * Used by `stringify.ts`'s force-escape handling (#17): a force-escaped
60
+ * text node's `getTextContent()` can have a sentinel token spliced onto
61
+ * either end by `sentinelAugmentedText` (annotate-serialize mode, see the
62
+ * module doc comment above) before the force-escape data is read, so
63
+ * treating the whole string as "one force-escaped run" would wrap the
64
+ * token's own characters in escape placeholders too. Splitting first lets
65
+ * the caller escape only the real content and leave token parts verbatim.
66
+ */
67
+ export function splitOnSentinelTokens(text) {
68
+ if (!text.includes(SENTINEL_OPEN_START) && !text.includes(SENTINEL_CLOSE_START)) {
69
+ return [{ text, isSentinel: false }];
70
+ }
71
+ const parts = [];
72
+ let cursor = 0;
73
+ SENTINEL_TOKEN.lastIndex = 0;
74
+ let match;
75
+ while ((match = SENTINEL_TOKEN.exec(text)) !== null) {
76
+ if (match.index > cursor) {
77
+ parts.push({ text: text.slice(cursor, match.index), isSentinel: false });
78
+ }
79
+ parts.push({ text: match[0], isSentinel: true });
80
+ cursor = match.index + match[0].length;
81
+ }
82
+ if (cursor < text.length) {
83
+ parts.push({ text: text.slice(cursor), isSentinel: false });
84
+ }
85
+ return parts;
86
+ }