@paged-media/plugin-sdk 0.2.6-canary.0 → 0.2.8-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 +480 -32
  2. package/dist/index.js +1294 -38
  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,168 @@ 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;
58
132
  interface CreateBundleHostOptions {
59
133
  storage?: StorageBacking;
134
+ /** Host-provided network consent (paged.data D-03; base-idea §11): the editor
135
+ * injects the consent prompt + the data-source-manifest UI. When absent,
136
+ * `host.network` denies every origin and `supports("network.consent@1")` is
137
+ * false (the honest no-consent posture). */
138
+ consent?: ConsentBackend;
139
+ /** Host-provided SHARED data-provider registry (paged.data §7.1 / D-09): the
140
+ * editor creates ONE (`createDataProviderRegistry`) and injects the SAME
141
+ * instance into every plugin host, so a provider and a consumer rendezvous
142
+ * through it. When absent, `host.dataProviders` discover() is empty +
143
+ * register() is a no-op and `supports("dataProviders@1")` is false. */
144
+ dataProviders?: DataProviderBackend;
60
145
  /** Console sink override (tests). */
61
146
  console?: Pick<Console, "debug" | "info" | "warn" | "error">;
62
147
  /** Shell actions the HOST APP owns (the cockpit's panel placement).
63
148
  * When absent, `host.shell` warns and no-ops, and
64
149
  * `supports("shell.openPanel@1")` answers false. */
65
150
  shell?: ShellSurface;
151
+ /** Host-provided panel widgets (W-04): the real code editor lives in
152
+ * the editor's UI package and is injected here. When absent,
153
+ * `host.widgets` is the plain-textarea fallback and
154
+ * `supports("widgets.codeEditor@1")` answers false. */
155
+ widgets?: WidgetSurface;
156
+ /** Host-provided SCHEMA-PANEL renderer (W3.1): the editor's
157
+ * `SchemaPanelRenderer` that walks a `PanelSchema` through the
158
+ * catalog + subscribes to the bundle's bindings. When absent,
159
+ * `contribute.schemaPanel` registers a visible "needs a host
160
+ * renderer" seam panel (never a throw, never fake UI) and
161
+ * `supports("contribute.schemaPanel@1")` still answers true (the
162
+ * door exists; only the rich rendering is host-injected). */
163
+ schemaPanelRenderer?: SchemaPanelRenderer;
164
+ /** Internal registration hook (the headless harness): called at the
165
+ * moment a schema panel registers, with the verbatim contribution,
166
+ * so the conformance log can record the SCHEMA assertably (the
167
+ * registry only sees the synthesized React `PanelContribution`). The
168
+ * returned disposer (if any) is tracked alongside the panel
169
+ * registration. Not part of the public contract — a host-adapter
170
+ * seam. */
171
+ onSchemaPanelRegistered?: (contribution: SchemaPanelContribution) => Disposable | void;
172
+ /** Internal registration hook (the headless harness, W3.2): called
173
+ * when an EDIT CONTEXT registers (after the namespace + capability
174
+ * gates pass), with the verbatim contribution, so the conformance log
175
+ * can record it. When the editor provides no `editContexts` registry,
176
+ * this is ALSO the recording stub's only consumer — the door no
177
+ * longer throws, it records. Not part of the public contract. */
178
+ onEditContextRegistered?: (contribution: EditContextContribution) => Disposable | void;
179
+ /** Internal registration hook (the headless harness, W3.2): the
180
+ * object-type analogue of `onEditContextRegistered`. */
181
+ onObjectTypeRegistered?: (contribution: ObjectTypeContribution) => Disposable | void;
182
+ /** Host-side problems-panel aggregator (W-05). When present,
183
+ * `supports("diagnostics.publish@1")` answers true and every
184
+ * `host.diagnostics.set/clear` fans out to it. */
185
+ diagnosticsSink?: DiagnosticsSink;
186
+ /** Host-provided ASSET byte source (W-06). When present,
187
+ * `host.assets.getFontFace` serves DOCUMENT font face bytes through
188
+ * it (capability-gated, budget-clamped) and
189
+ * `supports("assets.fonts@1")` answers true. When absent, every
190
+ * asset read answers `null` (the honest no-bytes door) and the
191
+ * feature flag is false. */
192
+ assetSource?: BundleAssetProvider;
193
+ /** Host-provided BLOB backend (K-4 / S-08). When present,
194
+ * `host.blob.*` persists per-plugin bytes through it (capability-
195
+ * gated, quota-clamped) and `supports("storage.blob@1")` answers true.
196
+ * When absent, reads answer empty and writes reject (the honest
197
+ * no-store door). */
198
+ blobStore?: BlobStore;
199
+ /**
200
+ * How the host treats a declaration↔use mismatch — a bundle that
201
+ * USES a door (`contribute.tool`, `document.mutate`, …) it did not
202
+ * DECLARE in its manifest (trust-line W0.11). Defaults to
203
+ * `'enforce'`: the violating call throws `PluginCapabilityError`
204
+ * (contribution registrations) or returns a non-applied
205
+ * `MutationOutcome` for the write doors (mutate-never-throws). In
206
+ * `'warn'` mode the violation is logged through `host.log.warn`
207
+ * and the call proceeds — the migration escape hatch for a host
208
+ * that loads not-yet-adopted manifests. The namespace gate and the
209
+ * metadata-namespace gate are UNAFFECTED — they are always loud.
210
+ */
211
+ capabilityMode?: "enforce" | "warn";
66
212
  }
67
213
  interface BundleHostHandle {
68
214
  host: BundleHost;
@@ -71,6 +217,214 @@ interface BundleHostHandle {
71
217
  }
72
218
  declare function createBundleHost(getEditor: () => PagedEditor, manifest: PluginManifest, options?: CreateBundleHostOptions): BundleHostHandle;
73
219
 
220
+ /** The published package the headless harness boots. */
221
+ declare const CANVAS_WASM_PKG = "@paged-media/canvas-wasm";
222
+ /**
223
+ * The wasm `CanvasWorker` surface the harness drives. A structural
224
+ * subset of the package's class — only the members the headless host
225
+ * needs. `handleMessage` is the JSON-envelope door; `loadDocumentDirect`
226
+ * is the binary side-channel for IDML bytes (the editor uses it to dodge
227
+ * the 8× `number[]` JSON inflation, and it is the only load path that
228
+ * does not require a `LoadDocument` envelope round-trip).
229
+ */
230
+ interface HeadlessCanvasWorker {
231
+ readonly protocolVersion: number;
232
+ handleMessage(input: string): string;
233
+ loadDocumentDirect(seq: number, bytes: Uint8Array, font?: Uint8Array, cmykIccProfile?: Uint8Array): string;
234
+ runResolveJson(): string | undefined;
235
+ free(): void;
236
+ }
237
+ interface LoadedEngine {
238
+ worker: HeadlessCanvasWorker;
239
+ /** The resolved package version (`0.<protocol>.<patch>`). */
240
+ version: string;
241
+ /** The wasm's reported protocol (the package minor). */
242
+ protocolVersion: number;
243
+ }
244
+ interface LoadHeadlessEngineOptions {
245
+ /**
246
+ * Where to resolve `@paged-media/canvas-wasm` from. Defaults to this
247
+ * module's own resolution (the SDK's node_modules), then the editor's
248
+ * `packages/client` (the sibling-checkout dev luxury) — mirroring
249
+ * scripts/sync-wire.mjs so the loader and the wire stamp agree on
250
+ * WHICH installed package they describe.
251
+ */
252
+ resolveFrom?: string;
253
+ /**
254
+ * Override the expected protocol the booted wasm must report. Defaults
255
+ * to the protocol derived from the vendored wire stamp. A mismatch
256
+ * throws — never silently downgrades.
257
+ */
258
+ expectedProtocol?: number;
259
+ }
260
+ /** Extract `@<version>` from the vendored wire stamp, or null. Async:
261
+ * the file read pulls `node:fs`, deferred so the browser barrel stays
262
+ * import-safe (only the Node headless path ever calls this). */
263
+ declare function readVendoredWireVersion(wireDtsPath?: string): Promise<string | null>;
264
+ /** The package minor IS the wire protocol (`0.<protocol>.<patch>`). */
265
+ declare function protocolFromVersion(version: string): number | null;
266
+ /**
267
+ * Resolve the published package's loader JS + `_bg.wasm` on disk.
268
+ * Throws (never warn-skips) when the package cannot be resolved: a
269
+ * headless host with no engine behind it is the exact fiction the
270
+ * harness exists to prevent (B-13).
271
+ */
272
+ declare function resolveCanvasWasm(resolveFrom?: string): Promise<{
273
+ loaderUrl: string;
274
+ wasmPath: string;
275
+ version: string;
276
+ dir: string;
277
+ }>;
278
+ /**
279
+ * Boot the published engine wasm in Node and return a `CanvasWorker`
280
+ * driving the editor's own dispatch. Asserts the booted protocol
281
+ * matches the vendored wire stamp.
282
+ */
283
+ declare function loadHeadlessEngine(options?: LoadHeadlessEngineOptions): Promise<LoadedEngine>;
284
+
285
+ /** One captured contribution — the assertable conformance log entry.
286
+ * `kind` is the surface; `value` is the contribution object verbatim
287
+ * (so a test can assert ids, titles, shortcuts, dock edges, …). */
288
+ interface RecordedContribution {
289
+ kind: "tool" | "panel" | "schemaPanel" | "command" | "keybinding" | "overlay" | "editContext" | "objectType" | "importer" | "exporter";
290
+ id: string;
291
+ value: ToolContribution | PanelContribution | SchemaPanelContribution | CommandContribution | KeybindingContribution | OverlayContribution | EditContextContribution | ObjectTypeContribution | ImporterContribution | ExporterContribution;
292
+ }
293
+ interface HarnessOptions extends LoadHeadlessEngineOptions, Pick<CreateBundleHostOptions, "console" | "storage" | "capabilityMode" | "assetSource" | "blobStore"> {
294
+ }
295
+ /** What `createHeadlessHost` resolves to: a real engine-backed host plus
296
+ * the conformance affordances (load an IDML, read the contribution log,
297
+ * load a bundle, dispose honestly). */
298
+ interface HeadlessHost {
299
+ /** The BundleHost a bundle's `activate(host)` receives. */
300
+ readonly host: BundleHost;
301
+ /** The booted package version (`0.<protocol>.<patch>`). */
302
+ readonly engineVersion: string;
303
+ /** The wasm's reported protocol (the package minor). */
304
+ readonly protocolVersion: number;
305
+ /** Every contribution registered through `host.contribute.*`, in
306
+ * registration order. Cleared structurally on dispose. */
307
+ readonly contributions: readonly RecordedContribution[];
308
+ /** Contributions of one surface (e.g. `tools()` for the rail). */
309
+ toolsContributed(): ToolContribution[];
310
+ panelsContributed(): PanelContribution[];
311
+ /** Declarative (schema) panels registered through
312
+ * `contribute.schemaPanel` — the W3.1 surface, recorded verbatim. */
313
+ schemaPanelsContributed(): SchemaPanelContribution[];
314
+ /** Edit contexts registered through `contribute.editContext` — the
315
+ * W3.2 surface (B-02), recorded verbatim (matcher fn included). */
316
+ editContextsContributed(): EditContextContribution[];
317
+ /** Object types registered through `contribute.objectType` — the W3.2
318
+ * surface (W-03), recorded verbatim. */
319
+ objectTypesContributed(): ObjectTypeContribution[];
320
+ /** Document importers registered through `contribute.importer` (K-2 /
321
+ * S-06), recorded verbatim (extensions + the `import()` callback). */
322
+ importersContributed(): ImporterContribution[];
323
+ /** Document exporters registered through `contribute.exporter` (K-2 /
324
+ * S-06), recorded verbatim. */
325
+ exportersContributed(): ExporterContribution[];
326
+ /** The last tool preview a bundle pushed through
327
+ * `host.overlay.setToolPreview` (B-07). There is no overlay SURFACE
328
+ * headlessly, but the channel is RECORDED so conformance can assert a
329
+ * pen/anchor handler emits the cubic `ToolPreviewPath` variant (true
330
+ * Béziers) rather than a flattened polyline. `null` until set, and
331
+ * reset to `null` when the bundle clears its preview. */
332
+ lastToolPreview(): ToolPreviewShape | null;
333
+ /** Load an IDML package into the headless document. Resolves to the
334
+ * loaded page ids (or throws on a parse failure). */
335
+ load(idml: Uint8Array): Promise<string[]>;
336
+ /** Activate a bundle against this host (apiVersion-negotiated). The
337
+ * returned disposer runs the bundle's teardown; the host's own
338
+ * facade teardown runs on `dispose()`. */
339
+ loadBundle(bundle: PagedBundle): Disposable;
340
+ /** Tear down: bundle teardown + facade teardown + free the wasm. The
341
+ * honesty contract — after dispose the contribution log is empty and
342
+ * the engine handle is released. */
343
+ dispose(): void;
344
+ }
345
+ /** Reserved alias kept for source/back-compat — see `createHeadlessHost`. */
346
+ type HeadlessHostHandle = HeadlessHost;
347
+ /**
348
+ * Build a headless, engine-backed `BundleHost`. Async because booting
349
+ * the wasm is async. The returned handle drives the conformance loop:
350
+ * load an IDML, activate a bundle, assert its contribution log, run real
351
+ * mutations, and dispose honestly.
352
+ *
353
+ * Resolves B-13 (RESOLVED + residuals — see module header). Replaces the
354
+ * v0 throw: the harness now stands on a real engine, so a bundle can no
355
+ * longer "pass against fiction".
356
+ */
357
+ declare function createHeadlessHost(options?: HarnessOptions): Promise<HeadlessHost>;
358
+
359
+ declare class DisposableStore implements Disposable {
360
+ private items;
361
+ private disposed;
362
+ get isDisposed(): boolean;
363
+ /** Track `d`; returns it for chaining. */
364
+ add<T extends Disposable>(d: T): T;
365
+ dispose(): void;
366
+ }
367
+ /** Wrap a plain cleanup function as a Disposable. */
368
+ declare function toDisposable(fn: () => void): Disposable;
369
+
370
+ /** The plugin API version this SDK implements. */
371
+ declare const API_VERSION = "0.2.0";
372
+ /**
373
+ * Does `version` satisfy `range`? Supported forms:
374
+ * `*` — anything
375
+ * `1.2.3` / `1.2`— exact (missing patch = 0)
376
+ * `^1.2.3` — npm caret: same major, >= base (major > 0);
377
+ * same major+minor, >= patch (major == 0 — the 0.x
378
+ * rule: minors are breaking during incubation)
379
+ */
380
+ declare function satisfiesApiVersion(range: string, version?: string): boolean;
381
+
382
+ /** The default widget catalog: a textarea CodeEditor. Replaced wholesale
383
+ * when the host app injects `widgets` into `createBundleHost`. */
384
+ declare const FALLBACK_WIDGETS: WidgetSurface;
385
+
386
+ /** One recorded `getFontFace` call. */
387
+ interface RecordedFontFaceRequest {
388
+ family: string;
389
+ style?: string;
390
+ }
391
+ /** A seeded face the fake serves. `family` match is case-insensitive
392
+ * (the document registry and CSS often differ only in casing). When
393
+ * `style` is given on a seed, the request must match it; a seed with no
394
+ * style answers any style request for that family. */
395
+ interface SeededFace extends FontFaceAsset {
396
+ /** When set, the seed only answers a request for this exact style. */
397
+ matchStyle?: string;
398
+ }
399
+ interface RecordableAssetSource extends BundleAssetProvider {
400
+ /** Every `getFontFace` call, in order. */
401
+ readonly requests: readonly RecordedFontFaceRequest[];
402
+ }
403
+ /**
404
+ * Build a recordable, in-memory asset source from a set of seeded
405
+ * faces. Families match case-insensitively; a seed carrying
406
+ * `matchStyle` only answers that style. Unknown families resolve to
407
+ * `null` (the honest no-bytes answer). Every call is recorded.
408
+ */
409
+ declare function createRecordableAssetSource(seeds?: readonly SeededFace[]): RecordableAssetSource;
410
+
411
+ /**
412
+ * Resolve a schema visibility / enablement gate against a published-
413
+ * bindings LOOKUP — the host-side evaluation (B-01: a lookup, NOT an
414
+ * expression language). Rules:
415
+ * · absent gate → `true` (always shown / enabled);
416
+ * · literal boolean → itself;
417
+ * · `{ bind }` → `Boolean(lookup(bind))`, a MISSING name reads
418
+ * `false` (a visible seam, never a throw);
419
+ * · `{ bind, negate: true }` → the inverse (the ONE transform — a
420
+ * NOT, not a DSL).
421
+ * Shared by the editor's `SchemaPanelRenderer` and the conformance
422
+ * tests so the two can't drift.
423
+ */
424
+ declare function resolveGate(gate: SchemaGate | undefined, lookup: (name: string) => unknown): boolean;
425
+ /** Build the registry `component` for a schema panel. */
426
+ declare function makeSchemaPanelComponent(contribution: SchemaPanelContribution, bindings: BindingsSurface, renderer: SchemaPanelRenderer | undefined): ComponentType<PanelProps>;
427
+
74
428
  interface LoadedBundle {
75
429
  readonly id: string;
76
430
  readonly active: boolean;
@@ -78,6 +432,81 @@ interface LoadedBundle {
78
432
  }
79
433
  declare function loadBundle(getEditor: () => PagedEditor, bundle: PagedBundle, options?: CreateBundleHostOptions): LoadedBundle;
80
434
 
435
+ /** WASM packaging budgets (v1). Rationale in docs/wasm-packaging.md.
436
+ * KEEP IN SYNC with plugin-cli's WASM_MAX_* and the schema's `maxBytes`
437
+ * maximum — the CLI hand-mirrors the contract. */
438
+ declare const WASM_BUDGETS: {
439
+ /** Hard per-artifact byte ceiling. A release-optimised wasm layout
440
+ * engine (Blitz-class) lands in the low-single-digit MiB; 8 MiB
441
+ * rejects an accidentally-bundled debug build while leaving headroom
442
+ * for one real engine. A manifest `maxBytes` may only TIGHTEN this. */
443
+ readonly maxArtifactBytes: number;
444
+ /** Total declared wasm across one bundle. Bounds a bundle that ships
445
+ * several modules (engine + a codec, say). */
446
+ readonly maxTotalBytes: number;
447
+ /** Wall-clock budget for fetch + compile + instantiate. Protects the
448
+ * editor's main flow from a pathological module; advisory, the loader
449
+ * aborts with a clear error when exceeded. */
450
+ readonly loadTimeBudgetMs: 3000;
451
+ /** Linear-memory growth ceiling, in 64 KiB wasm pages (4096 = 256 MiB).
452
+ * Passed as `WebAssembly.Memory({ maximum })` when the host owns the
453
+ * memory; a per-page layout pass should sit far under this. */
454
+ readonly maxMemoryPages: 4096;
455
+ };
456
+ /** A loaded bundle wasm module + the resources the host owns for it. */
457
+ interface LoadedBundleWasm {
458
+ /** The artifact descriptor it was loaded from. */
459
+ artifact: WasmArtifact;
460
+ module: WebAssembly.Module;
461
+ instance: WebAssembly.Instance;
462
+ /** The host-owned, non-shared memory the module was given (when the
463
+ * host provided one — see `provideMemory`). */
464
+ memory?: WebAssembly.Memory;
465
+ /** Compiled byte length (post-budget-check, for telemetry). */
466
+ byteLength: number;
467
+ }
468
+ /** How the loader reads a bundle-relative asset. The host supplies this
469
+ * (a URL fetch rooted at the bundle's asset base in the browser; a file
470
+ * read in Node/tests). The loader passes ONLY the declared `path`. */
471
+ type BundleAssetSource = (path: string) => Promise<Uint8Array> | Uint8Array;
472
+ interface LoadBundleWasmOptions {
473
+ /** Reads the declared artifact's bytes (required). */
474
+ assetSource: BundleAssetSource;
475
+ /**
476
+ * The host grant. A wasm artifact loads only if the host has granted
477
+ * it — `"*"` grants all declared artifacts; a `Set`/array grants by
478
+ * name. ABSENT means NO grant (refuse): wasm is opt-in, never ambient.
479
+ */
480
+ grant?: "*" | ReadonlyArray<string> | ReadonlySet<string>;
481
+ /**
482
+ * Import object handed to the module at instantiation. The loader adds
483
+ * NOTHING implicitly — if `provideMemory` is true and the imports omit
484
+ * `env.memory`, a host-owned bounded `WebAssembly.Memory` is injected;
485
+ * otherwise the module is on its own (no ambient engine/DOM/network).
486
+ */
487
+ imports?: WebAssembly.Imports;
488
+ /**
489
+ * When true (default), the loader owns linear memory: it creates a
490
+ * non-shared `WebAssembly.Memory` bounded by `maxMemoryPages` and
491
+ * injects it as `imports.env.memory` if not already present. Set false
492
+ * to let a module declare its own (still non-shared) memory.
493
+ */
494
+ provideMemory?: boolean;
495
+ /** Initial pages for the host-owned memory (default 16 = 1 MiB). */
496
+ initialMemoryPages?: number;
497
+ /** Override the load-time budget (ms). */
498
+ loadTimeBudgetMs?: number;
499
+ /** Clock injection for deterministic tests. */
500
+ now?: () => number;
501
+ }
502
+ /**
503
+ * Load a bundle-declared wasm artifact, enforcing declared-only access,
504
+ * the host grant, and the v1 budgets. Resolves to the instantiated
505
+ * module; rejects (loudly) on an undeclared name, a missing grant, an
506
+ * over-budget artifact, or a load-time overrun.
507
+ */
508
+ declare function loadBundleWasm(bundle: PagedBundle, name: string, options: LoadBundleWasmOptions): Promise<LoadedBundleWasm>;
509
+
81
510
  declare const CLICK_DRAG_THRESHOLD_PX = 4;
82
511
  /** A drag anchored to the page under the pointer at pointer-down. */
83
512
  interface PageDrag {
@@ -104,5 +533,24 @@ declare function commitAndSelect(paged: PagedEditor, mutation: Mutation, label:
104
533
  declare function contributeTool(host: BundleHost, tool: ToolContribution): Disposable;
105
534
 
106
535
  declare function contributePanel(host: BundleHost, panel: PanelContribution): Disposable;
536
+ /** Register a DECLARATIVE panel (W3.1, B-01): the host renders the
537
+ * schema from the catalog and subscribes to this bundle's published
538
+ * bindings (`host.bindings`) for visibility/enablement. No React
539
+ * crosses the boundary — the isolate-ready panel form. Same namespace
540
+ * + capability gate as `contributePanel` (`contributes.panels[]` must
541
+ * list the id). */
542
+ declare function contributeSchemaPanel(host: BundleHost, panel: SchemaPanelContribution): Disposable;
543
+
544
+ /** Register an EDIT CONTEXT (B-02): a content type that, on
545
+ * double-click (or programmatically), pushes a scoped context —
546
+ * restricted tools, emphasized panels, breadcrumb, narrowed
547
+ * write-scope, Esc pops. Capability-gated on `contributes.editContexts`.
548
+ */
549
+ declare function contributeEditContext(host: BundleHost, contribution: EditContextContribution): Disposable;
550
+ /** Register an OBJECT TYPE (W-03): a plugin-defined object (a webFrame
551
+ * is a rectangle with attached source metadata). A double-click on a
552
+ * matching element enters its `editContextType` instead of group
553
+ * descent. Capability-gated on `contributes.objectTypes`. */
554
+ declare function contributeObjectType(host: BundleHost, contribution: ObjectTypeContribution): Disposable;
107
555
 
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 };
556
+ export { API_VERSION, ASSET_BUDGETS, BLOB_BUDGETS, type BlobStore, type BundleAssetProvider, type BundleAssetSource, type BundleHostHandle, 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 };