diff --git a/adr/058-wrapper-collapse.md b/adr/058-wrapper-collapse.md index 6811c8d..44dc266 100644 --- a/adr/058-wrapper-collapse.md +++ b/adr/058-wrapper-collapse.md @@ -1,8 +1,8 @@ -# ADR 058: Wrapper Collapse Config Flag +# ADR 058: Collapsing Wrapped Primitives **Branch**: `058-wrapper-collapse` **Created**: 2026-06-16 -**Status**: DRAFT +**Status**: ACCEPTED **Deciders**: Nathan Curtis (author) **Supersedes**: *(none)* @@ -12,8 +12,32 @@ A common Figma construction — particularly for text and icon primitives — is a component whose root layer is a plain, style-free container holding a single `text` or `glyph` child. The wrapper adds no semantic styling (no corner radius, strokes, spacing, effects, or background) and carries no slot binding. From a spec consumer's perspective, the root element *is* the leaf; the container is a Figma-side convenience with no design-system meaning. +Common design-system components that exhibit this pattern: + +- **Heading / Title** — a frame named `Heading` or `Title` wrapping a single `Text` layer, where the frame itself carries no visual styling. +- **Paragraph / Body** — a `Paragraph` or `Body` frame wrapping a single `Text` layer with no border, background, or padding. +- **Label / Caption** — small typographic components structured identically: one unstyled frame around one text node. +- **Icon** — a frame wrapping a single vector/glyph node (e.g. a Figma component set for an icon library where each variant is a plain frame containing one `glyph`). + When the generator encounters such a component today, it faithfully emits the wrapper as `root` and the leaf as a child element. This produces a two-node anatomy and a `container` root type even though the component is semantically a single primitive. Consumers (code generators, documentation tools) must either detect and unwrap this pattern themselves or tolerate a structurally inflated spec. +**Collapse eligibility** — a component qualifies for collapse when *all* of the following are true: + +- The root element's type is `container`. +- The root has exactly one anatomy-visible child (children present in both the Figma layer tree and the `Elements` collection after exclusions). +- That child's type is `text` or `glyph`. +- The child has no children of its own. +- The root carries no slot binding on its children. +- The root carries none of the following styles after default/zero values are stripped: `clipContent`, `cornerRadius`, `strokes`, `strokeAlign`, `strokeWeight`, `itemSpacing`, `padding`, `effects`, `backgroundColor`, `cornerSmoothing`. + +**Collapse is blocked** by any of: + +- A root that already is a `text` or `glyph` (no wrapper present). +- A root with more than one visible child (e.g. an icon with a background layer). +- A root with a slot binding on its children (the wrapper has semantic slot structure). +- Any non-default value on the disqualifying style keys above (e.g. a corner radius, a border, padding, or a fill color on the container). +- Any variant where the root fails the check — collapse is all-or-nothing across the component's variant set. + The gap: no `Config` flag exists to tell the generator to collapse this wrapper, leaving downstream tools to implement their own heuristics or accept unnecessary structural noise. --- @@ -30,20 +54,21 @@ The gap: no `Config` flag exists to tell the generator to collapse this wrapper, ## Options Considered -### Option A: `processing.wrapperCollapse` *(Selected)* +### Option A: `processing.collapsePrimitiveWrapper` *(Selected)* A boolean field on `Config.processing`. When `true`, the generator promotes the single text/glyph child to root and strips the container wrapper. Default: `false`. -**Name rationale**: Names the thing being removed (`wrapper`) and what happens to it (`collapse`). Consistent with other processing booleans (`slotConstraints`, `inferNumberProps`) which name the capability being activated. "Wrapper" is a code-platform-neutral term — React, iOS, and Android all use it to describe decorative containers that carry no semantic substance. +**Name rationale**: Follows the verb-noun pattern established by `inferNumberProps` (infer + number props). `collapsePrimitiveWrapper` = collapse + primitive wrapper. "Primitive" identifies the element type being revealed (a leaf `text` or `glyph`, the primitive element types in the schema), and "wrapper" names the thing being removed — a code-platform-neutral term that React, iOS, and Android all use for decorative containers that carry no semantic substance. This is more precise than a bare noun phrase (`wrapperCollapse`, `leafCollapse`), which read as noun-verb or noun-noun compounds inconsistent with the rest of the `processing` block. **Pros**: - Additive optional field → MINOR bump only. - `false` default leaves all existing specs unchanged. -- Name is self-describing and matches the processing-block pattern. +- Verb-noun form is consistent with `inferNumberProps`. +- "Primitive" scopes the eligible leaf types without needing to enumerate them in the field name. - Symmetric: one field in type, one property in schema. **Cons / Trade-offs**: -- Consumers must opt in explicitly; no auto-collapse for legacy files without a config update. +- Longer than a bare noun phrase; consumers must opt in explicitly. --- @@ -51,7 +76,7 @@ A boolean field on `Config.processing`. When `true`, the generator promotes the Same boolean, named from the leaf's perspective (the thing being promoted) rather than the wrapper's (the thing being removed). -**Rejected because**: "Collapse" as a verb applied to the leaf is misleading — the leaf isn't collapsed, it's promoted. The wrapper is what collapses. `wrapperCollapse` more accurately describes the observable effect (the container disappears) and avoids the semantic mismatch. +**Rejected because**: "Collapse" as a verb applied to the leaf is misleading — the leaf isn't collapsed, it's promoted. The wrapper is what collapses. `collapsePrimitiveWrapper` more accurately describes the observable effect (the container disappears) and avoids the semantic mismatch. --- @@ -59,7 +84,7 @@ Same boolean, named from the leaf's perspective (the thing being promoted) rathe Active-verb framing — "promote the leaf to root." -**Rejected because**: Inconsistent with the existing processing-boolean naming convention, which favors noun phrases (`slotConstraints`, `inferNumberProps`, `wrapperCollapse`) over imperative verbs. Diverging from the established pattern increases cognitive friction for users reading a config file. +**Rejected because**: Describes the effect from the leaf's perspective rather than the operation being performed on the structure. "Promote" is also less precise — it applies broadly to any elevation operation, whereas "collapse" specifically means removing the intermediate container. `collapsePrimitiveWrapper` names both the subject (the primitive wrapper) and the action (collapse), making the config's effect self-evident without consulting documentation. --- @@ -69,9 +94,9 @@ Active-verb framing — "promote the leaf to root." | File | Change | Bump | |------|--------|------| -| `types/Config.ts` | Add optional `wrapperCollapse?: boolean` to `Config.processing` | MINOR | -| `types/Config.ts` | Add required `wrapperCollapse: boolean` to `ResolvedConfig.processing` | MINOR | -| `types/Config.ts` | Add `wrapperCollapse: false` to `DEFAULT_CONFIG.processing` | MINOR | +| `types/Config.ts` | Add optional `collapsePrimitiveWrapper?: boolean` to `Config.processing` | MINOR | +| `types/Config.ts` | Add required `collapsePrimitiveWrapper: boolean` to `ResolvedConfig.processing` | MINOR | +| `types/Config.ts` | Add `collapsePrimitiveWrapper: false` to `DEFAULT_CONFIG.processing` | MINOR | **Example — `Config` (partial, `types/Config.ts`)**: ```yaml @@ -84,7 +109,7 @@ processing: processing: slotConstraints?: boolean inferNumberProps?: boolean - wrapperCollapse?: boolean # optional — MINOR + collapsePrimitiveWrapper?: boolean # optional — MINOR ``` **Example — `ResolvedConfig` (partial, `types/Config.ts`)**: @@ -98,7 +123,7 @@ processing: processing: slotConstraints: boolean inferNumberProps: boolean - wrapperCollapse: boolean # required — mirrors resolved pattern + collapsePrimitiveWrapper: boolean # required — mirrors resolved pattern ``` **Example — `DEFAULT_CONFIG` (partial, `types/Config.ts`)**: @@ -112,19 +137,19 @@ processing: processing: slotConstraints: false inferNumberProps: false - wrapperCollapse: false + collapsePrimitiveWrapper: false ``` ### Schema changes (`schema/`) | File | Change | Bump | |------|--------|------| -| `schema/workspace.schema.json` | Add `wrapperCollapse` boolean property under `#/definitions/Config/properties/processing/properties` | MINOR | +| `schema/workspace.schema.json` | Add `collapsePrimitiveWrapper` boolean property under `#/definitions/Config/properties/processing/properties` | MINOR | **Example — new property (`schema/workspace.schema.json`)**: ```yaml # Under #/definitions/Config/properties/processing/properties -wrapperCollapse: +collapsePrimitiveWrapper: type: boolean default: false description: > @@ -137,7 +162,7 @@ wrapperCollapse: ### Notes -- `wrapperCollapse` is not added to `required` in the schema — it is optional with a `default` of `false`, matching the pattern for `slotConstraints` and `inferNumberProps`. +- `collapsePrimitiveWrapper` is not added to `required` in the schema — it is optional with a `default` of `false`, matching the pattern for `slotConstraints` and `inferNumberProps`. - The eligibility criteria (disqualifying container styles, slot-bound root, non-singleton children) are implementation details of `specs-from-figma` and are not expressed in the schema. --- @@ -145,7 +170,7 @@ wrapperCollapse: ## Type ↔ Schema Impact - **Symmetric**: Yes — one field added to `Config.processing` in `types/Config.ts`; one property added to `Config.properties.processing.properties` in `schema/workspace.schema.json`. Both carry `false` as default, both are optional in the user-facing contract. -- **Parity check**: `Config.processing.wrapperCollapse?: boolean` ↔ `#/definitions/Config/properties/processing/properties/wrapperCollapse` (`type: boolean`, `default: false`). +- **Parity check**: `Config.processing.collapsePrimitiveWrapper?: boolean` ↔ `#/definitions/Config/properties/processing/properties/collapsePrimitiveWrapper` (`type: boolean`, `default: false`). Both are absent from `required[]`. --- @@ -153,7 +178,7 @@ wrapperCollapse: | Consumer | Impact | Action required | |----------|--------|-----------------| -| `specs-from-figma` | New config field must be read from `ResolvedConfig` and used to gate the wrapper-collapse pass | Read `config.processing.wrapperCollapse`; run collapse only when `true` | +| `specs-from-figma` | New config field must be read from `ResolvedConfig` and used to gate the wrapper-collapse pass | Read `config.processing.collapsePrimitiveWrapper`; run collapse only when `true` | | `specs-plugin-2` | Recompile against updated types; no behavioral change unless the user enables the flag | Recompile; optionally expose toggle in plugin UI | | `specs-cli` | Recompile against updated types; config resolution and `DEFAULT_CONFIG` merge continue to work as-is | Recompile; no code changes required | @@ -169,7 +194,7 @@ wrapperCollapse: ## Consequences -- Consumers can opt in to structurally simpler specs for wrapper-only components by setting `processing.wrapperCollapse: true` in their config. +- Consumers can opt in to structurally simpler specs for wrapper-only components by setting `processing.collapsePrimitiveWrapper: true` in their config. - Existing specs and consumers are unaffected — `false` default reproduces the current behavior exactly. - The anatomy `root` entry for a collapsed component will carry the leaf's element type (`text` or `glyph`) rather than `container`, and `$extensions.com.figma.originalName` will record the original Figma layer name. - `layout` is cleared on collapsed variants (the wrapper's layout properties are not meaningful on a promoted primitive). diff --git a/adr/INDEX.md b/adr/INDEX.md index d0bd537..73aa725 100644 --- a/adr/INDEX.md +++ b/adr/INDEX.md @@ -34,6 +34,7 @@ | # | Title | Highlights | |---|-------|------------| +| 058 | Collapsing Wrapped Primitives — `processing.collapsePrimitiveWrapper` | Add optional boolean to `Config.processing` (default false); strips plain container wrappers around a single text/glyph child and promotes the leaf to spec root | | 057 | Fix `Metadata.generator.version` type: `number` → `string` | Corrects type mismatch — field holds semver strings (e.g. `"1.10.0"`) in all producers; was incorrectly typed as `number` | | 055 | Variant State Classification via `processing.states` | Add `VariantStateEntry` type; add `Config.processing.states` — classifies Figma variant props as browser-driven or consumer-controlled for CSS selector and contract output | | 051 | Platform Code-Syntax Token Profiles | Add `FIGMA_SYNTAX_WEB`/`_IOS`/`_ANDROID` to `Config.format.tokens`, emitting per-platform Figma code syntax with fallback to `TOKEN` | diff --git a/package.json b/package.json index 7d800f5..86dc1a2 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,6 @@ "vitest": "^3.1.1" }, "dependencies": { - "@directededges/specs-from-figma": "^0.19.0" + "@directededges/specs-from-figma": "file:../specs-from-figma" } } diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 903a3b2..ecb0297 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -9,9 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -### Changed - -### Removed +`Config.processing.collapsePrimitiveWrapper` is now wired into the CLI. The config loader defaults it to `false` when absent, and the `init` template includes it as a commented-out option with a description of its behavior. ## [0.21.0] - 2026-06-15 diff --git a/packages/cli/package.json b/packages/cli/package.json index d53649f..835dd02 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -20,8 +20,8 @@ "test": "cd ../.. && vitest -c vitest.config.ts packages/cli/tests" }, "dependencies": { - "@directededges/specs-schema": "^0.25.0", - "@directededges/specs-from-figma": "^0.23.0", + "@directededges/specs-schema": "file:../../../specs/packages/schema", + "@directededges/specs-from-figma": "file:../../../specs-from-figma", "commander": "^11.1.0", "fs-extra": "^11.2.0", "yaml": "^2.3.4", diff --git a/packages/cli/src/Config/ConfigLoader.ts b/packages/cli/src/Config/ConfigLoader.ts index eb95285..a505e56 100644 --- a/packages/cli/src/Config/ConfigLoader.ts +++ b/packages/cli/src/Config/ConfigLoader.ts @@ -173,6 +173,9 @@ export class ConfigLoader { if (typeof corrected.processing.inferNumberProps !== 'boolean') { corrected.processing.inferNumberProps = false; } + if (typeof corrected.processing.collapsePrimitiveWrapper !== 'boolean') { + corrected.processing.collapsePrimitiveWrapper = false; + } // Validate processing.subcomponents if (corrected.processing.subcomponents !== undefined) { diff --git a/packages/cli/src/Config/ConfigTemplates.ts b/packages/cli/src/Config/ConfigTemplates.ts index 1237502..ca2673e 100644 --- a/packages/cli/src/Config/ConfigTemplates.ts +++ b/packages/cli/src/Config/ConfigTemplates.ts @@ -118,6 +118,12 @@ config: # valid numbers are emitted as NumberProp instead of StringProp. (default: false) # inferNumberProps: false + # Collapse primitive wrappers: when true, a component whose root is a plain + # container wrapping a single text or glyph element (no meaningful container + # styles, no slot bindings) is collapsed — the wrapper is stripped and the + # leaf becomes the spec root. All-or-nothing across variants. (default: false) + # collapsePrimitiveWrapper: false + include: # Subcomponent inclusion is controlled by processing.subcomponents above. # If the subcomponents block is present, subcomponents are included. diff --git a/packages/cli/tests/unit/config/ConfigTemplates.test.ts b/packages/cli/tests/unit/config/ConfigTemplates.test.ts index a3876f6..f52908c 100644 --- a/packages/cli/tests/unit/config/ConfigTemplates.test.ts +++ b/packages/cli/tests/unit/config/ConfigTemplates.test.ts @@ -55,6 +55,7 @@ describe('ConfigTemplates', () => { expect(template).toContain('match:'); expect(template).toContain('variantDepth'); expect(template).toContain('details'); + expect(template).toContain('collapsePrimitiveWrapper'); }); it('should include format configuration section', () => { diff --git a/packages/schema/CHANGELOG.md b/packages/schema/CHANGELOG.md index 39d6224..f322d45 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 +- `Config.processing.collapsePrimitiveWrapper` — strip plain container wrappers around a single text/glyph child and promote the leaf to spec root; defaults to false (ADR-058) + ### Changed ### Removed diff --git a/packages/schema/schema/workspace.schema.json b/packages/schema/schema/workspace.schema.json index d057d65..48cdf4c 100644 --- a/packages/schema/schema/workspace.schema.json +++ b/packages/schema/schema/workspace.schema.json @@ -98,6 +98,11 @@ "default": false, "description": "Whether to consolidate slot constraints (anyOf, minChildren, maxChildren) from code-only props into the slot property" }, + "collapsePrimitiveWrapper": { + "type": "boolean", + "default": false, + "description": "When true, a component whose root is a plain container wrapping a single text or glyph element (no meaningful container styles, no slot bindings) is collapsed: the wrapper is stripped and the leaf becomes the spec root. All-or-nothing across variants." + }, "variantDepth": { "type": "number", "enum": [ diff --git a/packages/schema/tests/Config.test-d.ts b/packages/schema/tests/Config.test-d.ts index 13c7f95..96e9789 100644 --- a/packages/schema/tests/Config.test-d.ts +++ b/packages/schema/tests/Config.test-d.ts @@ -24,6 +24,7 @@ const fullConfig: Config = { glyphNamePattern: 'DS Icon Glyph /', codeOnlyPropsPattern: 'Code only props', slotConstraints: true, + collapsePrimitiveWrapper: false, variantDepth: 9999, details: 'LAYERED', inferNumberProps: true, @@ -180,7 +181,7 @@ const defaultTokensValue: typeof DEFAULT_CONFIG.format.tokens = 'TOKEN'; // ─── ResolvedConfig requires all defaultable fields ────────────────────────── const resolved: ResolvedConfig = { - processing: { slotConstraints: false, variantDepth: 9999, details: 'LAYERED', inferNumberProps: false }, + processing: { slotConstraints: false, collapsePrimitiveWrapper: false, variantDepth: 9999, details: 'LAYERED', inferNumberProps: false }, format: { output: 'JSON', keys: 'SAFE', layout: 'LAYOUT', tokens: 'TOKEN', color: 'HEX' }, include: { invalidVariants: false, invalidCombinations: true, emptyVariants: false, defaultSlotContent: false }, transformers: [], @@ -198,6 +199,9 @@ const _dRequired: _DRequired = true; type _SCRequired = ResolvedConfig['processing']['slotConstraints'] extends boolean ? true : never; const _scRequired: _SCRequired = true; +type _CPWRequired = ResolvedConfig['processing']['collapsePrimitiveWrapper'] extends boolean ? true : never; +const _cpwRequired: _CPWRequired = true; + type _INPRequired = ResolvedConfig['processing']['inferNumberProps'] extends boolean ? true : never; const _inpRequired: _INPRequired = true; @@ -240,6 +244,19 @@ const _inferUndefined: Config['processing']['inferNumberProps'] = undefined; const _slotConstraintsUndefined: Config['processing']['slotConstraints'] = undefined; +// ─── collapsePrimitiveWrapper is optional on Config ─────────────────────────── + +const _collapsePrimitiveWrapperUndefined: Config['processing']['collapsePrimitiveWrapper'] = undefined; + +const configWithCollapse: Config = { + processing: { collapsePrimitiveWrapper: true }, + format: {}, + include: {}, +}; + +// @ts-expect-error — collapsePrimitiveWrapper must be boolean +const _badCollapse: Config['processing']['collapsePrimitiveWrapper'] = 'yes'; + // ─── format.color is optional on Config ───────────────────────────────────── const _colorUndefined: Config['format']['color'] = undefined; diff --git a/packages/schema/types/Config.ts b/packages/schema/types/Config.ts index 4401e3d..7975755 100644 --- a/packages/schema/types/Config.ts +++ b/packages/schema/types/Config.ts @@ -81,6 +81,14 @@ export interface Config { codeOnlyPropsPattern?: string; /** Whether to consolidate slot constraints (anyOf, minChildren, maxChildren) from code-only props into the slot property. Optional; defaults to false. @since 0.14.0 */ slotConstraints?: boolean; + /** + * When true, a component whose root is a plain container wrapping a single `text` or + * `glyph` element (no meaningful container styles, no slot bindings) is collapsed: + * the wrapper is stripped and the leaf becomes the spec root. All-or-nothing across + * variants — if any variant fails eligibility, no collapse occurs. Defaults to false. + * @since 0.26.0 + */ + collapsePrimitiveWrapper?: boolean; /** Depth of variant expansion: 1-3 or 9999 for unlimited. Optional; defaults to 9999. */ variantDepth?: 1 | 2 | 3 | 9999; /** Level of detail in output. Optional; defaults to LAYERED. */ @@ -160,6 +168,8 @@ export interface ResolvedConfig { codeOnlyPropsPattern?: string; /** Whether to consolidate slot constraints from code-only props into the slot property. */ slotConstraints: boolean; + /** When true, plain container wrappers around a single text/glyph child are stripped and the leaf is promoted to spec root. */ + collapsePrimitiveWrapper: boolean; /** Depth of variant expansion: 1-3 or 9999 for unlimited. */ variantDepth: 1 | 2 | 3 | 9999; /** Level of detail in output. */ @@ -225,6 +235,7 @@ export interface ResolvedConfig { export const DEFAULT_CONFIG: ResolvedConfig = { processing: { slotConstraints: false, + collapsePrimitiveWrapper: false, variantDepth: 9999, details: 'LAYERED', inferNumberProps: false, diff --git a/site/astro.config.mjs b/site/astro.config.mjs index 5d932df..d74affb 100644 --- a/site/astro.config.mjs +++ b/site/astro.config.mjs @@ -146,6 +146,7 @@ export default defineConfig({ { label: 'codeOnlyPropsPattern', slug: 'config/code-only-props-pattern' }, { label: 'slotConstraints', slug: 'config/slot-constraints', badge: pro }, { label: 'inferNumberProps', slug: 'config/infer-number-props' }, + { label: 'collapsePrimitiveWrapper', slug: 'config/collapse-primitive-wrapper' }, { label: 'instanceExamples', slug: 'config/instance-examples', badge: pro }, ], }, diff --git a/site/src/content/docs/config/collapse-primitive-wrapper.md b/site/src/content/docs/config/collapse-primitive-wrapper.md new file mode 100644 index 0000000..f38373f --- /dev/null +++ b/site/src/content/docs/config/collapse-primitive-wrapper.md @@ -0,0 +1,62 @@ +--- +title: "Collapse Primitive Wrapper" +description: "Strip plain container wrappers around a single text or glyph element and promote the leaf to spec root" +--- + +When enabled, a component whose root is a plain, style-free container holding a single `text` or `glyph` child is collapsed: the wrapper is stripped and the leaf becomes the spec root. This eliminates structural noise for purely typographic or icon primitives — such as Heading, Paragraph, Body, Label, or Icon components — where the container adds no design-system meaning. + +## Configuration + +```yaml +config: + processing: + collapsePrimitiveWrapper: true +``` + +## Result + +**Without** collapse (`false`) a Heading component emits a two-node anatomy with a `container` root: + +```yaml +anatomy: + root: + type: container + label: + type: text + parent: root +``` + +**With** collapse (`true`) the wrapper is stripped and the leaf becomes root: + +```yaml +anatomy: + root: + type: text + $extensions: + com.figma.originalName: Label +``` + +The `$extensions.com.figma.originalName` field on the anatomy root carries the original Figma layer name so the source layer remains traceable. + +## Eligibility + +A component qualifies for collapse when **all** of the following are true: + +- The root element type is `container`. +- The root has exactly one anatomy-visible child. +- That child's type is `text` or `glyph`. +- The child has no children of its own. +- The root carries no slot binding on its children. +- The root carries none of the following styles after default/zero values are stripped: `clipContent`, `cornerRadius`, `strokes`, `strokeAlign`, `strokeWeight`, `itemSpacing`, `padding`, `effects`, `backgroundColor`, `cornerSmoothing`. + +Collapse is **all-or-nothing**: if any variant in the component set fails the eligibility check, no collapse occurs for any variant. + +## Options + +- **Type**: boolean +- **Default**: `false` +- **Effect**: When `true`, eligible wrapper-only components are collapsed so the leaf element becomes the spec root. When `false`, the container is emitted as root regardless of structure. + +## Path + +`config.processing.collapsePrimitiveWrapper` diff --git a/site/src/content/docs/schema/config.md b/site/src/content/docs/schema/config.md index d261af4..d34acf6 100644 --- a/site/src/content/docs/schema/config.md +++ b/site/src/content/docs/schema/config.md @@ -16,6 +16,7 @@ Controls how specs are generated. See the [feature guides](/specs/features/) for | `variantDepth` | `1 \| 2 \| 3 \| 9999` | `9999` | Maximum variant nesting depth (9999 = unlimited) | | `details` | `'FULL' \| 'LAYERED'` | `'LAYERED'` | Output detail level | | `inferNumberProps` | `boolean` | `false` | Infer number-typed props from Figma variant values | +| `collapsePrimitiveWrapper` | `boolean` | `false` | Strip plain container wrappers around a single text/glyph child and promote the leaf to spec root | | `instanceExamples` | `object` | — | **Pro.** Instance example detection — `scope` (PAGE or FILE), `match` patterns, `exclude` patterns, `parentNames` filter. Absent = no detection. Ignored on the free tier | ## `format` @@ -47,6 +48,7 @@ The only runtime export from `@directededges/specs-schema`. Provides defaults fo const DEFAULT_CONFIG: ResolvedConfig = { processing: { slotConstraints: false, + collapsePrimitiveWrapper: false, variantDepth: 9999, details: 'LAYERED', inferNumberProps: false,