@paged-media/plugin-sdk 0.2.41-canary.0 → 0.2.43-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, OpenDocumentResult, 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, EnteredEditContext, BindingsSurface, PanelProps, SchemaGate, WasmArtifact, CanvasPointerEvent, Mutation, MutationOutcome, BindingProviderHandle } from '@paged-media/plugin-api';
2
2
  import { ComponentType } from 'react';
3
3
 
4
4
  /**
@@ -104,6 +104,18 @@ interface BundleAssetProvider {
104
104
  /** The bytes of a document font face, or `null` when the host has
105
105
  * none. Style-agnostic when `style` is omitted. */
106
106
  getFontFace(family: string, style?: string): Promise<FontFaceAsset | null>;
107
+ /** W-15 — register a face in the engine's SCENE-LAYER face table
108
+ * (`registerFont` with `scope: "sceneLayer"`): scene-layer text
109
+ * resolves it, the document never sees it. The SDK calls it once per
110
+ * `host.assets.registerFont` and again when it replays the live faces
111
+ * after a disposal. Optional: a host without it (with
112
+ * `clearSceneFonts`) leaves `supports("assets.registerFont@1")` false. */
113
+ registerFont?(family: string, bytes: Uint8Array, style?: string): Promise<void>;
114
+ /** W-15 — drop every scene-layer face (`clearFontRegistry` with
115
+ * `scope: "sceneLayer"`), leaving the document's faces alone. The SDK
116
+ * owns which faces are live, so disposing one face is "clear, then
117
+ * register the rest again". */
118
+ clearSceneFonts?(): Promise<void>;
107
119
  }
108
120
  /** Blob-store budgets (K-4 / S-08). The default per-plugin quota when a
109
121
  * manifest requests none; a manifest's `storage.quotaBytes` may only
@@ -261,6 +273,18 @@ interface DocumentsBackend {
261
273
  };
262
274
  }): Promise<OpenDocumentResult>;
263
275
  }
276
+ /**
277
+ * v70 — the backend the editor injects to back `host.viewport`'s page
278
+ * members. The editor owns the camera and the page layout, so it answers
279
+ * which page is active and moves the camera to one. No backend →
280
+ * `goToPage` answers false, `activePage` null, the event never fires and
281
+ * `supports("viewport.pages@1")` is false.
282
+ */
283
+ interface PagesBackend {
284
+ goToPage(pageId: string, fit: "page" | "width"): boolean | Promise<boolean>;
285
+ activePage(): string | null;
286
+ onDidChangeActivePage(listener: (pageId: string | null) => void): () => void;
287
+ }
264
288
  /** The SHARED cross-plugin data-provider registry (paged.data §7.1 / D-09). The
265
289
  * editor creates ONE (`createDataProviderRegistry`) and injects the SAME
266
290
  * instance into every plugin host, so a provider plugin and a consumer plugin
@@ -423,6 +447,13 @@ interface CreateBundleHostOptions {
423
447
  * holds listeners that never run and `supports("document.onWillSave@1")`
424
448
  * is false. */
425
449
  willSave?: WillSaveBackend;
450
+ /** W-19 — the host's shell fills `pageId` / `pagePoint` /
451
+ * `contentPoint` on the `EnteredEditContext` of a POINTER entry (the
452
+ * double-click, the Type-tool click on owned content). The SDK passes
453
+ * the entered context through untouched either way; this flag only
454
+ * makes `supports("editContext.enterPoint@1")` true, so it is the HOST
455
+ * that vouches for delivery. */
456
+ editContextEnterPoint?: boolean;
426
457
  /** The host's tool-options store. When absent, `host.tools.settings`
427
458
  * answers `{}` and `supports("tools.settings@1")` is false. */
428
459
  toolSettings?: ToolSettingsBackend;
@@ -496,6 +527,10 @@ interface CreateBundleHostOptions {
496
527
  * `supports("documents.exportPaged@1")` / `supports("documents.open@1")`
497
528
  * answer true. When absent, both doors reject. */
498
529
  documents?: DocumentsBackend;
530
+ /** v70 — page navigation for `host.viewport` (`goToPage`,
531
+ * `activePage`, `onDidChangeActivePage`). When present,
532
+ * `supports("viewport.pages@1")` is true. */
533
+ pages?: PagesBackend;
499
534
  /** Host-provided WORKER backend (K-3 / S-07 / I-02). When present,
500
535
  * `host.workers.spawn` resolves a declared bundle-relative module +
501
536
  * constructs a host-owned `Worker` through it (capability-gated,
@@ -699,6 +734,19 @@ interface HeadlessHost {
699
734
  * bundle's "geometry AND label at once" claim is assertable without a
700
735
  * browser. `lastToolPreview()` tracks the list's FIRST shape. */
701
736
  lastToolPreviews(): readonly ToolPreviewShape[] | null;
737
+ /** W-20 — every LIVE overlay layer (`host.overlay.layer`), in stack
738
+ * order (bottom-most first), keyed `<manifest id>/<layer id>`, with the
739
+ * shapes it holds now (`[]` once cleared). A disposed layer is gone. */
740
+ overlayLayers(): ReadonlyArray<{
741
+ key: string;
742
+ shapes: readonly ToolPreviewShape[];
743
+ }>;
744
+ /** W-19 — play the SHELL's part on a pointer entry: call the registered
745
+ * edit context's `onEnter` with `ctx` (which may carry `pageId` /
746
+ * `pagePoint` / `contentPoint`, as the editor fills them). The harness
747
+ * advertises `editContext.enterPoint@1`. Returns `false` when no
748
+ * context of `ctx.type` is registered. */
749
+ enterEditContext(ctx: EnteredEditContext): boolean;
702
750
  /** ADR-023 phase A — the SHARED binding-provider registry this host
703
751
  * injected. It is the HOST side of the seam, so a conformance test
704
752
  * plays the part the editor's shared panel plays in phase C: enter
@@ -989,4 +1037,4 @@ declare function contributeObjectType(host: BundleHost, contribution: ObjectType
989
1037
  */
990
1038
  declare function contributeBindingProvider(host: BundleHost, contextType: string, provider: BindingProvider): BindingProviderHandle;
991
1039
 
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 };
1040
+ 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, type PagesBackend, 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
@@ -228,6 +228,10 @@ var HOST_FEATURES = [
228
228
  // needed — DESIGN.md §4.3d).
229
229
  "document.parentOf@1",
230
230
  "document.onDidChange@1",
231
+ // W-22 — the document-opened event. STATIC: it is the client's
232
+ // `documentLoaded` broadcast filtered, and every host posts that on
233
+ // every load (File ▸ Open, File ▸ New, a native-document open).
234
+ "document.onDidOpen@1",
231
235
  "document.getMetadata@1",
232
236
  "document.setMetadata@1",
233
237
  // Protocol 69 — the document-scoped label. Static like the snap door:
@@ -275,6 +279,10 @@ var HOST_FEATURES = [
275
279
  // door: the flag says this SDK forwards `snapPoint`; an engine older
276
280
  // than v67 answers the point unsnapped, never a throw.
277
281
  "document.snapPoint@1",
282
+ // v70 — page images from the engine's renderer (`requestSnapshot` with
283
+ // `hideItems`). Static like the snapper: the flag says this SDK
284
+ // forwards the query; an engine that refuses answers null.
285
+ "render.snapshot@1",
278
286
  // DOC-03 structured whole-story read (canvas-wasm v54). Static for the same
279
287
  // reason as storage.parts@1: the pinned engine is the server, and the worker
280
288
  // handshake refuses a protocol older than the pin — so a running bundle is
@@ -333,6 +341,15 @@ var ASSET_BUDGETS = {
333
341
  /** Largest font face the door will serve, in bytes (8 MiB). */
334
342
  maxFontFaceBytes: 8 * 1024 * 1024
335
343
  };
344
+ var sceneFaceLedgers = /* @__PURE__ */ new WeakMap();
345
+ function sceneFaceLedger(provider) {
346
+ let ledger = sceneFaceLedgers.get(provider);
347
+ if (!ledger) {
348
+ ledger = { entries: [], queue: Promise.resolve() };
349
+ sceneFaceLedgers.set(provider, ledger);
350
+ }
351
+ return ledger;
352
+ }
336
353
  var BLOB_BUDGETS = {
337
354
  /** Default per-plugin blob ceiling, in bytes (64 MiB). */
338
355
  defaultQuotaBytes: 64 * 1024 * 1024
@@ -1104,7 +1121,7 @@ function createBundleHost(getEditor, manifest, options) {
1104
1121
  }
1105
1122
  };
1106
1123
  const foreignMetadataKey = (m) => {
1107
- if (m.op === "setPluginMetadata" || m.op === "setDocumentMetadata") {
1124
+ if (m.op === "setPluginMetadata" || m.op === "setDocumentMetadata" || m.op === "setPageMetadata") {
1108
1125
  return m.args.key === metadataKey(manifest) ? null : m.args.key;
1109
1126
  }
1110
1127
  if (m.op === "batch") {
@@ -1442,6 +1459,28 @@ function createBundleHost(getEditor, manifest, options) {
1442
1459
  });
1443
1460
  return store.add(toDisposable(off));
1444
1461
  },
1462
+ // W-22 — the raw `documentLoaded` broadcast, narrowed to a clonable
1463
+ // snapshot (the handle's stats / ruler guides stay host-side).
1464
+ onDidOpen(listener) {
1465
+ requireDocRead("document.onDidOpen");
1466
+ const off = getEditor().client.subscribe((msg) => {
1467
+ if (msg.kind !== "documentLoaded") return;
1468
+ const h2 = msg.payload;
1469
+ try {
1470
+ listener({
1471
+ docId: h2.docId,
1472
+ pageCount: h2.pageCount,
1473
+ pageIds: [...h2.pageIds],
1474
+ pageSizesPt: h2.pageSizesPt.map(
1475
+ ([w, hgt]) => [w, hgt]
1476
+ )
1477
+ });
1478
+ } catch (err) {
1479
+ log.error(`document.onDidOpen listener failed: ${String(err)}`);
1480
+ }
1481
+ });
1482
+ return store.add(toDisposable(off));
1483
+ },
1445
1484
  // Protocol 66 batch — the save hook. The host awaits every
1446
1485
  // registered listener before it serialises the document; the wrapper
1447
1486
  // makes a bundle's failure a logged event, never a failed save.
@@ -1485,6 +1524,7 @@ function createBundleHost(getEditor, manifest, options) {
1485
1524
  return store.add(toDisposable(off));
1486
1525
  }
1487
1526
  };
1527
+ const pagesBackend = options?.pages;
1488
1528
  const viewport = {
1489
1529
  camera() {
1490
1530
  const cam = getEditor().camera.camera;
@@ -1493,6 +1533,53 @@ function createBundleHost(getEditor, manifest, options) {
1493
1533
  pxToPt(px) {
1494
1534
  const scale = getEditor().camera.camera.scale;
1495
1535
  return px / (scale > 0 ? scale : 1);
1536
+ },
1537
+ async goToPage(pageId, opts) {
1538
+ if (!pagesBackend || typeof pageId !== "string" || pageId === "") {
1539
+ return false;
1540
+ }
1541
+ const fit = opts?.fit === "width" ? "width" : "page";
1542
+ return await pagesBackend.goToPage(pageId, fit) === true;
1543
+ },
1544
+ activePage() {
1545
+ return pagesBackend ? pagesBackend.activePage() : null;
1546
+ },
1547
+ onDidChangeActivePage(listener) {
1548
+ if (!pagesBackend) {
1549
+ return toDisposable(() => {
1550
+ });
1551
+ }
1552
+ return store.add(toDisposable(pagesBackend.onDidChangeActivePage(listener)));
1553
+ }
1554
+ };
1555
+ const render = {
1556
+ async snapshot(pageId, opts) {
1557
+ requireDocRead("render.snapshot");
1558
+ const widthPx = Math.round(opts?.widthPx ?? 0);
1559
+ if (!(widthPx >= 1 && widthPx <= 8192)) {
1560
+ throw new Error("host.render.snapshot: widthPx must be 1 to 8192");
1561
+ }
1562
+ const hideItems = Array.isArray(opts?.hideItems) ? opts.hideItems : [];
1563
+ let reply;
1564
+ try {
1565
+ reply = await getEditor().client.send({
1566
+ kind: "requestSnapshot",
1567
+ payload: { pageId, targetWidthPx: widthPx, hideItems }
1568
+ });
1569
+ } catch {
1570
+ return null;
1571
+ }
1572
+ if (reply.kind !== "snapshotReady") {
1573
+ return null;
1574
+ }
1575
+ const p = reply.payload;
1576
+ return {
1577
+ pageId: p.pageId,
1578
+ widthPx: p.widthPx,
1579
+ heightPx: p.heightPx,
1580
+ png: Uint8Array.from(p.pngBytes),
1581
+ layoutGeneration: p.layoutGeneration
1582
+ };
1496
1583
  }
1497
1584
  };
1498
1585
  const text = {
@@ -1536,6 +1623,9 @@ function createBundleHost(getEditor, manifest, options) {
1536
1623
  );
1537
1624
  }
1538
1625
  };
1626
+ let overlayLayerSeq = 0;
1627
+ let overlayLayerWarned = false;
1628
+ const liveOverlayLayers = /* @__PURE__ */ new Set();
1539
1629
  const overlay = {
1540
1630
  setToolPreview(shape) {
1541
1631
  requireDeclared(
@@ -1565,6 +1655,56 @@ function createBundleHost(getEditor, manifest, options) {
1565
1655
  return;
1566
1656
  }
1567
1657
  signals.setToolPreview(list ? list[0] : null);
1658
+ },
1659
+ // W-20 — retained per-bundle layers. Each layer is a host-wide key
1660
+ // `<manifest id>/<layer id>`; registering it with `[]` at creation
1661
+ // fixes its place in the stack (creation order), so a later `set`
1662
+ // never reorders it. Without a host sink the handle is inert (warned
1663
+ // once per bundle) — never a throw, never a write into the shared
1664
+ // tool-preview slot, which would erase the active tool's preview.
1665
+ layer(id) {
1666
+ requireDeclared(
1667
+ hasRendering("overlay"),
1668
+ "overlay.layer",
1669
+ 'capabilities.rendering must include "overlay"'
1670
+ );
1671
+ const layerId = id ?? `layer-${++overlayLayerSeq}`;
1672
+ if (liveOverlayLayers.has(layerId)) {
1673
+ throw new Error(
1674
+ `host.overlay.layer("${layerId}"): a layer with this id is already live in ${manifest.id} \u2014 dispose it first or pick another id`
1675
+ );
1676
+ }
1677
+ const key = `${manifest.id}/${layerId}`;
1678
+ const signals = getEditor().overlaySignals;
1679
+ const sink2 = typeof signals?.setOverlayLayer === "function" && typeof signals?.removeOverlayLayer === "function" ? signals : null;
1680
+ if (!sink2 && !overlayLayerWarned) {
1681
+ overlayLayerWarned = true;
1682
+ log.warn(
1683
+ 'host.overlay.layer \u2014 this host renders no overlay layers (supports("overlay.layers@1") is false); shapes are dropped'
1684
+ );
1685
+ }
1686
+ liveOverlayLayers.add(layerId);
1687
+ sink2?.setOverlayLayer(key, []);
1688
+ let disposed = false;
1689
+ const handle = {
1690
+ id: layerId,
1691
+ set(shapes) {
1692
+ if (disposed) return;
1693
+ sink2?.setOverlayLayer(key, [...shapes]);
1694
+ },
1695
+ clear() {
1696
+ if (disposed) return;
1697
+ sink2?.setOverlayLayer(key, []);
1698
+ },
1699
+ dispose() {
1700
+ if (disposed) return;
1701
+ disposed = true;
1702
+ liveOverlayLayers.delete(layerId);
1703
+ sink2?.removeOverlayLayer(key);
1704
+ }
1705
+ };
1706
+ store.add(handle);
1707
+ return handle;
1568
1708
  }
1569
1709
  };
1570
1710
  const backing = options?.storage ?? defaultStorageBacking();
@@ -1829,6 +1969,65 @@ function createBundleHost(getEditor, manifest, options) {
1829
1969
  }
1830
1970
  return face;
1831
1971
  },
1972
+ // W-15 — the one WRITE: a scene-layer face. The engine has a single
1973
+ // scene table that `clearFontRegistry` can only empty as a whole, so
1974
+ // the SDK keeps the list of live faces per provider (across bundles)
1975
+ // and disposes one face by clearing and replaying the others. Every
1976
+ // engine call goes through the ledger's queue, so a replay never
1977
+ // interleaves with a registration.
1978
+ async registerFont(bytes, family, style) {
1979
+ requireDeclared(
1980
+ hasAsset("fonts"),
1981
+ "assets.registerFont",
1982
+ 'capabilities.assets must include "fonts"'
1983
+ );
1984
+ const label = `assets.registerFont("${family}"${style ? `, "${style}"` : ""})`;
1985
+ if (bytes.byteLength > ASSET_BUDGETS.maxFontFaceBytes) {
1986
+ throw new Error(
1987
+ `${label}: ${bytes.byteLength} bytes is over the ${ASSET_BUDGETS.maxFontFaceBytes}-byte per-face cap`
1988
+ );
1989
+ }
1990
+ const provider = assetSource;
1991
+ const register = provider?.registerFont?.bind(provider);
1992
+ const clear = provider?.clearSceneFonts?.bind(provider);
1993
+ if (!provider || !register || !clear) {
1994
+ log.warn(
1995
+ `${label} ignored \u2014 the host cannot register scene faces (probe supports("assets.registerFont@1")); scene text keeps its fallback face`
1996
+ );
1997
+ return toDisposable(() => {
1998
+ });
1999
+ }
2000
+ const ledger = sceneFaceLedger(provider);
2001
+ const entry = style === void 0 ? { family, bytes: bytes.slice() } : { family, style, bytes: bytes.slice() };
2002
+ const run = (job) => {
2003
+ const next = ledger.queue.then(job);
2004
+ ledger.queue = next.catch(() => {
2005
+ });
2006
+ return next;
2007
+ };
2008
+ await run(async () => {
2009
+ await register(entry.family, entry.bytes, entry.style);
2010
+ ledger.entries.push(entry);
2011
+ });
2012
+ let disposed = false;
2013
+ return store.add(
2014
+ toDisposable(() => {
2015
+ if (disposed) return;
2016
+ disposed = true;
2017
+ void run(async () => {
2018
+ const i = ledger.entries.indexOf(entry);
2019
+ if (i < 0) return;
2020
+ ledger.entries.splice(i, 1);
2021
+ await clear();
2022
+ for (const live of ledger.entries) {
2023
+ await register(live.family, live.bytes, live.style);
2024
+ }
2025
+ }).catch((error) => {
2026
+ log.warn(`${label}: dropping the face failed: ${String(error)}`);
2027
+ });
2028
+ })
2029
+ );
2030
+ },
1832
2031
  // C-5 / I-04 (core v42): a placed DOCUMENT image's ORIGINAL bytes,
1833
2032
  // straight from the engine's resolver/parse cache through the
1834
2033
  // `requestPlacedAssetBytes` wire query — no injected source needed
@@ -2356,6 +2555,12 @@ function createBundleHost(getEditor, manifest, options) {
2356
2555
  if (typeof getEditor().overlaySignals?.setToolPreviews === "function") {
2357
2556
  featureSet.add("overlay.multiPreview@1");
2358
2557
  }
2558
+ if (typeof getEditor().overlaySignals?.setOverlayLayer === "function" && typeof getEditor().overlaySignals?.removeOverlayLayer === "function") {
2559
+ featureSet.add("overlay.layers@1");
2560
+ }
2561
+ if (options?.editContextEnterPoint === true) {
2562
+ featureSet.add("editContext.enterPoint@1");
2563
+ }
2359
2564
  if (getEditor().images) {
2360
2565
  featureSet.add("rendering.resourceProvider@1");
2361
2566
  }
@@ -2399,6 +2604,9 @@ function createBundleHost(getEditor, manifest, options) {
2399
2604
  if (options?.assetSource) {
2400
2605
  featureSet.add("assets.fonts@1");
2401
2606
  }
2607
+ if (typeof options?.assetSource?.registerFont === "function" && typeof options?.assetSource?.clearSceneFonts === "function") {
2608
+ featureSet.add("assets.registerFont@1");
2609
+ }
2402
2610
  if (options?.consent) {
2403
2611
  featureSet.add("network.consent@1");
2404
2612
  }
@@ -2421,6 +2629,9 @@ function createBundleHost(getEditor, manifest, options) {
2421
2629
  featureSet.add("document.readNative@1");
2422
2630
  featureSet.add("document.openNative@1");
2423
2631
  }
2632
+ if (options?.pages) {
2633
+ featureSet.add("viewport.pages@1");
2634
+ }
2424
2635
  if (options?.documents) {
2425
2636
  featureSet.add("documents.exportPaged@1");
2426
2637
  featureSet.add("documents.open@1");
@@ -2441,6 +2652,7 @@ function createBundleHost(getEditor, manifest, options) {
2441
2652
  document,
2442
2653
  selection,
2443
2654
  viewport,
2655
+ render,
2444
2656
  text,
2445
2657
  overlay,
2446
2658
  shell,
@@ -2703,7 +2915,7 @@ function inMemorySecretStore() {
2703
2915
  };
2704
2916
  }
2705
2917
  var seqCounter = 1;
2706
- function makeEngineEditor(worker, recorder, onToolPreview, onToolPreviews, engineShaper = false) {
2918
+ function makeEngineEditor(worker, recorder, onToolPreview, onToolPreviews, overlayLayers, engineShaper = false) {
2707
2919
  const protocol = worker.protocolVersion;
2708
2920
  const listeners = /* @__PURE__ */ new Set();
2709
2921
  const fanOut = (reply) => {
@@ -2853,6 +3065,15 @@ function makeEngineEditor(worker, recorder, onToolPreview, onToolPreviews, engin
2853
3065
  toolPreview = toolPreviews ? toolPreviews[0] : null;
2854
3066
  onToolPreviews(toolPreviews);
2855
3067
  onToolPreview(toolPreview);
3068
+ },
3069
+ // W-20 — the retained layers. A Map keeps first-insertion order,
3070
+ // which is the stack order the editor renders; a re-set keeps the
3071
+ // key's place.
3072
+ setOverlayLayer(key, shapes) {
3073
+ overlayLayers.set(key, shapes);
3074
+ },
3075
+ removeOverlayLayer(key) {
3076
+ overlayLayers.delete(key);
2856
3077
  }
2857
3078
  },
2858
3079
  // No tool spine + no content caret headlessly — both are inert
@@ -2887,6 +3108,7 @@ async function createHeadlessHost(options = {}) {
2887
3108
  const contributions = [];
2888
3109
  let lastPreview = null;
2889
3110
  let lastPreviews = null;
3111
+ const overlayLayerMap = /* @__PURE__ */ new Map();
2890
3112
  const { editor, fanOut } = makeEngineEditor(
2891
3113
  worker,
2892
3114
  contributions,
@@ -2896,6 +3118,7 @@ async function createHeadlessHost(options = {}) {
2896
3118
  (value) => {
2897
3119
  lastPreviews = value;
2898
3120
  },
3121
+ overlayLayerMap,
2899
3122
  options.engineShaper === true
2900
3123
  );
2901
3124
  const loadDirect = (bytes) => {
@@ -2967,6 +3190,9 @@ async function createHeadlessHost(options = {}) {
2967
3190
  // headlessly (the editor's real adapter currently serves null;
2968
3191
  // DESIGN.md §13.4). Absent → the no-bytes door.
2969
3192
  assetSource: options.assetSource,
3193
+ // W-19 — `enterEditContext()` below delivers the entering point the
3194
+ // way the editor shell does, so the harness vouches for it.
3195
+ editContextEnterPoint: true,
2970
3196
  documents: documentsBackend,
2971
3197
  // Record the SCHEMA verbatim at registration — the panel registry
2972
3198
  // only ever sees the synthesized React panel, so the conformance
@@ -3082,12 +3308,26 @@ async function createHeadlessHost(options = {}) {
3082
3308
  lastToolPreviews() {
3083
3309
  return lastPreviews;
3084
3310
  },
3311
+ overlayLayers() {
3312
+ return [...overlayLayerMap].map(([key, shapes]) => ({ key, shapes }));
3313
+ },
3314
+ enterEditContext(ctx) {
3315
+ const entry = contributions.find(
3316
+ (c) => c.kind === "editContext" && c.id === ctx.type
3317
+ );
3318
+ if (!entry) return false;
3319
+ entry.value.onEnter?.(ctx);
3320
+ return true;
3321
+ },
3085
3322
  bindingProviders,
3086
3323
  willSave,
3087
3324
  toolSettings,
3088
3325
  async load(idml) {
3089
3326
  const reply = loadDirect(idml);
3090
- if (reply.kind === "documentLoaded") return reply.payload.pageIds;
3327
+ if (reply.kind === "documentLoaded") {
3328
+ fanOut(reply);
3329
+ return reply.payload.pageIds;
3330
+ }
3091
3331
  throw loadFailure(reply);
3092
3332
  },
3093
3333
  openedDocuments() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paged-media/plugin-sdk",
3
- "version": "0.2.41-canary.0",
3
+ "version": "0.2.43-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.41-canary.0"
14
+ "@paged-media/plugin-api": "0.2.43-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.69.0",
25
+ "@paged-media/canvas-wasm": "0.70.0",
26
26
  "@types/node": "20.19.39",
27
27
  "@types/react": "^18.3.31",
28
28
  "react": "^18.3.1",