@paged-media/plugin-api 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.
package/dist/host.d.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  import type { CollectionName, DocumentMeta, ElementGeometryItem, ElementId, HitFilter, HitResult, Mutation, PageId, PathAnchorsResult, SceneTreeNode, SelectionMode } from "./wire";
2
- import type { CommandContribution, KeybindingContribution, OverlayContribution, PagedEditor, PanelContribution, ToolContribution, ToolPreviewShape } from "./editor";
2
+ import type { CommandContribution, ExporterContribution, ImporterContribution, KeybindingContribution, OverlayContribution, PagedEditor, PanelContribution, ToolContribution, ToolPreviewShape } from "./editor";
3
+ import type { SceneLayer } from "./wire";
4
+ import type { AssetSurface } from "./assets";
3
5
  import type { PluginManifest } from "./manifest";
6
+ import type { SchemaPanelContribution } from "./panel-schema";
7
+ import type { WidgetSurface } from "./widgets";
4
8
  /** Everything a bundle registers is disposable; the host ALSO tracks
5
9
  * it, so deactivation teardown is structural, not conventional. */
6
10
  export interface Disposable {
@@ -14,20 +18,162 @@ export interface PluginLogger {
14
18
  warn(message: string, ...args: unknown[]): void;
15
19
  error(message: string, ...args: unknown[]): void;
16
20
  }
17
- /** Reserved (P0 shell work, paged.draw B-02 / paged.web §8): an
18
- * edit-context claim double-click entry on a content type, scoped
19
- * panel/tool sets. Throws PluginApiNotImplemented in v0. */
20
- export interface EditContextDescriptor {
21
+ /**
22
+ * What a matcher sees about a candidate element — a plain snapshot, so
23
+ * the predicate is clonable + isolate-portable (DESIGN.md §6). The host
24
+ * resolves it once per double-click (or programmatic test) from the
25
+ * engine: the element's kind, its containing-group ancestry, and —
26
+ * crucially — this plugin's OWN metadata envelope on the element (the
27
+ * `x-paged:<id>` carrier, W-02). A bundle matches on the namespace it
28
+ * already owns; it never sees a foreign plugin's metadata.
29
+ */
30
+ export interface EditContextCandidate {
31
+ /** The hit leaf element. */
32
+ id: ElementId;
33
+ /** The element's engine kind (`"polygon"`, `"rectangle"`, …) when the
34
+ * host knows it; `undefined` when the hit carried none. */
35
+ kind?: string;
36
+ /** Containing-group ancestry, outer-most first (the hit's
37
+ * `groupChain`). Empty when the element is not nested. */
38
+ groupChain: readonly string[];
39
+ /** THIS plugin's metadata envelope on the element, pre-resolved by the
40
+ * host (the `x-paged:<manifest id>` carrier — never a foreign key).
41
+ * `null` when the element carries none. The objectType matcher reads
42
+ * this to claim a webFrame ("has my source metadata"). */
43
+ metadata: PluginMetadataEnvelope | null;
44
+ }
45
+ /**
46
+ * An edit-context CLAIM (paged.draw B-02 / paged.web §8). Entering one
47
+ * (double-click on a matching element, or programmatically) pushes a
48
+ * context onto the shell's stack with: a restricted active tool-set, an
49
+ * emphasized panel-set, a breadcrumb, a narrowed write-scope (the
50
+ * context element's subtree), and Esc-pops-one-level. The plugin owns
51
+ * the matcher + the lifecycle hooks; the shell owns the stack, the
52
+ * chrome, and the scope enforcement.
53
+ */
54
+ export interface EditContextContribution {
55
+ /** The context TYPE — must match a `contributes.editContexts[].type`
56
+ * the manifest declares (the capability gate). Not namespace-prefixed
57
+ * (a content-type name, e.g. `"vectorGraphic"`, `"webFrame"`), but
58
+ * the bundle can only claim a type it declared. */
21
59
  type: string;
60
+ /** How the user enters it. `"doubleClick"` wires the canvas
61
+ * double-click entry; `"command"` is programmatic / menu-driven. */
22
62
  entry: "doubleClick" | "command";
63
+ /** Does this element warrant entering THIS context? Pure predicate
64
+ * over the candidate snapshot (kind / groupChain / this plugin's
65
+ * metadata). When an `objectType` already routes a double-click to a
66
+ * context (via `editContextType`), this matcher is not consulted —
67
+ * it is the fallback path for elements claimed by KIND, not metadata
68
+ * (the vectorGraphic case). Optional: a context entered only by
69
+ * command needs no matcher. */
70
+ matches?(candidate: EditContextCandidate): boolean;
71
+ /** Tool ids the context restricts the rail to (the active tool-set
72
+ * swap). Namespaced ids the bundle registered, plus host built-ins it
73
+ * names. Empty = no restriction (all tools stay available). */
74
+ toolIds?: string[];
75
+ /** Panel ids the cockpit emphasizes / raises on enter (the panel-set
76
+ * swap). The shell opens/raises these; it does not hide others. */
77
+ panelIds?: string[];
78
+ /** Called when the context becomes active (after the stack push + the
79
+ * scope narrowing). The element entered on is passed so the hook can
80
+ * prime panel state / publish bindings. */
81
+ onEnter?(ctx: EnteredEditContext): void;
82
+ /** Called when the context pops (Esc, or a programmatic exit), before
83
+ * the stack unwinds. */
84
+ onExit?(ctx: EnteredEditContext): void;
85
+ /** K-1 — pointer inside the context's frame, delivered in FRAME-CONTENT
86
+ * coordinates: the editor resolved the frame's content transform
87
+ * (`frame_outer ∘ content-box offset`), inverted it, and mapped the
88
+ * page-space pointer into the plugin's own space — so the plugin (a
89
+ * sheet grid) hit-tests in content coords regardless of how the frame
90
+ * is moved / scaled / rotated (§8.5 — the plugin never compensates).
91
+ * Optional: a context that doesn't edit by pointer omits them. */
92
+ onContentPointerDown?(e: ContentPointerEvent): void;
93
+ onContentPointerMove?(e: ContentPointerEvent): void;
94
+ onContentPointerUp?(e: ContentPointerEvent): void;
95
+ /** K-1 — a key while the context is active. The shell owns Esc (→
96
+ * `onCancel`) and Enter (→ `onCommit`); every other key forwards here. */
97
+ onContentKey?(e: KeyboardEvent): void;
98
+ /** K-1 — unsaved-edit probe: gates the discard prompt + the §8.0
99
+ * seamless-undo boundary. Absent ⇒ treated as never dirty. */
100
+ isDirty?(): boolean;
101
+ /** K-1 — modal COMMIT (Enter / a click outside the frame): keep the
102
+ * in-flight edits. Fires before `onExit`. */
103
+ onCommit?(): void;
104
+ /** K-1 — modal CANCEL (Esc): revert the in-flight edits. Fires before
105
+ * `onExit`. */
106
+ onCancel?(): void;
107
+ /** HOST-STAMPED, not author-supplied: the `x-paged:<manifest id>`
108
+ * metadata key the host resolves the candidate's `metadata` from
109
+ * before calling `matches`. The SDK adapter fills this from the
110
+ * bundle's manifest at registration; authors leave it undefined. */
111
+ metadataKey?: string;
23
112
  }
24
- /** Reserved (paged.web §9.1.2): a plugin-defined object type under
25
- * the metadata-plus-baked-fallback contract. Throws in v0. */
26
- export interface ObjectTypeDescriptor {
113
+ /** The live handle a context's `onEnter` / `onExit` receives — the
114
+ * element entered on + the context type, a clonable snapshot. */
115
+ export interface EnteredEditContext {
116
+ type: string;
117
+ /** The element the context was entered on (the write-scope root). */
118
+ id: ElementId;
119
+ }
120
+ /** K-1 — a pointer delivered to the ACTIVE edit context, in FRAME-CONTENT
121
+ * coordinates (the editor inverted the frame's content transform). */
122
+ export interface ContentPointerEvent {
123
+ /** Pointer in frame-content points (origin = the content-box top-left,
124
+ * x right, y down). */
125
+ contentPoint: [number, number];
126
+ /** The frame the active context edits (the stack's scope root). */
127
+ elementId: string;
128
+ modifiers: {
129
+ shift: boolean;
130
+ alt: boolean;
131
+ cmd: boolean;
132
+ ctrl: boolean;
133
+ };
134
+ /** Mouse button (0 = primary). */
135
+ button: number;
136
+ }
137
+ /**
138
+ * A plugin-defined OBJECT TYPE (paged.web §9.1.2). A webFrame is an
139
+ * ordinary rectangle with attached `x-paged:media.paged.web` source
140
+ * metadata; registering an object type lets the shell recognize it
141
+ * (`matches`) and route a double-click to a SOURCE edit context
142
+ * (`editContextType`) instead of descending into a group. The metadata
143
+ * namespace is the matcher's domain — `matches` reads the candidate's
144
+ * pre-resolved (own-namespace) envelope.
145
+ */
146
+ export interface ObjectTypeContribution {
147
+ /** The object-type name — must match a `contributes.objectTypes[].type`
148
+ * the manifest declares (the capability gate). */
27
149
  type: string;
28
- /** What the baked IDML form degrades to without the plugin. */
150
+ /** Is this element an instance of this object type? Reads the
151
+ * candidate's metadata envelope (this plugin's `x-paged:<id>` carrier)
152
+ * — e.g. "has a `source` field" for a webFrame. */
153
+ matches(candidate: EditContextCandidate): boolean;
154
+ /** The edit-context type a double-click on a matching element enters,
155
+ * instead of group descent. Must be a context the SAME bundle
156
+ * registered via `contribute.editContext`. Absent = the object type is
157
+ * recognized for selection/chrome but double-click falls through to
158
+ * the default (group descent) — the honest partial. */
159
+ editContextType?: string;
160
+ /** What the baked IDML form degrades to without the plugin (the
161
+ * metadata-plus-baked-fallback contract; `ObjectTypeBaker` produces
162
+ * the derived children). Carried for the bake loop (still reserved)
163
+ * and for selection-chrome hints. */
29
164
  bakedFallback: "group" | "rectangle" | "raster";
165
+ /** HOST-STAMPED (see `EditContextContribution.metadataKey`): the
166
+ * `x-paged:<manifest id>` key the host resolves the candidate's
167
+ * `metadata` from before calling `matches`. Authors leave it
168
+ * undefined; the SDK adapter fills it. */
169
+ metadataKey?: string;
30
170
  }
171
+ /** Back-compat aliases (the v0 reserved descriptors) — the rich
172
+ * contributions above supersede them; kept so existing references
173
+ * resolve. New code uses `EditContextContribution` /
174
+ * `ObjectTypeContribution`. */
175
+ export type EditContextDescriptor = EditContextContribution;
176
+ export type ObjectTypeDescriptor = ObjectTypeContribution;
31
177
  /**
32
178
  * The contribution surface. Every method enforces the namespace rule
33
179
  * (ids start with `<manifest.id>.`) and tracks the registration for
@@ -36,14 +182,79 @@ export interface ObjectTypeDescriptor {
36
182
  export interface ContributionSurface {
37
183
  tool(contribution: ToolContribution): Disposable;
38
184
  panel(contribution: PanelContribution): Disposable;
185
+ /**
186
+ * Register a DECLARATIVE panel (W3.1, closes B-01): sections/rows/
187
+ * widgets from the catalog vocabulary, with visibility/enablement
188
+ * driven by the bundle's PUBLISHED bindings (`host.bindings`) — no
189
+ * React crosses the boundary (the isolate-ready panel form; see
190
+ * panel-schema.ts). Same namespace + capability gate as `panel`
191
+ * (`contributes.panels[]` must list the id). The host renders the
192
+ * schema from the catalog and subscribes to the bundle's bindings;
193
+ * an expert-leaf React `panel` stays the escape hatch for custom UI.
194
+ */
195
+ schemaPanel(contribution: SchemaPanelContribution): Disposable;
39
196
  command(contribution: CommandContribution): Disposable;
40
197
  keybinding(contribution: KeybindingContribution): Disposable;
41
198
  overlay(contribution: OverlayContribution): Disposable;
42
- /** Reserved — throws PluginApiNotImplemented until edit contexts
43
- * land in the shell. Declared so manifests/docs can reference it. */
44
- editContext(descriptor: EditContextDescriptor): Disposable;
45
- /** Reserved throws PluginApiNotImplemented (paged.web W1). */
46
- objectType(descriptor: ObjectTypeDescriptor): Disposable;
199
+ /**
200
+ * Register an EDIT CONTEXT (W3.2, closes B-02): a double-click (or
201
+ * programmatic) entry on a content type that pushes a scoped context
202
+ *restricted tool-set, emphasized panels, breadcrumb, narrowed
203
+ * write-scope, Esc-pops. Capability-gated: the `type` must be listed
204
+ * in `contributes.editContexts[]`. The shell owns the stack + chrome
205
+ * + scope; the plugin owns the matcher + the onEnter/onExit hooks.
206
+ */
207
+ editContext(contribution: EditContextContribution): Disposable;
208
+ /**
209
+ * Register an OBJECT TYPE (W3.2, closes W-03): a plugin-defined object
210
+ * (a webFrame is a rectangle with attached source metadata). A
211
+ * double-click on a matching element enters its `editContextType`
212
+ * instead of descending into a group. Capability-gated: the `type`
213
+ * must be listed in `contributes.objectTypes[]`. The matcher reads the
214
+ * element's OWN-namespace metadata envelope (the `x-paged:<id>`
215
+ * carrier).
216
+ */
217
+ objectType(contribution: ObjectTypeContribution): Disposable;
218
+ /**
219
+ * Register a document IMPORTER (K-2 / S-06): claim file extensions /
220
+ * MIME types so an opened file (File menu, drag-drop, or
221
+ * `host.shell.pickFile`) routes its bytes to THIS plugin's `import()`
222
+ * instead of the default IDML loader — the plugin owns what the file
223
+ * becomes (load into its own engine, lower a range, …; it does not
224
+ * replace the document unless it chooses to). Capability-gated: the
225
+ * `id` must be listed in `contributes.importers[]`. Probe
226
+ * `supports("contribute.importer@1")`.
227
+ */
228
+ importer(contribution: ImporterContribution): Disposable;
229
+ /**
230
+ * Register a document EXPORTER (K-2 / S-06): produce bytes for a file
231
+ * type on demand (the export UI lists it and pulls on save).
232
+ * Capability-gated: the `id` must be listed in
233
+ * `contributes.exporters[]`.
234
+ */
235
+ exporter(contribution: ExporterContribution): Disposable;
236
+ /**
237
+ * Open a SCENE-LAYER surface (C-1): submit vector content that renders
238
+ * INSIDE a frame, in frame-content coordinates — core applies the
239
+ * frame's `ItemTransform` and clips to the content box (§8.5), so the
240
+ * plugin never compensates for the transform. The layer lowers through
241
+ * the same display-list → GPU/CPU path as native content (colour-
242
+ * managed, print-correct). Capability-gated: `capabilities.rendering`
243
+ * must include `"sceneLayer"`. Probe `supports("rendering.sceneLayer@1")`
244
+ * — false when the host wires no scene channel (the surface then warns +
245
+ * no-ops). The returned surface is disposable: disposing it clears every
246
+ * layer it submitted.
247
+ */
248
+ sceneLayer(): SceneLayerSurface;
249
+ }
250
+ /** The scene-layer surface (C-1) returned by `contribute.sceneLayer()`.
251
+ * `elementId` is the host `Self` id of the frame to render into. */
252
+ export interface SceneLayerSurface extends Disposable {
253
+ /** Submit (replacing any previous) the vector layer for `elementId`. */
254
+ submit(elementId: string, layer: SceneLayer): Promise<void>;
255
+ /** Clear the layer for `elementId` (returns the frame to native
256
+ * content). */
257
+ clear(elementId: string): Promise<void>;
47
258
  }
48
259
  /** Expected mutation failures are results, not throws — mirroring the
49
260
  * editor's mutate-never-throws convention. */
@@ -58,6 +269,23 @@ export type MutationOutcome = {
58
269
  export interface DocumentChangeEvent {
59
270
  kind: "mutationApplied" | "undoApplied" | "redoApplied";
60
271
  pageIds: PageId[];
272
+ /** Content-box reflow (protocol v38, C-2/S-05): present ONLY when the
273
+ * change RESIZED a frame's content box (a `resizeFrame`) — never on a
274
+ * pure transform (move/scale/rotate is display-only, §8.5). A
275
+ * pagination consumer re-splits on this and ignores transform-only
276
+ * changes (where it is absent). */
277
+ reflow?: {
278
+ frameId: string;
279
+ contentBox: [number, number, number, number];
280
+ };
281
+ }
282
+ /** One link in a text-frame thread (protocol v38, C-2/S-05). `next` is
283
+ * the following frame's id (null at the tail); `overflow` marks the tail
284
+ * frame as overset (story content past the chain end). */
285
+ export interface FrameChainLink {
286
+ frameId: string;
287
+ next: string | null;
288
+ overflow: boolean;
61
289
  }
62
290
  /**
63
291
  * Read-broad / write-through-one-door. `mutate` is the single write
@@ -75,6 +303,12 @@ export interface DocumentSurface {
75
303
  hitTest(pageId: PageId, point: [number, number], filter?: HitFilter): Promise<HitResult | null>;
76
304
  elementGeometry(ids: ElementId[]): Promise<ElementGeometryItem[]>;
77
305
  tree(): Promise<SceneTreeNode[]>;
306
+ /** Read a text frame's thread topology (protocol v38, C-2/S-05) — the
307
+ * ordered chain of frames a story flows through, tail-overflow flagged.
308
+ * Empty when the story has no frame or no document is loaded. A
309
+ * pagination consumer reads this to know the real host chain (rather
310
+ * than a caller-supplied one). */
311
+ frameChain(storyId: string): Promise<FrameChainLink[]>;
78
312
  onDidChange(listener: (e: DocumentChangeEvent) => void): Disposable;
79
313
  /**
80
314
  * Plugin-metadata carrier (protocol v33) — read this plugin's
@@ -143,6 +377,27 @@ export interface ViewportSurface {
143
377
  * screen-tolerance idiom every tool needs). */
144
378
  pxToPt(px: number): number;
145
379
  }
380
+ /** Font advance + vertical metrics, in document points (pt). */
381
+ export interface TextMetrics {
382
+ /** Total advance width of the run at `sizePt`. */
383
+ advance: number;
384
+ /** Face ascender at `sizePt`. */
385
+ ascender: number;
386
+ /** Face descender at `sizePt` (negative below the baseline). */
387
+ descender: number;
388
+ }
389
+ /**
390
+ * Text measurement against the loaded document's fonts (S-13). A read
391
+ * door — no capability gate (like {@link ViewportSurface}); it wraps the
392
+ * engine's shaper (`paged-text::shape_run`) so a plugin can size grid
393
+ * columns / lower content to widths the page surface will agree with
394
+ * (the §8.3 cross-surface-consistency requirement). Resolves the face
395
+ * from the document's font registry; falls back to the default face when
396
+ * `family` is unknown.
397
+ */
398
+ export interface TextSurface {
399
+ measureString(family: string, style: string | null, text: string, sizePt: number): Promise<TextMetrics>;
400
+ }
146
401
  /** The v0 overlay channel: the shared tool-preview signal (polyline /
147
402
  * rect). Retained plugin scene layers are the P2 channel — reserved,
148
403
  * not faked. */
@@ -161,6 +416,29 @@ export interface ShellSurface {
161
416
  * Window-menu / panel-rail path). */
162
417
  openPanel(panelId: string): void;
163
418
  closePanel(panelId: string): void;
419
+ /** Open the host's file picker and resolve to the chosen files' bytes
420
+ * (read at the host boundary — the contract never leaks a DOM `File`,
421
+ * so a bundle stays isolate-ready, K-5 / S-11). Resolves to `[]` when
422
+ * the user cancels OR no picker is wired (the honest no-picker door);
423
+ * probe `host.supports("shell.pickFile@1")` for the latter. */
424
+ pickFile(options?: FilePickerOptions): Promise<readonly PickedFile[]>;
425
+ }
426
+ /** Filter + multiplicity for `ShellSurface.pickFile` (K-5 / S-11). */
427
+ export interface FilePickerOptions {
428
+ /** Accept filter — extensions (leading dot) and/or MIME types, passed
429
+ * straight to the picker's `accept` (e.g. `[".xlsx"]`). Absent = any. */
430
+ accept?: readonly string[];
431
+ /** Allow choosing more than one file. Default `false`. */
432
+ multiple?: boolean;
433
+ }
434
+ /** A file the user chose through `pickFile`, bytes already read. */
435
+ export interface PickedFile {
436
+ /** File name incl. extension. */
437
+ name: string;
438
+ /** The file's raw bytes. */
439
+ bytes: Uint8Array;
440
+ /** MIME type the browser reported (may be `""`). */
441
+ mimeType: string;
164
442
  }
165
443
  /** Namespaced key-value persistence (`paged.plugin.<id>.*`), JSON
166
444
  * values. Backing is host-provided (localStorage in-process;
@@ -171,6 +449,120 @@ export interface StorageSurface {
171
449
  delete(key: string): void;
172
450
  keys(): string[];
173
451
  }
452
+ /** Per-plugin usage + the granted ceiling, in bytes (`BlobSurface.usage`). */
453
+ export interface BlobUsage {
454
+ /** Bytes this plugin currently stores. */
455
+ used: number;
456
+ /** The granted ceiling in bytes — the stricter of the host's hard
457
+ * per-plugin cap and the manifest's requested `storage.quotaBytes`.
458
+ * `0` when no store is wired. */
459
+ quota: number;
460
+ }
461
+ /**
462
+ * Persistent BINARY blob storage (K-4 / S-08): an OPFS-backed,
463
+ * per-plugin, quota-bounded store for bytes too large for the KV
464
+ * `host.storage` (multi-MB workbook bytes, decode spill). Keys are
465
+ * namespaced to THIS plugin (a bundle never sees another's bytes).
466
+ * Capability-gated: every door requires `capabilities.storage` ∋
467
+ * `blob: true`. Always present — when the host injects no backend a
468
+ * `read` answers `null`, `keys` is `[]`, `usage` is `{used:0,quota:0}`,
469
+ * and a `write` REJECTS (the honest no-store door; probe
470
+ * `supports("storage.blob@1")` first). A `write` that would exceed the
471
+ * granted quota rejects.
472
+ */
473
+ export interface BlobSurface {
474
+ write(key: string, bytes: Uint8Array): Promise<void>;
475
+ read(key: string): Promise<Uint8Array | null>;
476
+ delete(key: string): Promise<void>;
477
+ keys(): Promise<string[]>;
478
+ usage(): Promise<BlobUsage>;
479
+ }
480
+ /** The per-origin outcome of a consent request. */
481
+ export interface ConsentResult {
482
+ /** Origins the user granted (a subset of the requested set). */
483
+ granted: readonly string[];
484
+ /** Origins denied (out-of-allow-list or user-declined). */
485
+ denied: readonly string[];
486
+ /** Whether the grant was remembered for this document (survives reopen). */
487
+ remembered: boolean;
488
+ }
489
+ export interface NetworkSurface {
490
+ /** Request consent to reach `origins` (`scheme://host[:port]`) for a stated
491
+ * `purpose`; the host renders the data-source manifest for review and
492
+ * resolves the per-origin decision. NOTHING fetches before this resolves.
493
+ * Origins outside the declared `capabilities.network` allow-list are denied.
494
+ * Requires `capabilities.network` to be declared (else a capability error). */
495
+ requestConsent(origins: readonly string[], purpose: string): Promise<ConsentResult>;
496
+ /** The currently-granted origins (session grants + remembered) — a bundle
497
+ * gates its own reach on this. */
498
+ consentedOrigins(): readonly string[];
499
+ }
500
+ /** A field of a provider's schema (Arrow-seam shape). */
501
+ export interface ProviderField {
502
+ name: string;
503
+ /** Arrow-aligned logical type: `text|int|float|bool|date|datetime|bytes|null`. */
504
+ ty: string;
505
+ nullable?: boolean;
506
+ }
507
+ /** A provider's schema descriptor (the half `discover` surfaces without rows). */
508
+ export interface ProviderSchema {
509
+ fields: ProviderField[];
510
+ }
511
+ /** The columnar row payload a provider serves (Arrow-aligned): one array per
512
+ * schema field. Opaque to the contract beyond its shape — the consumer maps it
513
+ * to its own model. */
514
+ export interface ProviderRecordSet {
515
+ schema: ProviderSchema;
516
+ columns: unknown[][];
517
+ rowCount: number;
518
+ }
519
+ /** What a provider hands `register`: a discovery descriptor + a LAZY snapshot
520
+ * getter (invoked only when a consumer pulls, in the provider's own realm under
521
+ * the provider's own capability/consent) + the current content revision. */
522
+ export interface DataProviderRegistration {
523
+ id: string;
524
+ category: string;
525
+ schema: ProviderSchema;
526
+ /** An opaque content etag; bump it via the handle when the data changes. */
527
+ revision: string;
528
+ getSnapshot(): Promise<ProviderRecordSet> | ProviderRecordSet;
529
+ }
530
+ /** The handle a provider holds to signal refresh / tear its provider down. */
531
+ export interface DataProviderHandle {
532
+ /** Announce a new revision — consumers subscribed via `onDidChange` re-pull. */
533
+ update(revision: string): void;
534
+ /** Remove the provider; `discover` stops listing it. */
535
+ dispose(): void;
536
+ }
537
+ /** A discovery record (schema + revision, NO rows). */
538
+ export interface DataProviderInfo {
539
+ id: string;
540
+ category: string;
541
+ schema: ProviderSchema;
542
+ revision: string;
543
+ }
544
+ /** A pulled snapshot (the rows). */
545
+ export interface DataProviderSnapshot {
546
+ id: string;
547
+ revision: string;
548
+ records: ProviderRecordSet;
549
+ }
550
+ export interface DataProvidersSurface {
551
+ /** PROVIDER side — register a named provider (gated on
552
+ * `capabilities.dataProviders.publish` ∋ category). Returns a handle to
553
+ * signal refresh / dispose. */
554
+ register(registration: DataProviderRegistration): DataProviderHandle;
555
+ /** CONSUMER side — enumerate providers by category (schema + revision, NO
556
+ * rows). Gated on `capabilities.dataProviders.consume`. Empty when no shared
557
+ * registry is wired (graceful absence). */
558
+ discover(category?: string): readonly DataProviderInfo[];
559
+ /** CONSUMER side — pull a provider's current snapshot, or `null` if it no
560
+ * longer exists. Gated on `consume`. */
561
+ get(id: string): Promise<DataProviderSnapshot | null>;
562
+ /** CONSUMER side — fire when a provider's revision changes; re-pull on your
563
+ * own schedule. Subscribing to an absent id is inert. */
564
+ onDidChange(id: string, listener: (revision: string) => void): Disposable;
565
+ }
174
566
  export interface Diagnostic {
175
567
  severity: "error" | "warning" | "info";
176
568
  message: string;
@@ -188,6 +580,21 @@ export interface DiagnosticsSurface {
188
580
  get(key: string): Diagnostic[];
189
581
  onDidChange(listener: (key: string) => void): Disposable;
190
582
  }
583
+ export interface BindingsSurface {
584
+ /** Publish (or update) a named reactive value. Schema rows reading
585
+ * `{ bind: name }` re-render on every change. JSON only. */
586
+ publish(name: string, value: unknown): void;
587
+ /** Read the current value of a published binding (or `undefined`).
588
+ * The host uses this when first rendering a row; bundles rarely
589
+ * need it. */
590
+ get(name: string): unknown;
591
+ /** Remove a published binding. Rows reading it fall back to the
592
+ * gate's "absent" semantics (visible / enabled). */
593
+ delete(name: string): void;
594
+ /** Subscribe to changes of ANY published binding (the host's render
595
+ * subscription; the argument is the changed name). */
596
+ onDidChange(listener: (name: string) => void): Disposable;
597
+ }
191
598
  /**
192
599
  * What `activate(host)` receives. Types from `@paged-media/plugin-api`,
193
600
  * values from here — never import host values into a bundle's module
@@ -201,10 +608,53 @@ export interface BundleHost {
201
608
  readonly document: DocumentSurface;
202
609
  readonly selection: SelectionSurface;
203
610
  readonly viewport: ViewportSurface;
611
+ /** Font measurement against the document's fonts (S-13). A read door,
612
+ * no capability gate; `supports("text.measure@1")` reports whether the
613
+ * host wired the engine shaper (it is false under a host that injects
614
+ * no measurement backend — the headless harness returns an estimate). */
615
+ readonly text: TextSurface;
204
616
  readonly overlay: OverlaySurface;
205
617
  readonly shell: ShellSurface;
206
618
  readonly storage: StorageSurface;
619
+ /** The capability-gated BINARY blob store (K-4 / S-08): OPFS-backed,
620
+ * per-plugin, quota-bounded bytes for payloads too large for the KV
621
+ * `storage`. Always present; gated on `capabilities.storage` ∋ blob.
622
+ * When the host injects no backend, reads answer null / `[]` /
623
+ * `{used:0,quota:0}`, writes reject, and `supports("storage.blob@1")`
624
+ * is false (the honest no-store door). */
625
+ readonly blob: BlobSurface;
626
+ /** The capability-gated NETWORK CONSENT door (D-03; base-idea §11). Always
627
+ * present; gated on `capabilities.network` and per-origin user consent.
628
+ * When the host injects no consent backend, every request is DENIED (the
629
+ * honest no-consent posture) and `supports("network.consent@1")` is false. */
630
+ readonly network: NetworkSurface;
631
+ /** The cross-plugin DATA-PROVIDER registry (paged.data §7.1 / D-09). A bundle
632
+ * PUBLISHES a resolved dataset (gated on `capabilities.dataProviders.publish`)
633
+ * and/or DISCOVERS + reads others' (gated on `consume`) — the neutral
634
+ * rendezvous, never direct plugin contact. Always present; when the host wires
635
+ * no shared registry, `discover()` is empty + `register()` is a no-op and
636
+ * `supports("dataProviders@1")` is false (the honest no-registry posture). */
637
+ readonly dataProviders: DataProvidersSurface;
207
638
  readonly diagnostics: DiagnosticsSurface;
639
+ /** Published reactive values (W3.1) — the dynamic half of schema
640
+ * panels: a bundle publishes named booleans (and JSON values) that
641
+ * schema rows reference for `visible`/`enabled`. The plugin owns the
642
+ * derivation; the host owns the lookup + re-render. Always present
643
+ * (in-memory store; trivially proxyable across the isolate). */
644
+ readonly bindings: BindingsSurface;
645
+ /** Host-provided panel widgets (W-04): the code editor and future
646
+ * heavy controls the host owns. Always present — a plain-textarea
647
+ * fallback stands in when the host app injects no widget catalog
648
+ * (probe with `host.supports("widgets.codeEditor@1")`). */
649
+ readonly widgets: WidgetSurface;
650
+ /** The capability-gated ASSET STORE (W-06): a READ-ONLY door over the
651
+ * bytes the DOCUMENT already embeds/loads. v1 serves font face bytes
652
+ * (`getFontFace`) so a bundle can compose real `@font-face`. Always
653
+ * present — when the host app injects no asset source, every read
654
+ * answers `null` (the honest no-bytes door) and
655
+ * `supports("assets.fonts@1")` is false. Capability-gated:
656
+ * `getFontFace` requires `capabilities.assets` ∋ `"fonts"`. */
657
+ readonly assets: AssetSurface;
208
658
  /** Capability detection over version sniffing: feature strings of
209
659
  * the form `"area.member@major"` (see HOST_FEATURES in plugin-sdk). */
210
660
  supports(feature: string): boolean;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,8 @@
1
- export type { PluginId, PluginManifest, PluginCapabilities, PluginContributions, } from "./manifest";
1
+ export type { PluginId, PluginManifest, PluginCapabilities, PluginContributions, NetworkCapability, DataProvidersCapability, StorageCapability, WasmArtifact, WasmPurpose, } from "./manifest";
2
2
  export type { BundleHandle, PagedBundle } from "./bundle";
3
- export type { BundleHost, ContributionSurface, DocumentSurface, SelectionSurface, ViewportSurface, OverlaySurface, ShellSurface, StorageSurface, DiagnosticsSurface, Diagnostic, DocumentChangeEvent, MutationOutcome, Disposable, PluginLogger, EditContextDescriptor, ObjectTypeDescriptor, PluginMetadataEnvelope, ObjectTypeBaker, BakeContext, } from "./host";
3
+ export type { BundleHost, ContributionSurface, SceneLayerSurface, DocumentSurface, SelectionSurface, ViewportSurface, TextSurface, TextMetrics, FrameChainLink, OverlaySurface, ShellSurface, FilePickerOptions, PickedFile, StorageSurface, BlobSurface, BlobUsage, NetworkSurface, ConsentResult, DataProvidersSurface, DataProviderRegistration, DataProviderHandle, DataProviderInfo, DataProviderSnapshot, ProviderSchema, ProviderField, ProviderRecordSet, DiagnosticsSurface, BindingsSurface, Diagnostic, DocumentChangeEvent, MutationOutcome, Disposable, PluginLogger, EditContextContribution, ObjectTypeContribution, EditContextCandidate, EnteredEditContext, ContentPointerEvent, EditContextDescriptor, ObjectTypeDescriptor, PluginMetadataEnvelope, ObjectTypeBaker, BakeContext, } from "./host";
4
+ export type { PanelSchema, PanelSchemaSection, PanelSchemaRow, SchemaPanelContribution, SchemaPanelRenderer, SchemaPanelRendererProps, WidgetValueBinding, BindingRef, SchemaGate, } from "./panel-schema";
5
+ export type { WidgetSurface, CodeEditorProps, CodeEditorDiagnostic, CodeEditorLanguage, } from "./widgets";
6
+ export type { AssetSurface, AssetKind, FontFaceAsset, FontFaceFormat, } from "./assets";
4
7
  export type * from "./contributions";
5
8
  export type * from "./mutations";