@paged-media/plugin-sdk 0.2.6-canary.0 → 0.2.9-canary.0

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 (3) hide show
  1. package/dist/index.d.ts +504 -32
  2. package/dist/index.js +1338 -39
  3. package/package.json +14 -2
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { PagedBundle, BundleHost, Disposable, ShellSurface, PagedEditor, PluginManifest, CanvasPointerEvent, Mutation, MutationOutcome, ToolContribution, PanelContribution } from '@paged-media/plugin-api';
1
+ import { PagedBundle, FontFaceAsset, BundleHost, ConsentResult, DataProviderRegistration, DataProviderHandle, DataProviderInfo, DataProviderSnapshot, Disposable, ShellSurface, WidgetSurface, SchemaPanelRenderer, SchemaPanelContribution, EditContextContribution, ObjectTypeContribution, Diagnostic, PagedEditor, PluginManifest, ToolContribution, PanelContribution, CommandContribution, KeybindingContribution, OverlayContribution, ImporterContribution, ExporterContribution, ToolPreviewShape, BindingsSurface, PanelProps, SchemaGate, WasmArtifact, CanvasPointerEvent, Mutation, MutationOutcome } from '@paged-media/plugin-api';
2
+ import { ComponentType } from 'react';
2
3
 
3
4
  /**
4
5
  * Identity helper that pins a bundle to the `PagedBundle` contract
@@ -8,36 +9,6 @@ import { PagedBundle, BundleHost, Disposable, ShellSurface, PagedEditor, PluginM
8
9
  */
9
10
  declare function defineBundle(bundle: PagedBundle): PagedBundle;
10
11
 
11
- interface HarnessOptions {
12
- /** IDML bytes to load into the headless document. */
13
- idml?: Uint8Array;
14
- }
15
- /** Not implemented in API v0 — see module comment. */
16
- declare function createHeadlessHost(_options?: HarnessOptions): BundleHost;
17
-
18
- declare class DisposableStore implements Disposable {
19
- private items;
20
- private disposed;
21
- get isDisposed(): boolean;
22
- /** Track `d`; returns it for chaining. */
23
- add<T extends Disposable>(d: T): T;
24
- dispose(): void;
25
- }
26
- /** Wrap a plain cleanup function as a Disposable. */
27
- declare function toDisposable(fn: () => void): Disposable;
28
-
29
- /** The plugin API version this SDK implements. */
30
- declare const API_VERSION = "0.2.0";
31
- /**
32
- * Does `version` satisfy `range`? Supported forms:
33
- * `*` — anything
34
- * `1.2.3` / `1.2`— exact (missing patch = 0)
35
- * `^1.2.3` — npm caret: same major, >= base (major > 0);
36
- * same major+minor, >= patch (major == 0 — the 0.x
37
- * rule: minors are breaking during incubation)
38
- */
39
- declare function satisfiesApiVersion(range: string, version?: string): boolean;
40
-
41
12
  /** The implemented feature set — `host.supports()` answers from this,
42
13
  * so docs/tests can't drift from code. Form: `"area.member@major"`. */
43
14
  declare const HOST_FEATURES: readonly string[];
@@ -46,6 +17,27 @@ declare const HOST_FEATURES: readonly string[];
46
17
  declare class PluginApiNotImplemented extends Error {
47
18
  constructor(member: string, pointer: string);
48
19
  }
20
+ /**
21
+ * Thrown (in `capabilityMode: 'enforce'`) when a bundle USES a host
22
+ * door it did not DECLARE in its manifest — the trust-line record's
23
+ * (W0.11) "manifest capabilities ENFORCED, not advisory" gate, applied
24
+ * at the chokepoint. Same loud-honesty style as the namespace gate: the
25
+ * message names the door, the missing declaration, and where to add it.
26
+ *
27
+ * v1 stance (in-process, no isolation): this is HONESTY +
28
+ * accident-prevention, not a security boundary — a bundle holding the
29
+ * raw `host.editor` handle can still bypass the facade. The error makes
30
+ * declaration↔use drift loud during dogfooding so the manifest stays a
31
+ * truthful description of what the bundle actually touches.
32
+ */
33
+ declare class PluginCapabilityError extends Error {
34
+ /** The host door that was called (e.g. `"contribute.tool"`). */
35
+ readonly door: string;
36
+ /** The manifest declaration that would authorize it (e.g.
37
+ * `'contributes.tools[] must include "media.paged.web.tool.pen"'`). */
38
+ readonly missingDeclaration: string;
39
+ constructor(door: string, missingDeclaration: string, pluginId: string);
40
+ }
49
41
  /** Minimal storage backing so tests / headless hosts can inject one;
50
42
  * defaults to localStorage when present, else an in-memory Map. */
51
43
  interface StorageBacking {
@@ -55,14 +47,192 @@ interface StorageBacking {
55
47
  /** All keys currently in the backing (unfiltered). */
56
48
  keys(): string[];
57
49
  }
50
+ /** The host-provided network consent primitive (paged.data D-03; base-idea §11).
51
+ * The editor owns the consent UI (the visible data-source manifest) and the
52
+ * CSP `connect-src` enforcement; this hook is how the host adapter asks the
53
+ * user. When absent, `host.network.requestConsent` DENIES every origin (the
54
+ * honest no-consent posture) and `supports("network.consent@1")` is false. */
55
+ interface ConsentBackend {
56
+ request(origins: readonly string[], purpose: string): Promise<ConsentResult>;
57
+ }
58
+ /**
59
+ * A host-side aggregator the editor injects to power a PROBLEMS PANEL
60
+ * (paged.web W-05): every `host.diagnostics.set/clear` is mirrored
61
+ * here keyed by `(bundleId, key)`, so one editor surface can list
62
+ * diagnostics across all loaded bundles. The per-bundle in-host store
63
+ * (`host.diagnostics.get`) is unchanged — this is a fan-out, not a
64
+ * replacement. `bundleId` lets the panel attribute + de-dupe and
65
+ * lets click-to-focus resolve the owning panel.
66
+ */
67
+ interface DiagnosticsSink {
68
+ publish(bundleId: string, key: string, diagnostics: Diagnostic[]): void;
69
+ clear(bundleId: string, key?: string): void;
70
+ }
71
+ /** Asset-store budgets (W-06). The per-face cap mirrors the wasm lane's
72
+ * per-artifact ceiling (DESIGN.md §10/§13.3) — a bundle can never be
73
+ * handed an unbounded face buffer. The host facade refuses an
74
+ * over-budget face (returns `null` + a `log.warn`). */
75
+ declare const ASSET_BUDGETS: {
76
+ /** Largest font face the door will serve, in bytes (8 MiB). */
77
+ readonly maxFontFaceBytes: number;
78
+ };
79
+ /**
80
+ * The byte source the editor injects to back `host.assets` (W-06). The
81
+ * same injection shape as `widgets`/`diagnosticsSink`: a value the host
82
+ * app passes at `loadBundle` time. It serves the bytes the DOCUMENT
83
+ * already holds for a face — READ-ONLY, never a network fetch on the
84
+ * bundle's behalf (offline-forever, DESIGN.md §13.3). Returning `null`
85
+ * is the honest no-bytes answer (an unregistered family, or — in v1 of
86
+ * the editor adapter — every family, until the engine exposes a
87
+ * font-bytes read-back; DESIGN.md §13.4).
88
+ */
89
+ interface BundleAssetProvider {
90
+ /** The bytes of a document font face, or `null` when the host has
91
+ * none. Style-agnostic when `style` is omitted. */
92
+ getFontFace(family: string, style?: string): Promise<FontFaceAsset | null>;
93
+ }
94
+ /** Blob-store budgets (K-4 / S-08). The default per-plugin quota when a
95
+ * manifest requests none; a manifest's `storage.quotaBytes` may only
96
+ * TIGHTEN it (the adapter enforces the stricter). */
97
+ declare const BLOB_BUDGETS: {
98
+ /** Default per-plugin blob ceiling, in bytes (64 MiB). */
99
+ readonly defaultQuotaBytes: number;
100
+ };
101
+ /**
102
+ * The backend the editor injects to back `host.blob` (K-4 / S-08): a
103
+ * RAW per-plugin byte store (OPFS in-browser; an in-memory map in the
104
+ * headless harness). The SDK adapter owns namespacing (passes the
105
+ * plugin id), the capability gate, and the quota — the backend only does
106
+ * scoped IO. Keys are opaque strings the adapter forwards verbatim.
107
+ */
108
+ interface BlobStore {
109
+ write(pluginId: string, key: string, bytes: Uint8Array): Promise<void>;
110
+ read(pluginId: string, key: string): Promise<Uint8Array | null>;
111
+ delete(pluginId: string, key: string): Promise<void>;
112
+ keys(pluginId: string): Promise<string[]>;
113
+ /** Total bytes this plugin currently stores. */
114
+ used(pluginId: string): Promise<number>;
115
+ }
116
+ /** The SHARED cross-plugin data-provider registry (paged.data §7.1 / D-09). The
117
+ * editor creates ONE (`createDataProviderRegistry`) and injects the SAME
118
+ * instance into every plugin host, so a provider plugin and a consumer plugin
119
+ * rendezvous through it. The per-plugin capability gate lives in the host
120
+ * surface; this backend is the neutral store + fan-out. */
121
+ interface DataProviderBackend {
122
+ register(registration: DataProviderRegistration): DataProviderHandle;
123
+ discover(category?: string): readonly DataProviderInfo[];
124
+ get(id: string): Promise<DataProviderSnapshot | null>;
125
+ onDidChange(id: string, listener: (revision: string) => void): Disposable;
126
+ }
127
+ /** Build a shared in-memory data-provider registry (D-09). The editor holds ONE
128
+ * and injects it into every `createBundleHost` call as `options.dataProviders`;
129
+ * providers and consumers (different plugin hosts) meet through it. Reference
130
+ * implementation — an RPC/isolate host swaps a proxy with the same contract. */
131
+ declare function createDataProviderRegistry(): DataProviderBackend;
132
+ /**
133
+ * Trust posture the HOST asserts for a bundle at the load path
134
+ * (`plugin-trust-line.md`). Same-realm execution — full `BundleHost`
135
+ * + the raw `PagedEditor` via `host.editor` — is FIRST-PARTY-ONLY
136
+ * during incubation. There is no sound *cryptographic* identity signal
137
+ * yet (package signing is the last unchecked gate box), and the
138
+ * manifest `id` namespace (`media.paged.*`) is SELF-ASSERTED — a
139
+ * foreign bundle could claim it — so the trust signal cannot come from
140
+ * the bundle. It comes from the HOST vouching, here: a value the
141
+ * caller supplies, defaulting to `'first-party'`. The loader refuses
142
+ * anything else (`load.ts`), turning the drift-by-default the trust
143
+ * line warns about (a quiet dynamic-import of foreign code on the
144
+ * same-realm path) into a deliberate, greppable, reviewable act that
145
+ * still does not load until the isolate/RPC host gate is built.
146
+ */
147
+ type BundleTrust = "first-party";
58
148
  interface CreateBundleHostOptions {
149
+ /**
150
+ * The host's trust assertion for this bundle (`plugin-trust-line.md`).
151
+ * Defaults to `'first-party'` — the only value the loader accepts
152
+ * today. Passing any other value (e.g. via a future dynamic-import
153
+ * experiment) is refused loudly at the load path, gated on the
154
+ * isolate/RPC host + capability enforcement + signing.
155
+ */
156
+ trust?: BundleTrust;
59
157
  storage?: StorageBacking;
158
+ /** Host-provided network consent (paged.data D-03; base-idea §11): the editor
159
+ * injects the consent prompt + the data-source-manifest UI. When absent,
160
+ * `host.network` denies every origin and `supports("network.consent@1")` is
161
+ * false (the honest no-consent posture). */
162
+ consent?: ConsentBackend;
163
+ /** Host-provided SHARED data-provider registry (paged.data §7.1 / D-09): the
164
+ * editor creates ONE (`createDataProviderRegistry`) and injects the SAME
165
+ * instance into every plugin host, so a provider and a consumer rendezvous
166
+ * through it. When absent, `host.dataProviders` discover() is empty +
167
+ * register() is a no-op and `supports("dataProviders@1")` is false. */
168
+ dataProviders?: DataProviderBackend;
60
169
  /** Console sink override (tests). */
61
170
  console?: Pick<Console, "debug" | "info" | "warn" | "error">;
62
171
  /** Shell actions the HOST APP owns (the cockpit's panel placement).
63
172
  * When absent, `host.shell` warns and no-ops, and
64
173
  * `supports("shell.openPanel@1")` answers false. */
65
174
  shell?: ShellSurface;
175
+ /** Host-provided panel widgets (W-04): the real code editor lives in
176
+ * the editor's UI package and is injected here. When absent,
177
+ * `host.widgets` is the plain-textarea fallback and
178
+ * `supports("widgets.codeEditor@1")` answers false. */
179
+ widgets?: WidgetSurface;
180
+ /** Host-provided SCHEMA-PANEL renderer (W3.1): the editor's
181
+ * `SchemaPanelRenderer` that walks a `PanelSchema` through the
182
+ * catalog + subscribes to the bundle's bindings. When absent,
183
+ * `contribute.schemaPanel` registers a visible "needs a host
184
+ * renderer" seam panel (never a throw, never fake UI) and
185
+ * `supports("contribute.schemaPanel@1")` still answers true (the
186
+ * door exists; only the rich rendering is host-injected). */
187
+ schemaPanelRenderer?: SchemaPanelRenderer;
188
+ /** Internal registration hook (the headless harness): called at the
189
+ * moment a schema panel registers, with the verbatim contribution,
190
+ * so the conformance log can record the SCHEMA assertably (the
191
+ * registry only sees the synthesized React `PanelContribution`). The
192
+ * returned disposer (if any) is tracked alongside the panel
193
+ * registration. Not part of the public contract — a host-adapter
194
+ * seam. */
195
+ onSchemaPanelRegistered?: (contribution: SchemaPanelContribution) => Disposable | void;
196
+ /** Internal registration hook (the headless harness, W3.2): called
197
+ * when an EDIT CONTEXT registers (after the namespace + capability
198
+ * gates pass), with the verbatim contribution, so the conformance log
199
+ * can record it. When the editor provides no `editContexts` registry,
200
+ * this is ALSO the recording stub's only consumer — the door no
201
+ * longer throws, it records. Not part of the public contract. */
202
+ onEditContextRegistered?: (contribution: EditContextContribution) => Disposable | void;
203
+ /** Internal registration hook (the headless harness, W3.2): the
204
+ * object-type analogue of `onEditContextRegistered`. */
205
+ onObjectTypeRegistered?: (contribution: ObjectTypeContribution) => Disposable | void;
206
+ /** Host-side problems-panel aggregator (W-05). When present,
207
+ * `supports("diagnostics.publish@1")` answers true and every
208
+ * `host.diagnostics.set/clear` fans out to it. */
209
+ diagnosticsSink?: DiagnosticsSink;
210
+ /** Host-provided ASSET byte source (W-06). When present,
211
+ * `host.assets.getFontFace` serves DOCUMENT font face bytes through
212
+ * it (capability-gated, budget-clamped) and
213
+ * `supports("assets.fonts@1")` answers true. When absent, every
214
+ * asset read answers `null` (the honest no-bytes door) and the
215
+ * feature flag is false. */
216
+ assetSource?: BundleAssetProvider;
217
+ /** Host-provided BLOB backend (K-4 / S-08). When present,
218
+ * `host.blob.*` persists per-plugin bytes through it (capability-
219
+ * gated, quota-clamped) and `supports("storage.blob@1")` answers true.
220
+ * When absent, reads answer empty and writes reject (the honest
221
+ * no-store door). */
222
+ blobStore?: BlobStore;
223
+ /**
224
+ * How the host treats a declaration↔use mismatch — a bundle that
225
+ * USES a door (`contribute.tool`, `document.mutate`, …) it did not
226
+ * DECLARE in its manifest (trust-line W0.11). Defaults to
227
+ * `'enforce'`: the violating call throws `PluginCapabilityError`
228
+ * (contribution registrations) or returns a non-applied
229
+ * `MutationOutcome` for the write doors (mutate-never-throws). In
230
+ * `'warn'` mode the violation is logged through `host.log.warn`
231
+ * and the call proceeds — the migration escape hatch for a host
232
+ * that loads not-yet-adopted manifests. The namespace gate and the
233
+ * metadata-namespace gate are UNAFFECTED — they are always loud.
234
+ */
235
+ capabilityMode?: "enforce" | "warn";
66
236
  }
67
237
  interface BundleHostHandle {
68
238
  host: BundleHost;
@@ -71,6 +241,214 @@ interface BundleHostHandle {
71
241
  }
72
242
  declare function createBundleHost(getEditor: () => PagedEditor, manifest: PluginManifest, options?: CreateBundleHostOptions): BundleHostHandle;
73
243
 
244
+ /** The published package the headless harness boots. */
245
+ declare const CANVAS_WASM_PKG = "@paged-media/canvas-wasm";
246
+ /**
247
+ * The wasm `CanvasWorker` surface the harness drives. A structural
248
+ * subset of the package's class — only the members the headless host
249
+ * needs. `handleMessage` is the JSON-envelope door; `loadDocumentDirect`
250
+ * is the binary side-channel for IDML bytes (the editor uses it to dodge
251
+ * the 8× `number[]` JSON inflation, and it is the only load path that
252
+ * does not require a `LoadDocument` envelope round-trip).
253
+ */
254
+ interface HeadlessCanvasWorker {
255
+ readonly protocolVersion: number;
256
+ handleMessage(input: string): string;
257
+ loadDocumentDirect(seq: number, bytes: Uint8Array, font?: Uint8Array, cmykIccProfile?: Uint8Array): string;
258
+ runResolveJson(): string | undefined;
259
+ free(): void;
260
+ }
261
+ interface LoadedEngine {
262
+ worker: HeadlessCanvasWorker;
263
+ /** The resolved package version (`0.<protocol>.<patch>`). */
264
+ version: string;
265
+ /** The wasm's reported protocol (the package minor). */
266
+ protocolVersion: number;
267
+ }
268
+ interface LoadHeadlessEngineOptions {
269
+ /**
270
+ * Where to resolve `@paged-media/canvas-wasm` from. Defaults to this
271
+ * module's own resolution (the SDK's node_modules), then the editor's
272
+ * `packages/client` (the sibling-checkout dev luxury) — mirroring
273
+ * scripts/sync-wire.mjs so the loader and the wire stamp agree on
274
+ * WHICH installed package they describe.
275
+ */
276
+ resolveFrom?: string;
277
+ /**
278
+ * Override the expected protocol the booted wasm must report. Defaults
279
+ * to the protocol derived from the vendored wire stamp. A mismatch
280
+ * throws — never silently downgrades.
281
+ */
282
+ expectedProtocol?: number;
283
+ }
284
+ /** Extract `@<version>` from the vendored wire stamp, or null. Async:
285
+ * the file read pulls `node:fs`, deferred so the browser barrel stays
286
+ * import-safe (only the Node headless path ever calls this). */
287
+ declare function readVendoredWireVersion(wireDtsPath?: string): Promise<string | null>;
288
+ /** The package minor IS the wire protocol (`0.<protocol>.<patch>`). */
289
+ declare function protocolFromVersion(version: string): number | null;
290
+ /**
291
+ * Resolve the published package's loader JS + `_bg.wasm` on disk.
292
+ * Throws (never warn-skips) when the package cannot be resolved: a
293
+ * headless host with no engine behind it is the exact fiction the
294
+ * harness exists to prevent (B-13).
295
+ */
296
+ declare function resolveCanvasWasm(resolveFrom?: string): Promise<{
297
+ loaderUrl: string;
298
+ wasmPath: string;
299
+ version: string;
300
+ dir: string;
301
+ }>;
302
+ /**
303
+ * Boot the published engine wasm in Node and return a `CanvasWorker`
304
+ * driving the editor's own dispatch. Asserts the booted protocol
305
+ * matches the vendored wire stamp.
306
+ */
307
+ declare function loadHeadlessEngine(options?: LoadHeadlessEngineOptions): Promise<LoadedEngine>;
308
+
309
+ /** One captured contribution — the assertable conformance log entry.
310
+ * `kind` is the surface; `value` is the contribution object verbatim
311
+ * (so a test can assert ids, titles, shortcuts, dock edges, …). */
312
+ interface RecordedContribution {
313
+ kind: "tool" | "panel" | "schemaPanel" | "command" | "keybinding" | "overlay" | "editContext" | "objectType" | "importer" | "exporter";
314
+ id: string;
315
+ value: ToolContribution | PanelContribution | SchemaPanelContribution | CommandContribution | KeybindingContribution | OverlayContribution | EditContextContribution | ObjectTypeContribution | ImporterContribution | ExporterContribution;
316
+ }
317
+ interface HarnessOptions extends LoadHeadlessEngineOptions, Pick<CreateBundleHostOptions, "console" | "storage" | "capabilityMode" | "assetSource" | "blobStore"> {
318
+ }
319
+ /** What `createHeadlessHost` resolves to: a real engine-backed host plus
320
+ * the conformance affordances (load an IDML, read the contribution log,
321
+ * load a bundle, dispose honestly). */
322
+ interface HeadlessHost {
323
+ /** The BundleHost a bundle's `activate(host)` receives. */
324
+ readonly host: BundleHost;
325
+ /** The booted package version (`0.<protocol>.<patch>`). */
326
+ readonly engineVersion: string;
327
+ /** The wasm's reported protocol (the package minor). */
328
+ readonly protocolVersion: number;
329
+ /** Every contribution registered through `host.contribute.*`, in
330
+ * registration order. Cleared structurally on dispose. */
331
+ readonly contributions: readonly RecordedContribution[];
332
+ /** Contributions of one surface (e.g. `tools()` for the rail). */
333
+ toolsContributed(): ToolContribution[];
334
+ panelsContributed(): PanelContribution[];
335
+ /** Declarative (schema) panels registered through
336
+ * `contribute.schemaPanel` — the W3.1 surface, recorded verbatim. */
337
+ schemaPanelsContributed(): SchemaPanelContribution[];
338
+ /** Edit contexts registered through `contribute.editContext` — the
339
+ * W3.2 surface (B-02), recorded verbatim (matcher fn included). */
340
+ editContextsContributed(): EditContextContribution[];
341
+ /** Object types registered through `contribute.objectType` — the W3.2
342
+ * surface (W-03), recorded verbatim. */
343
+ objectTypesContributed(): ObjectTypeContribution[];
344
+ /** Document importers registered through `contribute.importer` (K-2 /
345
+ * S-06), recorded verbatim (extensions + the `import()` callback). */
346
+ importersContributed(): ImporterContribution[];
347
+ /** Document exporters registered through `contribute.exporter` (K-2 /
348
+ * S-06), recorded verbatim. */
349
+ exportersContributed(): ExporterContribution[];
350
+ /** The last tool preview a bundle pushed through
351
+ * `host.overlay.setToolPreview` (B-07). There is no overlay SURFACE
352
+ * headlessly, but the channel is RECORDED so conformance can assert a
353
+ * pen/anchor handler emits the cubic `ToolPreviewPath` variant (true
354
+ * Béziers) rather than a flattened polyline. `null` until set, and
355
+ * reset to `null` when the bundle clears its preview. */
356
+ lastToolPreview(): ToolPreviewShape | null;
357
+ /** Load an IDML package into the headless document. Resolves to the
358
+ * loaded page ids (or throws on a parse failure). */
359
+ load(idml: Uint8Array): Promise<string[]>;
360
+ /** Activate a bundle against this host (apiVersion-negotiated). The
361
+ * returned disposer runs the bundle's teardown; the host's own
362
+ * facade teardown runs on `dispose()`. */
363
+ loadBundle(bundle: PagedBundle): Disposable;
364
+ /** Tear down: bundle teardown + facade teardown + free the wasm. The
365
+ * honesty contract — after dispose the contribution log is empty and
366
+ * the engine handle is released. */
367
+ dispose(): void;
368
+ }
369
+ /** Reserved alias kept for source/back-compat — see `createHeadlessHost`. */
370
+ type HeadlessHostHandle = HeadlessHost;
371
+ /**
372
+ * Build a headless, engine-backed `BundleHost`. Async because booting
373
+ * the wasm is async. The returned handle drives the conformance loop:
374
+ * load an IDML, activate a bundle, assert its contribution log, run real
375
+ * mutations, and dispose honestly.
376
+ *
377
+ * Resolves B-13 (RESOLVED + residuals — see module header). Replaces the
378
+ * v0 throw: the harness now stands on a real engine, so a bundle can no
379
+ * longer "pass against fiction".
380
+ */
381
+ declare function createHeadlessHost(options?: HarnessOptions): Promise<HeadlessHost>;
382
+
383
+ declare class DisposableStore implements Disposable {
384
+ private items;
385
+ private disposed;
386
+ get isDisposed(): boolean;
387
+ /** Track `d`; returns it for chaining. */
388
+ add<T extends Disposable>(d: T): T;
389
+ dispose(): void;
390
+ }
391
+ /** Wrap a plain cleanup function as a Disposable. */
392
+ declare function toDisposable(fn: () => void): Disposable;
393
+
394
+ /** The plugin API version this SDK implements. */
395
+ declare const API_VERSION = "0.2.0";
396
+ /**
397
+ * Does `version` satisfy `range`? Supported forms:
398
+ * `*` — anything
399
+ * `1.2.3` / `1.2`— exact (missing patch = 0)
400
+ * `^1.2.3` — npm caret: same major, >= base (major > 0);
401
+ * same major+minor, >= patch (major == 0 — the 0.x
402
+ * rule: minors are breaking during incubation)
403
+ */
404
+ declare function satisfiesApiVersion(range: string, version?: string): boolean;
405
+
406
+ /** The default widget catalog: a textarea CodeEditor. Replaced wholesale
407
+ * when the host app injects `widgets` into `createBundleHost`. */
408
+ declare const FALLBACK_WIDGETS: WidgetSurface;
409
+
410
+ /** One recorded `getFontFace` call. */
411
+ interface RecordedFontFaceRequest {
412
+ family: string;
413
+ style?: string;
414
+ }
415
+ /** A seeded face the fake serves. `family` match is case-insensitive
416
+ * (the document registry and CSS often differ only in casing). When
417
+ * `style` is given on a seed, the request must match it; a seed with no
418
+ * style answers any style request for that family. */
419
+ interface SeededFace extends FontFaceAsset {
420
+ /** When set, the seed only answers a request for this exact style. */
421
+ matchStyle?: string;
422
+ }
423
+ interface RecordableAssetSource extends BundleAssetProvider {
424
+ /** Every `getFontFace` call, in order. */
425
+ readonly requests: readonly RecordedFontFaceRequest[];
426
+ }
427
+ /**
428
+ * Build a recordable, in-memory asset source from a set of seeded
429
+ * faces. Families match case-insensitively; a seed carrying
430
+ * `matchStyle` only answers that style. Unknown families resolve to
431
+ * `null` (the honest no-bytes answer). Every call is recorded.
432
+ */
433
+ declare function createRecordableAssetSource(seeds?: readonly SeededFace[]): RecordableAssetSource;
434
+
435
+ /**
436
+ * Resolve a schema visibility / enablement gate against a published-
437
+ * bindings LOOKUP — the host-side evaluation (B-01: a lookup, NOT an
438
+ * expression language). Rules:
439
+ * · absent gate → `true` (always shown / enabled);
440
+ * · literal boolean → itself;
441
+ * · `{ bind }` → `Boolean(lookup(bind))`, a MISSING name reads
442
+ * `false` (a visible seam, never a throw);
443
+ * · `{ bind, negate: true }` → the inverse (the ONE transform — a
444
+ * NOT, not a DSL).
445
+ * Shared by the editor's `SchemaPanelRenderer` and the conformance
446
+ * tests so the two can't drift.
447
+ */
448
+ declare function resolveGate(gate: SchemaGate | undefined, lookup: (name: string) => unknown): boolean;
449
+ /** Build the registry `component` for a schema panel. */
450
+ declare function makeSchemaPanelComponent(contribution: SchemaPanelContribution, bindings: BindingsSurface, renderer: SchemaPanelRenderer | undefined): ComponentType<PanelProps>;
451
+
74
452
  interface LoadedBundle {
75
453
  readonly id: string;
76
454
  readonly active: boolean;
@@ -78,6 +456,81 @@ interface LoadedBundle {
78
456
  }
79
457
  declare function loadBundle(getEditor: () => PagedEditor, bundle: PagedBundle, options?: CreateBundleHostOptions): LoadedBundle;
80
458
 
459
+ /** WASM packaging budgets (v1). Rationale in docs/wasm-packaging.md.
460
+ * KEEP IN SYNC with plugin-cli's WASM_MAX_* and the schema's `maxBytes`
461
+ * maximum — the CLI hand-mirrors the contract. */
462
+ declare const WASM_BUDGETS: {
463
+ /** Hard per-artifact byte ceiling. A release-optimised wasm layout
464
+ * engine (Blitz-class) lands in the low-single-digit MiB; 8 MiB
465
+ * rejects an accidentally-bundled debug build while leaving headroom
466
+ * for one real engine. A manifest `maxBytes` may only TIGHTEN this. */
467
+ readonly maxArtifactBytes: number;
468
+ /** Total declared wasm across one bundle. Bounds a bundle that ships
469
+ * several modules (engine + a codec, say). */
470
+ readonly maxTotalBytes: number;
471
+ /** Wall-clock budget for fetch + compile + instantiate. Protects the
472
+ * editor's main flow from a pathological module; advisory, the loader
473
+ * aborts with a clear error when exceeded. */
474
+ readonly loadTimeBudgetMs: 3000;
475
+ /** Linear-memory growth ceiling, in 64 KiB wasm pages (4096 = 256 MiB).
476
+ * Passed as `WebAssembly.Memory({ maximum })` when the host owns the
477
+ * memory; a per-page layout pass should sit far under this. */
478
+ readonly maxMemoryPages: 4096;
479
+ };
480
+ /** A loaded bundle wasm module + the resources the host owns for it. */
481
+ interface LoadedBundleWasm {
482
+ /** The artifact descriptor it was loaded from. */
483
+ artifact: WasmArtifact;
484
+ module: WebAssembly.Module;
485
+ instance: WebAssembly.Instance;
486
+ /** The host-owned, non-shared memory the module was given (when the
487
+ * host provided one — see `provideMemory`). */
488
+ memory?: WebAssembly.Memory;
489
+ /** Compiled byte length (post-budget-check, for telemetry). */
490
+ byteLength: number;
491
+ }
492
+ /** How the loader reads a bundle-relative asset. The host supplies this
493
+ * (a URL fetch rooted at the bundle's asset base in the browser; a file
494
+ * read in Node/tests). The loader passes ONLY the declared `path`. */
495
+ type BundleAssetSource = (path: string) => Promise<Uint8Array> | Uint8Array;
496
+ interface LoadBundleWasmOptions {
497
+ /** Reads the declared artifact's bytes (required). */
498
+ assetSource: BundleAssetSource;
499
+ /**
500
+ * The host grant. A wasm artifact loads only if the host has granted
501
+ * it — `"*"` grants all declared artifacts; a `Set`/array grants by
502
+ * name. ABSENT means NO grant (refuse): wasm is opt-in, never ambient.
503
+ */
504
+ grant?: "*" | ReadonlyArray<string> | ReadonlySet<string>;
505
+ /**
506
+ * Import object handed to the module at instantiation. The loader adds
507
+ * NOTHING implicitly — if `provideMemory` is true and the imports omit
508
+ * `env.memory`, a host-owned bounded `WebAssembly.Memory` is injected;
509
+ * otherwise the module is on its own (no ambient engine/DOM/network).
510
+ */
511
+ imports?: WebAssembly.Imports;
512
+ /**
513
+ * When true (default), the loader owns linear memory: it creates a
514
+ * non-shared `WebAssembly.Memory` bounded by `maxMemoryPages` and
515
+ * injects it as `imports.env.memory` if not already present. Set false
516
+ * to let a module declare its own (still non-shared) memory.
517
+ */
518
+ provideMemory?: boolean;
519
+ /** Initial pages for the host-owned memory (default 16 = 1 MiB). */
520
+ initialMemoryPages?: number;
521
+ /** Override the load-time budget (ms). */
522
+ loadTimeBudgetMs?: number;
523
+ /** Clock injection for deterministic tests. */
524
+ now?: () => number;
525
+ }
526
+ /**
527
+ * Load a bundle-declared wasm artifact, enforcing declared-only access,
528
+ * the host grant, and the v1 budgets. Resolves to the instantiated
529
+ * module; rejects (loudly) on an undeclared name, a missing grant, an
530
+ * over-budget artifact, or a load-time overrun.
531
+ */
532
+ declare function loadBundleWasm(bundle: PagedBundle, name: string, options: LoadBundleWasmOptions): Promise<LoadedBundleWasm>;
533
+
81
534
  declare const CLICK_DRAG_THRESHOLD_PX = 4;
82
535
  /** A drag anchored to the page under the pointer at pointer-down. */
83
536
  interface PageDrag {
@@ -104,5 +557,24 @@ declare function commitAndSelect(paged: PagedEditor, mutation: Mutation, label:
104
557
  declare function contributeTool(host: BundleHost, tool: ToolContribution): Disposable;
105
558
 
106
559
  declare function contributePanel(host: BundleHost, panel: PanelContribution): Disposable;
560
+ /** Register a DECLARATIVE panel (W3.1, B-01): the host renders the
561
+ * schema from the catalog and subscribes to this bundle's published
562
+ * bindings (`host.bindings`) for visibility/enablement. No React
563
+ * crosses the boundary — the isolate-ready panel form. Same namespace
564
+ * + capability gate as `contributePanel` (`contributes.panels[]` must
565
+ * list the id). */
566
+ declare function contributeSchemaPanel(host: BundleHost, panel: SchemaPanelContribution): Disposable;
567
+
568
+ /** Register an EDIT CONTEXT (B-02): a content type that, on
569
+ * double-click (or programmatically), pushes a scoped context —
570
+ * restricted tools, emphasized panels, breadcrumb, narrowed
571
+ * write-scope, Esc pops. Capability-gated on `contributes.editContexts`.
572
+ */
573
+ declare function contributeEditContext(host: BundleHost, contribution: EditContextContribution): Disposable;
574
+ /** Register an OBJECT TYPE (W-03): a plugin-defined object (a webFrame
575
+ * is a rectangle with attached source metadata). A double-click on a
576
+ * matching element enters its `editContextType` instead of group
577
+ * descent. Capability-gated on `contributes.objectTypes`. */
578
+ declare function contributeObjectType(host: BundleHost, contribution: ObjectTypeContribution): Disposable;
107
579
 
108
- export { API_VERSION, type BundleHostHandle, CLICK_DRAG_THRESHOLD_PX, type CreateBundleHostOptions, DisposableStore, HOST_FEATURES, type HarnessOptions, type LoadedBundle, type PageDrag, PluginApiNotImplemented, type StorageBacking, beginPageDrag, commitAndSelect, contributePanel, contributeTool, createBundleHost, createHeadlessHost, defineBundle, endLocalFor, loadBundle, pxToPt, satisfiesApiVersion, toDisposable };
580
+ export { API_VERSION, ASSET_BUDGETS, BLOB_BUDGETS, type BlobStore, type BundleAssetProvider, type BundleAssetSource, type BundleHostHandle, type BundleTrust, CANVAS_WASM_PKG, CLICK_DRAG_THRESHOLD_PX, type ConsentBackend, type CreateBundleHostOptions, type DataProviderBackend, type DiagnosticsSink, DisposableStore, FALLBACK_WIDGETS, HOST_FEATURES, type HarnessOptions, type HeadlessCanvasWorker, type HeadlessHost, type HeadlessHostHandle, type LoadBundleWasmOptions, type LoadHeadlessEngineOptions, type LoadedBundle, type LoadedBundleWasm, type LoadedEngine, type PageDrag, PluginApiNotImplemented, PluginCapabilityError, type RecordableAssetSource, type RecordedContribution, type RecordedFontFaceRequest, type SeededFace, type StorageBacking, WASM_BUDGETS, beginPageDrag, commitAndSelect, contributeEditContext, contributeObjectType, contributePanel, contributeSchemaPanel, contributeTool, createBundleHost, createDataProviderRegistry, createHeadlessHost, createRecordableAssetSource, defineBundle, endLocalFor, loadBundle, loadBundleWasm, loadHeadlessEngine, makeSchemaPanelComponent, protocolFromVersion, pxToPt, readVendoredWireVersion, resolveCanvasWasm, resolveGate, satisfiesApiVersion, toDisposable };