@texuli/spec 0.6.0 → 0.8.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/README.md CHANGED
@@ -14,9 +14,23 @@ by Texuli's Kotlin Multiplatform twin. The package is framework-free: no
14
14
  DOM, no Svelte, no Node APIs and no runtime dependencies.
15
15
 
16
16
  > **Status: 0.x release.** Texuli is in development. The packages are
17
- > published to npm as `0.6.0`, a pre-1.0 release (1.0.0 will be the first
18
- > stable release). Under 0.x, a new minor (`0.7.0`) may contain breaking
19
- > changes, so pin the exact version or use `^0.6.0`, which accepts only 0.6.x.
17
+ > published to npm as `0.8.0`, a pre-1.0 release (1.0.0 will be the first
18
+ > stable release). Under 0.x, a new minor (`0.9.0`) may contain breaking
19
+ > changes, so pin the exact version or use `^0.8.0`, which accepts only 0.8.x.
20
+
21
+ > **New in `0.8.0`.** `@texuli/spec@0.8.0` carries **spec 0.8.0**, which adds
22
+ > data-source bindings (the `@texuli/spec/source` subpath), the
23
+ > agent-emittable `schema-form`, `canvas`, `spreadsheet`, `data-dashboard`
24
+ > and `data-dashboard-widget` ids with their closed shapes, the closed
25
+ > expression AST, and the pinned `schemas/canvas.schema.json`. Every
26
+ > `0.7.0` envelope migrates by stamping `spec`.
27
+
28
+ > **New in `0.7.0`.** `@texuli/spec@0.7.0` carries **spec 0.7.0**, which adds
29
+ > an optional node `key` (unique in the tree), the agent-emittable
30
+ > `document` id with its `$defs.document` shape, the `patchDocument` edit
31
+ > op with a bounded RFC 6902 patch, and the pinned
32
+ > `schemas/document.schema.json`. Every `0.6.0` envelope migrates by
33
+ > stamping `spec`.
20
34
 
21
35
  > **New in `0.6.0`.** `@texuli/spec@0.6.0` carries **spec 0.6.0**: the
22
36
  > closed motion vocabulary (`@texuli/spec/motion`, below), and the new
@@ -61,7 +75,7 @@ import {
61
75
  validateInstance,
62
76
  } from '@texuli/spec';
63
77
 
64
- SPEC_VERSION; // the spec version: '0.6.0' in @texuli/spec@0.6.0
78
+ SPEC_VERSION; // the spec version: '0.8.0' in @texuli/spec@0.8.0
65
79
  SPEC_COMPONENT_IDS.includes('alert'); // true: the closed component vocabulary
66
80
 
67
81
  // The dialect validator: a closed subset of JSON Schema 2020-12.
@@ -93,7 +107,7 @@ import { type Schema, type SpecCatalog, validateBlueprint } from '@texuli/spec';
93
107
  import { migrateBlueprint } from '@texuli/spec/migrate';
94
108
  import { validateWorkflowGraph } from '@texuli/spec/workflow';
95
109
 
96
- const migrated = migrateBlueprint(blueprint); // 0.2.0 → … → 0.5.0 → 0.6.0; never throws
110
+ const migrated = migrateBlueprint(blueprint); // 0.2.0 → … → 0.7.0 → 0.8.0; never throws
97
111
  const result = validateBlueprint(migrated.blueprint, {
98
112
  blueprintSchema: blueprintSchemaJson as Schema,
99
113
  catalog: componentsJson as SpecCatalog,
@@ -114,7 +128,11 @@ refused state or flow declaration), `unknown-state` (a binding or action
114
128
  names no declared variable), `binding` (a malformed, misplaced, read-only or
115
129
  ill-fitting binding), `action` (an action with the wrong fields or target),
116
130
  `unknown-event` (an `on` key the component does not fire) and `workflow` (a
117
- workflow graph rule). Deprecated components and props add a `deprecated`
131
+ workflow graph rule), from spec 0.7.0, `duplicate-key` (a node key used
132
+ twice in the tree), and, from spec 0.8.0, `source` (a malformed, misplaced or
133
+ ill-fitting data-source binding, or any binding when the host passes no
134
+ `sources`) and `unknown-source` (a source the host never registered).
135
+ Deprecated components and props add a `deprecated`
118
136
  warning without failing.
119
137
 
120
138
  ## What's inside
@@ -127,6 +145,7 @@ warning without failing.
127
145
  | `@texuli/spec/surface` | Runtime: the state surface rules every validator shares (no imports) | `checkStateSurface`, `surfaceInputs`, `SPEC_CONTROL_STATE_TYPES`, `SURFACE_PLAYER_CODES`, `LIST_TEXT_PATTERN` |
128
146
  | `@texuli/spec/motion` | Runtime (new in `0.6.0`, spec `0.6.0`): the closed motion vocabulary and its check, shared by the catalog and the player ([motion.md](https://github.com/DailybotHQ/texuli/blob/main/packages/spec/docs/motion.md)) | `SPEC_MOTION_PRESETS`, `SPEC_MOTION_DURATIONS`, `SPEC_MOTION_STAGGERS`, `SPEC_MOTION_LEVELS`, `checkNodeMotion`, `checkEnvelopeMotion` |
129
147
  | `@texuli/spec/stream` | Runtime, opt-in (new in `0.6.0`): streamed blueprints ([stream.md](https://github.com/DailybotHQ/texuli/blob/main/packages/spec/docs/stream.md)) | `createJsonTokenizer`, `createBlueprintAssembler`, `createBlueprintStream`, `STREAM_NODE_ORDER` |
148
+ | `@texuli/spec/source` | Runtime (new in `0.8.0`, spec `0.8.0`): data-source bindings, shared by the spec validator, `@texuli/catalog` and the player ([source.md](https://github.com/DailybotHQ/texuli/blob/main/packages/spec/docs/source.md)) | `checkSourceBinding`, `isSourceBinding`, `SOURCE_BINDING_KEY`, `SPEC_SOURCE_LIMITS`, `SPEC_SOURCE_FILTER_OPERATORS`, `SpecSourceDescriptor` |
130
149
  | `@texuli/spec/codegen` | Build time: generation and conformance | `buildSpec`, `projectCatalog`, `checkConformance`, `checkCatalogContract`, `formatJson`, `nextSpecVersion`, `checkVersionHistory` |
131
150
 
132
151
  ## Key guarantees
@@ -162,6 +181,12 @@ warning without failing.
162
181
  motion tokens: a blueprint never carries CSS, keyframes, milliseconds or
163
182
  code, and anything else is refused. The `0.5.0 → 0.6.0` migration only
164
183
  stamps `spec`.
184
+ - **Documents as data (spec 0.7.0, new in `0.7.0`).** A
185
+ `document` node's `doc` must have the document shape (`$defs.document`);
186
+ the document's own vocabulary and URL policy are checked by
187
+ `@texuli/document` through `@texuli/catalog`'s `validateDraft`. Agents
188
+ edit documents with `patchDocument` (at most 1,000 RFC 6902 operations),
189
+ addressed by node `key`.
165
190
  - **Streams parse, never guess.** `@texuli/spec/stream` (new in `0.6.0`)
166
191
  reads a blueprint split anywhere in linear time under a byte budget and a
167
192
  depth cap, reports a node only once its shell (everything but
@@ -181,6 +206,44 @@ warning without failing.
181
206
  nodes without a matching view or event, and writers of a host-bound
182
207
  variable. The spec, `@texuli/catalog`'s `validateDraft` and the Kotlin
183
208
  twin run the same rules.
209
+ - **Data as a named source, never a request (spec 0.8.0, new in `0.8.0`).** A
210
+ component's `sourceable` prop may read rows with
211
+ `{"$source": name, "query": {...}, "map": {...}}`: an opaque name the host
212
+ registered, and a closed query (projection, the `@texuli/data` grid filter
213
+ model, sort, search, a page size). It is checked against the host's source
214
+ descriptors (`SpecDocuments.sources`) and refused without them; nothing in
215
+ it is a URL, SQL or code
216
+ ([source.md](https://github.com/DailybotHQ/texuli/blob/main/packages/spec/docs/source.md)).
217
+ - **Forms as data, never patterns or code (spec 0.8.0, new in `0.8.0`).** The
218
+ agent-emittable `schema-form` id carries a closed form schema
219
+ (`$defs.formSchema`): fields of 15 closed types in sections or steps,
220
+ named formats and host validator names (never a regular expression),
221
+ and conditions and cross-field rules in the closed expression AST of
222
+ `@texuli/core/expression` (`$defs.expression`, the `numberFormula` and
223
+ `condition` shapes extended with strings, booleans, field references and
224
+ calls to a closed function table). The schema checks the shape; the
225
+ forms validator (`@texuli/forms/model`, `checkForm` in `validateDraft`)
226
+ checks the rest at the host's tier. The Kotlin twin decodes the shape and
227
+ its list caps and paints a placeholder (planned).
228
+ - **Dashboards as data (spec 0.8.0, new in `0.8.0`).** The agent-emittable
229
+ `data-dashboard` id holds `data-dashboard-widget` children (`widgetId`,
230
+ `title` and a `place` in grid units); widget content is ordinary catalog
231
+ nodes, so its data comes only from host sources. The dashboard's `layout`
232
+ (a bounded list of `{id, x, y, w, h}` grid units) is bindable and
233
+ sourceable, so a state or a host source persists what people arrange. The
234
+ schema bounds the grid units; `@texuli/catalog` checks the composition
235
+ (widgets only directly in a dashboard, unique ids) on the web. The Kotlin
236
+ twin decodes the props and paints a placeholder (planned).
237
+ - **Spreadsheets as data, formulas in a closed grammar (spec 0.8.0,
238
+ new in `0.8.0`).** The agent-emittable `spreadsheet` id (not `sheet`, which
239
+ is the texuli `Sheet` overlay) carries a sheet in its `sheet` prop
240
+ (`$defs.sheet`, `$defs.sheetTab`): `format` `texuli-sheet`, 1 to 16 tabs
241
+ of `{ id, name, rows, columns, cells }` with cells keyed by A1 address.
242
+ The schema checks that envelope and its caps; the sheet validator
243
+ (`@texuli/sheets/model`, `checkSheet` in `validateDraft`) parses every
244
+ formula with the sheet grammar (no `INDIRECT`, `OFFSET` or `RAND`, a
245
+ closed function set). The Kotlin twin decodes the envelope and its caps;
246
+ painting it is planned.
184
247
  - **List item text never hides or reorders itself.** String fields of list
185
248
  state items refuse control characters, bidirectional overrides and
186
249
  isolates, and invisible formatting characters (`SPEC_PATTERNS.listText`;
@@ -210,9 +273,15 @@ catalog hash, and from 0.3.0 may declare the exact spec version it was
210
273
  written for (`spec`; omitted means 0.2.0). `migrateBlueprint`
211
274
  (`@texuli/spec/migrate`) brings an
212
275
  envelope up to the current spec with pure, per-version steps
213
- (`src/migrations/`: `0.2.0 → 0.3.0`, `0.3.0 → 0.4.0`, `0.4.0 → 0.5.0` and
214
- `0.5.0 → 0.6.0`, each of which only stamps `spec`); a compatible version range is planned,
215
- not built. `@texuli/spec@0.6.0` carries spec `0.6.0` (digest
276
+ (`src/migrations/`: `0.2.0 → 0.3.0`, `0.3.0 → 0.4.0`, `0.4.0 → 0.5.0`,
277
+ `0.5.0 → 0.6.0`, `0.6.0 → 0.7.0` and `0.7.0 → 0.8.0`, each of which only
278
+ stamps `spec`); a compatible version range is planned, not built.
279
+ `@texuli/spec@0.8.0` carries spec `0.8.0` (digest
280
+ `f6b6388280d247eb9c3106a925111f35b90d1ec3b59c23652542f06a9f103daf`), which
281
+ adds data-source bindings, forms, canvases, dashboards and spreadsheets;
282
+ `@texuli/spec@0.7.0` carried spec `0.7.0` (digest
283
+ `43e44fa9a198d337d686b4e2d86882a06a676ae1e1c1f02d023b3a525795a8b2`), which
284
+ adds node keys and documents; `@texuli/spec@0.6.0` carried spec `0.6.0` (digest
216
285
  `37141b85b9ab9f02af849a62a9c97ba729d6c5c2705130290bcdf45b308ea4cc`), which
217
286
  adds the closed motion vocabulary (ADR-012); `@texuli/spec@0.5.0` carried
218
287
  spec `0.5.0`, which added the `data-cohort-table`,
@@ -238,6 +307,8 @@ conformance harness) are in
238
307
  [motion.md](https://github.com/DailybotHQ/texuli/blob/main/packages/spec/docs/motion.md)
239
308
  - Streamed blueprints (the incremental tokenizer, the canonical order and
240
309
  node shells; new in `0.6.0`): [stream.md](https://github.com/DailybotHQ/texuli/blob/main/packages/spec/docs/stream.md)
310
+ - Data-source bindings (spec 0.8.0; new in `0.8.0`):
311
+ [source.md](https://github.com/DailybotHQ/texuli/blob/main/packages/spec/docs/source.md)
241
312
  - The Kotlin Multiplatform twin that consumes the generated models:
242
313
  [native/README.md](https://github.com/DailybotHQ/texuli/blob/main/native/README.md)
243
314
  - Architecture: [ARCHITECTURE.md](https://github.com/DailybotHQ/texuli/blob/main/docs/ARCHITECTURE.md)
package/dist/build.d.ts CHANGED
@@ -3,6 +3,15 @@ import { SPEC_VERSION } from './version.js';
3
3
  export { SPEC_VERSION };
4
4
  /** Where normative files live inside the package. */
5
5
  export declare const NORMATIVE_DIRS: readonly ["schemas/", "catalog/", "fixtures/"];
6
+ /**
7
+ * Normative schemas that are pinned by digest but are not in the dialect and
8
+ * generate nothing: they are exported by a higher-layer engine and kept here
9
+ * so the contract pins the exact version agents write against. Spec 0.7.0
10
+ * pins `@texuli/document`'s JSON Schema export (ADR-011) and spec 0.8.0
11
+ * `@texuli/canvas`'s (ADR-012); freshness tests in `@texuli/catalog` keep
12
+ * them equal to `documentJsonSchema()` and `canvasJsonSchema()`.
13
+ */
14
+ export declare const EXTERNAL_SCHEMAS: readonly string[];
6
15
  /** The schema documents, their generated names and output files. */
7
16
  export declare const SCHEMA_UNITS: readonly [{
8
17
  readonly name: "blueprint";
package/dist/build.js CHANGED
@@ -7,6 +7,18 @@ import { SPEC_VERSION } from "./version.js";
7
7
  export { SPEC_VERSION };
8
8
  /** Where normative files live inside the package. */
9
9
  export const NORMATIVE_DIRS = ['schemas/', 'catalog/', 'fixtures/'];
10
+ /**
11
+ * Normative schemas that are pinned by digest but are not in the dialect and
12
+ * generate nothing: they are exported by a higher-layer engine and kept here
13
+ * so the contract pins the exact version agents write against. Spec 0.7.0
14
+ * pins `@texuli/document`'s JSON Schema export (ADR-011) and spec 0.8.0
15
+ * `@texuli/canvas`'s (ADR-012); freshness tests in `@texuli/catalog` keep
16
+ * them equal to `documentJsonSchema()` and `canvasJsonSchema()`.
17
+ */
18
+ export const EXTERNAL_SCHEMAS = Object.freeze([
19
+ 'schemas/canvas.schema.json',
20
+ 'schemas/document.schema.json',
21
+ ]);
10
22
  /** The schema documents, their generated names and output files. */
11
23
  export const SCHEMA_UNITS = [
12
24
  { name: 'blueprint', prefix: 'Blueprint', ts: 'blueprint.ts', kt: 'Blueprint.kt' },
package/dist/catalog.d.ts CHANGED
@@ -26,6 +26,7 @@ export interface BuiltCatalogLike {
26
26
  emit: readonly string[];
27
27
  events?: readonly SpecEventName[];
28
28
  bindable?: readonly string[];
29
+ sourceable?: readonly string[];
29
30
  deprecated?: SpecDeprecation;
30
31
  props: readonly {
31
32
  name: string;
@@ -39,8 +40,8 @@ export interface BuiltCatalogLike {
39
40
  }
40
41
  /**
41
42
  * Project a built catalog into the spec vocabulary. Deterministic: components
42
- * sorted by id, props by name, emit tiers, events and bindable props sorted;
43
- * events, bindable, property schemas and deprecations only when present. Capability is `unknown`
43
+ * sorted by id, props by name, emit tiers, events, bindable and sourceable
44
+ * props sorted; events, bindable, sourceable, property schemas and deprecations only when present. Capability is `unknown`
44
45
  * unless `capabilities` (the reviewed `catalog/capabilities.json`) records an
45
46
  * audited value (ADR-003: no id is portable until audited).
46
47
  */
package/dist/catalog.js CHANGED
@@ -13,8 +13,8 @@ export const WEB_ONLY_KINDS = ['slot', 'event', 'unknown'];
13
13
  export const WEB_ONLY_PROP_NAMES = ['class', 'className', 'style', 'renderer'];
14
14
  /**
15
15
  * Project a built catalog into the spec vocabulary. Deterministic: components
16
- * sorted by id, props by name, emit tiers, events and bindable props sorted;
17
- * events, bindable, property schemas and deprecations only when present. Capability is `unknown`
16
+ * sorted by id, props by name, emit tiers, events, bindable and sourceable
17
+ * props sorted; events, bindable, sourceable, property schemas and deprecations only when present. Capability is `unknown`
18
18
  * unless `capabilities` (the reviewed `catalog/capabilities.json`) records an
19
19
  * audited value (ADR-003: no id is portable until audited).
20
20
  */
@@ -45,6 +45,7 @@ export function projectCatalog(catalog, catalogHash, capabilities = {}) {
45
45
  })),
46
46
  ...(entry.events?.length ? { events: [...entry.events].sort() } : {}),
47
47
  ...(entry.bindable?.length ? { bindable: [...entry.bindable].sort() } : {}),
48
+ ...(entry.sourceable?.length ? { sourceable: [...entry.sourceable].sort() } : {}),
48
49
  ...(entry.deprecated ? { deprecated: { ...entry.deprecated } } : {}),
49
50
  }));
50
51
  return { catalogHash, components };
@@ -4,7 +4,7 @@
4
4
  * projection, the committed-file JSON format and the conformance harness.
5
5
  * Pure functions: callers read and write the files.
6
6
  */
7
- export { buildSpec, KOTLIN_OUT, NORMATIVE_DIRS, SCHEMA_UNITS, type SpecBuild, TS_OUT, } from '../build.js';
7
+ export { buildSpec, EXTERNAL_SCHEMAS, KOTLIN_OUT, NORMATIVE_DIRS, SCHEMA_UNITS, type SpecBuild, TS_OUT, } from '../build.js';
8
8
  export { prettyJson } from '../canonical.js';
9
9
  export { type BuiltCatalogLike, projectCatalog } from '../catalog.js';
10
10
  export { type ConformanceIssue, checkCatalogContract, checkConformance, deprecationExpired, } from '../conformance.js';
@@ -4,7 +4,7 @@
4
4
  * projection, the committed-file JSON format and the conformance harness.
5
5
  * Pure functions: callers read and write the files.
6
6
  */
7
- export { buildSpec, KOTLIN_OUT, NORMATIVE_DIRS, SCHEMA_UNITS, TS_OUT, } from "../build.js";
7
+ export { buildSpec, EXTERNAL_SCHEMAS, KOTLIN_OUT, NORMATIVE_DIRS, SCHEMA_UNITS, TS_OUT, } from "../build.js";
8
8
  export { prettyJson } from "../canonical.js";
9
9
  export { projectCatalog } from "../catalog.js";
10
10
  export { checkCatalogContract, checkConformance, deprecationExpired, } from "../conformance.js";
@@ -391,6 +391,9 @@ export function vocabularyToKotlin(catalog, stamp) {
391
391
  if (c.bindable) {
392
392
  lines.push(`${INDENT}${INDENT}${INDENT}bindable = listOf(${c.bindable.map(kotlinString).join(', ')}),`);
393
393
  }
394
+ if (c.sourceable) {
395
+ lines.push(`${INDENT}${INDENT}${INDENT}sourceable = listOf(${c.sourceable.map(kotlinString).join(', ')}),`);
396
+ }
394
397
  if (c.deprecated)
395
398
  lines.push(`${INDENT}${INDENT}${INDENT}deprecated = ${deprecationExpr(c.deprecated)},`);
396
399
  lines.push(`${INDENT}${INDENT}),`);
@@ -9,7 +9,7 @@ export interface ConformanceIssue {
9
9
  * Run the spec's conformance harness (spec/Compose design §7.1 and §7.3)
10
10
  * over the normative files and the committed outputs (`manifest.json`,
11
11
  * `generated-manifest.json` and every generated file):
12
- * - every schema stays in the dialect;
12
+ * - every schema stays in the dialect (except the pinned `EXTERNAL_SCHEMAS`);
13
13
  * - `catalog/components.json` and `catalog/capabilities.json` are valid;
14
14
  * - the manifest digests match the normative files;
15
15
  * - regenerating gives byte-identical TypeScript and Kotlin, with no file
@@ -1,4 +1,4 @@
1
- import { buildSpec } from "./build.js";
1
+ import { buildSpec, EXTERNAL_SCHEMAS } from "./build.js";
2
2
  import { CAPABILITIES } from "./catalog.js";
3
3
  import { checkPropSchema, defCollisions } from "./contract.js";
4
4
  import { checkSchemaDocument, isRecord, validateInstance } from "./dialect.js";
@@ -9,7 +9,7 @@ import { validateWorkflowGraph } from "./workflow.js";
9
9
  * Run the spec's conformance harness (spec/Compose design §7.1 and §7.3)
10
10
  * over the normative files and the committed outputs (`manifest.json`,
11
11
  * `generated-manifest.json` and every generated file):
12
- * - every schema stays in the dialect;
12
+ * - every schema stays in the dialect (except the pinned `EXTERNAL_SCHEMAS`);
13
13
  * - `catalog/components.json` and `catalog/capabilities.json` are valid;
14
14
  * - the manifest digests match the normative files;
15
15
  * - regenerating gives byte-identical TypeScript and Kotlin, with no file
@@ -26,7 +26,8 @@ export function checkConformance(normative, committed) {
26
26
  const issues = [];
27
27
  const json = (path) => JSON.parse(normative[path] ?? 'null');
28
28
  const schemas = {};
29
- for (const path of Object.keys(normative).filter((p) => p.startsWith('schemas/'))) {
29
+ const dialectSchemas = Object.keys(normative).filter((p) => p.startsWith('schemas/') && !EXTERNAL_SCHEMAS.includes(p));
30
+ for (const path of dialectSchemas) {
30
31
  const schema = json(path);
31
32
  schemas[path] = schema;
32
33
  for (const issue of checkSchemaDocument(schema)) {
@@ -134,7 +135,15 @@ export function checkConformance(normative, committed) {
134
135
  }
135
136
  }
136
137
  if (blueprintSchema) {
137
- const documents = { blueprintSchema, catalog, workflowSchema, validateWorkflowGraph };
138
+ // The fixture host's data sources (spec 0.8.0): source bindings are checked against them.
139
+ const host = json('fixtures/sources.json');
140
+ const documents = {
141
+ blueprintSchema,
142
+ catalog,
143
+ workflowSchema,
144
+ validateWorkflowGraph,
145
+ ...(host?.sources ? { sources: host.sources } : {}),
146
+ };
138
147
  for (const path of Object.keys(normative).filter((p) => p.startsWith('fixtures/golden/'))) {
139
148
  const result = validateBlueprint(json(path), documents);
140
149
  for (const d of result.diagnostics) {
@@ -187,6 +196,16 @@ export function checkCatalogContract(catalog, specVersion = SPEC_VERSION) {
187
196
  });
188
197
  }
189
198
  }
199
+ for (const name of component.sourceable ?? []) {
200
+ const prop = component.props.find((p) => p.name === name);
201
+ if (!prop || prop.extension || prop.kind !== 'array') {
202
+ issues.push({
203
+ check: 'prop-schema',
204
+ path,
205
+ message: `${component.id}: sourceable "${name}" is not a portable array prop`,
206
+ });
207
+ }
208
+ }
190
209
  if (new Set(component.events ?? []).size !== (component.events ?? []).length) {
191
210
  issues.push({ check: 'catalog', path, message: `${component.id}: events repeat` });
192
211
  }
@@ -21,7 +21,7 @@ export type SpecActionOp = (typeof SPEC_ACTION_OPS)[number];
21
21
  export declare const SPEC_EVENT_NAMES: readonly ["press", "change", "select", "submit", "dismiss", "move"];
22
22
  export type SpecEventName = (typeof SPEC_EVENT_NAMES)[number];
23
23
  /** The `$ref` targets a catalog property schema may name. */
24
- export declare const SPEC_PROP_REFS: readonly ["binding", "reorder", "action", "eventMap", "condition", "workflowGraph"];
24
+ export declare const SPEC_PROP_REFS: readonly ["binding", "reorder", "action", "eventMap", "condition", "workflowGraph", "document", "formSchema", "canvas", "sheet"];
25
25
  /** The fields each action op takes (besides `op`) and the ones it needs. */
26
26
  export declare const ACTION_FIELDS: Readonly<Record<SpecActionOp, {
27
27
  readonly allowed: readonly string[];
@@ -54,6 +54,13 @@ export declare const SPEC_LIMITS: Readonly<{
54
54
  workflowStringLength: 2000;
55
55
  /** Deepest prop value walked for nested bindings. */
56
56
  propDepth: 32;
57
+ /** Top-level blocks a `document` node's doc may hold (`$defs.document.blocks`). */
58
+ documentBlocks: 2000;
59
+ /** Operations one `patchDocument` patch may carry (`$defs.documentPatch`, ADR-011). */
60
+ documentPatchOps: 1000;
61
+ /** Shapes and connectors a `canvas` node's canvas may hold at its top level (`$defs.canvas`). */
62
+ canvasShapes: 5000;
63
+ canvasConnectors: 5000;
57
64
  }>;
58
65
  /** The name patterns, as the schemas write them. */
59
66
  export declare const SPEC_PATTERNS: Readonly<{
@@ -62,6 +69,8 @@ export declare const SPEC_PATTERNS: Readonly<{
62
69
  flowName: "^[a-z][a-z0-9-]{0,31}$";
63
70
  intentName: "^[A-Za-z][A-Za-z0-9_-]{0,63}$";
64
71
  itemKey: "^[A-Za-z0-9._:-]{1,64}$";
72
+ /** A node's stable instance id (spec 0.7.0, ADR-014), unique in its tree. */
73
+ nodeKey: "^[A-Za-z0-9._:-]{1,64}$";
65
74
  groupName: "^[a-z][a-z0-9-]{0,31}$";
66
75
  workflowId: "^[A-Za-z0-9._:-]{1,64}$";
67
76
  workflowFieldName: "^[a-z][A-Za-z0-9_]{0,31}$";
@@ -88,6 +97,7 @@ export declare const SPEC_REGEX: Readonly<{
88
97
  dataName: RegExp;
89
98
  intentName: RegExp;
90
99
  itemKey: RegExp;
100
+ nodeKey: RegExp;
91
101
  listText: RegExp;
92
102
  }>;
93
103
  /**
package/dist/contract.js CHANGED
@@ -32,6 +32,13 @@ export const SPEC_PROP_REFS = [
32
32
  'eventMap',
33
33
  'condition',
34
34
  'workflowGraph',
35
+ 'document',
36
+ // Spec 0.8.0 (ADR-015): the closed form schema of a `schema-form` node.
37
+ 'formSchema',
38
+ // Spec 0.8.0 (ADR-012): the canvas a `canvas` node carries (envelope only).
39
+ 'canvas',
40
+ // Spec 0.8.0 (ADR-015): the sheet a `spreadsheet` node carries (envelope only).
41
+ 'sheet',
35
42
  ];
36
43
  /** The fields each action op takes (besides `op`) and the ones it needs. */
37
44
  export const ACTION_FIELDS = Object.freeze({
@@ -76,6 +83,13 @@ export const SPEC_LIMITS = Object.freeze({
76
83
  workflowStringLength: 2000,
77
84
  /** Deepest prop value walked for nested bindings. */
78
85
  propDepth: 32,
86
+ /** Top-level blocks a `document` node's doc may hold (`$defs.document.blocks`). */
87
+ documentBlocks: 2000,
88
+ /** Operations one `patchDocument` patch may carry (`$defs.documentPatch`, ADR-011). */
89
+ documentPatchOps: 1000,
90
+ /** Shapes and connectors a `canvas` node's canvas may hold at its top level (`$defs.canvas`). */
91
+ canvasShapes: 5000,
92
+ canvasConnectors: 5000,
79
93
  });
80
94
  /** The name patterns, as the schemas write them. */
81
95
  export const SPEC_PATTERNS = Object.freeze({
@@ -84,6 +98,8 @@ export const SPEC_PATTERNS = Object.freeze({
84
98
  flowName: '^[a-z][a-z0-9-]{0,31}$',
85
99
  intentName: '^[A-Za-z][A-Za-z0-9_-]{0,63}$',
86
100
  itemKey: '^[A-Za-z0-9._:-]{1,64}$',
101
+ /** A node's stable instance id (spec 0.7.0, ADR-014), unique in its tree. */
102
+ nodeKey: '^[A-Za-z0-9._:-]{1,64}$',
87
103
  groupName: '^[a-z][a-z0-9-]{0,31}$',
88
104
  workflowId: '^[A-Za-z0-9._:-]{1,64}$',
89
105
  workflowFieldName: '^[a-z][A-Za-z0-9_]{0,31}$',
@@ -110,6 +126,7 @@ export const SPEC_REGEX = Object.freeze({
110
126
  dataName: new RegExp(SPEC_PATTERNS.dataName, 'u'),
111
127
  intentName: new RegExp(SPEC_PATTERNS.intentName, 'u'),
112
128
  itemKey: new RegExp(SPEC_PATTERNS.itemKey, 'u'),
129
+ nodeKey: new RegExp(SPEC_PATTERNS.nodeKey, 'u'),
113
130
  listText: new RegExp(SPEC_PATTERNS.listText, 'u'),
114
131
  });
115
132
  /**