From 412f9e4eea0985075f9356eb08582fd251e71bf2 Mon Sep 17 00:00:00 2001 From: Nathan Curtis <1165904+nathanacurtis@users.noreply.github.com> Date: Sat, 4 Jul 2026 16:11:56 -0400 Subject: [PATCH 1/7] chore: start specs-schema v0.28.0 development --- packages/schema/CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/schema/CHANGELOG.md b/packages/schema/CHANGELOG.md index 7de72d1..3dabf16 100644 --- a/packages/schema/CHANGELOG.md +++ b/packages/schema/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to the Specs schema will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.28.0] - Unreleased + +### Added + +### Changed + +### Removed + + ## [0.27.0] - 2026-07-01 Dashed strokes are now first-class, typed data instead of falling through as an unrecognized style, and subcomponents carry enough Figma node identity for reverse-direction tools to resolve them back to a canvas location without a side-channel lookup. From 9ae46d0f5043d41c2a76d061bce282234a65dda9 Mon Sep 17 00:00:00 2001 From: Nathan Curtis <1165904+nathanacurtis@users.noreply.github.com> Date: Sat, 4 Jul 2026 16:12:14 -0400 Subject: [PATCH 2/7] chore: start specs-cli v0.25.0 development --- packages/cli/CHANGELOG.md | 9 +++++++++ packages/cli/package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index c87d6a0..2a396a1 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to `@directededges/specs-cli` are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.25.0] - Unreleased + +### Added + +### Changed + +### Removed + + ## [0.24.0] - 2026-07-04 `specs transform` gains two new transformers — `react` and `stories` — that scaffold a working React component and a matching Storybook page directly from the spec, plus a `--components` filter to scope a run to specific components. The `contract` and `css` transformers pick up complementary additions (slot visibility rules, structural CSS fixes) to support the new component scaffolding. Generated filenames are now prefixed with the component name for clarity outside the folder tree — a breaking change for any tooling that hardcodes the old unprefixed filenames. diff --git a/packages/cli/package.json b/packages/cli/package.json index 94cbc15..f689f1a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@directededges/specs-cli", - "version": "0.24.0", + "version": "0.25.0", "description": "Command-line interface for Specs design system operations", "type": "module", "main": "./dist/index.js", From 79c18c534e730967f52085a9e6781d0cb921809b Mon Sep 17 00:00:00 2001 From: Nathan Curtis <1165904+nathanacurtis@users.noreply.github.com> Date: Sun, 5 Jul 2026 14:44:17 -0400 Subject: [PATCH 3/7] docs(adr): draft 061 concern-split schema entry points + yaml schema header config Only whole-library and whole-component output shapes have JSON Schemas today; --split-concerns output (modes 4/5) has none. Proposes six new schema files (component-api/variants/examples + set wrappers) and a Config.format.yamlSchemaComment flag so specs-cli can emit a yaml-language-server header comment for RedHat YAML validation. --- adr/061-concern-schema-headers.md | 220 ++++++++++++++++++++++++++++++ adr/INDEX.md | 1 + 2 files changed, 221 insertions(+) create mode 100644 adr/061-concern-schema-headers.md diff --git a/adr/061-concern-schema-headers.md b/adr/061-concern-schema-headers.md new file mode 100644 index 0000000..942b155 --- /dev/null +++ b/adr/061-concern-schema-headers.md @@ -0,0 +1,220 @@ +# ADR: Schema Entry Points for Concern-Split Output, and a YAML Schema Header Comment + +**Branch**: `061-concern-schema-headers` +**Created**: 2026-07-05 +**Status**: DRAFT +**Deciders**: Nathan Curtis (author) +**Supersedes**: *(none)* + +--- + +## Context + +`specs-cli`'s `generate` command produces five distinct file shapes depending on `--split-components`, `--split-concerns`, and `--use-subfolders`: + +| Mode | Shape | Root structure | +|------|-------|-----------------| +| 1 | Whole library, one file | `{ components: { name: Component } }` | +| 2/3 | One component per file (flat or subfoldered) | `Component` | +| 4 | Concern-split, library-wide (`api.yaml`, `variants.yaml`, `examples.yaml`) | `{ components: { name: }, metadata: { generatedAt, componentCount, concern } }` | +| 5 | Concern-split, per-component (`Button/api.yaml`, etc.) | `` merged with `metadata: { ...Metadata, generatedAt, concern }` | + +Only modes 1–3 have a corresponding JSON Schema today: `components.schema.json` (mode 1) and `component.schema.json` (modes 2/3), unioned by `root.schema.json`. `Props`, `Anatomy`, `Variant`, `Variants`, `InstanceExamples`, and the slot-content-example map exist only as `#/definitions/*` fragments inside `component.schema.json` — they are not independently addressable root schemas, and no schema describes the partial-`Component` shapes that modes 4/5 actually emit. + +The concern split itself is fixed and implemented downstream (`specs-cli`'s `splitComponentByConcern`), grouping `Component` fields as: + +- **`api`**: `title`, `anatomy`, `props`, `metadata`, `subcomponents` (API-only, recursive) +- **`variants`**: `default`, `variants`, `invalidVariantCombinations` (if present), `metadata`, `subcomponents` (variants-only, recursive) +- **`examples`**: `metadata`, `slotContentExamples` (if present), `instanceExamples` (if present), `subcomponents` (examples-only, recursive) + +Separately, users who install the RedHat YAML VS Code extension get schema-driven validation and autocomplete only if a file either (a) is schema-mapped in workspace settings, or (b) carries a `# yaml-language-server: $schema=` comment at its top. No convention exists today for `specs-cli` to emit that comment, so generated YAML files are unvalidated in-editor even though a matching schema may exist. + +--- + +## Decision Drivers + +- **Additive-only**: new schema entry points and the header comment must not change any existing file's shape or require a MAJOR bump. +- **Type ↔ schema symmetry (Constitution I, IV)**: any new schema root needs a corresponding named type in `types/`, and vice versa. +- **No logic in this package (Constitution II)**: the schema package only *describes* the concern shapes; it does not implement `splitComponentByConcern` or decide which schema URL to select at generation time — that selection logic is `specs-cli`'s. +- **Minimal, intentional public API (Constitution III)**: avoid introducing more schema/type surface than the five observed output shapes require. +- **Naming — code platforms first (Constitution VI)**: concern names (`api`, `variants`, `examples`) are already established CLI/config vocabulary (`--split-concerns`, `concern: 'api' | 'variants' | 'examples'`); no code-platform disagreement exists to resolve. + +--- + +## Options Considered + +### Option A: Three concern schemas + three "set" wrappers *(Selected)* + +Add six new schema files mirroring the existing `component.schema.json` / `components.schema.json` pairing, one pair per concern: + +- `component-api.schema.json`, `component-variants.schema.json`, `component-examples.schema.json` — each describes the per-component partial shape (mode 5's root). +- `components-api.schema.json`, `components-variants.schema.json`, `components-examples.schema.json` — each wraps its singular counterpart in `{ components: { name: ... } }` (mode 4's root), exactly as `components.schema.json` wraps `component.schema.json`. + +Each pair is backed by one new type in `types/`: `ComponentApi`, `ComponentVariants`, `ComponentExamples` — structural subsets of `Component`, referencing the same `Anatomy`, `Props`, `Variant`, `Variants`, `Metadata`, `InstanceExamples` types already exported. + +**Pros**: +- Mirrors an established, already-understood pattern (`component.schema.json` ↔ `components.schema.json`) — no new schema idiom introduced. +- Every one of the five observed output shapes gets an exact, addressable schema. +- Purely additive: new files, new types, no changes to existing schemas. + +**Cons / Trade-offs**: +- Six new files is more surface than a single "partial-component" schema with conditional requirements — but conditional (`if`/`then`) JSON Schema is harder for RedHat YAML's validator to give useful autocomplete against, so explicit shapes are preferred over a clever union. + +--- + +### Option B: Single generic `PartialComponent` schema with a `concern` discriminator + +One schema and one type, `PartialComponent`, where every `Component` field is optional and a sibling `metadata.concern` value is documented (not enforced) as indicating which subset should be populated. + +**Rejected because**: it validates far looser than what `specs-cli` actually emits (e.g. it would silently accept a `variants.yaml` file with a stray `props` key), which undermines Constitution IV's "mechanically verifiable" schema goal. It also can't distinguish the mode-4 wrapped shape from the mode-5 unwrapped shape without a `oneOf`, at which point it's no simpler than Option A. + +--- + +## Decision + +### Type changes (`types/`) + +| File | Change | Bump | +|------|--------|------| +| `types/Component.ts` (or new `types/ComponentConcerns.ts`) | Add `ComponentApi`, `ComponentVariants`, `ComponentExamples` types | MINOR | +| `types/index.ts` | Export the three new types | MINOR | + +**Example — new shape** (`types/ComponentConcerns.ts`): +```yaml +# New types, each a structural subset of Component +ComponentApi: + title: string + anatomy: Anatomy + props: Props + metadata: Metadata + subcomponents?: Record # recursive, API fields only + +ComponentVariants: + default: Variant + variants: Variants + invalidVariantCombinations?: PropConfigurations[] + metadata: Metadata + subcomponents?: Record + +ComponentExamples: + metadata: Metadata + slotContentExamples?: Record + instanceExamples?: InstanceExamples + subcomponents?: Record +``` + +### Schema changes (`schema/`) + +| File | Change | Bump | +|------|--------|------| +| `component-api.schema.json` | New — mode 5 `api` root, mirrors `#/definitions/ComponentApi` | MINOR | +| `component-variants.schema.json` | New — mode 5 `variants` root | MINOR | +| `component-examples.schema.json` | New — mode 5 `examples` root | MINOR | +| `components-api.schema.json` | New — mode 4 `api` root, wraps `component-api.schema.json` in `{ components: {...} }` | MINOR | +| `components-variants.schema.json` | New — mode 4 `variants` root | MINOR | +| `components-examples.schema.json` | New — mode 4 `examples` root | MINOR | +| `root.schema.json` | Extend the top-level `oneOf` to include all six new refs alongside the existing two | MINOR | + +**Example — new shape** (`schema/component-api.schema.json`): +```yaml +$schema: "http://json-schema.org/draft-07/schema#" +title: "Specs Component API Concern Schema" +description: "The api concern subset of a component: title, anatomy, props, metadata." +type: object +properties: + title: { $ref: "component.schema.json#/definitions/Component/properties/title" } + anatomy: { $ref: "component.schema.json#/definitions/Anatomy" } + props: { $ref: "component.schema.json#/definitions/Props" } + metadata: { $ref: "component.schema.json#/definitions/Metadata" } + subcomponents: + type: object + additionalProperties: { $ref: "#" } # recursive, same api-only shape +required: [title, props, metadata] +additionalProperties: false +``` + +**Example — set wrapper** (`schema/components-api.schema.json`, mirrors `components.schema.json`): +```yaml +type: object +properties: + components: + type: object + patternProperties: + "^[a-zA-Z0-9_-]+$": { $ref: "component-api.schema.json" } + additionalProperties: false + metadata: + type: object + properties: + generatedAt: { type: string, format: date-time } + componentCount: { type: integer } + concern: { const: "api" } +required: [components] +additionalProperties: false +``` + +### Notes + +- `variants` and `examples` schemas follow the same two-file pattern (singular + set), substituting the relevant `$ref`s and `concern` const per the field mapping in Context. +- The library-wide `metadata` block (`generatedAt`, `componentCount`, `concern`) at mode-4's root is *not* the same as `Component`'s per-component `Metadata` type — it's new, manifest-level metadata scoped to these six schemas only, not added to `Metadata` itself. +- `additionalProperties: false` mirrors the strictness already present in `components.schema.json`. + +--- + +## Type ↔ Schema Impact + +- **Symmetric**: Yes. +- **Parity check**: `ComponentApi` ↔ `component-api.schema.json`; `ComponentVariants` ↔ `component-variants.schema.json`; `ComponentExamples` ↔ `component-examples.schema.json`. Each "set" schema has no independent type — it's structurally `Record` etc., expressed the same way `components.schema.json` has no separate type from `component.schema.json`. + +--- + +## Downstream Impact + +| Consumer | Impact | Action required | +|----------|--------|-----------------| +| `specs-cli` | Concern-split output (modes 4/5) can now be validated against a real schema; new config option controls a YAML header comment | Point `FileManifest` at the matching new schema URL per mode/concern when populating `metadata.schema`; add the config flag described below and emit the header comment when enabled | +| `specs-from-figma` | None | No change — it produces the full `Component` object; splitting happens in `specs-cli` | +| `specs-plugin-2` | None | No change — plugin output is not concern-split | + +### Config flag for the YAML schema header comment + +Separately from the schema additions, add a boolean to `Config.format` (not `Config.include` — this governs a file-level YAML decoration, not component data content, so it belongs alongside the other output-shaping toggles like `format.output` and `format.keys`, and only has meaning when `format.output = 'YAML'`): + +```yaml +# types/Config.ts — format block +format: + output?: 'JSON' | 'YAML' + keys?: 'SAFE' | 'CAMEL' | 'SNAKE' | 'KEBAB' | 'PASCAL' | 'TRAIN' + layout?: 'LAYOUT' | 'PARENT_CHILDREN' | 'BOTH' + # ...existing fields... + yamlSchemaComment?: boolean # NEW — default true (see DEFAULT_CONFIG) +``` + +Recommended name: **`format.yamlSchemaComment`** — scoped to YAML specifically (parallels `format.output`), and "comment" signals it's a header annotation, not data. Alternatives considered and rejected: +- `include.schemaHeaderComment` — `include` is reserved for toggles that add/remove *component data* (`invalidVariants`, `emptyVariants`, etc.); this isn't data, it's a file decoration. +- `format.schemaHeader` — ambiguous with the existing `metadata.schema` data block; "comment" disambiguates that this is the RedHat-YAML-specific `# yaml-language-server:` line, not the JSON `$schema` keyword. +- `format.emitSchemaReference` — accurate but longer with no added clarity over `yamlSchemaComment`. + +When `true` (default) and `format.output = 'YAML'`, `specs-cli` writes `# yaml-language-server: $schema=` as the first line of every generated file, selecting `` from the schema matching that file's shape (one of the eight schemas now covered by `root.schema.json`'s `oneOf`, using the same versioned-URL construction already used for `metadata.schema.url`). This is `specs-cli` writer logic, not a schema-package concern — `specs-schema`'s only obligation here is the new `Config.format.yamlSchemaComment` type/schema field and `DEFAULT_CONFIG.format.yamlSchemaComment = true`. + +| File | Change | Bump | +|------|--------|------| +| `types/Config.ts` | Add `format.yamlSchemaComment?: boolean` to `Config`; add required `format.yamlSchemaComment: boolean` to `ResolvedConfig` | MINOR | +| `types/Config.ts` (`DEFAULT_CONFIG`) | Set `format.yamlSchemaComment: true` | MINOR | +| `schema/workspace.schema.json` | Add `format.yamlSchemaComment` boolean property, default `true` | MINOR | + +--- + +## Semver Decision + +**Version bump**: `0.28.0 → 0.29.0` (`MINOR`) + +**Justification**: Every change is additive — six new schema files, three new types, one new optional `Config.format` field with a documented default. No existing type, field, or schema is removed, renamed, or made stricter. Per constitution Versioning Policy, additive types and optional fields are MINOR. + +--- + +## Consequences + +- Concern-split output (`--split-concerns`, both library-wide and per-component modes) has a real, addressable JSON Schema for the first time — `root.schema.json`'s `oneOf` grows from 2 to 8 entries. +- Users with the RedHat YAML extension get in-editor validation/autocomplete on any generated file, once `specs-cli` implements the header-comment writer logic against `Config.format.yamlSchemaComment`. +- `specs-cli` must be updated to select the correct one of eight schema URLs per output mode/concern when populating `metadata.schema.url` and the new header comment — this is tracked as `specs-cli` follow-up work, not part of this ADR's schema-package changes. +- Consumers who parse `Config.format` should treat `yamlSchemaComment` as optional-with-default; no migration needed since the default preserves current behavior for JSON output and only adds a comment line to YAML output. diff --git a/adr/INDEX.md b/adr/INDEX.md index 63f8d11..9ebf75f 100644 --- a/adr/INDEX.md +++ b/adr/INDEX.md @@ -4,6 +4,7 @@ | # | Title | Highlights | |---|-------|------------| +| 061 | Schema Entry Points for Concern-Split Output, and a YAML Schema Header Comment | | | 059 | Border Style and Dash Pattern — `borderStyle` and `borderDashPattern` on `Styles` | | | 058 | Wrapper Collapse Config Flag — `processing.wrapperCollapse` | | | 057 | Fix `Metadata.generator.version` type: `number` → `string` | | From f8d4cd63a5e420a24a7a926920f58187ff78cee8 Mon Sep 17 00:00:00 2001 From: Nathan Curtis <1165904+nathanacurtis@users.noreply.github.com> Date: Sun, 5 Jul 2026 15:14:52 -0400 Subject: [PATCH 4/7] docs(adr): fix version bump to stay within release target; justify schema comment vs metadata.schema.url --- adr/061-concern-schema-headers.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/adr/061-concern-schema-headers.md b/adr/061-concern-schema-headers.md index 942b155..b38bfc7 100644 --- a/adr/061-concern-schema-headers.md +++ b/adr/061-concern-schema-headers.md @@ -70,6 +70,14 @@ One schema and one type, `PartialComponent`, where every `Component` field is op --- +### Option C: Workspace-level `yaml.schemas` glob mapping instead of a per-file header comment + +Since every output mode produces deterministically-named files (`api.yaml`, `variants.yaml`, `examples.yaml` at a known base dir, or `/api.yaml` etc.), a one-time `yaml.schemas` mapping in `.vscode/settings.json` (or documented for users to add) could associate each glob pattern with its schema URL, giving RedHat YAML validation without touching generated file content or adding any `Config` field at all. `Component`'s existing `metadata.schema.url` data field already carries the resolved URL for programmatic consumers (round-trip tooling, version checks); this option would rely on it being the *only* schema pointer, with the editor association handled entirely out-of-band. + +**Rejected because**: the mapping doesn't travel with the file. A generated spec pasted into a gist, copied into another repo, or opened outside the workspace that configured the mapping loses validation entirely, whereas a self-contained header comment keeps working anywhere the RedHat extension is installed. `metadata.schema.url` alone doesn't help here either — the extension does not scan arbitrary data keys for schema hints, only the `yaml.schemas` setting or the modeline comment. The per-file comment and `metadata.schema.url` are not redundant in purpose: one is a data-plane field for programmatic consumers, the other is an editor-plane directive with its own reserved syntax — they happen to carry the same URL, generated from the same source, rather than being computed independently. + +--- + ## Decision ### Type changes (`types/`) @@ -206,9 +214,9 @@ When `true` (default) and `format.output = 'YAML'`, `specs-cli` writes `# yaml-l ## Semver Decision -**Version bump**: `0.28.0 → 0.29.0` (`MINOR`) +**Version bump**: none beyond the active release target — lands within `0.28.0` (the version `release/schema-0.28.0+cli-0.25.0` already targets for this cycle), classified `MINOR` relative to the prior published version. -**Justification**: Every change is additive — six new schema files, three new types, one new optional `Config.format` field with a documented default. No existing type, field, or schema is removed, renamed, or made stricter. Per constitution Versioning Policy, additive types and optional fields are MINOR. +**Justification**: Every change is additive — six new schema files, three new types, one new optional `Config.format` field with a documented default. No existing type, field, or schema is removed, renamed, or made stricter. Per constitution Versioning Policy, additive types and optional fields are MINOR. This ADR does not introduce a further bump on top of the release branch's reserved version. --- From d305bbac8d81367ef9f92ce67e007ae7252cf803 Mon Sep 17 00:00:00 2001 From: Nathan Curtis <1165904+nathanacurtis@users.noreply.github.com> Date: Sun, 5 Jul 2026 15:16:48 -0400 Subject: [PATCH 5/7] docs(adr): drop schema header comment / config flag material, focus on schema entry points --- adr/061-concern-schema-headers.md | 55 +++++-------------------------- adr/INDEX.md | 2 +- 2 files changed, 9 insertions(+), 48 deletions(-) diff --git a/adr/061-concern-schema-headers.md b/adr/061-concern-schema-headers.md index b38bfc7..c3c7fa8 100644 --- a/adr/061-concern-schema-headers.md +++ b/adr/061-concern-schema-headers.md @@ -1,4 +1,4 @@ -# ADR: Schema Entry Points for Concern-Split Output, and a YAML Schema Header Comment +# ADR: Schema Entry Points for Concern-Split Output **Branch**: `061-concern-schema-headers` **Created**: 2026-07-05 @@ -19,7 +19,7 @@ | 4 | Concern-split, library-wide (`api.yaml`, `variants.yaml`, `examples.yaml`) | `{ components: { name: }, metadata: { generatedAt, componentCount, concern } }` | | 5 | Concern-split, per-component (`Button/api.yaml`, etc.) | `` merged with `metadata: { ...Metadata, generatedAt, concern }` | -Only modes 1–3 have a corresponding JSON Schema today: `components.schema.json` (mode 1) and `component.schema.json` (modes 2/3), unioned by `root.schema.json`. `Props`, `Anatomy`, `Variant`, `Variants`, `InstanceExamples`, and the slot-content-example map exist only as `#/definitions/*` fragments inside `component.schema.json` — they are not independently addressable root schemas, and no schema describes the partial-`Component` shapes that modes 4/5 actually emit. +Only modes 1–3 have a corresponding JSON Schema today: `components.schema.json` (mode 1) and `component.schema.json` (modes 2/3), unioned by `root.schema.json`. `Props`, `Anatomy`, `Variant`, `Variants`, `InstanceExamples`, and the slot-content-example map exist only as `#/definitions/*` fragments inside `component.schema.json` — they are not independently addressable root schemas, and no schema describes the partial-`Component` shapes that modes 4/5 actually emit. A file produced by `--split-concerns` has nothing to validate against today, whether opened in an editor with schema tooling or validated in CI. The concern split itself is fixed and implemented downstream (`specs-cli`'s `splitComponentByConcern`), grouping `Component` fields as: @@ -27,15 +27,13 @@ The concern split itself is fixed and implemented downstream (`specs-cli`'s `spl - **`variants`**: `default`, `variants`, `invalidVariantCombinations` (if present), `metadata`, `subcomponents` (variants-only, recursive) - **`examples`**: `metadata`, `slotContentExamples` (if present), `instanceExamples` (if present), `subcomponents` (examples-only, recursive) -Separately, users who install the RedHat YAML VS Code extension get schema-driven validation and autocomplete only if a file either (a) is schema-mapped in workspace settings, or (b) carries a `# yaml-language-server: $schema=` comment at its top. No convention exists today for `specs-cli` to emit that comment, so generated YAML files are unvalidated in-editor even though a matching schema may exist. - --- ## Decision Drivers -- **Additive-only**: new schema entry points and the header comment must not change any existing file's shape or require a MAJOR bump. +- **Additive-only**: new schema entry points must not change any existing file's shape or require a MAJOR bump. - **Type ↔ schema symmetry (Constitution I, IV)**: any new schema root needs a corresponding named type in `types/`, and vice versa. -- **No logic in this package (Constitution II)**: the schema package only *describes* the concern shapes; it does not implement `splitComponentByConcern` or decide which schema URL to select at generation time — that selection logic is `specs-cli`'s. +- **No logic in this package (Constitution II)**: the schema package only *describes* the concern shapes; it does not implement `splitComponentByConcern` — that logic is `specs-cli`'s. - **Minimal, intentional public API (Constitution III)**: avoid introducing more schema/type surface than the five observed output shapes require. - **Naming — code platforms first (Constitution VI)**: concern names (`api`, `variants`, `examples`) are already established CLI/config vocabulary (`--split-concerns`, `concern: 'api' | 'variants' | 'examples'`); no code-platform disagreement exists to resolve. @@ -58,7 +56,7 @@ Each pair is backed by one new type in `types/`: `ComponentApi`, `ComponentVaria - Purely additive: new files, new types, no changes to existing schemas. **Cons / Trade-offs**: -- Six new files is more surface than a single "partial-component" schema with conditional requirements — but conditional (`if`/`then`) JSON Schema is harder for RedHat YAML's validator to give useful autocomplete against, so explicit shapes are preferred over a clever union. +- Six new files is more surface than a single "partial-component" schema with conditional requirements — but conditional (`if`/`then`) JSON Schema is harder for schema-aware tooling to give useful autocomplete against, so explicit shapes are preferred over a clever union. --- @@ -70,14 +68,6 @@ One schema and one type, `PartialComponent`, where every `Component` field is op --- -### Option C: Workspace-level `yaml.schemas` glob mapping instead of a per-file header comment - -Since every output mode produces deterministically-named files (`api.yaml`, `variants.yaml`, `examples.yaml` at a known base dir, or `/api.yaml` etc.), a one-time `yaml.schemas` mapping in `.vscode/settings.json` (or documented for users to add) could associate each glob pattern with its schema URL, giving RedHat YAML validation without touching generated file content or adding any `Config` field at all. `Component`'s existing `metadata.schema.url` data field already carries the resolved URL for programmatic consumers (round-trip tooling, version checks); this option would rely on it being the *only* schema pointer, with the editor association handled entirely out-of-band. - -**Rejected because**: the mapping doesn't travel with the file. A generated spec pasted into a gist, copied into another repo, or opened outside the workspace that configured the mapping loses validation entirely, whereas a self-contained header comment keeps working anywhere the RedHat extension is installed. `metadata.schema.url` alone doesn't help here either — the extension does not scan arbitrary data keys for schema hints, only the `yaml.schemas` setting or the modeline comment. The per-file comment and `metadata.schema.url` are not redundant in purpose: one is a data-plane field for programmatic consumers, the other is an editor-plane directive with its own reserved syntax — they happen to carry the same URL, generated from the same source, rather than being computed independently. - ---- - ## Decision ### Type changes (`types/`) @@ -179,50 +169,21 @@ additionalProperties: false | Consumer | Impact | Action required | |----------|--------|-----------------| -| `specs-cli` | Concern-split output (modes 4/5) can now be validated against a real schema; new config option controls a YAML header comment | Point `FileManifest` at the matching new schema URL per mode/concern when populating `metadata.schema`; add the config flag described below and emit the header comment when enabled | +| `specs-cli` | Concern-split output (modes 4/5) can now be validated against a real schema | Point `FileManifest` at the matching new schema URL per mode/concern when populating `metadata.schema` | | `specs-from-figma` | None | No change — it produces the full `Component` object; splitting happens in `specs-cli` | | `specs-plugin-2` | None | No change — plugin output is not concern-split | -### Config flag for the YAML schema header comment - -Separately from the schema additions, add a boolean to `Config.format` (not `Config.include` — this governs a file-level YAML decoration, not component data content, so it belongs alongside the other output-shaping toggles like `format.output` and `format.keys`, and only has meaning when `format.output = 'YAML'`): - -```yaml -# types/Config.ts — format block -format: - output?: 'JSON' | 'YAML' - keys?: 'SAFE' | 'CAMEL' | 'SNAKE' | 'KEBAB' | 'PASCAL' | 'TRAIN' - layout?: 'LAYOUT' | 'PARENT_CHILDREN' | 'BOTH' - # ...existing fields... - yamlSchemaComment?: boolean # NEW — default true (see DEFAULT_CONFIG) -``` - -Recommended name: **`format.yamlSchemaComment`** — scoped to YAML specifically (parallels `format.output`), and "comment" signals it's a header annotation, not data. Alternatives considered and rejected: -- `include.schemaHeaderComment` — `include` is reserved for toggles that add/remove *component data* (`invalidVariants`, `emptyVariants`, etc.); this isn't data, it's a file decoration. -- `format.schemaHeader` — ambiguous with the existing `metadata.schema` data block; "comment" disambiguates that this is the RedHat-YAML-specific `# yaml-language-server:` line, not the JSON `$schema` keyword. -- `format.emitSchemaReference` — accurate but longer with no added clarity over `yamlSchemaComment`. - -When `true` (default) and `format.output = 'YAML'`, `specs-cli` writes `# yaml-language-server: $schema=` as the first line of every generated file, selecting `` from the schema matching that file's shape (one of the eight schemas now covered by `root.schema.json`'s `oneOf`, using the same versioned-URL construction already used for `metadata.schema.url`). This is `specs-cli` writer logic, not a schema-package concern — `specs-schema`'s only obligation here is the new `Config.format.yamlSchemaComment` type/schema field and `DEFAULT_CONFIG.format.yamlSchemaComment = true`. - -| File | Change | Bump | -|------|--------|------| -| `types/Config.ts` | Add `format.yamlSchemaComment?: boolean` to `Config`; add required `format.yamlSchemaComment: boolean` to `ResolvedConfig` | MINOR | -| `types/Config.ts` (`DEFAULT_CONFIG`) | Set `format.yamlSchemaComment: true` | MINOR | -| `schema/workspace.schema.json` | Add `format.yamlSchemaComment` boolean property, default `true` | MINOR | - --- ## Semver Decision **Version bump**: none beyond the active release target — lands within `0.28.0` (the version `release/schema-0.28.0+cli-0.25.0` already targets for this cycle), classified `MINOR` relative to the prior published version. -**Justification**: Every change is additive — six new schema files, three new types, one new optional `Config.format` field with a documented default. No existing type, field, or schema is removed, renamed, or made stricter. Per constitution Versioning Policy, additive types and optional fields are MINOR. This ADR does not introduce a further bump on top of the release branch's reserved version. +**Justification**: Every change is additive — six new schema files and three new types. No existing type, field, or schema is removed, renamed, or made stricter. Per constitution Versioning Policy, additive types are MINOR. This ADR does not introduce a further bump on top of the release branch's reserved version. --- ## Consequences - Concern-split output (`--split-concerns`, both library-wide and per-component modes) has a real, addressable JSON Schema for the first time — `root.schema.json`'s `oneOf` grows from 2 to 8 entries. -- Users with the RedHat YAML extension get in-editor validation/autocomplete on any generated file, once `specs-cli` implements the header-comment writer logic against `Config.format.yamlSchemaComment`. -- `specs-cli` must be updated to select the correct one of eight schema URLs per output mode/concern when populating `metadata.schema.url` and the new header comment — this is tracked as `specs-cli` follow-up work, not part of this ADR's schema-package changes. -- Consumers who parse `Config.format` should treat `yamlSchemaComment` as optional-with-default; no migration needed since the default preserves current behavior for JSON output and only adds a comment line to YAML output. +- `specs-cli` must be updated to select the correct one of eight schema URLs per output mode/concern when populating `metadata.schema.url` — this is tracked as `specs-cli` follow-up work, not part of this ADR's schema-package changes. diff --git a/adr/INDEX.md b/adr/INDEX.md index 9ebf75f..d45d64f 100644 --- a/adr/INDEX.md +++ b/adr/INDEX.md @@ -4,7 +4,7 @@ | # | Title | Highlights | |---|-------|------------| -| 061 | Schema Entry Points for Concern-Split Output, and a YAML Schema Header Comment | | +| 061 | Schema Entry Points for Concern-Split Output | | | 059 | Border Style and Dash Pattern — `borderStyle` and `borderDashPattern` on `Styles` | | | 058 | Wrapper Collapse Config Flag — `processing.wrapperCollapse` | | | 057 | Fix `Metadata.generator.version` type: `number` → `string` | | From 6d97d90c79cffcdfb455629d3648291f15926c40 Mon Sep 17 00:00:00 2001 From: Nathan Curtis <1165904+nathanacurtis@users.noreply.github.com> Date: Sun, 5 Jul 2026 15:17:31 -0400 Subject: [PATCH 6/7] docs(adr): rename to 061-concern-split-schemas to match trimmed scope --- ...1-concern-schema-headers.md => 061-concern-split-schemas.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename adr/{061-concern-schema-headers.md => 061-concern-split-schemas.md} (99%) diff --git a/adr/061-concern-schema-headers.md b/adr/061-concern-split-schemas.md similarity index 99% rename from adr/061-concern-schema-headers.md rename to adr/061-concern-split-schemas.md index c3c7fa8..e9448fd 100644 --- a/adr/061-concern-schema-headers.md +++ b/adr/061-concern-split-schemas.md @@ -1,6 +1,6 @@ # ADR: Schema Entry Points for Concern-Split Output -**Branch**: `061-concern-schema-headers` +**Branch**: `061-concern-split-schemas` **Created**: 2026-07-05 **Status**: DRAFT **Deciders**: Nathan Curtis (author) From 1a7f36af9b8f29d8c63c97428903641f20c32de4 Mon Sep 17 00:00:00 2001 From: Nathan Curtis <1165904+nathanacurtis@users.noreply.github.com> Date: Sun, 5 Jul 2026 15:24:04 -0400 Subject: [PATCH 7/7] feat(schema): add concern-split schema entry points (ADR-061) component-api/variants/examples + components-api/variants/examples give --split-concerns output a real, addressable JSON Schema for the first time, mirroring the existing component/components pairing. --- packages/schema/CHANGELOG.md | 2 + .../schema/schema/component-api.schema.json | 36 +++++++ .../schema/component-examples.schema.json | 41 +++++++ .../schema/component-variants.schema.json | 40 +++++++ .../schema/schema/components-api.schema.json | 30 ++++++ .../schema/components-examples.schema.json | 30 ++++++ .../schema/components-variants.schema.json | 30 ++++++ packages/schema/schema/root.schema.json | 8 +- .../schema/tests/ComponentConcerns.test-d.ts | 62 +++++++++++ packages/schema/types/ComponentConcerns.ts | 101 ++++++++++++++++++ packages/schema/types/index.ts | 1 + 11 files changed, 380 insertions(+), 1 deletion(-) create mode 100644 packages/schema/schema/component-api.schema.json create mode 100644 packages/schema/schema/component-examples.schema.json create mode 100644 packages/schema/schema/component-variants.schema.json create mode 100644 packages/schema/schema/components-api.schema.json create mode 100644 packages/schema/schema/components-examples.schema.json create mode 100644 packages/schema/schema/components-variants.schema.json create mode 100644 packages/schema/tests/ComponentConcerns.test-d.ts create mode 100644 packages/schema/types/ComponentConcerns.ts diff --git a/packages/schema/CHANGELOG.md b/packages/schema/CHANGELOG.md index 3dabf16..ec588b2 100644 --- a/packages/schema/CHANGELOG.md +++ b/packages/schema/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `ComponentApi`, `ComponentVariants`, `ComponentExamples` — concern-split subsets of `Component`, each with a matching schema (`component-api`, `component-variants`, `component-examples`) and library-wide set wrapper (`components-api`, `components-variants`, `components-examples`); validates `specs-cli`'s `--split-concerns` output + ### Changed ### Removed diff --git a/packages/schema/schema/component-api.schema.json b/packages/schema/schema/component-api.schema.json new file mode 100644 index 0000000..a97ddde --- /dev/null +++ b/packages/schema/schema/component-api.schema.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "version": "0.28.0", + "title": "Specs Component API Concern Schema", + "description": "The api concern subset of a component: title, anatomy, props, metadata. Produced by specs-cli's --split-concerns output (api.yaml files) (ADR-061).", + "$comment": "Copyright (c) 2025 Directed Edges. Licensed under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). Attribution required.", + "definitions": { + "ComponentApi": { + "type": "object", + "properties": { + "title": { "$ref": "component.schema.json#/definitions/Component/properties/title" }, + "anatomy": { "$ref": "component.schema.json#/definitions/Anatomy" }, + "props": { "$ref": "component.schema.json#/definitions/Props" }, + "metadata": { "$ref": "component.schema.json#/definitions/Metadata" }, + "subcomponents": { + "type": "object", + "description": "The api concern of this component's subcomponents, keyed by name.", + "additionalProperties": { + "allOf": [ + { "$ref": "#/definitions/ComponentApi" }, + { + "not": { + "properties": { "metadata": {}, "subcomponents": {} }, + "required": ["metadata", "subcomponents"] + } + } + ] + } + } + }, + "required": ["title", "anatomy"], + "additionalProperties": false + } + }, + "$ref": "#/definitions/ComponentApi" +} diff --git a/packages/schema/schema/component-examples.schema.json b/packages/schema/schema/component-examples.schema.json new file mode 100644 index 0000000..150b904 --- /dev/null +++ b/packages/schema/schema/component-examples.schema.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "version": "0.28.0", + "title": "Specs Component Examples Concern Schema", + "description": "The examples concern subset of a component: slotContentExamples, instanceExamples, metadata. Produced by specs-cli's --split-concerns output (examples.yaml files), omitted for components with no example data (ADR-061).", + "$comment": "Copyright (c) 2025 Directed Edges. Licensed under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). Attribution required.", + "definitions": { + "ComponentExamples": { + "type": "object", + "properties": { + "metadata": { "$ref": "component.schema.json#/definitions/Metadata" }, + "slotContentExamples": { + "type": "object", + "description": "Named slot-content examples for this component.", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { "$ref": "component.schema.json#/definitions/SlotContent" } + }, + "additionalProperties": false + }, + "instanceExamples": { "$ref": "component.schema.json#/definitions/InstanceExamples" }, + "subcomponents": { + "type": "object", + "description": "The examples concern of this component's subcomponents, keyed by name.", + "additionalProperties": { + "allOf": [ + { "$ref": "#/definitions/ComponentExamples" }, + { + "not": { + "properties": { "metadata": {}, "subcomponents": {} }, + "required": ["metadata", "subcomponents"] + } + } + ] + } + } + }, + "additionalProperties": false + } + }, + "$ref": "#/definitions/ComponentExamples" +} diff --git a/packages/schema/schema/component-variants.schema.json b/packages/schema/schema/component-variants.schema.json new file mode 100644 index 0000000..3898cb7 --- /dev/null +++ b/packages/schema/schema/component-variants.schema.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "version": "0.28.0", + "title": "Specs Component Variants Concern Schema", + "description": "The variants concern subset of a component: default variant, variants, invalidVariantCombinations, metadata. Produced by specs-cli's --split-concerns output (variants.yaml files) (ADR-061).", + "$comment": "Copyright (c) 2025 Directed Edges. Licensed under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). Attribution required.", + "definitions": { + "ComponentVariants": { + "type": "object", + "properties": { + "default": { "$ref": "component.schema.json#/definitions/Variant" }, + "variants": { "$ref": "component.schema.json#/definitions/Variants" }, + "invalidVariantCombinations": { + "type": "array", + "description": "Non-default prop values that when used in combination result in an invalid component state.", + "items": { "$ref": "component.schema.json#/definitions/PropConfigurations" } + }, + "metadata": { "$ref": "component.schema.json#/definitions/Metadata" }, + "subcomponents": { + "type": "object", + "description": "The variants concern of this component's subcomponents, keyed by name.", + "additionalProperties": { + "allOf": [ + { "$ref": "#/definitions/ComponentVariants" }, + { + "not": { + "properties": { "metadata": {}, "subcomponents": {} }, + "required": ["metadata", "subcomponents"] + } + } + ] + } + } + }, + "required": ["default"], + "additionalProperties": false + } + }, + "$ref": "#/definitions/ComponentVariants" +} diff --git a/packages/schema/schema/components-api.schema.json b/packages/schema/schema/components-api.schema.json new file mode 100644 index 0000000..1e213de --- /dev/null +++ b/packages/schema/schema/components-api.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "version": "0.28.0", + "title": "Specs Components API Concern Set Schema", + "description": "A library-wide api-concern manifest: a set of named components, each conforming to the Specs component-api concern schema, plus generation metadata. Produced by specs-cli's --split-concerns output (library-wide api.yaml) (ADR-061).", + "$comment": "Copyright (c) 2025 Directed Edges. Licensed under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). Attribution required.", + "type": "object", + "properties": { + "components": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { "$ref": "component-api.schema.json" } + }, + "additionalProperties": false + }, + "metadata": { + "type": "object", + "description": "Generation metadata for this manifest file, distinct from any individual component's Metadata.", + "properties": { + "generatedAt": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp of when this manifest was generated." }, + "componentCount": { "type": "integer", "description": "Number of components included in this manifest." }, + "concern": { "const": "api", "description": "The concern this manifest represents." } + }, + "required": ["generatedAt", "componentCount", "concern"], + "additionalProperties": false + } + }, + "required": ["components"], + "additionalProperties": false +} diff --git a/packages/schema/schema/components-examples.schema.json b/packages/schema/schema/components-examples.schema.json new file mode 100644 index 0000000..0263dca --- /dev/null +++ b/packages/schema/schema/components-examples.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "version": "0.28.0", + "title": "Specs Components Examples Concern Set Schema", + "description": "A library-wide examples-concern manifest: a set of named components, each conforming to the Specs component-examples concern schema, plus generation metadata. Produced by specs-cli's --split-concerns output (library-wide examples.yaml), omitted when no component has example data (ADR-061).", + "$comment": "Copyright (c) 2025 Directed Edges. Licensed under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). Attribution required.", + "type": "object", + "properties": { + "components": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { "$ref": "component-examples.schema.json" } + }, + "additionalProperties": false + }, + "metadata": { + "type": "object", + "description": "Generation metadata for this manifest file, distinct from any individual component's Metadata.", + "properties": { + "generatedAt": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp of when this manifest was generated." }, + "componentCount": { "type": "integer", "description": "Number of components included in this manifest." }, + "concern": { "const": "examples", "description": "The concern this manifest represents." } + }, + "required": ["generatedAt", "componentCount", "concern"], + "additionalProperties": false + } + }, + "required": ["components"], + "additionalProperties": false +} diff --git a/packages/schema/schema/components-variants.schema.json b/packages/schema/schema/components-variants.schema.json new file mode 100644 index 0000000..a5c47bb --- /dev/null +++ b/packages/schema/schema/components-variants.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "version": "0.28.0", + "title": "Specs Components Variants Concern Set Schema", + "description": "A library-wide variants-concern manifest: a set of named components, each conforming to the Specs component-variants concern schema, plus generation metadata. Produced by specs-cli's --split-concerns output (library-wide variants.yaml) (ADR-061).", + "$comment": "Copyright (c) 2025 Directed Edges. Licensed under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). Attribution required.", + "type": "object", + "properties": { + "components": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { "$ref": "component-variants.schema.json" } + }, + "additionalProperties": false + }, + "metadata": { + "type": "object", + "description": "Generation metadata for this manifest file, distinct from any individual component's Metadata.", + "properties": { + "generatedAt": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp of when this manifest was generated." }, + "componentCount": { "type": "integer", "description": "Number of components included in this manifest." }, + "concern": { "const": "variants", "description": "The concern this manifest represents." } + }, + "required": ["generatedAt", "componentCount", "concern"], + "additionalProperties": false + } + }, + "required": ["components"], + "additionalProperties": false +} diff --git a/packages/schema/schema/root.schema.json b/packages/schema/schema/root.schema.json index c6778a3..642821a 100644 --- a/packages/schema/schema/root.schema.json +++ b/packages/schema/schema/root.schema.json @@ -5,6 +5,12 @@ "version": "0.12.0", "oneOf": [ { "$ref": "component.schema.json" }, - { "$ref": "components.schema.json" } + { "$ref": "components.schema.json" }, + { "$ref": "component-api.schema.json" }, + { "$ref": "component-variants.schema.json" }, + { "$ref": "component-examples.schema.json" }, + { "$ref": "components-api.schema.json" }, + { "$ref": "components-variants.schema.json" }, + { "$ref": "components-examples.schema.json" } ] } diff --git a/packages/schema/tests/ComponentConcerns.test-d.ts b/packages/schema/tests/ComponentConcerns.test-d.ts new file mode 100644 index 0000000..76349bc --- /dev/null +++ b/packages/schema/tests/ComponentConcerns.test-d.ts @@ -0,0 +1,62 @@ +import type { ComponentApi, ComponentVariants, ComponentExamples } from '../types/ComponentConcerns.js'; + +// ComponentApi — required fields only +const api: ComponentApi = { + title: 'Button', + anatomy: { root: { type: 'container' } }, +}; + +// ComponentApi — with optional props, metadata, and nested subcomponents (no metadata/subcomponents on the nested entry) +const apiFull: ComponentApi = { + title: 'Button', + anatomy: { root: { type: 'container' } }, + props: { label: { type: 'string', default: 'Click me' } }, + subcomponents: { + icon: { + title: 'Icon', + anatomy: { root: { type: 'container' } }, + }, + }, +}; + +const _nestedApi: Omit = { + title: 'Icon', + anatomy: { root: { type: 'container' } }, +}; +// @ts-expect-error — metadata is not assignable within a subcomponent's api concern +_nestedApi.metadata = {} as any; + +// ComponentVariants — required fields only +const variants: ComponentVariants = { + default: { layout: ['root'], elements: { root: {} } }, +}; + +// ComponentVariants — with optional variants and invalidVariantCombinations +const variantsFull: ComponentVariants = { + default: { layout: ['root'], elements: { root: {} } }, + variants: [], + invalidVariantCombinations: [{ disabled: true }], +}; + +// ComponentExamples — all fields optional +const examples: ComponentExamples = {}; + +// ComponentExamples — with instanceExamples and slotContentExamples +const examplesFull: ComponentExamples = { + instanceExamples: { + primary: { title: 'Primary', propConfigurations: {} }, + }, + slotContentExamples: { + composedLabel: { anatomy: { root: { type: 'container' } }, elements: { root: {} }, layout: ['root'] }, + }, +}; + +// Exported from index +import type { ComponentApi as IndexApi, ComponentVariants as IndexVariants, ComponentExamples as IndexExamples } from '../types/index.js'; +const _indexedApi: IndexApi = api; +const _indexedVariants: IndexVariants = variants; +const _indexedExamples: IndexExamples = examples; + +void apiFull; +void variantsFull; +void examplesFull; diff --git a/packages/schema/types/ComponentConcerns.ts b/packages/schema/types/ComponentConcerns.ts new file mode 100644 index 0000000..8f35049 --- /dev/null +++ b/packages/schema/types/ComponentConcerns.ts @@ -0,0 +1,101 @@ +import { Anatomy } from './Anatomy.js'; +import { Props } from './Props.js'; +import { Variant, Variants } from './Variant.js'; +import { Metadata } from './Metadata.js'; +import { PropConfigurations } from './PropConfigurations.js'; +import { InstanceExamples } from './InstanceExample.js'; +import { SlotContent } from './SlotContent.js'; + +/** + * The `api` concern subset of a `Component` — title, anatomy, and props, the + * fields needed to describe a component's public surface. Produced by + * `specs-cli`'s `--split-concerns` output (`api.yaml` files). + * @since 0.28.0 + */ +export type ComponentApi = { + /** + * The title of the component. + */ + title: string; + + /** + * The anatomy of the component. + */ + anatomy: Anatomy; + + /** + * The properties of the component. + */ + props?: Props; + + /** + * Metadata associated with the component. + */ + metadata?: Metadata; + + /** + * The api concern of this component's subcomponents, keyed by name. + */ + subcomponents?: Record>; +}; + +/** + * The `variants` concern subset of a `Component` — default variant, variants, + * and invalid variant combinations. Produced by `specs-cli`'s + * `--split-concerns` output (`variants.yaml` files). + * @since 0.28.0 + */ +export type ComponentVariants = { + /** + * The default variant of the component. + */ + default: Variant; + + /** + * The variants of the component. + */ + variants?: Variants; + + /** + * Invalid variant combinations for the component. + */ + invalidVariantCombinations?: PropConfigurations[]; + + /** + * Metadata associated with the component. + */ + metadata?: Metadata; + + /** + * The variants concern of this component's subcomponents, keyed by name. + */ + subcomponents?: Record>; +}; + +/** + * The `examples` concern subset of a `Component` — instance examples and + * slot-content examples. Produced by `specs-cli`'s `--split-concerns` output + * (`examples.yaml` files), omitted for components with no example data. + * @since 0.28.0 + */ +export type ComponentExamples = { + /** + * Metadata associated with the component. + */ + metadata?: Metadata; + + /** + * Named slot-content examples for this component. + */ + slotContentExamples?: Record; + + /** + * Named instance examples (documented usages) for this component. + */ + instanceExamples?: InstanceExamples; + + /** + * The examples concern of this component's subcomponents, keyed by name. + */ + subcomponents?: Record>; +}; diff --git a/packages/schema/types/index.ts b/packages/schema/types/index.ts index 22d14aa..2a9845f 100644 --- a/packages/schema/types/index.ts +++ b/packages/schema/types/index.ts @@ -14,6 +14,7 @@ export type { Variant, Variants } from './Variant.js'; export type { Metadata } from './Metadata.js'; export type { Subcomponent, Subcomponents, SubcomponentSource } from './Subcomponent.js'; export type { InstanceExample, InstanceExamples } from './InstanceExample.js'; +export type { ComponentApi, ComponentVariants, ComponentExamples } from './ComponentConcerns.js'; // Element and structure types export type { Element, Elements, ElementType } from './Element.js';