@texuli/spec 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +42 -11
  2. package/dist/build.d.ts +5 -0
  3. package/dist/build.js +1 -0
  4. package/dist/codegen/kotlin-tokens.d.ts +38 -0
  5. package/dist/codegen/kotlin-tokens.js +138 -0
  6. package/dist/codegen/kotlin.d.ts +3 -1
  7. package/dist/codegen/kotlin.js +5 -2
  8. package/dist/design-system/index.d.ts +11 -0
  9. package/dist/design-system/index.js +10 -0
  10. package/dist/design-system/schema.d.ts +3 -0
  11. package/dist/design-system/schema.js +164 -0
  12. package/dist/design-system/validate.d.ts +69 -0
  13. package/dist/design-system/validate.js +273 -0
  14. package/dist/design-system/vocabulary.d.ts +70 -0
  15. package/dist/design-system/vocabulary.js +351 -0
  16. package/dist/format.js +10 -1
  17. package/dist/generated/typescript/blueprint.d.ts +69 -12
  18. package/dist/generated/typescript/blueprint.js +1 -1
  19. package/dist/generated/typescript/catalog-schema.js +1 -1
  20. package/dist/generated/typescript/design-system.d.ts +1110 -0
  21. package/dist/generated/typescript/design-system.js +3 -0
  22. package/dist/generated/typescript/diagnostics.js +1 -1
  23. package/dist/generated/typescript/manifest.js +1 -1
  24. package/dist/generated/typescript/vocabulary.d.ts +15 -1
  25. package/dist/generated/typescript/vocabulary.js +2 -2
  26. package/dist/generated/typescript/workflow.js +1 -1
  27. package/dist/migrations/0.8.0-0.9.0.d.ts +8 -0
  28. package/dist/migrations/0.8.0-0.9.0.js +11 -0
  29. package/dist/migrations/0.9.0-0.10.0.d.ts +10 -0
  30. package/dist/migrations/0.9.0-0.10.0.js +13 -0
  31. package/dist/migrations/index.js +4 -0
  32. package/dist/validate.js +12 -0
  33. package/dist/version.d.ts +1 -1
  34. package/dist/version.js +1 -1
  35. package/package.json +5 -1
package/README.md CHANGED
@@ -14,11 +14,25 @@ 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.8.0`, a pre-1.0 release (1.0.0 will be the first
18
- > stable release). Under 0.x, a new minor (`0.9.0`) may contain breaking
19
- > changes, so pin the exact version or use `^0.8.0`, which accepts only 0.8.x.
20
-
21
- > **New in `0.8.0`.** `@texuli/spec@0.8.0` carries **spec 0.8.0**, which adds
17
+ > published to npm as `0.10.0`, a pre-1.0 release (1.0.0 will be the first
18
+ > stable release). Under 0.x, a new minor (`0.11.0`) may contain breaking
19
+ > changes, so pin the exact version or use `^0.10.0`, which accepts only 0.10.x.
20
+
21
+ > **New in `0.10.0`.** `@texuli/spec@0.10.0` carries **spec 0.10.0**: the
22
+ > design-system document (`schemas/design-system.schema.json`, format
23
+ > `texuli/design-system`) with its new `@texuli/spec/design-system`
24
+ > subpath, and the optional `designSystem` on the envelope and on a
25
+ > `document` (`$defs.designSystemReference`: `{ ref: "id@x.y.z" }` or
26
+ > `{ inline: <document> }`). Every `0.9.0` envelope migrates by stamping
27
+ > `spec`.
28
+
29
+ > **New in `0.9.0`.** `@texuli/spec@0.9.0` carried **spec 0.9.0**, a
30
+ > stamp-only catalog projection refresh (the Button pending note and the
31
+ > cartesian charts' `xKind` and `locale`, from the Protoyard gaps G94 and
32
+ > G97): the envelope, the `$defs` and the diagnostic codes are unchanged,
33
+ > and every `0.8.0` envelope migrates by stamping `spec`.
34
+
35
+ > **New in `0.8.0`.** `@texuli/spec@0.8.0` carried **spec 0.8.0**, which adds
22
36
  > data-source bindings (the `@texuli/spec/source` subpath), the
23
37
  > agent-emittable `schema-form`, `canvas`, `spreadsheet`, `data-dashboard`
24
38
  > and `data-dashboard-widget` ids with their closed shapes, the closed
@@ -75,7 +89,7 @@ import {
75
89
  validateInstance,
76
90
  } from '@texuli/spec';
77
91
 
78
- SPEC_VERSION; // the spec version: '0.8.0' in @texuli/spec@0.8.0
92
+ SPEC_VERSION; // the spec version: '0.10.0' in @texuli/spec@0.10.0
79
93
  SPEC_COMPONENT_IDS.includes('alert'); // true: the closed component vocabulary
80
94
 
81
95
  // The dialect validator: a closed subset of JSON Schema 2020-12.
@@ -107,7 +121,7 @@ import { type Schema, type SpecCatalog, validateBlueprint } from '@texuli/spec';
107
121
  import { migrateBlueprint } from '@texuli/spec/migrate';
108
122
  import { validateWorkflowGraph } from '@texuli/spec/workflow';
109
123
 
110
- const migrated = migrateBlueprint(blueprint); // 0.2.0 → … → 0.7.0 → 0.8.0; never throws
124
+ const migrated = migrateBlueprint(blueprint); // 0.2.0 → … → 0.9.0 → 0.10.0; never throws
111
125
  const result = validateBlueprint(migrated.blueprint, {
112
126
  blueprintSchema: blueprintSchemaJson as Schema,
113
127
  catalog: componentsJson as SpecCatalog,
@@ -146,6 +160,7 @@ warning without failing.
146
160
  | `@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` |
147
161
  | `@texuli/spec/stream` | Runtime, opt-in (new in `0.6.0`): streamed blueprints ([stream.md](https://github.com/DailybotHQ/texuli/blob/main/packages/spec/docs/stream.md)) | `createJsonTokenizer`, `createBlueprintAssembler`, `createBlueprintStream`, `STREAM_NODE_ORDER` |
148
162
  | `@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` |
163
+ | `@texuli/spec/design-system` | Runtime, opt-in (new in `0.10.0`, spec `0.10.0`): the design-system document contract (ADR-021) ([design-system.md](https://github.com/DailybotHQ/texuli/blob/main/packages/spec/docs/design-system.md)) | `validateDesignSystem`, `validateDesignSystemPatch`, `applyDesignSystemPatch`, `migrateDesignSystem`, `checkDesignSystemReference`, `DesignSystem`, `DesignSystemPatch`, `DESIGN_SYSTEM_CODES`, `DESIGN_SYSTEM_LIMITS` |
149
164
  | `@texuli/spec/codegen` | Build time: generation and conformance | `buildSpec`, `projectCatalog`, `checkConformance`, `checkCatalogContract`, `formatJson`, `nextSpecVersion`, `checkVersionHistory` |
150
165
 
151
166
  ## Key guarantees
@@ -216,7 +231,9 @@ warning without failing.
216
231
  ([source.md](https://github.com/DailybotHQ/texuli/blob/main/packages/spec/docs/source.md)).
217
232
  - **Forms as data, never patterns or code (spec 0.8.0, new in `0.8.0`).** The
218
233
  agent-emittable `schema-form` id carries a closed form schema
219
- (`$defs.formSchema`): fields of 15 closed types in sections or steps,
234
+ (`$defs.formSchema`): fields of 15 closed types in sections or steps (20
235
+ from `0.10.0`, which adds password, otp, color, datetime and the
236
+ host-painted custom type),
220
237
  named formats and host validator names (never a regular expression),
221
238
  and conditions and cross-field rules in the closed expression AST of
222
239
  `@texuli/core/expression` (`$defs.expression`, the `numberFormula` and
@@ -274,9 +291,21 @@ written for (`spec`; omitted means 0.2.0). `migrateBlueprint`
274
291
  (`@texuli/spec/migrate`) brings an
275
292
  envelope up to the current spec with pure, per-version steps
276
293
  (`src/migrations/`: `0.2.0 → 0.3.0`, `0.3.0 → 0.4.0`, `0.4.0 → 0.5.0`,
277
- `0.5.0 → 0.6.0`, `0.6.0 → 0.7.0` and `0.7.0 → 0.8.0`, each of which only
278
- stamps `spec`); a compatible version range is planned, not built.
279
- `@texuli/spec@0.8.0` carries spec `0.8.0` (digest
294
+ `0.5.0 → 0.6.0`, `0.6.0 → 0.7.0`, `0.7.0 → 0.8.0`, `0.8.0 → 0.9.0` and
295
+ `0.9.0 → 0.10.0`, each of which only stamps `spec`); a compatible version
296
+ range is planned, not built. `@texuli/spec@0.10.0` carries spec `0.10.0` (digest
297
+ `afcb9e3ed6b74b833a0f7477a657168621785ee98602ff582aa047e2d86aef17`), which adds the
298
+ design-system document (PLAN_011 T-ds-spec) and the envelope's
299
+ `designSystem` (T-ds-blueprint), plus a catalog projection refresh
300
+ (`data-filter-builder` gains the optional `issues` prop and
301
+ `fields[].optionLabels`, T-gaps-100-104) and an additive forms field
302
+ vocabulary (`$defs.formField` gains the `password`, `otp`, `color`,
303
+ `datetime` and `custom` types and the `kind`, `config`, `length`,
304
+ `protocols` and `clearable` members, T-gaps-113-121; `command-palette` lists
305
+ `searchLabel`, T-gaps-107-110). `@texuli/spec@0.9.0` carried spec `0.9.0` (digest
306
+ `497df4d3b0c67d53483163ebd9bd73733b52caf5f8e2966478f1cf860e6f9d9d`), an
307
+ additive catalog projection refresh (PLAN_011 T-gaps-87-95 and
308
+ T-gaps-96-98); `@texuli/spec@0.8.0` carried spec `0.8.0` (digest
280
309
  `f6b6388280d247eb9c3106a925111f35b90d1ec3b59c23652542f06a9f103daf`), which
281
310
  adds data-source bindings, forms, canvases, dashboards and spreadsheets;
282
311
  `@texuli/spec@0.7.0` carried spec `0.7.0` (digest
@@ -309,6 +338,8 @@ conformance harness) are in
309
338
  node shells; new in `0.6.0`): [stream.md](https://github.com/DailybotHQ/texuli/blob/main/packages/spec/docs/stream.md)
310
339
  - Data-source bindings (spec 0.8.0; new in `0.8.0`):
311
340
  [source.md](https://github.com/DailybotHQ/texuli/blob/main/packages/spec/docs/source.md)
341
+ - The design-system document (spec 0.10.0; new in `0.10.0`):
342
+ [design-system.md](https://github.com/DailybotHQ/texuli/blob/main/packages/spec/docs/design-system.md)
312
343
  - The Kotlin Multiplatform twin that consumes the generated models:
313
344
  [native/README.md](https://github.com/DailybotHQ/texuli/blob/main/native/README.md)
314
345
  - Architecture: [ARCHITECTURE.md](https://github.com/DailybotHQ/texuli/blob/main/docs/ARCHITECTURE.md)
package/dist/build.d.ts CHANGED
@@ -38,6 +38,11 @@ export declare const SCHEMA_UNITS: readonly [{
38
38
  readonly prefix: "";
39
39
  readonly ts: "workflow.ts";
40
40
  readonly kt: "Workflow.kt";
41
+ }, {
42
+ readonly name: "design-system";
43
+ readonly prefix: "DesignSystem";
44
+ readonly ts: "design-system.ts";
45
+ readonly kt: "DesignSystem.kt";
41
46
  }];
42
47
  export declare const TS_OUT = "generated/typescript/";
43
48
  export declare const KOTLIN_OUT = "generated/kotlin/com/texuli/spec/";
package/dist/build.js CHANGED
@@ -26,6 +26,7 @@ export const SCHEMA_UNITS = [
26
26
  { name: 'diagnostics', prefix: 'Spec', ts: 'diagnostics.ts', kt: 'Diagnostics.kt' },
27
27
  { name: 'manifest', prefix: 'Spec', ts: 'manifest.ts', kt: 'Manifest.kt' },
28
28
  { name: 'workflow', prefix: '', ts: 'workflow.ts', kt: 'Workflow.kt' },
29
+ { name: 'design-system', prefix: 'DesignSystem', ts: 'design-system.ts', kt: 'DesignSystem.kt' },
29
30
  ];
30
31
  export const TS_OUT = 'generated/typescript/';
31
32
  export const KOTLIN_OUT = 'generated/kotlin/com/texuli/spec/';
@@ -0,0 +1,38 @@
1
+ /** The color roles a token set carries, in output order (camelCase of `--tx-<role>`). */
2
+ export declare const KOTLIN_TOKEN_ROLES: readonly ["background", "foreground", "surface", "surfaceForeground", "muted", "mutedForeground", "border", "borderStrong", "ring", "primary", "primaryForeground", "primarySoft", "primarySoftForeground", "secondary", "secondaryForeground", "accent", "accentForeground", "link", "destructive", "destructiveForeground", "destructiveSoft", "destructiveSoftForeground", "success", "successForeground", "warning", "warningForeground", "info", "infoForeground", "scrim"];
3
+ export type KotlinTokenRole = (typeof KOTLIN_TOKEN_ROLES)[number];
4
+ /** The web variable a role reads (`primarySoft` → `--tx-primary-soft`). */
5
+ export declare function tokenVarName(role: string): string;
6
+ /** One design system, resolved. Colors are `#rrggbb` or `#rrggbbaa`. */
7
+ export interface KotlinTokenSystem {
8
+ /** The Kotlin property name (`Default`, `Acme`). */
9
+ name: string;
10
+ id: string;
11
+ version: string;
12
+ /** The compiled design system's hash. */
13
+ hash: string;
14
+ fonts: {
15
+ sans: string;
16
+ heading: string;
17
+ mono: string;
18
+ };
19
+ /** `--tx-radius` and `--tx-space-1`, in pixels (dp on native). */
20
+ radius: number;
21
+ space: number;
22
+ light: Readonly<Record<KotlinTokenRole, string>>;
23
+ dark?: Readonly<Record<KotlinTokenRole, string>> | null;
24
+ }
25
+ export interface KotlinTokenOptions {
26
+ /** Default `com.texuli.spec.tokens`. */
27
+ packageName?: string;
28
+ /** What produced the input, for the header comment. */
29
+ source?: string;
30
+ }
31
+ /** `#rrggbb` or `#rrggbbaa` as a Kotlin `0xAARRGGBBL` literal. */
32
+ export declare function argbLiteral(color: string): string;
33
+ /**
34
+ * The Kotlin source of `DesignTokenColors`, `DesignTokenSet` and an object
35
+ * holding each system. Throws on input it cannot express (a bad name, a
36
+ * color that is not sRGB hex, a negative length).
37
+ */
38
+ export declare function designTokensToKotlin(systems: readonly KotlinTokenSystem[], options?: KotlinTokenOptions): string;
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Kotlin design tokens (ADR-024): a compiled design system's values as a
3
+ * Kotlin source file, so the native twin paints with the same colors,
4
+ * fonts, radius and spacing as the web. The input is plain resolved data
5
+ * (colors already sRGB hex, lengths in pixels), which a build step makes
6
+ * from `compileDesignSystem` in `@texuli/tokens`; this module never
7
+ * imports the compiler, so `@texuli/spec` stays at L0. Deterministic: the
8
+ * same input gives byte-identical output. Internal for now: only
9
+ * `scripts/design-tokens.ts` uses it, and it is not exported from
10
+ * `@texuli/spec/codegen` (that entry is at its size budget; a public
11
+ * subpath is planned).
12
+ */
13
+ import { kotlinString } from "./kotlin.js";
14
+ /** The color roles a token set carries, in output order (camelCase of `--tx-<role>`). */
15
+ export const KOTLIN_TOKEN_ROLES = Object.freeze([
16
+ 'background',
17
+ 'foreground',
18
+ 'surface',
19
+ 'surfaceForeground',
20
+ 'muted',
21
+ 'mutedForeground',
22
+ 'border',
23
+ 'borderStrong',
24
+ 'ring',
25
+ 'primary',
26
+ 'primaryForeground',
27
+ 'primarySoft',
28
+ 'primarySoftForeground',
29
+ 'secondary',
30
+ 'secondaryForeground',
31
+ 'accent',
32
+ 'accentForeground',
33
+ 'link',
34
+ 'destructive',
35
+ 'destructiveForeground',
36
+ 'destructiveSoft',
37
+ 'destructiveSoftForeground',
38
+ 'success',
39
+ 'successForeground',
40
+ 'warning',
41
+ 'warningForeground',
42
+ 'info',
43
+ 'infoForeground',
44
+ 'scrim',
45
+ ]);
46
+ /** The web variable a role reads (`primarySoft` → `--tx-primary-soft`). */
47
+ export function tokenVarName(role) {
48
+ return `--tx-${role.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}`;
49
+ }
50
+ const I = ' ';
51
+ const NAME = /^[A-Z][A-Za-z0-9]{0,63}$/;
52
+ const PACKAGE = /^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)*$/;
53
+ /** `#rrggbb` or `#rrggbbaa` as a Kotlin `0xAARRGGBBL` literal. */
54
+ export function argbLiteral(color) {
55
+ const match = /^#([0-9a-fA-F]{6})([0-9a-fA-F]{2})?$/.exec(color);
56
+ if (!match)
57
+ throw new TypeError(`Not an sRGB hex color: ${JSON.stringify(color)}`);
58
+ return `0x${(match[2] ?? 'ff').toUpperCase()}${match[1].toUpperCase()}L`;
59
+ }
60
+ function number(value, what) {
61
+ if (!Number.isFinite(value) || value < 0 || value > 10_000) {
62
+ throw new TypeError(`${what} is not a length in pixels: ${value}`);
63
+ }
64
+ const text = String(Math.round(value * 1000) / 1000);
65
+ return text.includes('.') ? text : `${text}.0`;
66
+ }
67
+ function colors(values, depth) {
68
+ return [
69
+ 'DesignTokenColors(',
70
+ ...KOTLIN_TOKEN_ROLES.map((role) => `${depth}${I}${role} = ${argbLiteral(values[role])},`),
71
+ `${depth})`,
72
+ ];
73
+ }
74
+ /**
75
+ * The Kotlin source of `DesignTokenColors`, `DesignTokenSet` and an object
76
+ * holding each system. Throws on input it cannot express (a bad name, a
77
+ * color that is not sRGB hex, a negative length).
78
+ */
79
+ export function designTokensToKotlin(systems, options = {}) {
80
+ const packageName = options.packageName ?? 'com.texuli.spec.tokens';
81
+ if (!PACKAGE.test(packageName))
82
+ throw new TypeError(`Bad package name: ${packageName}`);
83
+ const names = new Set();
84
+ for (const system of systems) {
85
+ if (!NAME.test(system.name) || names.has(system.name)) {
86
+ throw new TypeError(`Bad or repeated token set name: ${JSON.stringify(system.name)}`);
87
+ }
88
+ names.add(system.name);
89
+ }
90
+ const lines = [
91
+ `// Generated from compiled design systems${options.source ? ` by ${options.source}` : ''}. Do not edit.`,
92
+ '',
93
+ `package ${packageName}`,
94
+ '',
95
+ '/** One mode of a design system: every color as `0xAARRGGBB` sRGB. */',
96
+ 'class DesignTokenColors(',
97
+ ...KOTLIN_TOKEN_ROLES.map((role) => `${I}val ${role}: Long,`),
98
+ ') {',
99
+ `${I}/** Each color by the web variable it mirrors (\`--tx-background\` …). */`,
100
+ `${I}val byVar: Map<String, Long>`,
101
+ `${I}${I}get() = mapOf(`,
102
+ ...KOTLIN_TOKEN_ROLES.map((role) => `${I}${I}${I}${kotlinString(tokenVarName(role))} to ${role},`),
103
+ `${I}${I})`,
104
+ '}',
105
+ '',
106
+ '/** A compiled design system: fonts as CSS stacks, lengths in px (dp), colors per mode. */',
107
+ 'class DesignTokenSet(',
108
+ `${I}val id: String,`,
109
+ `${I}val version: String,`,
110
+ `${I}val hash: String,`,
111
+ `${I}val fontSans: String,`,
112
+ `${I}val fontHeading: String,`,
113
+ `${I}val fontMono: String,`,
114
+ `${I}val radius: Double,`,
115
+ `${I}val space: Double,`,
116
+ `${I}val light: DesignTokenColors,`,
117
+ `${I}val dark: DesignTokenColors?,`,
118
+ ')',
119
+ '',
120
+ '/** The generated token sets. */',
121
+ 'object DesignTokens {',
122
+ ];
123
+ for (const system of systems) {
124
+ const d = `${I}${I}`;
125
+ lines.push(`${I}val ${system.name}: DesignTokenSet = DesignTokenSet(`, `${d}id = ${kotlinString(system.id)},`, `${d}version = ${kotlinString(system.version)},`, `${d}hash = ${kotlinString(system.hash)},`, `${d}fontSans = ${kotlinString(system.fonts.sans)},`, `${d}fontHeading = ${kotlinString(system.fonts.heading)},`, `${d}fontMono = ${kotlinString(system.fonts.mono)},`, `${d}radius = ${number(system.radius, 'radius')},`, `${d}space = ${number(system.space, 'space')},`);
126
+ const light = colors(system.light, d);
127
+ lines.push(`${d}light = ${light[0]}`, ...light.slice(1, -1), `${light.at(-1)},`);
128
+ if (system.dark) {
129
+ const dark = colors(system.dark, d);
130
+ lines.push(`${d}dark = ${dark[0]}`, ...dark.slice(1, -1), `${dark.at(-1)},`);
131
+ }
132
+ else
133
+ lines.push(`${d}dark = null,`);
134
+ lines.push(`${I})`, '');
135
+ }
136
+ lines.push(`${I}/** Every set, in generation order. */`, `${I}val all: List<DesignTokenSet>`, `${I}${I}get() = listOf(${systems.map((system) => system.name).join(', ')})`, '}', '');
137
+ return lines.join('\n');
138
+ }
@@ -12,7 +12,9 @@ export declare const KOTLIN_PACKAGE = "com.texuli.spec";
12
12
  export declare function kotlinString(value: string): string;
13
13
  /**
14
14
  * The Kotlin property for a wire name: a leading `$` is stripped (`$state` →
15
- * `state`), because `$` starts a string template in Kotlin. The wire name
15
+ * `state`), because `$` starts a string template in Kotlin, and a hyphen
16
+ * joins camelCase (`height-md` → `heightMd`), because kebab-case keys (the
17
+ * design-system component registry) are not Kotlin names. The wire name
16
18
  * itself is always emitted through {@link kotlinString}.
17
19
  */
18
20
  export declare function kotlinProperty(name: string): string;
@@ -66,11 +66,14 @@ function field(name) {
66
66
  }
67
67
  /**
68
68
  * The Kotlin property for a wire name: a leading `$` is stripped (`$state` →
69
- * `state`), because `$` starts a string template in Kotlin. The wire name
69
+ * `state`), because `$` starts a string template in Kotlin, and a hyphen
70
+ * joins camelCase (`height-md` → `heightMd`), because kebab-case keys (the
71
+ * design-system component registry) are not Kotlin names. The wire name
70
72
  * itself is always emitted through {@link kotlinString}.
71
73
  */
72
74
  export function kotlinProperty(name) {
73
- return name.startsWith('$') ? name.slice(1) : name;
75
+ const bare = name.startsWith('$') ? name.slice(1) : name;
76
+ return bare.replace(/-([A-Za-z0-9])/g, (_, char) => char.toUpperCase());
74
77
  }
75
78
  /** `$` escaped for a Kotlin string template (`\$state`). */
76
79
  function templateText(name) {
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `@texuli/spec/design-system` — the design-system document contract
3
+ * (ADR-021): the generated types, the closed vocabulary's codes and caps,
4
+ * `validateDesignSystem` with JSON paths, `DesignSystemPatch` with its check
5
+ * and pure merge, `migrateDesignSystem` and the blueprint reference check.
6
+ * Framework-free and dependency-free; the compiler lives in
7
+ * `@texuli/tokens/design-system/compile`.
8
+ */
9
+ export type * from '../generated/typescript/design-system.js';
10
+ export { applyDesignSystemPatch, checkDesignSystemReference, DESIGN_SYSTEM_MIGRATIONS, type DesignSystemMigration, type DesignSystemMigrationResult, type DesignSystemPatch, type DesignSystemValidateOptions, type DesignSystemValidation, migrateDesignSystem, validateDesignSystem, validateDesignSystemPatch, } from './validate.js';
11
+ export { DESIGN_SYSTEM_CODES, DESIGN_SYSTEM_COMPILER_CODES, DESIGN_SYSTEM_FORMAT, DESIGN_SYSTEM_FORMAT_VERSION, DESIGN_SYSTEM_LIMITS, type DesignSystemCode, } from './vocabulary.js';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `@texuli/spec/design-system` — the design-system document contract
3
+ * (ADR-021): the generated types, the closed vocabulary's codes and caps,
4
+ * `validateDesignSystem` with JSON paths, `DesignSystemPatch` with its check
5
+ * and pure merge, `migrateDesignSystem` and the blueprint reference check.
6
+ * Framework-free and dependency-free; the compiler lives in
7
+ * `@texuli/tokens/design-system/compile`.
8
+ */
9
+ export { applyDesignSystemPatch, checkDesignSystemReference, DESIGN_SYSTEM_MIGRATIONS, migrateDesignSystem, validateDesignSystem, validateDesignSystemPatch, } from "./validate.js";
10
+ export { DESIGN_SYSTEM_CODES, DESIGN_SYSTEM_COMPILER_CODES, DESIGN_SYSTEM_FORMAT, DESIGN_SYSTEM_FORMAT_VERSION, DESIGN_SYSTEM_LIMITS, } from "./vocabulary.js";
@@ -0,0 +1,3 @@
1
+ import type { Schema } from '../dialect.js';
2
+ /** The design-system document schema, projected from the vocabulary table. */
3
+ export declare function designSystemSchema(): Schema;
@@ -0,0 +1,164 @@
1
+ import { pascal } from "../codegen/shared.js";
2
+ import { DIALECT_URI } from "../dialect.js";
3
+ import { DESIGN_SYSTEM_CODES, DESIGN_SYSTEM_COMPILER_CODES, DESIGN_SYSTEM_LIMITS, DS_DEFS, DS_PATTERNS, DS_ROOT, } from "./vocabulary.js";
4
+ /**
5
+ * Build-time projection of the design-system vocabulary table into a
6
+ * dialect schema (`schemas/design-system.schema.json`), from which the spec
7
+ * generator writes the TypeScript and Kotlin types. Every object becomes a
8
+ * named `$defs` entry (named definitions keep their name; anonymous ones are
9
+ * named after their path), so both generators can project it. Rules the
10
+ * dialect cannot express (map key patterns, unique items, trust-dependent
11
+ * data URLs, inline byte caps, the `extends` chain) are described here and
12
+ * enforced by `validateDesignSystem`.
13
+ */
14
+ const DESCRIPTIONS = {
15
+ colorValue: 'A color: #rgb, #rrggbb, #rrggbbaa or a numeric oklch(L C H[ / A]).',
16
+ roleName: 'A semantic color role of the token contract.',
17
+ roleMap: 'Explicit role pins for one mode: checked by the compiler, never moved (ADR-022).',
18
+ dataVizPalette: 'One mode’s chart palette: 8 categorical, 9 sequential and 9 diverging colors.',
19
+ fontFile: `A self-hosted font file: a same-origin relative path (.woff2, .woff, .ttf, .otf), or at developer trust a base64 data:font URL of at most ${DESIGN_SYSTEM_LIMITS.font} bytes.`,
20
+ fontFamily: 'A family stack (1 to 8 names, quoted by the compiler) and its font files.',
21
+ typeRole: 'A type role: family, size (px), weight, line height and tracking (em).',
22
+ typeRoleName: 'A type role name.',
23
+ radiusStep: 'A shape radius step.',
24
+ spaceStep: 'A space step.',
25
+ dimension: 'A space, control, icon or target size step.',
26
+ borderStep: 'A border width step.',
27
+ elevationLevel: 'An elevation level.',
28
+ opacityStep: 'An opacity step.',
29
+ strokeWidth: 'A stroke width in px.',
30
+ asset: `An image: a same-origin relative path (png, jpg, gif, webp, avif, svg, ico; never .., \\, %, ?, # or //), or at developer trust a base64 data:image (png, jpeg, gif, webp, avif) of at most ${DESIGN_SYSTEM_LIMITS.logo} bytes. SVG renders only as <img>.`,
31
+ color: 'Color: brand seeds, exact pins, explicit role pins per mode, modes, data viz per mode and status colors.',
32
+ typography: 'Typography: family stacks with font files, the scale and the type roles.',
33
+ shape: 'Shape: radius (px), radius scale and border width (px).',
34
+ space: 'Space: the base unit (px) and the density multiplier.',
35
+ elevation: 'Elevation style.',
36
+ surface: 'Surface style and glass settings.',
37
+ motion: 'Motion personality, durations (ms) and a named easing.',
38
+ layout: 'Layout widths, breakpoints and the grid (px).',
39
+ icons: 'Icon style, stroke and sizes (px).',
40
+ components: `Component tokens (the token registry, z-index left out) and defaults (variant and size), per component.`,
41
+ assets: 'Brand assets: logos and the favicon.',
42
+ voice: 'Voice data: tone words, a lexicon (at most 200 entries) and guidelines.',
43
+ accessibility: 'Accessibility targets: contrast, minimum target size and the focus style.',
44
+ variant: 'A named variant (high-contrast, compact, seasonal): a closed partial document of sections, orthogonal to mode.',
45
+ issueCode: 'Every stable design-system code; aa-text, aa-focus and target-size are raised by the compiler.',
46
+ issue: 'One design-system issue: a stable code, a JSON path from $, a message and an optional hint.',
47
+ };
48
+ function source(node) {
49
+ const path = (node[1] === 'i' ? DS_PATTERNS.image : DS_PATTERNS.font).source.slice(1, -1);
50
+ const data = node[1] === 'i' ? 'data:image\\/(?:png|jpeg|gif|webp|avif)' : 'data:font\\/(?:woff2?|ttf|otf)';
51
+ return {
52
+ description: `At most ${node[2]} bytes inline; data URLs need developer trust.`,
53
+ type: 'string',
54
+ pattern: `^(?:${path}|${data};base64,[A-Za-z0-9+/]+={0,2})$`,
55
+ };
56
+ }
57
+ /** The design-system document schema, projected from the vocabulary table. */
58
+ export function designSystemSchema() {
59
+ const defs = {};
60
+ const toSchema = (node, name) => {
61
+ if (typeof node === 'string')
62
+ return { $ref: `#/$defs/${node}` };
63
+ if (!Array.isArray(node)) {
64
+ if (!Object.hasOwn(defs, name))
65
+ defs[name] = objectSchema(node, name);
66
+ return { $ref: `#/$defs/${name}` };
67
+ }
68
+ const n = node;
69
+ switch (n[0]) {
70
+ case 'e':
71
+ return { type: 'string', enum: n[1].split(' ') };
72
+ case 'n':
73
+ case 'i':
74
+ return { type: n[0] === 'i' ? 'integer' : 'number', minimum: n[1], maximum: n[2] };
75
+ case 's':
76
+ return { type: 'string', maxLength: n[2], pattern: n[1].source };
77
+ case 't':
78
+ return { type: 'string', maxLength: n[1], pattern: DS_PATTERNS.text.source };
79
+ case 'l':
80
+ return {
81
+ ...(n[4] ? { description: 'Unique items.' } : {}),
82
+ type: 'array',
83
+ items: toSchema(n[1], `${name}Item`),
84
+ ...(n[2] > 0 ? { minItems: n[2] } : {}),
85
+ maxItems: n[3],
86
+ };
87
+ case 'm':
88
+ return {
89
+ description: `At most ${n[2]} entries; keys match ${n[3].source} and are never __proto__, constructor or prototype.`,
90
+ type: 'object',
91
+ additionalProperties: toSchema(n[1], `${name}Value`),
92
+ };
93
+ case 'b':
94
+ return { type: 'boolean' };
95
+ case 'k':
96
+ return { const: n[1] };
97
+ case 'u':
98
+ return source(n);
99
+ case 'x':
100
+ return {
101
+ description: 'texuli:<theme> or an exact id@x.y.z; never a range, a URL or a path. Cycles and chains deeper than 4 are refused.',
102
+ type: 'string',
103
+ pattern: DS_PATTERNS.ref.source,
104
+ };
105
+ }
106
+ };
107
+ function objectSchema(shape, name) {
108
+ const properties = {};
109
+ const required = [];
110
+ for (const [raw, child] of Object.entries(shape)) {
111
+ const key = raw.replace(/!$/, '');
112
+ if (raw.endsWith('!'))
113
+ required.push(key);
114
+ properties[key] = toSchema(child, `${name}${pascal(key)}`);
115
+ }
116
+ return {
117
+ ...(DESCRIPTIONS[name] ? { description: DESCRIPTIONS[name] } : {}),
118
+ type: 'object',
119
+ properties,
120
+ ...(required.length > 0 ? { required } : {}),
121
+ additionalProperties: false,
122
+ };
123
+ }
124
+ for (const [name, node] of Object.entries(DS_DEFS)) {
125
+ if (typeof node !== 'string' && !Array.isArray(node))
126
+ toSchema(node, name);
127
+ else {
128
+ const schema = toSchema(node, name);
129
+ defs[name] = DESCRIPTIONS[name] ? { description: DESCRIPTIONS[name], ...schema } : schema;
130
+ }
131
+ }
132
+ defs.issueCode = {
133
+ description: DESCRIPTIONS.issueCode,
134
+ type: 'string',
135
+ enum: [...DESIGN_SYSTEM_CODES, ...DESIGN_SYSTEM_COMPILER_CODES],
136
+ };
137
+ defs.issue = {
138
+ description: DESCRIPTIONS.issue,
139
+ type: 'object',
140
+ properties: {
141
+ code: { $ref: '#/$defs/issueCode' },
142
+ path: { type: 'string' },
143
+ message: { type: 'string' },
144
+ hint: { type: 'string' },
145
+ },
146
+ required: ['code', 'path', 'message'],
147
+ additionalProperties: false,
148
+ };
149
+ const root = objectSchema(DS_ROOT, 'designSystem');
150
+ const ordered = {};
151
+ for (const key of Object.keys(defs).sort())
152
+ ordered[key] = defs[key];
153
+ return {
154
+ $schema: DIALECT_URI,
155
+ $id: 'https://texuli.example/spec/schemas/design-system.schema.json',
156
+ title: 'DesignSystem',
157
+ description: 'A Texuli design system (ADR-021, formatVersion 1): one closed JSON document a company or an agent writes once and every Texuli surface follows. Data only: colors are hex or numeric oklch, lengths are numbers in px, fonts and assets are same-origin relative paths or capped data URLs, and nothing reaches CSS that the token contract’s css-safe checks would refuse. Generated from @texuli/spec/design-system’s vocabulary by scripts/design-system-schema.ts; validateDesignSystem enforces the rules the dialect cannot express.',
158
+ type: 'object',
159
+ properties: root.properties,
160
+ required: root.required,
161
+ additionalProperties: false,
162
+ $defs: ordered,
163
+ };
164
+ }
@@ -0,0 +1,69 @@
1
+ import type { DesignSystem, DesignSystemIssue, DesignSystemVariant } from '../generated/typescript/design-system.js';
2
+ /** The result of every design-system check: issues with JSON paths from `$`. */
3
+ export interface DesignSystemValidation {
4
+ ok: boolean;
5
+ errors: readonly DesignSystemIssue[];
6
+ }
7
+ export interface DesignSystemValidateOptions {
8
+ /**
9
+ * `untrusted` (the default: agent or user documents) refuses every `data:`
10
+ * URL; `developer` (documents the host supplies) accepts base64 raster
11
+ * images and fonts within their caps. SVG is a relative path only.
12
+ */
13
+ trust?: 'developer' | 'untrusted';
14
+ /** Resolve `id@version` for the `extends` chain (cycles and depth); synchronous. */
15
+ resolve?: (ref: string) => unknown;
16
+ /** Stop after this many issues (default and ceiling 64). */
17
+ maxErrors?: number;
18
+ }
19
+ /**
20
+ * Check a design-system document against the closed vocabulary. Never
21
+ * throws on a JSON value; returns every issue (up to `maxErrors`) with a
22
+ * stable code and a JSON path. A future `formatVersion` stops at
23
+ * `format-version`.
24
+ */
25
+ export declare function validateDesignSystem(doc: unknown, options?: DesignSystemValidateOptions): DesignSystemValidation;
26
+ /** A typed partial document: objects merge, arrays replace, `null` removes. */
27
+ export type DesignSystemPatch = PatchOf<DesignSystemVariant>;
28
+ type PatchOf<T> = T extends readonly unknown[] ? T : T extends object ? {
29
+ [K in keyof T]?: PatchOf<T[K]> | null;
30
+ } : T;
31
+ /** Check a patch: the sections only, every field optional, `null` to remove. */
32
+ export declare function validateDesignSystemPatch(patch: unknown, options?: DesignSystemValidateOptions): DesignSystemValidation;
33
+ /**
34
+ * Merge a patch into a document, purely: objects merge, arrays and scalars
35
+ * replace, `null` removes, and `__proto__`, `constructor` and `prototype`
36
+ * are skipped. Validate the result again.
37
+ */
38
+ export declare function applyDesignSystemPatch(base: DesignSystem, patch: DesignSystemPatch): DesignSystem;
39
+ /** One `formatVersion` step. */
40
+ export interface DesignSystemMigration {
41
+ from: number;
42
+ to: number;
43
+ migrate(doc: Readonly<Record<string, unknown>>): Record<string, unknown>;
44
+ }
45
+ /** Every step, in order (none yet: formatVersion 1 is the first). */
46
+ export declare const DESIGN_SYSTEM_MIGRATIONS: readonly DesignSystemMigration[];
47
+ export type DesignSystemMigrationResult = {
48
+ ok: true;
49
+ document: DesignSystem;
50
+ from: number;
51
+ to: number;
52
+ applied: string[];
53
+ } | {
54
+ ok: false;
55
+ errors: [DesignSystemIssue];
56
+ };
57
+ /**
58
+ * Bring a document up to the current `formatVersion`. Pure and total: never
59
+ * throws on a JSON value and never mutates it; an unknown or future version
60
+ * is a `format-version` result. Validate the migrated document.
61
+ */
62
+ export declare function migrateDesignSystem(doc: unknown): DesignSystemMigrationResult;
63
+ /**
64
+ * Check a blueprint's design-system reference: exactly one of `ref`
65
+ * (`id@x.y.z` or `texuli:<theme>`) and `inline` (a whole document, checked
66
+ * by {@link validateDesignSystem} at `options.trust`).
67
+ */
68
+ export declare function checkDesignSystemReference(value: unknown, options?: DesignSystemValidateOptions): DesignSystemValidation;
69
+ export {};