@paged-media/plugin-api 0.2.42-canary.0 → 0.2.45-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.
@@ -0,0 +1,556 @@
1
+ import type { MutationInput } from "./mutations";
2
+ import type { Disposable, MutationOutcome } from "./host";
3
+ /**
4
+ * One object's address (ADR 131). A SESSION HANDLE, not a durable
5
+ * reference: InDesign renumbers every `Self` on save, so anything stored in
6
+ * a document (a binding target) is a selector over a stable key — a `Name`
7
+ * or an `x-paged:` label — never a raw id.
8
+ *
9
+ * Canonical core forms (what the formatter emits):
10
+ * `doc` · `textFrame:<id>` (and `rectangle:` `oval:` `polygon:`
11
+ * `graphicLine:` `group:`) · `page:<id>` · `page:#<n>` · `spread:<id>` ·
12
+ * `layer:<id>` · `master:<id>` · `story:<id>` ·
13
+ * `storyRange:<id>@<start>..<end>` · `field:<story>@<offset>` ·
14
+ * `table:<id>` · `cell:<table>/<row>,<col>` · `style:<family>/<rest>` ·
15
+ * `swatch:<rest>` · `gradient:<rest>` · `colorGroup:<rest>` ·
16
+ * `numberingList:<rest>` · `guide:<id>` · `condition:<rest>` ·
17
+ * `conditionSet:<rest>` · `section:<id>` · `hyperlink:<id>` ·
18
+ * `textVariable:<id>` (protocol 71) ·
19
+ * `master:<id> > <item address>`
20
+ * Aliases that parse and are never emitted: `frame:<id>`,
21
+ * `story:<id>@a..b`, `textframe`, `rect`, `graphicline`, `storyrange`.
22
+ * Plugin objects: `plugin:<pluginId>/<kind>/<rest>`. Batch handles:
23
+ * `$h:<name>`.
24
+ */
25
+ export type Address = string;
26
+ /** ADR 131 selector: `step *( (" > " | " ") step )`, a step being an
27
+ * address, a kind or `*` followed by predicates — `[key op value]` with
28
+ * `= != ^= *=`, keys `name` `label` `label.<key>` `kind` `layer` `style`
29
+ * or any property path, and `:nth(n)`. */
30
+ export type Selector = string;
31
+ /** A property path: `frameStrokeWeight`, `name`, `points[3].anchor`,
32
+ * `labels[x-paged:oid]`. The reserved path `"content"` reads/writes an
33
+ * object's content (its `ContentSchema`). */
34
+ export type PropertyPathName = string;
35
+ /** The page-item kinds (core's `ElementId` string-id kinds). */
36
+ export type PageItemKind = "textFrame" | "rectangle" | "oval" | "polygon" | "graphicLine" | "group";
37
+ /** Style families a `style:` address names (= core's `StyleCollection`). */
38
+ export type StyleFamily = "paragraph" | "character" | "object" | "cell" | "table";
39
+ /** A parsed address — what the SDK's `parseAddress` returns and
40
+ * `formatAddress` takes. Plain data. */
41
+ export type ParsedAddress = {
42
+ kind: "doc";
43
+ } | {
44
+ kind: "item";
45
+ itemKind: PageItemKind;
46
+ id: string;
47
+ }
48
+ /** `frame:<id>` — kind-agnostic; the resolver finds the item kind. */
49
+ | {
50
+ kind: "frame";
51
+ id: string;
52
+ } | {
53
+ kind: "page";
54
+ id: string;
55
+ }
56
+ /** `page:#<n>` — the n-th page, 1-based, document order. */
57
+ | {
58
+ kind: "pageIndex";
59
+ index: number;
60
+ } | {
61
+ kind: "spread";
62
+ id: string;
63
+ } | {
64
+ kind: "layer";
65
+ id: string;
66
+ } | {
67
+ kind: "master";
68
+ id: string;
69
+ } | {
70
+ kind: "story";
71
+ id: string;
72
+ } | {
73
+ kind: "storyRange";
74
+ story: string;
75
+ start: number;
76
+ end: number;
77
+ } | {
78
+ kind: "field";
79
+ story: string;
80
+ offset: number;
81
+ } | {
82
+ kind: "table";
83
+ id: string;
84
+ } | {
85
+ kind: "cell";
86
+ table: string;
87
+ row: number;
88
+ col: number;
89
+ } | {
90
+ kind: "style";
91
+ family: StyleFamily;
92
+ name: string;
93
+ } | {
94
+ kind: "swatch" | "gradient" | "colorGroup" | "numberingList" | "condition" | "conditionSet";
95
+ name: string;
96
+ } | {
97
+ kind: "guide" | "section" | "hyperlink" | "textVariable";
98
+ id: string;
99
+ } | {
100
+ kind: "onMaster";
101
+ master: string;
102
+ item: ParsedAddress;
103
+ } | {
104
+ kind: "plugin";
105
+ plugin: string;
106
+ objectKind: string;
107
+ id: string;
108
+ } | {
109
+ kind: "handle";
110
+ name: string;
111
+ };
112
+ /** The ADR 131 address kind of a parsed address (`ParsedAddress` folds the
113
+ * six item kinds into `item` and the page index into `pageIndex`). */
114
+ export type AddressKind = ParsedAddress["kind"];
115
+ /** ADR 132's value types, mirrored verbatim. Value shapes:
116
+ * bool → boolean · number → number (in `unit`) · length → number (pt;
117
+ * unit strings like "3mm" coerce) · color → a swatch ref (`Color/Red`)
118
+ * · enum → one of `members` · text → string · ref → an address or the
119
+ * target's self id · bounds → [top,left,bottom,right] · transform →
120
+ * [a,b,c,d,tx,ty] · point → [x,y] · bytes → Uint8Array · asset → a
121
+ * `data:<mime>;base64,<payload>` URI or bare base64 (core reads it back
122
+ * as a `data:` URI typed by the bytes' magic; `host.objects` also takes
123
+ * a `Uint8Array` / `ArrayBuffer` and encodes it) · list → array · map →
124
+ * record keyed by string · struct → record. */
125
+ export type ValueType = {
126
+ kind: "bool";
127
+ } | {
128
+ kind: "number";
129
+ integer?: boolean;
130
+ unit?: "pt" | "deg" | "percent" | "em1000" | "ppi";
131
+ } | {
132
+ kind: "length";
133
+ } | {
134
+ kind: "color";
135
+ } | {
136
+ kind: "enum";
137
+ members: readonly string[];
138
+ } | {
139
+ kind: "text";
140
+ multiline?: boolean;
141
+ maxLength?: number;
142
+ } | {
143
+ kind: "ref";
144
+ to: string;
145
+ } | {
146
+ kind: "bounds";
147
+ } | {
148
+ kind: "transform";
149
+ } | {
150
+ kind: "point";
151
+ } | {
152
+ kind: "bytes";
153
+ mime?: readonly string[];
154
+ }
155
+ /** `shape` names the encoding core expects (`data:<mime>;base64,<payload>`). */
156
+ | {
157
+ kind: "asset";
158
+ shape?: string;
159
+ } | {
160
+ kind: "list";
161
+ of: ValueType;
162
+ maxItems?: number;
163
+ } | {
164
+ kind: "map";
165
+ of: ValueType;
166
+ keyPattern?: string;
167
+ }
168
+ /** `shape` names a core wire struct (`SwatchColorSpec`, `TabStopSpec`);
169
+ * `open: true` accepts fields `fields` does not list — core's catalog
170
+ * (protocol 71) names the shape but not its fields, and the engine
171
+ * validates them. */
172
+ | {
173
+ kind: "struct";
174
+ fields: Readonly<Record<string, StructField>>;
175
+ shape?: string;
176
+ open?: true;
177
+ };
178
+ export type ValueTypeKind = ValueType["kind"];
179
+ /**
180
+ * One field of a `struct`: a value type, plus whether a record may omit it.
181
+ * · `optional: true` — the field may be absent; nothing is filled in.
182
+ * · `default` — the field may be absent; `host.objects.invoke` (and a
183
+ * struct-typed `set`) fill the default in before the handler or the
184
+ * plugin sees the value. The default must satisfy the field's type
185
+ * (checked at registration).
186
+ * A field with neither is required.
187
+ */
188
+ export type StructField = ValueType & {
189
+ optional?: true;
190
+ default?: unknown;
191
+ };
192
+ /** ADR 132's schema ROW (the JSON shape the core catalog publishes, and
193
+ * the one plugins publish). One row per (address kind, path). */
194
+ export interface PropertySchema {
195
+ /** ADR 131 kind (`frame`, `swatch`, …); plugin rows: `plugin:<id>/<kind>`
196
+ * (the registry fills it in when a kind omits it). */
197
+ addressKind?: string;
198
+ /** Item kinds the row applies to; omitted = all. */
199
+ appliesTo?: readonly string[];
200
+ path: PropertyPathName;
201
+ type: ValueType;
202
+ /** `null` clears / inherits. */
203
+ nullable?: boolean;
204
+ range?: {
205
+ min?: number;
206
+ max?: number;
207
+ step?: number;
208
+ };
209
+ default?: unknown;
210
+ /** Default `readWrite`. `derived` is computed from other paths. */
211
+ access?: "readWrite" | "readOnly" | "derived";
212
+ /** Core only: the dedicated op a virtual path resolves to; `clearOp`
213
+ * names the op a `null` write resolves to when clearing is a DIFFERENT
214
+ * op (`nextTextFrame` → `unlinkFrames`, `opacityMask` →
215
+ * `releaseOpacityMask`). */
216
+ virtual?: {
217
+ op: string;
218
+ field?: string;
219
+ clearOp?: string;
220
+ } | null;
221
+ idml?: {
222
+ element: string;
223
+ attribute: string;
224
+ } | null;
225
+ /** Default: `access === "readWrite"`. */
226
+ bindable?: boolean;
227
+ since?: number;
228
+ summary?: string;
229
+ title?: string;
230
+ /** An expensive read (pixels, a whole sheet, story text): `getAll`
231
+ * skips it unless asked (`getAll(address, { content: true })`). The
232
+ * `content` row is always heavy. */
233
+ heavy?: boolean;
234
+ }
235
+ /** What an object's CONTENT is; drives content ops and `get … --content`. */
236
+ export interface ContentSchema {
237
+ kind: "text" | "rows" | "cells" | "pixels" | "vector" | "html" | "blob";
238
+ mime?: readonly string[];
239
+ }
240
+ /** Why a call was refused. Expected failures are results, never throws. */
241
+ export type ObjectErrorCode = "badAddress" | "unknownAddress" | "unknownPath" | "unknownCommand" | "invalidValue" | "readOnly"
242
+ /** In the grammar, but no backend routes it yet (core forms whose write
243
+ * lands with core's `Set`, ADR 132). */
244
+ | "unmapped" | "denied" | "failed";
245
+ /** One read (ADR 323). `refused` is this SDK's addition: an unknown
246
+ * address, a bad path or an unmapped kind answer a typed reason rather
247
+ * than an `absent` that would read as "the path does not apply". */
248
+ export type ObjectValue = {
249
+ kind: "value";
250
+ value: unknown;
251
+ } | {
252
+ kind: "mixed";
253
+ } | {
254
+ kind: "absent";
255
+ } | {
256
+ kind: "refused";
257
+ code: ObjectErrorCode;
258
+ reason: string;
259
+ };
260
+ /** One op of a batch (ADR 323). */
261
+ export type ObjectOp = {
262
+ op: "set";
263
+ address: Address;
264
+ path: PropertyPathName;
265
+ value: unknown;
266
+ } | {
267
+ op: "create";
268
+ kind: string;
269
+ within?: Address;
270
+ props?: Record<string, unknown>;
271
+ handle?: string;
272
+ } | {
273
+ op: "delete";
274
+ address: Address;
275
+ } | {
276
+ op: "invoke";
277
+ command: string;
278
+ args: unknown;
279
+ };
280
+ /**
281
+ * An edit of a LIST value (ADR 323 property rows; DESIGN.md §12.8): what a
282
+ * host's list field does when the user adds, removes, reorders or edits
283
+ * one item. It is not a new batch op: `planValueEdit` / `editValue`
284
+ * (plugin-sdk) read the current value and LOWER the edit to plain `set`
285
+ * ops — the whole new list on the list's own path, or, when the kind only
286
+ * types its items (`path[].field` rows, no `path` row), `set path[i].field`
287
+ * — so it lands as ONE batch = ONE undo step, through the same validation
288
+ * as any write, and every planner keeps receiving `set` / `create` /
289
+ * `delete` only.
290
+ * · `add` — insert `item` (absent: the item type's default) at `index`
291
+ * (absent: the end).
292
+ * · `remove` — drop the item at `index`.
293
+ * · `move` — move the item at `from` so it ends up at `to`.
294
+ * · `itemSet` — replace the item at `index`, or with `field` one field of
295
+ * a struct item.
296
+ * Indices are positions in the VALUE (a stack painted bottom → top is
297
+ * still indexed bottom-first, whatever order the UI lists it in).
298
+ */
299
+ export type ListEdit = {
300
+ edit: "add";
301
+ index?: number;
302
+ item?: unknown;
303
+ } | {
304
+ edit: "remove";
305
+ index: number;
306
+ } | {
307
+ edit: "move";
308
+ from: number;
309
+ to: number;
310
+ } | {
311
+ edit: "itemSet";
312
+ index: number;
313
+ field?: string;
314
+ value: unknown;
315
+ };
316
+ /** An edit of one field of a STRUCT value (a gradient stop, an anchor
317
+ * point): lowered like {@link ListEdit} to a `set` of the whole struct,
318
+ * or `set path.field` when the kind types the field as its own row. */
319
+ export type StructEdit = {
320
+ edit: "fieldSet";
321
+ field: string;
322
+ value: unknown;
323
+ };
324
+ /** One edit of a list or struct value. */
325
+ export type ValueEdit = ListEdit | StructEdit;
326
+ /** A container part change, in the WRITER's own `paged/<plugin-id>/`
327
+ * namespace (relative path; `bytes: null` deletes). */
328
+ export interface PartChange {
329
+ path: string;
330
+ bytes: Uint8Array | null;
331
+ }
332
+ /**
333
+ * What a plugin write produces; the host commits it as ONE undo step.
334
+ * · `mutations` — core-backed state: folded into the batch.
335
+ * · `state` — plugin-backed state (the label-hash pattern): the parts
336
+ * are written first (content-addressed, never overwritten), then the
337
+ * plugin's label is set on `host` INSIDE the batch. Undo reverts the
338
+ * label; the plugin reloads the state the label points back to.
339
+ *
340
+ * `host` is a page item, a story (range), a page (`page:<id>`,
341
+ * `page:#n` — protocol 70 `setPageMetadata`) or the document (`doc` —
342
+ * `setDocumentMetadata`).
343
+ *
344
+ * Core holds ONE label per plugin per host, under exactly
345
+ * `x-paged:<pluginId>`, whose value is the envelope
346
+ * `{"v": <int >= 1>, "data": {…}}` (≤ 64 KiB). So `labelKey` is either
347
+ * - `x-paged:<pluginId>` — `labelValue` IS that whole envelope
348
+ * (JSON text), and replaces the label; or
349
+ * - `x-paged:<pluginId>.<sub>` — a SUB-KEY: the registry reads the
350
+ * current envelope, sets `data[<sub>]` to `labelValue` (parsed
351
+ * when it is JSON text, else kept as the string) and writes the
352
+ * merged envelope back. Several sub-keys on one host — in one
353
+ * write or across writes of one batch — merge into one label.
354
+ * Any other key is refused before anything applies.
355
+ * · `both` — the two together.
356
+ * · `rejected` — a typed no.
357
+ *
358
+ * `created` — the address of each object the write's `create` ops make,
359
+ * ONE per `create` op the planner received, in the order received (the
360
+ * registry splices them into `ObjectWriteResult.created` at the ops'
361
+ * batch positions). When the engine mints the id, write the placeholder
362
+ * `$h:<handle>` (the `bindCreated` handle of that insert) or `$created`
363
+ * (the batch's `createdId`) inside the address; the registry replaces it
364
+ * with the minted element's id after the commit. Omitted: each of those
365
+ * creates answers `""`. A list of the wrong length is refused (`failed`)
366
+ * before anything applies.
367
+ */
368
+ export type ObjectWrite = {
369
+ kind: "mutations";
370
+ mutations: readonly MutationInput[];
371
+ created?: readonly Address[];
372
+ } | {
373
+ kind: "state";
374
+ parts: readonly PartChange[];
375
+ host: Address;
376
+ labelKey: string;
377
+ labelValue: string;
378
+ created?: readonly Address[];
379
+ } | {
380
+ kind: "both";
381
+ mutations: readonly MutationInput[];
382
+ parts: readonly PartChange[];
383
+ host: Address;
384
+ labelKey: string;
385
+ labelValue: string;
386
+ created?: readonly Address[];
387
+ } | {
388
+ kind: "rejected";
389
+ reason: string;
390
+ };
391
+ /** What a kind's `list` receives. The registry ALSO post-filters the
392
+ * selector's predicates by reading paths through `get`, so honouring
393
+ * `predicates` in `list` is an optimisation, never a duty. */
394
+ export interface ObjectQuery {
395
+ /** The selector step that selected this kind (`plugin:x/layer[name="a"]`). */
396
+ selector?: Selector;
397
+ kind?: string;
398
+ /** The container the selector scoped the step to (`scope > kind`):
399
+ * a page item or story (a host), or one of this plugin's own
400
+ * addresses (`plugin:x/image/1 > plugin:x/layer`). Container scopes
401
+ * (`doc`, `page:`, `spread:`, `master:`, `layer:`) never reach `list`:
402
+ * the registry lists unscoped and keeps the objects whose `hostOf` lies
403
+ * in the container. */
404
+ within?: Address;
405
+ limit?: number;
406
+ /** The step's parsed predicates (this SDK's addition to ADR 323). */
407
+ predicates?: readonly ObjectPredicate[];
408
+ }
409
+ export interface ObjectPredicate {
410
+ key: string;
411
+ op: "=" | "!=" | "^=" | "*=";
412
+ value: string;
413
+ }
414
+ /** One object KIND a plugin contributes (ADR 323). Headless; live for the
415
+ * whole session; declared under `contributes.objectModel.kinds[]`. */
416
+ export interface ObjectKindContribution {
417
+ /** Plugin-local kind name (`layer`, `cell`). Addresses are
418
+ * `plugin:<manifest.id>/<kind>/<id>`. */
419
+ kind: string;
420
+ title: string;
421
+ /** At least one row; validated at registration. */
422
+ schema: readonly PropertySchema[];
423
+ content?: ContentSchema;
424
+ /** The core element hosting an object (frame / scene layer / page /
425
+ * `doc`): where its labels live (ADR 559), and how the registry scopes
426
+ * `page:x > plugin:…` selectors. May answer asynchronously. */
427
+ hostOf(address: Address): Address | null | Promise<Address | null>;
428
+ list(query: ObjectQuery): readonly Address[] | Promise<readonly Address[]>;
429
+ get(address: Address, path: PropertyPathName): ObjectValue | Promise<ObjectValue>;
430
+ set?(address: Address, path: PropertyPathName, value: unknown): ObjectWrite | Promise<ObjectWrite>;
431
+ /** Preferred over repeated `set`: many ops, one write, one undo step.
432
+ * Receives this kind's `set` / `create` / `delete` ops. Ignored when
433
+ * the contribution has a plugin-level `batch`. */
434
+ batch?(ops: readonly ObjectOp[]): ObjectWrite | Promise<ObjectWrite>;
435
+ }
436
+ /**
437
+ * A TYPED command (ADR 323): its args and result carry value types, so the
438
+ * CLI, Boa and a generated form can call it without knowing the plugin.
439
+ * `host.objects.invoke` validates `args` first. The untyped
440
+ * `contribute.command` stays for the migration (a shrink-only list).
441
+ */
442
+ export interface TypedCommandContribution<A = unknown, R = unknown> {
443
+ id: string;
444
+ title: string;
445
+ /** Usually `{ kind: "struct", fields }`. */
446
+ args: ValueType;
447
+ result?: ValueType;
448
+ handler(ctx: {
449
+ objects: ObjectsSurface;
450
+ }, args: A): R | Promise<R>;
451
+ }
452
+ export interface ObjectModelContribution {
453
+ kinds: readonly ObjectKindContribution[];
454
+ commands?: readonly TypedCommandContribution[];
455
+ /**
456
+ * PLUGIN-level planner: receives every `set` / `create` / `delete` op of
457
+ * a batch that targets ANY of this plugin's kinds, in batch order, and
458
+ * answers ONE write. Use it whenever two kinds share state (an image's
459
+ * layers and adjustments live in one stack): planned per kind, each
460
+ * kind would start from the same state and the second write would drop
461
+ * the first one's changes. When present, the kinds' own `batch`/`set`
462
+ * are not used for writes. Without it, ops are planned per kind, and
463
+ * two per-kind `state` writes landing on the same host are refused
464
+ * (`failed`, "conflicting state writes") rather than silently merged.
465
+ */
466
+ batch?(ops: readonly ObjectOp[]): ObjectWrite | Promise<ObjectWrite>;
467
+ }
468
+ /** What `contribute.objectModel` returns: a `Disposable` (ADR 323) that
469
+ * can also announce changes the engine does not see (wasm-held state). */
470
+ export interface ObjectModelHandle extends Disposable {
471
+ /** No argument = every object of this plugin may have changed. */
472
+ changed(addresses?: readonly Address[]): void;
473
+ }
474
+ export interface ObjectWriteResult {
475
+ applied: boolean;
476
+ reason?: string;
477
+ /** Engine undo steps the write took: a batch is ONE. */
478
+ undoSteps: 0 | 1;
479
+ /** This SDK's additions: the refusal code and the failing op. */
480
+ code?: ObjectErrorCode;
481
+ index?: number;
482
+ /** The engine outcome (minted ids for `create`/`$h:`). */
483
+ outcome?: MutationOutcome;
484
+ /** The address of every object a `create` op made, in op order (this
485
+ * SDK's addition; a core create needs engine protocol 71). */
486
+ created?: readonly Address[];
487
+ /** One entry per typed CORE command (`invoke` of `core.<op>`) the batch
488
+ * ran, in op order: its op index and the addresses of what it created
489
+ * (this SDK's addition, DESIGN.md §21.12). */
490
+ commands?: readonly ObjectCommandResult[];
491
+ }
492
+ /** What one typed core command of a batch made. */
493
+ export interface ObjectCommandResult {
494
+ index: number;
495
+ command: string;
496
+ created: readonly Address[];
497
+ }
498
+ export interface ObjectChange {
499
+ /** Empty = "anything may have changed" (core reports page-level damage
500
+ * today, not addresses). */
501
+ addresses: readonly Address[];
502
+ paths?: readonly string[];
503
+ origin: "core" | string;
504
+ }
505
+ export interface ObjectKindInfo {
506
+ owner: "core" | string;
507
+ /** Qualified: core kinds bare (`frame`), plugin kinds
508
+ * `plugin:<pluginId>/<kind>`. */
509
+ kind: string;
510
+ title: string;
511
+ /** The address form, e.g. `plugin:acme.notes/note/<id>`. */
512
+ addressForm: string;
513
+ }
514
+ export interface ObjectCommandInfo {
515
+ id: string;
516
+ title: string;
517
+ owner: string;
518
+ args: ValueType;
519
+ result?: ValueType;
520
+ }
521
+ /** `host.objects` — the one registry over core and every plugin. */
522
+ export interface ObjectsSurface {
523
+ query(selector: Selector, opts?: {
524
+ limit?: number;
525
+ }): Promise<Address[]>;
526
+ get(address: Address, path: PropertyPathName): Promise<ObjectValue>;
527
+ /** Every schema path of the object, read — except `heavy` rows (and
528
+ * `content`), which are read only with `{ content: true }`. */
529
+ getAll(address: Address, opts?: {
530
+ content?: boolean;
531
+ }): Promise<Record<string, ObjectValue>>;
532
+ set(address: Address, path: PropertyPathName, value: unknown, opts?: {
533
+ label?: string;
534
+ }): Promise<ObjectWriteResult>;
535
+ /** All-or-nothing: validated and planned before anything applies; core
536
+ * ops and plugin plans land as ONE engine batch = one undo step. */
537
+ batch(ops: readonly ObjectOp[], opts?: {
538
+ label?: string;
539
+ }): Promise<ObjectWriteResult>;
540
+ /** The rows of an address's kind, or of a qualified kind name. Empty
541
+ * when the kind is unknown. */
542
+ schema(addressOrKind: Address | string): Promise<readonly PropertySchema[]>;
543
+ kinds(): Promise<readonly ObjectKindInfo[]>;
544
+ /** Typed commands contributed through `contribute.objectModel`. */
545
+ commands(): Promise<readonly ObjectCommandInfo[]>;
546
+ /** Run a typed command. Rejects with an `Error` whose message starts
547
+ * with the refusal code (`unknownCommand: …`, `invalidValue: …`).
548
+ * A CORE command (`core.<op>`, listed by `commands()` with owner
549
+ * `"core"`) runs as ONE batch and answers `{ created, undoSteps }`;
550
+ * it also joins `batch` as an `invoke` op (one undo step with the
551
+ * batch's sets / creates / deletes). */
552
+ invoke(command: string, args: unknown): Promise<unknown>;
553
+ onDidChange(listener: (e: ObjectChange) => void, filter?: {
554
+ selector?: Selector;
555
+ }): Disposable;
556
+ }
@@ -1,5 +1,6 @@
1
1
  import type { ComponentType } from "react";
2
2
  import type { BindingsSurface } from "./host";
3
+ import type { Address, PropertyPathName, Selector } from "./object-model";
3
4
  import type { PropertyPath, Value } from "./wire";
4
5
  export type WidgetValueBinding = {
5
6
  kind: "literal";
@@ -90,7 +91,113 @@ export interface SchemaListSpec {
90
91
  /** Per-row action buttons. */
91
92
  actions?: SchemaListAction[];
92
93
  }
93
- export interface PanelSchemaRow {
94
+ /**
95
+ * Which object(s) a property row edits.
96
+ * · `"selection"` — the selected objects. With `kind` set, the objects of
97
+ * that kind HOSTED by each selected object (`<selected> > <kind>`):
98
+ * the web frame of a selected text frame, the layers of a selected
99
+ * image frame.
100
+ * · a selector string (ADR 131) — every address it selects, re-resolved
101
+ * when the object model changes. Also the durable "Bind to data…"
102
+ * target, so prefer a name/label selector over a raw session address.
103
+ * · `{ bind }` — an address, or a list of addresses, the plugin publishes
104
+ * through `host.bindings.publish(name, …)` (its active layer, the cell
105
+ * under the cursor). Absent / empty = no target (the field disables).
106
+ * · `{ addresses }` — explicit addresses, as given.
107
+ */
108
+ export type PropertyFieldAddress = "selection" | Selector | {
109
+ bind: string;
110
+ } | {
111
+ addresses: readonly Address[];
112
+ };
113
+ /** How a property row presents an `enum` (or a text row offering a
114
+ * collection): the host picks from the schema row otherwise. */
115
+ export type PropertyFieldStyle = "select" | "segments" | "collection";
116
+ /** One option override for a `select` / `segments` presentation (labels
117
+ * for enum members; the member list still comes from the schema). */
118
+ export interface PropertyFieldOption {
119
+ value: string;
120
+ label?: string;
121
+ icon?: string;
122
+ }
123
+ /** One sub-field a property row shows of a STRUCT value (or of each
124
+ * struct item of a list): `field` is the struct field's name; the widget
125
+ * comes from that field's value type, like a row's comes from its schema
126
+ * row. A bare string is `{ field }`. */
127
+ export interface PropertySubField {
128
+ field: string;
129
+ label?: string;
130
+ style?: PropertyFieldStyle;
131
+ options?: readonly PropertyFieldOption[];
132
+ /** Leaf chrome passthrough. */
133
+ props?: Record<string, unknown>;
134
+ }
135
+ /**
136
+ * How a property row presents a LIST value (`{ kind: "list", of }`): an
137
+ * appearance stack's fills, a gradient's stops. Every edit the host
138
+ * offers is a {@link ListEdit} the SDK lowers to `set` ops (one batch, one
139
+ * undo step) — `planValueEdit` / `editValue` in plugin-sdk.
140
+ */
141
+ export interface PropertyListPresentation {
142
+ /** Struct items: the fields each item shows, in order. Absent = every
143
+ * declared field of the item struct. Scalar items show one widget. */
144
+ fields?: readonly (string | PropertySubField)[];
145
+ /** The item field whose value titles an item (`name`, `stopColor`). */
146
+ itemLabel?: string;
147
+ /** Offer add / remove / reorder. Each defaults to `true` on a
148
+ * read-write row; `add` also stops at the type's `maxItems`. */
149
+ add?: boolean;
150
+ remove?: boolean;
151
+ reorder?: boolean;
152
+ /** List the LAST item first (a stack painted bottom → top reads
153
+ * top-first, like a layers panel). Indices in the edits stay value
154
+ * indices. Default `false`. */
155
+ reversed?: boolean;
156
+ /** The item `add` inserts. Absent = the item type's default
157
+ * (`defaultValueOf`: struct field defaults, the first enum member,
158
+ * 0, "", false, []). Must satisfy the item type. */
159
+ newItem?: unknown;
160
+ }
161
+ /** The property row (the property row). Mutually exclusive with `widget`: a
162
+ * row is EITHER a catalog widget OR an object-model property field. */
163
+ export interface PanelSchemaPropertyRow {
164
+ field: "property";
165
+ /** The object(s) the field edits — see {@link PropertyFieldAddress}. */
166
+ address: PropertyFieldAddress;
167
+ /** The ADR 132 path on the target's kind (`opacity`, `blendMode`,
168
+ * `points[0].anchor`). */
169
+ path: PropertyPathName;
170
+ /** The qualified kind (`plugin:<id>/<kind>`, or a core kind) whose
171
+ * schema row types the field. Required with `address: "selection"`
172
+ * for plugin objects (it scopes the selection to that kind); otherwise
173
+ * it lets the field show its widget before any target resolves. */
174
+ kind?: string;
175
+ /** Row label (sentence case). Absent = the schema row's `title`, then
176
+ * the path. */
177
+ label?: string;
178
+ style?: PropertyFieldStyle;
179
+ options?: readonly PropertyFieldOption[];
180
+ /** Offer "Bind to data…" (default: when the schema row is bindable). */
181
+ bindable?: boolean;
182
+ /** A STRUCT-typed path: the fields to show, in order (absent = every
183
+ * declared field; an `open` struct with no `fields` shows none and
184
+ * falls back to its JSON text). Edits are {@link StructEdit}s. */
185
+ fields?: readonly (string | PropertySubField)[];
186
+ /** A LIST-typed path: how its items present and which edits are
187
+ * offered. Absent = the defaults of {@link PropertyListPresentation}. */
188
+ items?: PropertyListPresentation;
189
+ /** Leaf chrome passthrough (placeholder, prefix, suffix, icon, …). */
190
+ props?: Record<string, unknown>;
191
+ visible?: SchemaGate;
192
+ enabled?: SchemaGate;
193
+ /** Never set on a property row (keeps `row.widget` readable on the
194
+ * union). */
195
+ widget?: undefined;
196
+ value?: undefined;
197
+ list?: undefined;
198
+ }
199
+ /** The catalog-widget row (schema v1 / v1.1). */
200
+ export interface PanelSchemaWidgetRow {
94
201
  /** Catalog widget id (a curated primitive-leaf id the host
95
202
  * registered). An unknown id renders a visible "unknown widget"
96
203
  * placeholder, not a throw. */
@@ -112,7 +219,12 @@ export interface PanelSchemaRow {
112
219
  * always enabled (subject to the widget's own no-write-path
113
220
  * disable). */
114
221
  enabled?: SchemaGate;
222
+ /** Never set on a widget row. */
223
+ field?: undefined;
115
224
  }
225
+ /** One panel row: a catalog widget, or an object-model property
226
+ * field. Discriminate with `isPropertyRow` (plugin-sdk) or `row.field`. */
227
+ export type PanelSchemaRow = PanelSchemaWidgetRow | PanelSchemaPropertyRow;
116
228
  export interface PanelSchemaSection {
117
229
  /** Section title (the kicker / disclosure header). Absent = an
118
230
  * untitled flat group. */