@paged-media/plugin-sdk 0.2.39-canary.0 → 0.2.41-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.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { PagedBundle, BindingProvider, Disposable, ActiveBindingProvider, BindingPropertyRequest, BindingReadResult, BindingPropertyWrite, BindingWriteResult, BindingCollectionRequest, BindingCollectionResult, MutationInput, FontFaceAsset, BundleHost, ClipboardPayload, ConsentResult, DataProviderRegistration, DataProviderHandle, DataProviderInfo, DataProviderSnapshot, JournalRecord, ShellSurface, WidgetSurface, WillSaveEvent, ToolSettingValue, SchemaPanelRenderer, SchemaPanelContribution, EditContextContribution, ObjectTypeContribution, Diagnostic, TextCaret, PagedEditor, PluginManifest, ToolContribution, PanelContribution, CommandContribution, KeybindingContribution, OverlayContribution, ImporterContribution, ExporterContribution, ToolPreviewShape, BindingsSurface, PanelProps, SchemaGate, WasmArtifact, CanvasPointerEvent, Mutation, MutationOutcome, BindingProviderHandle } from '@paged-media/plugin-api';
1
+ import { PagedBundle, BindingProvider, Disposable, ActiveBindingProvider, BindingPropertyRequest, BindingReadResult, BindingPropertyWrite, BindingWriteResult, BindingCollectionRequest, BindingCollectionResult, MutationInput, FontFaceAsset, BundleHost, ClipboardPayload, ConsentResult, DataProviderRegistration, DataProviderHandle, DataProviderInfo, DataProviderSnapshot, JournalRecord, ShellSurface, WidgetSurface, WillSaveEvent, ToolSettingValue, SchemaPanelRenderer, SchemaPanelContribution, EditContextContribution, ObjectTypeContribution, Diagnostic, TextCaret, OpenDocumentResult, PagedEditor, PluginManifest, ToolContribution, PanelContribution, CommandContribution, KeybindingContribution, OverlayContribution, ImporterContribution, ExporterContribution, ToolPreviewShape, BindingsSurface, PanelProps, SchemaGate, WasmArtifact, CanvasPointerEvent, Mutation, MutationOutcome, BindingProviderHandle } from '@paged-media/plugin-api';
2
2
  import { ComponentType } from 'react';
3
3
 
4
4
  /**
@@ -240,6 +240,27 @@ interface NativeDocumentBackend {
240
240
  listParts(prefix?: string): Promise<string[]>;
241
241
  open(bytes: Uint8Array): Promise<void>;
242
242
  }
243
+ /**
244
+ * D-26 — the backend the editor injects to back `host.documents`. The SDK
245
+ * owns the capability gate (`capabilities.documents.export` / `.open`); the
246
+ * backend owns the IO AND the unsaved-edits decision: `open` must ask the
247
+ * user (host UI) before discarding a dirty active document and answer
248
+ * `{ opened: false, reason: "declined" }` when they keep it. `requester`
249
+ * is filled by the SDK from the calling bundle's manifest (not from the
250
+ * plugin's arguments), so the prompt can name who is asking. No backend →
251
+ * both doors reject and `supports("documents.open@1")` /
252
+ * `supports("documents.exportPaged@1")` are false.
253
+ */
254
+ interface DocumentsBackend {
255
+ exportPaged(): Promise<Uint8Array>;
256
+ open(bytes: Uint8Array, request: {
257
+ name: string | null;
258
+ requester: {
259
+ id: string;
260
+ name: string;
261
+ };
262
+ }): Promise<OpenDocumentResult>;
263
+ }
243
264
  /** The SHARED cross-plugin data-provider registry (paged.data §7.1 / D-09). The
244
265
  * editor creates ONE (`createDataProviderRegistry`) and injects the SAME
245
266
  * instance into every plugin host, so a provider plugin and a consumer plugin
@@ -469,6 +490,12 @@ interface CreateBundleHostOptions {
469
490
  * `supports("document.openNative@1")` answer true. When absent, the reads
470
491
  * answer `null`/`[]` and `open` rejects (the honest no-backend door). */
471
492
  nativeDocument?: NativeDocumentBackend;
493
+ /** D-26 — host-provided DOCUMENTS backend. When present,
494
+ * `host.documents.exportPaged` / `open` go through it (gated on
495
+ * `capabilities.documents.export` / `.open`) and
496
+ * `supports("documents.exportPaged@1")` / `supports("documents.open@1")`
497
+ * answer true. When absent, both doors reject. */
498
+ documents?: DocumentsBackend;
472
499
  /** Host-provided WORKER backend (K-3 / S-07 / I-02). When present,
473
500
  * `host.workers.spawn` resolves a declared bundle-relative module +
474
501
  * constructs a host-owned `Worker` through it (capability-gated,
@@ -521,6 +548,14 @@ interface HeadlessCanvasWorker {
521
548
  handleMessage(input: string): string;
522
549
  loadDocumentDirect(seq: number, bytes: Uint8Array, font?: Uint8Array, cmykIccProfile?: Uint8Array): string;
523
550
  runResolveJson(): string | undefined;
551
+ /** The engine shaper (v38): `{ advance, ascender, descender }` in pt, or
552
+ * null when no document is loaded / the face resolves to nothing.
553
+ * Optional so a structural fake need not carry it. */
554
+ measureText?(family: string, style: string | null | undefined, text: string, sizePt: number): {
555
+ advance: number;
556
+ ascender: number;
557
+ descender: number;
558
+ } | null;
524
559
  free(): void;
525
560
  }
526
561
  interface LoadedEngine {
@@ -580,6 +615,32 @@ interface RecordedContribution {
580
615
  value: ToolContribution | PanelContribution | SchemaPanelContribution | CommandContribution | KeybindingContribution | OverlayContribution | EditContextContribution | ObjectTypeContribution | ImporterContribution | ExporterContribution;
581
616
  }
582
617
  interface HarnessOptions extends LoadHeadlessEngineOptions, Pick<CreateBundleHostOptions, "console" | "storage" | "capabilityMode" | "assetSource" | "blobStore" | "clipboard" | "secrets" | "bindingProviders" | "willSave" | "toolSettings"> {
618
+ /** D-27 — back `host.text` with the ENGINE's shaper
619
+ * (`CanvasWorker.measureText`, the one the editor's worker calls)
620
+ * instead of the estimate, so `measureString` / `measureStrings`
621
+ * answer real widths against the loaded document's fonts and
622
+ * `supports("text.measure@1")` / `("text.measureStrings@1")` are true.
623
+ * Off by default: existing headless suites pinned the estimate. */
624
+ engineShaper?: boolean;
625
+ /** D-26 — the headless stand-in for the editor's keep/discard prompt.
626
+ * `host.documents.open` calls it ONLY when the active document has
627
+ * unsaved edits; resolve `false` to keep them (the door then answers
628
+ * `{ opened: false, reason: "declined" }`). Default: discard. */
629
+ confirmReplace?: (request: {
630
+ name: string | null;
631
+ requester: {
632
+ id: string;
633
+ name: string;
634
+ };
635
+ }) => boolean | Promise<boolean>;
636
+ /** D-28 — the default font every load lays out with, the headless
637
+ * stand-in for the editor's `defaultFontProvider` (the editor passes
638
+ * `/fonts/Inter.ttf`). The engine lays text out, and reports a story
639
+ * `overset`, only when it has a font: without one nothing is laid out and
640
+ * every story reads `overset: false` (not measured). Passed to every
641
+ * load, `load()` and `host.documents.open` alike. Absent: no font, as
642
+ * before. */
643
+ defaultFont?: Uint8Array;
583
644
  }
584
645
  /** A headless will-save registry: listeners register per plugin and
585
646
  * `fire()` awaits them all, the way the editor's save path does — so a
@@ -656,6 +717,13 @@ interface HeadlessHost {
656
717
  /** Load an IDML package into the headless document. Resolves to the
657
718
  * loaded page ids (or throws on a parse failure). */
658
719
  load(idml: Uint8Array): Promise<string[]>;
720
+ /** D-26 — every document a bundle opened through `host.documents.open`,
721
+ * in order (declined opens are not listed). */
722
+ openedDocuments(): ReadonlyArray<{
723
+ name: string | null;
724
+ requester: string;
725
+ pageIds: string[];
726
+ }>;
659
727
  /** Activate a bundle against this host (apiVersion-negotiated). The
660
728
  * returned disposer runs the bundle's teardown; the host's own
661
729
  * facade teardown runs on `dispose()`. */
@@ -921,4 +989,4 @@ declare function contributeObjectType(host: BundleHost, contribution: ObjectType
921
989
  */
922
990
  declare function contributeBindingProvider(host: BundleHost, contextType: string, provider: BindingProvider): BindingProviderHandle;
923
991
 
924
- export { API_VERSION, ASSET_BUDGETS, BLOB_BUDGETS, type BindingProviderBackend, type BlobStore, type BundleAssetProvider, type BundleAssetSource, type BundleHostHandle, type BundleTrust, CANVAS_WASM_PKG, CLICK_DRAG_THRESHOLD_PX, type ClipboardBackend, type ConsentBackend, type CreateBundleHostOptions, type DataProviderBackend, type DiagnosticsSink, DisposableStore, FALLBACK_WIDGETS, HOST_FEATURES, type HarnessOptions, type HeadlessCanvasWorker, type HeadlessHost, type HeadlessHostHandle, type JournalSink, type LoadBundleWasmOptions, type LoadHeadlessEngineOptions, type LoadedBundle, type LoadedBundleWasm, type LoadedEngine, type NativeDocumentBackend, type PageDrag, PluginApiNotImplemented, PluginCapabilityError, type RecordableAssetSource, type RecordedContribution, type RecordedFontFaceRequest, type RegisteredBindingProvider, type SecretStoreBackend, type SeededFace, type ShellBackend, type SpawnedWorker, type StorageBacking, type TextCaretBackend, type ToolSettingsBackend, WASM_BUDGETS, WORKER_BUDGETS, type WillSaveBackend, type WorkerBackend, beginPageDrag, commitAndSelect, contributeBindingProvider, contributeEditContext, contributeObjectType, contributePanel, contributeSchemaPanel, contributeTool, createBindingProviderRegistry, createBundleHost, createDataProviderRegistry, createHeadlessHost, createRecordableAssetSource, defineBundle, endLocalFor, inMemoryToolSettings, inMemoryWillSave, loadBundle, loadBundleWasm, loadHeadlessEngine, makeSchemaPanelComponent, protocolFromVersion, pxToPt, readVendoredWireVersion, resolveCanvasWasm, resolveGate, satisfiesApiVersion, toDisposable };
992
+ export { API_VERSION, ASSET_BUDGETS, BLOB_BUDGETS, type BindingProviderBackend, type BlobStore, type BundleAssetProvider, type BundleAssetSource, type BundleHostHandle, type BundleTrust, CANVAS_WASM_PKG, CLICK_DRAG_THRESHOLD_PX, type ClipboardBackend, type ConsentBackend, type CreateBundleHostOptions, type DataProviderBackend, type DiagnosticsSink, DisposableStore, type DocumentsBackend, FALLBACK_WIDGETS, HOST_FEATURES, type HarnessOptions, type HeadlessCanvasWorker, type HeadlessHost, type HeadlessHostHandle, type JournalSink, type LoadBundleWasmOptions, type LoadHeadlessEngineOptions, type LoadedBundle, type LoadedBundleWasm, type LoadedEngine, type NativeDocumentBackend, type PageDrag, PluginApiNotImplemented, PluginCapabilityError, type RecordableAssetSource, type RecordedContribution, type RecordedFontFaceRequest, type RegisteredBindingProvider, type SecretStoreBackend, type SeededFace, type ShellBackend, type SpawnedWorker, type StorageBacking, type TextCaretBackend, type ToolSettingsBackend, WASM_BUDGETS, WORKER_BUDGETS, type WillSaveBackend, type WorkerBackend, beginPageDrag, commitAndSelect, contributeBindingProvider, contributeEditContext, contributeObjectType, contributePanel, contributeSchemaPanel, contributeTool, createBindingProviderRegistry, createBundleHost, createDataProviderRegistry, createHeadlessHost, createRecordableAssetSource, defineBundle, endLocalFor, inMemoryToolSettings, inMemoryWillSave, loadBundle, loadBundleWasm, loadHeadlessEngine, makeSchemaPanelComponent, protocolFromVersion, pxToPt, readVendoredWireVersion, resolveCanvasWasm, resolveGate, satisfiesApiVersion, toDisposable };
package/dist/index.js CHANGED
@@ -230,6 +230,11 @@ var HOST_FEATURES = [
230
230
  "document.onDidChange@1",
231
231
  "document.getMetadata@1",
232
232
  "document.setMetadata@1",
233
+ // Protocol 69 — the document-scoped label. Static like the snap door:
234
+ // the flag says this SDK forwards the two calls; an engine older than 69
235
+ // answers `null` on read (no `pluginMetadata` in its DocumentMeta) and
236
+ // `applied: false` on write (it does not know the op).
237
+ "document.documentMetadata@1",
233
238
  "selection@1",
234
239
  "viewport@1",
235
240
  "overlay.toolPreview@1",
@@ -628,6 +633,7 @@ function createBundleHost(getEditor, manifest, options) {
628
633
  const hasWorkers = () => typeof caps?.workers?.max === "number";
629
634
  const hasSecrets = () => caps?.secrets?.sources === true;
630
635
  const hasGpu = () => caps?.gpu?.realm === "bundle";
636
+ const hasDocuments = (dir) => caps?.documents?.[dir] === true;
631
637
  const lists = (arr, id) => arr?.includes(id) ?? false;
632
638
  const declaresType = (arr, type) => arr?.some((e) => e.type === type) ?? false;
633
639
  const requireDeclared = (ok, door, missing) => {
@@ -1006,10 +1012,12 @@ function createBundleHost(getEditor, manifest, options) {
1006
1012
  log.warn(
1007
1013
  `contribute.sceneLayer().submit("${elementId}") ignored \u2014 the host wired no scene channel (probe supports("rendering.sceneLayer@1"))`
1008
1014
  );
1009
- return;
1015
+ return { fontFallbacks: [] };
1010
1016
  }
1011
1017
  submitted.add(elementId);
1012
- await ch.submit(elementId, layer, manifest.id);
1018
+ const reply = await ch.submit(elementId, layer, manifest.id);
1019
+ const fontFallbacks = reply && Array.isArray(reply.fontFallbacks) ? [...reply.fontFallbacks] : [];
1020
+ return { fontFallbacks };
1013
1021
  },
1014
1022
  async clear(elementId) {
1015
1023
  submitted.delete(elementId);
@@ -1096,7 +1104,7 @@ function createBundleHost(getEditor, manifest, options) {
1096
1104
  }
1097
1105
  };
1098
1106
  const foreignMetadataKey = (m) => {
1099
- if (m.op === "setPluginMetadata") {
1107
+ if (m.op === "setPluginMetadata" || m.op === "setDocumentMetadata") {
1100
1108
  return m.args.key === metadataKey(manifest) ? null : m.args.key;
1101
1109
  }
1102
1110
  if (m.op === "batch") {
@@ -1145,7 +1153,7 @@ function createBundleHost(getEditor, manifest, options) {
1145
1153
  if (denied !== null) return { applied: false, error: denied };
1146
1154
  const foreign = foreignMetadataKey(mutation);
1147
1155
  if (foreign !== null) {
1148
- const error = `setPluginMetadata key "${foreign}" is outside this plugin's namespace ("${metadataKey(manifest)}")`;
1156
+ const error = `metadata key "${foreign}" is outside this plugin's namespace ("${metadataKey(manifest)}")`;
1149
1157
  log.warn(error);
1150
1158
  return { applied: false, error };
1151
1159
  }
@@ -1382,6 +1390,28 @@ function createBundleHost(getEditor, manifest, options) {
1382
1390
  }
1383
1391
  });
1384
1392
  },
1393
+ async getDocumentMetadata() {
1394
+ requireDocRead("document.getDocumentMetadata");
1395
+ const key = metadataKey(manifest);
1396
+ const meta = await getEditor().client.documentMeta();
1397
+ const entry = meta.pluginMetadata?.find((e) => e.key === key);
1398
+ if (!entry) return null;
1399
+ try {
1400
+ return JSON.parse(entry.value);
1401
+ } catch {
1402
+ return null;
1403
+ }
1404
+ },
1405
+ async setDocumentMetadata(envelope) {
1406
+ return this.mutate({
1407
+ op: "setDocumentMetadata",
1408
+ args: {
1409
+ key: metadataKey(manifest),
1410
+ value: envelope === null ? null : JSON.stringify(envelope),
1411
+ caller: manifest.id
1412
+ }
1413
+ });
1414
+ },
1385
1415
  async frameChain(storyId) {
1386
1416
  requireDocRead("document.frameChain");
1387
1417
  const reply = await getEditor().client.send({
@@ -1485,6 +1515,25 @@ function createBundleHost(getEditor, manifest, options) {
1485
1515
  // "no reader").
1486
1516
  caret() {
1487
1517
  return options?.textCaret?.read() ?? null;
1518
+ },
1519
+ // D-27 — the batched measure. One round-trip when the editor wires
1520
+ // `measureMany`; otherwise the same answers one call at a time (or
1521
+ // the estimate), so a bundle never needs two code paths.
1522
+ async measureStrings(family, style, texts, sizePt) {
1523
+ if (texts.length === 0) return [];
1524
+ const editorText = getEditor().text;
1525
+ if (editorText?.measureMany) {
1526
+ const out = await editorText.measureMany(family, style, texts, sizePt);
1527
+ if (out.length !== texts.length) {
1528
+ throw new Error(
1529
+ `host.text.measureStrings: the host answered ${out.length} metrics for ${texts.length} strings`
1530
+ );
1531
+ }
1532
+ return out;
1533
+ }
1534
+ return Promise.all(
1535
+ texts.map((t) => text.measureString(family, style, t, sizePt))
1536
+ );
1488
1537
  }
1489
1538
  };
1490
1539
  const overlay = {
@@ -2056,6 +2105,42 @@ function createBundleHost(getEditor, manifest, options) {
2056
2105
  return nativeDocBackend.open(bytes);
2057
2106
  }
2058
2107
  };
2108
+ const documentsBackend = options?.documents;
2109
+ const documents = {
2110
+ async exportPaged() {
2111
+ requireDeclared(
2112
+ hasDocuments("export"),
2113
+ "documents.exportPaged",
2114
+ "capabilities.documents.export must be declared"
2115
+ );
2116
+ if (!documentsBackend) {
2117
+ throw new Error(
2118
+ 'host.documents.exportPaged: the host wired no documents backend (supports("documents.exportPaged@1") is false)'
2119
+ );
2120
+ }
2121
+ return documentsBackend.exportPaged();
2122
+ },
2123
+ async open(bytes, opts) {
2124
+ requireDeclared(
2125
+ hasDocuments("open"),
2126
+ "documents.open",
2127
+ "capabilities.documents.open must be declared"
2128
+ );
2129
+ if (!documentsBackend) {
2130
+ throw new Error(
2131
+ 'host.documents.open: the host wired no documents backend (supports("documents.open@1") is false)'
2132
+ );
2133
+ }
2134
+ if (!(bytes instanceof Uint8Array) || bytes.byteLength === 0) {
2135
+ throw new Error("host.documents.open: bytes must be a non-empty Uint8Array");
2136
+ }
2137
+ const name = typeof opts?.name === "string" && opts.name.trim() !== "" ? opts.name.trim() : null;
2138
+ return documentsBackend.open(bytes.slice(), {
2139
+ name,
2140
+ requester: { id: manifest.id, name: manifest.name }
2141
+ });
2142
+ }
2143
+ };
2059
2144
  const clipboardBackend = options?.clipboard;
2060
2145
  const clipboardGate = (door) => {
2061
2146
  const grant = clipboardGrant();
@@ -2261,8 +2346,12 @@ function createBundleHost(getEditor, manifest, options) {
2261
2346
  if (getEditor().text) {
2262
2347
  featureSet.add("text.measure@1");
2263
2348
  }
2349
+ if (getEditor().text?.measureMany) {
2350
+ featureSet.add("text.measureStrings@1");
2351
+ }
2264
2352
  if (getEditor().sceneLayers) {
2265
2353
  featureSet.add("rendering.sceneLayer@1");
2354
+ featureSet.add("rendering.sceneLayer.faces@1");
2266
2355
  }
2267
2356
  if (typeof getEditor().overlaySignals?.setToolPreviews === "function") {
2268
2357
  featureSet.add("overlay.multiPreview@1");
@@ -2332,6 +2421,10 @@ function createBundleHost(getEditor, manifest, options) {
2332
2421
  featureSet.add("document.readNative@1");
2333
2422
  featureSet.add("document.openNative@1");
2334
2423
  }
2424
+ if (options?.documents) {
2425
+ featureSet.add("documents.exportPaged@1");
2426
+ featureSet.add("documents.open@1");
2427
+ }
2335
2428
  if (options?.workers) {
2336
2429
  featureSet.add("workers@1");
2337
2430
  }
@@ -2355,6 +2448,7 @@ function createBundleHost(getEditor, manifest, options) {
2355
2448
  blob,
2356
2449
  parts,
2357
2450
  nativeDocument,
2451
+ documents,
2358
2452
  network,
2359
2453
  dataProviders,
2360
2454
  diagnostics,
@@ -2609,7 +2703,7 @@ function inMemorySecretStore() {
2609
2703
  };
2610
2704
  }
2611
2705
  var seqCounter = 1;
2612
- function makeEngineEditor(worker, recorder, onToolPreview, onToolPreviews) {
2706
+ function makeEngineEditor(worker, recorder, onToolPreview, onToolPreviews, engineShaper = false) {
2613
2707
  const protocol = worker.protocolVersion;
2614
2708
  const listeners = /* @__PURE__ */ new Set();
2615
2709
  const fanOut = (reply) => {
@@ -2769,7 +2863,23 @@ function makeEngineEditor(worker, recorder, onToolPreview, onToolPreviews) {
2769
2863
  },
2770
2864
  contentSelection: { contentSelection: null }
2771
2865
  };
2772
- return editor;
2866
+ const measureText = worker.measureText?.bind(worker);
2867
+ if (engineShaper && measureText) {
2868
+ const zero = { advance: 0, ascender: 0, descender: 0 };
2869
+ const one = (family, style, t, sizePt) => {
2870
+ const m = measureText(family, style, t, sizePt);
2871
+ return m ? { advance: m.advance, ascender: m.ascender, descender: m.descender } : zero;
2872
+ };
2873
+ editor.text = {
2874
+ async measure(family, style, t, sizePt) {
2875
+ return one(family, style, t, sizePt);
2876
+ },
2877
+ async measureMany(family, style, texts, sizePt) {
2878
+ return texts.map((t) => one(family, style, t, sizePt));
2879
+ }
2880
+ };
2881
+ }
2882
+ return { editor, fanOut };
2773
2883
  }
2774
2884
  async function createHeadlessHost(options = {}) {
2775
2885
  const engine = await loadHeadlessEngine(options);
@@ -2777,7 +2887,7 @@ async function createHeadlessHost(options = {}) {
2777
2887
  const contributions = [];
2778
2888
  let lastPreview = null;
2779
2889
  let lastPreviews = null;
2780
- const editor = makeEngineEditor(
2890
+ const { editor, fanOut } = makeEngineEditor(
2781
2891
  worker,
2782
2892
  contributions,
2783
2893
  (value) => {
@@ -2785,8 +2895,54 @@ async function createHeadlessHost(options = {}) {
2785
2895
  },
2786
2896
  (value) => {
2787
2897
  lastPreviews = value;
2788
- }
2898
+ },
2899
+ options.engineShaper === true
2789
2900
  );
2901
+ const loadDirect = (bytes) => {
2902
+ const reply = JSON.parse(
2903
+ options.defaultFont ? worker.loadDocumentDirect(seqCounter++, bytes, options.defaultFont) : worker.loadDocumentDirect(seqCounter++, bytes)
2904
+ );
2905
+ if (reply.kind === "documentLoaded") {
2906
+ try {
2907
+ worker.runResolveJson();
2908
+ } catch {
2909
+ }
2910
+ }
2911
+ return reply;
2912
+ };
2913
+ const loadFailure = (reply) => {
2914
+ const errKind = reply.kind === "loadFailed" ? reply.payload.error.kind : reply.kind;
2915
+ return new Error(`headless load failed (${reply.kind}: ${errKind})`);
2916
+ };
2917
+ const opened = [];
2918
+ const documentsBackend = {
2919
+ async exportPaged() {
2920
+ const reply = await editor.client.send({ kind: "exportPaged", payload: {} });
2921
+ if (reply.kind === "pagedExported") return Uint8Array.from(reply.payload.bytes);
2922
+ if (reply.kind === "pagedPartFailed") throw new Error(reply.payload.error);
2923
+ throw new Error(`unexpected reply: ${reply.kind}`);
2924
+ },
2925
+ async open(bytes, request) {
2926
+ let dirty = false;
2927
+ try {
2928
+ dirty = (await editor.client.documentMeta()).dirty;
2929
+ } catch {
2930
+ dirty = false;
2931
+ }
2932
+ if (dirty && options.confirmReplace && !await options.confirmReplace(request)) {
2933
+ return { opened: false, reason: "declined" };
2934
+ }
2935
+ const reply = loadDirect(bytes);
2936
+ if (reply.kind !== "documentLoaded") throw loadFailure(reply);
2937
+ fanOut(reply);
2938
+ opened.push({
2939
+ name: request.name,
2940
+ requester: request.requester.id,
2941
+ pageIds: reply.payload.pageIds
2942
+ });
2943
+ return { opened: true, pageIds: reply.payload.pageIds };
2944
+ }
2945
+ };
2790
2946
  let active = null;
2791
2947
  let currentHost = null;
2792
2948
  let disposed = false;
@@ -2811,6 +2967,7 @@ async function createHeadlessHost(options = {}) {
2811
2967
  // headlessly (the editor's real adapter currently serves null;
2812
2968
  // DESIGN.md §13.4). Absent → the no-bytes door.
2813
2969
  assetSource: options.assetSource,
2970
+ documents: documentsBackend,
2814
2971
  // Record the SCHEMA verbatim at registration — the panel registry
2815
2972
  // only ever sees the synthesized React panel, so the conformance
2816
2973
  // log gets the schema through this adapter seam (no host renderer
@@ -2872,7 +3029,8 @@ async function createHeadlessHost(options = {}) {
2872
3029
  rendering: ["overlay", "hitTest", "sceneLayer"],
2873
3030
  keybindings: true,
2874
3031
  storage: { blob: true },
2875
- secrets: { sources: true }
3032
+ secrets: { sources: true },
3033
+ documents: { export: true, open: true }
2876
3034
  },
2877
3035
  // Broad contribution declarations so the neutral DRIVER host (which
2878
3036
  // registers arbitrary contributions directly in 'warn' mode) never
@@ -2928,17 +3086,12 @@ async function createHeadlessHost(options = {}) {
2928
3086
  willSave,
2929
3087
  toolSettings,
2930
3088
  async load(idml) {
2931
- const raw = worker.loadDocumentDirect(seqCounter++, idml);
2932
- const reply = JSON.parse(raw);
2933
- if (reply.kind === "documentLoaded") {
2934
- try {
2935
- worker.runResolveJson();
2936
- } catch {
2937
- }
2938
- return reply.payload.pageIds;
2939
- }
2940
- const errKind = reply.kind === "loadFailed" ? reply.payload.error.kind : reply.kind;
2941
- throw new Error(`headless load failed (${reply.kind}: ${errKind})`);
3089
+ const reply = loadDirect(idml);
3090
+ if (reply.kind === "documentLoaded") return reply.payload.pageIds;
3091
+ throw loadFailure(reply);
3092
+ },
3093
+ openedDocuments() {
3094
+ return opened;
2942
3095
  },
2943
3096
  loadBundle(bundle) {
2944
3097
  if (active) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paged-media/plugin-sdk",
3
- "version": "0.2.39-canary.0",
3
+ "version": "0.2.41-canary.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -11,7 +11,7 @@
11
11
  }
12
12
  },
13
13
  "dependencies": {
14
- "@paged-media/plugin-api": "0.2.39-canary.0"
14
+ "@paged-media/plugin-api": "0.2.41-canary.0"
15
15
  },
16
16
  "peerDependencies": {
17
17
  "react": "^18.3.0"
@@ -22,7 +22,7 @@
22
22
  }
23
23
  },
24
24
  "devDependencies": {
25
- "@paged-media/canvas-wasm": "0.67.0",
25
+ "@paged-media/canvas-wasm": "0.69.0",
26
26
  "@types/node": "20.19.39",
27
27
  "@types/react": "^18.3.31",
28
28
  "react": "^18.3.1",