@texuli/spec 0.6.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.
package/README.md CHANGED
@@ -14,9 +14,16 @@ 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.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`.
20
27
 
21
28
  > **New in `0.6.0`.** `@texuli/spec@0.6.0` carries **spec 0.6.0**: the
22
29
  > closed motion vocabulary (`@texuli/spec/motion`, below), and the new
@@ -61,7 +68,7 @@ import {
61
68
  validateInstance,
62
69
  } from '@texuli/spec';
63
70
 
64
- SPEC_VERSION; // the spec version: '0.6.0' in @texuli/spec@0.6.0
71
+ SPEC_VERSION; // the spec version: '0.7.0' in @texuli/spec@0.7.0
65
72
  SPEC_COMPONENT_IDS.includes('alert'); // true: the closed component vocabulary
66
73
 
67
74
  // The dialect validator: a closed subset of JSON Schema 2020-12.
@@ -93,7 +100,7 @@ import { type Schema, type SpecCatalog, validateBlueprint } from '@texuli/spec';
93
100
  import { migrateBlueprint } from '@texuli/spec/migrate';
94
101
  import { validateWorkflowGraph } from '@texuli/spec/workflow';
95
102
 
96
- const migrated = migrateBlueprint(blueprint); // 0.2.0 → … → 0.5.0 → 0.6.0; never throws
103
+ const migrated = migrateBlueprint(blueprint); // 0.2.0 → … → 0.6.0 → 0.7.0; never throws
97
104
  const result = validateBlueprint(migrated.blueprint, {
98
105
  blueprintSchema: blueprintSchemaJson as Schema,
99
106
  catalog: componentsJson as SpecCatalog,
@@ -114,7 +121,8 @@ refused state or flow declaration), `unknown-state` (a binding or action
114
121
  names no declared variable), `binding` (a malformed, misplaced, read-only or
115
122
  ill-fitting binding), `action` (an action with the wrong fields or target),
116
123
  `unknown-event` (an `on` key the component does not fire) and `workflow` (a
117
- 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`
118
126
  warning without failing.
119
127
 
120
128
  ## What's inside
@@ -162,6 +170,12 @@ warning without failing.
162
170
  motion tokens: a blueprint never carries CSS, keyframes, milliseconds or
163
171
  code, and anything else is refused. The `0.5.0 → 0.6.0` migration only
164
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`.
165
179
  - **Streams parse, never guess.** `@texuli/spec/stream` (new in `0.6.0`)
166
180
  reads a blueprint split anywhere in linear time under a byte budget and a
167
181
  depth cap, reports a node only once its shell (everything but
@@ -210,9 +224,12 @@ catalog hash, and from 0.3.0 may declare the exact spec version it was
210
224
  written for (`spec`; omitted means 0.2.0). `migrateBlueprint`
211
225
  (`@texuli/spec/migrate`) brings an
212
226
  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
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
216
233
  `37141b85b9ab9f02af849a62a9c97ba729d6c5c2705130290bcdf45b308ea4cc`), which
217
234
  adds the closed motion vocabulary (ADR-012); `@texuli/spec@0.5.0` carried
218
235
  spec `0.5.0`, which added the `data-cohort-table`,
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,13 +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
56
  /**
51
- * One catalog node: id, props, children (each with an optional named slot), when, bind, on and
52
- * motion.
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.
53
59
  */
54
60
  export interface BlueprintNode {
61
+ key?: BlueprintNodeKey;
55
62
  /** A catalog component id. */
56
63
  id: string;
57
64
  /** Props checked against the component's catalog entry. */
@@ -114,6 +121,11 @@ export type BlueprintFlowName = string;
114
121
  export type BlueprintIntentName = string;
115
122
  /** The id of one item in a list variable. */
116
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;
117
129
  /** A reorder group name. */
118
130
  export type BlueprintGroupName = string;
119
131
  /**
@@ -224,12 +236,52 @@ export interface BlueprintNodeMotion {
224
236
  export interface BlueprintEnvelopeMotion {
225
237
  level: BlueprintMotionLevel;
226
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[];
227
276
  /**
228
277
  * Envelope version 1 around one catalog tree (ADR-004, audit G13). Mirrors what
229
278
  * @texuli/catalog/envelope reads; a declared status, activation or tier is display data, never
230
279
  * authority. Spec 0.4.0 (ADR-008 + PLAN_008 deck encoding) adds an optional spec version, local
231
280
  * state and a flow; spec 0.6.0 (ADR-012, ADR-013) adds the closed motion vocabulary: a node motion
232
- * and the envelope motion level. Planned minor additions: the native profile and several roots.
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.
233
285
  */
234
286
  export interface BlueprintEnvelope {
235
287
  format: 'texuli-blueprint';
@@ -253,7 +305,7 @@ export interface BlueprintEnvelope {
253
305
  /**
254
306
  * The spec version the envelope was written for. Omitted means 0.2.0; migrateBlueprint stamps it.
255
307
  */
256
- spec?: '0.3.0' | '0.4.0' | '0.5.0' | '0.6.0';
308
+ spec?: '0.3.0' | '0.4.0' | '0.5.0' | '0.6.0' | '0.7.0';
257
309
  /**
258
310
  * Local state declarations (ADR-008), at most 64. Names, counts and per-type rules are checked by
259
311
  * the validator.
@@ -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.6.0, digest 37141b85b9ab9f02af849a62a9c97ba729d6c5c2705130290bcdf45b308ea4cc
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.6.0, digest 37141b85b9ab9f02af849a62a9c97ba729d6c5c2705130290bcdf45b308ea4cc
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.6.0, digest 37141b85b9ab9f02af849a62a9c97ba729d6c5c2705130290bcdf45b308ea4cc
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.6.0, digest 37141b85b9ab9f02af849a62a9c97ba729d6c5c2705130290bcdf45b308ea4cc
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.6.0, digest 37141b85b9ab9f02af849a62a9c97ba729d6c5c2705130290bcdf45b308ea4cc
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.6.0, digest 37141b85b9ab9f02af849a62a9c97ba729d6c5c2705130290bcdf45b308ea4cc
2
+ // Spec 0.7.0, digest 43e44fa9a198d337d686b4e2d86882a06a676ae1e1c1f02d023b3a525795a8b2
3
3
  export {};
@@ -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
+ });
@@ -4,6 +4,7 @@ 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
6
  import { migration050to060 } from "./0.5.0-0.6.0.js";
7
+ import { migration060to070 } from "./0.6.0-0.7.0.js";
7
8
  /** The version an envelope without `spec` was written for. */
8
9
  export const UNDECLARED_SPEC_VERSION = '0.2.0';
9
10
  /** Every step, in order; each `to` is the next step's `from`. */
@@ -12,6 +13,7 @@ export const SPEC_MIGRATIONS = Object.freeze([
12
13
  migration030to040,
13
14
  migration040to050,
14
15
  migration050to060,
16
+ migration060to070,
15
17
  ]);
16
18
  /**
17
19
  * Bring a blueprint envelope up to `target` (the current spec by default)
package/dist/validate.js CHANGED
@@ -275,9 +275,22 @@ export function validateTree(tree, catalog, options = {}) {
275
275
  const flow = options.flow ?? null;
276
276
  const root = options.root ?? { $defs: {} };
277
277
  const warn = (warning) => options.warnings?.push(warning);
278
+ // Spec 0.7.0 (ADR-014): a node key is unique in its tree; the schema checks its pattern.
279
+ const keys = new Set();
278
280
  const visit = (node, path, depth, parent, insideForm) => {
279
281
  if (!isRecord(node) || typeof node.id !== 'string' || depth > MAX_TREE_DEPTH)
280
282
  return;
283
+ if (typeof node.key === 'string' && SPEC_REGEX.nodeKey.test(node.key)) {
284
+ if (keys.has(node.key)) {
285
+ out.push({
286
+ code: 'duplicate-key',
287
+ path: `${path}.key`,
288
+ id: node.id,
289
+ message: `The key "${node.key.slice(0, 64)}" is already used in this tree.`,
290
+ });
291
+ }
292
+ keys.add(node.key);
293
+ }
281
294
  checkSlot(node.slot, parent, node.id, path, out);
282
295
  const component = byId.get(node.id);
283
296
  const context = reportInto({
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** The semantic version of the spec documents in this package. */
2
- export declare const SPEC_VERSION = "0.6.0";
2
+ export declare const SPEC_VERSION = "0.7.0";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /** The semantic version of the spec documents in this package. */
2
- export const SPEC_VERSION = '0.6.0';
2
+ export const SPEC_VERSION = '0.7.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@texuli/spec",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "The schema-first blueprint contract: JSON schemas, the catalog vocabulary, deterministic TypeScript and Kotlin projections, and the conformance harness.",
5
5
  "keywords": [
6
6
  "svelte",