@adobe/design-data-spec 4.12.1 → 4.14.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/conformance/invalid/SPEC-085/dataset.json +74 -0
- package/conformance/invalid/SPEC-085/expected-errors.json +10 -0
- package/conformance/invalid/SPEC-086/dataset.json +82 -0
- package/conformance/invalid/SPEC-086/expected-errors.json +10 -0
- package/conformance/valid/SPEC-085/dataset.json +74 -0
- package/package.json +1 -1
- package/rules/rules.yaml +73 -0
- package/schemas/component-spec-sheet.schema.json +10 -2
- package/schemas/component-visual.schema.json +68 -3
- package/schemas/dataset.schema.json +4 -2
- package/schemas/icon-set.schema.json +110 -0
- package/spec/component-visuals.md +65 -5
- package/spec/dataset-layout.md +3 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tokens": [],
|
|
3
|
+
"components": [
|
|
4
|
+
{
|
|
5
|
+
"$id": "https://opensource.adobe.com/spectrum-design-data/schemas/v0/components/checkbox.json",
|
|
6
|
+
"name": "checkbox",
|
|
7
|
+
"displayName": "Checkbox",
|
|
8
|
+
"meta": {
|
|
9
|
+
"category": "inputs",
|
|
10
|
+
"documentationUrl": "https://spectrum.adobe.com/page/checkbox/"
|
|
11
|
+
},
|
|
12
|
+
"anatomy": [
|
|
13
|
+
{
|
|
14
|
+
"name": "box",
|
|
15
|
+
"description": "Square control.",
|
|
16
|
+
"required": true,
|
|
17
|
+
"contains": ["checkmark"]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "checkmark",
|
|
21
|
+
"description": "Mark shown when selected."
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"visuals": [
|
|
27
|
+
{
|
|
28
|
+
"component": "checkbox",
|
|
29
|
+
"root": "box",
|
|
30
|
+
"sizeScale": {
|
|
31
|
+
"m": 100
|
|
32
|
+
},
|
|
33
|
+
"parts": {
|
|
34
|
+
"box": {
|
|
35
|
+
"kind": "box",
|
|
36
|
+
"children": ["checkmark"]
|
|
37
|
+
},
|
|
38
|
+
"checkmark": {
|
|
39
|
+
"kind": "icon",
|
|
40
|
+
"icon": {
|
|
41
|
+
"set": "ui",
|
|
42
|
+
"name": "tick"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"icons": [
|
|
49
|
+
{
|
|
50
|
+
"set": "ui",
|
|
51
|
+
"source": {
|
|
52
|
+
"repository": "https://github.com/adobe/react-spectrum",
|
|
53
|
+
"commit": "0000000000000000000000000000000000000000",
|
|
54
|
+
"path": "ui-icons",
|
|
55
|
+
"license": "Apache-2.0",
|
|
56
|
+
"copyright": "Copyright 2024 Adobe. All rights reserved."
|
|
57
|
+
},
|
|
58
|
+
"icons": {
|
|
59
|
+
"checkmark": {
|
|
60
|
+
"sizes": {
|
|
61
|
+
"100": {
|
|
62
|
+
"viewBox": "0 0 10 10",
|
|
63
|
+
"paths": [
|
|
64
|
+
{
|
|
65
|
+
"d": "M1 5l3 3 5-6"
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tokens": [],
|
|
3
|
+
"components": [
|
|
4
|
+
{
|
|
5
|
+
"$id": "https://opensource.adobe.com/spectrum-design-data/schemas/v0/components/checkbox.json",
|
|
6
|
+
"name": "checkbox",
|
|
7
|
+
"displayName": "Checkbox",
|
|
8
|
+
"meta": {
|
|
9
|
+
"category": "inputs",
|
|
10
|
+
"documentationUrl": "https://spectrum.adobe.com/page/checkbox/"
|
|
11
|
+
},
|
|
12
|
+
"anatomy": [
|
|
13
|
+
{
|
|
14
|
+
"name": "box",
|
|
15
|
+
"description": "Square control.",
|
|
16
|
+
"required": true,
|
|
17
|
+
"contains": ["checkmark"]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "checkmark",
|
|
21
|
+
"description": "Mark shown when selected."
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"visuals": [
|
|
27
|
+
{
|
|
28
|
+
"component": "checkbox",
|
|
29
|
+
"root": "box",
|
|
30
|
+
"sizeScale": {
|
|
31
|
+
"m": 100
|
|
32
|
+
},
|
|
33
|
+
"parts": {
|
|
34
|
+
"box": {
|
|
35
|
+
"kind": "box",
|
|
36
|
+
"children": ["checkmark"]
|
|
37
|
+
},
|
|
38
|
+
"checkmark": {
|
|
39
|
+
"kind": "icon",
|
|
40
|
+
"icon": {
|
|
41
|
+
"set": "ui",
|
|
42
|
+
"name": "checkmark"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"icons": [
|
|
49
|
+
{
|
|
50
|
+
"set": "ui",
|
|
51
|
+
"source": {
|
|
52
|
+
"repository": "https://github.com/adobe/react-spectrum",
|
|
53
|
+
"commit": "0000000000000000000000000000000000000000",
|
|
54
|
+
"path": "ui-icons",
|
|
55
|
+
"license": "Apache-2.0",
|
|
56
|
+
"copyright": "Copyright 2024 Adobe. All rights reserved."
|
|
57
|
+
},
|
|
58
|
+
"icons": {
|
|
59
|
+
"checkmark": {
|
|
60
|
+
"sizes": {
|
|
61
|
+
"75": {
|
|
62
|
+
"viewBox": "0 0 10 10",
|
|
63
|
+
"paths": [
|
|
64
|
+
{
|
|
65
|
+
"d": "M1 5l3 3 5-6"
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
"200": {
|
|
70
|
+
"viewBox": "0 0 10 10",
|
|
71
|
+
"paths": [
|
|
72
|
+
{
|
|
73
|
+
"d": "M1 5l3 3 5-6"
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tokens": [],
|
|
3
|
+
"components": [
|
|
4
|
+
{
|
|
5
|
+
"$id": "https://opensource.adobe.com/spectrum-design-data/schemas/v0/components/checkbox.json",
|
|
6
|
+
"name": "checkbox",
|
|
7
|
+
"displayName": "Checkbox",
|
|
8
|
+
"meta": {
|
|
9
|
+
"category": "inputs",
|
|
10
|
+
"documentationUrl": "https://spectrum.adobe.com/page/checkbox/"
|
|
11
|
+
},
|
|
12
|
+
"anatomy": [
|
|
13
|
+
{
|
|
14
|
+
"name": "box",
|
|
15
|
+
"description": "Square control.",
|
|
16
|
+
"required": true,
|
|
17
|
+
"contains": ["checkmark"]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "checkmark",
|
|
21
|
+
"description": "Mark shown when selected."
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"visuals": [
|
|
27
|
+
{
|
|
28
|
+
"component": "checkbox",
|
|
29
|
+
"root": "box",
|
|
30
|
+
"sizeScale": {
|
|
31
|
+
"m": 100
|
|
32
|
+
},
|
|
33
|
+
"parts": {
|
|
34
|
+
"box": {
|
|
35
|
+
"kind": "box",
|
|
36
|
+
"children": ["checkmark"]
|
|
37
|
+
},
|
|
38
|
+
"checkmark": {
|
|
39
|
+
"kind": "icon",
|
|
40
|
+
"icon": {
|
|
41
|
+
"set": "ui",
|
|
42
|
+
"name": "checkmark"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"icons": [
|
|
49
|
+
{
|
|
50
|
+
"set": "ui",
|
|
51
|
+
"source": {
|
|
52
|
+
"repository": "https://github.com/adobe/react-spectrum",
|
|
53
|
+
"commit": "0000000000000000000000000000000000000000",
|
|
54
|
+
"path": "ui-icons",
|
|
55
|
+
"license": "Apache-2.0",
|
|
56
|
+
"copyright": "Copyright 2024 Adobe. All rights reserved."
|
|
57
|
+
},
|
|
58
|
+
"icons": {
|
|
59
|
+
"checkmark": {
|
|
60
|
+
"sizes": {
|
|
61
|
+
"100": {
|
|
62
|
+
"viewBox": "0 0 10 10",
|
|
63
|
+
"paths": [
|
|
64
|
+
{
|
|
65
|
+
"d": "M1 5l3 3 5-6"
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
package/package.json
CHANGED
package/rules/rules.yaml
CHANGED
|
@@ -1097,3 +1097,76 @@ rules:
|
|
|
1097
1097
|
message: "Relationship #{index} has no uuid (component '{component}', property '{property}')"
|
|
1098
1098
|
spec_ref: spec/relationship-format.md#identity
|
|
1099
1099
|
introduced_in: "1.0.0-draft"
|
|
1100
|
+
|
|
1101
|
+
- id: SPEC-085
|
|
1102
|
+
name: visual-icon-exists
|
|
1103
|
+
severity: error
|
|
1104
|
+
layer: 2
|
|
1105
|
+
category: component
|
|
1106
|
+
note: >
|
|
1107
|
+
Runs only when the `icons/` catalog is loaded (`validate --icons-path`, the probed
|
|
1108
|
+
`packages/design-data/icons`, or `validate-dataset`). The recipe schema requires the
|
|
1109
|
+
reference on every `icon` part; this rule checks it against the catalog.
|
|
1110
|
+
assert: >
|
|
1111
|
+
The `icon` an icon part references (`set` and `name`) MUST exist in the `icons/` catalog.
|
|
1112
|
+
message: "Part '{part}' of '{component}' references icon {set}:{name}, but {reason}"
|
|
1113
|
+
spec_ref: spec/component-visuals.md#icons
|
|
1114
|
+
introduced_in: "1.0.0-draft"
|
|
1115
|
+
|
|
1116
|
+
- id: SPEC-086
|
|
1117
|
+
name: visual-icon-scale
|
|
1118
|
+
severity: warning
|
|
1119
|
+
layer: 2
|
|
1120
|
+
category: component
|
|
1121
|
+
note: >
|
|
1122
|
+
UI icons have one drawing per scale index. The renderer picks the scale from the part's
|
|
1123
|
+
size binding and falls back to the nearest drawing, which is usually a different design
|
|
1124
|
+
(stroke weight and proportions change between sizes). Checked over the request grid,
|
|
1125
|
+
like SPEC-079. Workflow icons have one drawing and are not checked. Runs only when the
|
|
1126
|
+
`icons/` catalog is loaded.
|
|
1127
|
+
assert: >
|
|
1128
|
+
Every `ui` icon a recipe draws SHOULD have a drawing at the scale its size binding gives:
|
|
1129
|
+
the bound size token's `scaleIndex` or `size` key, else the recipe's `sizeScale` entry
|
|
1130
|
+
for the request's size.
|
|
1131
|
+
message: "Part '{part}' of '{component}' draws ui:{name} at scale {scale} ({source}), which has no drawing; the nearest of {available} is drawn instead"
|
|
1132
|
+
spec_ref: spec/component-visuals.md#icons
|
|
1133
|
+
introduced_in: "1.0.0-draft"
|
|
1134
|
+
|
|
1135
|
+
- id: SPEC-087
|
|
1136
|
+
name: visual-derived-context-matches-token
|
|
1137
|
+
severity: warning
|
|
1138
|
+
layer: 2
|
|
1139
|
+
category: component
|
|
1140
|
+
note: >
|
|
1141
|
+
A recipe's `derived` entry declares a relationship context whose value follows from the
|
|
1142
|
+
solved layout (`(height - label.lineHeight) / 2`) instead of being bound. The context's
|
|
1143
|
+
token is still the documented value, so it must agree with the formula. The check binds
|
|
1144
|
+
the context to the root's padding in a scratch copy of the recipe, solves each size at
|
|
1145
|
+
each scale (`desktop`, `mobile`) and compares. A context whose relationship resolves no
|
|
1146
|
+
token is reported once per entry. A token with a desktop value only is not missing: it
|
|
1147
|
+
resolves for mobile too, so it shows as a mobile mismatch. A disagreement is one warning per
|
|
1148
|
+
scale, so a desktop one is told apart from mobile. Mobile values that the tokens do not
|
|
1149
|
+
carry yet show up here until the token data is fixed.
|
|
1150
|
+
assert: >
|
|
1151
|
+
For every size and scale, the token a `derived` context's relationship gives SHOULD equal
|
|
1152
|
+
the value of its formula over the solved scene.
|
|
1153
|
+
message: "Derived context '{context}' of '{component}' ({from}): the token value differs from the formula at {scale} for {sizes}"
|
|
1154
|
+
spec_ref: spec/component-visuals.md#derived-contexts
|
|
1155
|
+
introduced_in: "1.0.0-draft"
|
|
1156
|
+
|
|
1157
|
+
- id: SPEC-088
|
|
1158
|
+
name: relationship-duplicate-record
|
|
1159
|
+
severity: warning
|
|
1160
|
+
layer: 2
|
|
1161
|
+
category: relationship
|
|
1162
|
+
note: >
|
|
1163
|
+
An exact duplicate adds nothing to resolution, and a changelog that pairs records by
|
|
1164
|
+
identity cannot tell the copies apart. Records with different `setUuid` are not duplicates.
|
|
1165
|
+
SPEC-072 may also report a pair whose token has a scale counterpart, so the two rules can
|
|
1166
|
+
overlap.
|
|
1167
|
+
assert: >
|
|
1168
|
+
Two relationship records in the same file SHOULD NOT have equal scope, context,
|
|
1169
|
+
`$ref` (or inline value) and `setUuid`.
|
|
1170
|
+
message: "Relationship {scope} (context '{context}') repeats an earlier record with the same {ref_or_value}; remove the duplicate"
|
|
1171
|
+
spec_ref: spec/relationship-format.md
|
|
1172
|
+
introduced_in: "1.0.0-draft"
|
|
@@ -166,7 +166,11 @@
|
|
|
166
166
|
"description": "The group's relationship property with its size part dropped (the recipe's `{sizeIndex}`/`{size}` template without the placeholder). Distinct within a block.",
|
|
167
167
|
"type": "string"
|
|
168
168
|
},
|
|
169
|
-
"drawn": { "type": "boolean" }
|
|
169
|
+
"drawn": { "type": "boolean" },
|
|
170
|
+
"derived": {
|
|
171
|
+
"description": "True when the group is a context the recipe derives from the layout (`derived` in the recipe) instead of binding.",
|
|
172
|
+
"type": "boolean"
|
|
173
|
+
}
|
|
170
174
|
},
|
|
171
175
|
"additionalProperties": false
|
|
172
176
|
}
|
|
@@ -182,7 +186,11 @@
|
|
|
182
186
|
"prop": { "type": "string" },
|
|
183
187
|
"kind": { "type": "string" },
|
|
184
188
|
"label": { "type": "string" },
|
|
185
|
-
"relationship": { "type": ["string", "null"] }
|
|
189
|
+
"relationship": { "type": ["string", "null"] },
|
|
190
|
+
"derived": {
|
|
191
|
+
"description": "True for a context the recipe derives from the layout. The span is appended to scene.measurements (kind `padding`, token `derived from height`), and `label` is `derived from height` instead of a token name.",
|
|
192
|
+
"type": "boolean"
|
|
193
|
+
}
|
|
186
194
|
},
|
|
187
195
|
"additionalProperties": false
|
|
188
196
|
}
|
|
@@ -40,6 +40,40 @@
|
|
|
40
40
|
"minimum": 0
|
|
41
41
|
}
|
|
42
42
|
},
|
|
43
|
+
"derived": {
|
|
44
|
+
"type": "array",
|
|
45
|
+
"description": "Relationship contexts the recipe does not bind because their value follows from the solved layout (spec/component-visuals.md, Derived contexts). SPEC-087 checks each against its token.",
|
|
46
|
+
"items": {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"required": ["context", "property", "part", "from"],
|
|
49
|
+
"properties": {
|
|
50
|
+
"context": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"description": "The relationship `context` this entry derives."
|
|
53
|
+
},
|
|
54
|
+
"property": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"description": "The relationship `scope.property`, as in a `relationship` selector; may use {sizeIndex}, {size}."
|
|
57
|
+
},
|
|
58
|
+
"part": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"pattern": "^[a-z][a-z0-9-]*$",
|
|
61
|
+
"description": "The part the spacing leads to. Spec sheets put the marker there."
|
|
62
|
+
},
|
|
63
|
+
"edge": {
|
|
64
|
+
"enum": ["top", "bottom"],
|
|
65
|
+
"default": "top",
|
|
66
|
+
"description": "The container edge the spacing starts from."
|
|
67
|
+
},
|
|
68
|
+
"from": {
|
|
69
|
+
"type": "string",
|
|
70
|
+
"pattern": "^\\(height - [a-z][a-z0-9-]*\\.(lineHeight|height)\\) / 2$",
|
|
71
|
+
"description": "The only formula: `(height - <part>.lineHeight) / 2` or `(height - <part>.height) / 2`. `height` is the container's solved height."
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"additionalProperties": false
|
|
75
|
+
}
|
|
76
|
+
},
|
|
43
77
|
"parts": {
|
|
44
78
|
"type": "object",
|
|
45
79
|
"minProperties": 1,
|
|
@@ -53,9 +87,41 @@
|
|
|
53
87
|
},
|
|
54
88
|
"additionalProperties": false,
|
|
55
89
|
"$defs": {
|
|
90
|
+
"iconRef": {
|
|
91
|
+
"type": "object",
|
|
92
|
+
"description": "The icon an icon part draws, from the icons/ catalog (icon-set.schema.json). SPEC-085 checks it exists.",
|
|
93
|
+
"required": ["set", "name"],
|
|
94
|
+
"properties": {
|
|
95
|
+
"set": { "enum": ["ui", "workflow"] },
|
|
96
|
+
"name": {
|
|
97
|
+
"type": "string",
|
|
98
|
+
"pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$"
|
|
99
|
+
},
|
|
100
|
+
"content": {
|
|
101
|
+
"type": "boolean",
|
|
102
|
+
"description": "The icon is content, like a label: this is the recipe's default and a render request may replace it. Default false."
|
|
103
|
+
},
|
|
104
|
+
"optional": {
|
|
105
|
+
"type": "boolean",
|
|
106
|
+
"description": "A render request may remove this content icon (icon none). Requires content. Default false."
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"if": {
|
|
110
|
+
"properties": { "optional": { "const": true } },
|
|
111
|
+
"required": ["optional"]
|
|
112
|
+
},
|
|
113
|
+
"then": {
|
|
114
|
+
"properties": { "content": { "const": true } },
|
|
115
|
+
"required": ["content"]
|
|
116
|
+
},
|
|
117
|
+
"additionalProperties": false
|
|
118
|
+
},
|
|
56
119
|
"part": {
|
|
57
120
|
"type": "object",
|
|
58
121
|
"required": ["kind"],
|
|
122
|
+
"if": { "properties": { "kind": { "const": "icon" } } },
|
|
123
|
+
"then": { "required": ["icon"] },
|
|
124
|
+
"else": { "not": { "required": ["icon"] } },
|
|
59
125
|
"properties": {
|
|
60
126
|
"kind": {
|
|
61
127
|
"enum": ["box", "text", "icon", "ring", "progress"]
|
|
@@ -76,9 +142,8 @@
|
|
|
76
142
|
"pattern": "^[a-z][a-z0-9-]*$",
|
|
77
143
|
"description": "For a ring: the part it outlines. Defaults to the root."
|
|
78
144
|
},
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"description": "For an icon nested in a box: the built-in glyph to draw. Unset draws a placeholder."
|
|
145
|
+
"icon": {
|
|
146
|
+
"$ref": "#/$defs/iconRef"
|
|
82
147
|
},
|
|
83
148
|
"pressTarget": {
|
|
84
149
|
"type": "string",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"fields": { "const": true },
|
|
20
20
|
"modeSets": { "const": true },
|
|
21
21
|
"registry": { "const": true },
|
|
22
|
-
"guidelines": { "const": true }
|
|
22
|
+
"guidelines": { "const": true },
|
|
23
|
+
"icons": { "const": true }
|
|
23
24
|
},
|
|
24
25
|
"additionalProperties": false
|
|
25
26
|
},
|
|
@@ -32,7 +33,8 @@
|
|
|
32
33
|
"fields": { "type": "string", "minLength": 1 },
|
|
33
34
|
"modeSets": { "type": "string", "minLength": 1 },
|
|
34
35
|
"registry": { "type": "string", "minLength": 1 },
|
|
35
|
-
"guidelines": { "type": "string", "minLength": 1 }
|
|
36
|
+
"guidelines": { "type": "string", "minLength": 1 },
|
|
37
|
+
"icons": { "type": "string", "minLength": 1 }
|
|
36
38
|
},
|
|
37
39
|
"additionalProperties": false
|
|
38
40
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://opensource.adobe.com/spectrum-design-data/schemas/v0/icon-set.schema.json",
|
|
4
|
+
"title": "Icon set",
|
|
5
|
+
"description": "Layer 1 structural schema for one icon set in the icons/ catalog (spec/dataset-layout.md): path geometry keyed by icon id, with the source it was imported from. Paths carry no colour; a renderer fills them with the part's colour. A UI icon has one drawing per size, keyed by the scale index of the icon size tokens.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["set", "source", "icons"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"$schema": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"format": "uri"
|
|
12
|
+
},
|
|
13
|
+
"set": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"pattern": "^[a-z][a-z0-9-]*$",
|
|
16
|
+
"description": "Set id, also the file name (ui.json is set ui)."
|
|
17
|
+
},
|
|
18
|
+
"description": {
|
|
19
|
+
"type": "string"
|
|
20
|
+
},
|
|
21
|
+
"source": {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"description": "Provenance and licence of the imported geometry.",
|
|
24
|
+
"required": ["repository", "commit", "path", "license", "copyright"],
|
|
25
|
+
"properties": {
|
|
26
|
+
"repository": { "type": "string", "format": "uri" },
|
|
27
|
+
"commit": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"pattern": "^[0-9a-f]{40}$",
|
|
30
|
+
"description": "Full commit SHA the files were read at."
|
|
31
|
+
},
|
|
32
|
+
"path": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"description": "Directory of the source files in the repository."
|
|
35
|
+
},
|
|
36
|
+
"license": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"description": "SPDX licence id."
|
|
39
|
+
},
|
|
40
|
+
"copyright": { "type": "string" },
|
|
41
|
+
"notice": { "type": "string" }
|
|
42
|
+
},
|
|
43
|
+
"additionalProperties": false
|
|
44
|
+
},
|
|
45
|
+
"icons": {
|
|
46
|
+
"type": "object",
|
|
47
|
+
"description": "Icons keyed by id (kebab-case; the icon-terms registry id where one exists).",
|
|
48
|
+
"propertyNames": { "pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$" },
|
|
49
|
+
"minProperties": 1,
|
|
50
|
+
"additionalProperties": {
|
|
51
|
+
"oneOf": [
|
|
52
|
+
{ "$ref": "#/$defs/geometry" },
|
|
53
|
+
{
|
|
54
|
+
"type": "object",
|
|
55
|
+
"required": ["sizes"],
|
|
56
|
+
"properties": {
|
|
57
|
+
"sizes": {
|
|
58
|
+
"type": "object",
|
|
59
|
+
"description": "One drawing per size, keyed by scale index (50, 75, 100, …).",
|
|
60
|
+
"propertyNames": { "pattern": "^[0-9]+$" },
|
|
61
|
+
"minProperties": 1,
|
|
62
|
+
"additionalProperties": { "$ref": "#/$defs/geometry" }
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"additionalProperties": false
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"additionalProperties": false,
|
|
72
|
+
"$defs": {
|
|
73
|
+
"geometry": {
|
|
74
|
+
"type": "object",
|
|
75
|
+
"required": ["viewBox", "paths"],
|
|
76
|
+
"properties": {
|
|
77
|
+
"viewBox": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"pattern": "^-?[0-9.]+ -?[0-9.]+ [0-9.]+ [0-9.]+$",
|
|
80
|
+
"description": "SVG viewBox: min-x min-y width height."
|
|
81
|
+
},
|
|
82
|
+
"paths": {
|
|
83
|
+
"type": "array",
|
|
84
|
+
"minItems": 1,
|
|
85
|
+
"items": {
|
|
86
|
+
"type": "object",
|
|
87
|
+
"required": ["d"],
|
|
88
|
+
"properties": {
|
|
89
|
+
"d": {
|
|
90
|
+
"type": "string",
|
|
91
|
+
"minLength": 1,
|
|
92
|
+
"description": "SVG path data in viewBox units."
|
|
93
|
+
},
|
|
94
|
+
"fillRule": {
|
|
95
|
+
"enum": ["nonzero", "evenodd"],
|
|
96
|
+
"description": "SVG fill-rule; nonzero when absent."
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"additionalProperties": false
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"source": {
|
|
103
|
+
"type": "string",
|
|
104
|
+
"description": "Source file name the drawing was read from."
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"additionalProperties": false
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -41,6 +41,7 @@ Status: **draft / RFC**. The schema is [`component-visual.schema.json`](../schem
|
|
|
41
41
|
| `component` | string | REQUIRED | Component `name`. |
|
|
42
42
|
| `root` | string | REQUIRED | Key of the root part in `parts`. |
|
|
43
43
|
| `sizeScale` | object | OPTIONAL | Maps each `size` option value to a numeric scale index (e.g. `m` → `100`). Enables `{sizeIndex}` in selectors. A component with no `size` option declares one entry, the size renderers draw. |
|
|
44
|
+
| `derived` | array | OPTIONAL | Relationship contexts the recipe derives from the layout instead of binding. See [Derived contexts](#derived-contexts). SPEC-087. |
|
|
44
45
|
| `notVisual` | array | OPTIONAL | Component options and states that deliberately have no visual in this recipe (for example `["isReadOnly"]`). SPEC-066. |
|
|
45
46
|
| `parts` | object | REQUIRED | Map of anatomy part name → part definition. |
|
|
46
47
|
|
|
@@ -53,7 +54,7 @@ Status: **draft / RFC**. The schema is [`component-visual.schema.json`](../schem
|
|
|
53
54
|
| `visibleWhen` | condition | The part is drawn only when the condition holds. Absent means always drawn. |
|
|
54
55
|
| `placement` | object | For a child that is positioned rather than flowed: `anchor` (`top-start`, `top-end`, `bottom-start`, `bottom-end`, `center`) and optional `inset` value. `start`/`end` are logical and flip under RTL. |
|
|
55
56
|
| `around` | string | For a `ring`: the part it outlines. Defaults to the root. |
|
|
56
|
-
| `
|
|
57
|
+
| `icon` | object | For an `icon` part (REQUIRED there, not allowed on other kinds): the icon to draw, `{"set": "ui" \| "workflow", "name": "<id>"}`, plus optional `content` and `optional` flags. See [Icons](#icons). |
|
|
57
58
|
| `pressTarget` | string | For the root: the part the pressed scale applies to (for example a switch handle). Parts nested inside it scale with it (a checkbox `box` scales with its mark). Defaults to the whole scene. |
|
|
58
59
|
| `props` | object | Map of **visual property** → value source or conditional list. |
|
|
59
60
|
|
|
@@ -86,7 +87,38 @@ Status: **draft / RFC**. The schema is [`component-visual.schema.json`](../schem
|
|
|
86
87
|
|
|
87
88
|
**Column stretch.** In a column root, a flowed `box` with no `width` and no row content fills the column's inner width (a divider or a content region). A flowed `box` with no `height` and no children is as tall as its block padding.
|
|
88
89
|
|
|
89
|
-
**Nested boxes.** A flowed `box` child paints itself (`fill`, `stroke`, `strokeWidth`, `cornerRadius`) and may list its own `children`, which are boxes laid out inside it. A nested child is centered across the parent. Along the inline axis its `justify` prop (`start`, `center`, `end`) places it, with an inset equal to the cross-axis gap. A nested child with no `width` or `height` fills the parent. A `cornerRadius` of `0.5` with `relativeTo: "height"` draws a pill, or a circle for a square box. The switch uses this for its track and handle. A nested `icon` is centered on both axes
|
|
90
|
+
**Nested boxes.** A flowed `box` child paints itself (`fill`, `stroke`, `strokeWidth`, `cornerRadius`) and may list its own `children`, which are boxes laid out inside it. A nested child is centered across the parent. Along the inline axis its `justify` prop (`start`, `center`, `end`) places it, with an inset equal to the cross-axis gap. A nested child with no `width` or `height` fills the parent. A `cornerRadius` of `0.5` with `relativeTo: "height"` draws a pill, or a circle for a square box. The switch uses this for its track and handle. A nested `icon` is centered on both axes; the checkbox uses this for its checkmark and dash.
|
|
91
|
+
|
|
92
|
+
### Icons
|
|
93
|
+
|
|
94
|
+
An `icon` part names the icon it draws from the dataset's `icons/` catalog ([`icon-set.schema.json`](../schemas/icon-set.schema.json)):
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
"disclosure-icon": { "kind": "icon", "icon": { "set": "ui", "name": "chevron" }, "props": { ... } }
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
| Field | Type | Description |
|
|
101
|
+
| ---------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
102
|
+
| `set` | enum | `ui` (component glyphs such as `chevron`, `checkmark`, `dash`, one drawing per size) or `workflow` (content icons such as `add`, one 20 px drawing). |
|
|
103
|
+
| `name` | string | Icon id in that set. SPEC-085 checks it exists. |
|
|
104
|
+
| `content` | boolean | The icon is content, like a label: the recipe's icon is a default that a render request may replace. Default `false` (the icon is part of the design). |
|
|
105
|
+
| `optional` | boolean | With `content`, a render request may also remove the icon. Default `false`. |
|
|
106
|
+
|
|
107
|
+
A `chevron` points to the end; the `rotation` prop turns it (90 when an accordion item is open).
|
|
108
|
+
|
|
109
|
+
**Content icons.** A render request **MAY** carry `icon`, either `{"set", "name"}` or `"none"`. It applies to every part whose `icon` has `content: true`, the way `label` replaces the text of the recipe's label. `{"set", "name"}` replaces the part's icon. `"none"` removes the part from the layout as if its `visibleWhen` did not hold; it is allowed only on a part with `optional: true`, and on any other part the renderer **MUST** keep the recipe's icon and emit a diagnostic. A request icon that is not in the catalog draws the placeholder with a diagnostic. Parts without `content` ignore the request's `icon`. The button's icon is `content` and `optional` (a label-only button), the badge's is `content` only, because its `icon` option already hides it. Command-line and API spellings of the override (`--icon workflow:edit`, `--icon none`) belong to the tools, not to this format.
|
|
110
|
+
|
|
111
|
+
**Choosing a drawing.** A `workflow` icon has one drawing, scaled to the part box. A `ui` icon has one drawing per scale index, and the renderer **MUST** choose its scale like this:
|
|
112
|
+
|
|
113
|
+
1. The token bound to the part's `width` (else `height`) gives it: the token name's `scaleIndex`, else its `size` key (`xs` 50, `s` 75, `m` 100, `l` 200, `xl` 300, `xxl` 400, the scale of `workflow-icon-medium` and its siblings).
|
|
114
|
+
2. Otherwise (a literal size, an inline relationship value or a token whose name has neither field), the recipe's `sizeScale` entry for the request's `size`.
|
|
115
|
+
3. Otherwise 100.
|
|
116
|
+
|
|
117
|
+
The drawing at that scale is used; when the icon has none, the renderer **MUST** draw the nearest available scale (the larger one on a tie). SPEC-086 warns when a recipe falls back, since another size's drawing has different stroke weights and proportions. The scale is chosen once per size and mode, from the bindings the part has with no states and default options, so a state or option never changes the drawing. A renderer **MAY** make this choice when it compiles the recipe.
|
|
118
|
+
|
|
119
|
+
**Drawing.** The renderer scales the icon's `viewBox` uniformly to fit the part box, centered, applies `rotation` about the box center, and fills every path, honoring its `fillRule`, with the part's resolved `color`. Icon data carries no color. A part box that is not square leaves the icon at the smaller side, so an icon part's `width` and `height` are the icon's size, not the size of the shape it draws.
|
|
120
|
+
|
|
121
|
+
An icon with no drawing (no such set or icon in the catalog, or no catalog) **MUST** draw a placeholder plus in the part box and emit a diagnostic naming the set, the icon and the request's size.
|
|
90
122
|
|
|
91
123
|
### Value sources
|
|
92
124
|
|
|
@@ -110,6 +142,31 @@ An entry on `cornerRadius` **MAY** set `relativeTo: "height"`, making its numeri
|
|
|
110
142
|
|
|
111
143
|
**NORMATIVE:** A renderer **MUST NOT** substitute a literal for a value source it cannot resolve. It **MUST** emit a diagnostic naming the part, property and selector, and omit that property.
|
|
112
144
|
|
|
145
|
+
### Derived contexts
|
|
146
|
+
|
|
147
|
+
Some relationship contexts describe a value the layout already implies. The vertical spacing from a container's edge to its label is `(height - line height) / 2` for a content-centred container, so a recipe that binds the container's height and centres its content gets that spacing for free. Binding the spacing token as well would fix the value where the design source derives it (and break it when the label wraps or the scale changes). A recipe lists such a context under `derived` rather than binding it:
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
"derived": [
|
|
151
|
+
{
|
|
152
|
+
"context": "Spacing (top/bottom edge to label)",
|
|
153
|
+
"property": "component-top-to-text-{sizeIndex}",
|
|
154
|
+
"part": "label",
|
|
155
|
+
"from": "(height - label.lineHeight) / 2"
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
| Field | Description |
|
|
161
|
+
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
162
|
+
| `context` | The relationship `context` that is derived. |
|
|
163
|
+
| `property` | The relationship `scope.property` of the records it covers, as in a `relationship` selector. |
|
|
164
|
+
| `part` | The part the spacing leads to. Spec sheets put the marker there. |
|
|
165
|
+
| `edge` | `top` (default) or `bottom`: the container edge the spacing starts from. |
|
|
166
|
+
| `from` | The formula. Only `(height - <part>.lineHeight) / 2` and `(height - <part>.height) / 2` exist; `height` is the container's solved height. |
|
|
167
|
+
|
|
168
|
+
**NORMATIVE:** A renderer **MUST NOT** read a `derived` entry; it changes no scene. `<part>.lineHeight` is the part's solved height, which is its line height for a single-line label; tooling **MUST** solve with a label that does not wrap. Tooling reads a `derived` entry for two things. A spec sheet shows the context with a highlight on `part`, a marker and a measurement of the formula's value over the solved scene, labelled as derived from height. SPEC-087 compares, for every size and scale, the token the context's relationship gives with the formula's value over the solved scene. The comparison is a warning: a context whose relationship resolves no token is reported once, and a disagreement means the token data or the recipe's layout is wrong, never that the recipe binds the token. A control that is centred on the first label line (a checkbox or switch control) declares the label's formula, since the shared token is the space above the label line.
|
|
169
|
+
|
|
113
170
|
### Interim: relationship `context` strings
|
|
114
171
|
|
|
115
172
|
Relationship `context` is free text today (for example `Background (selected, emphasized)`). Recipes bind to those strings so the pilot can ship without changing relationship data. This is brittle: a reworded context breaks a recipe, and SPEC-062 reports a `relationship` selector that matches no relationship. **RECOMMENDED:** a follow-up adds structured `options` to relationships so recipes can select by option and state instead of by label.
|
|
@@ -135,6 +192,9 @@ The recipe schema checks one file's structure (Layer 1). A JSON Schema cannot re
|
|
|
135
192
|
| SPEC-079 | Warning. A platform-owned recipe renders over the request grid with no scene diagnostic. Planned to become an error in a later minor release |
|
|
136
193
|
| SPEC-080 | Info. A platform recipe replaces the foundation recipe of the same component name |
|
|
137
194
|
| SPEC-083 | Warning. A part binds only props the renderer reads for it (the root, its `kind`, or a `placement`); the schema accepts more, and the renderer ignores the rest |
|
|
195
|
+
| SPEC-085 | Error. An `icon` part's `set` and `name` exist in the `icons/` catalog. Runs when the catalog is loaded (`--icons-path`, or the probed `packages/design-data/icons`) |
|
|
196
|
+
| SPEC-086 | Warning. Over the request grid, every `ui` icon has a drawing at the scale its size binding gives (see [Icons](#icons)), so the renderer does not fall back to the nearest size |
|
|
197
|
+
| SPEC-087 | Warning. For every size and scale, the token a `derived` context gives equals the value of its formula over the solved scene (see [Derived contexts](#derived-contexts)) |
|
|
138
198
|
|
|
139
199
|
SPEC-066 to SPEC-070 are warnings, so `validate` stays green while recipes are migrated. A ratchet keeps the debt from growing: `sdk/core/tests/recipe_lint_baseline.rs` compares each (rule, recipe) warning count with `sdk/core/tests/fixtures/recipe-lint-baseline.json`. A new warning fails the test, an improvement must lower the baseline, and a recipe that is not in the baseline must have none.
|
|
140
200
|
|
|
@@ -154,7 +214,7 @@ These are the conventions the lints above enforce, plus the decisions made while
|
|
|
154
214
|
1. **Bind through relationships.** A recipe takes a value from the component's relationships by default (SPEC-067). Use a `token` selector only when no relationship covers the value, and a literal only for layout keywords, `transparent`, zero, ratio radii or geometry no token describes (SPEC-068). If the token you need is missing, file the gap; do not hide it behind a literal. Put option-dependent choices in the relationships, not in the recipe: pin `scope.options` on the relationship records (a default with no options, overrides that pin them) and bind the role once, so a variant added later is a data change. **Exception: platform-owned components.** A recipe for a component that a platform manifest declares (for example `button-ios`) MAY bind platform tokens directly with `token` selectors, because the platform's own code binds tokens by name and has no relationship layer. This applies to platform-owned components only; foundation components stay relationship-first. SPEC-062 and, for platform recipes, SPEC-079 keep checking that every selected token resolves. Watch the recipe size: the mapping from option to token then lives in the recipe, so name the platform's tokens so that selectors can template the option values, and measure the recipe's line count (see [Proposal 014](../../../docs/proposals/014-platform-owned-visual-recipes.md)).
|
|
155
215
|
2. **Bind current tokens.** Do not bind a deprecated token (SPEC-069; SPEC-058 covers relationships). When the only token is deprecated, bind it and record the gap, so the lint keeps showing it.
|
|
156
216
|
3. **Selection is an option, not a state.** Model "selected" as the `isSelected` option and scope relationships with `options.isSelected: true`. The renderer treats `isSelected` as the `selected` state and `isDisabled` as `disabled` when choosing between tokens that carry a state.
|
|
157
|
-
4. **Cover the component.** Every option and state the component declares either changes the recipe or is listed in `notVisual` (SPEC-066). `notVisual` records a deliberate absence, for example `isReadOnly` (it only suppresses hover and press) or `icon`
|
|
217
|
+
4. **Cover the component.** Every option and state the component declares either changes the recipe or is listed in `notVisual` (SPEC-066). `notVisual` records a deliberate absence, for example `isReadOnly` (it only suppresses hover and press), or the button's `icon` option, which names a workflow icon but does not yet choose the content icon a request draws (p6ga.17.4 decides how the two relate).
|
|
158
218
|
5. **Press scales the part the design scales.** Set `pressTarget` to the part the design source scales when pressed: the checkbox scales its `box` (with its mark), the switch scales its `handle`. Parts nested inside the target scale with it, and the label does not.
|
|
159
219
|
6. **Priorities.** Disabled overrides interaction states with `priority: 10` (11 where a more specific disabled case must win). Error uses `priority: 5`.
|
|
160
220
|
7. **Relationship contexts are the design token group label,** with no `S1` or `S2` prefix, and the same label names the same concept in every component (for example `Control (disabled)`).
|
|
@@ -165,7 +225,7 @@ New recipes **MUST** be free of SPEC-066 to SPEC-070 warnings. Existing recipes
|
|
|
165
225
|
|
|
166
226
|
## Render request and scene graph
|
|
167
227
|
|
|
168
|
-
A render request is tool input, not dataset content: `{component, options, states, modes, overlays}`. A **matrix** is a list of requests plus a layout (rows × columns by option or state).
|
|
228
|
+
A render request is tool input, not dataset content: `{component, options, states, modes, label, icon, overlays}`. `label` replaces the label text and `icon` the content icon (see [Icons](#icons)). A **matrix** is a list of requests plus a layout (rows × columns by option or state).
|
|
169
229
|
|
|
170
230
|
The scene graph is the contract between resolution and output:
|
|
171
231
|
|
|
@@ -189,7 +249,7 @@ These record the research behind this draft and are not normative.
|
|
|
189
249
|
|
|
190
250
|
**R3 — Layout: minimal flex first.** The pilot box model needs row direction, centering, gap, padding and one absolutely placed child. A purpose-built layout of that size avoids a dependency and keeps WASM small. Revisit `taffy` when text-field (wrapping, stacked label and help text) is piloted.
|
|
191
251
|
|
|
192
|
-
**R4 — Icons.**
|
|
252
|
+
**R4 — Icons.** Icon geometry is SVG path data in the dataset's `icons/` catalog, imported from React Spectrum S2 (UI icons at every size, a few workflow icons). Recipes reference it by set and name ([Icons](#icons)); they hold no path data.
|
|
193
253
|
|
|
194
254
|
**R5 — Figma.** The Figma backend consumes the scene graph and emits node JSON for a plugin to create frames bound to Figma Variables by token id, reusing the Variable mapping in `sdk/plugins/figma`. It requires no Figma-specific data in recipes.
|
|
195
255
|
|
package/spec/dataset-layout.md
CHANGED
|
@@ -18,6 +18,7 @@ A dataset is rooted at a single **dataset root** directory. The following table
|
|
|
18
18
|
| `mode-sets/` | Optional (registered) | Mode set declarations, one per `*.json` file. | [`mode-set.schema.json`](../schemas/mode-set.schema.json) |
|
|
19
19
|
| `registry/` | Optional (registered) | Registry value collections (vocabulary), one or more `*.json` files. | [`registry-value.json`](../schemas/registry-value.json) |
|
|
20
20
|
| `guidelines/` | Optional (registered) | One guideline document per `*.json` file. | [`guideline.schema.json`](../schemas/guideline.schema.json) |
|
|
21
|
+
| `icons/` | Optional (registered) | One icon set (path geometry keyed by icon id) per `*.json` file. | [`icon-set.schema.json`](../schemas/icon-set.schema.json) |
|
|
21
22
|
|
|
22
23
|
**NORMATIVE:** A conformant dataset **MUST** contain a `tokens/` directory holding at least one `*.tokens.json` file. A dataset with no `tokens/` directory, or a `tokens/` directory containing no `*.tokens.json` file, is **not** conformant.
|
|
23
24
|
|
|
@@ -37,6 +38,7 @@ A dataset is rooted at a single **dataset root** directory. The following table
|
|
|
37
38
|
| `mode-sets/` | `*.json` | One declaration per file. |
|
|
38
39
|
| `registry/` | `*.json` | One value collection per file. |
|
|
39
40
|
| `guidelines/` | `*.json` | One declaration per file. |
|
|
41
|
+
| `icons/` | `*.json` | One set per file; by convention named after its `set` (`ui.json`). |
|
|
40
42
|
|
|
41
43
|
## Dataset discovery
|
|
42
44
|
|
|
@@ -47,7 +49,7 @@ Tooling that operates on "the current dataset" (rather than an explicit path) **
|
|
|
47
49
|
3. **In-repo probing** — when running inside a monorepo checkout, probe for the standard layout relative to the working directory (`packages/design-data/tokens` and one level up). This preserves the in-repo workflow with zero configuration.
|
|
48
50
|
4. **Embedded snapshot** — when no dataset is found on disk, tooling MAY fall back to a snapshot of the foundation dataset embedded in the binary, materialized to a local cache directory.
|
|
49
51
|
|
|
50
|
-
**NORMATIVE:** The required directory (`tokens/`) and registered optional directories are resolved **relative to the dataset root** determined above. A discovery tier that resolves a dataset root **MUST** look for `tokens/`, `components/`, `fields/`, `mode-sets/`, `registry/`, and `
|
|
52
|
+
**NORMATIVE:** The required directory (`tokens/`) and registered optional directories are resolved **relative to the dataset root** determined above. A discovery tier that resolves a dataset root **MUST** look for `tokens/`, `components/`, `fields/`, `mode-sets/`, `registry/`, `guidelines/`, and `icons/` directly under that root.
|
|
51
53
|
|
|
52
54
|
The reference implementation of this algorithm lives in the SDK at `sdk/core/src/data_source/mod.rs` (`resolve`, `from_root`, and `probe_cwd`).
|
|
53
55
|
|