@texuli/spec 0.7.0 → 0.9.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,22 @@ 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.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.
17
+ > published to npm as `0.9.0`, a pre-1.0 release (1.0.0 will be the first
18
+ > stable release). Under 0.x, a new minor (`0.10.0`) may contain breaking
19
+ > changes, so pin the exact version or use `^0.9.0`, which accepts only 0.9.x.
20
+
21
+ > **New in `0.9.0`.** `@texuli/spec@0.9.0` carries **spec 0.9.0**, a
22
+ > stamp-only catalog projection refresh (the Button pending note and the
23
+ > cartesian charts' `xKind` and `locale`, from the Protoyard gaps G94 and
24
+ > G97): the envelope, the `$defs` and the diagnostic codes are unchanged,
25
+ > and every `0.8.0` envelope migrates by stamping `spec`.
26
+
27
+ > **New in `0.8.0`.** `@texuli/spec@0.8.0` carried **spec 0.8.0**, which adds
28
+ > data-source bindings (the `@texuli/spec/source` subpath), the
29
+ > agent-emittable `schema-form`, `canvas`, `spreadsheet`, `data-dashboard`
30
+ > and `data-dashboard-widget` ids with their closed shapes, the closed
31
+ > expression AST, and the pinned `schemas/canvas.schema.json`. Every
32
+ > `0.7.0` envelope migrates by stamping `spec`.
20
33
 
21
34
  > **New in `0.7.0`.** `@texuli/spec@0.7.0` carries **spec 0.7.0**, which adds
22
35
  > an optional node `key` (unique in the tree), the agent-emittable
@@ -68,7 +81,7 @@ import {
68
81
  validateInstance,
69
82
  } from '@texuli/spec';
70
83
 
71
- SPEC_VERSION; // the spec version: '0.7.0' in @texuli/spec@0.7.0
84
+ SPEC_VERSION; // the spec version: '0.9.0' in @texuli/spec@0.9.0
72
85
  SPEC_COMPONENT_IDS.includes('alert'); // true: the closed component vocabulary
73
86
 
74
87
  // The dialect validator: a closed subset of JSON Schema 2020-12.
@@ -100,7 +113,7 @@ import { type Schema, type SpecCatalog, validateBlueprint } from '@texuli/spec';
100
113
  import { migrateBlueprint } from '@texuli/spec/migrate';
101
114
  import { validateWorkflowGraph } from '@texuli/spec/workflow';
102
115
 
103
- const migrated = migrateBlueprint(blueprint); // 0.2.0 → … → 0.6.0 → 0.7.0; never throws
116
+ const migrated = migrateBlueprint(blueprint); // 0.2.0 → … → 0.8.0 → 0.9.0; never throws
104
117
  const result = validateBlueprint(migrated.blueprint, {
105
118
  blueprintSchema: blueprintSchemaJson as Schema,
106
119
  catalog: componentsJson as SpecCatalog,
@@ -121,8 +134,11 @@ refused state or flow declaration), `unknown-state` (a binding or action
121
134
  names no declared variable), `binding` (a malformed, misplaced, read-only or
122
135
  ill-fitting binding), `action` (an action with the wrong fields or target),
123
136
  `unknown-event` (an `on` key the component does not fire) and `workflow` (a
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`
137
+ workflow graph rule), from spec 0.7.0, `duplicate-key` (a node key used
138
+ twice in the tree), and, from spec 0.8.0, `source` (a malformed, misplaced or
139
+ ill-fitting data-source binding, or any binding when the host passes no
140
+ `sources`) and `unknown-source` (a source the host never registered).
141
+ Deprecated components and props add a `deprecated`
126
142
  warning without failing.
127
143
 
128
144
  ## What's inside
@@ -135,6 +151,7 @@ warning without failing.
135
151
  | `@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
152
  | `@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
153
  | `@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` |
154
+ | `@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` |
138
155
  | `@texuli/spec/codegen` | Build time: generation and conformance | `buildSpec`, `projectCatalog`, `checkConformance`, `checkCatalogContract`, `formatJson`, `nextSpecVersion`, `checkVersionHistory` |
139
156
 
140
157
  ## Key guarantees
@@ -195,6 +212,44 @@ warning without failing.
195
212
  nodes without a matching view or event, and writers of a host-bound
196
213
  variable. The spec, `@texuli/catalog`'s `validateDraft` and the Kotlin
197
214
  twin run the same rules.
215
+ - **Data as a named source, never a request (spec 0.8.0, new in `0.8.0`).** A
216
+ component's `sourceable` prop may read rows with
217
+ `{"$source": name, "query": {...}, "map": {...}}`: an opaque name the host
218
+ registered, and a closed query (projection, the `@texuli/data` grid filter
219
+ model, sort, search, a page size). It is checked against the host's source
220
+ descriptors (`SpecDocuments.sources`) and refused without them; nothing in
221
+ it is a URL, SQL or code
222
+ ([source.md](https://github.com/DailybotHQ/texuli/blob/main/packages/spec/docs/source.md)).
223
+ - **Forms as data, never patterns or code (spec 0.8.0, new in `0.8.0`).** The
224
+ agent-emittable `schema-form` id carries a closed form schema
225
+ (`$defs.formSchema`): fields of 15 closed types in sections or steps,
226
+ named formats and host validator names (never a regular expression),
227
+ and conditions and cross-field rules in the closed expression AST of
228
+ `@texuli/core/expression` (`$defs.expression`, the `numberFormula` and
229
+ `condition` shapes extended with strings, booleans, field references and
230
+ calls to a closed function table). The schema checks the shape; the
231
+ forms validator (`@texuli/forms/model`, `checkForm` in `validateDraft`)
232
+ checks the rest at the host's tier. The Kotlin twin decodes the shape and
233
+ its list caps and paints a placeholder (planned).
234
+ - **Dashboards as data (spec 0.8.0, new in `0.8.0`).** The agent-emittable
235
+ `data-dashboard` id holds `data-dashboard-widget` children (`widgetId`,
236
+ `title` and a `place` in grid units); widget content is ordinary catalog
237
+ nodes, so its data comes only from host sources. The dashboard's `layout`
238
+ (a bounded list of `{id, x, y, w, h}` grid units) is bindable and
239
+ sourceable, so a state or a host source persists what people arrange. The
240
+ schema bounds the grid units; `@texuli/catalog` checks the composition
241
+ (widgets only directly in a dashboard, unique ids) on the web. The Kotlin
242
+ twin decodes the props and paints a placeholder (planned).
243
+ - **Spreadsheets as data, formulas in a closed grammar (spec 0.8.0,
244
+ new in `0.8.0`).** The agent-emittable `spreadsheet` id (not `sheet`, which
245
+ is the texuli `Sheet` overlay) carries a sheet in its `sheet` prop
246
+ (`$defs.sheet`, `$defs.sheetTab`): `format` `texuli-sheet`, 1 to 16 tabs
247
+ of `{ id, name, rows, columns, cells }` with cells keyed by A1 address.
248
+ The schema checks that envelope and its caps; the sheet validator
249
+ (`@texuli/sheets/model`, `checkSheet` in `validateDraft`) parses every
250
+ formula with the sheet grammar (no `INDIRECT`, `OFFSET` or `RAND`, a
251
+ closed function set). The Kotlin twin decodes the envelope and its caps;
252
+ painting it is planned.
198
253
  - **List item text never hides or reorders itself.** String fields of list
199
254
  state items refuse control characters, bidirectional overrides and
200
255
  isolates, and invisible formatting characters (`SPEC_PATTERNS.listText`;
@@ -225,9 +280,15 @@ written for (`spec`; omitted means 0.2.0). `migrateBlueprint`
225
280
  (`@texuli/spec/migrate`) brings an
226
281
  envelope up to the current spec with pure, per-version steps
227
282
  (`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
283
+ `0.5.0 → 0.6.0`, `0.6.0 → 0.7.0`, `0.7.0 → 0.8.0` and `0.8.0 → 0.9.0`,
284
+ each of which only stamps `spec`); a compatible version range is planned,
285
+ not built. `@texuli/spec@0.9.0` carries spec `0.9.0` (digest
286
+ `497df4d3b0c67d53483163ebd9bd73733b52caf5f8e2966478f1cf860e6f9d9d`), an
287
+ additive catalog projection refresh (PLAN_011 T-gaps-87-95 and
288
+ T-gaps-96-98); `@texuli/spec@0.8.0` carried spec `0.8.0` (digest
289
+ `f6b6388280d247eb9c3106a925111f35b90d1ec3b59c23652542f06a9f103daf`), which
290
+ adds data-source bindings, forms, canvases, dashboards and spreadsheets;
291
+ `@texuli/spec@0.7.0` carried spec `0.7.0` (digest
231
292
  `43e44fa9a198d337d686b4e2d86882a06a676ae1e1c1f02d023b3a525795a8b2`), which
232
293
  adds node keys and documents; `@texuli/spec@0.6.0` carried spec `0.6.0` (digest
233
294
  `37141b85b9ab9f02af849a62a9c97ba729d6c5c2705130290bcdf45b308ea4cc`), which
@@ -255,6 +316,8 @@ conformance harness) are in
255
316
  [motion.md](https://github.com/DailybotHQ/texuli/blob/main/packages/spec/docs/motion.md)
256
317
  - Streamed blueprints (the incremental tokenizer, the canonical order and
257
318
  node shells; new in `0.6.0`): [stream.md](https://github.com/DailybotHQ/texuli/blob/main/packages/spec/docs/stream.md)
319
+ - Data-source bindings (spec 0.8.0; new in `0.8.0`):
320
+ [source.md](https://github.com/DailybotHQ/texuli/blob/main/packages/spec/docs/source.md)
258
321
  - The Kotlin Multiplatform twin that consumes the generated models:
259
322
  [native/README.md](https://github.com/DailybotHQ/texuli/blob/main/native/README.md)
260
323
  - Architecture: [ARCHITECTURE.md](https://github.com/DailybotHQ/texuli/blob/main/docs/ARCHITECTURE.md)
package/dist/build.d.ts CHANGED
@@ -7,8 +7,9 @@ export declare const NORMATIVE_DIRS: readonly ["schemas/", "catalog/", "fixtures
7
7
  * Normative schemas that are pinned by digest but are not in the dialect and
8
8
  * generate nothing: they are exported by a higher-layer engine and kept here
9
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()`.
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()`.
12
13
  */
13
14
  export declare const EXTERNAL_SCHEMAS: readonly string[];
14
15
  /** The schema documents, their generated names and output files. */
package/dist/build.js CHANGED
@@ -11,10 +11,14 @@ export const NORMATIVE_DIRS = ['schemas/', 'catalog/', 'fixtures/'];
11
11
  * Normative schemas that are pinned by digest but are not in the dialect and
12
12
  * generate nothing: they are exported by a higher-layer engine and kept here
13
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()`.
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()`.
16
17
  */
17
- export const EXTERNAL_SCHEMAS = Object.freeze(['schemas/document.schema.json']);
18
+ export const EXTERNAL_SCHEMAS = Object.freeze([
19
+ 'schemas/canvas.schema.json',
20
+ 'schemas/document.schema.json',
21
+ ]);
18
22
  /** The schema documents, their generated names and output files. */
19
23
  export const SCHEMA_UNITS = [
20
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 };
@@ -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}),`);
@@ -135,7 +135,15 @@ export function checkConformance(normative, committed) {
135
135
  }
136
136
  }
137
137
  if (blueprintSchema) {
138
- 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
+ };
139
147
  for (const path of Object.keys(normative).filter((p) => p.startsWith('fixtures/golden/'))) {
140
148
  const result = validateBlueprint(json(path), documents);
141
149
  for (const d of result.diagnostics) {
@@ -188,6 +196,16 @@ export function checkCatalogContract(catalog, specVersion = SPEC_VERSION) {
188
196
  });
189
197
  }
190
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
+ }
191
209
  if (new Set(component.events ?? []).size !== (component.events ?? []).length) {
192
210
  issues.push({ check: 'catalog', path, message: `${component.id}: events repeat` });
193
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", "document"];
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[];
@@ -58,6 +58,9 @@ export declare const SPEC_LIMITS: Readonly<{
58
58
  documentBlocks: 2000;
59
59
  /** Operations one `patchDocument` patch may carry (`$defs.documentPatch`, ADR-011). */
60
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;
61
64
  }>;
62
65
  /** The name patterns, as the schemas write them. */
63
66
  export declare const SPEC_PATTERNS: Readonly<{
package/dist/contract.js CHANGED
@@ -33,6 +33,12 @@ export const SPEC_PROP_REFS = [
33
33
  'condition',
34
34
  'workflowGraph',
35
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',
36
42
  ];
37
43
  /** The fields each action op takes (besides `op`) and the ones it needs. */
38
44
  export const ACTION_FIELDS = Object.freeze({
@@ -81,6 +87,9 @@ export const SPEC_LIMITS = Object.freeze({
81
87
  documentBlocks: 2000,
82
88
  /** Operations one `patchDocument` patch may carry (`$defs.documentPatch`, ADR-011). */
83
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,
84
93
  });
85
94
  /** The name patterns, as the schemas write them. */
86
95
  export const SPEC_PATTERNS = Object.freeze({
@@ -111,6 +111,175 @@ export interface BlueprintNumberFormula {
111
111
  ifTrue?: BlueprintNumberFormula;
112
112
  ifFalse?: BlueprintNumberFormula;
113
113
  }
114
+ /**
115
+ * The closed expression AST of @texuli/core/expression (spec 0.8.0, ADR-015): the numberFormula and
116
+ * condition shapes extended with strings, booleans, null, field references and calls to a closed
117
+ * function table (len, lower, upper, trim, contains, startsWith, endsWith, isEmpty, coalesce,
118
+ * includes, count, sum, abs, floor, ceil, number, today, date, isDate, daysBetween, addDays, year,
119
+ * month, day). Structural shape only: which members each op needs, the function table and arity, at
120
+ * most 1,000 nodes and depth 32 are checked by the runtime parser; ref and range are reserved for
121
+ * dialects (sheets) and refused in forms. Never code, never a regular expression.
122
+ */
123
+ export interface BlueprintExpression {
124
+ op: 'num' | 'str' | 'bool' | 'null' | 'var' | 'field' | 'ref' | 'range' | 'add' | 'sub' | 'mul' | 'div' | 'mod' | 'pow' | 'neg' | 'lt' | 'lte' | 'gt' | 'gte' | 'eq' | 'neq' | 'and' | 'or' | 'not' | 'if' | 'min' | 'max' | 'concat' | 'round' | 'clamp' | 'call';
125
+ /**
126
+ * A literal for num (number), str (string, at most 32,768 characters) and bool; an expression for
127
+ * neg, not, round and clamp.
128
+ */
129
+ value?: unknown;
130
+ /** The field (or var) a reference reads. */
131
+ name?: string;
132
+ /** A dialect reference (sheets); refused in forms. */
133
+ ref?: string;
134
+ from?: string;
135
+ to?: string;
136
+ left?: BlueprintExpression;
137
+ right?: BlueprintExpression;
138
+ base?: BlueprintExpression;
139
+ exponent?: BlueprintExpression;
140
+ /** Operands of and, or, min, max, concat and call (at most 64). */
141
+ args?: readonly BlueprintExpression[];
142
+ digits?: number;
143
+ min?: BlueprintExpression;
144
+ max?: BlueprintExpression;
145
+ test?: BlueprintExpression;
146
+ ifTrue?: BlueprintExpression;
147
+ ifFalse?: BlueprintExpression;
148
+ /** A function of the closed table. */
149
+ fn?: string;
150
+ }
151
+ /** One choice of a select, radio or multiselect field. */
152
+ export interface BlueprintFormOption {
153
+ value: string;
154
+ label: string;
155
+ description?: string;
156
+ }
157
+ /**
158
+ * Conditions on a field as expressions over earlier fields: shown while show holds, required while
159
+ * require holds, editable while enable holds. Hidden and disabled fields are not validated or
160
+ * submitted; only true holds (unknown does not).
161
+ */
162
+ export interface BlueprintFormWhen {
163
+ show?: BlueprintExpression;
164
+ require?: BlueprintExpression;
165
+ enable?: BlueprintExpression;
166
+ }
167
+ /** When a step or section shows: an expression over earlier fields. */
168
+ export interface BlueprintFormVisibility {
169
+ show?: BlueprintExpression;
170
+ }
171
+ /** A help link under a field; href passes the URL policy at the host's tier. */
172
+ export interface BlueprintFormLink {
173
+ label: string;
174
+ href: string;
175
+ }
176
+ /** Plain-text messages that replace the defaults per rule ({label}, {min}, {max} are filled in). */
177
+ export interface BlueprintFormMessages {
178
+ required?: string;
179
+ minLength?: string;
180
+ maxLength?: string;
181
+ min?: string;
182
+ max?: string;
183
+ step?: string;
184
+ format?: string;
185
+ minItems?: string;
186
+ maxItems?: string;
187
+ option?: string;
188
+ fileType?: string;
189
+ fileSize?: string;
190
+ }
191
+ /**
192
+ * One field of a closed type. Which members apply to which type, value types, option membership,
193
+ * ranges and the conditions' field references are checked by @texuli/forms/model
194
+ * (validateFormSchema). Formats are names; a pattern is never accepted.
195
+ */
196
+ export interface BlueprintFormField {
197
+ /** The key in the submitted values, unique in the form. */
198
+ name: string;
199
+ type: 'text' | 'textarea' | 'number' | 'email' | 'url' | 'date' | 'time' | 'select' | 'multiselect' | 'checkbox' | 'radio' | 'switch' | 'slider' | 'file' | 'tags';
200
+ label: string;
201
+ description?: string;
202
+ placeholder?: string;
203
+ /**
204
+ * A value of the field's type (a string, number, boolean, YYYY-MM-DD, HH:MM or a list of option
205
+ * values). Not for file fields.
206
+ */
207
+ default?: unknown;
208
+ options?: readonly BlueprintFormOption[];
209
+ required?: boolean;
210
+ when?: BlueprintFormWhen;
211
+ minLength?: number;
212
+ maxLength?: number;
213
+ /** A number for number and slider; YYYY-MM-DD for date; HH:MM for time. */
214
+ min?: unknown;
215
+ /** A number for number and slider; YYYY-MM-DD for date; HH:MM for time. */
216
+ max?: unknown;
217
+ /** Above 0, for number and slider. */
218
+ step?: number;
219
+ minItems?: number;
220
+ maxItems?: number;
221
+ /** A named format, checked by owned linear-time code. */
222
+ format?: 'email' | 'url' | 'phone' | 'postal-code' | 'postal-code-us' | 'postal-code-ca' | 'postal-code-uk' | 'postal-code-de' | 'postal-code-fr' | 'postal-code-es' | 'postal-code-mx' | 'postal-code-br' | 'postal-code-co' | 'postal-code-in' | 'postal-code-jp' | 'postal-code-au' | 'digits' | 'alphanumeric' | 'slug' | 'iso-date' | 'time' | 'hex-color';
223
+ /**
224
+ * The name of a validator the host registered (sync or async); an unregistered name fails closed.
225
+ */
226
+ validator?: string;
227
+ messages?: BlueprintFormMessages;
228
+ rows?: number;
229
+ /** File types: MIME types (image/png, image/*) or extensions (.pdf). */
230
+ accept?: readonly string[];
231
+ /** Largest accepted file in bytes; the host uploads files. */
232
+ maxSize?: number;
233
+ /** An autocomplete token; payment, credential and one-time-code tokens are absent. */
234
+ autocomplete?: 'off' | 'name' | 'given-name' | 'family-name' | 'nickname' | 'email' | 'tel' | 'organization' | 'organization-title' | 'street-address' | 'address-level1' | 'address-level2' | 'postal-code' | 'country-name' | 'language' | 'url' | 'bday';
235
+ link?: BlueprintFormLink;
236
+ }
237
+ /** A group of fields (a fieldset when titled). */
238
+ export interface BlueprintFormSection {
239
+ id: string;
240
+ title?: string;
241
+ description?: string;
242
+ when?: BlueprintFormVisibility;
243
+ fields: readonly BlueprintFormField[];
244
+ }
245
+ /**
246
+ * One step of a multi-step form: exactly one of fields or sections; validated before the next step
247
+ * opens.
248
+ */
249
+ export interface BlueprintFormStep {
250
+ id: string;
251
+ title: string;
252
+ description?: string;
253
+ when?: BlueprintFormVisibility;
254
+ fields?: readonly BlueprintFormField[];
255
+ sections?: readonly BlueprintFormSection[];
256
+ }
257
+ /**
258
+ * A cross-field rule: fails when test is false (unknown passes); the message attaches to field, or
259
+ * to the form.
260
+ */
261
+ export interface BlueprintFormRule {
262
+ id: string;
263
+ test: BlueprintExpression;
264
+ message: string;
265
+ field?: string;
266
+ }
267
+ /**
268
+ * A form as data (spec 0.8.0, ADR-015), the schema prop of a schema-form node: a title and exactly
269
+ * one of fields (one page), sections or steps (a multi-step flow), plus cross-field rules. At most
270
+ * 200 fields, 50 sections, 20 steps and 50 rules. Checked again by @texuli/forms/model at the
271
+ * host's tier (validateDraft): closed members per type, unique names, options, ranges, named
272
+ * formats, link URLs, and conditions that read earlier fields only.
273
+ */
274
+ export interface BlueprintFormSchema {
275
+ title: string;
276
+ description?: string;
277
+ submitLabel?: string;
278
+ fields?: readonly BlueprintFormField[];
279
+ sections?: readonly BlueprintFormSection[];
280
+ steps?: readonly BlueprintFormStep[];
281
+ rules?: readonly BlueprintFormRule[];
282
+ }
114
283
  /** A local state variable name. */
115
284
  export type BlueprintStateName = string;
116
285
  /** A host data source name, such as tickets.open. */
@@ -273,6 +442,114 @@ export interface BlueprintDocumentPatchOperation {
273
442
  }
274
443
  /** A bounded RFC 6902 patch (ADR-011): at most 1,000 operations; test operations are encouraged. */
275
444
  export type BlueprintDocumentPatch = readonly BlueprintDocumentPatchOperation[];
445
+ /**
446
+ * A Texuli canvas (spec 0.8.0, ADR-012): the format, version, metadata, grid, shapes and connectors
447
+ * of @texuli/canvas. This contract checks the envelope of the canvas only; the closed shape
448
+ * vocabulary and palette, text safety, the URL policy and the caps are checked by
449
+ * @texuli/canvas/model (schemas/canvas.schema.json) inside the runtime validator, @texuli/catalog's
450
+ * validateDraft, at the host's tier (the host supplies checkCanvas).
451
+ */
452
+ export interface BlueprintCanvas {
453
+ format: 'texuli-canvas';
454
+ formatVersion: 1;
455
+ /** Title, locale and description. */
456
+ meta?: Readonly<Record<string, unknown>>;
457
+ /** The snap grid in world units. */
458
+ grid?: number;
459
+ /**
460
+ * The shapes, bottom to top: rect, ellipse, diamond, line, arrow, path, text, sticky, image (a
461
+ * host ref and alt text), frame and group (which hold shapes).
462
+ */
463
+ shapes: readonly Readonly<Record<string, unknown>>[];
464
+ /**
465
+ * Connectors between shapes or points, routed straight, elbow or curved, with optional caps and a
466
+ * label.
467
+ */
468
+ connectors: readonly Readonly<Record<string, unknown>>[];
469
+ }
470
+ /**
471
+ * A Texuli sheet, the sheet prop of a spreadsheet node (spec 0.8.0, ADR-012 and ADR-015): the
472
+ * format, version, metadata and tabs of @texuli/sheets. This contract checks the envelope of the
473
+ * sheet only (format, version, members, at most 16 tabs, each with an id, a name, a row and column
474
+ * count within the caps, and cells keyed by A1 address); the cell inputs, the formula grammar and
475
+ * its closed function set, text safety and the cell cap are checked by @texuli/sheets/model
476
+ * (validateSheet) inside the runtime validator, @texuli/catalog's validateDraft (the host supplies
477
+ * checkSheet).
478
+ */
479
+ export interface BlueprintSheet {
480
+ format: 'texuli-sheet';
481
+ formatVersion: 1;
482
+ /** The title. */
483
+ meta?: Readonly<Record<string, unknown>>;
484
+ /**
485
+ * The tabs, in order: { id, name, rows (1 to 10,000), columns (1 to 256), cells } where cells
486
+ * maps A1 addresses to a number, a boolean, text or formula text that starts with =.
487
+ */
488
+ sheets: readonly BlueprintSheetTab[];
489
+ }
490
+ /**
491
+ * One tab of a sheet: a stable id, the name formulas use, the row and column counts, and the cell
492
+ * inputs by A1 address.
493
+ */
494
+ export interface BlueprintSheetTab {
495
+ id: string;
496
+ name: string;
497
+ rows: number;
498
+ columns: number;
499
+ cells?: Readonly<Record<string, unknown>>;
500
+ }
501
+ /**
502
+ * A data source the host registered (spec 0.8.0, ADR-015): an opaque name, never a URL. Checked
503
+ * against the host's registry; with no registry a binding is refused.
504
+ */
505
+ export type BlueprintSourceName = string;
506
+ /** A field the source declares. */
507
+ export type BlueprintSourceFieldName = string;
508
+ /**
509
+ * The @texuli/data grid filter model: a condition ({type: condition, field, operator, value}) or an
510
+ * and/or group ({type: group, op, children}). At most 6 levels and 64 nodes. Which members each
511
+ * type takes, the operators each field allows and the value types are checked by the validator
512
+ * against the source's declaration.
513
+ */
514
+ export interface BlueprintSourceFilter {
515
+ type: 'condition' | 'group';
516
+ field?: BlueprintSourceFieldName;
517
+ operator?: 'eq' | 'neq' | 'contains' | 'in' | 'between' | 'before' | 'after' | 'isEmpty' | 'isNotEmpty';
518
+ /**
519
+ * A string, number, boolean or null; a list of them for in (at most 100) and between (exactly 2).
520
+ * Data, never code.
521
+ */
522
+ value?: unknown;
523
+ op?: 'and' | 'or';
524
+ children?: readonly BlueprintSourceFilter[];
525
+ }
526
+ /** One sort key on a sortable field. */
527
+ export interface BlueprintSourceSort {
528
+ field: BlueprintSourceFieldName;
529
+ direction?: 'asc' | 'desc';
530
+ }
531
+ /**
532
+ * The closed query a data-source binding sends (ADR-015): projection, a filter, sort keys, search
533
+ * text and a page size. The host adapter receives it validated and decides how to run it; Texuli
534
+ * never builds URLs, SQL or code from it.
535
+ */
536
+ export interface BlueprintSourceQuery {
537
+ fields?: readonly BlueprintSourceFieldName[];
538
+ filter?: BlueprintSourceFilter;
539
+ sort?: readonly BlueprintSourceSort[];
540
+ search?: string;
541
+ pageSize?: number;
542
+ }
543
+ /**
544
+ * Reads a whole sourceable prop from a host data source (spec 0.8.0, ADR-015): {"$source": name,
545
+ * "query": {...}, "map": {itemField: sourceField}}. The player loads the rows through the host's
546
+ * registry; map renames source fields to the component's item fields (at most 16 entries).
547
+ */
548
+ export interface BlueprintSourceBinding {
549
+ $source: BlueprintSourceName;
550
+ query?: BlueprintSourceQuery;
551
+ map?: Readonly<Record<string, BlueprintSourceFieldName>>;
552
+ }
276
553
  /**
277
554
  * Envelope version 1 around one catalog tree (ADR-004, audit G13). Mirrors what
278
555
  * @texuli/catalog/envelope reads; a declared status, activation or tier is display data, never
@@ -280,8 +557,14 @@ export type BlueprintDocumentPatch = readonly BlueprintDocumentPatchOperation[];
280
557
  * state and a flow; spec 0.6.0 (ADR-012, ADR-013) adds the closed motion vocabulary: a node motion
281
558
  * and the envelope motion level; spec 0.7.0 (ADR-011, ADR-012, ADR-014) adds an optional node key
282
559
  * (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.
560
+ * patchDocument edit op with its bounded RFC 6902 patch. Spec 0.8.0 (ADR-012, ADR-015) adds
561
+ * data-source bindings: a sourceable prop may read rows from a source the host registered
562
+ * ({"$source": name, "query": {...}}), with a closed query, and forms as data: the closed form
563
+ * schema a `schema-form` node carries ($defs.formSchema) with conditions and cross-field rules in
564
+ * the closed expression AST of @texuli/core/expression ($defs.expression). Spec 0.8.0 (ADR-012)
565
+ * adds the canvas shape a `canvas` node carries ($defs.canvas). Spec 0.9.0 refreshes the catalog
566
+ * projection only (an additive minor; the envelope shape is unchanged). Planned minor additions:
567
+ * the native profile and several roots.
285
568
  */
286
569
  export interface BlueprintEnvelope {
287
570
  format: 'texuli-blueprint';
@@ -305,7 +588,7 @@ export interface BlueprintEnvelope {
305
588
  /**
306
589
  * The spec version the envelope was written for. Omitted means 0.2.0; migrateBlueprint stamps it.
307
590
  */
308
- spec?: '0.3.0' | '0.4.0' | '0.5.0' | '0.6.0' | '0.7.0';
591
+ spec?: '0.3.0' | '0.4.0' | '0.5.0' | '0.6.0' | '0.7.0' | '0.8.0' | '0.9.0';
309
592
  /**
310
593
  * Local state declarations (ADR-008), at most 64. Names, counts and per-type rules are checked by
311
594
  * 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.7.0, digest 43e44fa9a198d337d686b4e2d86882a06a676ae1e1c1f02d023b3a525795a8b2
2
+ // Spec 0.9.0, digest 497df4d3b0c67d53483163ebd9bd73733b52caf5f8e2966478f1cf860e6f9d9d
3
3
  export {};
@@ -14,6 +14,11 @@ export interface SpecComponent {
14
14
  events?: readonly SpecEventName[];
15
15
  /** Props a node may two-way bind (node bind). */
16
16
  bindable?: readonly string[];
17
+ /**
18
+ * Props a node may read from a host data source (spec 0.8.0, ADR-015): {"$source": name, "query":
19
+ * {...}}. Each names an array prop whose items are rows.
20
+ */
21
+ sourceable?: readonly string[];
17
22
  deprecated?: SpecDeprecation;
18
23
  }
19
24
  export interface SpecProp {
@@ -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.7.0, digest 43e44fa9a198d337d686b4e2d86882a06a676ae1e1c1f02d023b3a525795a8b2
2
+ // Spec 0.9.0, digest 497df4d3b0c67d53483163ebd9bd73733b52caf5f8e2966478f1cf860e6f9d9d
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' | 'duplicate-key';
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' | 'source' | 'unknown-source';
8
8
  export interface SpecWarning {
9
9
  code: SpecWarningCode;
10
10
  path: string;