@adobe/design-data-spec 4.8.1 → 4.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/conformance/README.md +32 -9
  2. package/conformance/invalid/SPEC-051/dataset.json +9 -3
  3. package/conformance/invalid/SPEC-052/dataset.json +14 -4
  4. package/conformance/invalid/SPEC-053/dataset.json +16 -4
  5. package/conformance/invalid/SPEC-054/dataset.json +18 -5
  6. package/conformance/invalid/SPEC-055/dataset.json +9 -3
  7. package/conformance/invalid/SPEC-057/dataset.json +5 -2
  8. package/conformance/invalid/SPEC-058/dataset.json +8 -3
  9. package/conformance/invalid/SPEC-059/dataset.json +28 -0
  10. package/conformance/invalid/SPEC-059/expected-errors.json +10 -0
  11. package/conformance/invalid/SPEC-060/dataset.json +44 -0
  12. package/conformance/invalid/SPEC-060/expected-errors.json +10 -0
  13. package/conformance/invalid/SPEC-061/dataset.json +63 -0
  14. package/conformance/invalid/SPEC-061/expected-errors.json +15 -0
  15. package/conformance/invalid/SPEC-062/dataset.json +50 -0
  16. package/conformance/invalid/SPEC-062/expected-errors.json +10 -0
  17. package/conformance/invalid/SPEC-063/dataset.json +57 -0
  18. package/conformance/invalid/SPEC-063/expected-errors.json +10 -0
  19. package/conformance/invalid/SPEC-064/dataset.json +53 -0
  20. package/conformance/invalid/SPEC-064/expected-errors.json +15 -0
  21. package/conformance/invalid/SPEC-065/dataset.json +78 -0
  22. package/conformance/invalid/SPEC-065/expected-errors.json +10 -0
  23. package/conformance/invalid/SPEC-066/dataset.json +37 -0
  24. package/conformance/invalid/SPEC-066/expected-errors.json +10 -0
  25. package/conformance/invalid/SPEC-067/dataset.json +60 -0
  26. package/conformance/invalid/SPEC-067/expected-errors.json +10 -0
  27. package/conformance/invalid/SPEC-068/dataset.json +37 -0
  28. package/conformance/invalid/SPEC-068/expected-errors.json +10 -0
  29. package/conformance/invalid/SPEC-069/dataset.json +63 -0
  30. package/conformance/invalid/SPEC-069/expected-errors.json +10 -0
  31. package/conformance/invalid/SPEC-070/dataset.json +71 -0
  32. package/conformance/invalid/SPEC-070/expected-errors.json +15 -0
  33. package/conformance/invalid/SPEC-071/dataset.json +77 -0
  34. package/conformance/invalid/SPEC-071/expected-errors.json +10 -0
  35. package/conformance/invalid/SPEC-072/dataset.json +4 -2
  36. package/conformance/invalid/SPEC-073/dataset.json +59 -0
  37. package/conformance/invalid/SPEC-073/expected-errors.json +10 -0
  38. package/conformance/invalid/SPEC-074/dataset.json +36 -0
  39. package/conformance/invalid/SPEC-074/expected-errors.json +10 -0
  40. package/conformance/invalid/SPEC-075/dataset.json +53 -0
  41. package/conformance/invalid/SPEC-075/expected-errors.json +10 -0
  42. package/conformance/invalid/SPEC-076/dataset.json +62 -0
  43. package/conformance/invalid/SPEC-076/expected-errors.json +10 -0
  44. package/conformance/invalid/SPEC-076/manifest.json +6 -0
  45. package/conformance/invalid/SPEC-079/dataset.json +62 -0
  46. package/conformance/invalid/SPEC-079/expected-errors.json +10 -0
  47. package/conformance/invalid/SPEC-079/manifest.json +58 -0
  48. package/conformance/invalid/SPEC-080/dataset.json +62 -0
  49. package/conformance/invalid/SPEC-080/expected-errors.json +10 -0
  50. package/conformance/invalid/SPEC-080/manifest.json +32 -0
  51. package/conformance/invalid/SPEC-081/dataset.json +62 -0
  52. package/conformance/invalid/SPEC-081/expected-errors.json +10 -0
  53. package/conformance/invalid/SPEC-081/manifest.json +7 -0
  54. package/conformance/invalid/SPEC-082/dataset.json +62 -0
  55. package/conformance/invalid/SPEC-082/expected-errors.json +10 -0
  56. package/conformance/invalid/SPEC-082/manifest.json +14 -0
  57. package/conformance/invalid/SPEC-083/dataset.json +47 -0
  58. package/conformance/invalid/SPEC-083/expected-errors.json +10 -0
  59. package/conformance/invalid/SPEC-084/dataset.json +16 -0
  60. package/conformance/invalid/SPEC-084/expected-errors.json +10 -0
  61. package/conformance/valid/SPEC-025/dataset.json +1 -1
  62. package/conformance/valid/SPEC-049/dataset.json +1 -1
  63. package/conformance/valid/SPEC-051/dataset.json +40 -9
  64. package/conformance/valid/SPEC-064/dataset.json +64 -0
  65. package/conformance/valid/SPEC-064/expected-errors.json +4 -0
  66. package/conformance/valid/SPEC-065/dataset.json +89 -0
  67. package/conformance/valid/SPEC-065/expected-errors.json +4 -0
  68. package/conformance/valid/SPEC-079/dataset.json +62 -0
  69. package/conformance/valid/SPEC-079/manifest.json +58 -0
  70. package/package.json +1 -1
  71. package/rules/rules.yaml +358 -0
  72. package/schemas/anatomy-part.schema.json +11 -0
  73. package/schemas/component-changelog.schema.json +306 -0
  74. package/schemas/component-spec-sheet.schema.json +215 -0
  75. package/schemas/component-visual.schema.json +287 -0
  76. package/schemas/component.schema.json +80 -0
  77. package/schemas/manifest.schema.json +5 -0
  78. package/schemas/relationship.schema.json +8 -6
  79. package/spec/anatomy-format.md +97 -10
  80. package/spec/artifact-moves.md +157 -0
  81. package/spec/component-changelog.md +49 -0
  82. package/spec/component-format.md +4 -0
  83. package/spec/component-visuals.md +210 -0
  84. package/spec/index.md +5 -0
  85. package/spec/manifest.md +37 -21
  86. package/spec/option-constraints.md +87 -0
  87. package/spec/relationship-format.md +14 -11
@@ -0,0 +1,287 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://opensource.adobe.com/spectrum-design-data/schemas/v0/component-visual.schema.json",
4
+ "title": "Component visual recipe",
5
+ "description": "Layer 1 structural schema for a renderer-neutral component visual recipe (spec/component-visuals.md). Semantic rules (component exists, parts are declared anatomy, relationship selectors resolve) are Layer 2.",
6
+ "type": "object",
7
+ "required": ["component", "root", "parts"],
8
+ "properties": {
9
+ "$schema": {
10
+ "type": "string"
11
+ },
12
+ "$id": {
13
+ "type": "string"
14
+ },
15
+ "specVersion": {
16
+ "type": "string",
17
+ "const": "1.0.0-draft"
18
+ },
19
+ "component": {
20
+ "type": "string",
21
+ "pattern": "^[a-z][a-z0-9-]*$",
22
+ "description": "Name of the component declaration this recipe draws."
23
+ },
24
+ "root": {
25
+ "type": "string",
26
+ "pattern": "^[a-z][a-z0-9-]*$",
27
+ "description": "Key of the root part in `parts`."
28
+ },
29
+ "notVisual": {
30
+ "type": "array",
31
+ "items": { "type": "string", "pattern": "^[a-zA-Z][a-zA-Z0-9-]*$" },
32
+ "uniqueItems": true,
33
+ "description": "Options and states the component declares that deliberately have no visual in this recipe (SPEC-066). Each name MUST be a declared option or state of the component."
34
+ },
35
+ "sizeScale": {
36
+ "type": "object",
37
+ "description": "Maps each size option value to a numeric scale index, substituted for {sizeIndex} in selectors. Selectors may also use {size} and {<option>} for any declared option.",
38
+ "additionalProperties": {
39
+ "type": "integer",
40
+ "minimum": 0
41
+ }
42
+ },
43
+ "parts": {
44
+ "type": "object",
45
+ "minProperties": 1,
46
+ "propertyNames": {
47
+ "pattern": "^[a-z][a-z0-9-]*$"
48
+ },
49
+ "additionalProperties": {
50
+ "$ref": "#/$defs/part"
51
+ }
52
+ }
53
+ },
54
+ "additionalProperties": false,
55
+ "$defs": {
56
+ "part": {
57
+ "type": "object",
58
+ "required": ["kind"],
59
+ "properties": {
60
+ "kind": {
61
+ "enum": ["box", "text", "icon", "ring", "progress"]
62
+ },
63
+ "children": {
64
+ "type": "array",
65
+ "items": {
66
+ "type": "string",
67
+ "pattern": "^[a-z][a-z0-9-]*$"
68
+ },
69
+ "uniqueItems": true
70
+ },
71
+ "visibleWhen": {
72
+ "$ref": "#/$defs/condition"
73
+ },
74
+ "around": {
75
+ "type": "string",
76
+ "pattern": "^[a-z][a-z0-9-]*$",
77
+ "description": "For a ring: the part it outlines. Defaults to the root."
78
+ },
79
+ "glyph": {
80
+ "enum": ["checkmark", "dash", "chevron"],
81
+ "description": "For an icon nested in a box: the built-in glyph to draw. Unset draws a placeholder."
82
+ },
83
+ "pressTarget": {
84
+ "type": "string",
85
+ "pattern": "^[a-z][a-z0-9-]*$",
86
+ "description": "For the root: the part the press scale applies to, together with the parts nested inside it. Defaults to the whole scene."
87
+ },
88
+ "placement": {
89
+ "type": "object",
90
+ "required": ["anchor"],
91
+ "properties": {
92
+ "anchor": {
93
+ "enum": [
94
+ "top-start",
95
+ "top-end",
96
+ "bottom-start",
97
+ "bottom-end",
98
+ "center"
99
+ ]
100
+ },
101
+ "inset": {
102
+ "$ref": "#/$defs/valueSource"
103
+ }
104
+ },
105
+ "additionalProperties": false
106
+ },
107
+ "props": {
108
+ "type": "object",
109
+ "propertyNames": {
110
+ "enum": [
111
+ "direction",
112
+ "align",
113
+ "justify",
114
+ "gap",
115
+ "paddingInlineStart",
116
+ "paddingInlineEnd",
117
+ "paddingBlockStart",
118
+ "paddingBlockEnd",
119
+ "height",
120
+ "minWidth",
121
+ "width",
122
+ "cornerRadius",
123
+ "cornerRadiusStartStart",
124
+ "cornerRadiusStartEnd",
125
+ "cornerRadiusEndStart",
126
+ "cornerRadiusEndEnd",
127
+ "fill",
128
+ "stroke",
129
+ "strokeWidth",
130
+ "opacity",
131
+ "color",
132
+ "trackColor",
133
+ "fontFamily",
134
+ "fontSize",
135
+ "fontWeight",
136
+ "lineHeight",
137
+ "gapToParent",
138
+ "rotation",
139
+ "pressDifference",
140
+ "pressWidthRatio",
141
+ "pressMinPerspective",
142
+ "pressMaxPerspective"
143
+ ]
144
+ },
145
+ "additionalProperties": {
146
+ "oneOf": [
147
+ {
148
+ "$ref": "#/$defs/conditionalValueSource"
149
+ },
150
+ {
151
+ "type": "array",
152
+ "minItems": 1,
153
+ "items": {
154
+ "$ref": "#/$defs/conditionalValueSource"
155
+ }
156
+ }
157
+ ]
158
+ }
159
+ }
160
+ },
161
+ "additionalProperties": false
162
+ },
163
+ "condition": {
164
+ "type": "object",
165
+ "properties": {
166
+ "options": {
167
+ "type": "object",
168
+ "additionalProperties": {
169
+ "oneOf": [
170
+ {
171
+ "type": ["string", "boolean", "number"]
172
+ },
173
+ {
174
+ "type": "array",
175
+ "minItems": 1,
176
+ "items": {
177
+ "type": ["string", "boolean", "number"]
178
+ }
179
+ }
180
+ ]
181
+ }
182
+ },
183
+ "states": {
184
+ "type": "array",
185
+ "items": {
186
+ "type": "string",
187
+ "pattern": "^[a-z][a-z0-9-]*$"
188
+ },
189
+ "uniqueItems": true
190
+ },
191
+ "modes": {
192
+ "type": "object",
193
+ "additionalProperties": {
194
+ "type": "string"
195
+ }
196
+ }
197
+ },
198
+ "additionalProperties": false
199
+ },
200
+ "valueSource": {
201
+ "type": "object",
202
+ "oneOf": [
203
+ {
204
+ "required": ["value"],
205
+ "properties": {
206
+ "value": {}
207
+ },
208
+ "additionalProperties": false
209
+ },
210
+ {
211
+ "required": ["token"],
212
+ "properties": {
213
+ "token": {
214
+ "$ref": "#/$defs/tokenSelector"
215
+ }
216
+ },
217
+ "additionalProperties": false
218
+ },
219
+ {
220
+ "required": ["relationship"],
221
+ "properties": {
222
+ "relationship": {
223
+ "$ref": "#/$defs/relationshipSelector"
224
+ }
225
+ },
226
+ "additionalProperties": false
227
+ }
228
+ ]
229
+ },
230
+ "conditionalValueSource": {
231
+ "type": "object",
232
+ "properties": {
233
+ "when": {
234
+ "$ref": "#/$defs/condition"
235
+ },
236
+ "priority": {
237
+ "type": "integer"
238
+ },
239
+ "relativeTo": {
240
+ "enum": ["height"]
241
+ },
242
+ "value": {},
243
+ "token": {
244
+ "$ref": "#/$defs/tokenSelector"
245
+ },
246
+ "relationship": {
247
+ "$ref": "#/$defs/relationshipSelector"
248
+ }
249
+ },
250
+ "additionalProperties": false,
251
+ "oneOf": [
252
+ {
253
+ "required": ["value"]
254
+ },
255
+ {
256
+ "required": ["token"]
257
+ },
258
+ {
259
+ "required": ["relationship"]
260
+ }
261
+ ],
262
+ "description": "A value source with optional `when`, `priority` and `relativeTo`. Used alone or in an ordered list."
263
+ },
264
+ "tokenSelector": {
265
+ "type": "object",
266
+ "minProperties": 1,
267
+ "additionalProperties": {
268
+ "type": ["string", "number", "boolean"]
269
+ }
270
+ },
271
+ "relationshipSelector": {
272
+ "type": "object",
273
+ "required": ["property"],
274
+ "properties": {
275
+ "property": {
276
+ "type": "string",
277
+ "minLength": 1
278
+ },
279
+ "context": {
280
+ "type": "string",
281
+ "minLength": 1
282
+ }
283
+ },
284
+ "additionalProperties": false
285
+ }
286
+ }
287
+ }
@@ -47,6 +47,13 @@
47
47
  "options": {
48
48
  "$ref": "#/$defs/optionsMap"
49
49
  },
50
+ "constraints": {
51
+ "type": "array",
52
+ "items": {
53
+ "$ref": "#/$defs/optionConstraint"
54
+ },
55
+ "description": "Rules restricting which option value combinations are valid. See spec/option-constraints.md."
56
+ },
50
57
  "slots": {
51
58
  "type": "array",
52
59
  "items": {
@@ -61,6 +68,20 @@
61
68
  },
62
69
  "description": "Named visible parts of the component. Full spec in spec/anatomy-format.md (Phase 6.2)."
63
70
  },
71
+ "anatomyReview": {
72
+ "type": "object",
73
+ "required": ["status"],
74
+ "properties": {
75
+ "status": { "enum": ["draft", "reviewed"] },
76
+ "reviewedIn": {
77
+ "type": "string",
78
+ "description": "Version in which the anatomy was last reviewed against the anatomy definition."
79
+ },
80
+ "notes": { "type": "string" }
81
+ },
82
+ "additionalProperties": false,
83
+ "description": "Review status of this component's anatomy. Absent means draft. See spec/anatomy-format.md#review-checklist."
84
+ },
64
85
  "states": {
65
86
  "type": "array",
66
87
  "items": {
@@ -157,6 +178,54 @@
157
178
  "$ref": "#/$defs/optionDescriptor"
158
179
  }
159
180
  },
181
+ "optionConstraint": {
182
+ "type": "object",
183
+ "description": "If-then rule over option values. Layer 1 checks shape only; that names and values are declared in `options` is Layer 2.",
184
+ "required": ["when", "require", "severity"],
185
+ "properties": {
186
+ "when": {
187
+ "type": "object",
188
+ "minProperties": 1,
189
+ "additionalProperties": {
190
+ "oneOf": [
191
+ { "type": ["string", "number", "boolean"] },
192
+ {
193
+ "type": "array",
194
+ "minItems": 1,
195
+ "items": { "type": ["string", "number", "boolean"] }
196
+ }
197
+ ]
198
+ },
199
+ "description": "Option conditions (ANDed). A literal matches that value; an array matches any of its values."
200
+ },
201
+ "require": {
202
+ "type": "object",
203
+ "minProperties": 1,
204
+ "additionalProperties": {
205
+ "oneOf": [
206
+ {
207
+ "type": "array",
208
+ "minItems": 1,
209
+ "items": { "type": ["string", "number", "boolean"] }
210
+ },
211
+ {
212
+ "type": "object",
213
+ "required": ["present"],
214
+ "properties": { "present": { "type": "boolean" } },
215
+ "additionalProperties": false
216
+ }
217
+ ]
218
+ },
219
+ "description": "What matching combinations must satisfy: permitted values, or { present } for presence."
220
+ },
221
+ "severity": {
222
+ "enum": ["error", "advisory"]
223
+ },
224
+ "description": { "type": "string" },
225
+ "source": { "type": "string" }
226
+ },
227
+ "additionalProperties": false
228
+ },
160
229
  "optionDescriptor": {
161
230
  "type": "object",
162
231
  "description": "Descriptor for a single component option. The `type` keyword follows JSON Schema conventions. Use `values` (not `enum`) to declare permitted values — the structured array allows per-value lifecycle metadata. Other JSON Schema keywords (default, description, pattern, minimum, maximum, items, properties, $ref) are permitted as needed.",
@@ -273,6 +342,12 @@
273
342
  "type": "boolean",
274
343
  "default": false
275
344
  },
345
+ "presentIn": {
346
+ "type": "array",
347
+ "items": { "type": "string" },
348
+ "uniqueItems": true,
349
+ "description": "Declared component states in which this part is present; drawn when any is active. Absent means any state. Not for state-dependent styling."
350
+ },
276
351
  "contains": {
277
352
  "type": "array",
278
353
  "items": { "type": "string" },
@@ -288,6 +363,11 @@
288
363
  "description": "Version lifecycle metadata for this anatomy part. Set lifecycle.deprecatedIn to signal that tokens referencing this part should migrate."
289
364
  }
290
365
  },
366
+ "if": {
367
+ "required": ["required"],
368
+ "properties": { "required": { "const": true } }
369
+ },
370
+ "then": { "not": { "required": ["presentIn"] } },
291
371
  "additionalProperties": false
292
372
  },
293
373
  "stateDeclaration": {
@@ -103,6 +103,11 @@
103
103
  "minLength": 1,
104
104
  "description": "Path (relative to the manifest) to the platform's extensions directory, holding platform-local tokens/components/fields/relationships/guidelines/platform-extensions as one file per artifact (see spec/manifest.md#extensions-directory). Default: \"extensions\"."
105
105
  },
106
+ "componentAliases": {
107
+ "type": "object",
108
+ "description": "Maps a foundation component name to a component the platform declares in extensions/components/. Applies to render, site, WASM render and recipe lookup only (see spec/manifest.md#componentaliases; SPEC-081).",
109
+ "additionalProperties": { "type": "string", "minLength": 1 }
110
+ },
106
111
  "namingExceptions": {
107
112
  "type": "object",
108
113
  "description": "Platform-local overlay on the base naming-exceptions set: names to add and/or remove for this platform's naming validation.",
@@ -64,7 +64,7 @@
64
64
  },
65
65
  "options": {
66
66
  "type": "object",
67
- "description": "Option-scoped narrowing (e.g. variant, size, style, arbitrary component option keys). Each key's value MUST validate against the referenced component's options.<key>.values (SPEC-053), except `state`, which is an ordered array of atomic state ids validated against the component's declared states (SPEC-054).",
67
+ "description": "Option-scoped narrowing (e.g. variant, size, style, arbitrary component option keys). Each string value MUST validate against the referenced component's options.<key>.values (SPEC-053); a boolean option (for example isSelected) takes `true` or `false`; an array matches when any listed value matches. `state` is the exception: an ordered array of atomic state ids validated against the component's declared states (SPEC-054).",
68
68
  "properties": {
69
69
  "state": {
70
70
  "type": "array",
@@ -77,7 +77,9 @@
77
77
  }
78
78
  },
79
79
  "additionalProperties": {
80
- "type": "string"
80
+ "type": ["string", "boolean", "array"],
81
+ "items": { "type": ["string", "boolean"] },
82
+ "minItems": 1
81
83
  }
82
84
  }
83
85
  },
@@ -85,7 +87,7 @@
85
87
  },
86
88
  "ctrWithValue": {
87
89
  "type": "object",
88
- "required": ["scope", "value"],
90
+ "required": ["scope", "value", "uuid"],
89
91
  "not": {
90
92
  "required": ["$ref"]
91
93
  },
@@ -123,7 +125,7 @@
123
125
  "uuid": {
124
126
  "type": "string",
125
127
  "format": "uuid",
126
- "description": "MUST be globally unique across all relationship entries in the same layer (SPEC-056)."
128
+ "description": "Required stable identity (SPEC-084). MUST be globally unique across all relationship entries in the same layer (SPEC-056) and never change once assigned."
127
129
  },
128
130
  "lifecycle": {
129
131
  "$ref": "#/$defs/lifecycle"
@@ -155,7 +157,7 @@
155
157
  },
156
158
  "ctrWithRef": {
157
159
  "type": "object",
158
- "required": ["scope", "$ref"],
160
+ "required": ["scope", "$ref", "uuid"],
159
161
  "not": {
160
162
  "required": ["value"]
161
163
  },
@@ -187,7 +189,7 @@
187
189
  "uuid": {
188
190
  "type": "string",
189
191
  "format": "uuid",
190
- "description": "MUST be globally unique across all relationship entries in the same layer (SPEC-056)."
192
+ "description": "Required stable identity (SPEC-084). MUST be globally unique across all relationship entries in the same layer (SPEC-056) and never change once assigned."
191
193
  },
192
194
  "lifecycle": {
193
195
  "$ref": "#/$defs/lifecycle"
@@ -19,6 +19,7 @@ An anatomy part is a **JSON object** that appears as an element of a component d
19
19
  | `name` | string | REQUIRED | Kebab-case identifier. **MUST** match the pattern `^[a-z][a-z0-9-]*$`. |
20
20
  | `description` | string | OPTIONAL | Plain-text description of the part's visual role and boundaries. |
21
21
  | `required` | boolean | OPTIONAL | Whether this part is always rendered regardless of configuration. Default: `false`. |
22
+ | `presentIn` | array of strings | OPTIONAL | Declared component states in which the part is present. Absent means any state — see [`presentIn`](#presentin). |
22
23
  | `contains` | array of strings | OPTIONAL | Informative list of child anatomy part names nested within this part (e.g. a `field` contains `["label", "help-text"]`). |
23
24
  | `lifecycle` | object | OPTIONAL | Version lifecycle metadata for this anatomy part — see [lifecycle](#lifecycle). |
24
25
 
@@ -78,6 +79,89 @@ Each string in `contains` **MUST** match the pattern `^[a-z][a-z0-9-]*$`. Refere
78
79
  ]
79
80
  ```
80
81
 
82
+ ## What is an anatomy part
83
+
84
+ Anatomy describes a component in terms a designer on any platform would recognize. It is not a description of how one renderer builds the component.
85
+
86
+ **NORMATIVE:** A part **MUST** be both:
87
+
88
+ 1. **Visible**: it draws something (a fill, border, mark, glyph or text) in at least one state or configuration the component declares.
89
+ 2. **Styled on its own**: at least one visual property belongs to it alone, whether a token, a relationship context, or a fixed design decision. A visible piece with nothing of its own to style belongs to its parent.
90
+
91
+ A token is not required. A part with only fixed decisions (a corner triangle, say) still counts.
92
+
93
+ ### State-only parts
94
+
95
+ A part that appears only in some interaction states (the focus indicator) belongs in the default anatomy, because it is part of the component's design. Mark it with `presentIn`. A part driven by an option instead (a checkmark while selected, a progress circle while pending) is simply optional; the visual recipe says when it shows with `visibleWhen.options`.
96
+
97
+ #### `presentIn`
98
+
99
+ **OPTIONAL.** A list of declared component state names in which the part is present. The part is drawn when **any** listed state is active, and not otherwise. Absent means the part can appear in any state. Each entry **MUST** match a `states[].name` declared on the same component (rule SPEC-074), and for a part that also appears in a visual recipe, the recipe's `visibleWhen.states` **MUST** agree (rule SPEC-075). Conditions on options stay in the visual recipe; `presentIn` is only for state names.
100
+
101
+ `presentIn` says whether a part is present, not how it is styled. A color, border or size that changes with a state (a hover fill, a down scale) is not presence: it belongs in the relationships and the recipe, and **MUST NOT** be recorded on the part.
102
+
103
+ **NORMATIVE:** A part with `presentIn` **MUST NOT** also be `required: true`, because a required part is present in every state. Layer 1 rejects the combination.
104
+
105
+ State names follow the state registry. Use `focus` for the focus state.
106
+
107
+ ### Naming
108
+
109
+ **NORMATIVE:** Part names **MUST** be general and platform-neutral:
110
+
111
+ * Use a registry term (`anatomy-terms.json`) when one fits. Otherwise use a new name with a `description` (SPEC-023, SPEC-035).
112
+ * Use a singular noun in kebab-case.
113
+ * Name the part for its role, not its shape or construction: `focus-indicator`, not `ring` or `outline`.
114
+ * Do not use platform words (`wrapper`, `slot`, `host`, `root`, `view`, `pseudo`).
115
+ * Do not repeat the component name (`label`, not `button-label`).
116
+
117
+ ### Nesting
118
+
119
+ **NORMATIVE:** `contains` **MUST** describe visual enclosure: the parent visibly bounds its children, or its own styling (background, padding, border) frames them. It **MUST NOT** record DOM or layout nesting. A part has at most one parent. A state-only part may be contained by the part it surrounds.
120
+
121
+ ### Required and optional
122
+
123
+ **NORMATIVE:** `required: true` means the part is drawn in every configuration and every declared state. A part that an option can remove (an icon, a label hidden in an icon-only layout) is optional.
124
+
125
+ ### What is not anatomy
126
+
127
+ The following **MUST NOT** be declared as parts:
128
+
129
+ * Platform wrappers and hosts.
130
+ * Layout helpers: gaps, spacers, alignment boxes, stacks.
131
+ * Hit areas and accessibility-only elements: hidden inputs, live regions, hidden text.
132
+ * Elements that exist only so one renderer works: clip layers, pseudo-elements, a second element used to fake a border.
133
+ * A grouping that has no style of its own, such as a `control` that only wraps a `box` and a `checkmark`.
134
+
135
+ Another component shown inside this one (for example the progress circle in a button) is a part only when the host component styles it as its own role.
136
+
137
+ ### Review checklist
138
+
139
+ Anatomy is hand-written and has to be reviewed against this chapter before recipes depend on it. For each part:
140
+
141
+ 1. Is it visible in at least one declared state or option?
142
+ 2. Does it have a style of its own?
143
+ 3. Would someone on web, iOS, Android and in Figma recognize it without explanation?
144
+ 4. Is it free of anything that exists only to make one renderer work?
145
+ 5. Does its name follow the naming rules, using a registry term when one fits?
146
+ 6. Is `required` correct for every configuration and every state?
147
+ 7. Does `presentIn` list every state the part is present in, and only those? (Required for state-only parts.) Is any state-dependent styling kept off the part?
148
+ 8. Does each `contains` entry describe visual enclosure?
149
+
150
+ For the component:
151
+
152
+ 9. Does every token `name.anatomy` and every relationship context part resolve to a declared part, with no orphan parts?
153
+ 10. Is every visible piece with its own style declared? Check against the guidelines and states, not one platform's code.
154
+
155
+ ### `anatomyReview`
156
+
157
+ The component declaration carries the review result next to `anatomy`:
158
+
159
+ ```json
160
+ "anatomyReview": { "status": "reviewed", "reviewedIn": "1.0.0-draft", "notes": "Dropped control; focus-indicator now state-only." }
161
+ ```
162
+
163
+ `status` is `draft` or `reviewed`. A component with anatomy and no `anatomyReview` is `draft`. A recipe **SHOULD** only depend on `reviewed` anatomy. Rule SPEC-073 reports, at info level, the components that have relationships but no anatomy and those whose anatomy is still `draft`.
164
+
81
165
  ## Canonical anatomy vocabulary
82
166
 
83
167
  The following anatomy part names are defined by the cross-platform design audit and **SHOULD** be used in preference to custom names when their semantics match. Using canonical names enables cross-component tooling, documentation generation, and token audits.
@@ -101,7 +185,7 @@ The following anatomy part names are defined by the cross-platform design audit
101
185
 
102
186
  Custom part names are permitted. When a custom name is used, the anatomy part object **SHOULD** include a `description` field explaining its visual role (rule SPEC-023).
103
187
 
104
- The table above is informative; the authoritative vocabulary is `@adobe/spectrum-design-data/registry/anatomy-terms.json` (119 entries). SPEC-035 fires advisory warnings when a declared anatomy part `name` is not in that registry, pointing authors at the canonical list. Custom names remain valid — SPEC-035 is advisory, not an error.
188
+ The table above is informative; the authoritative vocabulary is `@adobe/spectrum-design-data/registry/anatomy-terms.json` (140 entries). SPEC-035 fires advisory warnings when a declared anatomy part `name` is not in that registry, pointing authors at the canonical list. Custom names remain valid — SPEC-035 is advisory, not an error.
105
189
 
106
190
  ## Cross-reference with token name objects
107
191
 
@@ -129,15 +213,18 @@ See [Token format — Name object](token-format.md#name-object) for the full nam
129
213
 
130
214
  The following rules in the Layer 2 rule catalog (`rules/rules.yaml`) apply to anatomy part declarations. SPEC-020 was introduced in Phase 6.1 (component-format); SPEC-023, SPEC-024, and SPEC-025 are introduced by this chapter.
131
215
 
132
- | Rule ID | Name | Severity | Assert |
133
- | -------- | --------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
134
- | SPEC-020 | `component-anatomy-valid` | error | Token `anatomy` field value **MUST** match the `name` of a declared anatomy part on the referenced component. |
135
- | SPEC-023 | `anatomy-custom-part-documented` | warning | Anatomy part declarations with a `name` outside the canonical anatomy vocabulary **SHOULD** include a `description` field documenting the part's purpose. |
136
- | SPEC-024 | `anatomy-part-name-unique` | error | Anatomy part `name` values **MUST** be unique within a single component's `anatomy` array. |
137
- | SPEC-025 | `anatomy-requires-component` | error | A token name object **MUST NOT** include an `anatomy` field unless a `component` field is also present. |
138
- | SPEC-035 | `anatomy-part-name-registry-sync` | warning | A component anatomy part's `name` **SHOULD** appear in the canonical anatomy-terms registry (`anatomy-terms.json`). |
139
- | SPEC-037 | `sub-entity-deprecation-cascade` | warning | A non-deprecated token **SHOULD NOT** reference a deprecated anatomy part via `name.anatomy`. Advisory warning prompts migration. |
140
- | SPEC-048 | `anatomy-contains-resolves` | warning | An anatomy part's `contains` entries **SHOULD** match the `name` of another part declared on the same component. Advisory warning for unresolved entries. |
216
+ | Rule ID | Name | Severity | Assert |
217
+ | -------- | ----------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
218
+ | SPEC-020 | `component-anatomy-valid` | error | Token `anatomy` field value **MUST** match the `name` of a declared anatomy part on the referenced component. |
219
+ | SPEC-023 | `anatomy-custom-part-documented` | warning | Anatomy part declarations with a `name` outside the canonical anatomy vocabulary **SHOULD** include a `description` field documenting the part's purpose. |
220
+ | SPEC-024 | `anatomy-part-name-unique` | error | Anatomy part `name` values **MUST** be unique within a single component's `anatomy` array. |
221
+ | SPEC-025 | `anatomy-requires-component` | error | A token name object **MUST NOT** include an `anatomy` field unless a `component` field is also present. |
222
+ | SPEC-035 | `anatomy-part-name-registry-sync` | warning | A component anatomy part's `name` **SHOULD** appear in the canonical anatomy-terms registry (`anatomy-terms.json`). |
223
+ | SPEC-037 | `sub-entity-deprecation-cascade` | warning | A non-deprecated token **SHOULD NOT** reference a deprecated anatomy part via `name.anatomy`. Advisory warning prompts migration. |
224
+ | SPEC-048 | `anatomy-contains-resolves` | warning | An anatomy part's `contains` entries **SHOULD** match the `name` of another part declared on the same component. Advisory warning for unresolved entries. |
225
+ | SPEC-073 | `component-anatomy-reviewed` | info | A component with relationships **SHOULD** declare anatomy, and its anatomy **SHOULD** be `reviewed` (see [`anatomyReview`](#anatomyreview)). One report for the whole dataset. |
226
+ | SPEC-074 | `anatomy-present-in-declared` | error | Every `presentIn` entry **MUST** match a state declared on the component, after state-name normalization (`focus`, `key-focus` and `keyboard-focus` are one state). |
227
+ | SPEC-075 | `visual-present-in-matches-anatomy` | error | A recipe part's `visibleWhen.states` **MUST** agree with the `presentIn` of the anatomy part of the same name. Info when either lists more than one state, since `visibleWhen.states` means all and `presentIn` means any. |
141
228
 
142
229
  ## Full example
143
230