@markii/host 0.0.0-stage → 0.17.0-next.2

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/LICENSE +21 -0
  2. package/README.md +19 -2
  3. package/dist/browser-worker.js +639 -0
  4. package/dist/browser.d.ts +57 -0
  5. package/dist/browser.js +167 -0
  6. package/dist/conformance/host/01-plain-open/expected.json +5 -0
  7. package/dist/conformance/host/01-plain-open/note.mk.md +8 -0
  8. package/dist/conformance/host/01-plain-open/scenario.json +6 -0
  9. package/dist/conformance/host/02-run-manual-grant-accepted/expected.json +16 -0
  10. package/dist/conformance/host/02-run-manual-grant-accepted/note.mk.md +5 -0
  11. package/dist/conformance/host/02-run-manual-grant-accepted/scenario.json +14 -0
  12. package/dist/conformance/host/03-run-manual-grant-refused/expected.json +18 -0
  13. package/dist/conformance/host/03-run-manual-grant-refused/note.mk.md +5 -0
  14. package/dist/conformance/host/03-run-manual-grant-refused/scenario.json +14 -0
  15. package/dist/conformance/host/04-run-auto-write-denied/expected.json +16 -0
  16. package/dist/conformance/host/04-run-auto-write-denied/note.mk.md +10 -0
  17. package/dist/conformance/host/04-run-auto-write-denied/scenario.json +13 -0
  18. package/dist/conformance/host/05-export-html-pack-directive/expected.json +14 -0
  19. package/dist/conformance/host/05-export-html-pack-directive/note.mk.md +8 -0
  20. package/dist/conformance/host/05-export-html-pack-directive/scenario.json +6 -0
  21. package/dist/conformance/host/06-install-pack-then-render/expected.json +18 -0
  22. package/dist/conformance/host/06-install-pack-then-render/files/ana-1.0.0/pack.json +13 -0
  23. package/dist/conformance/host/06-install-pack-then-render/files/ana-1.0.0/webview.js +1 -0
  24. package/dist/conformance/host/06-install-pack-then-render/note.mk.md +5 -0
  25. package/dist/conformance/host/06-install-pack-then-render/scenario.json +15 -0
  26. package/dist/conformance/host/07-complete-in-directive/expected.json +14 -0
  27. package/dist/conformance/host/07-complete-in-directive/note.mk.md +3 -0
  28. package/dist/conformance/host/07-complete-in-directive/scenario.json +6 -0
  29. package/dist/conformance/host/08-hover-standard-component/expected.json +5 -0
  30. package/dist/conformance/host/08-hover-standard-component/note.mk.md +5 -0
  31. package/dist/conformance/host/08-hover-standard-component/scenario.json +6 -0
  32. package/dist/conformance/host/09-insert-skeleton/expected.json +5 -0
  33. package/dist/conformance/host/09-insert-skeleton/note.mk.md +4 -0
  34. package/dist/conformance/host/09-insert-skeleton/scenario.json +6 -0
  35. package/dist/conformance/host/10-scripts-disabled/expected.json +11 -0
  36. package/dist/conformance/host/10-scripts-disabled/note.mk.md +5 -0
  37. package/dist/conformance/host/10-scripts-disabled/scenario.json +9 -0
  38. package/dist/conformance/host/11-complete-closing-fence/expected.json +5 -0
  39. package/dist/conformance/host/11-complete-closing-fence/note.mk.md +5 -0
  40. package/dist/conformance/host/11-complete-closing-fence/scenario.json +12 -0
  41. package/dist/conformance/host/12-complete-nested-container/expected.json +5 -0
  42. package/dist/conformance/host/12-complete-nested-container/note.mk.md +5 -0
  43. package/dist/conformance/host/12-complete-nested-container/scenario.json +12 -0
  44. package/dist/conformance/host/13-install-pack-declined/expected.json +12 -0
  45. package/dist/conformance/host/13-install-pack-declined/files/ana-1.0.0/pack.json +13 -0
  46. package/dist/conformance/host/13-install-pack-declined/files/ana-1.0.0/webview.js +1 -0
  47. package/dist/conformance/host/13-install-pack-declined/note.mk.md +5 -0
  48. package/dist/conformance/host/13-install-pack-declined/scenario.json +10 -0
  49. package/dist/conformance/host/14-remove-pack/expected.json +14 -0
  50. package/dist/conformance/host/14-remove-pack/files/ana-1.0.0/pack.json +13 -0
  51. package/dist/conformance/host/14-remove-pack/files/ana-1.0.0/webview.js +1 -0
  52. package/dist/conformance/host/14-remove-pack/note.mk.md +5 -0
  53. package/dist/conformance/host/14-remove-pack/scenario.json +11 -0
  54. package/dist/conformance/host/15-load-packs-engine-views/expected.json +19 -0
  55. package/dist/conformance/host/15-load-packs-engine-views/files/duo-1.0.0/pack.json +9 -0
  56. package/dist/conformance/host/15-load-packs-engine-views/files/duo-1.0.0/webview.js +1 -0
  57. package/dist/conformance/host/15-load-packs-engine-views/note.mk.md +9 -0
  58. package/dist/conformance/host/15-load-packs-engine-views/scenario.json +10 -0
  59. package/dist/conformance/host/16-pack-trust/expected.json +37 -0
  60. package/dist/conformance/host/16-pack-trust/files/ana-1.0.0/pack.json +12 -0
  61. package/dist/conformance/host/16-pack-trust/files/ana-1.0.0/webview.js +1 -0
  62. package/dist/conformance/host/16-pack-trust/files/sneaky-1.0.0/pack.json +12 -0
  63. package/dist/conformance/host/16-pack-trust/files/sneaky-1.0.0/webview.js +1 -0
  64. package/dist/conformance/host/16-pack-trust/note.mk.md +5 -0
  65. package/dist/conformance/host/16-pack-trust/scenario.json +26 -0
  66. package/dist/conformance/host/README.md +221 -0
  67. package/dist/conformance.d.ts +2 -0
  68. package/dist/conformance.js +5 -0
  69. package/dist/diagnostics/render-diagnostics.d.ts +41 -0
  70. package/dist/diagnostics/render-diagnostics.js +46 -0
  71. package/dist/export/cascade.d.ts +98 -0
  72. package/dist/export/cascade.js +159 -0
  73. package/dist/export/export-zip.d.ts +19 -0
  74. package/dist/export/export-zip.js +38 -0
  75. package/dist/export/image-embed.d.ts +126 -0
  76. package/dist/export/image-embed.js +274 -0
  77. package/dist/export/note-export.d.ts +332 -0
  78. package/dist/export/note-export.js +366 -0
  79. package/dist/export/note-links.d.ts +83 -0
  80. package/dist/export/note-links.js +241 -0
  81. package/dist/glue.wasm +0 -0
  82. package/dist/host/adapter.d.ts +223 -0
  83. package/dist/host/adapter.js +9 -0
  84. package/dist/host/cascade-export-behavior.d.ts +120 -0
  85. package/dist/host/cascade-export-behavior.js +151 -0
  86. package/dist/host/conformance-runner.d.ts +156 -0
  87. package/dist/host/conformance-runner.js +634 -0
  88. package/dist/host/create-host.d.ts +253 -0
  89. package/dist/host/create-host.js +329 -0
  90. package/dist/host/editor-behavior.d.ts +157 -0
  91. package/dist/host/editor-behavior.js +227 -0
  92. package/dist/host/export/md-plain.d.ts +15 -0
  93. package/dist/host/export/md-plain.js +125 -0
  94. package/dist/host/export-behavior.d.ts +113 -0
  95. package/dist/host/export-behavior.js +257 -0
  96. package/dist/host/labels.d.ts +24 -0
  97. package/dist/host/labels.js +32 -0
  98. package/dist/host/pack-archive.d.ts +30 -0
  99. package/dist/host/pack-archive.js +68 -0
  100. package/dist/host/pack-install.d.ts +88 -0
  101. package/dist/host/pack-install.js +189 -0
  102. package/dist/host/pack-load.d.ts +72 -0
  103. package/dist/host/pack-load.js +57 -0
  104. package/dist/host/pack-remove.d.ts +21 -0
  105. package/dist/host/pack-remove.js +65 -0
  106. package/dist/host/pack-trust-behavior.d.ts +79 -0
  107. package/dist/host/pack-trust-behavior.js +262 -0
  108. package/dist/host/refresh-interval.d.ts +39 -0
  109. package/dist/host/refresh-interval.js +61 -0
  110. package/dist/host/run-behavior.d.ts +89 -0
  111. package/dist/host/run-behavior.js +168 -0
  112. package/dist/host/run-diagnostics.d.ts +26 -0
  113. package/dist/host/run-diagnostics.js +43 -0
  114. package/dist/host/script-execution.d.ts +72 -0
  115. package/dist/host/script-execution.js +68 -0
  116. package/dist/host/terminal-packs.d.ts +39 -0
  117. package/dist/host/terminal-packs.js +51 -0
  118. package/dist/html-engine.d.ts +2 -0
  119. package/dist/html-engine.js +29 -0
  120. package/dist/index.d.ts +95 -0
  121. package/dist/index.js +70 -0
  122. package/dist/insert/component-catalog.d.ts +23 -0
  123. package/dist/insert/component-catalog.js +96 -0
  124. package/dist/node-fs.d.ts +14 -0
  125. package/dist/node-fs.js +70 -0
  126. package/dist/node.d.ts +5 -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 +81 -0
  132. package/dist/packs/pack-diagnostics.js +105 -0
  133. package/dist/packs/pack-paths.d.ts +53 -0
  134. package/dist/packs/pack-paths.js +106 -0
  135. package/dist/packs/pack-render-registry.d.ts +83 -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 +81 -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 +57 -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 +70 -0
  166. package/dist/run/net-provider.js +289 -0
  167. package/dist/run/node-isolate.d.ts +30 -0
  168. package/dist/run/node-isolate.js +117 -0
  169. package/dist/run/run-flow.d.ts +187 -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 +42 -0
  186. package/dist/run/worker-entry-browser.js +77 -0
  187. package/dist/run/worker-entry.d.ts +1 -0
  188. package/dist/run/worker-entry.js +96 -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 +84 -4
@@ -0,0 +1,157 @@
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
+ * Survey finding A5: `apps/vscode/src/completion.ts:completionOriginTag`
8
+ * and `apps/obsidian/src/complete-component.ts:completionOriginTag` had
9
+ * IDENTICAL function bodies, differing only in which locally-defined
10
+ * constant stood in for the standard/layout tag string. Both apps'
11
+ * constants held the same lower-case values (`'standard'`/`'layout'`),
12
+ * so there was never a wording difference to preserve.
13
+ */
14
+ export declare function completionOriginTag(item: CompletionItem): string;
15
+ /** 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. */
16
+ export interface CatalogCache {
17
+ /** 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. */
18
+ get(): Promise<readonly InsertableComponent[]>;
19
+ /** The cached catalog if one is built, else `undefined`. Never starts a build. */
20
+ peek(): readonly InsertableComponent[] | undefined;
21
+ /** Drops the cached catalog (and any in-flight build), so the next `get()` rebuilds from `load`. */
22
+ invalidate(): void;
23
+ }
24
+ /**
25
+ * Builds a `CatalogCache` around `buildCatalog` (typically
26
+ * `../insert/component-catalog.js`'s `buildComponentCatalog` composed with
27
+ * a pack-discovery call). Concurrent `get()` calls made before a build
28
+ * finishes share one in-flight promise rather than each starting their own
29
+ * pack discovery.
30
+ */
31
+ export declare function createCatalogCache(buildCatalog: () => Promise<readonly InsertableComponent[]>): CatalogCache;
32
+ /**
33
+ * `completionAt` composed over a `HostEditor`'s document text, at the
34
+ * POSITION a completion request names — not `editor.cursor()`. A real
35
+ * editor's completion provider is invoked with an explicit position (the
36
+ * position the user is typing at when the provider fires), which is why a
37
+ * completion/hover request always carries its own `line`/`column` rather
38
+ * than reading the live caret a second time. `editor.cursor()` stays
39
+ * reserved for `insertComponentPlan`, which genuinely inserts at the
40
+ * caret.
41
+ */
42
+ export declare function completeAtViaEditor(editor: Pick<HostEditor, 'documentText'>, position: LineColumn, catalog: readonly InsertableComponent[]): CompletionContext;
43
+ /**
44
+ * Everything a completion UI needs for one request, decided once in the
45
+ * shared layer: the items, the replace span (`replaceStart`/`replaceEnd`),
46
+ * the token start a filtering popup should score against (`tokenStart`,
47
+ * which equals `replaceStart` unless a colon run precedes the name), and
48
+ * the fence lengthening edits an accepted CONTAINER item needs. Hosts only
49
+ * map this onto their own popup type.
50
+ */
51
+ export interface CompletionPlan {
52
+ readonly kind: CompletionContext['kind'];
53
+ readonly items: readonly CompletionItem[];
54
+ readonly replaceStart: number;
55
+ readonly replaceEnd: number;
56
+ readonly tokenStart: number;
57
+ /**
58
+ * Fence rewrites to apply in the SAME undo step as an accepted container
59
+ * item, in pre-edit coordinates, never on the insertion line. Empty when
60
+ * no item in the response opens a container or the document's fences do
61
+ * not pair cleanly. Every container item in one context inserts the same
62
+ * fence shape, so one list serves them all; a host applies it only when
63
+ * the accepted item is a container (`isContainerCompletionItem`).
64
+ */
65
+ readonly fenceEdits: readonly HostTextEdit[];
66
+ }
67
+ /** Whether accepting `item` opens a container, and so needs `CompletionPlan.fenceEdits` applied with it. */
68
+ export declare function isContainerCompletionItem(item: {
69
+ readonly insertText: string;
70
+ }): boolean;
71
+ /**
72
+ * `completionAt` over a whole document at a position, plus the two
73
+ * decisions both editors used to repeat at their own trigger boundary: a
74
+ * bare colon run that CLOSES an open container is not an opener waiting
75
+ * for a name (so no plan), and fence edits are computed once per response.
76
+ * Returns `undefined` when there is nothing to offer. Never throws.
77
+ */
78
+ export declare function completionPlanAt(documentText: string, position: LineColumn, catalog: readonly InsertableComponent[]): CompletionPlan | undefined;
79
+ /** `hoverAt` composed over a `HostEditor`'s document text at a given position, the same way `completeAtViaEditor` composes `completionAt`. */
80
+ export declare function hoverAtViaEditor(editor: Pick<HostEditor, 'documentText'>, position: LineColumn, catalog: readonly InsertableComponent[]): HoverInfo | undefined;
81
+ /**
82
+ * Plain-text hover documentation for `item`: `@markii/stdlib/editor`'s
83
+ * `formatComponentDocumentation`, the shape every host without a
84
+ * rich-markdown hover surface (or a host doing its own markdown mapping)
85
+ * can use as-is. This is also what `createMarkiiHost`'s `hoverAt` returns
86
+ * (via `hoverAtViaEditor`'s `HoverInfo.documentation`) rather than the
87
+ * bare summary sentence: the summary alone can read as generic prose with
88
+ * no component name in it (a callout's summary is "A colored box for an
89
+ * aside, warning, or danger note.", nowhere mentioning "callout"), while
90
+ * the formatted text's `Example: :::callout{}` line always names the
91
+ * directive.
92
+ */
93
+ export declare function hoverDocumentationText(item: InsertableComponent): string;
94
+ /** One text edit and the cursor position an insertion leaves behind, both relative to the document `insertionLine`/`insertionColumn` named. */
95
+ export interface InsertComponentEdit {
96
+ readonly line: number;
97
+ readonly startColumn: number;
98
+ readonly endColumn: number;
99
+ readonly text: string;
100
+ }
101
+ export interface InsertComponentPlan {
102
+ /**
103
+ * Every edit `HostEditor.applyEdits` must apply together as one
104
+ * undoable edit: the skeleton insertion plus any fence lines an
105
+ * enclosing container needs lengthened.
106
+ *
107
+ * All edits are expressed in the ORIGINAL document's coordinates (the
108
+ * text `documentText` named, before any of these edits land) — exactly
109
+ * like `fenceExtensionEdits` already promises, and exactly what both
110
+ * apps' `HostTextEdit`-consuming adapters already assume of a
111
+ * multi-edit `applyEdits` call. That is what makes the array safe to
112
+ * apply as one atomic transaction: a host whose `applyEdits` resolves
113
+ * every position against the pre-edit document (VS Code's
114
+ * `WorkspaceEdit`, Obsidian's `Editor.transaction`) can apply them in
115
+ * any order.
116
+ *
117
+ * The array is ALSO returned bottom-to-top (descending by line, ties
118
+ * broken by descending column) as a second-line defense for a simpler
119
+ * host: one that applies this list as a sequence of plain string
120
+ * splices against a single mutable buffer rather than a coordinate-free
121
+ * transaction. Splicing text at a line shifts every line and column
122
+ * number below it; splicing from the bottom of the document upward
123
+ * means each edit is always applied before anything that could move
124
+ * out from under it, and nothing below an edit's line is ever touched
125
+ * after that edit lands. Fence edits never land on the insertion line
126
+ * itself (`fenceExtensionEdits`'s own guarantee, an enclosing pair by
127
+ * definition straddles it), so the insertion edit's own line never
128
+ * collides with a fence edit's line and the ordering rule never has to
129
+ * break a tie between them.
130
+ */
131
+ readonly edits: readonly InsertComponentEdit[];
132
+ readonly cursor: LineColumn;
133
+ }
134
+ /**
135
+ * Builds the skeleton for `component` (`@markii/stdlib/editor`'s
136
+ * `componentSkeleton`), turns its flat `cursorOffset` into an absolute
137
+ * line/column given where the insertion happens, and folds in the fence
138
+ * edits (`@markii/stdlib/editor`'s `fenceExtensionEdits`) an enclosing
139
+ * container needs lengthened so it still nests legally. This is the pure
140
+ * half of both apps' `insert-component.ts` command handlers: build the
141
+ * skeleton, insert it at the cursor, lengthen any enclosing fences, place
142
+ * the cursor inside it — all in one plan a host applies as one undoable
143
+ * edit (`HostEditor.applyEdits`'s contract).
144
+ *
145
+ * `documentText` is the document as it stands before this insertion (the
146
+ * same text `fenceExtensionEdits` scans); a caller inserting from a
147
+ * completion accept passes the same text it already read to decide what
148
+ * to insert.
149
+ *
150
+ * Never throws: a failure to compute fence edits (hostile input, an
151
+ * unpaired document `fenceExtensionEdits` refuses to touch) degrades to
152
+ * the skeleton insertion alone, exactly as inserting always worked before
153
+ * fence auto-extension existed.
154
+ */
155
+ export declare function insertComponentPlan(component: Pick<InsertableComponent, 'directiveName' | 'kind'> & {
156
+ readonly requiredAttributes?: readonly string[];
157
+ }, documentText: string, insertionLine: number, insertionColumn: number): InsertComponentPlan;
@@ -0,0 +1,227 @@
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` (survey finding A5, plus table rows 31/33).
6
+ *
7
+ * NODE-FREE by design (`tmp/W11-adapter-design.md`, section 1): 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, per the survey's section D): 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
+ * Survey finding A5: `apps/vscode/src/completion.ts:completionOriginTag`
29
+ * and `apps/obsidian/src/complete-component.ts:completionOriginTag` had
30
+ * IDENTICAL function bodies, differing only in which locally-defined
31
+ * constant stood in for the standard/layout tag string. Both apps'
32
+ * constants held the same lower-case values (`'standard'`/`'layout'`),
33
+ * so there was never a wording difference to preserve.
34
+ */
35
+ export function completionOriginTag(item) {
36
+ if (item.kind !== 'component')
37
+ return '';
38
+ if (item.group === 'standard')
39
+ return STANDARD_ORIGIN_TAG;
40
+ if (item.group === 'layout')
41
+ return LAYOUT_ORIGIN_TAG;
42
+ return item.packName ?? '';
43
+ }
44
+ /**
45
+ * Builds a `CatalogCache` around `buildCatalog` (typically
46
+ * `../insert/component-catalog.js`'s `buildComponentCatalog` composed with
47
+ * a pack-discovery call). Concurrent `get()` calls made before a build
48
+ * finishes share one in-flight promise rather than each starting their own
49
+ * pack discovery.
50
+ */
51
+ export function createCatalogCache(buildCatalog) {
52
+ let cached;
53
+ let pending;
54
+ async function build() {
55
+ try {
56
+ return await buildCatalog();
57
+ }
58
+ catch {
59
+ return [];
60
+ }
61
+ }
62
+ return {
63
+ async get() {
64
+ if (cached !== undefined)
65
+ return cached;
66
+ if (pending === undefined) {
67
+ pending = build().then((catalog) => {
68
+ cached = catalog;
69
+ pending = undefined;
70
+ return catalog;
71
+ });
72
+ }
73
+ return pending;
74
+ },
75
+ peek() {
76
+ return cached;
77
+ },
78
+ invalidate() {
79
+ cached = undefined;
80
+ pending = undefined;
81
+ },
82
+ };
83
+ }
84
+ /** The line of `documentText` that `line` (zero-based) names, or `''` past the end of the document. */
85
+ function lineTextAt(documentText, line) {
86
+ return documentText.split('\n')[line] ?? '';
87
+ }
88
+ /**
89
+ * `completionAt` composed over a `HostEditor`'s document text, at the
90
+ * POSITION a completion request names — not `editor.cursor()`. A real
91
+ * editor's completion provider is invoked with an explicit position (the
92
+ * position the user is typing at when the provider fires), which is why a
93
+ * completion/hover request always carries its own `line`/`column` rather
94
+ * than reading the live caret a second time. `editor.cursor()` stays
95
+ * reserved for `insertComponentPlan`, which genuinely inserts at the
96
+ * caret.
97
+ */
98
+ export function completeAtViaEditor(editor, position, catalog) {
99
+ const line = lineTextAt(editor.documentText(), position.line);
100
+ return completionAt(line, position.column, catalog);
101
+ }
102
+ /** Whether accepting `item` opens a container, and so needs `CompletionPlan.fenceEdits` applied with it. */
103
+ export function isContainerCompletionItem(item) {
104
+ try {
105
+ return insertedContainerColonCount(item.insertText) !== undefined;
106
+ }
107
+ catch {
108
+ return false;
109
+ }
110
+ }
111
+ /**
112
+ * `completionAt` over a whole document at a position, plus the two
113
+ * decisions both editors used to repeat at their own trigger boundary: a
114
+ * bare colon run that CLOSES an open container is not an opener waiting
115
+ * for a name (so no plan), and fence edits are computed once per response.
116
+ * Returns `undefined` when there is nothing to offer. Never throws.
117
+ */
118
+ export function completionPlanAt(documentText, position, catalog) {
119
+ try {
120
+ const line = lineTextAt(documentText, position.line);
121
+ if (closesOpenContainerFence(documentText, position.line, line)) {
122
+ return undefined;
123
+ }
124
+ const context = completionAt(line, position.column, catalog);
125
+ if (context.kind === 'none' || context.items.length === 0) {
126
+ return undefined;
127
+ }
128
+ const container = context.items.find(isContainerCompletionItem);
129
+ let fenceEdits = [];
130
+ if (container !== undefined) {
131
+ try {
132
+ fenceEdits = fenceExtensionEdits(documentText, position.line, container.insertText)
133
+ .filter((edit) => edit.line !== position.line)
134
+ .map((edit) => ({
135
+ line: edit.line,
136
+ startColumn: edit.column,
137
+ endColumn: edit.column + edit.oldText.length,
138
+ text: edit.newText,
139
+ }));
140
+ }
141
+ catch {
142
+ fenceEdits = [];
143
+ }
144
+ }
145
+ return {
146
+ kind: context.kind,
147
+ items: context.items,
148
+ replaceStart: context.replaceStart,
149
+ replaceEnd: context.replaceEnd,
150
+ tokenStart: context.tokenStart ?? context.replaceStart,
151
+ fenceEdits,
152
+ };
153
+ }
154
+ catch {
155
+ return undefined;
156
+ }
157
+ }
158
+ /** `hoverAt` composed over a `HostEditor`'s document text at a given position, the same way `completeAtViaEditor` composes `completionAt`. */
159
+ export function hoverAtViaEditor(editor, position, catalog) {
160
+ const line = lineTextAt(editor.documentText(), position.line);
161
+ return hoverAt(line, position.column, catalog);
162
+ }
163
+ /**
164
+ * Plain-text hover documentation for `item`: `@markii/stdlib/editor`'s
165
+ * `formatComponentDocumentation`, the shape every host without a
166
+ * rich-markdown hover surface (or a host doing its own markdown mapping)
167
+ * can use as-is. This is also what `createMarkiiHost`'s `hoverAt` returns
168
+ * (via `hoverAtViaEditor`'s `HoverInfo.documentation`) rather than the
169
+ * bare summary sentence: the summary alone can read as generic prose with
170
+ * no component name in it (a callout's summary is "A colored box for an
171
+ * aside, warning, or danger note.", nowhere mentioning "callout"), while
172
+ * the formatted text's `Example: :::callout{}` line always names the
173
+ * directive.
174
+ */
175
+ export function hoverDocumentationText(item) {
176
+ return formatComponentDocumentation(componentDocumentation(item));
177
+ }
178
+ /**
179
+ * Builds the skeleton for `component` (`@markii/stdlib/editor`'s
180
+ * `componentSkeleton`), turns its flat `cursorOffset` into an absolute
181
+ * line/column given where the insertion happens, and folds in the fence
182
+ * edits (`@markii/stdlib/editor`'s `fenceExtensionEdits`) an enclosing
183
+ * container needs lengthened so it still nests legally. This is the pure
184
+ * half of both apps' `insert-component.ts` command handlers: build the
185
+ * skeleton, insert it at the cursor, lengthen any enclosing fences, place
186
+ * the cursor inside it — all in one plan a host applies as one undoable
187
+ * edit (`HostEditor.applyEdits`'s contract).
188
+ *
189
+ * `documentText` is the document as it stands before this insertion (the
190
+ * same text `fenceExtensionEdits` scans); a caller inserting from a
191
+ * completion accept passes the same text it already read to decide what
192
+ * to insert.
193
+ *
194
+ * Never throws: a failure to compute fence edits (hostile input, an
195
+ * unpaired document `fenceExtensionEdits` refuses to touch) degrades to
196
+ * the skeleton insertion alone, exactly as inserting always worked before
197
+ * fence auto-extension existed.
198
+ */
199
+ export function insertComponentPlan(component, documentText, insertionLine, insertionColumn) {
200
+ const skeleton = componentSkeleton(component.directiveName, component.kind, component.requiredAttributes ?? []);
201
+ const relative = offsetToLineColumn(skeleton.text, skeleton.cursorOffset);
202
+ const cursor = relative.line === 0
203
+ ? { line: insertionLine, column: insertionColumn + relative.column }
204
+ : { line: insertionLine + relative.line, column: relative.column };
205
+ const insertionEdit = {
206
+ line: insertionLine,
207
+ startColumn: insertionColumn,
208
+ endColumn: insertionColumn,
209
+ text: skeleton.text,
210
+ };
211
+ let fenceEdits;
212
+ try {
213
+ fenceEdits = fenceExtensionEdits(documentText, insertionLine, skeleton.text)
214
+ .filter((edit) => edit.line !== insertionLine)
215
+ .map((edit) => ({
216
+ line: edit.line,
217
+ startColumn: edit.column,
218
+ endColumn: edit.column + edit.oldText.length,
219
+ text: edit.newText,
220
+ }));
221
+ }
222
+ catch {
223
+ fenceEdits = [];
224
+ }
225
+ const edits = [...fenceEdits, insertionEdit].sort((a, b) => a.line !== b.line ? b.line - a.line : b.startColumn - a.startColumn);
226
+ return { edits, cursor };
227
+ }
@@ -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,113 @@
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` (survey finding A10, table rows 22/26).
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, exactly as
16
+ * `tmp/W11-adapter-design.md` specifies: this module builds the same HTML
17
+ * document `html` would have written and hands it to
18
+ * `adapter.exports.htmlToPdf`, but the DEGRADE-TO-HTML-ON-FAILURE product
19
+ * behavior Obsidian's own `exportNoteAsPdf` layers on top (falling back to
20
+ * writing the HTML file when printing is unavailable or fails, and
21
+ * reporting which of the two happened) is Obsidian-specific policy about
22
+ * what to do with a seam's failure, not the seam itself — it stays
23
+ * app-side. See `tmp/W11-phase1b.md` for this call.
24
+ *
25
+ * Cascade orchestration (multi-note export, `walkNoteCascade` +
26
+ * `assignCascadeFileNames` + `rewriteCascadeLinks` + `zipExportArchive`)
27
+ * is NOT moved in this pass — see `tmp/W11-phase1b.md`'s deviations
28
+ * section for why, and what Phase 2 (or a follow-up) still needs to do.
29
+ */
30
+ import type { StoredValue } from '@markii/runtime';
31
+ import type { OnDiagnostic, UnavailableComponent } from '@markii/stdlib';
32
+ import type { HtmlExportEngine, ExportBodyRenderer, ExportPackStylesheet, ExportRenderInfo, StaticExportReason } from '../export/note-export.js';
33
+ import type { EmbeddedImageReport, ExportImageReader } from '../export/image-embed.js';
34
+ import type { HostAdapter, HostExportFormat, HostLabels } from './adapter.js';
35
+ /** 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. */
36
+ export declare function exportDefaultFileName(notePath: string, format: HostExportFormat): string;
37
+ /** 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. */
38
+ export type NoteFileExportOutcome = {
39
+ readonly kind: 'written';
40
+ readonly path: string;
41
+ readonly bytes: number;
42
+ /** How many last-run values were baked into the file. */
43
+ readonly valueCount: number;
44
+ /** `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. */
45
+ readonly hasScripts?: boolean;
46
+ /** Which engine rendered the body, and why when it was the static one. Diagnostics-facing only. */
47
+ readonly render: ExportRenderInfo;
48
+ /** What image embedding did. The empty report when the note has no images or none was offered a reader. */
49
+ readonly images: EmbeddedImageReport;
50
+ } | {
51
+ readonly kind: 'failed';
52
+ readonly path?: string;
53
+ readonly reason: string;
54
+ };
55
+ /** The last path segment of a `/`-separated or `\`-separated path, for naming a file in a message. */
56
+ export declare function fileNameOf(path: string): string;
57
+ /**
58
+ * The short result message for a written or failed html/pdf export. A
59
+ * success names the file. A failure says what failed and points at this
60
+ * host's own diagnostics surface (`labels.diagnosticsSurface`), never at
61
+ * a stack trace — the verbatim reason goes there instead.
62
+ */
63
+ export declare function exportResultMessage(outcome: NoteFileExportOutcome, labels: HostLabels): string;
64
+ /** 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. */
65
+ export declare function renderEngineDiagnosticLine(render: ExportRenderInfo): string;
66
+ /** `bytes`, formatted as a human-readable size (`"1.2 KB"`, `"512 bytes"`). */
67
+ export declare function formatByteSize(bytes: number): string;
68
+ /** 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. */
69
+ export declare function imageEmbedDiagnosticLines(images: EmbeddedImageReport): string[];
70
+ /** The full diagnostics-surface detail for one html/pdf export attempt. */
71
+ export declare function exportDiagnosticLines(outcome: NoteFileExportOutcome): string[];
72
+ /** What `exportHtmlDocumentViaAdapter` needs beyond the note's own text and path. */
73
+ export interface ExportHtmlDocumentRequest {
74
+ /** The static HTML engine. Required for the `html` and `pdf` formats; `exportNoteFileViaAdapter` declines those without it. */
75
+ readonly engine?: HtmlExportEngine;
76
+ readonly notePath: string;
77
+ readonly text: string;
78
+ readonly values?: Record<string, StoredValue>;
79
+ readonly renderBody?: ExportBodyRenderer;
80
+ readonly staticReason?: StaticExportReason;
81
+ readonly packStylesheets?: readonly ExportPackStylesheet[];
82
+ readonly packCount?: number;
83
+ readonly embedImages?: ExportImageReader;
84
+ readonly hideScriptBlocks?: boolean;
85
+ /** Installed pack components the static engine cannot draw; see `NoteExportBuildRequest`. */
86
+ readonly unavailableComponents?: readonly UnavailableComponent[];
87
+ /** Receives the static engine's render diagnostics. */
88
+ readonly onDiagnostic?: OnDiagnostic;
89
+ }
90
+ /** 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. */
91
+ export declare function buildExportDocument(request: ExportHtmlDocumentRequest & {
92
+ readonly engine: HtmlExportEngine;
93
+ }): Promise<{
94
+ html: string;
95
+ valueCount: number;
96
+ hasScripts: boolean;
97
+ render: ExportRenderInfo;
98
+ images: EmbeddedImageReport;
99
+ }>;
100
+ /**
101
+ * Exports one note as `format`, gated on `adapter.exports?.formats`. A
102
+ * format the host has not declared returns `undefined` immediately
103
+ * (`createMarkiiHost`'s `exportNote` turns that into the shared
104
+ * `Unsupported` outcome and writes its own diagnostics line, matching
105
+ * every other capability-gated behavior).
106
+ *
107
+ * `renderAnsi` is required to actually produce an `'ansi'` outcome — a
108
+ * host that offers the `'ansi'` FORMAT capability but passes no renderer
109
+ * is a configuration bug on that host's part, not a Markii user's problem
110
+ * to see, so it also resolves to `undefined` here (never a thrown error)
111
+ * and the caller's `Unsupported` fallback covers it.
112
+ */
113
+ export declare function exportNoteFileViaAdapter(adapter: HostAdapter, format: HostExportFormat, request: ExportHtmlDocumentRequest, renderAnsi?: (text: string) => Promise<string>): Promise<NoteFileExportOutcome | undefined>;