@contentful/experience-design-system-cli 2.43.0-alpha.1 → 2.43.1-dev-build-2edf92d4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +1 -1
- package/dist/app.js +17127 -24719
- package/dist/index.js +18183 -27344
- package/dist/package.json +1 -2
- package/dist/src/commands/import-options.d.ts +0 -8
- package/dist/src/tui/debug-store.d.ts +3 -3
- package/dist/src/tui/import/shared/error-step/use-support-bundle.d.ts +3 -0
- package/dist/src/tui/import/shared/running-step/logic.d.ts +21 -7
- package/dist/src/tui/import/shared/running-step/running-step.d.ts +1 -2
- package/dist/src/tui/import/shared/running-step/use-elapsed.d.ts +0 -1
- package/dist/src/tui/import/steps/03-path-validation/logic.d.ts +12 -3
- package/dist/src/tui/import/steps/03-path-validation/screen.d.ts +0 -1
- package/dist/src/tui/import/steps/06-extraction/logic.d.ts +6 -3
- package/dist/src/tui/import/steps/06-extraction/run-tokens-only.d.ts +0 -1
- package/dist/src/tui/import/steps/06-extraction/run.d.ts +9 -9
- package/dist/src/tui/import/steps/06-extraction/screen.d.ts +3 -1
- package/dist/src/tui/import/steps/06-extraction/selection.d.ts +1 -7
- package/dist/src/tui/import/steps/06-extraction/types.d.ts +0 -13
- package/dist/src/tui/import/steps/06-extraction/use-extraction.d.ts +1 -0
- package/dist/src/tui/import/steps/07-scope-gate/graph/index.d.ts +1 -3
- package/dist/src/tui/import/steps/07-scope-gate/screen.d.ts +1 -2
- package/dist/src/tui/import/steps/07-scope-gate/sidebar/ai-flag.d.ts +1 -0
- package/dist/src/tui/import/steps/07-scope-gate/sidebar/components/GroupedSidebar.d.ts +0 -2
- package/dist/src/tui/import/steps/07-scope-gate/state.d.ts +2 -5
- package/dist/src/tui/import/steps/07-scope-gate/types.d.ts +6 -4
- package/dist/src/tui/import/steps/07-scope-gate/use-scope-gate-flow.d.ts +0 -3
- package/dist/src/tui/import/steps/07-scope-gate/use-scope-gate.d.ts +3 -7
- package/dist/src/tui/import/steps/08-generation/logic.d.ts +3 -2
- package/dist/src/tui/import/steps/08-generation/map-tokens.d.ts +1 -1
- package/dist/src/tui/import/steps/08-generation/run.d.ts +1 -9
- package/dist/src/tui/import/steps/08-generation/save-decisions.d.ts +5 -3
- package/dist/src/tui/import/steps/08-generation/screen.d.ts +3 -1
- package/dist/src/tui/import/steps/08-generation/types.d.ts +10 -16
- package/dist/src/tui/import/steps/08-generation/use-generation.d.ts +1 -0
- package/dist/src/tui/import/steps/09-review/components/FieldEditor.d.ts +2 -50
- package/dist/src/tui/import/steps/09-review/components/ReviewComponentPanel.d.ts +1 -4
- package/dist/src/tui/import/steps/09-review/components/ReviewDetailsEditor.d.ts +1 -1
- package/dist/src/tui/import/steps/09-review/generate-review-step.d.ts +0 -17
- package/dist/src/tui/import/steps/09-review/graph/cycle-view.d.ts +0 -3
- package/dist/src/tui/import/steps/09-review/history.d.ts +0 -1
- package/dist/src/tui/import/steps/09-review/review-details-panel.d.ts +2 -1
- package/dist/src/tui/import/steps/09-review/run-live-preview.d.ts +0 -1
- package/dist/src/tui/import/steps/09-review/types.d.ts +1 -2
- package/dist/src/tui/import/steps/09-review/use-review-editor.d.ts +2 -7
- package/dist/src/tui/import/steps/10-flow/flow/preview.d.ts +0 -1
- package/dist/src/tui/import/steps/10-flow/flow/push.d.ts +0 -2
- package/dist/src/tui/import/steps/10-flow/flow/save.d.ts +0 -2
- package/dist/src/tui/import/steps/10-flow/support/push-progress.d.ts +1 -0
- package/dist/src/tui/import/steps/10-flow/support/use-timed-spinner.d.ts +4 -0
- package/dist/src/tui/support-bundle.d.ts +23 -0
- package/dist/src/tui/zip.d.ts +7 -0
- package/package.json +3 -4
- package/skills/generate-components.md +427 -156
- package/skills/generate-tokens.md +7 -1
- package/skills/map-tokens.md +1 -1
- package/skills/select-components.md +159 -20
- package/dist/src/tui/import/cancellation.d.ts +0 -26
- package/dist/src/tui/import/debug-resume-screen.d.ts +0 -7
- package/dist/src/tui/import/debug-resume.d.ts +0 -53
- package/dist/src/tui/import/shared/agent-activity.d.ts +0 -2
- package/dist/src/tui/import/steps/09-review/components/RepresentationEditor.d.ts +0 -11
- package/dist/src/tui/import/steps/09-review/components/source-observation-rows.d.ts +0 -3
- package/dist/src/tui/import/steps/09-review/persist-review-entry.d.ts +0 -14
- package/dist/src/tui/import/steps/09-review/review-models.d.ts +0 -12
- package/dist/src/tui/settings/PreferenceScreen.d.ts +0 -17
- package/skills/consensus/check.md +0 -6
- package/skills/consensus/tier.md +0 -5
- package/skills/consensus/verify.md +0 -6
- package/skills/debate-select.md +0 -37
- package/skills/explore/components.md +0 -30
- package/skills/explore/full-inventory.md +0 -27
- package/skills/explore/model-component.md +0 -44
- package/skills/explore/rules.md +0 -20
|
@@ -30,8 +30,8 @@ All input is embedded inline in the prompt before this file:
|
|
|
30
30
|
- **Raw component data** — `RawComponentDefinition[]` (one component for this run)
|
|
31
31
|
- **DTCG token data** — full token tree, if provided
|
|
32
32
|
- **Token-name sidecar** — raw CSS custom property name → DTCG dot-notation path, if provided
|
|
33
|
-
- **Component source references** — the real file text for the component's own source, plus up to 5 sibling files (e.g. a co-located `.styles.ts` or `utils.ts`, or a file reached transitively through another sibling — such as a re-exported component's own styles module), if provided.
|
|
34
|
-
- A **"uses not shown"** line may accompany these files. The files are
|
|
33
|
+
- **Component source references** — the real file text for the component's own source, plus up to 5 sibling files (e.g. a co-located `.styles.ts` or `utils.ts`, or a file reached transitively through another sibling — such as a re-exported component's own styles module), if provided. **You have no filesystem access and no tools — `sourcePath` is a citation label only, never something to open.**
|
|
34
|
+
- A **"uses not shown"** line may accompany these files. The files are excerpts windowed around the property names, and this line names the properties with at least one use that fell outside the excerpt budget. Treat those properties' consumption as unknown: do not classify them `token` on what is shown, and do not conclude they are unread. Unknown resolves to `enum` (or `string`) per "Ambiguity resolves to `enum`".
|
|
35
35
|
|
|
36
36
|
```typescript
|
|
37
37
|
interface RawPropDefinition {
|
|
@@ -59,18 +59,12 @@ interface RawComponentDefinition {
|
|
|
59
59
|
}
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
The `category` field
|
|
62
|
+
The `category` field on each prop is a pre-classification hint from static analysis heuristics.
|
|
63
|
+
It is correct approximately 80% of the time for simple props. You should:
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
-
|
|
67
|
-
- A closed primitive domain requires evidence of the complete allowed set, such as a closed declared union, an explicit `allowedValues` contract, or source that establishes an exhaustive finite set. A default, one caller, one observed value, a prop name, a partial lookup table, or an existing model is an example or alignment clue; none alone proves the complete set.
|
|
68
|
-
- Preserve the input's declared prop `required` value. Change it only when explicit source evidence establishes the contract. Typical caller presence, a default, empty-render guesses, or model overlap do not establish requiredness.
|
|
69
|
-
- Preserve valid defaults exactly, including `false`, `0`, and the empty string. Do not treat a default as the only permitted value.
|
|
70
|
-
- Source refs and observations are bounded. Missing imports, callers, excerpts, or source files leave those facts unknown; they do not establish that a prop is unused, a component is non-rendering, alternatives are absent, or capture is complete.
|
|
71
|
-
- Keep uncertain properties in the output when evidence is incomplete. State what is unknown in `reason` instead of dropping the property or inventing a domain, requirement, token restriction, or source fact.
|
|
72
|
-
- Decide authorability before choosing a representation. A verified declaration and complete implementation can establish that an input is never read or forwarded; use a reasoned `exclude_prop` for that input even if its type is unknown. Check aliases, rest/spread forwarding, computed access and delegated behavior; unresolved paths prevent that conclusion. Describe the positive implementation evidence, rather than treating an empty use list as proof. Unrelated partial coverage does not invalidate complete local evidence; unknown types or unavailable representations alone never justify exclusion.
|
|
73
|
-
- Existing model summaries are optional alignment context. They may identify a possible match, but source evidence controls the current component's prop names, alternatives, defaults, and requiredness.
|
|
65
|
+
- Trust it for obvious cases (event handlers excluded, text labels as content)
|
|
66
|
+
- Override it when your domain knowledge indicates otherwise
|
|
67
|
+
- NEVER produce zero output — if you disagree with all hints, explain why in descriptions
|
|
74
68
|
|
|
75
69
|
---
|
|
76
70
|
|
|
@@ -97,19 +91,13 @@ Emit one JSON object per line. The CLI parses lines starting with `{`. Lines not
|
|
|
97
91
|
**Rules:**
|
|
98
92
|
|
|
99
93
|
- Emit exactly one JSON object per line. No multi-line JSON.
|
|
100
|
-
-
|
|
101
|
-
- Defaults are typed JSON values; omit them when unknown, use `default_intent: {"action":"clear"}` to clear a decision, and never execute source expressions or narrow a declared domain from observed/default values.
|
|
102
|
-
- Source graphs and defaults remain independent of advisory shape/category/representation; unsupported deeper or cyclic positions retain source evidence and require an explicit plan with unknown mapper/cardinality context.
|
|
103
|
-
- Proposal stdout is staged typed drafts only; caller readiness and activation are separate.
|
|
104
|
-
- Every property in the host-owned required decision inventory must produce exactly one call: `classify_prop` OR `exclude_prop`, including inputs absent from advisory props.
|
|
105
|
-
- Every retained property requires `classify_prop` with `cdf_category`: `content`, `design` or `state`, and either a supported native `cdf_type` or a supplied source-bound `representation_selection`. Missing both representation choices is an incomplete draft and cannot finish generation.
|
|
106
|
-
- An unresolved draft retains `cdf_category`, description, source identity and a nonblank reason, but omits `required`, defaults and all other representation companions until a representation is chosen; declared requiredness remains source evidence.
|
|
94
|
+
- Every prop in the input must produce exactly one call: `classify_prop` OR `exclude_prop`.
|
|
107
95
|
- Every slot must produce exactly one `classify_slot` call.
|
|
108
96
|
- Emit `classify_component` once at the start (required). The `description` field is **required** — always provide a brief description of the component's purpose.
|
|
109
|
-
- `values` is
|
|
110
|
-
- **Do NOT include `values` for `cdf_type: "token"`.** The two property types carry different kinds of list.
|
|
97
|
+
- `values` is required for `cdf_type: "enum"` — must be a non-empty string array of the variant names the prop accepts. **There is no legal `enum` with a guessed list.** If the value set is shown to you anywhere in the material below — the type's own declaration, an `allowedValues` list on the raw prop, a variant/lookup map, a default, a sibling excerpt — copy it and emit `enum`. If it is shown nowhere, emit `cdf_type: "string"` and leave `values` off. `string` is an unrestricted enum: the author can still pass the real value. A guessed list silently rejects it.
|
|
98
|
+
- **Do NOT include `values` for `cdf_type: "token"`.** The two property types carry different kinds of list. An `enum` prop's list holds _variant names_ the component accepts (`"primary"`, `"secondary"`). A `token` prop's list holds _design token paths_ (`"color.brand.primary"`), produced by the separate token-mapping step (`$token.allowed`) — never by you. Emitting `values` on a token prop makes the definition invalid.
|
|
111
99
|
- `token_kind` is required for `cdf_type: "token"` — must be a DTCG `$type` string, e.g. `"color"`.
|
|
112
|
-
- `required` must be a JSON boolean (`true`/`false`), not a string.
|
|
100
|
+
- `required` must be a JSON boolean (`true`/`false`), not a string.
|
|
113
101
|
- `description` on `classify_prop` is customer-facing — keep it short and subject to the description content rules below.
|
|
114
102
|
- `reason` on `classify_prop` is **required** and is your internal rationale — shown to the developer reviewing the import, never to end-users. Use it to explain your reasoning in detail. The customer-facing description content rules below apply to `description` only, not to `reason`.
|
|
115
103
|
- `rationale` on `classify_component` is **REQUIRED**. It is an object with three REQUIRED string sub-fields:
|
|
@@ -130,50 +118,117 @@ Emit one JSON object per line. The CLI parses lines starting with `{`. Lines not
|
|
|
130
118
|
|
|
131
119
|
## Valid cdf_type values
|
|
132
120
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
The host supplies compiler-derived `analysisRepresentationCandidates` for each property. When a native property cannot preserve a nested or recursive structure, select that property's `component-model` candidate with:
|
|
136
|
-
|
|
137
|
-
```json
|
|
138
|
-
{"tool":"classify_prop","prop":"<exact source property>","cdf_category":"content","representation_selection":{"strategy":"component-model","candidateId":"<supplied candidate id>"},"source_revision":"<current sourceRevision>","source_node_id":"<current nodeId>","description":"<what the input supplies>","reason":"<source-grounded choice>"}
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
This is a representation of the original input, separate from a source-declared composition slot. The host derives compact model definitions, slots, ordering, cardinality, references and reconstruction mappings from the compiler graph. Do not invent model definitions, add `classify_slot` subjects, change the inventory, or encode framework/name-based modeling recipes. A model selection replaces the native `cdf_type` choice; omit native definition companions and `representation_proposal`. Preserve source requiredness, defaults and null/presence distinctions. The host validates the candidate identity and complete local plan; unresolved deployment and rendering requirements remain explicit and do not prevent local definition generation. Missing source coverage or unmapped source nodes still prevent completion.
|
|
121
|
+
Exactly **6** valid types:
|
|
142
122
|
|
|
143
|
-
|
|
123
|
+
| cdf_type | Use case |
|
|
124
|
+
| ---------- | -------------------------------------------------------------------------- |
|
|
125
|
+
| `string` | Plain text, URLs, href props, numbers (as string), any string-shaped value |
|
|
126
|
+
| `richtext` | Formatted text, HTML, ReactNode used as markup |
|
|
127
|
+
| `media` | Images, videos, media assets |
|
|
128
|
+
| `enum` | Fixed set of string choices — requires `values` |
|
|
129
|
+
| `token` | Design-token-linked prop — requires `token_kind` |
|
|
130
|
+
| `boolean` | Boolean toggle props (visible, disabled, enabled, etc.) |
|
|
144
131
|
|
|
145
|
-
|
|
146
|
-
- Select a supplied component-model candidate for independently editable data that cannot fit native structures. Do not request a Component merely for nullable/optional syntax, an array wrapper or a single-field structural envelope.
|
|
147
|
-
- Represent arrays of model values through ordered slots, reusing model definitions for recursive elements. Retain a grouping boundary when flattening would merge nested arrays or remove nullable/empty element positions.
|
|
148
|
-
- Carry single-field structural envelopes through source-keyed reconstruction metadata. Exact keys, source paths, requiredness, defaults and empty/unset behavior remain in the logical graph and mappings; separately editable fields stay native properties or model Components.
|
|
149
|
-
- Multi-alternative unions retain their real branch choices. Collapsing a union with one nonempty value is an authoring-structure change, not permission to equate distinct null behavior or erase required source inputs.
|
|
150
|
-
- Explain the editable data and unresolved reconstruction requirements in reason. The host validates the source-derived compact plan and creates its definitions; do not prescribe a model count or layout from a component/library name.
|
|
151
|
-
|
|
152
|
-
Valid local CDF types:
|
|
153
|
-
|
|
154
|
-
| cdf_type | Use case |
|
|
155
|
-
| ---------------------- | ------------------------------------------------------- |
|
|
156
|
-
| `string` | Plain text and string-shaped source values |
|
|
157
|
-
| `richtext` | Formatted text, HTML, ReactNode used as markup |
|
|
158
|
-
| `media` | Images, videos, media assets |
|
|
159
|
-
| `string` with `values` | Fixed declared set of string choices |
|
|
160
|
-
| `token` | Design-token-linked prop — requires `token_kind` |
|
|
161
|
-
| `boolean` | Boolean toggle props (visible, disabled, enabled, etc.) |
|
|
162
|
-
|
|
163
|
-
> Native `number`, `array` and `record` definitions preserve typed values and complete recursive metadata locally; this vocabulary does not prove remote mapper capability.
|
|
132
|
+
> **IMPORTANT: No `number` type.** The design-systems API only supports the `String` design property variant for numeric values. All numeric props must use `cdf_type: "string"` with the number as a string default (e.g. `"0"`, `"100"`). Boolean props can now use `cdf_type: "boolean"` directly.
|
|
164
133
|
|
|
165
134
|
> **Avoid `link` type for simple URL props.** Props named `href`, `url`, or holding plain URL strings → `cdf_type: "string"`, `cdf_category: "content"`. Reserve `link` for props that hold a reference to another Contentful entry.
|
|
166
135
|
|
|
167
136
|
---
|
|
168
137
|
|
|
169
|
-
###
|
|
138
|
+
### `enum` versus `token`
|
|
139
|
+
|
|
140
|
+
Both describe a closed set of choices, but the _contents_ differ, and that is the whole distinction:
|
|
141
|
+
|
|
142
|
+
| | What the list holds | Example |
|
|
143
|
+
| ------- | --------------------------------------- | ------------------------------------------------ |
|
|
144
|
+
| `enum` | Variant names the component accepts | `["primary", "secondary", "ghost"]` |
|
|
145
|
+
| `token` | Design token paths the prop may bind to | `["color.brand.primary", "color.brand.neutral"]` |
|
|
146
|
+
|
|
147
|
+
The distinction is **what the component does with the value it receives**. That
|
|
148
|
+
is decidable from source; it is not inferred from the property's name, from the
|
|
149
|
+
component's role, or from the property's TypeScript type.
|
|
150
|
+
|
|
151
|
+
An `enum` design property is delivered as a plain string, so the component
|
|
152
|
+
receives `variant="primary"` — the value its own code branches on. A `token`
|
|
153
|
+
property is resolved to the token's value _before_ it reaches the component, so
|
|
154
|
+
that same component would receive `variant="#0059c8"` and match no branch.
|
|
155
|
+
|
|
156
|
+
#### The three-question procedure (closed, ordered)
|
|
157
|
+
|
|
158
|
+
For every design prop that carries a value (not a boolean), answer these three
|
|
159
|
+
questions **in this order**, from the source shown, and stop at the first answer
|
|
160
|
+
that decides. There is no fourth question.
|
|
161
|
+
|
|
162
|
+
- **Q1 — Does the component look the value up, switch on it, or compare it against names?**
|
|
163
|
+
An indexed access (`tokens[fontColor]`, `SPACING[padding]`, a `Record<Variant, Token>`),
|
|
164
|
+
a `switch (variant)` / `if (variant === 'x')` chain, a `styles[variant]` class lookup,
|
|
165
|
+
or a vocabulary of its own (`allowedValues`, a literal union) — any of these means
|
|
166
|
+
the component needs the _name_. A presence check (`if (width)`) or arithmetic on
|
|
167
|
+
the value is not a lookup.
|
|
168
|
+
**Yes → `enum`, names in `values`, stop.** No → Q2.
|
|
169
|
+
- **Q2 — Is the value written straight into a style or attribute?**
|
|
170
|
+
`rx={radius}`, `padding: ${padding}`, `style={{ color }}`, `background: var(${bg})` —
|
|
171
|
+
the value reaches the renderer without the component resolving it.
|
|
172
|
+
**No → not `token`; leave this procedure and continue with the type rules.** Yes → Q3.
|
|
173
|
+
- **Q3 — Is there a design-token reference at that use?** Any one of:
|
|
174
|
+
the parameter default is a token (`radius = tokens.borderRadiusSmall`), the raw prop
|
|
175
|
+
carries a `tokenReference`, or an inline `tokens.*` / `var(--*)` sits in the same
|
|
176
|
+
expression. The reference must belong to _this_ prop — a `tokens.*` on some other
|
|
177
|
+
line of the file, or in a sibling's style object, is not one.
|
|
178
|
+
**Yes → `token`, stop; resolve `token_kind` from that reference. No → `string`, stop.**
|
|
179
|
+
|
|
180
|
+
**The raw TypeScript type is not an input to Q1–Q3.** `string`, `number`,
|
|
181
|
+
`string | number`, `stringOrNumber` — none of these answers any question above;
|
|
182
|
+
they say what the compiler accepts, not what the component does with the value.
|
|
183
|
+
"The type accepts any value, the default just happens to be a token reference" is
|
|
184
|
+
a Q3-yes described as a no: a token parameter default _is_ the design-token
|
|
185
|
+
reference Q3 asks for, and the prop is `token`. Equally, a prop named `color` with
|
|
186
|
+
no lookup and no token reference is `string`, however token-like the name.
|
|
187
|
+
|
|
188
|
+
**Two worked examples — same shape, opposite answers:**
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
// Divider.tsx
|
|
192
|
+
export const Divider = ({ thickness = tokens.borderWidthDefault, inset = 0 }: DividerProps) => (
|
|
193
|
+
<hr style={{ borderTopWidth: thickness, marginLeft: inset }} />
|
|
194
|
+
);
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
- `thickness` — Q1: no lookup. Q2: yes, `borderTopWidth: thickness`. Q3: yes, the
|
|
198
|
+
parameter default is `tokens.borderWidthDefault`. → **`token`**, `token_kind` from
|
|
199
|
+
`borderWidthDefault` via the sidecar. Its `string | number` annotation changes nothing.
|
|
200
|
+
- `inset` — Q1: no lookup. Q2: yes, `marginLeft: inset`. Q3: no, the default is the
|
|
201
|
+
bare number `0` and nothing at this use is a token. → **`string`**, `default: "0"`.
|
|
202
|
+
|
|
203
|
+
A lookup map from variant names to tokens is evidence **for** `enum`, not against
|
|
204
|
+
it — that is Q1 answering yes. The map exists precisely because the component was
|
|
205
|
+
handed a name it had to resolve itself.
|
|
206
|
+
|
|
207
|
+
**`token` is earned by Q2 and Q3 together, and only that way.** Cite both lines
|
|
208
|
+
in `reason`: the interpolation and the token reference. Once both are cited, the
|
|
209
|
+
prop is `token` — do not then downgrade it because of its type annotation, because
|
|
210
|
+
the default "could be overridden" (every default can), or because the tie-break
|
|
211
|
+
below exists. Nothing downstream re-derives, corrects, or second-guesses this —
|
|
212
|
+
what you emit is what ships.
|
|
213
|
+
|
|
214
|
+
**Ambiguity resolves to `enum` — but only when Q1–Q3 cannot be answered, and only
|
|
215
|
+
when you can still cite its values.** The tie-break applies solely when the source
|
|
216
|
+
shown does not let you answer the three questions: the property is interpolated
|
|
217
|
+
in one place and looked up in another, the use sits in a file you cannot see, or
|
|
218
|
+
the source is truncated at the point of use (the "uses not shown" line). In those
|
|
219
|
+
cases emit `enum` if a value set is citable from the source shown, else `cdf_type:
|
|
220
|
+
"string"` per "no legal `enum` with a guessed list" — either way, record the
|
|
221
|
+
ambiguity in `reason`. When the three
|
|
222
|
+
questions _can_ be answered from the source shown, they are answered and the
|
|
223
|
+
tie-break does not apply: a clear Q1-yes is `enum`, a clear Q2-yes + Q3-yes is
|
|
224
|
+
`token`, and a type annotation does not create ambiguity. An `enum` is delivered
|
|
225
|
+
as the plain string the component was written to receive and remains reachable
|
|
226
|
+
from a content-type field; a wrong `token` delivers a resolved value the
|
|
227
|
+
component cannot branch on and is permanently excluded from content-type field
|
|
228
|
+
mapping. That asymmetric cost is why a genuine tie goes to `enum` — it is not a
|
|
229
|
+
reason to call a decided case a tie.
|
|
170
230
|
|
|
171
|
-
|
|
172
|
-
A domain with an open alternative remains open even when its known values are also design token names.
|
|
173
|
-
Use `token` only when the source contract consumes a resolved design token value; a lookup vocabulary consumes names and is not itself a token value.
|
|
174
|
-
Preserve all source-supported alternatives and explain unrepresented alternatives for review; do not invent a narrower type to resolve uncertainty.
|
|
175
|
-
For DTCG tokens, look up `$type` and `$token.kind` from supplied token data or the token-name sidecar.
|
|
176
|
-
Do not include `values` when `cdf_type` is `token`; `$token.allowed` is handled by the separate mapping stage.
|
|
231
|
+
---
|
|
177
232
|
|
|
178
233
|
## Valid cdf_category values
|
|
179
234
|
|
|
@@ -187,127 +242,362 @@ The pre-classified `category` in the raw input is a starting point — correct i
|
|
|
187
242
|
|
|
188
243
|
---
|
|
189
244
|
|
|
190
|
-
## Mapping guidance —
|
|
245
|
+
## Mapping guidance — Classification decision tree
|
|
246
|
+
|
|
247
|
+
For each `RawPropDefinition`, apply in order:
|
|
248
|
+
|
|
249
|
+
1. **Framework / DOM / accessibility pass-through?** → `exclude_prop`. These are escape hatches for developers, not configurable surfaces for marketers. Exposing them in the ExO editor adds noise that obscures the props that actually carry intent.
|
|
250
|
+
|
|
251
|
+
**The governing principle:** a prop is pass-through if it is inherited from a base interface the component extends (`React.HTMLAttributes<...>`, `AllHTMLAttributes`, a native element's attribute interface, an ARIA-attributes interface, etc.) or is one of a small set of universal framework hooks, **and the component does not redeclare or give it component-specific meaning**. Test this from the type declaration, not from name-matching against a list — a component that `extends React.HTMLAttributes<HTMLDivElement>` inherits `title`, `lang`, `translate`, `slot`, and dozens more that a fixed list will never enumerate; a component that redeclares one of these with its own type or JSDoc (e.g. its own `title: string` documented as "card heading") has given it new meaning and it is no longer pass-through.
|
|
252
|
+
|
|
253
|
+
Always exclude under this principle:
|
|
254
|
+
- Framework internals: `ref`, `innerRef`, event handlers (any `onSomething`), `testId`, `data-testid`, `key`
|
|
255
|
+
- Any prop inherited from an HTML-attributes base interface and not redeclared by the component — this replaces any fixed name list. Common examples include `className`, `class`, `classes`, `classNames`, `rootClassName`, `prefixCls`, `style`, `styles`, `id`, `role`, `tabIndex`, `htmlFor`, `for`, `slot`, `is`, `lang`, `dir`, `hidden`, `draggable`, `spellCheck`, `contentEditable`, `inputMode`, `autoComplete`, `autoFocus`, `translate`, `part`, `exportparts` — but these are examples of the principle, not an exhaustive list; apply the inheritance test to any prop the examples don't cover.
|
|
256
|
+
- Accessibility pass-through: any `aria-*` or `ariaSomething` prop (including bare `aria` as an aria-attributes object), `aria-label`, `aria-hidden`, `aria-describedby`, `aria-controls`
|
|
257
|
+
- Data attributes: any `data-*` prop
|
|
258
|
+
- **Polymorphic component props**: `as`, `element`, `component` (when typed as an HTML tag string or component reference) — these change rendered HTML, not marketer-visible behavior
|
|
259
|
+
- **Framework theming / pass-through escape hatches**: PrimeVue's `dt` / `pt` / `ptOptions` / `unstyled`, MUI/Chakra-style `sx`, anything explicitly typed as a developer "override" / "passthrough" object
|
|
260
|
+
- **Important caveat**: only exclude when the prop is inherited/universal _and unredeclared_. Compound names like `fileName`, `displayName`, `dataset`, `dataSource`, `roleDescription`, `idLabel` are not pass-through — classify them normally. Likewise, a `title` (or any other inherited-attribute name) that the component redeclares with its own type or doc comment is component-specific content, not pass-through — classify it per rule 2 onward, do not exclude it and do not route it through the `title` → `content` correction below.
|
|
261
|
+
|
|
262
|
+
2. **Common semantic props — DO classify, do not exclude.** The LLM has been over-excluding these because they sound like framework internals; they are not. Classify each per the rest of this tree:
|
|
263
|
+
- `icon` / `leftIcon` / `rightIcon` / `prefixIcon` / `suffixIcon` — slot or `string` (icon name); see slot guidance below
|
|
264
|
+
- `items` / `options` / `actions` / `links` — usually array content; if the element shape is simple, classify as `string` (comma-separated names/IDs) and note in `description`. Only exclude when elements are deep nested objects with no flat representation.
|
|
265
|
+
- `value` (the bare prop, not `modelValue`) — content prop, usually `string` (or `enum` if from a fixed set). Note: Vue's `modelValue` / `modelModifiers` are excluded by pre-classify because they're v-model framework wiring.
|
|
266
|
+
- `name` — content prop, usually `string`. Treat it as semantic component data, not as a DOM pass-through.
|
|
267
|
+
- `form` (when not the literal `<form>` HTML attribute) — typically content; classify as `string` unless it's a complex form-config object
|
|
268
|
+
- `inputId` / `componentId` — these CAN be content (anchor IDs, marketer-set tracking refs). Classify as `string`, `cdf_category: "content"` when the type is a plain string. Only exclude if the prop is clearly internal (e.g. typed as a generated React ID).
|
|
269
|
+
- `accessibleNameRef` / `accessibleDescriptionRef` (web components) — these are ID references for a11y wiring; classify as `string`, `cdf_category: "state"` (behavioral wiring, not design or content).
|
|
270
|
+
- `eventDetails` / similar telemetry props — `cdf_category: "state"`.
|
|
271
|
+
3. **Positional/geometric design prop?** (`top`, `bottom`, `left`, `right`, `rotation`, `offset`, `zIndex`) → `classify_prop`, `cdf_type: "string"`, `cdf_category: "design"`.
|
|
272
|
+
4. **`enum` or `token`? — run the three-question procedure first.** Before any type-based rule, for every design prop that carries a value, answer Q1–Q3 from "`enum` versus `token`" above, in order, from the source shown:
|
|
273
|
+
- **Q1** looked up / switched on / compared / has its own vocabulary → `cdf_type: "enum"`, names in `values`, no `token_kind`. This holds even when every name maps 1:1 to a design token; the map is the component resolving a name it was given.
|
|
274
|
+
- **Q2** not written straight into a style or attribute → not `token`; continue to rules 5–7.
|
|
275
|
+
- **Q3** written straight into a style or attribute **and** a token reference at that use (a `tokens.*` parameter default, a `tokenReference`, an inline `tokens.*` / `var(--*)`) → `cdf_type: "token"`, `token_kind` from that reference via the sidecar, no `values`. Interpolated with no token reference at that use → `cdf_type: "string"`.
|
|
276
|
+
- Q1–Q3 **cannot be answered** from the source shown (unread, truncated, in a file you cannot see, evidence pointing both ways) → `cdf_type: "enum"` (or `string`). See "Ambiguity resolves to `enum`" above.
|
|
277
|
+
|
|
278
|
+
**Do not classify on the component's role.** A layout primitive is not automatically a token consumer. Design systems routinely key their primitives on token _names_, and those props are `enum` — the component needs the name to perform its own lookup:
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
// Box.tsx — padding is an enum, despite Box being a layout primitive
|
|
282
|
+
const { padding } = props; // "spacingXs"
|
|
283
|
+
getSpacingStyles({ padding }); // → SpacingTable["spacingXs"] → "0.5rem"
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Classify that `padding` as `token` and the platform delivers `"0.5rem"`, so `SpacingTable["0.5rem"]` is `undefined` and the spacing silently disappears. A value that is spelled like a token name is still a lookup key (Q1 yes). Curated components with named variant APIs (`Button`, `Tag`, `Badge`, `Avatar`, `Notification`) are `enum` for the same reason. A design system may legitimately produce **zero** token-typed props.
|
|
287
|
+
|
|
288
|
+
5. **Union of string literals** (e.g. `'a' | 'b' | 'c'`)? → `cdf_type: "enum"`, extract literals into `values`. (Rule 4's Q1 will normally have decided this already — a literal union is the prop's own vocabulary.) This applies even when Component source references show each literal resolving to a design token internally — a resolved token map is evidence about the component's internals, not its interface.
|
|
289
|
+
6. **Raw type is `string`** and prop name is `href`, `url`, or clearly a URL? → `cdf_type: "string"`, `cdf_category: "content"`.
|
|
290
|
+
7. **Raw type is `string` / `number` / `boolean`?** → For `boolean`, use `cdf_type: "boolean"` with `default: true` or `false` (native boolean). For `number`, use `cdf_type: "string"` with `default` as the numeric value as a string (e.g. `"0"`). For `string`, use `cdf_type: "string"`. **A design prop reaches this rule only after leaving rule 4 at Q2 (not written into a style) or Q3 (written into a style with no token reference at that use).** A prop that answered Q3 yes is already `token` and never arrives here — a `string`, `number`, or `string | number` annotation on it does not bring it back to this rule.
|
|
291
|
+
8. **Media/image type** (`ImageProps`, `MediaSource`, asset types)? → `cdf_type: "media"`.
|
|
292
|
+
9. **Rich text / markup** (`ReactNode` used as content, HTML string)? → `cdf_type: "richtext"`.
|
|
293
|
+
10. **Complex type — resolve before excluding** (see below).
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Resolving complex types — do not exclude without reasoning
|
|
298
|
+
|
|
299
|
+
A prop with a complex TypeScript type is **not automatically excluded**. Many props that appear complex carry real marketer-configurable information. Before excluding, ask: _"Could a marketer set this value in Contentful?"_ If yes, classify it.
|
|
300
|
+
|
|
301
|
+
**Common resolvable patterns:**
|
|
302
|
+
|
|
303
|
+
| Raw type pattern | How to resolve |
|
|
304
|
+
| ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
305
|
+
| `'primary' \| 'secondary' \| 'ghost'` (union of literals) | → `enum`, extract `values` |
|
|
306
|
+
| `HeadingSize` / `ButtonVariant` / any named type that is clearly a finite set of visual options | → `enum` if you can cite its values from the source shown (the type's own declaration, a lookup table, a default) — copy them, never invent them. If the source shown does not enumerate the values, this is not the Q1–Q3 ambiguity tie-break — it is the "no legal `enum` with a guessed list" rule: emit `cdf_type: "string"` instead, with the missing evidence noted in `reason`. Do not default to a generic guess like `['sm', 'md', 'lg']` or `['primary', 'secondary']` — those are one design system's vocabulary, not a fallback for missing evidence. |
|
|
307
|
+
| `Variant` / `variant` prop | Usually a visual design variant. → `enum`, `cdf_category: "design"`. Same evidence rule as above — cite values from the source shown; do not invent them. |
|
|
308
|
+
| `Section[]` / array of custom items where the structure is unclear | → `exclude_prop` only if the array elements are complex objects with no obvious flat representation. If items are simple (title, label, id), consider representing as `string` (a comma-separated IDs or keys) or note in `description` why. |
|
|
309
|
+
| `ExperienceConfiguration<Variant>` / deep generic | Personalization config — → `exclude_prop`, reason: `"personalization configuration — framework internal"` |
|
|
310
|
+
| `React.Dispatch<...>` / setter | State setter — → `exclude_prop`, reason: `"React state setter — framework internal"` |
|
|
311
|
+
| `React.RefObject<...>` / `ref` | → `exclude_prop`, reason: `"ref — framework internal"` |
|
|
312
|
+
| `() => void` / callback | → `exclude_prop`, reason: `"callback function — framework internal"` |
|
|
313
|
+
| `ReactNode` used as a slot-like prop (children, `icon`, `footer`) | → classify as a `slot` if it represents an injectable area, or `richtext` if it is inline markup content |
|
|
314
|
+
| `boolean` with a name like `hideChevron`, `imageOnLeft`, `enableBackgroundColorEffect` | → `boolean`, `cdf_category: "design"`, `default: true` or `false` — these control visual appearance |
|
|
315
|
+
| `boolean` with a name like `preview`, `hideContentForPersonalization` | → `boolean`, `cdf_category: "state"`, `default: false` — these control behavior |
|
|
316
|
+
| `string` used as a `componentId`, `sectionKey`, `componentName` | → `string`, `cdf_category: "state"` — these are identifiers for tracking/lookup |
|
|
317
|
+
| `string` locale (e.g. `locale: string`) | → `string`, `cdf_category: "state"` — locale is a behavioral/routing value |
|
|
318
|
+
|
|
319
|
+
**When to finally exclude:**
|
|
320
|
+
|
|
321
|
+
- The type is a callback signature or event handler
|
|
322
|
+
- The type is a React ref
|
|
323
|
+
- The type is a React state setter (`Dispatch`)
|
|
324
|
+
- The type is a deep generic used for personalization/A-B testing platform config (e.g. `ExperienceConfiguration<T>`)
|
|
325
|
+
- The type is an array of rich objects where no flat representation makes sense for a marketer
|
|
326
|
+
|
|
327
|
+
If you exclude a prop that could have been classified, the marketer loses the ability to configure it in Contentful. Prefer classifying with a reasonable inference over excluding.
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## Handling nested object props
|
|
332
|
+
|
|
333
|
+
When a prop has an inline object type (e.g., `item: { url?: string; alt?: string; target?: string }`),
|
|
334
|
+
classify EACH field as a separate prop using underscore notation (parentName_fieldName):
|
|
191
335
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
Choose `content`, `design` or `state` from the evidenced responsibility of the input; keep unsupported reasoning explicit.
|
|
195
|
-
Exclude an input only when its source purpose establishes why it cannot be represented by the target contract.
|
|
196
|
-
A ref, event handler or style-shaped input still needs its own source reasoning; do not exclude it from its spelling alone.
|
|
197
|
-
Keep structured/complex alternatives and source defaults in the evidence record when the target format cannot represent them; preserve that capability limit for review.
|
|
198
|
-
Never manufacture requiredness from callers, typical usage or model agreement.
|
|
336
|
+
Input:
|
|
337
|
+
{"name":"item","type":"{ url?: string; alt?: string; linkUrl?: string; target?: string }","required":true}
|
|
199
338
|
|
|
200
|
-
|
|
339
|
+
Output:
|
|
340
|
+
{"tool":"classify_prop","prop":"item_url","cdf_type":"string","cdf_category":"content","required":false,"description":"Image source URL"}
|
|
341
|
+
{"tool":"classify_prop","prop":"item_alt","cdf_type":"string","cdf_category":"content","required":false,"description":"Image alt text"}
|
|
342
|
+
{"tool":"classify_prop","prop":"item_linkUrl","cdf_type":"string","cdf_category":"content","required":false,"description":"Navigation URL"}
|
|
343
|
+
{"tool":"classify_prop","prop":"item_target","cdf_type":"enum","cdf_category":"design","required":false,"values":["_blank","_self","_parent","_top"],"description":"Link open behavior"}
|
|
201
344
|
|
|
202
|
-
|
|
345
|
+
Note: Underscore notation is used (not dot-notation) because the backend's `toDisplayName()`
|
|
346
|
+
function splits on `.` and takes only the last segment, producing poor display names. Underscore
|
|
347
|
+
produces display names like "Item Url", "Item Alt" which are more readable in the ExO editor.
|
|
203
348
|
|
|
204
|
-
|
|
205
|
-
- Assess defaults, comparisons, forwarding and output behavior using supplied source evidence. If null and omission have equivalent behavior, an optional underlying type can represent that authoring state. Neither a type union, a prop name nor a framework convention proves equivalence.
|
|
206
|
-
- Record each nullable union judgment in `null_handling`: `[{"sourceNodeId":"<nullable-union-node>","handling":"omit-equivalent","reason":"<source behavior supporting equivalence>"}]`. Copy the current property `source_revision` and root `source_node_id` from the supplied identities. Each judgment applies only to its named source union. `distinct` and `unknown` retain meaningful or uncertain null behavior; they do not authorize omission.
|
|
207
|
-
- A supported optional underlying type must still preserve every non-null alternative, finite domain, field and default. Never replace null with empty strings, false, zero or empty collections.
|
|
208
|
-
- Ordinary scalar fields cannot carry null defaults. For established omission equivalence, leave the editable default absent and retain the original null default in source evidence; omission invokes the unchanged source behavior. Explicit incompatible defaults remain validation errors.
|
|
209
|
-
- These are source-bound agent judgments, not compiler proofs. Keep their rationale separate from original source facts. Meaningful or uncertain distinctions require a compatible representation or a precise validation failure; do not discard the property or claim readiness.
|
|
349
|
+
Rules for nested objects:
|
|
210
350
|
|
|
211
|
-
|
|
351
|
+
- Flatten to max depth 2 (e.g., `item_nested_deep` is acceptable, deeper is not)
|
|
352
|
+
- Each leaf field gets its own classify_prop call with underscore-joined name
|
|
353
|
+
- Apply the same classification rules as top-level props
|
|
354
|
+
- If the object has > 10 fields, classify the most important 10 and exclude the rest
|
|
355
|
+
- If the object type cannot be resolved (opaque generic, imported interface without visible fields), exclude the parent prop with reason "opaque nested type"
|
|
212
356
|
|
|
213
|
-
|
|
214
|
-
- Root Array, root Record and direct Array<Record> may use the supported local native profile; retain every declared field and nested/item requiredness.
|
|
215
|
-
- Full source graph/type/census/default/domain evidence is authoritative; advisory shape is optional and cannot establish completeness.
|
|
216
|
-
- Preserve deeper, open-indexed, tuple, union, cyclic, imported and unresolved source evidence with explicit coverage and outcomes; propose generic source-derived alternatives when native mapping is unavailable.
|
|
217
|
-
- Alternate definitions require source revision/node identity, complete field accounting and dependency closure, plus caller-owned mapper/cardinality/readiness context; no draft can authorize activation.
|
|
357
|
+
---
|
|
218
358
|
|
|
219
359
|
## Resolving `token_kind`
|
|
220
360
|
|
|
221
361
|
`token_kind` becomes `$token.kind` in the CDF output — the DTCG `$type` of the token the prop binds to. For a prop that earned `cdf_type: "token"` (Q2 yes, Q3 yes), take the token name cited at Q3 (`tokens.borderRadiusSmall`, `var(--brand-primary)`, a raw `tokenReference`) and resolve it against the inline token data: through the token-name sidecar (raw name → DTCG dot-notation path) when one is provided, otherwise by matching the name to the DTCG tree by spelling (`tokens.borderRadiusSmall` → `border-radius.border-radius-small`); the leaf's `$type` (`"dimension"`) is the `token_kind`. If the name cannot be resolved to a leaf, keep `cdf_type: "token"`, omit `token_kind`, and say so in `description` (`"WARNING: token_kind unknown — <name> not found in token data"`).
|
|
222
362
|
|
|
223
|
-
**Canonical example — a named variant backed by tokens is still
|
|
363
|
+
**Canonical example — a named variant backed by tokens is still `enum`:**
|
|
224
364
|
|
|
225
365
|
```
|
|
226
366
|
Tag.variant has allowedValues ["primary","secondary","warning","negative"] and PillNext.styles.ts
|
|
227
367
|
maps each one to a token (primary→tokens.blue100, warning→tokens.orange100, ...). The mapping is
|
|
228
368
|
internal to the design system — Tag's prop signature accepts the name, not the token. Classifying
|
|
229
|
-
as
|
|
230
|
-
{"tool":"classify_prop","prop":"variant","cdf_type":"
|
|
369
|
+
as enum so the author picks "primary" and the component receives "primary".
|
|
370
|
+
{"tool":"classify_prop","prop":"variant","cdf_type":"enum","cdf_category":"design","required":false,"values":["primary","secondary","warning","negative"],"default":"primary","description":"Visual variant","reason":"Fully token-backed internally, but the component consumes the variant name. Token classification would deliver a resolved color the component cannot branch on."}
|
|
231
371
|
```
|
|
232
372
|
|
|
233
373
|
---
|
|
234
374
|
|
|
375
|
+
## Category correction rules
|
|
376
|
+
|
|
377
|
+
The pre-classified `category` is wrong in predictable ways. Correct silently (document in `description`):
|
|
378
|
+
|
|
379
|
+
- Visual style props (`color`, `size`, `padding`, `spacing`, `variant`, `theme`, `bgColor`, `imageOnLeft`, `enableXxx`) classified as `content` or `state` → `design`
|
|
380
|
+
- Interactive/behavioral state props (`disabled`, `loading`, `expanded`, `selected`, `checked`, `active`, `isOpen`, `isSearchVisible`, `showXxx`, `hideXxx`, `preview`, `componentId`, `sectionKey`, `variantIndex`) classified as `design` or `content` → `state`
|
|
381
|
+
- Text/label/data props (`label`, `description`, `caption`, `text`, `boldText`, `labelText`, `richText`, `backgroundImage`, `link`, `placeholder`, `searchValue`, `total`, `slug`) classified as `design` → `content`. `title` is excluded from this auto-correction — it is one of the most common HTML-inherited attribute names (see the pass-through principle above). Only apply this correction to a `title` prop the component redeclares with its own type or documented, component-specific meaning; a `title` that is bare inherited pass-through was already excluded in rule 1 and never reaches this step.
|
|
382
|
+
- Locale classified as `content` → `state` (it is a behavioral routing value, not editor-filled text)
|
|
383
|
+
|
|
384
|
+
> **Key question for category**: "Who fills this in?" — A content editor fills in `content`. A designer configures `design`. Neither fills in `state` — it comes from routing, runtime behavior, or component infrastructure.
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
235
388
|
## Slot classification
|
|
236
389
|
|
|
237
|
-
`classify_slot` maps to `$slots
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
390
|
+
The `classify_slot` tool call maps to CDF's `$slots` object. Each slot you classify becomes a `$slots` entry; the `allowed_components` field maps to `$allowedComponents` in the output CDF.
|
|
391
|
+
|
|
392
|
+
For each `RawSlotDefinition`:
|
|
393
|
+
|
|
394
|
+
- `name` → `slot` field
|
|
395
|
+
- `description` → `description` field
|
|
396
|
+
- `allowedComponents` → `allowed_components` (pass through)
|
|
397
|
+
- `allowed_components` must contain only exact names from the hard allowlist supplied in the prompt; never invent a child from an import, JSX element, type name, icon name, or implementation helper, and omit any child that is not listed.
|
|
398
|
+
- `required`:
|
|
399
|
+
- `true` if the component does not render correctly without content in this slot (primary content area of a Card, Dialog body, etc.)
|
|
400
|
+
- `false` if clearly optional (icon slot, footer slot with a default, decorative slot)
|
|
401
|
+
- Default to `true` when the source gives no signal
|
|
402
|
+
|
|
403
|
+
**Pre-named slots:** If the input contains a slot whose `name` was already inferred by the pipeline (e.g. `"children"`, `"slot_0"`), treat it as you would any named slot — classify it normally. The pipeline renames empty-named slots to heuristic names before passing them to you; your job is to confirm or enrich the classification (set `required`, `description`, `allowed_components`), not to rename again.
|
|
404
|
+
|
|
405
|
+
---
|
|
243
406
|
|
|
244
407
|
## Examples
|
|
245
408
|
|
|
246
|
-
|
|
247
|
-
|
|
409
|
+
### Simple component
|
|
410
|
+
|
|
411
|
+
Input:
|
|
412
|
+
|
|
413
|
+
```json
|
|
414
|
+
{
|
|
415
|
+
"name": "Button",
|
|
416
|
+
"props": [
|
|
417
|
+
{
|
|
418
|
+
"name": "label",
|
|
419
|
+
"type": "string",
|
|
420
|
+
"category": "content",
|
|
421
|
+
"required": true
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
"name": "variant",
|
|
425
|
+
"type": "'primary'|'secondary'|'ghost'",
|
|
426
|
+
"category": "design",
|
|
427
|
+
"defaultValue": "'primary'"
|
|
428
|
+
},
|
|
429
|
+
{ "name": "disabled", "type": "boolean", "category": "design" },
|
|
430
|
+
{ "name": "onClick", "type": "()=>void", "category": "state" },
|
|
431
|
+
{ "name": "className", "type": "string" }
|
|
432
|
+
],
|
|
433
|
+
"slots": [{ "name": "icon", "description": "Optional leading icon" }]
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Output:
|
|
438
|
+
|
|
439
|
+
```
|
|
440
|
+
Starting Button classification — 5 props, 1 slot
|
|
441
|
+
{"tool":"classify_component","description":"Primary action button with variant and state support","rationale":{"description":"Button is an atom — a single interactive control that triggers an action. It carries a label, a small set of visual variants, and a disabled flag, which is the minimal surface a marketer needs to configure a call-to-action.","props":"Kept label (content), variant (enum, design), disabled (boolean, state), and className (string, design escape hatch). Excluded onClick because it is an event handler — framework-internal and not configurable in Contentful.","slots":"Kept the icon slot as optional because the button renders correctly without it and the icon is purely decorative."}}
|
|
442
|
+
label is a required string content prop
|
|
443
|
+
{"tool":"classify_prop","prop":"label","cdf_type":"string","cdf_category":"content","required":true,"description":"Button label text"}
|
|
444
|
+
variant is a string union — enum type, category design
|
|
445
|
+
{"tool":"classify_prop","prop":"variant","cdf_type":"enum","cdf_category":"design","required":false,"values":["primary","secondary","ghost"],"default":"primary","description":"Visual variant"}
|
|
446
|
+
disabled is a boolean state prop — raw category says design, correcting to state
|
|
447
|
+
{"tool":"classify_prop","prop":"disabled","cdf_type":"boolean","cdf_category":"state","required":false,"default":false,"description":"Disables the button"}
|
|
448
|
+
onClick is an event handler — framework internal
|
|
449
|
+
{"tool":"exclude_prop","prop":"onClick","reason":"event handler — framework internal"}
|
|
450
|
+
className is a DOM pass-through — developers wire CSS, marketers never set this
|
|
451
|
+
{"tool":"exclude_prop","prop":"className","reason":"DOM pass-through — not a marketer-configurable surface"}
|
|
452
|
+
icon slot is clearly optional (decorative leading icon)
|
|
453
|
+
{"tool":"classify_slot","slot":"icon","required":false,"description":"Optional leading icon","rationale":"Icon is a decorative leading glyph — optional because the button reads cleanly without it, but kept as a slot so marketers can inject a brand-specific icon component when desired."}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### Component with multiple slots (Card)
|
|
457
|
+
|
|
458
|
+
Input:
|
|
459
|
+
|
|
460
|
+
```json
|
|
461
|
+
{
|
|
462
|
+
"name": "Card",
|
|
463
|
+
"props": [
|
|
464
|
+
{
|
|
465
|
+
"name": "title",
|
|
466
|
+
"type": "string",
|
|
467
|
+
"category": "content",
|
|
468
|
+
"required": true
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
"name": "elevation",
|
|
472
|
+
"type": "'flat'|'raised'|'floating'",
|
|
473
|
+
"category": "design",
|
|
474
|
+
"defaultValue": "'raised'"
|
|
475
|
+
}
|
|
476
|
+
],
|
|
477
|
+
"slots": [
|
|
478
|
+
{ "name": "body", "description": "Main card body" },
|
|
479
|
+
{ "name": "footer", "description": "Optional footer area" }
|
|
480
|
+
]
|
|
481
|
+
}
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
Output:
|
|
485
|
+
|
|
486
|
+
```
|
|
487
|
+
Starting Card classification — 2 props, 2 slots
|
|
488
|
+
{"tool":"classify_component","description":"Container that groups related content with a title, body, and optional footer","rationale":{"description":"Card is a molecule — it composes a title with body and footer slots into a single visual container. Useful as a building block for lists and grids of related content.","props":"Kept title (string, content) as the customer-facing label and elevation (enum, design) for the three visual depth variants. Nothing was excluded — both raw props map cleanly to CDF.","slots":"Kept both body (required, primary content area) and footer (optional, supplementary area). Body is required because a card with no body renders empty; footer is optional because many cards do not need one."}}
|
|
489
|
+
title is a required content string
|
|
490
|
+
{"tool":"classify_prop","prop":"title","cdf_type":"string","cdf_category":"content","required":true,"description":"Card title text"}
|
|
491
|
+
elevation is a finite visual variant — enum, design
|
|
492
|
+
{"tool":"classify_prop","prop":"elevation","cdf_type":"enum","cdf_category":"design","required":false,"values":["flat","raised","floating"],"default":"raised","description":"Visual depth variant"}
|
|
493
|
+
body is the primary content region — required
|
|
494
|
+
{"tool":"classify_slot","slot":"body","required":true,"description":"Main card body content","rationale":"Body is the primary content region of the card. Required because a card with no body renders an empty container, which is never a useful editor state."}
|
|
495
|
+
footer is supplementary and optional
|
|
496
|
+
{"tool":"classify_slot","slot":"footer","required":false,"description":"Optional footer area for actions or metadata","rationale":"Footer is a supplementary region typically used for actions or metadata. Optional because most cards do not need one and the card renders correctly without it."}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### Named type (HeadingSize, ButtonVariant, etc.) — do NOT infer the values
|
|
500
|
+
|
|
501
|
+
When a prop has a named TypeScript type that is not inlined as a union literal,
|
|
502
|
+
the value set is whatever that type's declaration says. A type _name_ is not
|
|
503
|
+
evidence of its members: `HeadingSize` could be `h1`–`h6`, `sm|md|lg`, or
|
|
504
|
+
`s|m|l|xl`. Guessing picks one design system's vocabulary and is wrong more
|
|
505
|
+
often than it is right, and a wrong name matches no style rule — the author's
|
|
506
|
+
setting silently does nothing.
|
|
507
|
+
|
|
508
|
+
So: look for the declaration in the source shown. If it is there, copy it. If it
|
|
509
|
+
is not, **emit `string`** — `string` is an unrestricted enum, so the author stays
|
|
510
|
+
unrestricted but correct.
|
|
511
|
+
|
|
512
|
+
Members visible in the source shown — copy them, do not paraphrase:
|
|
513
|
+
|
|
514
|
+
```
|
|
515
|
+
titleSize has type HeadingSize; HeadingSize is declared in the excerpt as 'sm' | 'md' | 'lg'
|
|
516
|
+
{"tool":"classify_prop","prop":"titleSize","cdf_type":"enum","cdf_category":"design","required":false,"values":["sm","md","lg"],"description":"Heading size","reason":"Copied from the HeadingSize declaration shown in the excerpt."}
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
Members NOT visible in the source shown — degrade to `string`:
|
|
520
|
+
|
|
521
|
+
```
|
|
522
|
+
titleSize has type HeadingSize; HeadingSize is not declared anywhere in the source shown
|
|
523
|
+
no evidence for its members, so no values list — string, not a guess
|
|
524
|
+
{"tool":"classify_prop","prop":"titleSize","cdf_type":"string","cdf_category":"design","required":false,"description":"Heading size","reason":"HeadingSize is not declared in the source shown, so its members cannot be cited. Emitted as string rather than inventing a value list."}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### href prop
|
|
528
|
+
|
|
529
|
+
```
|
|
530
|
+
href is a URL string — cdf_type string (not link), category content
|
|
531
|
+
{"tool":"classify_prop","prop":"href","cdf_type":"string","cdf_category":"content","required":false,"description":"Navigation URL"}
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
---
|
|
248
535
|
|
|
249
536
|
## Edge cases
|
|
250
537
|
|
|
251
|
-
- **
|
|
538
|
+
- **Prop with unresolvable type** (generics, intersection, callback) → `exclude_prop` with reason `"complex type — not representable in CDF"`.
|
|
252
539
|
- **Component with zero classified props after exclusions** → still emit `classify_component`. The DB entry will have an empty `$properties` object.
|
|
253
|
-
- **
|
|
254
|
-
- **
|
|
540
|
+
- **Slot not in DB** → skipped with a warning; does not abort the run.
|
|
541
|
+
- **Prop not in DB** → skipped with a warning; does not abort the run.
|
|
255
542
|
|
|
256
543
|
## Validation step — Pre-emit checklist
|
|
257
544
|
|
|
258
545
|
Before emitting any tool calls, verify:
|
|
259
546
|
|
|
260
|
-
1. Every prop in the input has exactly one `classify_prop` or `exclude_prop` call
|
|
261
|
-
2. Every slot has exactly one `classify_slot` call
|
|
262
|
-
3. `classify_component` is emitted exactly once
|
|
263
|
-
4. Every
|
|
264
|
-
5. Every token
|
|
265
|
-
6. `
|
|
266
|
-
7.
|
|
267
|
-
8.
|
|
268
|
-
9.
|
|
269
|
-
10.
|
|
270
|
-
11.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
547
|
+
1. Every prop in the input has exactly one `classify_prop` or `exclude_prop` call
|
|
548
|
+
2. Every slot has exactly one `classify_slot` call
|
|
549
|
+
3. `classify_component` is emitted exactly once
|
|
550
|
+
4. Every `cdf_type: "enum"` has a non-empty `values` array taken from the material shown. A prop whose value set was shown nowhere is `cdf_type: "string"` with no `values` — never an `enum` with a plausible-looking list, and never an `exclude_prop`.
|
|
551
|
+
5. Every `cdf_type: "token"` has `token_kind` (or a warning in `description` if lookup failed) and does **not** have `values` set
|
|
552
|
+
6. No `cdf_type: "link"` — all href/url props use `string`
|
|
553
|
+
7. `required` values are JSON booleans, not strings
|
|
554
|
+
8. Framework, DOM, accessibility, and data-_ pass-through props are excluded per the inheritance principle (rule 1) — any prop inherited from an HTML-attributes base interface and not redeclared by the component, plus `aria-_`(and bare`aria`), `data-\*`, polymorphic `as`/`element`/`component`, framework theming `dt`/`pt`/`ptOptions`/`unstyled`/`sx`. A prop matching an inherited-attribute name (including `title`) that the component redeclares with its own type or meaning is NOT excluded. Discrete positional/geometric props (`top`, `bottom`, `left`, `right`, `rotation`, etc.) ARE classified as `string` design props. Common semantic props (`icon`, `items`, `actions`, `options`, `value`, `name`, `form`, `inputId`, `componentId`) are NOT excluded — classify them per their content/design/state nature.
|
|
555
|
+
9. No `cdf_type: "link"` used — `link` is reserved and rejected by the CLI parser
|
|
556
|
+
10. No `cdf_type: "number"` used — this is not a supported type; use `"string"` with numeric defaults. `cdf_type: "boolean"` IS valid — use it for boolean toggle props.
|
|
557
|
+
11. `classify_component` includes a `rationale` object with all three sub-fields (`rationale.description`, `rationale.props`, `rationale.slots`) populated as non-empty strings.
|
|
558
|
+
12. Every `classify_slot` includes a non-empty `rationale` string.
|
|
559
|
+
13. `rationale.description` follows the same "Description content rules" as `description` — no internal initiative names (`INTEG-*`, `EDSI`, `DSI`, `M1`, `M2`, wave/phase references, etc.).
|
|
560
|
+
14. Every `cdf_type: "token"` prop cites a line that interpolates its value into a style **and** a token reference at that use (Q2 yes, Q3 yes), and carries no `values`. If you cannot cite both, it is `enum` or `string`; if you can, it is `token` — its type annotation is not a reason to emit `string`.
|
|
561
|
+
15. Every prop whose values are friendly names is `enum` with a non-empty `values` array, even when each name resolves to a design token internally; every prop for which Q1–Q3 could not be answered was emitted as `enum` (or `string`) with the ambiguity stated in `reason`, and no answerable prop was called ambiguous.
|
|
562
|
+
16. Every `enum`'s `values` array is copied from evidence in the material shown — a type declaration, an `allowedValues` list on the raw prop, a variant or lookup map, a default, in the component's own file or in any sibling excerpt — and none of it was invented. Where the value set appears nowhere, the prop is `cdf_type: "string"`, with the reason recorded in `reason`. Do not drop a prop merely because you could not enumerate its values.
|
|
563
|
+
|
|
564
|
+
After the run completes, the developer can validate the pipeline output with:
|
|
565
|
+
|
|
566
|
+
```
|
|
567
|
+
experience-design-system-cli print validate --components <out-path>
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
Re-run or re-iterate on any components flagged by warnings until the output passes validation.
|
|
571
|
+
|
|
572
|
+
---
|
|
573
|
+
|
|
574
|
+
## CRITICAL: Zero-output is a failure
|
|
575
|
+
|
|
576
|
+
You MUST produce at least one classify_prop call for this component. A response with zero
|
|
577
|
+
classify_prop/exclude_prop calls means the component will be pushed with no configurable
|
|
578
|
+
properties — this is never acceptable.
|
|
579
|
+
|
|
580
|
+
If you are genuinely uncertain about every prop, classify each as:
|
|
581
|
+
{"tool":"classify_prop","prop":"<name>","cdf_type":"string","cdf_category":"content","required":false,"description":"Uncertain classification — review recommended"}
|
|
582
|
+
|
|
583
|
+
An imperfect classification is infinitely better than no classification. That is
|
|
584
|
+
about **classifying a prop rather than dropping it** — it is not a licence to
|
|
585
|
+
invent a `values` list. The uncertain answer is `cdf_type: "string"`, every time.
|
|
296
586
|
|
|
297
587
|
---
|
|
298
588
|
|
|
299
589
|
## Existing entities in the target space
|
|
300
590
|
|
|
301
|
-
The preamble may include an "Existing components in the target Contentful space" JSON block with a `likelyMatch` (a fuzzy-matched space component that
|
|
591
|
+
The preamble may include an "Existing components in the target Contentful space" JSON block with a `likelyMatch` (a fuzzy-matched space component that likely corresponds to the codebase component you're classifying), an `otherComponents` list, and a rolled-up token summary. Use it to align your classifications with what the customer already has.
|
|
302
592
|
|
|
303
|
-
### When `likelyMatch` is present —
|
|
593
|
+
### When `likelyMatch` is present — align rename intent + categories + slot names
|
|
304
594
|
|
|
305
|
-
|
|
595
|
+
Your goal is **consistency**: the customer will see the diff between what you propose and what already exists, and every unnecessary category flip or slot-shape mismatch becomes review friction.
|
|
306
596
|
|
|
307
597
|
1. **Prop names**: `classify_prop.prop` MUST always be the codebase prop name — that's the DB lookup key. When a codebase prop looks like it corresponds to an existing space prop (e.g. codebase `kind` ↔ space `variant`, codebase `text` ↔ space `label`), keep `prop` as the codebase name and put the alignment note in `reason`, e.g. `"looks like existing space prop 'variant' — customer may want to reconcile at review"`. Never rewrite `prop` itself; reconciliation happens downstream, not in this tool call.
|
|
308
|
-
2. **Categories**:
|
|
309
|
-
3. **Slot names**:
|
|
310
|
-
4. **Enum values**:
|
|
598
|
+
2. **Categories**: match the space's category (`design` / `content`) for equivalent props, even if you'd normally categorize differently. Note the alignment in `reason`.
|
|
599
|
+
3. **Slot names**: match slot IDs / names when the shape is equivalent.
|
|
600
|
+
4. **Enum values**: never invent new values to bridge a mismatch. If the codebase's enum diverges from the space's, classify against your best-effort mapping and cite the divergence in `reason` — this is a signal for human review, not a green light to rewrite the space's enum.
|
|
311
601
|
5. **Never realign silently.** Every classification influenced by the `likelyMatch` MUST cite it in `reason`.
|
|
312
602
|
|
|
313
603
|
### When only `otherComponents` is present (no direct match)
|
|
@@ -320,22 +610,3 @@ The preamble's "Existing design tokens" section lists tokens (by name + DTCG typ
|
|
|
320
610
|
|
|
321
611
|
1. When classifying a `design`-category, `token`-typed prop, prefer binding to an existing token name over inventing a new one. Cite the target token in `reason`.
|
|
322
612
|
2. If no existing token semantically matches, proceed with the current rules — propose a new token. The diff surfaces this for review.
|
|
323
|
-
|
|
324
|
-
## Richer content representations
|
|
325
|
-
|
|
326
|
-
The desired CDF vocabulary additionally includes `Integer`, `DateTime`, `Literal`, `RichText`, `JSON` and `ResourceLink`; these are content representations. Local file validity does not establish deployed mapper support.
|
|
327
|
-
|
|
328
|
-
- `Integer` requires a complete, compiler-established integral source domain; names, a numeric default and observed callers do not prove it.
|
|
329
|
-
- `DateTime` requires a complete source domain of timezone-qualified ISO strings; preserve their spelling and do not convert Date objects or infer semantics from names.
|
|
330
|
-
- `Literal` requires an exact source singleton, `value` and recursive CDF `valueType` in the same call; a default is separate from its intrinsic value and must equal it. Singleton proof does not establish RichText or ResourceLink semantics for an underlying type.
|
|
331
|
-
- `RichText`, `JSON` and `ResourceLink` require `representation_proposal` in the same classification call. Copy the host-supplied `componentId`, `propertyId`, `key`, `sourceRevision` and source-node identities; use `name` equal to the exact source property. Supply independent `declaredConstraints`, `provenConstraints`, `observations`, `judgment`, `desiredType`, complete target `desiredDefinition`, and `compatibility` with `status`, `sourceUseRefs` and `unresolvedRequirements`. Readiness is host-owned and must not be included in the draft.
|
|
332
|
-
- Evidence channels are arrays with exact item shapes: `declaredConstraints` and `provenConstraints` use `[{"nodeId":"<current source node>","statement":"<source-grounded statement>"}]`; `observations` uses `[{"nodeId":"<current source node>","value":"<observed JSON value>"}]`. A JSON value may also be a number, boolean, null, array or object. Use `[]` when no evidence is available, rather than objects or unbound statement strings. Keep the prose judgment separate.
|
|
333
|
-
- `desiredType` must exactly equal the enclosing `cdf_type`, and `desiredDefinition.type` must equal `desiredType`. `desiredDefinition` must carry the same complete target definition, including the description, requiredness and any other selected definition metadata. Do not attach a proposal for a different alternative to a native record or other selected type; choose and validate one coherent representation.
|
|
334
|
-
- A judged-compatible proposal must cite current positive output-flow references. A primitive string is not a RichText document; lowercase `richtext` retains its existing markup projection. JSON requires proven JSON-compatible semantics and an explicitly justified opaque contract, rather than a fallback for unknown or meaningful editable structure.
|
|
335
|
-
- ResourceLink restrictions use `Contentful:Entry` with `allowedTypes`, or `Contentful:Asset`, and the exact same-space source `crn:contentful:::content:spaces/$self/environments/$self`. Keep resource and delivery prerequisites explicit; neither a URL nor a matching name proves a resource identity.
|
|
336
|
-
- RichText and JSON cannot have target defaults; source defaults remain evidence. ResourceLink defaults require a verified delivery contract and remain held. Retain unresolved proposals and source facts instead of inventing proof.
|
|
337
|
-
- When no representation is established, omit `value`, `valueType`, `allowedResources` and `representation_proposal` as well as all other representation companions.
|
|
338
|
-
|
|
339
|
-
## Discriminated union content representations
|
|
340
|
-
|
|
341
|
-
`DiscriminatedUnion` is a desired content representation. A `classify_prop` call must carry `discriminator` and every complete recursive CDF `members` definition together. Members are `record` definitions with a required intrinsic `Literal` field at that discriminator key, using typed `value` and recursive `valueType`. Keep every compiler branch, each branch's fields and requiredness, and complete finite domains; do not flatten branches, infer tags from names, coerce tags, or use observed callers as an exhaustive domain. Defaults must match one exact branch. Treat the complete exploration `definition` as one advisory judgment to validate against current compiler evidence. Native positions, target tag lookup/path constraints, mapper support, exclusive branch cardinality and reconstruction remain separate requirements; preserve source-bound model alternatives when native representation cannot preserve them. A final retained property must select a native `cdf_type` or a source-bound component-model candidate; a DiscriminatedUnion decision requires its complete discriminator and members before generation can finish.
|