@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,218 @@
1
+ /**
2
+ * `HostAdapter`: the minimal set of primitives a host (VS Code, Obsidian,
3
+ * the CLI, or a future one) provides so `createMarkiiHost` can run every
4
+ * shared behavior once. Nothing in this file names a `vscode`, `obsidian`,
5
+ * Electron or TTY type — an adapter is built from injected dependencies,
6
+ * so a test can construct the REAL adapter over fakes.
7
+ *
8
+ * A capability group (`isolate`, `editor`, `packs`, `exports`) is absent
9
+ * when a host does not offer it. `createMarkiiHost` never assumes one is
10
+ * present: a behavior whose capability is missing declines with an
11
+ * `Unsupported` outcome and a diagnostics line, per AGENTS.md's "clean is
12
+ * not silent" rule.
13
+ */
14
+ import type { GrantMemento } from '../run/grant-flow.js';
15
+ import type { IsolateSpawner } from '../run/isolate.js';
16
+ import type { LineColumn } from '@markii/stdlib/editor';
17
+ import type { DiscoveredPack } from '../packs/discover.js';
18
+ /** One entry from `listFolder`. Reuses the shape `@markii/host`'s existing `PackDirectoryLister` (`../packs/discover.ts`) already returns, rather than inventing a second. */
19
+ export interface HostDirEntry {
20
+ readonly name: string;
21
+ readonly isDirectory: boolean;
22
+ }
23
+ /**
24
+ * One yes/no question. Every grant, consent, and replace prompt in the
25
+ * whole app surface routes through this single method. `message`, `allowLabel`, and `denyLabel` are already fully
26
+ * built by `@markii/host`; the adapter only has to show them.
27
+ */
28
+ export interface HostPromptRequest {
29
+ readonly kind: 'grant-host' | 'grant-unknown-hosts' | 'grant-many-hosts' | 'pack-install-consent' | 'pack-replace' | 'pack-overwrite-export' | 'enable-scripts';
30
+ readonly message: string;
31
+ readonly allowLabel: string;
32
+ readonly denyLabel: string;
33
+ /**
34
+ * `true` for anything authorizing execution or network. A host that can
35
+ * only show a non-blocking surface (the CLI with no TTY, for example)
36
+ * must DENY rather than assume yes — that policy stays the host's own,
37
+ * `HostAdapter.prompt`'s contract only requires it never THROWS.
38
+ */
39
+ readonly consequential: boolean;
40
+ }
41
+ /**
42
+ * A discriminated union: `workerPath` and `workerBytes` are mutually
43
+ * exclusive per host. A Node host (VS Code, the CLI)
44
+ * gives a synchronous path lookup; a host whose runtime cannot create
45
+ * worker threads (an Electron renderer, which is what the Obsidian plugin
46
+ * runs in) gives an async, disposable Web Worker spawner instead.
47
+ */
48
+ export type HostIsolate = {
49
+ readonly kind: 'node';
50
+ /**
51
+ * The worker entry on disk. A host with its own bundled worker
52
+ * resolves it here. `createNodeHostIsolate()` from
53
+ * `@markii/host/node` supplies the packaged one.
54
+ */
55
+ workerPath(): string;
56
+ /**
57
+ * Creates the worker-thread isolate: `workerThreadIsolate` from
58
+ * `@markii/host/node`. Injected so the shared run path imports
59
+ * nothing from `node:*`.
60
+ */
61
+ readonly spawnIsolate: IsolateSpawner;
62
+ } | {
63
+ readonly kind: 'browser';
64
+ spawner(): Promise<IsolateSpawner>;
65
+ dispose(): void;
66
+ /**
67
+ * What a browser isolate reports as its entry. A Web Worker started
68
+ * from a blob URL has no path to name, but the run path still labels
69
+ * the entry it handed out, so this supplies that label. It is never
70
+ * resolved or opened. Defaults to `BROWSER_ISOLATE_ENTRY`.
71
+ */
72
+ entryLabel?: string;
73
+ };
74
+ /**
75
+ * The entry label a blob-URL Web Worker reports when its host does not
76
+ * supply one. It is a label, never a path: nothing resolves or opens it,
77
+ * and the worker's real bytes come from the blob the host built. It exists
78
+ * because the shared run path always names the entry it handed out, and
79
+ * the alternative (leaving the entry unset) reaches a fallback that is
80
+ * deliberately dev-only and throws in a packaged host.
81
+ */
82
+ export declare const BROWSER_ISOLATE_ENTRY = "markii:embedded-worker";
83
+ export interface HostTextEdit {
84
+ /** Zero-based. */
85
+ readonly line: number;
86
+ readonly startColumn: number;
87
+ readonly endColumn: number;
88
+ readonly text: string;
89
+ }
90
+ /**
91
+ * The live editor surface, for hosts that have one. `applyEdits` applies
92
+ * the whole array as ONE undoable edit — both GUI hosts already do this
93
+ * for insertion plus fence extension; stating it here means a third host
94
+ * cannot get it wrong. The CLI omits `editor` entirely: it has no live
95
+ * cursor or edit surface.
96
+ */
97
+ export interface HostEditor {
98
+ documentText(): string;
99
+ documentPath(): string | undefined;
100
+ cursor(): LineColumn;
101
+ applyEdits(edits: readonly HostTextEdit[]): Promise<boolean>;
102
+ }
103
+ /**
104
+ * Pack POLICY (which folders are even candidates) is irreducibly
105
+ * host-specific; everything after the list exists is
106
+ * already shared in `../packs/discover.ts` and friends. The adapter
107
+ * supplies only the list and the install destination.
108
+ */
109
+ export interface HostPackSource {
110
+ /**
111
+ * The renderer engine this host runs packs for (`@markii/pack`'s
112
+ * per-component `sources` keys): `react` for the GUI hosts, `ansi` for
113
+ * the terminal host. Required: the host layer names no engine itself.
114
+ * Discovery, completion and insert keep only packs and components that
115
+ * have a source for it.
116
+ */
117
+ readonly engine: string;
118
+ /**
119
+ * Whether installed packs are gated on a device-local trust list.
120
+ * `'device-list'`: a folder in `installRoot` loads only when the trust
121
+ * list in the adapter's `memento` names its namespace, and the host
122
+ * gains `installedPacks`, `enablePack` and `disablePack`. An install
123
+ * trusts the pack it installs. Requires `installRoot`. `'none'`: every
124
+ * folder this host's own policy authorizes loads, which is right for a
125
+ * host that trusts its own install store (VS Code, the CLI). A web host
126
+ * that installs packs from users picks `'device-list'`. Required: a
127
+ * host states its choice explicitly.
128
+ */
129
+ readonly trust: 'device-list' | 'none';
130
+ /**
131
+ * Folders this host currently AUTHORIZES. Under `trust: 'device-list'`
132
+ * these are the folders beyond the install root (the host adds the
133
+ * trusted installed ones itself): VS Code's resolved
134
+ * `markii.packs` setting plus bundled packs; Obsidian's trust-filtered
135
+ * installed folders plus bundled packs.
136
+ */
137
+ authorizedFolders(): Promise<readonly string[]>;
138
+ /**
139
+ * Namespaces an install may never claim. Empty for VS Code, the bundled
140
+ * three (`read`/`dash`/`prep`) for Obsidian.
141
+ */
142
+ reservedNamespaces(): ReadonlySet<string>;
143
+ /** Where an installed pack is unzipped. Absent means this host cannot install a pack. */
144
+ installRoot?(): string;
145
+ /**
146
+ * Packs embedded in the host's own build, with no folder on disk
147
+ * (Obsidian carries its bundled packs inside `main.js`). They join the
148
+ * catalog completion, hover, and insert read, ahead of the discovered
149
+ * ones. Absent when every pack the host offers lives in a folder
150
+ * `authorizedFolders` names.
151
+ */
152
+ bundledPacks?(): readonly DiscoveredPack[];
153
+ /** Removes an installed pack folder. Required when `installRoot` is present. */
154
+ removeFolder?(path: string): Promise<void>;
155
+ }
156
+ /** Every export format any host offers today. */
157
+ export type HostExportFormat = 'html' | 'pdf' | 'ansi' | 'md-plain';
158
+ export interface HostExportCapabilities {
159
+ readonly formats: ReadonlySet<HostExportFormat>;
160
+ /** GUI hosts show a picker; the CLI returns the path it was given. */
161
+ resolveTarget(suggestedFileName: string, format: HostExportFormat): Promise<string | undefined>;
162
+ /** Only present on a host declaring `'pdf'`. The one Electron-touching seam. */
163
+ htmlToPdf?(html: string): Promise<Uint8Array>;
164
+ }
165
+ /**
166
+ * The wording nouns every shared behavior's message templates interpolate
167
+ * (`./labels.ts`). One template plus these per-host nouns
168
+ * replaces a per-host copy of each sentence.
169
+ */
170
+ export interface HostLabels {
171
+ /** "extension" | "plugin" | "command line tool" */
172
+ readonly appNoun: string;
173
+ /** "every Markii preview" | "the Markii preview" | "the rendered note" */
174
+ readonly previewNoun: string;
175
+ /** "the Markii output" | "the Markii diagnostics" | "the run log" */
176
+ readonly diagnosticsSurface: string;
177
+ /** "this workspace" | "this device" */
178
+ readonly deviceNoun: string;
179
+ /**
180
+ * The literal setting a user would toggle to turn script execution back
181
+ * on, when this host exposes one as a named setting (`"markii.scriptsDisabled"`
182
+ * for VS Code). Naming it is how a user finds the switch without opening
183
+ * developer tools (AGENTS.md's "clean is not silent" test), so
184
+ * `./script-execution.ts`'s diagnostics-surface lines include it when
185
+ * present. `undefined` for a host with no single named setting (a
186
+ * command or toggle instead) — the plain sentence is used there.
187
+ */
188
+ readonly settingName?: string;
189
+ }
190
+ /**
191
+ * Everything an adapter provides. An adapter is constructible from
192
+ * injected dependencies (no module-scope global reads), which is what
193
+ * lets a conformance test build the REAL adapter over a fake editor
194
+ *.
195
+ */
196
+ export interface HostAdapter {
197
+ readonly id: 'vscode' | 'obsidian' | 'cli' | (string & {});
198
+ readonly labels: HostLabels;
199
+ readFile(path: string): Promise<Uint8Array>;
200
+ exists(path: string): Promise<boolean>;
201
+ writeFile(path: string, bytes: Uint8Array): Promise<void>;
202
+ /** A missing folder resolves to an empty list — never throws, matching the existing `PackDirectoryLister` contract. */
203
+ listFolder(path: string): Promise<readonly HostDirEntry[]>;
204
+ prompt(request: HostPromptRequest): Promise<boolean>;
205
+ /**
206
+ * Device-local state. Structurally the existing `GrantMemento`,
207
+ * unchanged, so VS Code keeps handing `context.workspaceState` straight
208
+ * through with no adapter wrapper at all.
209
+ */
210
+ readonly memento: GrantMemento;
211
+ /** One diagnostics line on this host's designated surface (docs/integration.md). */
212
+ diagnostics(line: string): void;
213
+ now(): number;
214
+ readonly isolate?: HostIsolate;
215
+ readonly editor?: HostEditor;
216
+ readonly packs?: HostPackSource;
217
+ readonly exports?: HostExportCapabilities;
218
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The entry label a blob-URL Web Worker reports when its host does not
3
+ * supply one. It is a label, never a path: nothing resolves or opens it,
4
+ * and the worker's real bytes come from the blob the host built. It exists
5
+ * because the shared run path always names the entry it handed out, and
6
+ * the alternative (leaving the entry unset) reaches a fallback that is
7
+ * deliberately dev-only and throws in a packaged host.
8
+ */
9
+ export const BROWSER_ISOLATE_ENTRY = 'markii:embedded-worker';
@@ -0,0 +1,120 @@
1
+ /**
2
+ * The cascade export's shared half: walking the notes a root note links
3
+ * to, rendering each one the way a single-note export does, packing the
4
+ * results into one archive, and every user-facing string that outcome
5
+ * produces. Written once so the hosts cannot drift on what a cascade is
6
+ * or what it says.
7
+ *
8
+ * Deliberately outside `createMarkiiHost`: choosing where the archive goes
9
+ * and writing it are each host's own picker and file-system business, so
10
+ * this module builds the bytes and words the outcome, and the host writes
11
+ * and displays. The per-host nouns (which surface holds the details) come
12
+ * from `HostLabels`, never from a literal here.
13
+ *
14
+ * BOUNDS ARE FIXED. The walk always uses `DEFAULT_CASCADE_MAX_DEPTH` and
15
+ * `DEFAULT_CASCADE_MAX_NOTES`; nothing here lets a caller override them.
16
+ */
17
+ import type { StoredValue } from '@markii/runtime';
18
+ import type { OnDiagnostic, UnavailableComponent } from '@markii/stdlib';
19
+ import type { CascadeLinkResolver, CascadeNoteReader, CascadeTruncation } from '../export/cascade.js';
20
+ import type { HtmlExportEngine, ExportBodyRenderer, ExportPackStylesheet, ExportRenderInfo, StaticExportReason } from '../export/note-export.js';
21
+ import type { EmbeddedImageReport, ExportImageReader } from '../export/image-embed.js';
22
+ import type { HostLabels } from './adapter.js';
23
+ /** One note the cascade exported, for the diagnostics surface. */
24
+ export interface CascadeExportedNote {
25
+ /** The note's own path, in the same form the reader and resolver use. */
26
+ readonly path: string;
27
+ /** The file name this note got inside the archive. */
28
+ readonly entryName: string;
29
+ readonly valueCount: number;
30
+ readonly render: ExportRenderInfo;
31
+ readonly images: EmbeddedImageReport;
32
+ }
33
+ /** A link that resolved to a note in range which could not be read. */
34
+ export interface UnreadableCascadeNote {
35
+ readonly path: string;
36
+ readonly from: string;
37
+ }
38
+ /** What one cascade export attempt did, for both the message and the diagnostics lines. */
39
+ export type CascadeExportOutcome = {
40
+ readonly kind: 'written';
41
+ /** The written archive's display path. */
42
+ readonly path: string;
43
+ readonly bytes: number;
44
+ /** Every note the archive contains, breadth first, the root note first. */
45
+ readonly notes: readonly CascadeExportedNote[];
46
+ /** Notes a link pointed at that could not be read. */
47
+ readonly unreadable: readonly UnreadableCascadeNote[];
48
+ /** Set when a bound stopped the walk before it ran out of links to follow. */
49
+ readonly truncated?: CascadeTruncation;
50
+ } | {
51
+ readonly kind: 'failed';
52
+ /** Where the write was attempted, when it got that far. */
53
+ readonly path?: string;
54
+ /** The verbatim reason. Diagnostics only, never the popup. */
55
+ readonly reason: string;
56
+ };
57
+ /** What building one cascade archive needs beyond a single note's export inputs. */
58
+ export interface CascadeArchiveRequest {
59
+ /** The static HTML engine every note and the index page render with. */
60
+ readonly engine: HtmlExportEngine;
61
+ /** The root note's path, in whatever form `readNote` and `resolveLink` understand. */
62
+ readonly rootPath: string;
63
+ readonly readNote: CascadeNoteReader;
64
+ readonly resolveLink: CascadeLinkResolver;
65
+ /** Reads one note's persisted last-run values, keyed by that note's own path. An empty object exports that note's standard empty states. */
66
+ readonly readValues: (notePath: string) => Record<string, StoredValue>;
67
+ /** The host's React render of a note's body, shared by every note in the cascade since it comes from one merged pack registry. Omitted renders every note with the static engine. */
68
+ readonly renderBody?: ExportBodyRenderer;
69
+ /** Why the static engine is used when `renderBody` is omitted. Defaults to `no-packs`. */
70
+ readonly staticReason?: StaticExportReason;
71
+ readonly packStylesheets?: readonly ExportPackStylesheet[];
72
+ readonly packCount?: number;
73
+ /** Installed pack components the static engine cannot draw, labeled "no HTML view" in every note rendered statically. */
74
+ readonly unavailableComponents?: readonly UnavailableComponent[];
75
+ /** Receives the static engine's render diagnostics for every note. */
76
+ readonly onDiagnostic?: OnDiagnostic;
77
+ /** Builds the image reader for one note, bound to that note's own folder so a relative source resolves against the note that wrote it. Omitted embeds no images. */
78
+ readonly embedImagesFor?: (notePath: string) => ExportImageReader;
79
+ /** Hides the collapsed script marker in every exported note. Defaults to `false`. */
80
+ readonly hideScriptBlocks?: boolean;
81
+ }
82
+ /** The archive a cascade would write, or why it could not be built. Nothing is written here; the caller owns the save path. */
83
+ export type CascadeArchiveResult = {
84
+ readonly kind: 'archive';
85
+ readonly bytes: Uint8Array;
86
+ readonly notes: readonly CascadeExportedNote[];
87
+ readonly unreadable: readonly UnreadableCascadeNote[];
88
+ readonly truncated?: CascadeTruncation;
89
+ } | {
90
+ readonly kind: 'failed';
91
+ readonly reason: string;
92
+ };
93
+ /** The verbatim reason for a thrown value, for the diagnostics surface. Never shown in a popup. */
94
+ export declare function cascadeReasonOf(error: unknown): string;
95
+ /** `count` plus `noun`, pluralized with a trailing `s`. Every noun used here is regular. */
96
+ export declare function countedNoun(count: number, noun: string): string;
97
+ /**
98
+ * Walks a note's cascade, exports every note it reaches, and returns the
99
+ * archive bytes. Never throws: an error anywhere in the walk or a render
100
+ * comes back as a `failed` result.
101
+ */
102
+ export declare function buildCascadeArchive(request: CascadeArchiveRequest): Promise<CascadeArchiveResult>;
103
+ /**
104
+ * The short message shown after a cascade export. A success names the
105
+ * archive and how many notes it holds, plus an optional host-specific
106
+ * `where` sentence (for example where the archive landed). A partial
107
+ * success adds one sentence pointing at the host's diagnostics surface,
108
+ * because a cascade that quietly left notes out would be mute about it. A
109
+ * failure says what failed and points at the same surface, never at a
110
+ * stack trace.
111
+ */
112
+ export declare function cascadeResultMessage(outcome: CascadeExportOutcome, labels: HostLabels, where?: string): string;
113
+ /**
114
+ * The lines for the host's diagnostics surface for one cascade export:
115
+ * every note reached named with the file it became, every note skipped
116
+ * named with the note that linked to it, and any bound that cut the walk
117
+ * short. A failure reaches here in full, including the reason the popup
118
+ * deliberately omits.
119
+ */
120
+ export declare function cascadeDiagnosticLines(outcome: CascadeExportOutcome): string[];
@@ -0,0 +1,151 @@
1
+ import { CASCADE_INDEX_FILE_NAME, DEFAULT_CASCADE_MAX_DEPTH, DEFAULT_CASCADE_MAX_NOTES, assignCascadeFileNames, rewriteCascadeLinks, walkNoteCascade, } from '../export/cascade.js';
2
+ import { buildCascadeIndexHtml, buildNoteExport, exportBaseName, exportDocumentTitle, } from '../export/note-export.js';
3
+ import { zipExportArchive } from '../export/export-zip.js';
4
+ import { fileNameOf, imageEmbedDiagnosticLines, renderEngineDiagnosticLine, } from './export-behavior.js';
5
+ /** The verbatim reason for a thrown value, for the diagnostics surface. Never shown in a popup. */
6
+ export function cascadeReasonOf(error) {
7
+ return error instanceof Error ? error.message : String(error);
8
+ }
9
+ /** `count` plus `noun`, pluralized with a trailing `s`. Every noun used here is regular. */
10
+ export function countedNoun(count, noun) {
11
+ return `${String(count)} ${noun}${count === 1 ? '' : 's'}`;
12
+ }
13
+ /**
14
+ * Walks a note's cascade, exports every note it reaches, and returns the
15
+ * archive bytes. Never throws: an error anywhere in the walk or a render
16
+ * comes back as a `failed` result.
17
+ */
18
+ export async function buildCascadeArchive(request) {
19
+ try {
20
+ const walk = await walkNoteCascade({
21
+ rootPath: request.rootPath,
22
+ readNote: request.readNote,
23
+ resolveLink: request.resolveLink,
24
+ maxDepth: DEFAULT_CASCADE_MAX_DEPTH,
25
+ maxNotes: DEFAULT_CASCADE_MAX_NOTES,
26
+ });
27
+ if (walk.notes.length === 0) {
28
+ const rootUnreadable = walk.unreadable.find((entry) => entry.path === request.rootPath);
29
+ return {
30
+ kind: 'failed',
31
+ reason: rootUnreadable
32
+ ? `could not read the root note ${rootUnreadable.path}`
33
+ : 'nothing was exported',
34
+ };
35
+ }
36
+ // Every archive carries an index.html listing its notes, so an unzipped
37
+ // set has one page to open first; a note named index is renamed clear of it.
38
+ const fileNames = assignCascadeFileNames(walk.notes.map((note) => note.path), [CASCADE_INDEX_FILE_NAME]);
39
+ const notes = [];
40
+ const entries = [];
41
+ for (const note of walk.notes) {
42
+ const text = rewriteCascadeLinks(note, fileNames, request.resolveLink);
43
+ const document = await buildNoteExport({
44
+ engine: request.engine,
45
+ text,
46
+ fileName: note.path,
47
+ values: request.readValues(note.path),
48
+ ...(request.renderBody !== undefined
49
+ ? { renderBody: request.renderBody }
50
+ : {}),
51
+ ...(request.staticReason !== undefined
52
+ ? { staticReason: request.staticReason }
53
+ : {}),
54
+ ...(request.packStylesheets !== undefined
55
+ ? { packStylesheets: request.packStylesheets }
56
+ : {}),
57
+ ...(request.packCount !== undefined
58
+ ? { packCount: request.packCount }
59
+ : {}),
60
+ ...(request.unavailableComponents !== undefined
61
+ ? { unavailableComponents: request.unavailableComponents }
62
+ : {}),
63
+ ...(request.onDiagnostic !== undefined
64
+ ? { onDiagnostic: request.onDiagnostic }
65
+ : {}),
66
+ ...(request.embedImagesFor !== undefined
67
+ ? { embedImages: request.embedImagesFor(note.path) }
68
+ : {}),
69
+ ...(request.hideScriptBlocks !== undefined
70
+ ? { hideScriptBlocks: request.hideScriptBlocks }
71
+ : {}),
72
+ });
73
+ const entryName = fileNames.get(note.path) ?? `${exportBaseName(note.path)}.html`;
74
+ entries.push({ name: entryName, text: document.html });
75
+ notes.push({
76
+ path: note.path,
77
+ entryName,
78
+ valueCount: document.valueCount,
79
+ render: document.render,
80
+ images: document.images,
81
+ });
82
+ }
83
+ entries.push({
84
+ name: CASCADE_INDEX_FILE_NAME,
85
+ text: buildCascadeIndexHtml(notes.map((note) => ({
86
+ title: exportDocumentTitle(note.path),
87
+ fileName: note.entryName,
88
+ })), request.engine),
89
+ });
90
+ return {
91
+ kind: 'archive',
92
+ bytes: zipExportArchive(entries),
93
+ notes,
94
+ unreadable: walk.unreadable,
95
+ ...(walk.truncated !== undefined ? { truncated: walk.truncated } : {}),
96
+ };
97
+ }
98
+ catch (error) {
99
+ return { kind: 'failed', reason: cascadeReasonOf(error) };
100
+ }
101
+ }
102
+ /**
103
+ * The short message shown after a cascade export. A success names the
104
+ * archive and how many notes it holds, plus an optional host-specific
105
+ * `where` sentence (for example where the archive landed). A partial
106
+ * success adds one sentence pointing at the host's diagnostics surface,
107
+ * because a cascade that quietly left notes out would be mute about it. A
108
+ * failure says what failed and points at the same surface, never at a
109
+ * stack trace.
110
+ */
111
+ export function cascadeResultMessage(outcome, labels, where) {
112
+ if (outcome.kind === 'failed') {
113
+ return `Markii: could not export this cascade. Open ${labels.diagnosticsSurface} for details.`;
114
+ }
115
+ const name = fileNameOf(outcome.path);
116
+ const summary = `Markii: exported ${name} with ${countedNoun(outcome.notes.length, 'note')}.`;
117
+ const partial = outcome.truncated !== undefined || outcome.unreadable.length > 0;
118
+ if (partial) {
119
+ return `${summary} Some linked notes were left out, so open ${labels.diagnosticsSurface} for details.`;
120
+ }
121
+ return where === undefined ? summary : `${summary} ${where}`;
122
+ }
123
+ /**
124
+ * The lines for the host's diagnostics surface for one cascade export:
125
+ * every note reached named with the file it became, every note skipped
126
+ * named with the note that linked to it, and any bound that cut the walk
127
+ * short. A failure reaches here in full, including the reason the popup
128
+ * deliberately omits.
129
+ */
130
+ export function cascadeDiagnosticLines(outcome) {
131
+ if (outcome.kind === 'failed') {
132
+ const where = outcome.path ? ` to ${outcome.path}` : '';
133
+ return [`Cascade export failed${where}: ${outcome.reason}`];
134
+ }
135
+ const lines = [
136
+ `Cascade export wrote ${outcome.path}: ${String(outcome.bytes)} bytes, ${countedNoun(outcome.notes.length, 'note')}.`,
137
+ ];
138
+ for (const note of outcome.notes) {
139
+ lines.push(`Exported ${note.path} as ${note.entryName} with ${countedNoun(note.valueCount, 'stored value')} baked in.`, renderEngineDiagnosticLine(note.render), ...imageEmbedDiagnosticLines(note.images));
140
+ }
141
+ if (outcome.truncated === 'depth') {
142
+ lines.push(`The walk stopped at the maximum depth of ${String(DEFAULT_CASCADE_MAX_DEPTH)} hops, so notes further out were left out.`);
143
+ }
144
+ else if (outcome.truncated === 'count') {
145
+ lines.push(`The walk stopped at the maximum of ${countedNoun(DEFAULT_CASCADE_MAX_NOTES, 'note')}, so the remaining links were left out.`);
146
+ }
147
+ for (const entry of outcome.unreadable) {
148
+ lines.push(`Could not read ${entry.path}, linked from ${entry.from}.`);
149
+ }
150
+ return lines;
151
+ }
@@ -0,0 +1,159 @@
1
+ import type { ArchiveExtractFs } from './pack-archive.js';
2
+ import type { RunTrigger } from '@markii/runtime';
3
+ import type { HostAdapter } from './adapter.js';
4
+ /**
5
+ * `conformance/host/`, resolved relative to this module's own file, never
6
+ * `process.cwd()`. An installed package carries the corpus at
7
+ * `dist/conformance/host/`, a sibling of `dist/host/`; a repo checkout runs
8
+ * from `src/host/` and finds the repo-root corpus through
9
+ * `@markii/core/corpus`'s `conformanceDir`.
10
+ */
11
+ export declare function hostConformanceDir(): string;
12
+ /** Every scenario directory name under `dir`, sorted. */
13
+ export declare function listHostScenarioNames(dir?: string): string[];
14
+ /** A `run` action's stand-in for what a real isolate would have produced — see this file's top comment. */
15
+ export interface RunSimulation {
16
+ /** The script/value name the simulated run reports on. */
17
+ readonly name: string;
18
+ /** The value a successful simulated run stores. Defaults to `1`. */
19
+ readonly value?: number;
20
+ /** A host that must be in the run's granted allowlist for the value to land; otherwise the simulated run reports `'capability-denied'`. */
21
+ readonly requiresHost?: string;
22
+ /** `true` for a simulated write: the run reports `'tier-blocked'` under any trigger but `'manual'`, regardless of grants. */
23
+ readonly requiresManualTier?: boolean;
24
+ }
25
+ export type HostScenarioAction = {
26
+ readonly type: 'open';
27
+ } | {
28
+ readonly type: 'run';
29
+ readonly trigger: RunTrigger;
30
+ /** `"allow"`/`"deny"`, consumed by the grant flow's prompts, in order, across the WHOLE scenario (see `createAnswerQueue`). */
31
+ readonly answers?: readonly string[];
32
+ /** When `true`, this action never reaches `host.run` at all — see this file's top comment on the scripts-disabled scenario. */
33
+ readonly scriptsDisabled?: boolean;
34
+ readonly simulate?: RunSimulation;
35
+ } | {
36
+ readonly type: 'exportHtml';
37
+ } | {
38
+ readonly type: 'exportAnsi';
39
+ } | {
40
+ readonly type: 'installPack';
41
+ /** A directory under the scenario's own folder (e.g. `"files/ana-1.0.0"`), zipped into `.mkp` bytes by this module — never a committed binary. */
42
+ readonly archive: string;
43
+ readonly answers?: readonly string[];
44
+ /** `true`: the action belongs to a trust scenario, so a host without the device trust list reports it as `unsupported` (`pack-trust`) instead of running it. */
45
+ readonly requiresTrust?: boolean;
46
+ } | {
47
+ readonly type: 'loadPacks';
48
+ readonly requiresTrust?: boolean;
49
+ } | {
50
+ readonly type: 'removePack';
51
+ readonly namespace: string;
52
+ readonly requiresTrust?: boolean;
53
+ } | {
54
+ /** Copies a directory under the scenario's folder into the install root as `<namespace>/`, with no consent and no trust entry: the way a pack arrives through a file sync or a hand copy. */
55
+ readonly type: 'placePack';
56
+ readonly archive: string;
57
+ readonly namespace: string;
58
+ } | {
59
+ readonly type: 'listPacks';
60
+ } | {
61
+ readonly type: 'enablePack';
62
+ readonly namespace: string;
63
+ readonly answers?: readonly string[];
64
+ } | {
65
+ readonly type: 'disablePack';
66
+ readonly namespace: string;
67
+ } | {
68
+ readonly type: 'completeAt';
69
+ readonly line: number;
70
+ readonly column: number;
71
+ } | {
72
+ readonly type: 'hoverAt';
73
+ readonly line: number;
74
+ readonly column: number;
75
+ } | {
76
+ readonly type: 'insertComponent';
77
+ readonly name: string;
78
+ };
79
+ export interface HostScenario {
80
+ readonly id: string;
81
+ readonly invariant: string;
82
+ /** Fixes `adapter.now()` for the whole scenario, so a `RunTrace` write is byte-stable. */
83
+ readonly clock: number;
84
+ readonly actions: readonly HostScenarioAction[];
85
+ }
86
+ /** `expected.json`'s schema. `outcomes[i]` is a coarse, partial projection of `actions[i]`'s real outcome — see `outcomeMismatches` for exactly what each `kind` compares. */
87
+ export interface HostScenarioExpected {
88
+ readonly outcomes: readonly Record<string, unknown>[];
89
+ readonly diagnostics: readonly string[];
90
+ readonly prompts: readonly string[];
91
+ }
92
+ export interface LoadedHostScenario {
93
+ readonly dir: string;
94
+ readonly noteText: string;
95
+ readonly scenario: HostScenario;
96
+ readonly expected: HostScenarioExpected;
97
+ }
98
+ /** Loads one scenario's three files. Never validates beyond `JSON.parse` — a malformed fixture is a corpus bug, and throwing loudly here is the right failure mode for it. */
99
+ export declare function loadHostScenario(name: string, baseDir?: string): LoadedHostScenario;
100
+ /** Zips a scenario's `files/<name>` directory into `.mkp` archive bytes on the fly — this is how the corpus ships pack fixtures as plain files instead of a committed binary. */
101
+ export declare function zipArchiveDirectory(archiveDir: string): Uint8Array;
102
+ export type HostScenarioCapability = 'isolate' | 'editor' | 'packs' | 'pack-trust' | 'export-format';
103
+ /** The one static map from an action's type to the capability it needs. `undefined` means every host supports it (there is nothing to decline). */
104
+ export declare const ACTION_CAPABILITY: Readonly<Record<HostScenarioAction['type'], HostScenarioCapability | undefined>>;
105
+ /** Whether `adapter` declares the capability `action` needs. Never consults the scenario or `expected.json` — only the adapter under test. */
106
+ export declare function hostDeclaresCapability(adapter: HostAdapter, action: HostScenarioAction): boolean;
107
+ /**
108
+ * Flattens every `run`/`installPack` action's `answers` into one queue, in
109
+ * document order — the same order the grant/consent prompts fire in, since
110
+ * `expected.json`'s `prompts` array is itself the ordered proof of that.
111
+ * `next()` THROWS once exhausted rather than returning a default: an
112
+ * unanswered prompt means the scenario and the implementation disagree
113
+ * about how many questions get asked, which must never be silently read
114
+ * as a deny.
115
+ */
116
+ export declare function createAnswerQueue(scenario: HostScenario): {
117
+ next(): boolean;
118
+ };
119
+ export interface HostScenarioRunResult {
120
+ /** `outcomes[i]` is `actions[i]`'s real (or synthesized, for the two cases documented above) outcome, JSON-shaped for diffing. */
121
+ readonly outcomes: readonly unknown[];
122
+ /** Every diagnostics line written, UNPREFIXED (this module wraps `adapter.diagnostics` before the real adapter's own sink prefixing runs), in order. */
123
+ readonly diagnostics: readonly string[];
124
+ /** Every prompt's `message`, in order, exactly as built by `@markii/host`'s shared message functions. */
125
+ readonly prompts: readonly string[];
126
+ /** Set when a scripted prompt ran out of answers — a hard error, never folded into a silent deny. */
127
+ readonly promptQueueError?: string;
128
+ }
129
+ /**
130
+ * Runs `scenario` against `adapter` end to end. `adapter` is the REAL,
131
+ * already-constructed host adapter under test (built by the caller from
132
+ * its own injected dependencies plus a scripted prompt source, e.g.
133
+ * `createAnswerQueue`); this function never builds one itself, so it works
134
+ * identically for any `HostAdapter`.
135
+ */
136
+ export declare function runHostScenario(adapter: HostAdapter, scenario: HostScenario, noteText: string, scenarioDir: string, options?: {
137
+ readonly renderAnsi?: (text: string) => Promise<string>;
138
+ /** Where an installed pack archive is extracted. Defaults to the Node file system, which is right for an adapter that reads and writes real files. An adapter over another store (memory, a virtual file system) passes the matching writer. */
139
+ readonly archiveExtractFs?: ArchiveExtractFs;
140
+ }): Promise<HostScenarioRunResult>;
141
+ /** `expected[i]` compared against `actual[i]` for each index up to `expected`'s own length; a length mismatch is reported once rather than index-by-index noise. */
142
+ export declare function containsInOrder(actual: readonly string[], expected: readonly string[]): string[];
143
+ /**
144
+ * A coarse, per-`kind` comparison between one action's real outcome and its
145
+ * `expected.json` entry. Deliberately partial (matching the design's own
146
+ * "deliberately coarse" posture): a `'ran'` outcome compares landed values
147
+ * and failure KINDS, never timings or raw messages; an `'exported'` outcome
148
+ * compares `contains`/`excludes` substrings against the written file's
149
+ * content, never the whole document.
150
+ */
151
+ export declare function outcomeMismatches(actual: unknown, expected: Record<string, unknown> | undefined): string[];
152
+ /**
153
+ * The full assertion an app's conformance test makes for one scenario: for
154
+ * every action whose capability `adapter` declares, its outcome matches
155
+ * `expected`'s; for every other action, the outcome is exactly the shared
156
+ * `unsupported` record. Returns a list of human-readable problems — empty
157
+ * means the scenario passed.
158
+ */
159
+ export declare function assertHostScenario(adapter: HostAdapter, scenario: HostScenario, expected: HostScenarioExpected, result: HostScenarioRunResult): string[];