@markii/host 0.0.1 → 0.17.0-next.3

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 (193) hide show
  1. package/README.md +20 -2
  2. package/dist/browser-worker.js +639 -0
  3. package/dist/browser.d.ts +61 -0
  4. package/dist/browser.js +172 -0
  5. package/dist/conformance/host/01-plain-open/expected.json +5 -0
  6. package/dist/conformance/host/01-plain-open/note.mk.md +8 -0
  7. package/dist/conformance/host/01-plain-open/scenario.json +6 -0
  8. package/dist/conformance/host/02-run-manual-grant-accepted/expected.json +16 -0
  9. package/dist/conformance/host/02-run-manual-grant-accepted/note.mk.md +5 -0
  10. package/dist/conformance/host/02-run-manual-grant-accepted/scenario.json +14 -0
  11. package/dist/conformance/host/03-run-manual-grant-refused/expected.json +18 -0
  12. package/dist/conformance/host/03-run-manual-grant-refused/note.mk.md +5 -0
  13. package/dist/conformance/host/03-run-manual-grant-refused/scenario.json +14 -0
  14. package/dist/conformance/host/04-run-auto-write-denied/expected.json +16 -0
  15. package/dist/conformance/host/04-run-auto-write-denied/note.mk.md +10 -0
  16. package/dist/conformance/host/04-run-auto-write-denied/scenario.json +13 -0
  17. package/dist/conformance/host/05-export-html-pack-directive/expected.json +14 -0
  18. package/dist/conformance/host/05-export-html-pack-directive/note.mk.md +8 -0
  19. package/dist/conformance/host/05-export-html-pack-directive/scenario.json +6 -0
  20. package/dist/conformance/host/06-install-pack-then-render/expected.json +18 -0
  21. package/dist/conformance/host/06-install-pack-then-render/files/ana-1.0.0/pack.json +13 -0
  22. package/dist/conformance/host/06-install-pack-then-render/files/ana-1.0.0/webview.js +1 -0
  23. package/dist/conformance/host/06-install-pack-then-render/note.mk.md +5 -0
  24. package/dist/conformance/host/06-install-pack-then-render/scenario.json +15 -0
  25. package/dist/conformance/host/07-complete-in-directive/expected.json +14 -0
  26. package/dist/conformance/host/07-complete-in-directive/note.mk.md +3 -0
  27. package/dist/conformance/host/07-complete-in-directive/scenario.json +6 -0
  28. package/dist/conformance/host/08-hover-standard-component/expected.json +5 -0
  29. package/dist/conformance/host/08-hover-standard-component/note.mk.md +5 -0
  30. package/dist/conformance/host/08-hover-standard-component/scenario.json +6 -0
  31. package/dist/conformance/host/09-insert-skeleton/expected.json +5 -0
  32. package/dist/conformance/host/09-insert-skeleton/note.mk.md +4 -0
  33. package/dist/conformance/host/09-insert-skeleton/scenario.json +6 -0
  34. package/dist/conformance/host/10-scripts-disabled/expected.json +11 -0
  35. package/dist/conformance/host/10-scripts-disabled/note.mk.md +5 -0
  36. package/dist/conformance/host/10-scripts-disabled/scenario.json +9 -0
  37. package/dist/conformance/host/11-complete-closing-fence/expected.json +5 -0
  38. package/dist/conformance/host/11-complete-closing-fence/note.mk.md +5 -0
  39. package/dist/conformance/host/11-complete-closing-fence/scenario.json +12 -0
  40. package/dist/conformance/host/12-complete-nested-container/expected.json +5 -0
  41. package/dist/conformance/host/12-complete-nested-container/note.mk.md +5 -0
  42. package/dist/conformance/host/12-complete-nested-container/scenario.json +12 -0
  43. package/dist/conformance/host/13-install-pack-declined/expected.json +12 -0
  44. package/dist/conformance/host/13-install-pack-declined/files/ana-1.0.0/pack.json +13 -0
  45. package/dist/conformance/host/13-install-pack-declined/files/ana-1.0.0/webview.js +1 -0
  46. package/dist/conformance/host/13-install-pack-declined/note.mk.md +5 -0
  47. package/dist/conformance/host/13-install-pack-declined/scenario.json +10 -0
  48. package/dist/conformance/host/14-remove-pack/expected.json +14 -0
  49. package/dist/conformance/host/14-remove-pack/files/ana-1.0.0/pack.json +13 -0
  50. package/dist/conformance/host/14-remove-pack/files/ana-1.0.0/webview.js +1 -0
  51. package/dist/conformance/host/14-remove-pack/note.mk.md +5 -0
  52. package/dist/conformance/host/14-remove-pack/scenario.json +11 -0
  53. package/dist/conformance/host/15-load-packs-engine-views/expected.json +19 -0
  54. package/dist/conformance/host/15-load-packs-engine-views/files/duo-1.0.0/pack.json +9 -0
  55. package/dist/conformance/host/15-load-packs-engine-views/files/duo-1.0.0/webview.js +1 -0
  56. package/dist/conformance/host/15-load-packs-engine-views/note.mk.md +9 -0
  57. package/dist/conformance/host/15-load-packs-engine-views/scenario.json +10 -0
  58. package/dist/conformance/host/16-pack-trust/expected.json +37 -0
  59. package/dist/conformance/host/16-pack-trust/files/ana-1.0.0/pack.json +12 -0
  60. package/dist/conformance/host/16-pack-trust/files/ana-1.0.0/webview.js +1 -0
  61. package/dist/conformance/host/16-pack-trust/files/sneaky-1.0.0/pack.json +12 -0
  62. package/dist/conformance/host/16-pack-trust/files/sneaky-1.0.0/webview.js +1 -0
  63. package/dist/conformance/host/16-pack-trust/note.mk.md +5 -0
  64. package/dist/conformance/host/16-pack-trust/scenario.json +26 -0
  65. package/dist/conformance/host/README.md +221 -0
  66. package/dist/conformance.d.ts +2 -0
  67. package/dist/conformance.js +5 -0
  68. package/dist/diagnostics/render-diagnostics.d.ts +41 -0
  69. package/dist/diagnostics/render-diagnostics.js +46 -0
  70. package/dist/export/cascade.d.ts +98 -0
  71. package/dist/export/cascade.js +159 -0
  72. package/dist/export/export-zip.d.ts +19 -0
  73. package/dist/export/export-zip.js +38 -0
  74. package/dist/export/image-embed.d.ts +126 -0
  75. package/dist/export/image-embed.js +274 -0
  76. package/dist/export/note-export.d.ts +332 -0
  77. package/dist/export/note-export.js +366 -0
  78. package/dist/export/note-links.d.ts +83 -0
  79. package/dist/export/note-links.js +241 -0
  80. package/dist/glue.wasm +0 -0
  81. package/dist/host/adapter.d.ts +218 -0
  82. package/dist/host/adapter.js +9 -0
  83. package/dist/host/cascade-export-behavior.d.ts +120 -0
  84. package/dist/host/cascade-export-behavior.js +151 -0
  85. package/dist/host/conformance-runner.d.ts +159 -0
  86. package/dist/host/conformance-runner.js +632 -0
  87. package/dist/host/create-host.d.ts +251 -0
  88. package/dist/host/create-host.js +329 -0
  89. package/dist/host/editor-behavior.d.ts +153 -0
  90. package/dist/host/editor-behavior.js +223 -0
  91. package/dist/host/export/md-plain.d.ts +15 -0
  92. package/dist/host/export/md-plain.js +125 -0
  93. package/dist/host/export-behavior.d.ts +111 -0
  94. package/dist/host/export-behavior.js +257 -0
  95. package/dist/host/labels.d.ts +23 -0
  96. package/dist/host/labels.js +32 -0
  97. package/dist/host/pack-archive.d.ts +30 -0
  98. package/dist/host/pack-archive.js +62 -0
  99. package/dist/host/pack-install.d.ts +87 -0
  100. package/dist/host/pack-install.js +186 -0
  101. package/dist/host/pack-load.d.ts +71 -0
  102. package/dist/host/pack-load.js +57 -0
  103. package/dist/host/pack-remove.d.ts +21 -0
  104. package/dist/host/pack-remove.js +65 -0
  105. package/dist/host/pack-trust-behavior.d.ts +79 -0
  106. package/dist/host/pack-trust-behavior.js +262 -0
  107. package/dist/host/refresh-interval.d.ts +37 -0
  108. package/dist/host/refresh-interval.js +59 -0
  109. package/dist/host/run-behavior.d.ts +88 -0
  110. package/dist/host/run-behavior.js +189 -0
  111. package/dist/host/run-diagnostics.d.ts +26 -0
  112. package/dist/host/run-diagnostics.js +43 -0
  113. package/dist/host/script-execution.d.ts +72 -0
  114. package/dist/host/script-execution.js +68 -0
  115. package/dist/host/terminal-packs.d.ts +39 -0
  116. package/dist/host/terminal-packs.js +51 -0
  117. package/dist/html-engine.d.ts +2 -0
  118. package/dist/html-engine.js +29 -0
  119. package/dist/index.d.ts +95 -0
  120. package/dist/index.js +69 -0
  121. package/dist/insert/component-catalog.d.ts +23 -0
  122. package/dist/insert/component-catalog.js +96 -0
  123. package/dist/node-fs.d.ts +14 -0
  124. package/dist/node-fs.js +70 -0
  125. package/dist/node-worker.js +20657 -0
  126. package/dist/node.d.ts +6 -0
  127. package/dist/node.js +13 -0
  128. package/dist/package.json +3 -0
  129. package/dist/packs/discover.d.ts +119 -0
  130. package/dist/packs/discover.js +266 -0
  131. package/dist/packs/pack-diagnostics.d.ts +79 -0
  132. package/dist/packs/pack-diagnostics.js +103 -0
  133. package/dist/packs/pack-paths.d.ts +53 -0
  134. package/dist/packs/pack-paths.js +105 -0
  135. package/dist/packs/pack-render-registry.d.ts +82 -0
  136. package/dist/packs/pack-render-registry.js +182 -0
  137. package/dist/packs/pack-scripts.d.ts +20 -0
  138. package/dist/packs/pack-scripts.js +96 -0
  139. package/dist/packs/pack-trust.d.ts +57 -0
  140. package/dist/packs/pack-trust.js +111 -0
  141. package/dist/packs/prebuilt.d.ts +49 -0
  142. package/dist/packs/prebuilt.js +97 -0
  143. package/dist/path-util.d.ts +24 -0
  144. package/dist/path-util.js +82 -0
  145. package/dist/run/browser-isolate.d.ts +45 -0
  146. package/dist/run/browser-isolate.js +85 -0
  147. package/dist/run/bundle-document.d.ts +29 -0
  148. package/dist/run/bundle-document.js +40 -0
  149. package/dist/run/bundle-run.d.ts +188 -0
  150. package/dist/run/bundle-run.js +232 -0
  151. package/dist/run/grant-flow.d.ts +232 -0
  152. package/dist/run/grant-flow.js +390 -0
  153. package/dist/run/ip-address.d.ts +41 -0
  154. package/dist/run/ip-address.js +255 -0
  155. package/dist/run/isolate.d.ts +59 -0
  156. package/dist/run/isolate.js +22 -0
  157. package/dist/run/lua-resolver.d.ts +21 -0
  158. package/dist/run/lua-resolver.js +63 -0
  159. package/dist/run/net-bridge-worker.d.ts +26 -0
  160. package/dist/run/net-bridge-worker.js +66 -0
  161. package/dist/run/net-bridge.d.ts +56 -0
  162. package/dist/run/net-bridge.js +59 -0
  163. package/dist/run/net-pinning.d.ts +82 -0
  164. package/dist/run/net-pinning.js +137 -0
  165. package/dist/run/net-provider.d.ts +72 -0
  166. package/dist/run/net-provider.js +303 -0
  167. package/dist/run/node-isolate.d.ts +22 -0
  168. package/dist/run/node-isolate.js +68 -0
  169. package/dist/run/run-flow.d.ts +186 -0
  170. package/dist/run/run-flow.js +323 -0
  171. package/dist/run/run-host.d.ts +107 -0
  172. package/dist/run/run-host.js +241 -0
  173. package/dist/run/run-job.d.ts +130 -0
  174. package/dist/run/run-job.js +279 -0
  175. package/dist/run/run-progress.d.ts +55 -0
  176. package/dist/run/run-progress.js +42 -0
  177. package/dist/run/run-trace.d.ts +44 -0
  178. package/dist/run/run-trace.js +36 -0
  179. package/dist/run/script-requirements.d.ts +98 -0
  180. package/dist/run/script-requirements.js +212 -0
  181. package/dist/run/snapshot-storage.d.ts +25 -0
  182. package/dist/run/snapshot-storage.js +31 -0
  183. package/dist/run/stale-values.d.ts +25 -0
  184. package/dist/run/stale-values.js +32 -0
  185. package/dist/run/worker-entry-browser.d.ts +62 -0
  186. package/dist/run/worker-entry-browser.js +172 -0
  187. package/dist/run/worker-entry.d.ts +1 -0
  188. package/dist/run/worker-entry.js +98 -0
  189. package/dist/values-failure.d.ts +17 -0
  190. package/dist/values-failure.js +1 -0
  191. package/dist/values-merge.d.ts +39 -0
  192. package/dist/values-merge.js +33 -0
  193. package/package.json +80 -4
@@ -0,0 +1,153 @@
1
+ import type { CompletionContext, CompletionItem, HoverInfo, InsertableComponent, LineColumn } from '@markii/stdlib/editor';
2
+ import type { HostEditor, HostTextEdit } from './adapter.js';
3
+ /** The origin tag every host's completion row shows for a component item: `'standard'`, `'layout'`, or the owning pack's own namespace. Empty string for an attribute or value item, which has no `group`. */
4
+ export declare const STANDARD_ORIGIN_TAG = "standard";
5
+ export declare const LAYOUT_ORIGIN_TAG = "layout";
6
+ /**
7
+ * The origin tag a completion item carries: the lower-case
8
+ * `'standard'`/`'layout'` strings above, or the pack name.
9
+ */
10
+ export declare function completionOriginTag(item: CompletionItem): string;
11
+ /** A small cache in front of `buildComponentCatalog` (`../insert/component-catalog.js`), so a completion/hover provider does not re-discover packs from disk on every keystroke. */
12
+ export interface CatalogCache {
13
+ /** The cached catalog, building it via `load` on first use and again after `invalidate()`. Never throws: a rejected `load` degrades to the standard-set-only catalog. */
14
+ get(): Promise<readonly InsertableComponent[]>;
15
+ /** The cached catalog if one is built, else `undefined`. Never starts a build. */
16
+ peek(): readonly InsertableComponent[] | undefined;
17
+ /** Drops the cached catalog (and any in-flight build), so the next `get()` rebuilds from `load`. */
18
+ invalidate(): void;
19
+ }
20
+ /**
21
+ * Builds a `CatalogCache` around `buildCatalog` (typically
22
+ * `../insert/component-catalog.js`'s `buildComponentCatalog` composed with
23
+ * a pack-discovery call). Concurrent `get()` calls made before a build
24
+ * finishes share one in-flight promise rather than each starting their own
25
+ * pack discovery.
26
+ */
27
+ export declare function createCatalogCache(buildCatalog: () => Promise<readonly InsertableComponent[]>): CatalogCache;
28
+ /**
29
+ * `completionAt` composed over a `HostEditor`'s document text, at the
30
+ * POSITION a completion request names — not `editor.cursor()`. A real
31
+ * editor's completion provider is invoked with an explicit position (the
32
+ * position the user is typing at when the provider fires), which is why a
33
+ * completion/hover request always carries its own `line`/`column` rather
34
+ * than reading the live caret a second time. `editor.cursor()` stays
35
+ * reserved for `insertComponentPlan`, which genuinely inserts at the
36
+ * caret.
37
+ */
38
+ export declare function completeAtViaEditor(editor: Pick<HostEditor, 'documentText'>, position: LineColumn, catalog: readonly InsertableComponent[]): CompletionContext;
39
+ /**
40
+ * Everything a completion UI needs for one request, decided once in the
41
+ * shared layer: the items, the replace span (`replaceStart`/`replaceEnd`),
42
+ * the token start a filtering popup should score against (`tokenStart`,
43
+ * which equals `replaceStart` unless a colon run precedes the name), and
44
+ * the fence lengthening edits an accepted CONTAINER item needs. Hosts only
45
+ * map this onto their own popup type.
46
+ */
47
+ export interface CompletionPlan {
48
+ readonly kind: CompletionContext['kind'];
49
+ readonly items: readonly CompletionItem[];
50
+ readonly replaceStart: number;
51
+ readonly replaceEnd: number;
52
+ readonly tokenStart: number;
53
+ /**
54
+ * Fence rewrites to apply in the SAME undo step as an accepted container
55
+ * item, in pre-edit coordinates, never on the insertion line. Empty when
56
+ * no item in the response opens a container or the document's fences do
57
+ * not pair cleanly. Every container item in one context inserts the same
58
+ * fence shape, so one list serves them all; a host applies it only when
59
+ * the accepted item is a container (`isContainerCompletionItem`).
60
+ */
61
+ readonly fenceEdits: readonly HostTextEdit[];
62
+ }
63
+ /** Whether accepting `item` opens a container, and so needs `CompletionPlan.fenceEdits` applied with it. */
64
+ export declare function isContainerCompletionItem(item: {
65
+ readonly insertText: string;
66
+ }): boolean;
67
+ /**
68
+ * `completionAt` over a whole document at a position, plus the two
69
+ * decisions both editors used to repeat at their own trigger boundary: a
70
+ * bare colon run that CLOSES an open container is not an opener waiting
71
+ * for a name (so no plan), and fence edits are computed once per response.
72
+ * Returns `undefined` when there is nothing to offer. Never throws.
73
+ */
74
+ export declare function completionPlanAt(documentText: string, position: LineColumn, catalog: readonly InsertableComponent[]): CompletionPlan | undefined;
75
+ /** `hoverAt` composed over a `HostEditor`'s document text at a given position, the same way `completeAtViaEditor` composes `completionAt`. */
76
+ export declare function hoverAtViaEditor(editor: Pick<HostEditor, 'documentText'>, position: LineColumn, catalog: readonly InsertableComponent[]): HoverInfo | undefined;
77
+ /**
78
+ * Plain-text hover documentation for `item`: `@markii/stdlib/editor`'s
79
+ * `formatComponentDocumentation`, the shape every host without a
80
+ * rich-markdown hover surface (or a host doing its own markdown mapping)
81
+ * can use as-is. This is also what `createMarkiiHost`'s `hoverAt` returns
82
+ * (via `hoverAtViaEditor`'s `HoverInfo.documentation`) rather than the
83
+ * bare summary sentence: the summary alone can read as generic prose with
84
+ * no component name in it (a callout's summary is "A colored box for an
85
+ * aside, warning, or danger note.", nowhere mentioning "callout"), while
86
+ * the formatted text's `Example: :::callout{}` line always names the
87
+ * directive.
88
+ */
89
+ export declare function hoverDocumentationText(item: InsertableComponent): string;
90
+ /** One text edit and the cursor position an insertion leaves behind, both relative to the document `insertionLine`/`insertionColumn` named. */
91
+ export interface InsertComponentEdit {
92
+ readonly line: number;
93
+ readonly startColumn: number;
94
+ readonly endColumn: number;
95
+ readonly text: string;
96
+ }
97
+ export interface InsertComponentPlan {
98
+ /**
99
+ * Every edit `HostEditor.applyEdits` must apply together as one
100
+ * undoable edit: the skeleton insertion plus any fence lines an
101
+ * enclosing container needs lengthened.
102
+ *
103
+ * All edits are expressed in the ORIGINAL document's coordinates (the
104
+ * text `documentText` named, before any of these edits land) — exactly
105
+ * like `fenceExtensionEdits` already promises, and exactly what both
106
+ * apps' `HostTextEdit`-consuming adapters already assume of a
107
+ * multi-edit `applyEdits` call. That is what makes the array safe to
108
+ * apply as one atomic transaction: a host whose `applyEdits` resolves
109
+ * every position against the pre-edit document (VS Code's
110
+ * `WorkspaceEdit`, Obsidian's `Editor.transaction`) can apply them in
111
+ * any order.
112
+ *
113
+ * The array is ALSO returned bottom-to-top (descending by line, ties
114
+ * broken by descending column) as a second-line defense for a simpler
115
+ * host: one that applies this list as a sequence of plain string
116
+ * splices against a single mutable buffer rather than a coordinate-free
117
+ * transaction. Splicing text at a line shifts every line and column
118
+ * number below it; splicing from the bottom of the document upward
119
+ * means each edit is always applied before anything that could move
120
+ * out from under it, and nothing below an edit's line is ever touched
121
+ * after that edit lands. Fence edits never land on the insertion line
122
+ * itself (`fenceExtensionEdits`'s own guarantee, an enclosing pair by
123
+ * definition straddles it), so the insertion edit's own line never
124
+ * collides with a fence edit's line and the ordering rule never has to
125
+ * break a tie between them.
126
+ */
127
+ readonly edits: readonly InsertComponentEdit[];
128
+ readonly cursor: LineColumn;
129
+ }
130
+ /**
131
+ * Builds the skeleton for `component` (`@markii/stdlib/editor`'s
132
+ * `componentSkeleton`), turns its flat `cursorOffset` into an absolute
133
+ * line/column given where the insertion happens, and folds in the fence
134
+ * edits (`@markii/stdlib/editor`'s `fenceExtensionEdits`) an enclosing
135
+ * container needs lengthened so it still nests legally. This is the pure
136
+ * half of both apps' `insert-component.ts` command handlers: build the
137
+ * skeleton, insert it at the cursor, lengthen any enclosing fences, place
138
+ * the cursor inside it — all in one plan a host applies as one undoable
139
+ * edit (`HostEditor.applyEdits`'s contract).
140
+ *
141
+ * `documentText` is the document as it stands before this insertion (the
142
+ * same text `fenceExtensionEdits` scans); a caller inserting from a
143
+ * completion accept passes the same text it already read to decide what
144
+ * to insert.
145
+ *
146
+ * Never throws: a failure to compute fence edits (hostile input, an
147
+ * unpaired document `fenceExtensionEdits` refuses to touch) degrades to
148
+ * the skeleton insertion alone, exactly as inserting always worked before
149
+ * fence auto-extension existed.
150
+ */
151
+ export declare function insertComponentPlan(component: Pick<InsertableComponent, 'directiveName' | 'kind'> & {
152
+ readonly requiredAttributes?: readonly string[];
153
+ }, documentText: string, insertionLine: number, insertionColumn: number): InsertComponentPlan;
@@ -0,0 +1,223 @@
1
+ /**
2
+ * The `completeAt`/`hoverAt`/`insertComponent` behaviors' shared half,
3
+ * merged from `apps/vscode/src/completion.ts` + `completion-catalog.ts`,
4
+ * `apps/obsidian/src/complete-component.ts`, and both apps'
5
+ * `insert-component.ts`.
6
+ *
7
+ * NODE-FREE by design: re-exported
8
+ * from `../browser.ts` as well as `../index.ts`, so VS Code's webview
9
+ * bundle can read the origin-tag vocabulary and the pure completion math
10
+ * directly if a future slice needs to. `CatalogCache.get`'s injected
11
+ * `load` is the one thing that reaches disk, and it is always supplied by
12
+ * the caller (a main-entry-only pack-discovery call), never imported here.
13
+ *
14
+ * What stays app-side (deliberately): VS
15
+ * Code's sectioned quick-pick presentation (`Standard`/`Layout`/`<pack>
16
+ * pack` separators) and Obsidian's flat fuzzy list are a genuine product
17
+ * difference, not duplication — this module returns the catalog and the
18
+ * origin tag; it never picks a picker shape. Snippet escaping
19
+ * (`snippetText`) is VS Code's own snippet-syntax concern and stays there
20
+ * too. Hover exists only on VS Code today; a host with no `adapter.editor`
21
+ * simply never calls `hoverAtViaAdapter`.
22
+ */
23
+ import { closesOpenContainerFence, completionAt, componentDocumentation, fenceExtensionEdits, insertedContainerColonCount, formatComponentDocumentation, hoverAt, offsetToLineColumn, componentSkeleton, } from '@markii/stdlib/editor';
24
+ /** The origin tag every host's completion row shows for a component item: `'standard'`, `'layout'`, or the owning pack's own namespace. Empty string for an attribute or value item, which has no `group`. */
25
+ export const STANDARD_ORIGIN_TAG = 'standard';
26
+ export const LAYOUT_ORIGIN_TAG = 'layout';
27
+ /**
28
+ * The origin tag a completion item carries: the lower-case
29
+ * `'standard'`/`'layout'` strings above, or the pack name.
30
+ */
31
+ export function completionOriginTag(item) {
32
+ if (item.kind !== 'component')
33
+ return '';
34
+ if (item.group === 'standard')
35
+ return STANDARD_ORIGIN_TAG;
36
+ if (item.group === 'layout')
37
+ return LAYOUT_ORIGIN_TAG;
38
+ return item.packName ?? '';
39
+ }
40
+ /**
41
+ * Builds a `CatalogCache` around `buildCatalog` (typically
42
+ * `../insert/component-catalog.js`'s `buildComponentCatalog` composed with
43
+ * a pack-discovery call). Concurrent `get()` calls made before a build
44
+ * finishes share one in-flight promise rather than each starting their own
45
+ * pack discovery.
46
+ */
47
+ export function createCatalogCache(buildCatalog) {
48
+ let cached;
49
+ let pending;
50
+ async function build() {
51
+ try {
52
+ return await buildCatalog();
53
+ }
54
+ catch {
55
+ return [];
56
+ }
57
+ }
58
+ return {
59
+ async get() {
60
+ if (cached !== undefined)
61
+ return cached;
62
+ if (pending === undefined) {
63
+ pending = build().then((catalog) => {
64
+ cached = catalog;
65
+ pending = undefined;
66
+ return catalog;
67
+ });
68
+ }
69
+ return pending;
70
+ },
71
+ peek() {
72
+ return cached;
73
+ },
74
+ invalidate() {
75
+ cached = undefined;
76
+ pending = undefined;
77
+ },
78
+ };
79
+ }
80
+ /** The line of `documentText` that `line` (zero-based) names, or `''` past the end of the document. */
81
+ function lineTextAt(documentText, line) {
82
+ return documentText.split('\n')[line] ?? '';
83
+ }
84
+ /**
85
+ * `completionAt` composed over a `HostEditor`'s document text, at the
86
+ * POSITION a completion request names — not `editor.cursor()`. A real
87
+ * editor's completion provider is invoked with an explicit position (the
88
+ * position the user is typing at when the provider fires), which is why a
89
+ * completion/hover request always carries its own `line`/`column` rather
90
+ * than reading the live caret a second time. `editor.cursor()` stays
91
+ * reserved for `insertComponentPlan`, which genuinely inserts at the
92
+ * caret.
93
+ */
94
+ export function completeAtViaEditor(editor, position, catalog) {
95
+ const line = lineTextAt(editor.documentText(), position.line);
96
+ return completionAt(line, position.column, catalog);
97
+ }
98
+ /** Whether accepting `item` opens a container, and so needs `CompletionPlan.fenceEdits` applied with it. */
99
+ export function isContainerCompletionItem(item) {
100
+ try {
101
+ return insertedContainerColonCount(item.insertText) !== undefined;
102
+ }
103
+ catch {
104
+ return false;
105
+ }
106
+ }
107
+ /**
108
+ * `completionAt` over a whole document at a position, plus the two
109
+ * decisions both editors used to repeat at their own trigger boundary: a
110
+ * bare colon run that CLOSES an open container is not an opener waiting
111
+ * for a name (so no plan), and fence edits are computed once per response.
112
+ * Returns `undefined` when there is nothing to offer. Never throws.
113
+ */
114
+ export function completionPlanAt(documentText, position, catalog) {
115
+ try {
116
+ const line = lineTextAt(documentText, position.line);
117
+ if (closesOpenContainerFence(documentText, position.line, line)) {
118
+ return undefined;
119
+ }
120
+ const context = completionAt(line, position.column, catalog);
121
+ if (context.kind === 'none' || context.items.length === 0) {
122
+ return undefined;
123
+ }
124
+ const container = context.items.find(isContainerCompletionItem);
125
+ let fenceEdits = [];
126
+ if (container !== undefined) {
127
+ try {
128
+ fenceEdits = fenceExtensionEdits(documentText, position.line, container.insertText)
129
+ .filter((edit) => edit.line !== position.line)
130
+ .map((edit) => ({
131
+ line: edit.line,
132
+ startColumn: edit.column,
133
+ endColumn: edit.column + edit.oldText.length,
134
+ text: edit.newText,
135
+ }));
136
+ }
137
+ catch {
138
+ fenceEdits = [];
139
+ }
140
+ }
141
+ return {
142
+ kind: context.kind,
143
+ items: context.items,
144
+ replaceStart: context.replaceStart,
145
+ replaceEnd: context.replaceEnd,
146
+ tokenStart: context.tokenStart ?? context.replaceStart,
147
+ fenceEdits,
148
+ };
149
+ }
150
+ catch {
151
+ return undefined;
152
+ }
153
+ }
154
+ /** `hoverAt` composed over a `HostEditor`'s document text at a given position, the same way `completeAtViaEditor` composes `completionAt`. */
155
+ export function hoverAtViaEditor(editor, position, catalog) {
156
+ const line = lineTextAt(editor.documentText(), position.line);
157
+ return hoverAt(line, position.column, catalog);
158
+ }
159
+ /**
160
+ * Plain-text hover documentation for `item`: `@markii/stdlib/editor`'s
161
+ * `formatComponentDocumentation`, the shape every host without a
162
+ * rich-markdown hover surface (or a host doing its own markdown mapping)
163
+ * can use as-is. This is also what `createMarkiiHost`'s `hoverAt` returns
164
+ * (via `hoverAtViaEditor`'s `HoverInfo.documentation`) rather than the
165
+ * bare summary sentence: the summary alone can read as generic prose with
166
+ * no component name in it (a callout's summary is "A colored box for an
167
+ * aside, warning, or danger note.", nowhere mentioning "callout"), while
168
+ * the formatted text's `Example: :::callout{}` line always names the
169
+ * directive.
170
+ */
171
+ export function hoverDocumentationText(item) {
172
+ return formatComponentDocumentation(componentDocumentation(item));
173
+ }
174
+ /**
175
+ * Builds the skeleton for `component` (`@markii/stdlib/editor`'s
176
+ * `componentSkeleton`), turns its flat `cursorOffset` into an absolute
177
+ * line/column given where the insertion happens, and folds in the fence
178
+ * edits (`@markii/stdlib/editor`'s `fenceExtensionEdits`) an enclosing
179
+ * container needs lengthened so it still nests legally. This is the pure
180
+ * half of both apps' `insert-component.ts` command handlers: build the
181
+ * skeleton, insert it at the cursor, lengthen any enclosing fences, place
182
+ * the cursor inside it — all in one plan a host applies as one undoable
183
+ * edit (`HostEditor.applyEdits`'s contract).
184
+ *
185
+ * `documentText` is the document as it stands before this insertion (the
186
+ * same text `fenceExtensionEdits` scans); a caller inserting from a
187
+ * completion accept passes the same text it already read to decide what
188
+ * to insert.
189
+ *
190
+ * Never throws: a failure to compute fence edits (hostile input, an
191
+ * unpaired document `fenceExtensionEdits` refuses to touch) degrades to
192
+ * the skeleton insertion alone, exactly as inserting always worked before
193
+ * fence auto-extension existed.
194
+ */
195
+ export function insertComponentPlan(component, documentText, insertionLine, insertionColumn) {
196
+ const skeleton = componentSkeleton(component.directiveName, component.kind, component.requiredAttributes ?? []);
197
+ const relative = offsetToLineColumn(skeleton.text, skeleton.cursorOffset);
198
+ const cursor = relative.line === 0
199
+ ? { line: insertionLine, column: insertionColumn + relative.column }
200
+ : { line: insertionLine + relative.line, column: relative.column };
201
+ const insertionEdit = {
202
+ line: insertionLine,
203
+ startColumn: insertionColumn,
204
+ endColumn: insertionColumn,
205
+ text: skeleton.text,
206
+ };
207
+ let fenceEdits;
208
+ try {
209
+ fenceEdits = fenceExtensionEdits(documentText, insertionLine, skeleton.text)
210
+ .filter((edit) => edit.line !== insertionLine)
211
+ .map((edit) => ({
212
+ line: edit.line,
213
+ startColumn: edit.column,
214
+ endColumn: edit.column + edit.oldText.length,
215
+ text: edit.newText,
216
+ }));
217
+ }
218
+ catch {
219
+ fenceEdits = [];
220
+ }
221
+ const edits = [...fenceEdits, insertionEdit].sort((a, b) => a.line !== b.line ? b.line - a.line : b.startColumn - a.startColumn);
222
+ return { edits, cursor };
223
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Downgrades `text` to plain CommonMark: container directives lose their
3
+ * opening/closing fence lines and keep their inner markdown, leaf
4
+ * directives are removed, a text directive is replaced by its own text
5
+ * content, and named script fences are removed entirely. A component
6
+ * becomes its inner content only — this is a readability floor, not a
7
+ * faithful render.
8
+ *
9
+ * Every edit is a pure deletion (the inner text a directive wraps is
10
+ * already the correct, final text — nothing is rewritten, only the
11
+ * surrounding directive syntax is cut away), applied from the END of the
12
+ * source backwards so earlier offsets stay valid as later ones are
13
+ * consumed.
14
+ */
15
+ export declare function mdPlainFromSource(text: string): string;
@@ -0,0 +1,125 @@
1
+ /**
2
+ * The `md-plain` export format: a SOURCE-LEVEL rewrite of a note down to
3
+ * plain CommonMark. Moved out of `apps/cli/src/export-note.ts` (the only
4
+ * host that ever offered this format): pure logic over `@markii/core`'s
5
+ * parse tree that never imports `node:*`, so it never belonged behind an
6
+ * app's main entry in the first place.
7
+ *
8
+ * No markdown serializer, no new dependency: every directive's own
9
+ * fence/bracket syntax is deleted from the original text using the
10
+ * offsets `@markii/core`'s `parse` already puts on every node, leaving the
11
+ * author's own inner markdown untouched.
12
+ */
13
+ import { parse, extractScripts } from '@markii/core';
14
+ import { visit, SKIP } from 'unist-util-visit';
15
+ function offsetsOf(position) {
16
+ if (position === undefined ||
17
+ position.start.offset === undefined ||
18
+ position.end.offset === undefined) {
19
+ return undefined;
20
+ }
21
+ return { start: position.start.offset, end: position.end.offset };
22
+ }
23
+ /**
24
+ * The shared logic for a container directive or a text directive: keep
25
+ * exactly the children's own source range, trimming the fence lines
26
+ * (container) or `:name[`/`]{...}` (text) away on either side. An empty
27
+ * directive (no children — attributes only, or no label) has nothing to
28
+ * preserve, so its whole range is removed instead, and visiting does not
29
+ * need to descend into it.
30
+ */
31
+ function wrapperEdits(node, edits) {
32
+ const outer = offsetsOf(node.position);
33
+ if (outer === undefined)
34
+ return undefined;
35
+ const { children } = node;
36
+ if (children.length === 0) {
37
+ edits.push(outer);
38
+ return SKIP;
39
+ }
40
+ const first = children[0];
41
+ const last = children[children.length - 1];
42
+ const firstOffsets = offsetsOf(first?.position);
43
+ const lastOffsets = offsetsOf(last?.position);
44
+ if (firstOffsets !== undefined && firstOffsets.start > outer.start) {
45
+ edits.push({ start: outer.start, end: firstOffsets.start });
46
+ }
47
+ if (lastOffsets !== undefined && lastOffsets.end < outer.end) {
48
+ edits.push({ start: lastOffsets.end, end: outer.end });
49
+ }
50
+ // No SKIP: keep visiting into `children` so a nested directive inside
51
+ // this one's content still gets its own wrapper stripped.
52
+ return undefined;
53
+ }
54
+ /**
55
+ * Every directive's wrapper-syntax edit: a leaf directive (`::name[...]`)
56
+ * is removed in full (there is no "inner markdown" to keep — a leaf
57
+ * directive's label is inline attributes-shaped content, not a nested
58
+ * document); a container or text directive keeps its inner markdown and
59
+ * loses only its own fence/bracket syntax (`wrapperEdits`, above).
60
+ */
61
+ function collectDirectiveEdits(tree) {
62
+ const edits = [];
63
+ visit(tree, 'containerDirective', (node) => wrapperEdits(node, edits));
64
+ visit(tree, 'textDirective', (node) => wrapperEdits(node, edits));
65
+ visit(tree, 'leafDirective', (node) => {
66
+ const outer = offsetsOf(node.position);
67
+ if (outer !== undefined)
68
+ edits.push(outer);
69
+ return SKIP;
70
+ });
71
+ return edits;
72
+ }
73
+ /** Script fences are removed entirely — their own `position` (the whole fence, per `@markii/core`'s `extractScripts`) is the edit. */
74
+ function collectScriptEdits(tree) {
75
+ const edits = [];
76
+ const blocks = extractScripts(tree);
77
+ for (const block of blocks) {
78
+ const offsets = offsetsOf(block.position);
79
+ if (offsets !== undefined)
80
+ edits.push(offsets);
81
+ }
82
+ return edits;
83
+ }
84
+ /**
85
+ * Downgrades `text` to plain CommonMark: container directives lose their
86
+ * opening/closing fence lines and keep their inner markdown, leaf
87
+ * directives are removed, a text directive is replaced by its own text
88
+ * content, and named script fences are removed entirely. A component
89
+ * becomes its inner content only — this is a readability floor, not a
90
+ * faithful render.
91
+ *
92
+ * Every edit is a pure deletion (the inner text a directive wraps is
93
+ * already the correct, final text — nothing is rewritten, only the
94
+ * surrounding directive syntax is cut away), applied from the END of the
95
+ * source backwards so earlier offsets stay valid as later ones are
96
+ * consumed.
97
+ */
98
+ export function mdPlainFromSource(text) {
99
+ const tree = parse(text);
100
+ const edits = [
101
+ ...collectDirectiveEdits(tree),
102
+ ...collectScriptEdits(tree),
103
+ ].sort((a, b) => b.start - a.start);
104
+ let result = text;
105
+ for (const edit of edits) {
106
+ result = result.slice(0, edit.start) + result.slice(edit.end);
107
+ }
108
+ return collapseBlankRuns(result);
109
+ }
110
+ /**
111
+ * Collapses a run of three or more consecutive newlines down to two, and
112
+ * trims the leading and trailing blank lines off the whole file.
113
+ *
114
+ * Cutting a directive out of the source leaves the blank lines that stood
115
+ * on either side of it behind, so a note with a few leaf directives comes
116
+ * out pocked with three and four line gaps. CommonMark treats any run of
117
+ * blank lines as one paragraph break, so closing them up changes nothing
118
+ * about how the file parses and a great deal about how it reads.
119
+ */
120
+ function collapseBlankRuns(text) {
121
+ return `${text
122
+ .replace(/\n{3,}/g, '\n\n')
123
+ .replace(/^\n+/, '')
124
+ .replace(/\n+$/, '')}\n`;
125
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * The `exportNote` behavior: outcome shape, wording, and format gating,
3
+ * merged from `apps/vscode/src/export-html.ts`, the host-neutral half of
4
+ * `apps/obsidian/src/export-note.ts`, and `apps/cli/src/export-note.ts`'s
5
+ * `exportHtml`.
6
+ *
7
+ * `html` and `md-plain` are fully implemented here, over `../export/note-export.js`'s
8
+ * `buildNoteExport` (already host-neutral, already shared) and
9
+ * `./export/md-plain.js`'s `mdPlainFromSource` (moved in this same phase).
10
+ * `ansi` needs an engine this package does not depend on
11
+ * (`@markii/ansi` is deliberately never a `@markii/host` dependency — see
12
+ * AGENTS.md's package boundaries), so it is rendered through the injected
13
+ * `renderAnsi` option; a host that omits it gets `Unsupported`.
14
+ *
15
+ * PDF stays a seam, never an implementation here, this module builds the same HTML
16
+ * document `html` would have written and hands it to
17
+ * `adapter.exports.htmlToPdf`, but the DEGRADE-TO-HTML-ON-FAILURE product
18
+ * behavior Obsidian's own `exportNoteAsPdf` layers on top (falling back to
19
+ * writing the HTML file when printing is unavailable or fails, and
20
+ * reporting which of the two happened) is Obsidian-specific policy about
21
+ * what to do with a seam's failure, not the seam itself — it stays
22
+ * app-side.
23
+ *
24
+ * Cascade orchestration (multi-note export, `walkNoteCascade` +
25
+ * `assignCascadeFileNames` + `rewriteCascadeLinks` + `zipExportArchive`)
26
+ * is not part of this module: it stays with each host.
27
+ */
28
+ import type { StoredValue } from '@markii/runtime';
29
+ import type { OnDiagnostic, UnavailableComponent } from '@markii/stdlib';
30
+ import type { HtmlExportEngine, ExportBodyRenderer, ExportPackStylesheet, ExportRenderInfo, StaticExportReason } from '../export/note-export.js';
31
+ import type { EmbeddedImageReport, ExportImageReader } from '../export/image-embed.js';
32
+ import type { HostAdapter, HostExportFormat, HostLabels } from './adapter.js';
33
+ /** The file name an export lands under, by format: the note's own base name with the format's own extension, so the export lands beside the note unless the host's picker says otherwise. */
34
+ export declare function exportDefaultFileName(notePath: string, format: HostExportFormat): string;
35
+ /** What a `'html'` or `'pdf'` export attempt produced. Every shape except `failed` means the host got a file, and every shape but `failed` carries the same diagnostics-facing detail so the two hosts' wording can share it. */
36
+ export type NoteFileExportOutcome = {
37
+ readonly kind: 'written';
38
+ readonly path: string;
39
+ readonly bytes: number;
40
+ /** How many last-run values were baked into the file. */
41
+ readonly valueCount: number;
42
+ /** `true` when the note contains a script fence. Absent or `false` means no scripts, so the message never explains an empty state the note cannot have. */
43
+ readonly hasScripts?: boolean;
44
+ /** Which engine rendered the body, and why when it was the static one. Diagnostics-facing only. */
45
+ readonly render: ExportRenderInfo;
46
+ /** What image embedding did. The empty report when the note has no images or none was offered a reader. */
47
+ readonly images: EmbeddedImageReport;
48
+ } | {
49
+ readonly kind: 'failed';
50
+ readonly path?: string;
51
+ readonly reason: string;
52
+ };
53
+ /** The last path segment of a `/`-separated or `\`-separated path, for naming a file in a message. */
54
+ export declare function fileNameOf(path: string): string;
55
+ /**
56
+ * The short result message for a written or failed html/pdf export. A
57
+ * success names the file. A failure says what failed and points at this
58
+ * host's own diagnostics surface (`labels.diagnosticsSurface`), never at
59
+ * a stack trace — the verbatim reason goes there instead.
60
+ */
61
+ export declare function exportResultMessage(outcome: NoteFileExportOutcome, labels: HostLabels): string;
62
+ /** One diagnostic line describing HOW an export's body was rendered: the pack-vs-static distinction, kept off the result message and put here instead. */
63
+ export declare function renderEngineDiagnosticLine(render: ExportRenderInfo): string;
64
+ /** `bytes`, formatted as a human-readable size (`"1.2 KB"`, `"512 bytes"`). */
65
+ export declare function formatByteSize(bytes: number): string;
66
+ /** The image lines for `exportDiagnosticLines`: how many images were embedded and how many bytes they added, one line per skipped image, and a remote-source count when any sources still reach the network. Empty when a note has no images at all. */
67
+ export declare function imageEmbedDiagnosticLines(images: EmbeddedImageReport): string[];
68
+ /** The full diagnostics-surface detail for one html/pdf export attempt. */
69
+ export declare function exportDiagnosticLines(outcome: NoteFileExportOutcome): string[];
70
+ /** What `exportHtmlDocumentViaAdapter` needs beyond the note's own text and path. */
71
+ export interface ExportHtmlDocumentRequest {
72
+ /** The static HTML engine. Required for the `html` and `pdf` formats; `exportNoteFileViaAdapter` declines those without it. */
73
+ readonly engine?: HtmlExportEngine;
74
+ readonly notePath: string;
75
+ readonly text: string;
76
+ readonly values?: Record<string, StoredValue>;
77
+ readonly renderBody?: ExportBodyRenderer;
78
+ readonly staticReason?: StaticExportReason;
79
+ readonly packStylesheets?: readonly ExportPackStylesheet[];
80
+ readonly packCount?: number;
81
+ readonly embedImages?: ExportImageReader;
82
+ readonly hideScriptBlocks?: boolean;
83
+ /** Installed pack components the static engine cannot draw; see `NoteExportBuildRequest`. */
84
+ readonly unavailableComponents?: readonly UnavailableComponent[];
85
+ /** Receives the static engine's render diagnostics. */
86
+ readonly onDiagnostic?: OnDiagnostic;
87
+ }
88
+ /** Builds the standalone HTML document `html` and `pdf` both export, via the shared, already-host-neutral `buildNoteExport`. Never throws: a failing `renderBody` degrades to the static engine rather than propagating. */
89
+ export declare function buildExportDocument(request: ExportHtmlDocumentRequest & {
90
+ readonly engine: HtmlExportEngine;
91
+ }): Promise<{
92
+ html: string;
93
+ valueCount: number;
94
+ hasScripts: boolean;
95
+ render: ExportRenderInfo;
96
+ images: EmbeddedImageReport;
97
+ }>;
98
+ /**
99
+ * Exports one note as `format`, gated on `adapter.exports?.formats`. A
100
+ * format the host has not declared returns `undefined` immediately
101
+ * (`createMarkiiHost`'s `exportNote` turns that into the shared
102
+ * `Unsupported` outcome and writes its own diagnostics line, matching
103
+ * every other capability-gated behavior).
104
+ *
105
+ * `renderAnsi` is required to actually produce an `'ansi'` outcome — a
106
+ * host that offers the `'ansi'` FORMAT capability but passes no renderer
107
+ * is a configuration bug on that host's part, not a Markii user's problem
108
+ * to see, so it also resolves to `undefined` here (never a thrown error)
109
+ * and the caller's `Unsupported` fallback covers it.
110
+ */
111
+ export declare function exportNoteFileViaAdapter(adapter: HostAdapter, format: HostExportFormat, request: ExportHtmlDocumentRequest, renderAnsi?: (text: string) => Promise<string>): Promise<NoteFileExportOutcome | undefined>;