@texuli/spec 0.1.0-next.0 → 0.1.0-next.1

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
@@ -11,6 +11,13 @@ framework-free. Kotlin is generated as committed text by this package's
11
11
  TypeScript generator; no Kotlin toolchain runs here, and nothing is compiled
12
12
  or published to Maven.
13
13
 
14
+ The package tarball build compiles `generated/typescript/` into the matching
15
+ `dist/generated/typescript/` location. The source stays beside `src/` so the
16
+ spec generator and integrity manifest keep their established output paths;
17
+ the packer maps runtime and declaration imports to emitted `.js` and `.d.ts`
18
+ files there. Kotlin projections remain generated text and are not part of the
19
+ TypeScript package output.
20
+
14
21
  ## Layout
15
22
 
16
23
  | Path | What it holds |
@@ -18,9 +25,10 @@ or published to Maven.
18
25
  | `schemas/` | The normative JSON schemas: `blueprint` (envelope v1 and the node tree), `catalog`, `diagnostics`, `manifest` |
19
26
  | `catalog/components.json` | The closed vocabulary, projected from the built catalog: id, label, summary, group, placement (`app`/`content`), emit tiers, capability and props |
20
27
  | `catalog/capabilities.json` | Reviewed capability claims. Empty: no id is audited portable yet, so every capability is `unknown` (ADR-003) |
21
- | `fixtures/golden/` | Blueprints that must validate: a report, a dashboard and a proactive deck (fictional data) |
22
- | `fixtures/invalid/` | Blueprints that must fail with exact diagnostic codes, one or more per code |
28
+ | `fixtures/golden/` | Blueprints that must validate: a report, a dashboard, a proactive deck and a report with a named slot and a `when` condition (fictional data) |
29
+ | `fixtures/invalid/` | Blueprints that must fail with exact diagnostic codes, one or more per code, including slot and condition cases (an undeclared or root `slot`, an unknown condition `op`, an extra condition field) |
23
30
  | `manifest.json` | The spec version, the dialect and a SHA-256 per normative file |
31
+ | `versions.json` | Every released spec version pinned to its spec digest (not normative: it pins the normative files) |
24
32
  | `generated/typescript/`, `generated/kotlin/` | The projections. Never edit them |
25
33
  | `generated-manifest.json` | A SHA-256 per generated file |
26
34
 
@@ -40,7 +48,8 @@ maps. Anything else fails `checkSchemaDocument`, and generation refuses it.
40
48
  - `validateInstance` (the dialect validator);
41
49
  - `validateBlueprint` (the target-neutral blueprint check: envelope
42
50
  schema, closed ids and props, prop kinds and enums, required props, no
43
- platform-extension props from data, the portable profile failing closed,
51
+ platform-extension props from data, a node `slot` that names a slot prop
52
+ of its parent (`unknown-slot`), the portable profile failing closed,
44
53
  and an optional catalog binding);
45
54
  - `canonicalJson` and a synchronous `sha256`.
46
55
  - `@texuli/spec/codegen`, build time: `buildSpec`, `projectCatalog`,
@@ -84,10 +93,68 @@ pnpm exec tsx packages/spec/scripts/generate.ts --check # fail on drift
84
93
  blueprint schema matches the envelope `@texuli/catalog` reads;
85
94
  - generation is deterministic and refuses schemas outside the dialect;
86
95
  - the Kotlin text declares its package, balances its brackets, defines
87
- every type it references and decodes closed.
96
+ every type it references and decodes closed;
97
+ - the spec version and digest match the newest release in `versions.json`.
88
98
 
89
99
  When the catalog changes, refresh `components.json`, regenerate and review
90
- the diff.
100
+ the diff. Then classify the change, bump the version, and pin it (see
101
+ Versioning).
102
+
103
+ ## Versioning
104
+
105
+ **The spec digest** is the SHA-256 of `manifest.json`'s canonical JSON: the
106
+ spec version, the dialect and a digest per normative file (schemas,
107
+ `catalog/components.json`, `catalog/capabilities.json` and every fixture).
108
+ Every generated file carries it in its stamp (`// Spec 0.2.0, digest …`).
109
+ The same normative files always give the same digest.
110
+
111
+ **The version follows the digest.** Every normative change changes the
112
+ digest, and a changed digest needs a new semantic version.
113
+ `tests/versioning.test.ts` fails when the digest moves without one. The
114
+ release goes in `versions.json`: its version, its digest, its change class
115
+ and a one-line summary.
116
+
117
+ | Class | What it covers | Before 1.0.0 | From 1.0.0 |
118
+ | --- | --- | --- | --- |
119
+ | patch | Nothing accepted changes: descriptions, diagnostic wording, a new fixture for behavior that already holds | patch | patch |
120
+ | minor | Additive: optional fields, new catalog ids, new optional props, new diagnostic codes for data that was already refused, new named extensions. Existing portable fixtures still pass | minor | minor |
121
+ | major | Removing or renaming ids, props or fields; changing a prop type; making something required; changing what accepted data means | minor | major |
122
+
123
+ `nextSpecVersion(previous, class)` computes the step and
124
+ `checkVersionHistory` enforces it. Before 1.0.0, any change to accepted data
125
+ bumps the minor number. Moving to 1.0.0 is a release decision for the owner,
126
+ not a side effect of a change. 0.1.0, the bootstrap, has no pinned digest:
127
+ its normative files moved without a version until 0.2.0.
128
+
129
+ **What a projection change means for consumers:**
130
+ - **TypeScript (`@texuli/spec`):** a patch release leaves the generated types
131
+ and `SPEC_COMPONENT_IDS` alone, apart from the stamp. Code that compiled
132
+ against a minor release keeps compiling, but new optional fields and ids
133
+ can appear in data it reads. A major-class change (a minor bump before
134
+ 1.0.0) may remove or retype what it relied on.
135
+ - **Kotlin and native (`native/`):** `texuli-spec` compiles
136
+ `generated/kotlin` from this package, so a native build always matches the
137
+ checked-out spec. The generated decoders are closed: a blueprint that uses
138
+ a field from a newer spec fails to decode on an older native build with a
139
+ `schema` diagnostic. It does not pass silently. After a change, regenerate,
140
+ rebuild native, and refresh the conformance traces when goldens moved
141
+ (`pnpm exec tsx native/conformance/web/trace.ts --write`). Native
142
+ conformance compares the native refusals with every invalid fixture's
143
+ codes.
144
+ - **Blueprints:** a blueprint binds to an exact catalog hash today. The
145
+ envelope field for a compatible spec range is **planned, not built**.
146
+ Until it exists, a consumer checks the spec version it ships against the
147
+ stamp of the spec it was built from.
148
+
149
+ Migrations between versions (deterministic blueprint transforms tested per
150
+ version pair) are **planned, not built**: nothing has needed one yet.
151
+
152
+
153
+ ## Named slots and conditions
154
+
155
+ A node may carry `slot` (audit G04: the parent's named slot, a string such as
156
+ `hero`) and `when` (audit G07: the blocks `Condition` AST that shows the node
157
+ when true). Both match what `@texuli/catalog` renders.
91
158
 
92
159
  ## Not yet
93
160
 
@@ -96,3 +163,8 @@ the diff.
96
163
  - Making the spec authoritative for the web packages (B1 step 3 cutover);
97
164
  today the catalog manifests are the bootstrap input.
98
165
  - Compiling the Kotlin, and the native runtime (B2).
166
+ - A full type for `when`. The dialect has no unions, so `condition` and
167
+ `numberFormula` are closed records whose `op` is an enum and whose operands
168
+ are typed (except `value`, which is a number for `num` and a formula for
169
+ `round` and `clamp`). Which operands each `op` needs, and the formula
170
+ limits, are checked by the runtime parser in `@texuli/blocks/formula`.
package/dist/catalog.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * projection from a built catalog. The projection is the bootstrap step of
4
4
  * spec/Compose design §2.3; the parity check reruns it to catch drift.
5
5
  */
6
- import type { SpecCatalog, SpecComponent, SpecProp } from '../generated/typescript/catalog-schema.js';
6
+ import type { SpecCatalog, SpecComponent, SpecProp } from './generated/typescript/catalog-schema.js';
7
7
  export type { SpecCatalog, SpecComponent, SpecProp };
8
8
  export declare const CAPABILITIES: readonly ["portable-core", "web-only", "native-only", "unknown"];
9
9
  export type Capability = SpecComponent['capability'];
@@ -9,5 +9,6 @@ export { prettyJson } from '../canonical.js';
9
9
  export { type BuiltCatalogLike, projectCatalog } from '../catalog.js';
10
10
  export { type ConformanceIssue, checkConformance } from '../conformance.js';
11
11
  export { formatJson, LINE_WIDTH } from '../format.js';
12
+ export { checkVersionHistory, nextSpecVersion, type SpecChangeClass, type SpecVersionHistory, type SpecVersionPin, type VersionIssue, } from '../versioning.js';
12
13
  export { KOTLIN_PACKAGE, kotlinString } from './kotlin.js';
13
14
  export type { SchemaUnit, Stamp } from './shared.js';
@@ -9,4 +9,5 @@ export { prettyJson } from "../canonical.js";
9
9
  export { projectCatalog } from "../catalog.js";
10
10
  export { checkConformance } from "../conformance.js";
11
11
  export { formatJson, LINE_WIDTH } from "../format.js";
12
+ export { checkVersionHistory, nextSpecVersion, } from "../versioning.js";
12
13
  export { KOTLIN_PACKAGE, kotlinString } from "./kotlin.js";
@@ -47,13 +47,55 @@ export interface BlueprintEditOp {
47
47
  tree?: BlueprintNode;
48
48
  }
49
49
  export type BlueprintNodePath = readonly number[];
50
- /** One catalog node: id, props and children only. */
50
+ /** One catalog node: id, props, children, and an optional named slot and condition. */
51
51
  export interface BlueprintNode {
52
52
  /** A catalog component id. */
53
53
  id: string;
54
54
  /** Props checked against the component's catalog entry. */
55
55
  props?: Readonly<Record<string, unknown>>;
56
56
  children?: readonly BlueprintNode[];
57
+ /**
58
+ * The parent's named slot this node fills (audit G04): a slot prop of the parent's catalog entry.
59
+ * Omitted or "children" is the default slot.
60
+ */
61
+ slot?: string;
62
+ when?: BlueprintCondition;
63
+ }
64
+ /**
65
+ * Shows the node when true (audit G07): the closed Condition AST of the blocks formula grammar.
66
+ * False or unknown hides the node. Structural shape only; which operands each op needs, and the
67
+ * size limits, are checked by the runtime formula parser.
68
+ */
69
+ export interface BlueprintCondition {
70
+ op: 'lt' | 'lte' | 'gt' | 'gte' | 'eq' | 'neq' | 'and' | 'or' | 'not';
71
+ left?: BlueprintNumberFormula;
72
+ right?: BlueprintNumberFormula;
73
+ /** The conditions of and / or. */
74
+ args?: readonly BlueprintCondition[];
75
+ value?: BlueprintCondition;
76
+ }
77
+ /**
78
+ * A number expression of the blocks formula grammar. Structural shape only; the runtime formula
79
+ * parser checks which operands each op needs.
80
+ */
81
+ export interface BlueprintNumberFormula {
82
+ op: 'num' | 'var' | 'add' | 'sub' | 'mul' | 'div' | 'pow' | 'min' | 'max' | 'round' | 'clamp' | 'if';
83
+ /** A number for num; a number formula for round and clamp. */
84
+ value?: unknown;
85
+ /** The input a var reads. */
86
+ name?: string;
87
+ left?: BlueprintNumberFormula;
88
+ right?: BlueprintNumberFormula;
89
+ base?: BlueprintNumberFormula;
90
+ exponent?: BlueprintNumberFormula;
91
+ /** The operands of min / max. */
92
+ args?: readonly BlueprintNumberFormula[];
93
+ digits?: number;
94
+ min?: BlueprintNumberFormula;
95
+ max?: BlueprintNumberFormula;
96
+ test?: BlueprintCondition;
97
+ ifTrue?: BlueprintNumberFormula;
98
+ ifFalse?: BlueprintNumberFormula;
57
99
  }
58
100
  /**
59
101
  * Envelope version 1 around one catalog tree (ADR-003, audit G13). Mirrors what
@@ -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.1.0, digest 47afaa4f056320fc4111239e03afd97e8a073bd8dbd6d2ef0dec2d171acb2f8e
2
+ // Spec 0.2.0, digest d9eccaba1826e7d87648a4a33787d873245592dda843607a10b2c68a77f1806f
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.1.0, digest 47afaa4f056320fc4111239e03afd97e8a073bd8dbd6d2ef0dec2d171acb2f8e
2
+ // Spec 0.2.0, digest d9eccaba1826e7d87648a4a33787d873245592dda843607a10b2c68a77f1806f
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';
7
+ export type SpecDiagnosticCode = 'schema' | 'unknown-id' | 'unknown-prop' | 'prop-type' | 'enum-value' | 'missing-required' | 'extension-prop' | 'catalog-mismatch' | 'unsupported-profile' | 'unknown-slot';
8
8
  /** The stable result shape of validating a blueprint against the spec. */
9
9
  export interface SpecValidation {
10
10
  ok: boolean;
@@ -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.1.0, digest 47afaa4f056320fc4111239e03afd97e8a073bd8dbd6d2ef0dec2d171acb2f8e
2
+ // Spec 0.2.0, digest d9eccaba1826e7d87648a4a33787d873245592dda843607a10b2c68a77f1806f
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.1.0, digest 47afaa4f056320fc4111239e03afd97e8a073bd8dbd6d2ef0dec2d171acb2f8e
2
+ // Spec 0.2.0, digest d9eccaba1826e7d87648a4a33787d873245592dda843607a10b2c68a77f1806f
3
3
  export {};
@@ -1,7 +1,7 @@
1
1
  /** The built catalog this vocabulary was projected from. */
2
- export declare const SPEC_CATALOG_HASH = "2516510c15ec62a630d56770349e47cad35f6d0e1654e8116ab1ef2607572617";
2
+ export declare const SPEC_CATALOG_HASH = "b073e30b4c8b1a30b3ef3ccb372e552d2d88e584616b74591c6c32923b471376";
3
3
  /** Every closed component id, sorted. */
4
- export declare const SPEC_COMPONENT_IDS: readonly ["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", "checkbox", "chip", "code", "combobox", "command-palette", "content-layout", "context-menu", "context-menu-trigger", "description", "detail-panel", "dialog", "drawer", "dropdown-menu", "dropdown-menu-content", "dropdown-menu-item", "dropdown-menu-trigger", "empty-state", "error-state", "field", "field-error", "form", "hover-card", "hover-card-content", "icon-button", "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-settings", "layout-sidebar", "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", "split-view", "stat", "stepper", "switch", "table", "tag", "text-counter", "textarea", "timeline", "toaster", "tooltip"];
4
+ export declare const SPEC_COMPONENT_IDS: readonly ["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", "checkbox", "chip", "code", "combobox", "command-palette", "content-layout", "context-menu", "context-menu-trigger", "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", "form", "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-settings", "layout-sidebar", "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", "split-view", "stat", "stepper", "switch", "table", "tag", "text-counter", "textarea", "timeline", "toaster", "tooltip"];
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 {
@@ -44,6 +44,7 @@ export interface SpecComponentProps {
44
44
  variant?: 'default' | 'inset';
45
45
  };
46
46
  badge: {
47
+ label?: string;
47
48
  variant?: 'default' | 'secondary' | 'outline' | 'success' | 'warning' | 'destructive' | 'info' | 'neutral';
48
49
  };
49
50
  banner: {
@@ -112,6 +113,7 @@ export interface SpecComponentProps {
112
113
  disabled?: boolean;
113
114
  href?: string;
114
115
  hrefTrust?: 'developer' | 'untrusted';
116
+ label?: string;
115
117
  loading?: boolean;
116
118
  loadingLabel?: string;
117
119
  size?: 'sm' | 'md' | 'lg' | 'icon';
@@ -147,6 +149,7 @@ export interface SpecComponentProps {
147
149
  mode?: 'light' | 'dark';
148
150
  series: readonly unknown[];
149
151
  title: string;
152
+ visibleDomain?: string;
150
153
  };
151
154
  'charts-bar': {
152
155
  annotations?: string;
@@ -156,6 +159,7 @@ export interface SpecComponentProps {
156
159
  mode?: 'light' | 'dark';
157
160
  series: readonly unknown[];
158
161
  title: string;
162
+ visibleDomain?: string;
159
163
  };
160
164
  'charts-bubble': {
161
165
  annotations?: string;
@@ -170,6 +174,7 @@ export interface SpecComponentProps {
170
174
  };
171
175
  'charts-bullet': {
172
176
  description?: string;
177
+ format?: Readonly<Record<string, unknown>>;
173
178
  label: string;
174
179
  labels?: Readonly<Record<string, unknown>>;
175
180
  max?: number;
@@ -192,6 +197,7 @@ export interface SpecComponentProps {
192
197
  mode?: 'light' | 'dark';
193
198
  series: readonly unknown[];
194
199
  title: string;
200
+ visibleDomain?: string;
195
201
  };
196
202
  'charts-combo': {
197
203
  annotations?: string;
@@ -201,6 +207,7 @@ export interface SpecComponentProps {
201
207
  mode?: 'light' | 'dark';
202
208
  series: readonly unknown[];
203
209
  title: string;
210
+ visibleDomain?: string;
204
211
  };
205
212
  'charts-donut': {
206
213
  data: readonly unknown[];
@@ -223,6 +230,7 @@ export interface SpecComponentProps {
223
230
  'charts-gauge': {
224
231
  bandLabels?: readonly unknown[];
225
232
  description?: string;
233
+ format?: Readonly<Record<string, unknown>>;
226
234
  label: string;
227
235
  labels?: Readonly<Record<string, unknown>>;
228
236
  max?: number;
@@ -268,6 +276,7 @@ export interface SpecComponentProps {
268
276
  mode?: 'light' | 'dark';
269
277
  series: readonly unknown[];
270
278
  title: string;
279
+ visibleDomain?: string;
271
280
  };
272
281
  'charts-network': {
273
282
  description?: string;
@@ -289,6 +298,7 @@ export interface SpecComponentProps {
289
298
  };
290
299
  'charts-progress-ring': {
291
300
  description?: string;
301
+ format?: Readonly<Record<string, unknown>>;
292
302
  label: string;
293
303
  labels?: Readonly<Record<string, unknown>>;
294
304
  max?: number;
@@ -318,6 +328,7 @@ export interface SpecComponentProps {
318
328
  };
319
329
  'charts-sparkline': {
320
330
  description?: string;
331
+ format?: Readonly<Record<string, unknown>>;
321
332
  height?: number;
322
333
  label: string;
323
334
  labels?: Readonly<Record<string, unknown>>;
@@ -334,6 +345,7 @@ export interface SpecComponentProps {
334
345
  mode?: 'light' | 'dark';
335
346
  series: readonly unknown[];
336
347
  title: string;
348
+ visibleDomain?: string;
337
349
  };
338
350
  'charts-sunburst': {
339
351
  description: string;
@@ -422,6 +434,13 @@ export interface SpecComponentProps {
422
434
  'context-menu-trigger': {
423
435
  disabled?: boolean;
424
436
  };
437
+ 'deck-slide': {
438
+ master?: 'title' | 'section' | 'content' | 'two-column' | 'media-left' | 'media-right' | 'quote' | 'closing';
439
+ notes?: string;
440
+ slideId: string;
441
+ title?: string;
442
+ transition?: 'none' | 'fade' | 'fade-up';
443
+ };
425
444
  description: Record<string, never>;
426
445
  'detail-panel': {
427
446
  closeOnEscape?: boolean;
@@ -508,6 +527,9 @@ export interface SpecComponentProps {
508
527
  loading?: boolean;
509
528
  variant?: 'default' | 'secondary' | 'outline' | 'ghost' | 'destructive' | 'link';
510
529
  };
530
+ 'inline-text': {
531
+ text: string;
532
+ };
511
533
  input: {
512
534
  clearLabel?: string;
513
535
  clearable?: boolean;
@@ -525,7 +547,9 @@ export interface SpecComponentProps {
525
547
  readonly?: boolean;
526
548
  value?: string;
527
549
  };
528
- kbd: Record<string, never>;
550
+ kbd: {
551
+ label?: string;
552
+ };
529
553
  kpi: {
530
554
  comparisonLabel?: string;
531
555
  currency?: string;
@@ -576,6 +600,11 @@ export interface SpecComponentProps {
576
600
  storageKey?: string;
577
601
  };
578
602
  'layout-deck': {
603
+ aspect?: '16:9' | '4:3' | '1:1';
604
+ description?: string;
605
+ locale?: string;
606
+ masters?: readonly unknown[];
607
+ settings?: Readonly<Record<string, unknown>>;
579
608
  slides?: readonly unknown[];
580
609
  title?: string;
581
610
  };
@@ -1,7 +1,7 @@
1
1
  // Generated by @texuli/spec from catalog/components.json. Do not edit: run the spec generator.
2
- // Spec 0.1.0, digest 47afaa4f056320fc4111239e03afd97e8a073bd8dbd6d2ef0dec2d171acb2f8e
2
+ // Spec 0.2.0, digest d9eccaba1826e7d87648a4a33787d873245592dda843607a10b2c68a77f1806f
3
3
  /** The built catalog this vocabulary was projected from. */
4
- export const SPEC_CATALOG_HASH = '2516510c15ec62a630d56770349e47cad35f6d0e1654e8116ab1ef2607572617';
4
+ export const SPEC_CATALOG_HASH = 'b073e30b4c8b1a30b3ef3ccb372e552d2d88e584616b74591c6c32923b471376';
5
5
  /** Every closed component id, sorted. */
6
6
  export const SPEC_COMPONENT_IDS = [
7
7
  'alert',
@@ -55,6 +55,7 @@ export const SPEC_COMPONENT_IDS = [
55
55
  'content-layout',
56
56
  'context-menu',
57
57
  'context-menu-trigger',
58
+ 'deck-slide',
58
59
  'description',
59
60
  'detail-panel',
60
61
  'dialog',
@@ -71,6 +72,7 @@ export const SPEC_COMPONENT_IDS = [
71
72
  'hover-card',
72
73
  'hover-card-content',
73
74
  'icon-button',
75
+ 'inline-text',
74
76
  'input',
75
77
  'input-group',
76
78
  'kbd',
package/dist/index.d.ts CHANGED
@@ -5,11 +5,11 @@
5
5
  * hashing. Build-time generation and the conformance harness live in
6
6
  * `@texuli/spec/codegen`. Framework-free: no DOM, no Svelte and no Node APIs.
7
7
  */
8
- export type * from '../generated/typescript/blueprint.js';
9
- export type * from '../generated/typescript/catalog-schema.js';
10
- export type * from '../generated/typescript/diagnostics.js';
11
- export type * from '../generated/typescript/manifest.js';
12
- export { SPEC_CATALOG_HASH, SPEC_COMPONENT_IDS, type SpecComponentId, type SpecComponentProps, } from '../generated/typescript/vocabulary.js';
8
+ export type * from './generated/typescript/blueprint.js';
9
+ export type * from './generated/typescript/catalog-schema.js';
10
+ export type * from './generated/typescript/diagnostics.js';
11
+ export type * from './generated/typescript/manifest.js';
12
+ export { SPEC_CATALOG_HASH, SPEC_COMPONENT_IDS, type SpecComponentId, type SpecComponentProps, } from './generated/typescript/vocabulary.js';
13
13
  export { canonicalJson, sha256 } from './canonical.js';
14
14
  export { CAPABILITIES, type Capability, type SpecPropKind, WEB_ONLY_KINDS, WEB_ONLY_PROP_NAMES, } from './catalog.js';
15
15
  export { checkSchemaDocument, DIALECT_KEYWORDS, DIALECT_TYPES, DIALECT_URI, type DialectType, type Schema, type SchemaIssue, validateInstance, } from './dialect.js';
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * hashing. Build-time generation and the conformance harness live in
6
6
  * `@texuli/spec/codegen`. Framework-free: no DOM, no Svelte and no Node APIs.
7
7
  */
8
- export { SPEC_CATALOG_HASH, SPEC_COMPONENT_IDS, } from "../generated/typescript/vocabulary.js";
8
+ export { SPEC_CATALOG_HASH, SPEC_COMPONENT_IDS, } from "./generated/typescript/vocabulary.js";
9
9
  export { canonicalJson, sha256 } from "./canonical.js";
10
10
  export { CAPABILITIES, WEB_ONLY_KINDS, WEB_ONLY_PROP_NAMES, } from "./catalog.js";
11
11
  export { checkSchemaDocument, DIALECT_KEYWORDS, DIALECT_TYPES, DIALECT_URI, validateInstance, } from "./dialect.js";
@@ -1,4 +1,4 @@
1
- import type { SpecDiagnostic, SpecDiagnosticCode, SpecValidation } from '../generated/typescript/diagnostics.js';
1
+ import type { SpecDiagnostic, SpecDiagnosticCode, SpecValidation } from './generated/typescript/diagnostics.js';
2
2
  import type { SpecCatalog } from './catalog.js';
3
3
  import { type Schema } from './dialect.js';
4
4
  export type { SpecDiagnostic, SpecDiagnosticCode, SpecValidation };
@@ -10,7 +10,9 @@ export interface SpecDocuments {
10
10
  * Validate a blueprint envelope against the spec alone: the envelope schema,
11
11
  * then every node against the catalog vocabulary (closed ids, closed prop
12
12
  * names, prop kinds and enum values, required props, no platform-extension
13
- * props from data) and the declared profile. With `catalogHash`, an envelope
13
+ * props from data, a `slot` that names a slot prop of the parent) and the
14
+ * declared profile. A `when` condition is checked for shape only; the
15
+ * runtime formula parser checks its operands. With `catalogHash`, an envelope
14
16
  * bound to another catalog is refused. This is the shared, target-neutral
15
17
  * contract; URL policy, limits, grammar and rendering stay in the runtime
16
18
  * validators (`@texuli/catalog/validate`).
package/dist/validate.js CHANGED
@@ -4,7 +4,9 @@ const MAX_TREE_DEPTH = 64;
4
4
  * Validate a blueprint envelope against the spec alone: the envelope schema,
5
5
  * then every node against the catalog vocabulary (closed ids, closed prop
6
6
  * names, prop kinds and enum values, required props, no platform-extension
7
- * props from data) and the declared profile. With `catalogHash`, an envelope
7
+ * props from data, a `slot` that names a slot prop of the parent) and the
8
+ * declared profile. A `when` condition is checked for shape only; the
9
+ * runtime formula parser checks its operands. With `catalogHash`, an envelope
8
10
  * bound to another catalog is refused. This is the shared, target-neutral
9
11
  * contract; URL policy, limits, grammar and rendering stay in the runtime
10
12
  * validators (`@texuli/catalog/validate`).
@@ -30,9 +32,10 @@ export function validateBlueprint(input, documents, options = {}) {
30
32
  export function validateTree(tree, catalog, options = {}) {
31
33
  const byId = new Map(catalog.components.map((c) => [c.id, c]));
32
34
  const out = [];
33
- const visit = (node, path, depth) => {
35
+ const visit = (node, path, depth, parent) => {
34
36
  if (!isRecord(node) || typeof node.id !== 'string' || depth > MAX_TREE_DEPTH)
35
37
  return;
38
+ checkSlot(node.slot, parent, node.id, path, out);
36
39
  const component = byId.get(node.id);
37
40
  if (!component) {
38
41
  out.push({
@@ -55,13 +58,35 @@ export function validateTree(tree, catalog, options = {}) {
55
58
  }
56
59
  if (Array.isArray(node.children)) {
57
60
  node.children.forEach((child, index) => {
58
- visit(child, `${path}.children[${index}]`, depth + 1);
61
+ visit(child, `${path}.children[${index}]`, depth + 1, component);
59
62
  });
60
63
  }
61
64
  };
62
- visit(tree, options.path ?? '$', 0);
65
+ visit(tree, options.path ?? '$', 0, null);
63
66
  return out;
64
67
  }
68
+ /**
69
+ * A node's `slot` (audit G04) must name a slot prop of its parent; omitted
70
+ * or `children` is the default slot. A root fills no slot. An unknown
71
+ * parent was already reported, so its children's slots are not judged.
72
+ */
73
+ function checkSlot(slot, parent, id, path, out) {
74
+ if (slot === undefined || slot === 'children' || parent === undefined)
75
+ return;
76
+ const named = parent !== null &&
77
+ typeof slot === 'string' &&
78
+ parent.props.some((prop) => prop.kind === 'slot' && prop.name === slot);
79
+ if (named)
80
+ return;
81
+ out.push({
82
+ code: 'unknown-slot',
83
+ path: `${path}.slot`,
84
+ id,
85
+ message: parent === null
86
+ ? 'A root node fills no slot.'
87
+ : `"${String(slot)}" is not a slot of "${parent.id}".`,
88
+ });
89
+ }
65
90
  function checkProps(props, component, path, out) {
66
91
  const given = isRecord(props) ? props : {};
67
92
  const id = component.id;
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.1.0";
2
+ export declare const SPEC_VERSION = "0.2.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.1.0';
2
+ export const SPEC_VERSION = '0.2.0';
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The spec's version history (`versions.json`): every released spec version
3
+ * pinned to the digest of its normative files, so the digest and the semantic
4
+ * version cannot drift apart. A normative change (schema, catalog projection,
5
+ * capability claim or fixture) changes the digest; it needs a new version
6
+ * and a new pin, with the change class the policy assigns to it.
7
+ */
8
+ /** What a release changes, by the policy in the package README. */
9
+ export type SpecChangeClass = 'patch' | 'minor' | 'major';
10
+ export interface SpecVersionPin {
11
+ specVersion: string;
12
+ /** The spec digest of that release, or `null` for a release pinned retroactively without one. */
13
+ specDigest: string | null;
14
+ change: SpecChangeClass | 'initial';
15
+ summary: string;
16
+ }
17
+ export interface SpecVersionHistory {
18
+ releases: readonly SpecVersionPin[];
19
+ }
20
+ export interface VersionIssue {
21
+ version: string;
22
+ message: string;
23
+ }
24
+ /**
25
+ * The version that must follow `previous` for a change of `change` class.
26
+ * Before 1.0.0 any change to accepted data (minor or major class) bumps the
27
+ * minor number and a patch-class change bumps the patch; from 1.0.0 each
28
+ * class bumps its own number.
29
+ */
30
+ export declare function nextSpecVersion(previous: string, change: SpecChangeClass): string;
31
+ /**
32
+ * Check the history and that the newest release is the spec as built now:
33
+ * the same version and the same digest. Returns every problem; empty means
34
+ * the version and the digest agree.
35
+ */
36
+ export declare function checkVersionHistory(history: SpecVersionHistory, current: {
37
+ specVersion: string;
38
+ specDigest: string;
39
+ }): VersionIssue[];
@@ -0,0 +1,87 @@
1
+ /**
2
+ * The spec's version history (`versions.json`): every released spec version
3
+ * pinned to the digest of its normative files, so the digest and the semantic
4
+ * version cannot drift apart. A normative change (schema, catalog projection,
5
+ * capability claim or fixture) changes the digest; it needs a new version
6
+ * and a new pin, with the change class the policy assigns to it.
7
+ */
8
+ const SEMVER = /^(\d+)\.(\d+)\.(\d+)$/;
9
+ const DIGEST = /^[0-9a-f]{64}$/;
10
+ function parts(version) {
11
+ const match = SEMVER.exec(version);
12
+ return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
13
+ }
14
+ /**
15
+ * The version that must follow `previous` for a change of `change` class.
16
+ * Before 1.0.0 any change to accepted data (minor or major class) bumps the
17
+ * minor number and a patch-class change bumps the patch; from 1.0.0 each
18
+ * class bumps its own number.
19
+ */
20
+ export function nextSpecVersion(previous, change) {
21
+ const p = parts(previous);
22
+ if (!p)
23
+ throw new Error(`${previous}: not a semantic version`);
24
+ const [major, minor, patch] = p;
25
+ if (change === 'patch')
26
+ return `${major}.${minor}.${patch + 1}`;
27
+ if (major === 0 || change === 'minor')
28
+ return `${major}.${minor + 1}.0`;
29
+ return `${major + 1}.0.0`;
30
+ }
31
+ /**
32
+ * Check the history and that the newest release is the spec as built now:
33
+ * the same version and the same digest. Returns every problem; empty means
34
+ * the version and the digest agree.
35
+ */
36
+ export function checkVersionHistory(history, current) {
37
+ const issues = [];
38
+ const releases = history.releases;
39
+ if (releases.length === 0) {
40
+ return [{ version: current.specVersion, message: 'versions.json lists no release.' }];
41
+ }
42
+ releases.forEach((release, index) => {
43
+ const { specVersion: version } = release;
44
+ if (!parts(version))
45
+ issues.push({ version, message: 'not a semantic version' });
46
+ if (release.specDigest !== null && !DIGEST.test(release.specDigest)) {
47
+ issues.push({ version, message: 'the digest is not a SHA-256 hex string' });
48
+ }
49
+ if (index === 0) {
50
+ if (release.change !== 'initial') {
51
+ issues.push({ version, message: 'the first release must have change "initial"' });
52
+ }
53
+ return;
54
+ }
55
+ const previous = releases[index - 1];
56
+ if (release.change === 'initial') {
57
+ issues.push({ version, message: 'only the first release is "initial"' });
58
+ return;
59
+ }
60
+ if (release.specDigest === null) {
61
+ issues.push({ version, message: 'only the first release may lack a digest' });
62
+ }
63
+ if (parts(previous.specVersion)) {
64
+ const expected = nextSpecVersion(previous.specVersion, release.change);
65
+ if (version !== expected) {
66
+ issues.push({
67
+ version,
68
+ message: `a ${release.change} change after ${previous.specVersion} is ${expected}`,
69
+ });
70
+ }
71
+ }
72
+ });
73
+ const newest = releases[releases.length - 1];
74
+ if (newest.specVersion !== current.specVersion) {
75
+ issues.push({
76
+ version: current.specVersion,
77
+ message: `the newest release in versions.json is ${newest.specVersion}, the spec is ${current.specVersion}`,
78
+ });
79
+ }
80
+ else if (newest.specDigest !== current.specDigest) {
81
+ issues.push({
82
+ version: current.specVersion,
83
+ message: 'the normative files changed without a new version: classify the change, bump SPEC_VERSION, regenerate, and append a release pinning the new digest',
84
+ });
85
+ }
86
+ return issues;
87
+ }
package/package.json CHANGED
@@ -1,8 +1,23 @@
1
1
  {
2
2
  "name": "@texuli/spec",
3
- "version": "0.1.0-next.0",
3
+ "version": "0.1.0-next.1",
4
4
  "description": "The schema-first blueprint contract: JSON schemas, the catalog vocabulary, deterministic TypeScript and Kotlin projections, and the conformance harness.",
5
+ "keywords": [
6
+ "svelte",
7
+ "svelte-5",
8
+ "components",
9
+ "design-system",
10
+ "texuli"
11
+ ],
12
+ "homepage": "https://github.com/DailybotHQ/texuli#readme",
13
+ "bugs": "https://github.com/DailybotHQ/texuli/issues",
5
14
  "license": "MIT",
15
+ "author": "Dailybot, Inc.",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/DailybotHQ/texuli.git",
19
+ "directory": "packages/spec"
20
+ },
6
21
  "type": "module",
7
22
  "sideEffects": [
8
23
  "**/*.css"
@@ -18,11 +33,6 @@
18
33
  }
19
34
  },
20
35
  "files": [
21
- "dist",
22
- "generated/typescript"
23
- ],
24
- "publishConfig": {
25
- "access": "public",
26
- "tag": "next"
27
- }
36
+ "dist"
37
+ ]
28
38
  }