@canonical/anatomy-dsl 0.3.0 → 0.5.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/README.md CHANGED
@@ -9,12 +9,11 @@ A YAML-based DSL for representing design system component anatomies — platform
9
9
  node:
10
10
  uri: global.component.button
11
11
  styles:
12
- layout.type: flow
13
- layout.direction: horizontal
12
+ layout.type: inline-flex
14
13
  layout.align: center
15
- spacing.internal: spacing/medium
16
- appearance.background: color/surface/button
17
- appearance.radius: radius/button
14
+ spacing.internal.inline.start: spacing.inset.action.inline
15
+ appearance.background: [modifier.color.foreground.primary, color.foreground.primary]
16
+ appearance.radius: dimension.radius.medium
18
17
  edges:
19
18
  - node:
20
19
  uri: global.subcomponent.button-icon
@@ -28,7 +27,30 @@ node:
28
27
  slotName: default
29
28
  ```
30
29
 
31
- Style values are **design token paths** (`spacing/medium`, `color/surface/button`) — forward-slash delimited references resolved at runtime against the active theme. Primitives like `flow` and `center` are used for layout semantics that don't vary across themes.
30
+ Style values are **token symbols**, written in the symbol's own dotted
31
+ spelling: `dimension.radius.medium`, `color.foreground.primary`. A list is
32
+ the **fallback order** — a one-to-one transcription of the implementation's
33
+ own `var()` chain, so `[modifier.color.foreground.primary,
34
+ color.foreground.primary]` says "read the primary-foreground channel, and
35
+ fall back to the primary foreground token", which is what the reference CSS
36
+ does. A **channel** (`modifier.*`, `surface.*`) is a symbol like any other:
37
+ the anatomy consumes it by name where the implementation reads the channel,
38
+ and the semantic token by name where it reads the token — nothing is
39
+ inferred. Primitives like `inline-flex` and `center` are used for layout
40
+ semantics that do not vary across themes, and one may end a list where the
41
+ implementation's chain ends in a literal
42
+ (`[modifier.color.icon, modifier.color.text, currentColor]`).
43
+
44
+ Keys come from a **closed roster** — `definitions/style-keys.yaml`, from
45
+ which `definitions/registry.ttl` is generated. The roster is measured, not
46
+ designed: its 111 keys are the right-hand column of a table whose left-hand
47
+ column was every CSS property bound on a component selector across 89
48
+ published component stylesheets, so a key exists because an implementation
49
+ binds the property. That measurement lives in `canonical/design-system`,
50
+ where the reference stylesheets are an input; this package reads no CSS and
51
+ holds the roster, with its provenance stated in both files. The
52
+ slash-delimited path and the trailing `?` marker of earlier versions are
53
+ retired — see `docs/api-reference.md` §5, §11 and §12.
32
54
 
33
55
  ## Projections
34
56
 
@@ -138,13 +160,13 @@ usage in anatomies splits into exactly two cases:
138
160
  Semantics:
139
161
 
140
162
  - Pins live on **named nodes only** — anonymous nodes have no prop surface.
141
- This is enforced in the types, the parser, the JSON Schema, and SHACL.
163
+ This is enforced in the types, the parser, and SHACL.
142
164
  - The DSL **never defines a prop surface** (names, types, optionality live in
143
165
  the design system ontology); it only asserts values. Whether a pinned prop
144
166
  exists on the component, and whether a value is admissible (e.g. a glyph
145
167
  name in the icon set), are consumer-side checks against the DS graph — the
146
168
  same posture as projection checking against the provider SDL.
147
- - Values are scalars, coerced to strings — no token paths or fallback arrays.
169
+ - Values are scalars, coerced to strings — no symbols and no fallback lists: a pin is a value, not a style.
148
170
  Pins are *values with meaning*, not styles: a theme may reskin what
149
171
  `chevron-down` looks like (asset layer), but never remap which glyph an
150
172
  anatomy means.
@@ -166,10 +188,11 @@ key is the default state:
166
188
  styles:
167
189
  interaction.cursor: pointer
168
190
  interaction.cursor@disabled: not-allowed
169
- appearance.background: color/fill/default
170
- appearance.background@hover: color/fill/default/hover
171
- appearance.background@disabled: color/fill/default/disabled
172
- appearance.outline@focus: color/focus-ring # exists only in a state
191
+ appearance.background: [modifier.color.foreground.primary, color.foreground.primary]
192
+ appearance.background@hover: color.foreground.primary.hover
193
+ appearance.background@disabled: color.foreground.primary.disabled
194
+ # A slot may read a channel only in one state.
195
+ appearance.outline.color@focus: [modifier.color.focusRing, color.focusRing, currentColor]
173
196
  ```
174
197
 
175
198
  The state vocabulary is **closed and registry-governed**: `hover`, `active`,
@@ -188,15 +211,16 @@ The boundaries that keep "state machines out of scope" true:
188
211
  transitions, triggers, or logic.
189
212
  - **Gate vs appearance**: `props: { disabled: true }` (or the consumer) puts
190
213
  a node in the disabled state; `…@disabled` styles say how it looks there.
191
- - The canonical state token is the base token path plus a state leaf segment
192
- (`color/fill/default/hover`), matching the token tree. A consumer-side lint
193
- checks that a state-leafed token value agrees with its key's `@state`.
214
+ - A state-scoped value that differs from its base state's is a **lint, not a
215
+ constraint**: it is reported with the ranks at which they differ and never
216
+ rejected, because the reference does it Button's `:disabled` reads
217
+ `color.text.disabled`, a different symbol from its resting `color.text`.
194
218
  - The grammar reserves repeatable markers for compound states
195
219
  (`@selected@hover`, canonical order: value/control state before user-action
196
220
  state); v1 permits a single `@`.
197
221
 
198
222
  In TTL, the Style tuple gains one optional dimension:
199
- `[ styleKey "appearance.background" ; styleState "hover" ; styleValue "…" ]`.
223
+ `[ a :Style ; :styleKey "appearance.background" ; :styleState "hover" ; :styleValue "…" ]`.
200
224
  See `examples/yaml/stateful-button.anatomy.yaml`.
201
225
 
202
226
  ## Install
@@ -219,19 +243,19 @@ const ttl = anatomyToTTL(spec);
219
243
  The button example above produces:
220
244
 
221
245
  ```turtle
222
- @prefix : <http://anatomy-dsl.example.org/ontology#> .
246
+ @prefix : <https://anatomy.canonical.com/> .
247
+ @prefix dt: <https://dt.canonical.com/> .
223
248
 
224
249
  [] a :Specification ;
225
250
  :rootNode [
226
251
  a :NamedNode ;
227
252
  :uri "global.component.button" ;
228
253
  :hasStyle
229
- [ :styleKey "layout.type" ; :styleValue "flow" ] ,
230
- [ :styleKey "layout.direction" ; :styleValue "horizontal" ] ,
231
- [ :styleKey "layout.align" ; :styleValue "center" ] ,
232
- [ :styleKey "spacing.internal" ; :styleValue "spacing/medium" ] ,
233
- [ :styleKey "appearance.background" ; :styleValue "color/surface/button" ] ,
234
- [ :styleKey "appearance.radius" ; :styleValue "radius/button" ] ;
254
+ [ a :Style ; :styleKey "layout.type" ; :styleValue "inline-flex" ] ,
255
+ [ a :Style ; :styleKey "layout.align" ; :styleValue "center" ] ,
256
+ [ a :Style ; :styleKey "spacing.internal.inline.start" ; :styleValue "spacing.inset.action.inline" ; :consumes ( dt:spacing.inset.action.inline ) ] ,
257
+ [ a :Style ; :styleKey "appearance.background" ; :styleValue "[modifier.color.foreground.primary, color.foreground.primary]" ; :consumes ( dt:modifier.color.foreground.primary dt:color.foreground.primary ) ] ,
258
+ [ a :Style ; :styleKey "appearance.radius" ; :styleValue "dimension.radius.medium" ; :consumes ( dt:dimension.radius.medium ) ] ;
235
259
  :hasEdge [
236
260
  a :Edge ;
237
261
  :edgeTarget [
@@ -290,11 +314,14 @@ All types mirror the [OWL ontology](definitions/ontology.ttl) exactly:
290
314
  ## Repository Structure
291
315
 
292
316
  ```
293
- definitions/ Turtle ontology (OWL) + SHACL shapes
294
- schemas/ JSON Schema for validating .anatomy.yaml files
317
+ definitions/ Turtle ontology (OWL) + SHACL shapes, the style-key roster and
318
+ the registry generated from it, and the lift fixture — all of
319
+ it public API, since exports lists it
295
320
  docs/ API reference (WD404 + WD404.1 + WD404.2 + WD404.3)
296
- examples/ Example anatomy files (YAML + Turtle pairs)
297
- src/ TypeScript types, parser, and transform
321
+ examples/ Example anatomy files (YAML + Turtle pairs), the corpus the
322
+ round-trip and SHACL tests read
323
+ src/ TypeScript types, parser, value grammar, transform and the
324
+ generators
298
325
  ```
299
326
 
300
327
  ## Scope
@@ -313,7 +340,15 @@ component configuration at a position), and **state-scoped styles**
313
340
 
314
341
  ## Design Notes
315
342
 
316
- Styles are modelled as reified key-value tuples (`hasStyle [ styleKey "…" ; styleValue "…" ]`). This keeps the ontology open-ended while remaining lossless. Frequently used style keys may be promoted to first-class datatype properties in a future version.
343
+ Styles are modelled as reified key-value tuples
344
+ (`hasStyle [ a :Style ; :styleKey "…" ; :styleValue "…" ]`), which stays
345
+ lossless whatever the key roster becomes. Since 0.4.0 a tuple whose key takes
346
+ a token also carries `:consumes`, an ordered `rdf:List` of the `dt:` symbols
347
+ it reads: `:styleValue` is the authored spelling kept verbatim as evidence —
348
+ it is where a terminal literal lives, since a literal is not a symbol — and
349
+ `:consumes` is the form a query can walk. The key vocabulary is no longer
350
+ open: `anatomy:styleKey`'s `sh:in` is projected from the registry, so a key
351
+ outside the roster is a SHACL violation rather than a silent addition.
317
352
 
318
353
  Projections and pinned props reuse the same reification idiom (`hasProjection [ projectionType "…" ; projectionField "…" ]`, `hasProp [ propName "…" ; propValue "…" ]`). Projections attach to both nodes and relations — the reified `Relation` is precisely what makes slot-level traversal annotations possible without changing the `Edge` class; pins attach to named nodes only.
319
354
 
@@ -0,0 +1,216 @@
1
+ {
2
+ "$comment": [
3
+ "The committed lift fixture (ADR J §4.1, §8.2). Both TypeScript repositories assert against it, so anatomy-dsl and design-system lift identically and cannot drift apart without a failing test.",
4
+ "`values` is the value lift this package implements: `liftSymbols(value)` over what the YAML parser holds for a style value — a scalar or a sequence of scalars — returning the symbols in fallback order. A primitive is not a symbol: it resolves against nothing by design, so it is never lifted.",
5
+ "`rejections` is the retired notation, with the rule each breaks. Every one throws an AnatomyValueError naming the value and the rule.",
6
+ "`names` is the CSS-name lift of §5.1 step 5d, which design-system implements because it needs S4 to run: a custom-property name to the dotted symbol it lifts to, or null where it names no symbol in any stratum and is kept as consumed with a register row (AT.09). It is DATA here, not an implementation — this package ships it so the two repositories agree on the expected pairs."
7
+ ],
8
+ "values": [
9
+ { "case": "a symbol", "value": "color.text", "symbols": ["color.text"] },
10
+ {
11
+ "case": "a camelCase segment is the symbol's own spelling, not renamed",
12
+ "value": "color.focusRing",
13
+ "symbols": ["color.focusRing"]
14
+ },
15
+ {
16
+ "case": "a numeric segment",
17
+ "value": "dimension.100",
18
+ "symbols": ["dimension.100"]
19
+ },
20
+ {
21
+ "case": "a deep camelCase symbol",
22
+ "value": "typography.weight.semiBold",
23
+ "symbols": ["typography.weight.semiBold"]
24
+ },
25
+ {
26
+ "case": "a channel symbol, consumed by name where the implementation reads the channel",
27
+ "value": "modifier.color.text",
28
+ "symbols": ["modifier.color.text"]
29
+ },
30
+ {
31
+ "case": "the surface channel spelling",
32
+ "value": "surface.color.background",
33
+ "symbols": ["surface.color.background"]
34
+ },
35
+ {
36
+ "case": "the programme's central binding: a channel then the semantic token",
37
+ "value": ["modifier.color.text", "color.text"],
38
+ "symbols": ["modifier.color.text", "color.text"]
39
+ },
40
+ {
41
+ "case": "three ranks, in the reference's own order",
42
+ "value": ["modifier.color.border", "color.border.highlighted", "color.border"],
43
+ "symbols": [
44
+ "modifier.color.border",
45
+ "color.border.highlighted",
46
+ "color.border"
47
+ ]
48
+ },
49
+ {
50
+ "case": "a terminal literal is kept in the value and lifted from none of it",
51
+ "value": ["modifier.color.icon", "modifier.color.text", "currentColor"],
52
+ "symbols": ["modifier.color.icon", "modifier.color.text"]
53
+ },
54
+ {
55
+ "case": "a dimension tail",
56
+ "value": ["dimension.stroke.thickness.large", "2px"],
57
+ "symbols": ["dimension.stroke.thickness.large"]
58
+ },
59
+ {
60
+ "case": "a bare keyword, for a key the registry says takes a primitive",
61
+ "value": "flow",
62
+ "symbols": []
63
+ },
64
+ {
65
+ "case": "a hyphenated CSS keyword, which the reference uses throughout",
66
+ "value": "not-allowed",
67
+ "symbols": []
68
+ },
69
+ { "case": "a number", "value": "1.6", "symbols": [] },
70
+ { "case": "a percentage", "value": "0%", "symbols": [] },
71
+ { "case": "a colour", "value": "#ccc", "symbols": [] },
72
+ {
73
+ "case": "a literal holding a slash between spaces: the slash ban is on symbol spellings, never on literals",
74
+ "value": "1 / -1",
75
+ "symbols": []
76
+ },
77
+ {
78
+ "case": "a literal a bare YAML would read as an alias",
79
+ "value": "*",
80
+ "symbols": []
81
+ },
82
+ {
83
+ "case": "an undeclared name is still a symbol by grammar: whether it resolves is the register's question, not the parser's",
84
+ "value": ["modifier.surface", "color.foreground.primary"],
85
+ "symbols": ["modifier.surface", "color.foreground.primary"]
86
+ },
87
+ {
88
+ "case": "a computed state variable, kept as consumed under the hover.X spelling",
89
+ "value": ["hover.color.foreground.secondary", "color.foreground.secondary.hover"],
90
+ "symbols": [
91
+ "hover.color.foreground.secondary",
92
+ "color.foreground.secondary.hover"
93
+ ]
94
+ }
95
+ ],
96
+ "rejections": [
97
+ { "case": "a slash path", "value": "spacing/medium", "rule": "slashPath" },
98
+ {
99
+ "case": "a slash path with a camelCase-bearing segment",
100
+ "value": "color/focus-ring",
101
+ "rule": "slashPath"
102
+ },
103
+ {
104
+ "case": "a slash path inside a sequence",
105
+ "value": ["modifier/color/foreground", "color/foreground/primary"],
106
+ "rule": "slashPath"
107
+ },
108
+ {
109
+ "case": "the retired optional marker on a slash path",
110
+ "value": "color/surface/button?",
111
+ "rule": "slashPath"
112
+ },
113
+ {
114
+ "case": "the retired optional marker on a dotted symbol",
115
+ "value": "color.surface.button?",
116
+ "rule": "marker"
117
+ },
118
+ { "case": "the reserved root segment", "value": "$root", "rule": "root" },
119
+ {
120
+ "case": "the reserved root segment inside a dotted name",
121
+ "value": "color.$root.text",
122
+ "rule": "root"
123
+ },
124
+ {
125
+ "case": "a bare root segment",
126
+ "value": "color.root.text",
127
+ "rule": "root"
128
+ },
129
+ {
130
+ "case": "a primitive before the end: the sequence is the fallback order and a literal is what the chain ends in",
131
+ "value": ["2px", "dimension.stroke.thickness.large"],
132
+ "rule": "primitiveNotLast"
133
+ },
134
+ {
135
+ "case": "a primitive in the middle",
136
+ "value": ["modifier.color.text", "currentColor", "color.text"],
137
+ "rule": "primitiveNotLast"
138
+ },
139
+ {
140
+ "case": "a one-element sequence: a fallback order needs two",
141
+ "value": ["color.text"],
142
+ "rule": "singleton"
143
+ },
144
+ { "case": "an empty sequence", "value": [], "rule": "empty" },
145
+ { "case": "an empty scalar", "value": "", "rule": "empty" }
146
+ ],
147
+ "names": [
148
+ {
149
+ "case": "a plain semantic token",
150
+ "variable": "--color-text",
151
+ "symbol": "color.text"
152
+ },
153
+ {
154
+ "case": "camelCase is restored from the kebab spelling S4 emits",
155
+ "variable": "--color-focus-ring",
156
+ "symbol": "color.focusRing"
157
+ },
158
+ {
159
+ "case": "the drift spelling S4 also emits for the same symbol",
160
+ "variable": "--color-focusRing",
161
+ "symbol": "color.focusRing"
162
+ },
163
+ {
164
+ "case": "a modifier channel",
165
+ "variable": "--modifier-color-text",
166
+ "symbol": "modifier.color.text"
167
+ },
168
+ {
169
+ "case": "a surface channel",
170
+ "variable": "--surface-color-background",
171
+ "symbol": "surface.color.background"
172
+ },
173
+ {
174
+ "case": "a channel with no base symbol: kept as consumed, register row X7",
175
+ "variable": "--modifier-surface",
176
+ "symbol": "modifier.surface",
177
+ "resolves": false
178
+ },
179
+ {
180
+ "case": "a state variant of a channel: register row X5",
181
+ "variable": "--modifier-surface-hover",
182
+ "symbol": "modifier.surface.hover",
183
+ "resolves": false
184
+ },
185
+ {
186
+ "case": "a computed state variable: the double dash lifts to a dot, register row X15",
187
+ "variable": "--hover--color-foreground-secondary",
188
+ "symbol": "hover.color.foreground.secondary",
189
+ "resolves": false
190
+ },
191
+ {
192
+ "case": "a component-local variable is followed through its :root definition and replaced by what it consumes",
193
+ "variable": "--button-color-text",
194
+ "symbol": "color.text",
195
+ "via": "--button-color-text: var(--color-text)"
196
+ },
197
+ {
198
+ "case": "a component-local variable whose definition is itself a chain",
199
+ "variable": "--button-color-background",
200
+ "symbol": "modifier.color.foreground.primary",
201
+ "via": "--button-color-background: var(--modifier-color-foreground-primary, var(--color-foreground-primary))"
202
+ },
203
+ {
204
+ "case": "an undeclared name: kept as consumed with a comment, register row X1",
205
+ "variable": "--motion-duration-fast",
206
+ "symbol": "motion.duration.fast",
207
+ "resolves": false
208
+ },
209
+ {
210
+ "case": "a composite typography slot no stratum declares",
211
+ "variable": "--typography-text-secondary-font-size",
212
+ "symbol": "typography.text.secondary.fontSize",
213
+ "resolves": false
214
+ }
215
+ ]
216
+ }
@@ -1,17 +1,25 @@
1
- @prefix anatomy: <http://anatomy-dsl.example.org/ontology#> .
1
+ @prefix anatomy: <https://anatomy.canonical.com/> .
2
2
  @prefix owl: <http://www.w3.org/2002/07/owl#> .
3
3
  @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
4
4
  @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
5
5
  @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
6
6
  @prefix skos: <http://www.w3.org/2004/02/skos/core#> .
7
7
  @prefix graphql: <https://pragma.canonical.com/graphql#> .
8
+ # The two graphs this one is read beside. `dt:` is the token graph
9
+ # (@canonical/token-ontology): a style value's symbols are dt:TokenSymbol
10
+ # individuals, and anatomy:consumes names them. `ds:` is the design system
11
+ # graph (canonical/design-system), whose ds:TokenBinding record reuses
12
+ # anatomy:styleKey and anatomy:styleState — which is why neither carries an
13
+ # rdfs:domain any more.
14
+ @prefix dt: <https://dt.canonical.com/> .
15
+ @prefix ds: <https://ds.canonical.com/> .
8
16
 
9
17
  # ═══════════════════════════════════════════════════════════════
10
18
  # ONTOLOGY DECLARATION
11
19
  # ═══════════════════════════════════════════════════════════════
12
20
 
13
- <http://anatomy-dsl.example.org/ontology> a owl:Ontology ;
14
- owl:versionInfo "0.3.2" ;
21
+ <https://anatomy.canonical.com/> a owl:Ontology ;
22
+ owl:versionInfo "0.4.0" ;
15
23
  rdfs:label "Anatomy DSL Ontology" ;
16
24
  rdfs:comment "Compliant with OWL DL profile" ;
17
25
  skos:definition """Meta-model for the Anatomy DSL — a YAML-based language
@@ -101,6 +109,57 @@ anatomy:SwitchCase a owl:Class ;
101
109
  rdfs:label "Switch Case" ;
102
110
  skos:definition "One alternative within a Switch." .
103
111
 
112
+ # ═══════════════════════════════════════════════════════════════
113
+ # THE STYLE-KEY REGISTRY
114
+ # ═══════════════════════════════════════════════════════════════
115
+ #
116
+ # The individuals live in registry.ttl, projected from the roster in
117
+ # style-keys.yaml by `bun run generate:registry`; the same roster closes
118
+ # anatomy:styleKey's sh:in in shapes.ttl. The vocabulary was open until
119
+ # 0.4.0 — "about 14 keys" documented against 39 the corpus used — and an
120
+ # open roster is what admitted `background.color` on a component whose
121
+ # stylesheet says `background-color`.
122
+
123
+ anatomy:StyleKey a owl:Class ;
124
+ rdfs:label "Style Key" ;
125
+ skos:definition """One canonical style key: a platform-agnostic property
126
+ the anatomy may bind. The roster was measured, not designed: it is the
127
+ right-hand column of a table whose left-hand column was every CSS property
128
+ the reference implementations bind, so a key exists because an
129
+ implementation binds the property, never because the vocabulary looked
130
+ incomplete. registry.ttl states where that measurement lives.""" ;
131
+ skos:example "appearance.background" ,
132
+ "spacing.internal.inline.start" ,
133
+ "typography.color" .
134
+
135
+ anatomy:valueKind a owl:DatatypeProperty ;
136
+ rdfs:label "value kind" ;
137
+ rdfs:domain anatomy:StyleKey ;
138
+ rdfs:range xsd:string ;
139
+ skos:definition """What the key admits: "token" (a symbol, or a list of
140
+ symbols ending at most in one primitive), "primitive" (a literal the
141
+ implementation writes as it stands), or "either". Measured from the
142
+ reference declarations mapped onto the key when the roster was taken —
143
+ all reading a var() is token, none is primitive, mixed is either. An
144
+ "either" key compiles to an sh:or in StyleShape: it carries
145
+ anatomy:consumes, or it carries a primitive styleValue.""" ;
146
+ skos:example "token" , "primitive" , "either" .
147
+
148
+ anatomy:tokenNamespace a owl:DatatypeProperty ;
149
+ rdfs:label "token namespace" ;
150
+ rdfs:domain anatomy:StyleKey ;
151
+ rdfs:range xsd:string ;
152
+ skos:definition """A dotted prefix, ending in a dot, whose symbols the key
153
+ admits — matched against a symbol's dotted NAME with STRSTARTS, never
154
+ against its IRI. Multi-valued, and a key whose base namespace is `N.`
155
+ carries `N.`, `modifier.N.` and `surface.N.`: a channel of a symbol in
156
+ `N.` is exactly what the anatomy consumes where the implementation reads
157
+ the channel, so without the channel spellings every channel binding in
158
+ the corpus would be a namespace violation. It is the single declaration of
159
+ which symbols a key admits, and the inverse question — which keys admit
160
+ this symbol — is the query over it (dt:appliesTo is retired).""" ;
161
+ skos:example "color." , "modifier.color." , "spacing." .
162
+
104
163
  # ═══════════════════════════════════════════════════════════════
105
164
  # OBJECT PROPERTIES
106
165
  # ═══════════════════════════════════════════════════════════════
@@ -165,6 +224,31 @@ anatomy:hasProp a owl:ObjectProperty ;
165
224
  rdfs:range anatomy:Prop ;
166
225
  skos:definition "Attaches a pinned prop value to a named node. Named nodes only: anonymous nodes have no prop surface to pin." .
167
226
 
227
+ anatomy:consumes a owl:ObjectProperty ;
228
+ rdfs:label "consumes" ;
229
+ rdfs:domain anatomy:Style ;
230
+ rdfs:range rdf:List ;
231
+ skos:definition """The token symbols this style binding consumes, as an
232
+ ordered rdf:List whose elements are dt:TokenSymbol individuals in FALLBACK
233
+ ORDER: rdf:first is the primary symbol, and each rest is what the
234
+ implementation falls back to when the one before it is undefined. It is
235
+ the RDF form of the authored value — a one-to-one transcription of the
236
+ implementation's `var(a, var(b, …))` chain — and the reason the list and
237
+ not a single symbol is that the fallback is a per-slot choice: a
238
+ component's background and its text fall back differently, so no rule
239
+ declared once on a symbol can carry it.
240
+
241
+ A slot consumes a CHANNEL by name where the implementation reads the
242
+ channel (`modifier.color.text`) and the semantic token by name where it
243
+ reads the token; nothing is inferred from the spelling. A terminal
244
+ literal the chain ends in (`currentColor`, `0`, `1em`) is NOT an element
245
+ here — it resolves against nothing by design — and is carried in
246
+ anatomy:styleValue, which keeps the authored spelling verbatim as
247
+ evidence. Whether a key carries this at all is anatomy:valueKind's
248
+ business, and whether a symbol RESOLVES is the consumer's: a name no
249
+ stratum declares is kept as consumed with a register row.""" ;
250
+ skos:example "( dt:modifier.color.text dt:color.text )" .
251
+
168
252
  # ═══════════════════════════════════════════════════════════════
169
253
  # DATATYPE PROPERTIES
170
254
  # ═══════════════════════════════════════════════════════════════
@@ -217,11 +301,17 @@ anatomy:slotName a owl:DatatypeProperty ;
217
301
 
218
302
  anatomy:styleKey a owl:DatatypeProperty ;
219
303
  rdfs:label "style key" ;
220
- rdfs:domain anatomy:Style ;
304
+ # No rdfs:domain, deliberately. design-system's ds:TokenBinding record
305
+ # (one per consumed symbol per rank) carries the key and the state of the
306
+ # tuple it was derived from, so a domain of anatomy:Style would type every
307
+ # record an anatomy:Style. The constraint that a tuple carries exactly one
308
+ # key lives in shapes.ttl, where it belongs. Stays an
309
+ # owl:DatatypeProperty: pragma asserts every term a lookup selects is a
310
+ # declared datatype or object property.
221
311
  rdfs:range xsd:string ;
222
312
  skos:definition "Platform-agnostic style property identifier." ;
223
313
  skos:example "layout.type" ,
224
- "spacing.internal" ,
314
+ "spacing.internal.inline.start" ,
225
315
  "appearance.background" ,
226
316
  "typography.weight" .
227
317
 
@@ -229,15 +319,24 @@ anatomy:styleValue a owl:DatatypeProperty ;
229
319
  rdfs:label "style value" ;
230
320
  rdfs:domain anatomy:Style ;
231
321
  rdfs:range xsd:string ;
232
- skos:definition "Value for a style property." ;
233
- skos:example "stack" ,
234
- "spacing/medium" ,
235
- "color/surface/primary" ,
236
- "color/surface/button?" .
322
+ skos:definition """The AUTHORED SPELLING of the value, verbatim: a scalar
323
+ as written, a sequence in its flow form. It is the evidence of what the
324
+ reference says, and it is kept even where anatomy:consumes carries the
325
+ same symbols in a form a query can walk — the terminal literal of a
326
+ fallback chain lives only here, because a literal is not a symbol.
327
+
328
+ A value is a symbol, a primitive, or a sequence whose elements are
329
+ symbols with at most one primitive and only as the last element. The
330
+ slash-delimited path and the trailing '?' marker are RETIRED: an element
331
+ that resolves nowhere is a register row and a comment, not a sigil.""" ;
332
+ skos:example "flow" ,
333
+ "dimension.100" ,
334
+ "[modifier.color.text, color.text]" ,
335
+ "[modifier.color.icon, modifier.color.text, currentColor]" .
237
336
 
238
337
  anatomy:styleState a owl:DatatypeProperty ;
239
338
  rdfs:label "style state" ;
240
- rdfs:domain anatomy:Style ;
339
+ # No rdfs:domain, for the reason anatomy:styleKey gives.
241
340
  rdfs:range xsd:string ;
242
341
  skos:definition """Interaction state this style value applies in. Absent
243
342
  means the default state. Closed vocabulary (hover, active, focus,