@texuli/spec 0.5.0 → 0.7.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.
Files changed (37) hide show
  1. package/README.md +55 -11
  2. package/dist/build.d.ts +8 -0
  3. package/dist/build.js +8 -0
  4. package/dist/codegen/index.d.ts +1 -1
  5. package/dist/codegen/index.js +1 -1
  6. package/dist/conformance.d.ts +1 -1
  7. package/dist/conformance.js +4 -3
  8. package/dist/contract.d.ts +8 -1
  9. package/dist/contract.js +8 -0
  10. package/dist/generated/typescript/blueprint.d.ts +98 -5
  11. package/dist/generated/typescript/blueprint.js +1 -1
  12. package/dist/generated/typescript/catalog-schema.js +1 -1
  13. package/dist/generated/typescript/diagnostics.d.ts +1 -1
  14. package/dist/generated/typescript/diagnostics.js +1 -1
  15. package/dist/generated/typescript/manifest.js +1 -1
  16. package/dist/generated/typescript/vocabulary.d.ts +7 -2
  17. package/dist/generated/typescript/vocabulary.js +3 -2
  18. package/dist/generated/typescript/workflow.js +1 -1
  19. package/dist/migrations/0.5.0-0.6.0.d.ts +9 -0
  20. package/dist/migrations/0.5.0-0.6.0.js +12 -0
  21. package/dist/migrations/0.6.0-0.7.0.d.ts +10 -0
  22. package/dist/migrations/0.6.0-0.7.0.js +13 -0
  23. package/dist/migrations/index.js +4 -0
  24. package/dist/motion.d.ts +40 -0
  25. package/dist/motion.js +133 -0
  26. package/dist/stream/assembler.d.ts +101 -0
  27. package/dist/stream/assembler.js +284 -0
  28. package/dist/stream/index.d.ts +11 -0
  29. package/dist/stream/index.js +11 -0
  30. package/dist/stream/source.d.ts +22 -0
  31. package/dist/stream/source.js +72 -0
  32. package/dist/stream/tokenizer.d.ts +67 -0
  33. package/dist/stream/tokenizer.js +383 -0
  34. package/dist/validate.js +13 -0
  35. package/dist/version.d.ts +1 -1
  36. package/dist/version.js +1 -1
  37. package/package.json +9 -1
package/README.md CHANGED
@@ -14,9 +14,21 @@ 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.5.0`, a pre-1.0 release (1.0.0 will be the first
18
- > stable release). Under 0.x, a new minor (`0.6.0`) may contain breaking
19
- > changes, so pin the exact version or use `^0.5.0`, which accepts only 0.5.x.
17
+ > published to npm as `0.7.0`, a pre-1.0 release (1.0.0 will be the first
18
+ > stable release). Under 0.x, a new minor (`0.8.0`) may contain breaking
19
+ > changes, so pin the exact version or use `^0.7.0`, which accepts only 0.7.x.
20
+
21
+ > **New in `0.7.0`.** `@texuli/spec@0.7.0` carries **spec 0.7.0**, which adds
22
+ > an optional node `key` (unique in the tree), the agent-emittable
23
+ > `document` id with its `$defs.document` shape, the `patchDocument` edit
24
+ > op with a bounded RFC 6902 patch, and the pinned
25
+ > `schemas/document.schema.json`. Every `0.6.0` envelope migrates by
26
+ > stamping `spec`.
27
+
28
+ > **New in `0.6.0`.** `@texuli/spec@0.6.0` carries **spec 0.6.0**: the
29
+ > closed motion vocabulary (`@texuli/spec/motion`, below), and the new
30
+ > `@texuli/spec/stream` subpath reads streamed blueprints. Every `0.5.0`
31
+ > envelope migrates by stamping `spec`.
20
32
 
21
33
  > **New in `0.4.0`.** `@texuli/spec@0.4.0` carries **spec 0.4.0**: spec
22
34
  > 0.3.0 (state, bindings, closed actions, `reorder`, workflow graphs), spec
@@ -56,7 +68,7 @@ import {
56
68
  validateInstance,
57
69
  } from '@texuli/spec';
58
70
 
59
- SPEC_VERSION; // the spec version: '0.5.0' in @texuli/spec@0.5.0 ('0.4.0' in @texuli/spec@0.4.0)
71
+ SPEC_VERSION; // the spec version: '0.7.0' in @texuli/spec@0.7.0
60
72
  SPEC_COMPONENT_IDS.includes('alert'); // true: the closed component vocabulary
61
73
 
62
74
  // The dialect validator: a closed subset of JSON Schema 2020-12.
@@ -88,7 +100,7 @@ import { type Schema, type SpecCatalog, validateBlueprint } from '@texuli/spec';
88
100
  import { migrateBlueprint } from '@texuli/spec/migrate';
89
101
  import { validateWorkflowGraph } from '@texuli/spec/workflow';
90
102
 
91
- const migrated = migrateBlueprint(blueprint); // 0.2.0 → 0.3.0 → 0.4.0 → 0.5.0; never throws
103
+ const migrated = migrateBlueprint(blueprint); // 0.2.0 → … → 0.6.0 → 0.7.0; never throws
92
104
  const result = validateBlueprint(migrated.blueprint, {
93
105
  blueprintSchema: blueprintSchemaJson as Schema,
94
106
  catalog: componentsJson as SpecCatalog,
@@ -109,7 +121,8 @@ refused state or flow declaration), `unknown-state` (a binding or action
109
121
  names no declared variable), `binding` (a malformed, misplaced, read-only or
110
122
  ill-fitting binding), `action` (an action with the wrong fields or target),
111
123
  `unknown-event` (an `on` key the component does not fire) and `workflow` (a
112
- workflow graph rule). Deprecated components and props add a `deprecated`
124
+ workflow graph rule), and, from spec 0.7.0, `duplicate-key` (a node key used
125
+ twice in the tree). Deprecated components and props add a `deprecated`
113
126
  warning without failing.
114
127
 
115
128
  ## What's inside
@@ -120,6 +133,8 @@ warning without failing.
120
133
  | `@texuli/spec/workflow` | Runtime, opt-in: workflow graph validation | `validateWorkflowGraph` (pass it in `SpecDocuments.validateWorkflowGraph`) |
121
134
  | `@texuli/spec/migrate` | Runtime, opt-in: envelope migrations | `migrateBlueprint`, `SPEC_MIGRATIONS`, `UNDECLARED_SPEC_VERSION` |
122
135
  | `@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` |
136
+ | `@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` |
137
+ | `@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` |
123
138
  | `@texuli/spec/codegen` | Build time: generation and conformance | `buildSpec`, `projectCatalog`, `checkConformance`, `checkCatalogContract`, `formatJson`, `nextSpecVersion`, `checkVersionHistory` |
124
139
 
125
140
  ## Key guarantees
@@ -148,6 +163,25 @@ warning without failing.
148
163
  component slides. The change is additive: every 0.3.0 envelope is a valid
149
164
  0.4.0 envelope, and the `0.3.0 → 0.4.0` migration only stamps `spec`. The
150
165
  deck model itself is validated by `@texuli/deck`, not by this package.
166
+ - **Motion as data (spec 0.6.0, new in `0.6.0`).** A node may declare `motion`
167
+ (`enter` and `exit` presets `fade`, `rise`, `scale`, `slide-start` and
168
+ `slide-end`; `layout`; `stagger` and `duration` in token steps) and the
169
+ envelope a `motion.level` (`full`, `subtle`, `none`). Names map to the
170
+ motion tokens: a blueprint never carries CSS, keyframes, milliseconds or
171
+ code, and anything else is refused. The `0.5.0 → 0.6.0` migration only
172
+ stamps `spec`.
173
+ - **Documents as data (spec 0.7.0, new in `0.7.0`).** A
174
+ `document` node's `doc` must have the document shape (`$defs.document`);
175
+ the document's own vocabulary and URL policy are checked by
176
+ `@texuli/document` through `@texuli/catalog`'s `validateDraft`. Agents
177
+ edit documents with `patchDocument` (at most 1,000 RFC 6902 operations),
178
+ addressed by node `key`.
179
+ - **Streams parse, never guess.** `@texuli/spec/stream` (new in `0.6.0`)
180
+ reads a blueprint split anywhere in linear time under a byte budget and a
181
+ depth cap, reports a node only once its shell (everything but
182
+ `children`) has closed, and refuses duplicate and prototype keys. It
183
+ judges no rule: the catalog checks each node, and the complete draft
184
+ goes through `validateDraft`.
151
185
  - **Structure only.** This validator checks structure. URL policy, tree
152
186
  limits and rendering live in the runtime validator of
153
187
  [`@texuli/catalog`](https://www.npmjs.com/package/@texuli/catalog). The
@@ -190,11 +224,17 @@ catalog hash, and from 0.3.0 may declare the exact spec version it was
190
224
  written for (`spec`; omitted means 0.2.0). `migrateBlueprint`
191
225
  (`@texuli/spec/migrate`) brings an
192
226
  envelope up to the current spec with pure, per-version steps
193
- (`src/migrations/`: `0.2.0 → 0.3.0`, `0.3.0 → 0.4.0` and `0.4.0 → 0.5.0`,
194
- each of which only stamps `spec`); a compatible version range is planned,
195
- not built. `@texuli/spec@0.5.0` carries spec `0.5.0`, which adds the
196
- `data-cohort-table`, `data-notification-inbox`, `data-filter-builder` and
197
- `time-field` catalog ids; `@texuli/spec@0.4.0` carried spec `0.4.0` (digest
227
+ (`src/migrations/`: `0.2.0 → 0.3.0`, `0.3.0 → 0.4.0`, `0.4.0 → 0.5.0`,
228
+ `0.5.0 → 0.6.0` and `0.6.0 → 0.7.0`, each of which only stamps `spec`); a
229
+ compatible version range is planned, not built. `@texuli/spec@0.7.0`
230
+ carries spec `0.7.0` (digest
231
+ `43e44fa9a198d337d686b4e2d86882a06a676ae1e1c1f02d023b3a525795a8b2`), which
232
+ adds node keys and documents; `@texuli/spec@0.6.0` carried spec `0.6.0` (digest
233
+ `37141b85b9ab9f02af849a62a9c97ba729d6c5c2705130290bcdf45b308ea4cc`), which
234
+ adds the closed motion vocabulary (ADR-012); `@texuli/spec@0.5.0` carried
235
+ spec `0.5.0`, which added the `data-cohort-table`,
236
+ `data-notification-inbox`, `data-filter-builder` and `time-field` catalog
237
+ ids, and `@texuli/spec@0.4.0` spec `0.4.0` (digest
198
238
  `fac29f542b16ddb78ab401c508e5b8346f655991c56e25aa811f4931661a8cb3`). Every
199
239
  pin is in `versions.json`.
200
240
 
@@ -211,6 +251,10 @@ conformance harness) are in
211
251
  [packages/spec](https://github.com/DailybotHQ/texuli/tree/main/packages/spec)
212
252
  - The contract in depth (the JSON Schema dialect, projections, conformance
213
253
  and versioning): [contract.md](https://github.com/DailybotHQ/texuli/blob/main/packages/spec/docs/contract.md)
254
+ - The motion vocabulary (spec 0.6.0):
255
+ [motion.md](https://github.com/DailybotHQ/texuli/blob/main/packages/spec/docs/motion.md)
256
+ - Streamed blueprints (the incremental tokenizer, the canonical order and
257
+ node shells; new in `0.6.0`): [stream.md](https://github.com/DailybotHQ/texuli/blob/main/packages/spec/docs/stream.md)
214
258
  - The Kotlin Multiplatform twin that consumes the generated models:
215
259
  [native/README.md](https://github.com/DailybotHQ/texuli/blob/main/native/README.md)
216
260
  - Architecture: [ARCHITECTURE.md](https://github.com/DailybotHQ/texuli/blob/main/docs/ARCHITECTURE.md)
package/dist/build.d.ts CHANGED
@@ -3,6 +3,14 @@ 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); a freshness test in
11
+ * `@texuli/catalog` keeps it equal to `documentJsonSchema()`.
12
+ */
13
+ export declare const EXTERNAL_SCHEMAS: readonly string[];
6
14
  /** The schema documents, their generated names and output files. */
7
15
  export declare const SCHEMA_UNITS: readonly [{
8
16
  readonly name: "blueprint";
package/dist/build.js CHANGED
@@ -7,6 +7,14 @@ 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); a freshness test in
15
+ * `@texuli/catalog` keeps it equal to `documentJsonSchema()`.
16
+ */
17
+ export const EXTERNAL_SCHEMAS = Object.freeze(['schemas/document.schema.json']);
10
18
  /** The schema documents, their generated names and output files. */
11
19
  export const SCHEMA_UNITS = [
12
20
  { name: 'blueprint', prefix: 'Blueprint', ts: 'blueprint.ts', kt: 'Blueprint.kt' },
@@ -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";
@@ -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)) {
@@ -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"];
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,10 @@ 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;
57
61
  }>;
58
62
  /** The name patterns, as the schemas write them. */
59
63
  export declare const SPEC_PATTERNS: Readonly<{
@@ -62,6 +66,8 @@ export declare const SPEC_PATTERNS: Readonly<{
62
66
  flowName: "^[a-z][a-z0-9-]{0,31}$";
63
67
  intentName: "^[A-Za-z][A-Za-z0-9_-]{0,63}$";
64
68
  itemKey: "^[A-Za-z0-9._:-]{1,64}$";
69
+ /** A node's stable instance id (spec 0.7.0, ADR-014), unique in its tree. */
70
+ nodeKey: "^[A-Za-z0-9._:-]{1,64}$";
65
71
  groupName: "^[a-z][a-z0-9-]{0,31}$";
66
72
  workflowId: "^[A-Za-z0-9._:-]{1,64}$";
67
73
  workflowFieldName: "^[a-z][A-Za-z0-9_]{0,31}$";
@@ -88,6 +94,7 @@ export declare const SPEC_REGEX: Readonly<{
88
94
  dataName: RegExp;
89
95
  intentName: RegExp;
90
96
  itemKey: RegExp;
97
+ nodeKey: RegExp;
91
98
  listText: RegExp;
92
99
  }>;
93
100
  /**
package/dist/contract.js CHANGED
@@ -32,6 +32,7 @@ export const SPEC_PROP_REFS = [
32
32
  'eventMap',
33
33
  'condition',
34
34
  'workflowGraph',
35
+ 'document',
35
36
  ];
36
37
  /** The fields each action op takes (besides `op`) and the ones it needs. */
37
38
  export const ACTION_FIELDS = Object.freeze({
@@ -76,6 +77,10 @@ export const SPEC_LIMITS = Object.freeze({
76
77
  workflowStringLength: 2000,
77
78
  /** Deepest prop value walked for nested bindings. */
78
79
  propDepth: 32,
80
+ /** Top-level blocks a `document` node's doc may hold (`$defs.document.blocks`). */
81
+ documentBlocks: 2000,
82
+ /** Operations one `patchDocument` patch may carry (`$defs.documentPatch`, ADR-011). */
83
+ documentPatchOps: 1000,
79
84
  });
80
85
  /** The name patterns, as the schemas write them. */
81
86
  export const SPEC_PATTERNS = Object.freeze({
@@ -84,6 +89,8 @@ export const SPEC_PATTERNS = Object.freeze({
84
89
  flowName: '^[a-z][a-z0-9-]{0,31}$',
85
90
  intentName: '^[A-Za-z][A-Za-z0-9_-]{0,63}$',
86
91
  itemKey: '^[A-Za-z0-9._:-]{1,64}$',
92
+ /** A node's stable instance id (spec 0.7.0, ADR-014), unique in its tree. */
93
+ nodeKey: '^[A-Za-z0-9._:-]{1,64}$',
87
94
  groupName: '^[a-z][a-z0-9-]{0,31}$',
88
95
  workflowId: '^[A-Za-z0-9._:-]{1,64}$',
89
96
  workflowFieldName: '^[a-z][A-Za-z0-9_]{0,31}$',
@@ -110,6 +117,7 @@ export const SPEC_REGEX = Object.freeze({
110
117
  dataName: new RegExp(SPEC_PATTERNS.dataName, 'u'),
111
118
  intentName: new RegExp(SPEC_PATTERNS.intentName, 'u'),
112
119
  itemKey: new RegExp(SPEC_PATTERNS.itemKey, 'u'),
120
+ nodeKey: new RegExp(SPEC_PATTERNS.nodeKey, 'u'),
113
121
  listText: new RegExp(SPEC_PATTERNS.listText, 'u'),
114
122
  });
115
123
  /**
@@ -34,10 +34,15 @@ export interface BlueprintProvenanceEntry {
34
34
  kind: 'apply' | 'undo' | 'redo';
35
35
  ops: readonly BlueprintEditOp[];
36
36
  }
37
- /** One edit op. The fields each op kind takes are checked by the edit parser. */
37
+ /**
38
+ * One edit op. The fields each op kind takes are checked by the edit parser. From spec 0.7.0,
39
+ * patchDocument applies a bounded RFC 6902 patch to the doc of a document node it addresses by path
40
+ * or by key.
41
+ */
38
42
  export interface BlueprintEditOp {
39
- op: 'insert' | 'remove' | 'move' | 'setProp' | 'unsetProp' | 'replace' | 'replaceAll';
43
+ op: 'insert' | 'remove' | 'move' | 'setProp' | 'unsetProp' | 'replace' | 'replaceAll' | 'patchDocument';
40
44
  path?: BlueprintNodePath;
45
+ key?: BlueprintNodeKey;
41
46
  parent?: BlueprintNodePath;
42
47
  index?: number;
43
48
  name?: string;
@@ -45,10 +50,15 @@ export interface BlueprintEditOp {
45
50
  value?: unknown;
46
51
  node?: BlueprintNode;
47
52
  tree?: BlueprintNode;
53
+ patch?: BlueprintDocumentPatch;
48
54
  }
49
55
  export type BlueprintNodePath = readonly number[];
50
- /** One catalog node: id, props, children (each with an optional named slot), when, bind and on. */
56
+ /**
57
+ * One catalog node: an optional key (spec 0.7.0), id, props, children (each with an optional named
58
+ * slot), when, bind, on and motion.
59
+ */
51
60
  export interface BlueprintNode {
61
+ key?: BlueprintNodeKey;
52
62
  /** A catalog component id. */
53
63
  id: string;
54
64
  /** Props checked against the component's catalog entry. */
@@ -63,6 +73,7 @@ export interface BlueprintNode {
63
73
  /** Two-way bindings: a prop the component lists as bindable → a $state binding it writes back. */
64
74
  bind?: Readonly<Record<string, BlueprintBinding>>;
65
75
  on?: BlueprintEventMap;
76
+ motion?: BlueprintNodeMotion;
66
77
  }
67
78
  /**
68
79
  * Shows the node when true (audit G07): the closed Condition AST of the blocks formula grammar.
@@ -110,6 +121,11 @@ export type BlueprintFlowName = string;
110
121
  export type BlueprintIntentName = string;
111
122
  /** The id of one item in a list variable. */
112
123
  export type BlueprintItemKey = string;
124
+ /**
125
+ * A node's stable instance id (spec 0.7.0, ADR-012 and ADR-014): unique in its tree. Streaming,
126
+ * live updates and edit ops address a node by its key, else by its path.
127
+ */
128
+ export type BlueprintNodeKey = string;
113
129
  /** A reorder group name. */
114
130
  export type BlueprintGroupName = string;
115
131
  /**
@@ -185,11 +201,87 @@ export interface BlueprintReorder {
185
201
  pull?: 'move' | 'clone' | 'none';
186
202
  disabled?: boolean;
187
203
  }
204
+ /**
205
+ * A closed motion preset (ADR-013): fade, rise (fade in from the block end), scale (fade and grow
206
+ * from 96%), slide-start or slide-end (fade in from the inline start or end, mirrored right to
207
+ * left). The runtime maps each to token keyframes; a blueprint never carries keyframes.
208
+ */
209
+ export type BlueprintMotionPreset = 'fade' | 'rise' | 'scale' | 'slide-start' | 'slide-end';
210
+ /** A duration token step (@texuli/tokens motion durations), never milliseconds. */
211
+ export type BlueprintMotionDuration = 'fast' | 'base' | 'slow';
212
+ /**
213
+ * The offset between the entering children of this node, in token steps: none, fast or base. The
214
+ * runtime caps the whole spread.
215
+ */
216
+ export type BlueprintMotionStagger = 'none' | 'fast' | 'base';
217
+ /**
218
+ * A motion ceiling (ADR-013): full, subtle (shorter, no travel or scale) or none (instant). The
219
+ * host and the reader's reduced-motion preference can only lower it.
220
+ */
221
+ export type BlueprintMotionLevel = 'full' | 'subtle' | 'none';
222
+ /**
223
+ * How a node moves (spec 0.6.0, ADR-012): named presets and token steps only. Nothing here is CSS,
224
+ * a duration in milliseconds or code. The server paints the end state, and nothing plays on
225
+ * hydration or under reduced motion.
226
+ */
227
+ export interface BlueprintNodeMotion {
228
+ enter?: BlueprintMotionPreset;
229
+ exit?: BlueprintMotionPreset;
230
+ /** Animate a change of this node's position or size among its siblings (FLIP). */
231
+ layout?: boolean;
232
+ stagger?: BlueprintMotionStagger;
233
+ duration?: BlueprintMotionDuration;
234
+ }
235
+ /** The blueprint's motion settings (spec 0.6.0): the level every node motion plays at. */
236
+ export interface BlueprintEnvelopeMotion {
237
+ level: BlueprintMotionLevel;
238
+ }
239
+ /**
240
+ * A Texuli document (spec 0.7.0, ADR-011): the format, version, metadata, page settings, block list
241
+ * and annotations of @texuli/document. This contract checks the envelope of the document only; the
242
+ * closed block vocabulary, text safety, the URL policy and the caps are checked by
243
+ * @texuli/document/schema (schemas/document.schema.json) inside the runtime validator,
244
+ * @texuli/catalog's validateDraft, at the host's tier.
245
+ */
246
+ export interface BlueprintDocument {
247
+ format: 'texuli-document';
248
+ formatVersion: 1;
249
+ /** Title (required), description, locale, direction, authors, tags and dates. */
250
+ meta: Readonly<Record<string, unknown>>;
251
+ /** Page size, orientation, margins, header and footer for paged output. */
252
+ page?: Readonly<Record<string, unknown>>;
253
+ /**
254
+ * The block list: paragraph, heading, code, list, quote, divider, media, callout, toggle,
255
+ * columns, page-link, block (a @texuli/blocks block) and embed (a one-node catalog component, a
256
+ * deck, a sheet or a canvas).
257
+ */
258
+ blocks: readonly Readonly<Record<string, unknown>>[];
259
+ /** Comment threads, suggestions and named versions, kept apart from content. */
260
+ annotations?: Readonly<Record<string, unknown>>;
261
+ }
262
+ /**
263
+ * One RFC 6902 operation on a document. Paths are RFC 6901 pointers into the document;
264
+ * @texuli/document/ops refuses prototype paths and malformed escapes, applies the patch all or none
265
+ * and validates the result.
266
+ */
267
+ export interface BlueprintDocumentPatchOperation {
268
+ op: 'add' | 'remove' | 'replace' | 'move' | 'copy' | 'test';
269
+ path: string;
270
+ from?: string;
271
+ /** Any plain JSON value (add, replace and test). */
272
+ value?: unknown;
273
+ }
274
+ /** A bounded RFC 6902 patch (ADR-011): at most 1,000 operations; test operations are encouraged. */
275
+ export type BlueprintDocumentPatch = readonly BlueprintDocumentPatchOperation[];
188
276
  /**
189
277
  * Envelope version 1 around one catalog tree (ADR-004, audit G13). Mirrors what
190
278
  * @texuli/catalog/envelope reads; a declared status, activation or tier is display data, never
191
279
  * authority. Spec 0.4.0 (ADR-008 + PLAN_008 deck encoding) adds an optional spec version, local
192
- * state and a flow. Planned minor additions: the native profile and several roots.
280
+ * state and a flow; spec 0.6.0 (ADR-012, ADR-013) adds the closed motion vocabulary: a node motion
281
+ * and the envelope motion level; spec 0.7.0 (ADR-011, ADR-012, ADR-014) adds an optional node key
282
+ * (a stable instance id, unique in the tree), the document shape a `document` node carries and the
283
+ * patchDocument edit op with its bounded RFC 6902 patch. Planned minor additions: the native
284
+ * profile and several roots.
193
285
  */
194
286
  export interface BlueprintEnvelope {
195
287
  format: 'texuli-blueprint';
@@ -213,11 +305,12 @@ export interface BlueprintEnvelope {
213
305
  /**
214
306
  * The spec version the envelope was written for. Omitted means 0.2.0; migrateBlueprint stamps it.
215
307
  */
216
- spec?: '0.3.0' | '0.4.0' | '0.5.0';
308
+ spec?: '0.3.0' | '0.4.0' | '0.5.0' | '0.6.0' | '0.7.0';
217
309
  /**
218
310
  * Local state declarations (ADR-008), at most 64. Names, counts and per-type rules are checked by
219
311
  * the validator.
220
312
  */
221
313
  state?: Readonly<Record<string, BlueprintStateDeclaration>>;
222
314
  flow?: BlueprintFlow;
315
+ motion?: BlueprintEnvelopeMotion;
223
316
  }
@@ -1,3 +1,3 @@
1
1
  // Generated by @texuli/spec from schemas/blueprint.schema.json. Do not edit: run the spec generator.
2
- // Spec 0.5.0, digest 1ba15e8b082f68140c4b2768b8fd69c9f760e655dd117fac756f356a71525b2d
2
+ // Spec 0.7.0, digest 43e44fa9a198d337d686b4e2d86882a06a676ae1e1c1f02d023b3a525795a8b2
3
3
  export {};
@@ -1,3 +1,3 @@
1
1
  // Generated by @texuli/spec from schemas/catalog.schema.json. Do not edit: run the spec generator.
2
- // Spec 0.5.0, digest 1ba15e8b082f68140c4b2768b8fd69c9f760e655dd117fac756f356a71525b2d
2
+ // Spec 0.7.0, digest 43e44fa9a198d337d686b4e2d86882a06a676ae1e1c1f02d023b3a525795a8b2
3
3
  export {};
@@ -4,7 +4,7 @@ export interface SpecDiagnostic {
4
4
  message: string;
5
5
  id?: string;
6
6
  }
7
- export type SpecDiagnosticCode = 'schema' | 'unknown-id' | 'unknown-prop' | 'prop-type' | 'enum-value' | 'missing-required' | 'extension-prop' | 'catalog-mismatch' | 'unsupported-profile' | 'unknown-slot' | 'state' | 'unknown-state' | 'binding' | 'action' | 'unknown-event' | 'workflow';
7
+ export type SpecDiagnosticCode = 'schema' | 'unknown-id' | 'unknown-prop' | 'prop-type' | 'enum-value' | 'missing-required' | 'extension-prop' | 'catalog-mismatch' | 'unsupported-profile' | 'unknown-slot' | 'state' | 'unknown-state' | 'binding' | 'action' | 'unknown-event' | 'workflow' | 'duplicate-key';
8
8
  export interface SpecWarning {
9
9
  code: SpecWarningCode;
10
10
  path: string;
@@ -1,3 +1,3 @@
1
1
  // Generated by @texuli/spec from schemas/diagnostics.schema.json. Do not edit: run the spec generator.
2
- // Spec 0.5.0, digest 1ba15e8b082f68140c4b2768b8fd69c9f760e655dd117fac756f356a71525b2d
2
+ // Spec 0.7.0, digest 43e44fa9a198d337d686b4e2d86882a06a676ae1e1c1f02d023b3a525795a8b2
3
3
  export {};
@@ -1,3 +1,3 @@
1
1
  // Generated by @texuli/spec from schemas/manifest.schema.json. Do not edit: run the spec generator.
2
- // Spec 0.5.0, digest 1ba15e8b082f68140c4b2768b8fd69c9f760e655dd117fac756f356a71525b2d
2
+ // Spec 0.7.0, digest 43e44fa9a198d337d686b4e2d86882a06a676ae1e1c1f02d023b3a525795a8b2
3
3
  export {};
@@ -1,7 +1,7 @@
1
1
  /** The built catalog this vocabulary was projected from. */
2
- export declare const SPEC_CATALOG_HASH = "4ac7ecedd1c59d1999a9f7eea2f523c7653fececb97258a478f09253f6b6057f";
2
+ export declare const SPEC_CATALOG_HASH = "c6bd7047eba3e1f32e9ed74097c390a58416cf4260267c8958e0ea70821abf35";
3
3
  /** Every closed component id, sorted. */
4
- export declare const SPEC_COMPONENT_IDS: readonly ["action-form", "alert", "alert-dialog", "analytics-grid", "app-nav", "badge", "banner", "blocks-code", "blocks-data", "blocks-diagram", "blocks-embed", "blocks-interactive", "blocks-map", "blocks-media", "blocks-structure", "blocks-text", "breadcrumb", "button", "button-group", "callout", "card", "charts-area", "charts-bar", "charts-bubble", "charts-bullet", "charts-canvas-renderer", "charts-cartesian", "charts-combo", "charts-donut", "charts-funnel", "charts-gauge", "charts-geo-map", "charts-geo-point-map", "charts-heatmap", "charts-line", "charts-network", "charts-pie", "charts-progress-ring", "charts-sankey", "charts-scatter", "charts-sparkline", "charts-stacked-bar", "charts-sunburst", "charts-treemap", "chat-message", "chat-thread", "checkbox", "chip", "code", "combobox", "command-palette", "content-layout", "context-menu", "context-menu-trigger", "control-choice", "control-filter", "control-slider", "control-toggle", "data-board", "data-calendar", "data-cohort-table", "data-files", "data-filter-builder", "data-grid", "data-map", "data-notification-inbox", "data-timeline", "data-tree-view", "deck-slide", "description", "detail-panel", "dialog", "drawer", "dropdown-menu", "dropdown-menu-content", "dropdown-menu-item", "dropdown-menu-trigger", "empty-state", "error-state", "field", "field-error", "flow-action", "flow-view", "form", "form-field", "form-submit", "host-region", "hover-card", "hover-card-content", "icon-button", "inline-text", "input", "input-group", "kbd", "kpi", "label", "layout-auth-split", "layout-blog", "layout-card", "layout-chrome", "layout-dashboard-grid", "layout-deck", "layout-doc", "layout-docs", "layout-drawer", "layout-dual-sidebar", "layout-editorial", "layout-grid", "layout-host", "layout-landing", "layout-marketing", "layout-page", "layout-pricing", "layout-row", "layout-settings", "layout-sidebar", "layout-split", "layout-stack", "layout-tile", "layout-top-nav", "layout-wizard", "media-composition-canvas", "media-composition-svg", "media-image", "menubar", "menubar-trigger", "multi-select", "nav-icon", "navigation-menu", "pagination", "popover", "popover-content", "radio", "radio-group", "responsive-modal", "screen-outlet", "select", "select-content", "select-group", "select-item", "select-label", "select-portal", "select-separator", "select-trigger", "select-value", "select-viewport", "sheet", "sidebar", "sortable-list", "split-view", "stat", "stepper", "switch", "table", "tag", "text-counter", "textarea", "time-field", "timeline", "toaster", "tooltip", "workflow-graph"];
4
+ export declare const SPEC_COMPONENT_IDS: readonly ["action-form", "alert", "alert-dialog", "analytics-grid", "app-nav", "badge", "banner", "blocks-code", "blocks-data", "blocks-diagram", "blocks-embed", "blocks-interactive", "blocks-map", "blocks-media", "blocks-structure", "blocks-text", "breadcrumb", "button", "button-group", "callout", "card", "charts-area", "charts-bar", "charts-bubble", "charts-bullet", "charts-canvas-renderer", "charts-cartesian", "charts-combo", "charts-donut", "charts-funnel", "charts-gauge", "charts-geo-map", "charts-geo-point-map", "charts-heatmap", "charts-line", "charts-network", "charts-pie", "charts-progress-ring", "charts-sankey", "charts-scatter", "charts-sparkline", "charts-stacked-bar", "charts-sunburst", "charts-treemap", "chat-message", "chat-thread", "checkbox", "chip", "code", "combobox", "command-palette", "content-layout", "context-menu", "context-menu-trigger", "control-choice", "control-filter", "control-slider", "control-toggle", "data-board", "data-calendar", "data-cohort-table", "data-files", "data-filter-builder", "data-grid", "data-map", "data-notification-inbox", "data-timeline", "data-tree-view", "deck-slide", "description", "detail-panel", "dialog", "document", "drawer", "dropdown-menu", "dropdown-menu-content", "dropdown-menu-item", "dropdown-menu-trigger", "empty-state", "error-state", "field", "field-error", "flow-action", "flow-view", "form", "form-field", "form-submit", "host-region", "hover-card", "hover-card-content", "icon-button", "inline-text", "input", "input-group", "kbd", "kpi", "label", "layout-auth-split", "layout-blog", "layout-card", "layout-chrome", "layout-dashboard-grid", "layout-deck", "layout-doc", "layout-docs", "layout-drawer", "layout-dual-sidebar", "layout-editorial", "layout-grid", "layout-host", "layout-landing", "layout-marketing", "layout-page", "layout-pricing", "layout-row", "layout-settings", "layout-sidebar", "layout-split", "layout-stack", "layout-tile", "layout-top-nav", "layout-wizard", "media-composition-canvas", "media-composition-svg", "media-image", "menubar", "menubar-trigger", "multi-select", "nav-icon", "navigation-menu", "pagination", "popover", "popover-content", "radio", "radio-group", "responsive-modal", "screen-outlet", "select", "select-content", "select-group", "select-item", "select-label", "select-portal", "select-separator", "select-trigger", "select-value", "select-viewport", "sheet", "sidebar", "sortable-list", "split-view", "stat", "stepper", "switch", "table", "tag", "text-counter", "textarea", "time-field", "timeline", "toaster", "tooltip", "workflow-graph"];
5
5
  export type SpecComponentId = (typeof SPEC_COMPONENT_IDS)[number];
6
6
  /** The props a blueprint may set on each component (platform extensions left out). */
7
7
  export interface SpecComponentProps {
@@ -567,6 +567,11 @@ export interface SpecComponentProps {
567
567
  size?: 'sm' | 'md' | 'lg' | 'xl';
568
568
  title: string;
569
569
  };
570
+ document: {
571
+ doc: Readonly<Record<string, unknown>>;
572
+ label?: string;
573
+ outline?: boolean;
574
+ };
570
575
  drawer: {
571
576
  closeOnEscape?: boolean;
572
577
  dismissOutside?: boolean;
@@ -1,7 +1,7 @@
1
1
  // Generated by @texuli/spec from catalog/components.json. Do not edit: run the spec generator.
2
- // Spec 0.5.0, digest 1ba15e8b082f68140c4b2768b8fd69c9f760e655dd117fac756f356a71525b2d
2
+ // Spec 0.7.0, digest 43e44fa9a198d337d686b4e2d86882a06a676ae1e1c1f02d023b3a525795a8b2
3
3
  /** The built catalog this vocabulary was projected from. */
4
- export const SPEC_CATALOG_HASH = '4ac7ecedd1c59d1999a9f7eea2f523c7653fececb97258a478f09253f6b6057f';
4
+ export const SPEC_CATALOG_HASH = 'c6bd7047eba3e1f32e9ed74097c390a58416cf4260267c8958e0ea70821abf35';
5
5
  /** Every closed component id, sorted. */
6
6
  export const SPEC_COMPONENT_IDS = [
7
7
  'action-form',
@@ -76,6 +76,7 @@ export const SPEC_COMPONENT_IDS = [
76
76
  'description',
77
77
  'detail-panel',
78
78
  'dialog',
79
+ 'document',
79
80
  'drawer',
80
81
  'dropdown-menu',
81
82
  'dropdown-menu-content',
@@ -1,3 +1,3 @@
1
1
  // Generated by @texuli/spec from schemas/workflow.schema.json. Do not edit: run the spec generator.
2
- // Spec 0.5.0, digest 1ba15e8b082f68140c4b2768b8fd69c9f760e655dd117fac756f356a71525b2d
2
+ // Spec 0.7.0, digest 43e44fa9a198d337d686b4e2d86882a06a676ae1e1c1f02d023b3a525795a8b2
3
3
  export {};
@@ -0,0 +1,9 @@
1
+ import type { SpecMigration } from './types.js';
2
+ /**
3
+ * 0.5.0 → 0.6.0 is additive (PLAN_010 T-motion-spec, ADR-012 and ADR-013):
4
+ * nodes gain an optional closed `motion` (enter and exit presets, `layout`,
5
+ * `stagger` and `duration` in token steps) and the envelope an optional
6
+ * `motion.level`. Every 0.5.0 envelope is a valid 0.6.0 envelope, so the
7
+ * step only stamps `spec`.
8
+ */
9
+ export declare const migration050to060: SpecMigration;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * 0.5.0 → 0.6.0 is additive (PLAN_010 T-motion-spec, ADR-012 and ADR-013):
3
+ * nodes gain an optional closed `motion` (enter and exit presets, `layout`,
4
+ * `stagger` and `duration` in token steps) and the envelope an optional
5
+ * `motion.level`. Every 0.5.0 envelope is a valid 0.6.0 envelope, so the
6
+ * step only stamps `spec`.
7
+ */
8
+ export const migration050to060 = Object.freeze({
9
+ from: '0.5.0',
10
+ to: '0.6.0',
11
+ migrate: (envelope) => ({ ...envelope, spec: '0.6.0' }),
12
+ });
@@ -0,0 +1,10 @@
1
+ import type { SpecMigration } from './types.js';
2
+ /**
3
+ * 0.6.0 → 0.7.0 is additive (PLAN_010 T-doc-agents, ADR-011, ADR-012 and
4
+ * ADR-014): nodes gain an optional `key` (a stable instance id, unique in
5
+ * the tree), the catalog gains the `document` id whose `doc` prop carries a
6
+ * Texuli document, and edit ops gain `patchDocument` (a bounded RFC 6902
7
+ * patch) and key addressing. Every 0.6.0 envelope is a valid 0.7.0
8
+ * envelope, so the step only stamps `spec`.
9
+ */
10
+ export declare const migration060to070: SpecMigration;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * 0.6.0 → 0.7.0 is additive (PLAN_010 T-doc-agents, ADR-011, ADR-012 and
3
+ * ADR-014): nodes gain an optional `key` (a stable instance id, unique in
4
+ * the tree), the catalog gains the `document` id whose `doc` prop carries a
5
+ * Texuli document, and edit ops gain `patchDocument` (a bounded RFC 6902
6
+ * patch) and key addressing. Every 0.6.0 envelope is a valid 0.7.0
7
+ * envelope, so the step only stamps `spec`.
8
+ */
9
+ export const migration060to070 = Object.freeze({
10
+ from: '0.6.0',
11
+ to: '0.7.0',
12
+ migrate: (envelope) => ({ ...envelope, spec: '0.7.0' }),
13
+ });
@@ -3,6 +3,8 @@ import { SPEC_VERSION } from "../version.js";
3
3
  import { migration020to030 } from "./0.2.0-0.3.0.js";
4
4
  import { migration030to040 } from "./0.3.0-0.4.0.js";
5
5
  import { migration040to050 } from "./0.4.0-0.5.0.js";
6
+ import { migration050to060 } from "./0.5.0-0.6.0.js";
7
+ import { migration060to070 } from "./0.6.0-0.7.0.js";
6
8
  /** The version an envelope without `spec` was written for. */
7
9
  export const UNDECLARED_SPEC_VERSION = '0.2.0';
8
10
  /** Every step, in order; each `to` is the next step's `from`. */
@@ -10,6 +12,8 @@ export const SPEC_MIGRATIONS = Object.freeze([
10
12
  migration020to030,
11
13
  migration030to040,
12
14
  migration040to050,
15
+ migration050to060,
16
+ migration060to070,
13
17
  ]);
14
18
  /**
15
19
  * Bring a blueprint envelope up to `target` (the current spec by default)