@canonical/pragma-cli 0.36.0 → 0.38.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 +3 -2
- package/bundled-skills/adoption-a1-styles/SKILL.md +24 -0
- package/bundled-skills/adoption-a2-components/SKILL.md +22 -6
- package/bundled-skills/anatomy-author/ANATOMY_DSL_SPEC.md +1397 -327
- package/bundled-skills/anatomy-author/SKILL.md +840 -137
- package/bundled-skills/design-auditor/SKILL.md +133 -9
- package/bundled-skills/implement-component/SKILL.md +192 -23
- package/bundled-skills/specify-component/SKILL.md +124 -12
- package/bundled-skills/specify-pattern/SKILL.md +47 -8
- package/dist/package.json +23 -20
- package/dist/pragma.conf.js +963 -40
- package/dist/pragma.conf.js.map +1 -1
- package/dist/src/capabilities/capabilities/catalog.js +14 -5
- package/dist/src/capabilities/capabilities/catalog.js.map +1 -1
- package/dist/src/capabilities/capabilities/hints.js +33 -5
- package/dist/src/capabilities/capabilities/hints.js.map +1 -1
- package/dist/src/capabilities/config/show.render.js +30 -2
- package/dist/src/capabilities/config/show.render.js.map +1 -1
- package/dist/src/capabilities/create/create.verb.js +2 -1
- package/dist/src/capabilities/create/create.verb.js.map +1 -1
- package/dist/src/capabilities/create/createSurface.generated.js +23 -1
- package/dist/src/capabilities/create/createSurface.generated.js.map +1 -1
- package/dist/src/capabilities/doctor/checks/checkNodeVersion.js +11 -6
- package/dist/src/capabilities/doctor/checks/checkNodeVersion.js.map +1 -1
- package/dist/src/capabilities/doctor/checks/harnessInventory.js +1 -1
- package/dist/src/capabilities/doctor/checks/harnessInventory.js.map +1 -1
- package/dist/src/capabilities/doctor/checks/isSupportedNodeVersion.js +38 -0
- package/dist/src/capabilities/doctor/checks/isSupportedNodeVersion.js.map +1 -0
- package/dist/src/capabilities/doctor/checks/targetHealth.js +104 -14
- package/dist/src/capabilities/doctor/checks/targetHealth.js.map +1 -1
- package/dist/src/capabilities/graph/connect.render.js +101 -0
- package/dist/src/capabilities/graph/connect.render.js.map +1 -0
- package/dist/src/capabilities/graph/connect.types.js +21 -0
- package/dist/src/capabilities/graph/connect.types.js.map +1 -0
- package/dist/src/capabilities/graph/connect.verb.js +147 -0
- package/dist/src/capabilities/graph/connect.verb.js.map +1 -0
- package/dist/src/capabilities/graph/index.js +5 -3
- package/dist/src/capabilities/graph/index.js.map +1 -1
- package/dist/src/capabilities/graph/queryText.js +206 -0
- package/dist/src/capabilities/graph/queryText.js.map +1 -0
- package/dist/src/capabilities/graph/relationIndex.js +302 -0
- package/dist/src/capabilities/graph/relationIndex.js.map +1 -0
- package/dist/src/capabilities/graph/runConnect.js +456 -0
- package/dist/src/capabilities/graph/runConnect.js.map +1 -0
- package/dist/src/capabilities/graph/runQuery.js +21 -4
- package/dist/src/capabilities/graph/runQuery.js.map +1 -1
- package/dist/src/capabilities/setup/buildPlan.js +6 -2
- package/dist/src/capabilities/setup/buildPlan.js.map +1 -1
- package/dist/src/capabilities/setup/operations/index.js +7 -2
- package/dist/src/capabilities/setup/operations/index.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupCompletions.js +6 -2
- package/dist/src/capabilities/setup/operations/setupCompletions.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupConfig.js +4 -2
- package/dist/src/capabilities/setup/operations/setupConfig.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupGenerator.js +72 -14
- package/dist/src/capabilities/setup/operations/setupGenerator.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupLsp.js +372 -62
- package/dist/src/capabilities/setup/operations/setupLsp.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupMcp.js +150 -5
- package/dist/src/capabilities/setup/operations/setupMcp.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupSkills.js +4 -2
- package/dist/src/capabilities/setup/operations/setupSkills.js.map +1 -1
- package/dist/src/capabilities/setup/operations/writability.js +114 -0
- package/dist/src/capabilities/setup/operations/writability.js.map +1 -0
- package/dist/src/capabilities/setup/plan.js.map +1 -1
- package/dist/src/capabilities/setup/setup.verb.js +80 -31
- package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
- package/dist/src/capabilities/setup/targets.js +124 -42
- package/dist/src/capabilities/setup/targets.js.map +1 -1
- package/dist/src/capabilities/shared/index.js +1 -1
- package/dist/src/capabilities/shared/index.js.map +1 -1
- package/dist/src/constants.js +7 -1
- package/dist/src/constants.js.map +1 -1
- package/dist/src/kernel/error/renderError.js +3 -2
- package/dist/src/kernel/error/renderError.js.map +1 -1
- package/dist/src/kernel/error/validOptions.js +41 -0
- package/dist/src/kernel/error/validOptions.js.map +1 -0
- package/dist/src/kernel/packs/compile.js +90 -6
- package/dist/src/kernel/packs/compile.js.map +1 -1
- package/dist/src/kernel/packs/cursor.js +135 -0
- package/dist/src/kernel/packs/cursor.js.map +1 -0
- package/dist/src/kernel/packs/graphql/buildLookupDocument.js +9 -2
- package/dist/src/kernel/packs/graphql/buildLookupDocument.js.map +1 -1
- package/dist/src/kernel/packs/paging.js +108 -0
- package/dist/src/kernel/packs/paging.js.map +1 -0
- package/dist/src/kernel/packs/renderPack.js +154 -5
- package/dist/src/kernel/packs/renderPack.js.map +1 -1
- package/dist/src/kernel/packs/resolveEntity.js +118 -19
- package/dist/src/kernel/packs/resolveEntity.js.map +1 -1
- package/dist/src/kernel/packs/runBodies.js +156 -18
- package/dist/src/kernel/packs/runBodies.js.map +1 -1
- package/dist/src/kernel/packs/schema.js +160 -12
- package/dist/src/kernel/packs/schema.js.map +1 -1
- package/dist/src/kernel/packs/sparql/authorQuery.js +227 -0
- package/dist/src/kernel/packs/sparql/authorQuery.js.map +1 -0
- package/dist/src/kernel/packs/sparql/buildListQuery.js +251 -0
- package/dist/src/kernel/packs/sparql/buildListQuery.js.map +1 -0
- package/dist/src/kernel/packs/sparql/buildLookupQuery.js +125 -22
- package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -1
- package/dist/src/kernel/packs/sparql/filterValues.js +145 -0
- package/dist/src/kernel/packs/sparql/filterValues.js.map +1 -0
- package/dist/src/kernel/packs/sparql/index.js +24 -14
- package/dist/src/kernel/packs/sparql/index.js.map +1 -1
- package/dist/src/kernel/packs/sparql/runSelect.js +10 -4
- package/dist/src/kernel/packs/sparql/runSelect.js.map +1 -1
- package/dist/src/kernel/packs/sparql/searchTerm.js +25 -0
- package/dist/src/kernel/packs/sparql/searchTerm.js.map +1 -0
- package/dist/src/kernel/packs/storyRules.js +151 -0
- package/dist/src/kernel/packs/storyRules.js.map +1 -0
- package/dist/src/kernel/packs/tierScope.js +310 -0
- package/dist/src/kernel/packs/tierScope.js.map +1 -0
- package/dist/src/kernel/packs/types.js +80 -0
- package/dist/src/kernel/packs/types.js.map +1 -1
- package/dist/src/kernel/project/cli/buildProgram.js +10 -1
- package/dist/src/kernel/project/cli/buildProgram.js.map +1 -1
- package/dist/src/kernel/project/cli/dispatch.js +104 -3
- package/dist/src/kernel/project/cli/dispatch.js.map +1 -1
- package/dist/src/kernel/project/cli/rootHelp.js +8 -5
- package/dist/src/kernel/project/cli/rootHelp.js.map +1 -1
- package/dist/src/kernel/project/cli/suggestNames.js +31 -7
- package/dist/src/kernel/project/cli/suggestNames.js.map +1 -1
- package/dist/src/kernel/project/mcp/registerVerb.js +5 -1
- package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -1
- package/dist/src/kernel/render/renderers.js +34 -2
- package/dist/src/kernel/render/renderers.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js +2 -3
- package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js +1 -1
- package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.js +4 -0
- package/dist/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.js.map +1 -0
- package/dist/src/kernel/runtime/graphpack/embedded.js +13 -6
- package/dist/src/kernel/runtime/graphpack/embedded.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/read.js +17 -10
- package/dist/src/kernel/runtime/graphpack/read.js.map +1 -1
- package/dist/src/kernel/runtime/loadSession.js +3 -1
- package/dist/src/kernel/runtime/loadSession.js.map +1 -1
- package/dist/src/kernel/runtime/probe.js +1 -1
- package/dist/src/kernel/runtime/probe.js.map +1 -1
- package/dist/src/kernel/runtime/readEntity.js +19 -4
- package/dist/src/kernel/runtime/readEntity.js.map +1 -1
- package/dist/src/kernel/runtime/refs/parseRef.js +17 -1
- package/dist/src/kernel/runtime/refs/parseRef.js.map +1 -1
- package/dist/src/kernel/runtime/refs/resolve.js +38 -5
- package/dist/src/kernel/runtime/refs/resolve.js.map +1 -1
- package/dist/src/kernel/spec/emitReference.js +1 -1
- package/dist/src/kernel/spec/emitReference.js.map +1 -1
- package/package.json +23 -20
- package/pragma.conf.ts +983 -40
- package/src/capabilities/block.parity.test.ts +207 -27
- package/src/capabilities/block.shipped.exec.test.ts +156 -14
- package/src/capabilities/block.tierRank.exec.test.ts +46 -9
- package/src/capabilities/capabilities/catalog.ts +14 -5
- package/src/capabilities/capabilities/hints.ts +40 -5
- package/src/capabilities/config/collectConfigShow.test.ts +1 -0
- package/src/capabilities/config/set.test.ts +24 -25
- package/src/capabilities/config/show.render.test.ts +9 -4
- package/src/capabilities/config/show.render.ts +30 -2
- package/src/capabilities/create/byteEquality.test.ts +28 -1
- package/src/capabilities/create/create.test.ts +1 -0
- package/src/capabilities/create/create.verb.ts +2 -1
- package/src/capabilities/create/createGrammar.test.ts +4 -1
- package/src/capabilities/create/createSurface.generated.ts +23 -1
- package/src/capabilities/create/crossCli.subprocess.test.ts +10 -3
- package/src/capabilities/distribution.parity.test.ts +12 -11
- package/src/capabilities/distribution.test.ts +77 -7
- package/src/capabilities/doctor/checks/checkNodeVersion.test.ts +49 -0
- package/src/capabilities/doctor/checks/checkNodeVersion.ts +11 -7
- package/src/capabilities/doctor/checks/harnessInventory.test.ts +13 -6
- package/src/capabilities/doctor/checks/harnessInventory.ts +8 -4
- package/src/capabilities/doctor/checks/isSupportedNodeVersion.test.ts +111 -0
- package/src/capabilities/doctor/checks/isSupportedNodeVersion.ts +34 -0
- package/src/capabilities/doctor/checks/mcpCommand.test.ts +7 -1
- package/src/capabilities/doctor/checks/targetHealth.ts +114 -13
- package/src/capabilities/doctor/doctor.test.ts +327 -36
- package/src/capabilities/emptyRecovery.test.ts +84 -28
- package/src/capabilities/graph/connect.render.ts +141 -0
- package/src/capabilities/graph/connect.shipped.exec.test.ts +370 -0
- package/src/capabilities/graph/connect.test.ts +361 -0
- package/src/capabilities/graph/connect.types.ts +136 -0
- package/src/capabilities/graph/connect.verb.ts +165 -0
- package/src/capabilities/graph/index.ts +5 -3
- package/src/capabilities/graph/query.shipped.exec.test.ts +66 -0
- package/src/capabilities/graph/query.test.ts +17 -0
- package/src/capabilities/graph/queryText.test.ts +121 -0
- package/src/capabilities/graph/queryText.ts +240 -0
- package/src/capabilities/graph/relationIndex.ts +372 -0
- package/src/capabilities/graph/runConnect.ts +577 -0
- package/src/capabilities/graph/runQuery.ts +21 -6
- package/src/capabilities/lazy.test.ts +32 -2
- package/src/capabilities/listBudget.shipped.exec.test.ts +239 -0
- package/src/capabilities/listLookup.shipped.exec.test.ts +387 -0
- package/src/capabilities/listQuery.shipped.exec.test.ts +400 -0
- package/src/capabilities/resources/resources.test.ts +38 -2
- package/src/capabilities/setup/buildPlan.ts +7 -1
- package/src/capabilities/setup/operations/index.ts +14 -0
- package/src/capabilities/setup/operations/setupCompletions.ts +9 -2
- package/src/capabilities/setup/operations/setupConfig.ts +7 -2
- package/src/capabilities/setup/operations/setupGenerator.ts +93 -15
- package/src/capabilities/setup/operations/setupLsp.ts +544 -85
- package/src/capabilities/setup/operations/setupMcp.ts +212 -4
- package/src/capabilities/setup/operations/setupSkills.ts +7 -2
- package/src/capabilities/setup/operations/writability.test.ts +151 -0
- package/src/capabilities/setup/operations/writability.ts +135 -0
- package/src/capabilities/setup/plan.ts +11 -0
- package/src/capabilities/setup/setup.test.ts +992 -16
- package/src/capabilities/setup/setup.verb.ts +84 -36
- package/src/capabilities/setup/targets.ts +152 -48
- package/src/capabilities/setup/types.ts +41 -5
- package/src/capabilities/shared/index.ts +1 -0
- package/src/capabilities/sources/sources.test.ts +55 -0
- package/src/capabilities/surface.test.ts +59 -11
- package/src/capabilities/tier.parity.test.ts +2 -2
- package/src/capabilities/token.parity.test.ts +784 -0
- package/src/capabilities/tokenGraph.cli.test.ts +279 -0
- package/src/capabilities/tokenGraph.shipped.exec.test.ts +653 -0
- package/src/capabilities/variable.parity.test.ts +448 -0
- package/src/constants.ts +8 -0
- package/src/kernel/completion/shellDrive.test.ts +32 -30
- package/src/kernel/config/defaults.test.ts +21 -2
- package/src/kernel/config/readConfig.test.ts +1 -1
- package/src/kernel/error/renderError.ts +3 -2
- package/src/kernel/error/validOptions.test.ts +57 -0
- package/src/kernel/error/validOptions.ts +45 -0
- package/src/kernel/packs/collect.test.ts +2 -0
- package/src/kernel/packs/compile.test.ts +188 -15
- package/src/kernel/packs/compile.ts +109 -14
- package/src/kernel/packs/cursor.ts +135 -0
- package/src/kernel/packs/filteredEmpty.test.ts +213 -0
- package/src/kernel/packs/graphql/buildLookupDocument.ts +16 -9
- package/src/kernel/packs/graphqlSource.test.ts +152 -0
- package/src/kernel/packs/listNotice.test.ts +156 -0
- package/src/kernel/packs/paging.ts +110 -0
- package/src/kernel/packs/renderPack.ts +186 -13
- package/src/kernel/packs/resolveEntity.test.ts +62 -3
- package/src/kernel/packs/resolveEntity.ts +186 -26
- package/src/kernel/packs/runBodies.ts +196 -26
- package/src/kernel/packs/schema.ts +180 -13
- package/src/kernel/packs/sparql/authorQuery.test.ts +179 -0
- package/src/kernel/packs/sparql/authorQuery.ts +257 -0
- package/src/kernel/packs/sparql/buildListQuery.test.ts +344 -0
- package/src/kernel/packs/sparql/buildListQuery.ts +344 -0
- package/src/kernel/packs/sparql/buildLookupQuery.test.ts +138 -0
- package/src/kernel/packs/sparql/buildLookupQuery.ts +140 -20
- package/src/kernel/packs/sparql/filterValues.test.ts +86 -0
- package/src/kernel/packs/sparql/filterValues.ts +182 -0
- package/src/kernel/packs/sparql/index.ts +30 -14
- package/src/kernel/packs/sparql/runSelect.test.ts +63 -0
- package/src/kernel/packs/sparql/runSelect.ts +42 -2
- package/src/kernel/packs/sparql/searchTerm.ts +29 -0
- package/src/kernel/packs/storyRules.ts +180 -0
- package/src/kernel/packs/tierScope.test.ts +487 -0
- package/src/kernel/packs/tierScope.ts +409 -0
- package/src/kernel/packs/toolDescription.test.ts +13 -10
- package/src/kernel/packs/types.ts +311 -9
- package/src/kernel/project/cli/buildProgram.ts +17 -1
- package/src/kernel/project/cli/dispatch.test.ts +87 -0
- package/src/kernel/project/cli/dispatch.ts +114 -4
- package/src/kernel/project/cli/rootHelp.ts +8 -5
- package/src/kernel/project/cli/suggest.test.ts +35 -0
- package/src/kernel/project/cli/suggestNames.ts +34 -7
- package/src/kernel/project/mcp/registerVerb.ts +5 -1
- package/src/kernel/render/contracts.ts +2 -0
- package/src/kernel/render/prefixes.test.ts +2 -0
- package/src/kernel/render/renderers.test.ts +43 -0
- package/src/kernel/render/renderers.ts +34 -1
- package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +2 -3
- package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +1 -1
- package/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.ts +3 -0
- package/src/kernel/runtime/graphpack/embedded.ts +14 -6
- package/src/kernel/runtime/graphpack/graphpack.test.ts +31 -5
- package/src/kernel/runtime/graphpack/read.ts +17 -13
- package/src/kernel/runtime/loadSession.ts +3 -1
- package/src/kernel/runtime/probe.ts +1 -1
- package/src/kernel/runtime/readEntity.ts +19 -4
- package/src/kernel/runtime/refs/parseRef.test.ts +49 -1
- package/src/kernel/runtime/refs/parseRef.ts +25 -1
- package/src/kernel/runtime/refs/resolve.ts +39 -5
- package/src/kernel/runtime/types.ts +30 -0
- package/src/kernel/spec/emitReference.ts +1 -1
- package/src/kernel/spec/surfaceConformance.test.ts +2 -2
- package/src/kernel/spec/types.ts +17 -0
- package/src/testing/PARITY_GAPS.ts +4 -4
- package/src/testing/behavioral/agentSession.mcp.test.ts +43 -39
- package/src/testing/behavioral/completion.test.ts +7 -6
- package/src/testing/behavioral/disclosure.test.ts +39 -37
- package/src/testing/behavioral/errorMatrix.mcp.test.ts +82 -64
- package/src/testing/behavioral/errorMatrix.storeless.test.ts +11 -10
- package/src/testing/behavioral/journeys.cli.test.ts +71 -32
- package/src/testing/behavioral/journeys.defaultPack.test.ts +6 -4
- package/src/testing/behavioral/journeys.firstInstall.test.ts +70 -5
- package/src/testing/behavioral/journeys.mcpLifecycle.mcp.test.ts +4 -3
- package/src/testing/behavioral/journeys.packageStories.test.ts +2 -1
- package/src/testing/behavioral/mcpSurface.test.ts +28 -25
- package/src/testing/behavioral/parity.test.ts +25 -23
- package/src/testing/eval/__snapshots__/eval.test.ts.snap +20 -0
- package/src/testing/eval/cases/effectful.ts +7 -1
- package/src/testing/eval/cases/readNouns.ts +100 -11
- package/src/testing/fixtures/blockGraph.ts +89 -5
- package/src/testing/fixtures/connectGraph.ts +159 -0
- package/src/testing/fixtures/graph/canonical.ts +128 -32
- package/src/testing/helpers/fsProbe.ts +88 -0
- package/src/testing/helpers/runCli.test.ts +44 -0
- package/src/testing/helpers/runCli.ts +27 -3
- package/src/testing/perf/budgets.test.ts +3 -1
- package/src/testing/perf/budgets.ts +138 -54
- package/dist/src/kernel/packs/sparql/applyFilters.js +0 -167
- package/dist/src/kernel/packs/sparql/applyFilters.js.map +0 -1
- package/dist/src/kernel/packs/sparql/applySearch.js +0 -25
- package/dist/src/kernel/packs/sparql/applySearch.js.map +0 -1
- package/src/kernel/packs/sparql/applyFilters.ts +0 -203
- package/src/kernel/packs/sparql/applySearch.ts +0 -33
|
@@ -23,6 +23,9 @@ The Anatomy DSL represents design system components as YAML trees with:
|
|
|
23
23
|
- **Anonymous nodes**: Structural elements with roles (`content wrapper`)
|
|
24
24
|
- **Edges**: Parent-child relationships with cardinality and slot names
|
|
25
25
|
- **Styles**: Platform-agnostic properties using CTI-inspired keys
|
|
26
|
+
- **Projections**: Graph-data bindings — which entity type a subtree is a view over, and which field each position renders
|
|
27
|
+
- **Props**: Pinned prop values fixing a referenced component's configuration at one position (the icon case)
|
|
28
|
+
- **Interaction states**: Style values scoped to a state via an `@state` key suffix (`appearance.background@hover`)
|
|
26
29
|
|
|
27
30
|
This skill helps you author these specifications correctly and consistently.
|
|
28
31
|
|
|
@@ -50,7 +53,27 @@ Decision gates — in collaboration, pause at each and resolve it WITH the perso
|
|
|
50
53
|
The full specification ships beside this file as `ANATOMY_DSL_SPEC.md` in the
|
|
51
54
|
installed skill folder — `pragma skill lookup anatomy-author` renders only this
|
|
52
55
|
SKILL.md, so open the sibling file directly when you need the complete spec (the
|
|
53
|
-
appendix below carries its type system).
|
|
56
|
+
appendix below carries its type system). That file is a verbatim copy of
|
|
57
|
+
`docs/api-reference.md` in canonical/anatomy-dsl, which is the normative source for
|
|
58
|
+
the NOTATION: the grammar, the type system, what parses and what does not. Where this
|
|
59
|
+
SKILL.md and the sibling spec disagree about the notation, the spec wins, and the
|
|
60
|
+
disagreement is a bug to report. Reach for it for the parts too long to restate
|
|
61
|
+
here — the worked projections gallery, the derived-fragment contract, and the
|
|
62
|
+
full example set.
|
|
63
|
+
|
|
64
|
+
The spec's reach stops at the grammar, and one boundary is worth stating because it
|
|
65
|
+
reads like a contradiction otherwise. The spec's Deferred Items and Out of Scope
|
|
66
|
+
tables list what the GRAMMAR does not provide — a construct, a keyword, a schema. They
|
|
67
|
+
do not rule on how an author models something the grammar has no construct for. Where a
|
|
68
|
+
modelling convention is what is at issue — a pseudo-element drawn by the stylesheet, a
|
|
69
|
+
modifier family a component supports — this SKILL.md is the authority, and it says so
|
|
70
|
+
at each of those places. The authored corpus follows this file.
|
|
71
|
+
|
|
72
|
+
**And this file, in the design-system repository, is the authority over the copy
|
|
73
|
+
`pragma skill lookup anatomy-author` prints.** That copy is bundled into a pragma
|
|
74
|
+
release and lags the repository until the next one, so a sentence you find there and
|
|
75
|
+
cannot find here is old text, not a second opinion. Read the repository file when the
|
|
76
|
+
two differ.
|
|
54
77
|
|
|
55
78
|
## Opening move: ask, or offer the tutorial
|
|
56
79
|
|
|
@@ -163,18 +186,113 @@ pragma block lookup Card # full spec: anatomy, modifiers,
|
|
|
163
186
|
pragma graph inspect ds:global.component.card # every triple on the entity
|
|
164
187
|
```
|
|
165
188
|
|
|
166
|
-
A bare name
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
189
|
+
A bare name resolves `ds:name` globally, so where several tiers carry the name the
|
|
190
|
+
lookup answers with EVERY one of them: the full writeups concatenated, each under its
|
|
191
|
+
own `## <Name>` heading with its own `- Tier:` line. `pragma block lookup Chip` returns
|
|
192
|
+
the global Chip and the launchpad Chip in one answer, and they are DIFFERENT blocks —
|
|
193
|
+
separately authored, with different properties and different anatomies — not repeats of
|
|
194
|
+
one. So the read is not "confirm the tier it picked": scroll the whole answer, read
|
|
195
|
+
every `- Tier:` line, count the headings, and pick the block you were asked to write.
|
|
196
|
+
Stopping at the first heading is how a second, unrelated component's anatomy gets
|
|
197
|
+
transcribed by mistake.
|
|
198
|
+
|
|
199
|
+
`block lookup` also takes a `ds:` IRI, which answers with exactly one block and skips
|
|
200
|
+
the disambiguation: `pragma block lookup ds:global.component.chip`. The bare dotted
|
|
201
|
+
name is NOT a key — a `block list` row prints `ds:apps_lxd.component.meter`, and
|
|
202
|
+
dropping the `ds:` prefix comes back `ENTITY_NOT_FOUND`. A glob (`*chip*`) fans out the
|
|
203
|
+
same way a shared display name does, one block per tier.
|
|
204
|
+
|
|
205
|
+
The name query in Workflow §1 lists every tier carrying the name with each block's IRI,
|
|
206
|
+
which is the read when you want the list rather than the writeups. If the name has no
|
|
207
|
+
row at all, the block may be a Group — `block list` covers no groups; the group
|
|
208
|
+
pre-step in Workflow §1 lists them.
|
|
209
|
+
|
|
210
|
+
### The token graph answers whether a symbol exists
|
|
211
|
+
|
|
212
|
+
Every symbol you are about to write is queryable, and guessing is not authoring. Two
|
|
213
|
+
questions come up while transcribing a stylesheet — does this symbol exist, and what
|
|
214
|
+
does this variable stand for — and three commands answer them:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
pragma token lookup color.text # one symbol in full: type, description, every
|
|
218
|
+
# definition behind it, the families that may
|
|
219
|
+
# rebind it, and what it resolves to
|
|
220
|
+
pragma token list --search focus # which symbols exist around a word
|
|
221
|
+
pragma variable lookup modifier-color-text # what a CSS variable stands for
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`token lookup` answers with the symbol's `- Type:`, its `- Description:`, a
|
|
225
|
+
`### Definitions` list, a `### Covered by` list of the families that may rebind it,
|
|
226
|
+
and a `### Values` list. A name nothing declares comes back as `ENTITY_NOT_FOUND`,
|
|
227
|
+
and that is the answer: it is not a value to write. `token list --search motion`
|
|
228
|
+
answers `## Token (0)` today, which is why no anatomy binds a `motion.*` symbol.
|
|
229
|
+
|
|
230
|
+
`token list` is the read for "does this family exist under some other name", and it
|
|
231
|
+
needs a filter to be trusted. It pages at 300 rows sorted by name, so a bare
|
|
232
|
+
`pragma token list` returns colour tokens and nothing else — every `dimension.`,
|
|
233
|
+
`number.`, `typography.` and `fontWeight` symbol is off the first page. The output says
|
|
234
|
+
so, in a heading ending "and more exist" and a closing line offering an `--after`
|
|
235
|
+
cursor, but a bare list piped through `grep` swallows both and reads as an absence that
|
|
236
|
+
is not there. Narrow instead: `--search <word>` over name and description,
|
|
237
|
+
`--type <type>` for one type's population, `--channel-of <symbol>` for the channels
|
|
238
|
+
provisioning one symbol, and `--limit` raised past 300 when the whole population is
|
|
239
|
+
what you want.
|
|
240
|
+
|
|
241
|
+
`variable lookup` is the other direction, and it is the one that reads a stylesheet.
|
|
242
|
+
Give it the custom property WITHOUT its leading dashes. It has THREE outcomes, and only
|
|
243
|
+
the first two are usually anticipated:
|
|
244
|
+
|
|
245
|
+
1. **It resolves and carries a `- Symbol:` line.** That symbol is what the variable
|
|
246
|
+
stands for, and it is what the anatomy binds. The answer also carries a
|
|
247
|
+
`### Declarations` list — one row per declaration, each naming its `selector`, its
|
|
248
|
+
`inAtRule` layer, what it `emits` and the `file:line` it is declared at.
|
|
249
|
+
`modifier-color-text` answers with one row per modifier, which is the whole family's
|
|
250
|
+
coverage in one read.
|
|
251
|
+
2. **It resolves and carries NO `- Symbol:` line at all.** The variable is a DERIVED
|
|
252
|
+
one — a state product or a delta, autogenerated from another variable rather than
|
|
253
|
+
authored — and it stands for no symbol, so there is nothing to bind.
|
|
254
|
+
`hover--color-foreground-secondary` is one: it resolves, its tier is
|
|
255
|
+
`dt:tier.derived`, its `emits` is an `oklch(from …)` expression, and no `- Symbol:`
|
|
256
|
+
row appears. Do not read the missing line as a failed lookup and do not bind the
|
|
257
|
+
name. Ask what it reaches, and carry on down the implementation's own fallback chain
|
|
258
|
+
to the next variable, which is usually where the real symbol is:
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
pragma variable chain --variable hover--color-foreground-secondary
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
3. **`ENTITY_NOT_FOUND`.** The graph has never heard of the name — `--button-gap`,
|
|
265
|
+
`--icon-size` and `--focus-outline-width` all answer this way, because they are
|
|
266
|
+
component-local properties and not design tokens. That is the case for a `#` comment
|
|
267
|
+
on the line rather than a binding.
|
|
268
|
+
|
|
269
|
+
**A component-local variable is rarely the end of the chain — follow every hop.** The
|
|
270
|
+
`ENTITY_NOT_FOUND` says the token graph does not declare the NAME; it says nothing about
|
|
271
|
+
what the stylesheet declares the name AS. A component's own `:root` block routinely
|
|
272
|
+
aliases a declared variable behind a local one, and the alias can be several hops deep:
|
|
273
|
+
|
|
274
|
+
```css
|
|
275
|
+
:root {
|
|
276
|
+
--chip-color-background: var(--color-foreground-secondary);
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
`chip-color-background` is `ENTITY_NOT_FOUND`, and `color-foreground-secondary`
|
|
281
|
+
resolves to `color.foreground.secondary`. Skipping the hop loses a real binding. So for
|
|
282
|
+
every `ENTITY_NOT_FOUND` on a `--<component>-*` name, go back to the component's own
|
|
283
|
+
stylesheet, read what that property is declared as, and look THAT name up — repeating
|
|
284
|
+
until you reach a declared variable (bind its symbol) or a literal (write the literal
|
|
285
|
+
where the key admits one, and comment it where it does not). Only a chain that ends in
|
|
286
|
+
neither is a `#` comment saying what the implementation reads.
|
|
287
|
+
|
|
288
|
+
The same verbs are MCP tools under the names `token_lookup`, `token_list`,
|
|
289
|
+
`variable_lookup` and `variable_chain`, with the same arguments; use whichever surface
|
|
290
|
+
you are on, and take the current catalog from `pragma capabilities`.
|
|
174
291
|
|
|
175
292
|
### Discovery Queries
|
|
176
293
|
|
|
177
|
-
Common prefixes (`ds:`, `cs:`) are applied automatically — no PREFIX preamble
|
|
294
|
+
Common prefixes (`ds:`, `cs:`, `dt:`) are applied automatically — no PREFIX preamble
|
|
295
|
+
needed.
|
|
178
296
|
|
|
179
297
|
**Find components by tier:**
|
|
180
298
|
```bash
|
|
@@ -190,16 +308,19 @@ pragma graph query "SELECT ?component ?name WHERE {
|
|
|
190
308
|
pragma graph query "SELECT ?sub ?name WHERE {
|
|
191
309
|
?sub a ds:Subcomponent ;
|
|
192
310
|
ds:name ?name ;
|
|
193
|
-
ds:parentComponent
|
|
311
|
+
ds:parentComponent ds:global.component.card .
|
|
194
312
|
}"
|
|
195
313
|
```
|
|
196
314
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
315
|
+
A prefixed name carrying dots or slashes parses inside a SPARQL body, so write the name
|
|
316
|
+
the graph prints: `ds:global.component.card` and `dt:s4/web/cond/layer-ds-states/universal`
|
|
317
|
+
both resolve, as do the dot-free (`ds:Component`) and single-dot
|
|
318
|
+
(`ds:tag.needsdefinition`) forms. The full IRI
|
|
319
|
+
(`<https://ds.canonical.com/global.component.card>`) is accepted too and answers
|
|
320
|
+
identically — it is what `pragma graph query` prints back, so a copied row is always
|
|
321
|
+
safe. What is NOT accepted is the prefixed name inside angle brackets, `<ds:…>`: that
|
|
322
|
+
returns an EMPTY table rather than an error, which reads as "no such thing" when the
|
|
323
|
+
thing is there.
|
|
203
324
|
|
|
204
325
|
### The empty-anatomy worklist
|
|
205
326
|
|
|
@@ -212,8 +333,58 @@ pragma graph query "SELECT ?b ?name WHERE { ?b ds:anatomyDsl ?a ; ds:name ?name
|
|
|
212
333
|
|
|
213
334
|
The worklist rows point at blocks whose data lives under `data/` — never hand-edit
|
|
214
335
|
that tree: it is regenerated destructively from Coda by CI, and hand edits are
|
|
215
|
-
overwritten by the next sync. An authored
|
|
216
|
-
|
|
336
|
+
overwritten by the next sync. An authored anatomy lands in a file of its own, and
|
|
337
|
+
"Where the anatomy lands" below is the whole path from that file to the document.
|
|
338
|
+
|
|
339
|
+
### Where the anatomy lands
|
|
340
|
+
|
|
341
|
+
An anatomy is written by hand, from the component's implementation stylesheet, into
|
|
342
|
+
one file in the design-system repository:
|
|
343
|
+
|
|
344
|
+
anatomies/authored/<tier>/<uri>.yaml
|
|
345
|
+
|
|
346
|
+
The file name IS the dotted uri (`anatomies/authored/global/global.component.button.yaml`),
|
|
347
|
+
which is how the write finds the row it belongs to, and the text of the file is the
|
|
348
|
+
text of the `anatomy_dsl` cell verbatim. Open the file with a comment saying where it
|
|
349
|
+
was read from — the package, the stylesheet and the selector — because the next reader
|
|
350
|
+
has to be able to check it:
|
|
351
|
+
|
|
352
|
+
```yaml
|
|
353
|
+
---
|
|
354
|
+
# Authored 2026-09-13 from @canonical/react-ds-global src/lib/component/Button/styles.css on .ds.button
|
|
355
|
+
node:
|
|
356
|
+
uri: global.component.button
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Then, in order:
|
|
360
|
+
|
|
361
|
+
1. **Check it offline.** `bun src/cli.ts anatomies validate --authored` reads every
|
|
362
|
+
authored file and runs the law over it, with no network and no document. It answers
|
|
363
|
+
with the count it read, any warnings, and a verdict — `✓ 19 warning(s), 0 findings
|
|
364
|
+
— every file is lawful` — and exits 0 when the corpus is lawful. Findings are what
|
|
365
|
+
you fix; the `AT11` state-differs-from-base lint is a warning and is usually the
|
|
366
|
+
implementation telling the truth.
|
|
367
|
+
2. **Review it as a file.** The file goes through a pull request like any other
|
|
368
|
+
change, which is the point of authoring in files: the node boundaries, the
|
|
369
|
+
comments and the symbol choices are all readable in a diff.
|
|
370
|
+
3. **Write it to the document.** `anatomies write` sends the authored files to the
|
|
371
|
+
`anatomy_dsl` cells, and it is dry by default — run it with no flags first and read
|
|
372
|
+
the plan, then one anatomy as a canary with `--only <uri> --apply` and look at the
|
|
373
|
+
cell, then `--apply` for the rest. It snapshots every cell it is about to touch
|
|
374
|
+
before its first write, and `anatomies restore <snapshot>` puts them back. The
|
|
375
|
+
repository README's anatomies section is the authority on the gates that step runs
|
|
376
|
+
behind; do not improvise around them.
|
|
377
|
+
|
|
378
|
+
**After the write, the document is the source of record.** `data/` is regenerated from
|
|
379
|
+
it by the pull sync, the pack is built from `data/`, and that is the chain a
|
|
380
|
+
`pragma block lookup` answer comes down. Until an authored anatomy has been written
|
|
381
|
+
and the pack rebuilt, `block lookup`'s `### Anatomy (DSL)` section still answers with
|
|
382
|
+
whatever the cell said before — today, for most blocks, the retired notation. A
|
|
383
|
+
`color/text/muted` in that output is history, not a house style to copy.
|
|
384
|
+
|
|
385
|
+
There is no derivation step, no `references.yaml` and no register category for a
|
|
386
|
+
stylesheet fact: reading an implementation and choosing the node, the key and the
|
|
387
|
+
symbol is judgement, which is why it happens in a file under review.
|
|
217
388
|
|
|
218
389
|
## DSL Reference
|
|
219
390
|
|
|
@@ -234,15 +405,22 @@ node:
|
|
|
234
405
|
AnatomySpec
|
|
235
406
|
-> node: NamedNode (root must be named)
|
|
236
407
|
|
|
408
|
+
Style keys may carry an @state suffix ("appearance.background@hover");
|
|
409
|
+
the unmarked key is the default state.
|
|
410
|
+
|
|
237
411
|
NamedNode
|
|
238
412
|
-> uri: string ("tier.type.name")
|
|
239
|
-
->
|
|
413
|
+
-> projection?: Projection
|
|
414
|
+
-> props?: Record<string, string | number | boolean>
|
|
415
|
+
-> styles?: Record<StyleKey, Symbol | [...Symbol[], Primitive?] | Primitive>
|
|
240
416
|
-> edges?: Edge[]
|
|
241
417
|
|
|
242
418
|
AnonymousNode
|
|
243
419
|
-> role: string ("content wrapper")
|
|
244
|
-
->
|
|
420
|
+
-> projection?: Projection
|
|
421
|
+
-> styles?: Record<StyleKey, Symbol | [...Symbol[], Primitive?] | Primitive>
|
|
245
422
|
-> edges?: Edge[]
|
|
423
|
+
(no props — anonymous nodes have no prop surface)
|
|
246
424
|
|
|
247
425
|
Edge
|
|
248
426
|
-> node?: NamedNode | AnonymousNode
|
|
@@ -256,11 +434,19 @@ Switch
|
|
|
256
434
|
SwitchCase
|
|
257
435
|
-> uri?: string (shorthand)
|
|
258
436
|
-> node?: Node (full form)
|
|
259
|
-
|
|
437
|
+
(exactly one of the two; there is NO `default` marker)
|
|
260
438
|
|
|
261
439
|
Relation
|
|
262
440
|
-> cardinality: string ("1", "0..1", "0..*", "1..*")
|
|
263
441
|
-> slotName?: string ("default", "header", "icon")
|
|
442
|
+
-> projection?: RelationProjection
|
|
443
|
+
|
|
444
|
+
Projection (on a node — at least one of the two)
|
|
445
|
+
-> on?: string GraphQL type condition ("Component")
|
|
446
|
+
-> field?: string dot-delimited field path ("_meta.title")
|
|
447
|
+
|
|
448
|
+
RelationProjection (on a relation — the traversal filling the slot)
|
|
449
|
+
-> field: string required; never carries `on`
|
|
264
450
|
```
|
|
265
451
|
|
|
266
452
|
### URI Encoding Convention
|
|
@@ -317,6 +503,43 @@ Subcomponents must reference their parent in the name:
|
|
|
317
503
|
|
|
318
504
|
**Rule of thumb:** Ask "Can/should a user write `<ComponentName>` in their code?" If yes, use `uri`. If no, use `role`.
|
|
319
505
|
|
|
506
|
+
**A part the stylesheet draws with `::before` or `::after` is a node.** It is visible,
|
|
507
|
+
it takes paint of its own, and it is not something a consumer fills — so it is an
|
|
508
|
+
anonymous `role:` node with a comment saying how it is drawn, and it has no
|
|
509
|
+
`slotName`. The live checkbox glyph is the case:
|
|
510
|
+
|
|
511
|
+
```yaml
|
|
512
|
+
edges:
|
|
513
|
+
- node:
|
|
514
|
+
# drawn as ::before
|
|
515
|
+
role: selection glyph
|
|
516
|
+
styles:
|
|
517
|
+
layout.position: absolute
|
|
518
|
+
appearance.background@selected: [surface.color.foreground.checkbox.checkmark, color.foreground.checkbox.checkmark]
|
|
519
|
+
relation:
|
|
520
|
+
cardinality: "1"
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
The DSL has no pseudo-element construct, and it does not need one: the node above is an
|
|
524
|
+
ordinary anonymous node, and the `# drawn as ::before` comment is what records which
|
|
525
|
+
pseudo-element the implementation used. The spec's Deferred Items row on pseudo-elements
|
|
526
|
+
is about that missing GRAMMAR — a first-class pseudo-element type — not about whether
|
|
527
|
+
the part is modelled. It is modelled, by the convention above, and the authored corpus
|
|
528
|
+
does it this way. So a `::before` separator, glyph or overlay is never left out of a
|
|
529
|
+
tree because the grammar has no keyword for it.
|
|
530
|
+
|
|
531
|
+
**A native control's own chrome is turned off, not modelled.** Where the
|
|
532
|
+
implementation writes `appearance: none` over a native `<input>` or `<select>` and
|
|
533
|
+
draws the control itself, the anatomy says so on the node that IS the control:
|
|
534
|
+
|
|
535
|
+
```yaml
|
|
536
|
+
node:
|
|
537
|
+
uri: global.subcomponent.checkbox_input
|
|
538
|
+
styles:
|
|
539
|
+
appearance.native: none
|
|
540
|
+
appearance.border.color: color.border
|
|
541
|
+
```
|
|
542
|
+
|
|
320
543
|
### DRY Principle
|
|
321
544
|
|
|
322
545
|
When a node has a URI (is not anonymous), it references its own DSL file. Do not inline the full tree—reference the URI only. A named child MAY carry `styles:` that override its own anatomy in this context — live, `apps_landscape.component.password_constraints` sizes down its `global.component.icon` child — but never a copy of its subtree.
|
|
@@ -343,12 +566,29 @@ edges:
|
|
|
343
566
|
- node:
|
|
344
567
|
uri: global.component.icon
|
|
345
568
|
styles:
|
|
346
|
-
size.width:
|
|
347
|
-
size.height:
|
|
569
|
+
size.width: dimension.300
|
|
570
|
+
size.height: dimension.300
|
|
348
571
|
relation:
|
|
349
572
|
cardinality: "1"
|
|
350
573
|
```
|
|
351
574
|
|
|
575
|
+
**Which blocks a `uri:` may reach.** A reference points at a block in the same tier or
|
|
576
|
+
in `global`, and never the other way: a `global` anatomy that reached into an app
|
|
577
|
+
tier would make the global block depend on one product. Where the part is real but the
|
|
578
|
+
block it would name does not exist, write an anonymous `role:` node and say so in a
|
|
579
|
+
comment — the reference is a claim about the graph, and a claim about a block nobody
|
|
580
|
+
has created is one an anatomy must not make:
|
|
581
|
+
|
|
582
|
+
```yaml
|
|
583
|
+
edges:
|
|
584
|
+
- node:
|
|
585
|
+
# The list is the Landscape table's own, and no block names it yet.
|
|
586
|
+
role: result list
|
|
587
|
+
relation:
|
|
588
|
+
cardinality: "1"
|
|
589
|
+
slotName: default
|
|
590
|
+
```
|
|
591
|
+
|
|
352
592
|
### Cardinality Notation
|
|
353
593
|
|
|
354
594
|
| Notation | Meaning | Example Use |
|
|
@@ -371,7 +611,6 @@ edges:
|
|
|
371
611
|
on: <discriminator>
|
|
372
612
|
cases:
|
|
373
613
|
- uri: <component-uri>
|
|
374
|
-
default: true # optional, marks default case
|
|
375
614
|
- uri: <component-uri>
|
|
376
615
|
- uri: $custom # reserved URI for user-provided components
|
|
377
616
|
relation:
|
|
@@ -391,6 +630,11 @@ This enum is NORMATIVE. Live anatomies that predate it may carry qualified
|
|
|
391
630
|
discriminators (`props/<prop>`) or a `with:` map in exploratory drafts — do not
|
|
392
631
|
copy those into new anatomies.
|
|
393
632
|
|
|
633
|
+
A case carries `uri` or `node`, and nothing else. There is no `default:` marker
|
|
634
|
+
in the DSL: `SwitchCase` is `additionalProperties: false`, so a case carrying one
|
|
635
|
+
FAILS schema validation. Which alternative is the fallback is the component's
|
|
636
|
+
behaviour, not its anatomy — say it in a YAML comment if it matters.
|
|
637
|
+
|
|
394
638
|
#### Shorthand Expansion
|
|
395
639
|
|
|
396
640
|
The shorthand `- uri: X` expands to `- node: { uri: X }`. This allows cases to be expressed concisely when no additional metadata is needed:
|
|
@@ -414,7 +658,7 @@ cases:
|
|
|
414
658
|
uri: global.subcomponent.textarea_input
|
|
415
659
|
styles:
|
|
416
660
|
size.height: hug
|
|
417
|
-
size.min.height:
|
|
661
|
+
size.min.height: 6rem
|
|
418
662
|
```
|
|
419
663
|
|
|
420
664
|
#### Reserved URI: `$custom`
|
|
@@ -482,6 +726,104 @@ node:
|
|
|
482
726
|
cardinality: "1..*"
|
|
483
727
|
```
|
|
484
728
|
|
|
729
|
+
### Projections
|
|
730
|
+
|
|
731
|
+
A projection binds a position in the tree to graph data, after the Relay
|
|
732
|
+
fragment-colocation pattern: the tree carries its own data requirements. Two
|
|
733
|
+
places take one, and they mean different things.
|
|
734
|
+
|
|
735
|
+
```yaml
|
|
736
|
+
---
|
|
737
|
+
node:
|
|
738
|
+
uri: global.component.entity-card
|
|
739
|
+
projection:
|
|
740
|
+
on: Component # type condition — this tree is a view over one Component
|
|
741
|
+
edges:
|
|
742
|
+
- node:
|
|
743
|
+
uri: global.subcomponent.entity-card-header
|
|
744
|
+
projection:
|
|
745
|
+
field: _meta.title # this node RENDERS the entity's title
|
|
746
|
+
relation:
|
|
747
|
+
cardinality: "1"
|
|
748
|
+
slotName: header
|
|
749
|
+
- node:
|
|
750
|
+
uri: global.component.chip
|
|
751
|
+
projection:
|
|
752
|
+
field: _meta.title # relative to the TRAVERSED entity, not the root
|
|
753
|
+
relation:
|
|
754
|
+
cardinality: "0..*"
|
|
755
|
+
slotName: tags
|
|
756
|
+
projection:
|
|
757
|
+
field: documentationStages # traversal populating the slot
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
| Position | Field | Means |
|
|
761
|
+
|----------|-------|-------|
|
|
762
|
+
| Root node | `on` | Establishes the data context — the anatomy is a view over one entity of that type. Children inherit it. |
|
|
763
|
+
| Child node | `on` | Narrows the traversed entity's type — the analog of an inline fragment. |
|
|
764
|
+
| Any node | `field` | This node renders that field's value. |
|
|
765
|
+
| Relation | `field` | Traversal: the slot is populated from that field of the current context. Required, and never carries `on`. |
|
|
766
|
+
|
|
767
|
+
A node projection needs at least one of `on` / `field`. Both are schema names:
|
|
768
|
+
`on` matches `^[A-Z][A-Za-z0-9_]*$`, `field` is dot-delimited
|
|
769
|
+
(`^[_A-Za-z][_A-Za-z0-9]*(\.[_A-Za-z][_A-Za-z0-9]*)*$`).
|
|
770
|
+
|
|
771
|
+
Two rules a checker cannot enforce from the anatomy alone — hold them yourself:
|
|
772
|
+
|
|
773
|
+
- **Cardinality decomposes against the schema.** The upper bound claims
|
|
774
|
+
multiplicity (`..1` an object or scalar field, `..*` a connection or list);
|
|
775
|
+
the lower bound claims nullability (`0..` tolerates `null`, `1..` requires the
|
|
776
|
+
provider to always have the value). So `field: _meta.title` may sit under
|
|
777
|
+
`cardinality: "1"`, but a nullable `summary` must sit under `0..1`. `1..*`
|
|
778
|
+
asserts a non-empty list — stronger than GraphQL can express, checkable only
|
|
779
|
+
at runtime.
|
|
780
|
+
- **Mechanism-blindness.** Whether a plural field is a Relay connection or a
|
|
781
|
+
plain list is a provider mechanism, not an anatomy fact. NEVER write an
|
|
782
|
+
unwrapping path like `edges.node` — consumers discover the shape from the SDL.
|
|
783
|
+
This is what lets an anatomy survive a provider promoting a list to a
|
|
784
|
+
connection unchanged.
|
|
785
|
+
|
|
786
|
+
Projections are optional. A structure-only anatomy stays valid, and unprojected
|
|
787
|
+
positions simply contribute nothing to the derived fragment. Add them when the
|
|
788
|
+
component's whole point is rendering a known entity; leave them off for generic
|
|
789
|
+
containers. The committed provider SDL is the naming authority — check field
|
|
790
|
+
names against it rather than guessing.
|
|
791
|
+
|
|
792
|
+
### Pinned Props
|
|
793
|
+
|
|
794
|
+
A named node can PIN props of the component it references, fixing a value at
|
|
795
|
+
one tree position:
|
|
796
|
+
|
|
797
|
+
```yaml
|
|
798
|
+
node:
|
|
799
|
+
uri: global.component.icon
|
|
800
|
+
props:
|
|
801
|
+
icon: chevron-down
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
| Aspect | Rule |
|
|
805
|
+
|--------|------|
|
|
806
|
+
| Keys | Prop names as defined on the component in the DS ontology (camelCase). |
|
|
807
|
+
| Values | Scalars only — no token paths, no fallback arrays. Pins are values, not styles. |
|
|
808
|
+
| Nodes | Named nodes ONLY. `props` under a `role` node is rejected by the parser and the schema. |
|
|
809
|
+
|
|
810
|
+
The DSL never DEFINES a prop surface — names, types and optionality live in the
|
|
811
|
+
DS ontology (`ds:hasProperty`). A pin asserts one value at one position. Whether
|
|
812
|
+
the prop exists, and whether the value is admissible, are checks against the
|
|
813
|
+
graph: `pragma block lookup <Component>` lists the properties.
|
|
814
|
+
|
|
815
|
+
**Icons are the canonical case**, and need no icon-specific construct. The icon
|
|
816
|
+
is a component whose glyph is a required prop, so icon usage splits in two:
|
|
817
|
+
|
|
818
|
+
| Case | Authoring |
|
|
819
|
+
|------|-----------|
|
|
820
|
+
| Consumer-filled icon slot (Button's `slotName: icon`) | Icon-component edge with a slot and NO pin — the consumer chooses the glyph, and the anatomy correctly says nothing. |
|
|
821
|
+
| Component-intrinsic icon (accordion chevron, modal close ×, status glyph) | Icon-component node with `props: { icon: … }` — the spec fixes the glyph. |
|
|
822
|
+
|
|
823
|
+
**Pins are static by design.** A data-driven value is a projection
|
|
824
|
+
(`projection: { field: … }`); a state-driven one is a `switch` whose cases pin
|
|
825
|
+
different values. If a value varies at runtime, it is not a pin.
|
|
826
|
+
|
|
485
827
|
### Slot Names
|
|
486
828
|
|
|
487
829
|
Common slot conventions:
|
|
@@ -498,71 +840,297 @@ Common slot conventions:
|
|
|
498
840
|
|
|
499
841
|
**Convention:** `slotName: default` implies the main children slot (`children` in React, default slot in Vue).
|
|
500
842
|
|
|
501
|
-
###
|
|
843
|
+
### A style value is the symbol consumed
|
|
502
844
|
|
|
503
|
-
|
|
845
|
+
A style value is **the name of a token symbol**, written in the symbol's own dotted
|
|
846
|
+
spelling — or **an ordered list of symbol names**, which is the fallback order the
|
|
847
|
+
implementation reads, ending in the one literal it falls back to last.
|
|
504
848
|
|
|
505
|
-
#### Layout (typically invariant)
|
|
506
849
|
```yaml
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
850
|
+
styles:
|
|
851
|
+
# One symbol.
|
|
852
|
+
typography.weight: typography.weight.medium
|
|
853
|
+
# A fallback order: the channel first, then the semantic token behind it. This is
|
|
854
|
+
# the implementation's own `var()` chain, transcribed.
|
|
855
|
+
typography.color: [modifier.color.text, color.text]
|
|
856
|
+
# A chain that ends in a literal keeps the literal: an implementation regenerated
|
|
857
|
+
# from this anatomy has to be able to write the whole chain.
|
|
858
|
+
appearance.outline.color@focus: [modifier.color.focusRing, color.focusRing, currentColor]
|
|
859
|
+
# A primitive is a literal the implementation writes, and resolves against nothing.
|
|
860
|
+
layout.type: inline-flex
|
|
515
861
|
```
|
|
516
862
|
|
|
517
|
-
|
|
863
|
+
Three things that are **retired** and rejected by the parser:
|
|
864
|
+
|
|
865
|
+
| Retired | Write instead |
|
|
866
|
+
|---|---|
|
|
867
|
+
| a slash path — `color/text/muted` | the symbol's dotted name — `color.text.muted` |
|
|
868
|
+
| a trailing `?` marker — `shadow/card?` | nothing: a name no stratum declares is not written at all, and a `#` comment on the line records it |
|
|
869
|
+
| `root` or `$root` inside a value | the dotted name without it — `color.text` |
|
|
870
|
+
|
|
871
|
+
**Consume the channel where the implementation reads the channel.** A modifier or
|
|
872
|
+
surface family reaches a component through a channel variable, and the anatomy says so
|
|
873
|
+
by consuming the channel by name: `modifier.color.text` is the channel of `color.text`,
|
|
874
|
+
and it is a symbol of its own. Where the implementation reads the semantic token
|
|
875
|
+
directly, the anatomy consumes the semantic token. Nothing is inferred either way.
|
|
876
|
+
|
|
877
|
+
**This is also how a component's SUPPORT for a modifier family is expressed — and it is
|
|
878
|
+
the only way.** There is no key that names a family, no `@state` for one, and no
|
|
879
|
+
`switch` discriminator over one: a family is never written into an anatomy by name.
|
|
880
|
+
What an anatomy says is which channels it reads, and a channel is where a family lands.
|
|
881
|
+
So `typography.color: [modifier.color.text, color.text]` is the statement "whatever
|
|
882
|
+
family covers `color.text` reaches this node's text colour", and
|
|
883
|
+
`appearance.background: [surface.color.background, color.background]` is the same
|
|
884
|
+
statement for the surface side. Which families those are is a question for the token
|
|
885
|
+
graph, not the anatomy — `pragma token lookup color.text` lists them under
|
|
886
|
+
`### Covered by`, and `pragma token values --symbol modifier.color.text` shows the
|
|
887
|
+
routing per modifier. Button is the worked chain: it carries the Anticipation family,
|
|
888
|
+
its text colour consumes `modifier.color.text`, and that channel routes
|
|
889
|
+
`anticipation.caution` to `color.text.warning` and `anticipation.destructive` to
|
|
890
|
+
`color.text.destructive` — while Button's own anatomy says the word "Anticipation"
|
|
891
|
+
nowhere. The family is in the channel, not in the tree.
|
|
892
|
+
|
|
893
|
+
Two consequences worth holding on to. A component whose spec claims a family and whose
|
|
894
|
+
anatomy consumes no channel of the symbols that family covers does not in fact support
|
|
895
|
+
it — that mismatch is a finding, and the anatomy is where it shows. And the reverse:
|
|
896
|
+
where the stylesheet reads the plain semantic token with no channel in front of it, the
|
|
897
|
+
anatomy binds the plain token, and the component is correctly saying it is NOT
|
|
898
|
+
family-sensitive at that key. Do not add a channel to express a family the
|
|
899
|
+
implementation does not read.
|
|
900
|
+
|
|
901
|
+
**A name no stratum declares is never written. The comment is the record.** An
|
|
902
|
+
implementation reads plenty of variables that stand for no symbol — a component-local
|
|
903
|
+
`--button-gap`, a state variable the stylesheet computes, a family that has not
|
|
904
|
+
landed. The rule is to bind what does resolve and say in a `#` comment what the
|
|
905
|
+
implementation reads first, on the line it belongs to:
|
|
906
|
+
|
|
518
907
|
```yaml
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
908
|
+
styles:
|
|
909
|
+
# reads --modifier-color-text-disabled first; no symbol declares it.
|
|
910
|
+
typography.color@disabled: color.text.disabled
|
|
911
|
+
# The transition reads --motion-duration-fast and --motion-easing-standard; both
|
|
912
|
+
# bindings are dropped because no symbol declares either name and neither var()
|
|
913
|
+
# carries a literal fallback.
|
|
914
|
+
motion.property: "background-color, color"
|
|
523
915
|
```
|
|
524
916
|
|
|
525
|
-
|
|
917
|
+
The comment is where a reader meets the fact, and it travels with the value — there is
|
|
918
|
+
no register row for an authored file, and nothing is substituted merely to make a name
|
|
919
|
+
resolve. `pragma token lookup <symbol>` settles whether a name resolves before you
|
|
920
|
+
write it; `pragma variable lookup <name>` settles what a variable in the stylesheet
|
|
921
|
+
stands for (see Discovery Flow above).
|
|
922
|
+
|
|
923
|
+
**A list is the implementation's fallback chain, and the first element wins.** The head
|
|
924
|
+
is what the slot reads, each later element is what it falls back to, and a chain may
|
|
925
|
+
end in the one literal the implementation writes last. A list of one element is written
|
|
926
|
+
as the scalar instead, and a primitive anywhere but last is a parse error.
|
|
927
|
+
|
|
928
|
+
### The style keys are a closed roster
|
|
929
|
+
|
|
930
|
+
There are 111 style keys and they are the DSL's own vocabulary, published as
|
|
931
|
+
`@canonical/anatomy-dsl`'s `definitions/style-keys.yaml`. That file is the authority
|
|
932
|
+
on every question of the form "what does this key take" — it is installed, so read it
|
|
933
|
+
rather than reasoning from the examples here:
|
|
934
|
+
|
|
935
|
+
```bash
|
|
936
|
+
less node_modules/@canonical/anatomy-dsl/definitions/style-keys.yaml
|
|
937
|
+
```
|
|
938
|
+
|
|
939
|
+
Each key carries a `valueKind` (`token`, `primitive` or `either`) and, where it takes a
|
|
940
|
+
token, the one `namespace` whose symbols it admits. Those two fields settle it: if a key
|
|
941
|
+
says `either`, a literal is legal there whatever the examples below happen to show, and
|
|
942
|
+
if it names a namespace, a symbol outside that namespace is not legal there however
|
|
943
|
+
well it reads. The roster's own header says how each value was measured, which is worth
|
|
944
|
+
reading once — a key exists because an implementation binds the property, and
|
|
945
|
+
`valueKind` is what the measurement found, not a preference.
|
|
946
|
+
|
|
947
|
+
A key outside the roster is rejected, and each key states what it admits:
|
|
948
|
+
|
|
949
|
+
- **token** — a symbol, always. `appearance.border.color`, `size.inline`.
|
|
950
|
+
- **primitive** — a literal, always. `layout.type`, `appearance.border.style`.
|
|
951
|
+
- **either** — a symbol or a literal, because the corpus binds both.
|
|
952
|
+
`appearance.background`, `spacing.gap`.
|
|
953
|
+
|
|
954
|
+
And each token-taking key admits ONE namespace, in three spellings — the family, its
|
|
955
|
+
modifier channel and its surface channel:
|
|
956
|
+
|
|
957
|
+
| Key family | Namespace it admits |
|
|
958
|
+
|---|---|
|
|
959
|
+
| `appearance.background`, `appearance.border.*.color`, `appearance.outline.color`, `typography.color` | `color.`, `modifier.color.`, `surface.color.` |
|
|
960
|
+
| `appearance.border.width`, `appearance.outline.*`, `appearance.radius`, `layout.flex.basis`, `layout.offset.*`, `size.*`, `typography.letterSpacing`, `typography.size`, `typography.decoration.*` | `dimension.`, `modifier.dimension.`, `surface.dimension.` |
|
|
961
|
+
| `spacing.gap*`, `spacing.internal.*`, `spacing.external.*` | `spacing.`, `modifier.spacing.`, `surface.spacing.` |
|
|
962
|
+
| `typography.font`, `typography.fontFamily`, `typography.lineHeight`, `typography.weight` | `typography.`, `modifier.typography.`, `surface.typography.` |
|
|
963
|
+
| `motion.duration`, `motion.easing` | `motion.`, `modifier.motion.`, `surface.motion.` |
|
|
964
|
+
|
|
965
|
+
Read the table as a summary of the roster, not as a substitute for it: where the two
|
|
966
|
+
differ the roster is right and this table is stale. Note in particular that several
|
|
967
|
+
keys in the `dimension.` row are `either`, not `token` — `appearance.radius` is one, so
|
|
968
|
+
`appearance.radius: 1rem` is a lawful binding where the implementation writes `1rem`
|
|
969
|
+
literally and declares no variable. The namespace constrains which SYMBOL may go on a
|
|
970
|
+
key; it does not turn an `either` key into a token-only one.
|
|
971
|
+
|
|
972
|
+
**Spacing keys admit `dimension.*` too, for now.** Not one of the twelve `spacing.*`
|
|
973
|
+
roles resolves to the same dimension in every product context, and the implementations
|
|
974
|
+
read `--dimension-*` directly for padding and gaps. So where the stylesheet reads
|
|
975
|
+
`--dimension-200`, the anatomy writes `dimension.200` on the spacing key, and the
|
|
976
|
+
second namespace is in the roster for exactly as long as that is true — it comes back
|
|
977
|
+
out once a semantic spacing namespace lands. Write the symbol the implementation reads,
|
|
978
|
+
not the role you would have chosen.
|
|
979
|
+
|
|
980
|
+
**Not every CSS property has a key.** `box-shadow`, `animation`, a vendor prefix, a
|
|
981
|
+
mask: no key in the roster takes them, so an anatomy does not bind what the
|
|
982
|
+
implementation consumes through them, and a `#` comment on the node says what was
|
|
983
|
+
left unbound. Do not invent a key for a property that has none.
|
|
984
|
+
|
|
985
|
+
### The keys, by family
|
|
986
|
+
|
|
987
|
+
Every key below is in the roster, and the comment says what it admits. Where a key
|
|
988
|
+
takes `either`, the choice is not taste: bind the symbol where the implementation
|
|
989
|
+
reads a variable, and write the literal where it writes a literal.
|
|
990
|
+
|
|
991
|
+
#### Layout — primitives throughout
|
|
992
|
+
```yaml
|
|
993
|
+
layout.type: inline-flex # the display type, as CSS spells it
|
|
994
|
+
layout.direction: column
|
|
995
|
+
layout.align: center # align-items, on the node that is the container
|
|
996
|
+
layout.alignSelf: center # align-self, on the child that overrides it
|
|
997
|
+
layout.alignContent: center # align-content, on the container
|
|
998
|
+
layout.justify: space-between
|
|
999
|
+
layout.wrap: wrap
|
|
1000
|
+
layout.overflow: hidden
|
|
1001
|
+
layout.flex.grow: 1
|
|
1002
|
+
layout.flex.basis: 20rem # either: a dimension symbol or a literal
|
|
1003
|
+
layout.position: relative
|
|
1004
|
+
layout.grid.columns: repeat(2, 1fr)
|
|
1005
|
+
layout.offset.top: dimension.100 # either: dimension.*
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
**`layout.align` is `align-items` and nothing else.** The roster carries three keys
|
|
1009
|
+
where CSS has three properties, so there is nothing to disambiguate by context: a
|
|
1010
|
+
child's `align-self: center` is `layout.alignSelf: center` on that child, and
|
|
1011
|
+
`align-content` is `layout.alignContent`. Writing `layout.align` on a leaf that
|
|
1012
|
+
overrides its parent's alignment says the wrong thing — it reads as "this node aligns
|
|
1013
|
+
ITS children" — and the gate will not catch it, because both keys are in the roster and
|
|
1014
|
+
both take a primitive. Take the CSS property from the stylesheet and map it to the key
|
|
1015
|
+
of the same name.
|
|
1016
|
+
|
|
1017
|
+
#### Spacing — a `spacing.*` role, or the `dimension.*` the stylesheet reads
|
|
1018
|
+
```yaml
|
|
1019
|
+
spacing.internal.inline.start: spacing.inset.action.inline # padding, one side
|
|
1020
|
+
spacing.internal.block.start: spacing.inset.surface.block
|
|
1021
|
+
spacing.external.block.end: spacing.inset.surface.block # margin, one side
|
|
1022
|
+
spacing.gap: spacing.gap.mark.inline # gap between children
|
|
1023
|
+
spacing.gap.block: spacing.gap.group.block # token only
|
|
1024
|
+
spacing.internal.inline.end: dimension.200 # --dimension-200, read directly
|
|
1025
|
+
```
|
|
1026
|
+
|
|
1027
|
+
**A symmetric CSS shorthand becomes both keys, written out.** There is no key for
|
|
1028
|
+
`padding-block` or `padding-inline` as a pair, so a stylesheet's
|
|
1029
|
+
`padding-block: var(--spacing-x)` is TWO bindings — `spacing.internal.block.start` and
|
|
1030
|
+
`spacing.internal.block.end`, each carrying the same value — and `padding-inline`
|
|
1031
|
+
likewise on `.inline.start` and `.inline.end`. The same holds for `margin-block` and
|
|
1032
|
+
`margin-inline` on the `spacing.external.*` keys. Both sides are written; neither is
|
|
1033
|
+
left implicit:
|
|
1034
|
+
|
|
526
1035
|
```yaml
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
appearance.shadow: shadow/elevated/medium
|
|
531
|
-
appearance.radius: shape/rounded/full
|
|
1036
|
+
# padding-inline: var(--spacing-inset-action-inline)
|
|
1037
|
+
spacing.internal.inline.start: spacing.inset.action.inline
|
|
1038
|
+
spacing.internal.inline.end: spacing.inset.action.inline
|
|
532
1039
|
```
|
|
533
1040
|
|
|
534
|
-
####
|
|
1041
|
+
#### Appearance — colour and dimension
|
|
535
1042
|
```yaml
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
1043
|
+
appearance.background: [modifier.color.foreground.primary, color.foreground.primary]
|
|
1044
|
+
appearance.border.color: [modifier.color.border, color.border]
|
|
1045
|
+
appearance.border.width: dimension.stroke.thickness.medium
|
|
1046
|
+
appearance.border.style: solid # primitive
|
|
1047
|
+
appearance.radius: dimension.radius.medium
|
|
1048
|
+
appearance.outline.color@focus: [modifier.color.focusRing, color.focusRing, currentColor]
|
|
1049
|
+
appearance.outline.width@focus: dimension.stroke.thickness.large
|
|
1050
|
+
appearance.opacity: 0.5 # primitive
|
|
541
1051
|
```
|
|
542
1052
|
|
|
543
|
-
####
|
|
1053
|
+
#### Size — a dimension symbol, or a literal where the size is structural
|
|
544
1054
|
```yaml
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
typography.line.height: 1.5
|
|
1055
|
+
size.width: 100% # either: a literal is right here
|
|
1056
|
+
size.max.width: 60rem
|
|
1057
|
+
size.min.height: dimension.900 # token only
|
|
1058
|
+
size.inline: dimension.400 # token only
|
|
550
1059
|
```
|
|
551
1060
|
|
|
552
|
-
####
|
|
1061
|
+
#### Typography
|
|
553
1062
|
```yaml
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
1063
|
+
typography.font: typography.text.primary # the composite
|
|
1064
|
+
typography.size: dimension.size.fontSize.200
|
|
1065
|
+
typography.weight: typography.weight.medium
|
|
1066
|
+
typography.lineHeight: typography.text.primary
|
|
1067
|
+
typography.color: [modifier.color.text, color.text]
|
|
1068
|
+
typography.letterSpacing: dimension.letterSpacing.wide
|
|
1069
|
+
typography.align: center # primitive
|
|
558
1070
|
```
|
|
559
1071
|
|
|
560
|
-
####
|
|
1072
|
+
#### Interaction and motion
|
|
561
1073
|
```yaml
|
|
562
|
-
|
|
563
|
-
|
|
1074
|
+
interaction.cursor: pointer # primitive
|
|
1075
|
+
interaction.select: none # primitive
|
|
1076
|
+
motion.property: background-color # primitive
|
|
1077
|
+
# No stratum declares a motion.* symbol yet, so a duration or an easing the
|
|
1078
|
+
# stylesheet reads through a variable is not bound at all — a `#` comment on the
|
|
1079
|
+
# node says what it reads. A literal the stylesheet writes literally is written.
|
|
1080
|
+
motion.easing: ease-in-out # either: a literal until the family lands
|
|
564
1081
|
```
|
|
565
1082
|
|
|
1083
|
+
### Interaction States (`@state`)
|
|
1084
|
+
|
|
1085
|
+
A style key MAY take an `@state` suffix scoping its value to one interaction
|
|
1086
|
+
state. The unmarked key IS the default state — `@default` is invalid.
|
|
1087
|
+
|
|
1088
|
+
```yaml
|
|
1089
|
+
styles:
|
|
1090
|
+
interaction.cursor: pointer
|
|
1091
|
+
interaction.cursor@disabled: not-allowed
|
|
1092
|
+
appearance.background: color.foreground.primary
|
|
1093
|
+
appearance.background@hover: color.foreground.primary.hover
|
|
1094
|
+
appearance.outline.color@focus: [modifier.color.focusRing, color.focusRing] # a channel may exist only in a state
|
|
1095
|
+
```
|
|
1096
|
+
|
|
1097
|
+
**Vocabulary — closed, registry-governed.** Eight states, and only these:
|
|
1098
|
+
|
|
1099
|
+
| State | Meaning | What the implementation keys off |
|
|
1100
|
+
|-------|---------|----------------------------------|
|
|
1101
|
+
| `hover` | Pointer over the element | `:hover` |
|
|
1102
|
+
| `active` | Being pressed or activated (MD3 *pressed*, Spectrum *down*) | `:active` |
|
|
1103
|
+
| `disabled` | Interaction unavailable | `:disabled`, `aria-disabled` |
|
|
1104
|
+
| `focus` | Keyboard focus indication | `:focus-visible` on the web |
|
|
1105
|
+
| `selected` | Chosen within a set | `aria-selected`, and `:checked` where the control is one of a set |
|
|
1106
|
+
| `expanded` | A disclosure is open | `[open]`, `aria-expanded` |
|
|
1107
|
+
| `indeterminate` | A checkbox or a progress is mixed, on neither setting | `:indeterminate`, `aria-checked="mixed"` |
|
|
1108
|
+
| `invalid` | Validation has failed | `:invalid`, `aria-invalid` |
|
|
1109
|
+
|
|
1110
|
+
The last three are the newest, and how they arrived is the rule: candidates
|
|
1111
|
+
(`checked`, `visited`, `dragged`, `pending`, `error`, `read-only`) enter through
|
|
1112
|
+
the registry with a definition and a per-platform mapping — NEVER by loosening the
|
|
1113
|
+
schema locally. If an anatomy seems to need a ninth state, that is a registry
|
|
1114
|
+
proposal, not a local decision. `open` is not a second spelling of `expanded`.
|
|
1115
|
+
|
|
1116
|
+
**States re-value channels; they never add structure.** This is the line to
|
|
1117
|
+
hold:
|
|
1118
|
+
|
|
1119
|
+
- A state that changes the TREE is a `switch`, not a state. The test is whether
|
|
1120
|
+
the implementation RE-VALUES a channel in the state or changes what exists: a
|
|
1121
|
+
disclosure header that takes a different background while open is `@expanded`;
|
|
1122
|
+
the panel that exists only while open is a `switch`, and the same disclosure
|
|
1123
|
+
carries both.
|
|
1124
|
+
- The gate and the appearance are separate questions.
|
|
1125
|
+
`props: { disabled: true }` PUTS a node in the disabled state; `…@disabled`
|
|
1126
|
+
styles say how it LOOKS there.
|
|
1127
|
+
- The canonical state token is the base token path plus a state leaf
|
|
1128
|
+
(`color.foreground.primary.hover`), matching the token tree. Where a state-scoped
|
|
1129
|
+
value is a token path whose leaf is a state name, the leaf must agree with
|
|
1130
|
+
the key's `@state`.
|
|
1131
|
+
- One `@` per key in this version. Compound states (`@selected@hover`) are
|
|
1132
|
+
reserved, not available.
|
|
1133
|
+
|
|
566
1134
|
## Templates
|
|
567
1135
|
|
|
568
1136
|
### Basic Component Template
|
|
@@ -572,12 +1140,12 @@ node:
|
|
|
572
1140
|
uri: {tier}.component.{name}
|
|
573
1141
|
styles:
|
|
574
1142
|
# Structural (invariant)
|
|
575
|
-
layout.type:
|
|
576
|
-
layout.direction:
|
|
1143
|
+
layout.type: flex
|
|
1144
|
+
layout.direction: column
|
|
577
1145
|
|
|
578
1146
|
# Themeable
|
|
579
|
-
appearance.background: color
|
|
580
|
-
spacing.internal: spacing
|
|
1147
|
+
appearance.background: color.background
|
|
1148
|
+
spacing.internal.inline.start: spacing.inset.surface.block
|
|
581
1149
|
|
|
582
1150
|
edges:
|
|
583
1151
|
- node:
|
|
@@ -593,11 +1161,12 @@ node:
|
|
|
593
1161
|
node:
|
|
594
1162
|
uri: global.component.card
|
|
595
1163
|
styles:
|
|
596
|
-
layout.type:
|
|
597
|
-
layout.direction:
|
|
598
|
-
appearance.background: color
|
|
599
|
-
|
|
600
|
-
|
|
1164
|
+
layout.type: flex
|
|
1165
|
+
layout.direction: column
|
|
1166
|
+
appearance.background: color.background
|
|
1167
|
+
# A shadow has no key: `box-shadow` is one of the properties the roster
|
|
1168
|
+
# leaves unmapped, so it is not bound and this comment is the record.
|
|
1169
|
+
appearance.radius: dimension.radius.full
|
|
601
1170
|
|
|
602
1171
|
edges:
|
|
603
1172
|
# Named children reference their URI only (DRY Principle above) — each
|
|
@@ -627,9 +1196,9 @@ node:
|
|
|
627
1196
|
node:
|
|
628
1197
|
uri: global.component.accordion
|
|
629
1198
|
styles:
|
|
630
|
-
layout.type:
|
|
631
|
-
layout.direction:
|
|
632
|
-
appearance.border:
|
|
1199
|
+
layout.type: flex
|
|
1200
|
+
layout.direction: column
|
|
1201
|
+
appearance.border.style: solid
|
|
633
1202
|
|
|
634
1203
|
edges:
|
|
635
1204
|
# The repeating child is NAMED — reference its URI only (DRY Principle
|
|
@@ -650,22 +1219,22 @@ anonymous roles:
|
|
|
650
1219
|
node:
|
|
651
1220
|
uri: global.subcomponent.accordion-item
|
|
652
1221
|
styles:
|
|
653
|
-
layout.type:
|
|
654
|
-
layout.direction:
|
|
1222
|
+
layout.type: flex
|
|
1223
|
+
layout.direction: column
|
|
655
1224
|
edges:
|
|
656
1225
|
- node:
|
|
657
1226
|
role: header tab
|
|
658
1227
|
styles:
|
|
659
|
-
layout.type:
|
|
660
|
-
layout.direction:
|
|
1228
|
+
layout.type: flex
|
|
1229
|
+
layout.direction: row
|
|
661
1230
|
layout.align: center
|
|
662
1231
|
interaction.cursor: pointer
|
|
663
1232
|
edges:
|
|
664
1233
|
- node:
|
|
665
1234
|
role: control
|
|
666
1235
|
styles:
|
|
667
|
-
size.width:
|
|
668
|
-
size.height:
|
|
1236
|
+
size.width: dimension.300
|
|
1237
|
+
size.height: dimension.300
|
|
669
1238
|
relation:
|
|
670
1239
|
cardinality: "1"
|
|
671
1240
|
- node:
|
|
@@ -696,9 +1265,9 @@ Transcribed from the live `global.group.keyboard_keys`:
|
|
|
696
1265
|
node:
|
|
697
1266
|
uri: global.group.keyboard_keys
|
|
698
1267
|
styles:
|
|
699
|
-
layout.
|
|
1268
|
+
layout.type: inline-flex
|
|
700
1269
|
layout.align: center
|
|
701
|
-
spacing.gap: spacing
|
|
1270
|
+
spacing.gap: spacing.gap.mark.inline
|
|
702
1271
|
edges:
|
|
703
1272
|
- node:
|
|
704
1273
|
uri: global.component.keyboard_key
|
|
@@ -715,15 +1284,15 @@ Use when you need a structural element without design system identity:
|
|
|
715
1284
|
node:
|
|
716
1285
|
uri: global.pattern.modal
|
|
717
1286
|
styles:
|
|
718
|
-
layout.type:
|
|
719
|
-
appearance.background: color
|
|
1287
|
+
layout.type: flex
|
|
1288
|
+
appearance.background: color.background
|
|
720
1289
|
|
|
721
1290
|
edges:
|
|
722
1291
|
- node:
|
|
723
1292
|
role: backdrop overlay # anonymous - no uri
|
|
724
1293
|
styles:
|
|
725
|
-
layout.
|
|
726
|
-
appearance.background: color
|
|
1294
|
+
layout.type: block
|
|
1295
|
+
appearance.background: color.background.contrasted
|
|
727
1296
|
interaction.cursor: pointer
|
|
728
1297
|
relation:
|
|
729
1298
|
cardinality: "1..1"
|
|
@@ -731,9 +1300,9 @@ node:
|
|
|
731
1300
|
- node:
|
|
732
1301
|
role: content container # anonymous wrapper
|
|
733
1302
|
styles:
|
|
734
|
-
layout.type:
|
|
735
|
-
size.max.width:
|
|
736
|
-
spacing.internal: spacing
|
|
1303
|
+
layout.type: flex
|
|
1304
|
+
size.max.width: 40rem
|
|
1305
|
+
spacing.internal.inline.start: spacing.inset.surface.inline
|
|
737
1306
|
edges:
|
|
738
1307
|
- node:
|
|
739
1308
|
uri: global.subcomponent.modal-header
|
|
@@ -760,29 +1329,30 @@ node:
|
|
|
760
1329
|
uri: global.component.button
|
|
761
1330
|
styles:
|
|
762
1331
|
# Structural
|
|
763
|
-
layout.type:
|
|
764
|
-
layout.direction:
|
|
1332
|
+
layout.type: flex
|
|
1333
|
+
layout.direction: row
|
|
765
1334
|
layout.align: center
|
|
766
1335
|
layout.justify: center
|
|
767
1336
|
|
|
768
1337
|
# Themeable
|
|
769
|
-
spacing.internal: spacing
|
|
770
|
-
spacing.gap: spacing
|
|
771
|
-
appearance.background: color
|
|
772
|
-
appearance.radius:
|
|
773
|
-
typography.weight:
|
|
1338
|
+
spacing.internal.inline.start: spacing.inset.action.inline
|
|
1339
|
+
spacing.gap: spacing.gap.mark.inline
|
|
1340
|
+
appearance.background: color.foreground.primary
|
|
1341
|
+
appearance.radius: dimension.radius.medium
|
|
1342
|
+
typography.weight: typography.weight.medium
|
|
774
1343
|
|
|
775
1344
|
# Interaction
|
|
776
1345
|
interaction.cursor: pointer
|
|
777
|
-
|
|
778
|
-
|
|
1346
|
+
motion.property: background, transform
|
|
1347
|
+
# The transition also reads --motion-duration-fast, which no symbol declares,
|
|
1348
|
+
# so the duration is not bound.
|
|
779
1349
|
|
|
780
1350
|
edges:
|
|
781
1351
|
- node:
|
|
782
1352
|
role: icon
|
|
783
1353
|
styles:
|
|
784
|
-
size.width:
|
|
785
|
-
size.height:
|
|
1354
|
+
size.width: dimension.300
|
|
1355
|
+
size.height: dimension.300
|
|
786
1356
|
relation:
|
|
787
1357
|
cardinality: "0..1"
|
|
788
1358
|
slotName: icon
|
|
@@ -790,8 +1360,8 @@ node:
|
|
|
790
1360
|
- node:
|
|
791
1361
|
role: label
|
|
792
1362
|
styles:
|
|
793
|
-
typography.size:
|
|
794
|
-
typography.color: color
|
|
1363
|
+
typography.size: dimension.size.fontSize.200
|
|
1364
|
+
typography.color: color.text.onForegroundPrimary
|
|
795
1365
|
relation:
|
|
796
1366
|
cardinality: "0..1"
|
|
797
1367
|
slotName: default
|
|
@@ -848,6 +1418,27 @@ Ask:
|
|
|
848
1418
|
- Can any parts repeat?
|
|
849
1419
|
- Are there anonymous structural wrappers needed?
|
|
850
1420
|
|
|
1421
|
+
Two sources answer these, and they disagree more often than you would expect: the
|
|
1422
|
+
block's `ds:hasProperty` rows in the graph, and the implementation you are authoring
|
|
1423
|
+
from. The graph records what was DOCUMENTED; the implementation ships what was BUILT,
|
|
1424
|
+
and the two run ahead of each other in both directions. Chip is the live case — the
|
|
1425
|
+
graph lists `icon` and `badge` as properties that the current `ChipProps` does not
|
|
1426
|
+
have, and marks `value` as required where the code renders it conditionally.
|
|
1427
|
+
|
|
1428
|
+
**An anatomy transcribes the implementation, so where they disagree the implementation
|
|
1429
|
+
wins — and the disagreement is recorded, not smoothed over.** Write the tree the
|
|
1430
|
+
stylesheet and the component file support, put a `#` comment on the node (or in the
|
|
1431
|
+
file header) saying which graph row it departs from and why, and raise the mismatch:
|
|
1432
|
+
a property in the graph that the code cannot reach is a documentation gap, and a part
|
|
1433
|
+
in the code that the graph does not list is a specification gap. Neither is fixed by
|
|
1434
|
+
inventing a node for a property nothing renders, or by marking a node required because
|
|
1435
|
+
a row said `optional: "false"`.
|
|
1436
|
+
|
|
1437
|
+
The one exception is a block you were asked to author from its SPEC rather than from an
|
|
1438
|
+
implementation — a component not built yet. There the graph is all there is, and the
|
|
1439
|
+
anatomy follows it; say so in the file header, because the next reader cannot tell the
|
|
1440
|
+
two situations apart from the file alone.
|
|
1441
|
+
|
|
851
1442
|
### 3. Determine Tier and Type
|
|
852
1443
|
|
|
853
1444
|
The URI is `{tier}.{type}.{snake_name}` (see URI Encoding Convention above). The tier
|
|
@@ -882,21 +1473,88 @@ For each node, consider:
|
|
|
882
1473
|
- **Interaction**: Cursor, transitions (for interactive nodes)
|
|
883
1474
|
|
|
884
1475
|
**Rule of thumb**:
|
|
885
|
-
- Structural values (
|
|
886
|
-
-
|
|
887
|
-
|
|
888
|
-
### 6.
|
|
1476
|
+
- Structural values (`flex`, `center`, `100%`) = invariant
|
|
1477
|
+
- A symbol (spacing.inset.surface.block) = themeable
|
|
1478
|
+
|
|
1479
|
+
### 6. Bind Data and Pin Props
|
|
1480
|
+
|
|
1481
|
+
Both are optional, and both are decision gates — bring them to the person
|
|
1482
|
+
rather than deciding alone.
|
|
1483
|
+
|
|
1484
|
+
**Projections** — ask whether this component is a view over a known entity. If
|
|
1485
|
+
it is, give the root an `on`, then for each position ask: does it RENDER a field
|
|
1486
|
+
(`projection.field` on the node), or is it FILLED BY a traversal
|
|
1487
|
+
(`projection.field` on the relation)? Check every name against the provider SDL,
|
|
1488
|
+
and re-check that each relation's cardinality matches the field's nullability
|
|
1489
|
+
and multiplicity. Generic containers project nothing — that is a real answer.
|
|
1490
|
+
|
|
1491
|
+
**Pins** — for each named child, ask what the spec FIXES versus what it leaves
|
|
1492
|
+
to the consumer. An intrinsic icon is pinned (`props: { icon: … }`); a
|
|
1493
|
+
consumer-filled icon slot is not. Confirm each prop name against
|
|
1494
|
+
`pragma block lookup <Component>`; a pin naming a prop the component does not
|
|
1495
|
+
have is invisible to schema validation and will only fail later.
|
|
1496
|
+
|
|
1497
|
+
### 7. Validate
|
|
1498
|
+
|
|
1499
|
+
Two checks, and they cover different ground. Know which is which before you read an
|
|
1500
|
+
exit code as approval.
|
|
1501
|
+
|
|
1502
|
+
**What the gate checks.** `anatomies validate --authored` runs the law over every
|
|
1503
|
+
authored file: that the document parses, that the root is named, that every style key
|
|
1504
|
+
is in the roster, that every value has a lawful SHAPE, that every symbol-shaped element
|
|
1505
|
+
RESOLVES in a stratum, and that each symbol sits inside the namespace its key admits.
|
|
1506
|
+
That is a great deal, and it is all mechanical.
|
|
1507
|
+
|
|
1508
|
+
**What the gate does not check is whether the anatomy is TRUE of the component.** It
|
|
1509
|
+
cannot: it never reads the stylesheet. So every one of these passes the gate silently —
|
|
1510
|
+
a lawful key that is the wrong key for the CSS property (`layout.align` where the
|
|
1511
|
+
stylesheet wrote `align-self`), a resolving symbol in the right namespace that is the
|
|
1512
|
+
wrong symbol for the part, a node boundary drawn in the wrong place, a cardinality that
|
|
1513
|
+
contradicts the component's own props, a missing node for a part the stylesheet draws.
|
|
1514
|
+
Exit 0 with no findings means the file is LAWFUL. It does not mean the judgment calls
|
|
1515
|
+
were right, and it is not a second opinion on them. The check that catches those is the
|
|
1516
|
+
pull-request review by someone who can open the same stylesheet — which is why the file
|
|
1517
|
+
goes through review at all.
|
|
1518
|
+
|
|
1519
|
+
So read the list below as the author's own pass, and keep its items honest about which
|
|
1520
|
+
kind they are: the ones the gate will confirm, and the ones only a reader can.
|
|
889
1521
|
|
|
890
1522
|
Check your anatomy against:
|
|
891
1523
|
- [ ] Root node is named (has `uri`)
|
|
892
1524
|
- [ ] All cardinalities are valid notation
|
|
893
|
-
- [ ]
|
|
894
|
-
- [ ]
|
|
1525
|
+
- [ ] Every style key is in the roster — the gate confirms this one
|
|
1526
|
+
- [ ] Each key is the key for the CSS property the stylesheet actually wrote (judgment: `layout.align` for `align-items`, `layout.alignSelf` for `align-self`; the gate accepts either)
|
|
1527
|
+
- [ ] Every style value is one dotted symbol, a fallback chain with at most one primitive LAST, or a primitive — no slash path, no `?` marker, no `root`/`$root` segment
|
|
1528
|
+
- [ ] Every symbol resolves (`pragma token lookup <symbol>`) and sits in its key's namespace; a name that does not resolve is a `#` comment, never a value — the gate confirms both
|
|
1529
|
+
- [ ] Every component-local variable that came back `ENTITY_NOT_FOUND` was followed through the component's own `:root` to a declared variable or a literal before it was written off
|
|
1530
|
+
- [ ] `layout.type` and `layout.direction` are spelled as CSS spells them (`flex`, `grid`, `inline-flex`, `block`; `row`, `column`)
|
|
895
1531
|
- [ ] Anonymous nodes have `role`, not `uri`
|
|
896
1532
|
- [ ] Multi-word names encode word boundaries with `_`; `-` appears only where the name carries a dot (per the URI Encoding Convention table)
|
|
897
1533
|
- [ ] Every non-root `uri:` either resolves (`pragma graph inspect ds:<uri>`) or is `$custom`, a template placeholder, or a new block or child this same spec introduces (§3)
|
|
898
1534
|
- [ ] Named (`uri:`) children carry no copy of their own subtree — contextual style overrides only
|
|
899
|
-
- [ ] Nested components make semantic sense
|
|
1535
|
+
- [ ] Nested components make semantic sense (judgment — nothing checks this but a reader)
|
|
1536
|
+
- [ ] Every symbol is the right symbol for the part, not merely one that resolves in the key's namespace (judgment — the gate checks resolution and namespace, never fit)
|
|
1537
|
+
- [ ] Switch cases carry `uri` or `node` and nothing else — no `default:`
|
|
1538
|
+
- [ ] Every `@state` marker is one of hover, active, focus, disabled, selected, expanded, indeterminate, invalid — one `@` per key, never `@default`
|
|
1539
|
+
- [ ] No `@state` stands in for a structural difference — that is a `switch`
|
|
1540
|
+
- [ ] Every node `projection` has at least one of `on` / `field`; every relation `projection` has `field` and no `on`
|
|
1541
|
+
- [ ] No projection spells an unwrapping path (`edges.node`), and every projected field name exists in the provider SDL
|
|
1542
|
+
- [ ] Each projected relation's cardinality matches the field's nullability and multiplicity
|
|
1543
|
+
- [ ] `props` appears only on named nodes, holds scalars only, and every pinned prop name exists on that component (`pragma block lookup`)
|
|
1544
|
+
- [ ] A part the stylesheet draws as `::before`/`::after` is an anonymous `role:` node with a comment saying so, and no `slotName`
|
|
1545
|
+
- [ ] Every `uri:` names a block in this tier or in `global`, and a `global` anatomy reaches into no app tier
|
|
1546
|
+
|
|
1547
|
+
Then run the law over the file, which is the check no list can stand in for:
|
|
1548
|
+
|
|
1549
|
+
```bash
|
|
1550
|
+
bun src/cli.ts anatomies validate --authored
|
|
1551
|
+
```
|
|
1552
|
+
|
|
1553
|
+
Exit 0 with `0 findings` is the verdict on lawfulness; a finding names the file, the uri
|
|
1554
|
+
and the rule it broke, and a warning — `AT11`, a state binding differing from its base —
|
|
1555
|
+
is usually the implementation telling the truth. What exit 0 does NOT settle is
|
|
1556
|
+
anything in the paragraphs above: the semantic calls travel to review, so say in the
|
|
1557
|
+
pull request which ones you made and what you read to make them.
|
|
900
1558
|
|
|
901
1559
|
## Response Format
|
|
902
1560
|
|
|
@@ -924,9 +1582,12 @@ When creating an anatomy, respond with:
|
|
|
924
1582
|
- {Design decision 2}
|
|
925
1583
|
|
|
926
1584
|
### Next Steps
|
|
927
|
-
1. Land the
|
|
928
|
-
|
|
929
|
-
2.
|
|
1585
|
+
1. Land the file at `anatomies/authored/{tier}/{uri}.yaml` in the design-system
|
|
1586
|
+
repository — never under `data/`, which the pull sync regenerates
|
|
1587
|
+
2. `bun src/cli.ts anatomies validate --authored` — exit 0, and no findings
|
|
1588
|
+
3. Review it as a file, in a pull request
|
|
1589
|
+
4. `anatomies write` — dry, then a `--only <uri> --apply` canary, then `--apply`;
|
|
1590
|
+
after that the document is the source of record
|
|
930
1591
|
```
|
|
931
1592
|
|
|
932
1593
|
## Tips
|
|
@@ -1004,19 +1665,25 @@ The children one-to-many relation follows inspiration from the [Relay connection
|
|
|
1004
1665
|
* - Invariant (structural): layout.type, layout.direction, size.width: "fill"
|
|
1005
1666
|
* - Themeable (brand/theme): appearance.*, spacing.* (when using tokens)
|
|
1006
1667
|
*/
|
|
1007
|
-
|
|
1668
|
+
/**
|
|
1669
|
+
* Keys may carry an "@state" suffix scoping the value to one interaction
|
|
1670
|
+
* state: "appearance.background@hover". The unmarked key is the default
|
|
1671
|
+
* state; "@default" is invalid. Vocabulary is closed: hover, active, focus,
|
|
1672
|
+
* disabled, selected, expanded, indeterminate, invalid.
|
|
1673
|
+
*/
|
|
1674
|
+
type Styles = Record<StyleKey, Symbol | [...Symbol[], Primitive?] | Primitive>;
|
|
1008
1675
|
|
|
1009
1676
|
/**
|
|
1010
|
-
*
|
|
1011
|
-
* Examples: "spacing
|
|
1677
|
+
* Symbol: a token symbol's own dotted name, as the token graph declares it.
|
|
1678
|
+
* Examples: "spacing.inset.surface.block", "color.background", "typography.heading.1"
|
|
1012
1679
|
*/
|
|
1013
|
-
type
|
|
1680
|
+
type Symbol = string;
|
|
1014
1681
|
|
|
1015
1682
|
/**
|
|
1016
|
-
*
|
|
1017
|
-
* Examples: "
|
|
1683
|
+
* Primitive: a literal the implementation writes, resolved against nothing.
|
|
1684
|
+
* Examples: "flex", "center", "100%", 1.5, true
|
|
1018
1685
|
*/
|
|
1019
|
-
type
|
|
1686
|
+
type Primitive = string | number | boolean;
|
|
1020
1687
|
|
|
1021
1688
|
/* Main types */
|
|
1022
1689
|
|
|
@@ -1036,12 +1703,48 @@ interface Relation {
|
|
|
1036
1703
|
* Common values: "default", "header", "footer", "icon", "label"
|
|
1037
1704
|
*/
|
|
1038
1705
|
slotName?: string;
|
|
1706
|
+
|
|
1707
|
+
/** The traversal populating this slot from the current data context. */
|
|
1708
|
+
projection?: RelationProjection;
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
/**
|
|
1712
|
+
* Fragment-style binding of a node to graph data, after the Relay
|
|
1713
|
+
* fragment-colocation pattern. `on` is a GraphQL type condition, which
|
|
1714
|
+
* establishes the data context on the root and narrows it on a child;
|
|
1715
|
+
* `field` is the field the node renders, relative to the enclosing
|
|
1716
|
+
* context. At least one of the two is required.
|
|
1717
|
+
*/
|
|
1718
|
+
type Projection =
|
|
1719
|
+
| { on: string; field?: string }
|
|
1720
|
+
| { on?: string; field: string };
|
|
1721
|
+
|
|
1722
|
+
/**
|
|
1723
|
+
* A traversal populating a slot, carried by the reified Relation: the
|
|
1724
|
+
* field of the current data context whose value(s) fill this position.
|
|
1725
|
+
* Names the field ONLY — never an unwrapping path like `edges.node`,
|
|
1726
|
+
* since connection-vs-list is a provider mechanism. Type narrowing lives
|
|
1727
|
+
* on the child node's own `on`, never here.
|
|
1728
|
+
*/
|
|
1729
|
+
interface RelationProjection {
|
|
1730
|
+
field: string;
|
|
1039
1731
|
}
|
|
1040
1732
|
|
|
1733
|
+
/**
|
|
1734
|
+
* Pinned prop values: the anatomy FIXES props of the referenced component
|
|
1735
|
+
* at this position. Keys are props defined on the component in the design
|
|
1736
|
+
* system ontology; the DSL never defines the prop surface itself. Values
|
|
1737
|
+
* are scalars — no token paths, no fallback arrays. Named nodes only.
|
|
1738
|
+
*/
|
|
1739
|
+
type Props = Record<string, string | number | boolean>;
|
|
1740
|
+
|
|
1041
1741
|
/**
|
|
1042
1742
|
* Base interface for all nodes in the anatomy tree.
|
|
1043
1743
|
*/
|
|
1044
1744
|
interface BaseNode {
|
|
1745
|
+
/** Graph-data binding for this position. */
|
|
1746
|
+
projection?: Projection;
|
|
1747
|
+
|
|
1045
1748
|
/**
|
|
1046
1749
|
* Style properties using CTI-inspired keys.
|
|
1047
1750
|
*
|
|
@@ -1069,6 +1772,9 @@ interface NamedNode extends BaseNode {
|
|
|
1069
1772
|
* Examples: "global.component.button", "apps.layout.application_layout"
|
|
1070
1773
|
*/
|
|
1071
1774
|
uri: string;
|
|
1775
|
+
|
|
1776
|
+
/** Pinned prop values, e.g. { icon: "chevron-down" }. */
|
|
1777
|
+
props?: Props;
|
|
1072
1778
|
}
|
|
1073
1779
|
|
|
1074
1780
|
/**
|
|
@@ -1105,9 +1811,6 @@ interface Switch {
|
|
|
1105
1811
|
interface SwitchCase {
|
|
1106
1812
|
uri?: string;
|
|
1107
1813
|
node?: Node;
|
|
1108
|
-
|
|
1109
|
-
/** Marks the default case. */
|
|
1110
|
-
default?: boolean;
|
|
1111
1814
|
}
|
|
1112
1815
|
|
|
1113
1816
|
interface Edge {
|
|
@@ -1148,11 +1851,11 @@ node:
|
|
|
1148
1851
|
uri: global.component.accordion
|
|
1149
1852
|
styles:
|
|
1150
1853
|
# Structural (invariant)
|
|
1151
|
-
layout.type:
|
|
1152
|
-
layout.direction:
|
|
1854
|
+
layout.type: flex
|
|
1855
|
+
layout.direction: column
|
|
1153
1856
|
|
|
1154
1857
|
# Themeable
|
|
1155
|
-
appearance.border:
|
|
1858
|
+
appearance.border.style: solid
|
|
1156
1859
|
|
|
1157
1860
|
edges:
|
|
1158
1861
|
# The repeating child is NAMED — reference its URI only (DRY Principle);
|
|
@@ -1171,22 +1874,22 @@ The child's own anatomy — a separate spec on `global.subcomponent.accordion-it
|
|
|
1171
1874
|
node:
|
|
1172
1875
|
uri: global.subcomponent.accordion-item
|
|
1173
1876
|
styles:
|
|
1174
|
-
layout.type:
|
|
1175
|
-
layout.direction:
|
|
1877
|
+
layout.type: flex
|
|
1878
|
+
layout.direction: column
|
|
1176
1879
|
edges:
|
|
1177
1880
|
- node:
|
|
1178
1881
|
role: header tab
|
|
1179
1882
|
styles:
|
|
1180
|
-
layout.type:
|
|
1181
|
-
layout.direction:
|
|
1883
|
+
layout.type: flex
|
|
1884
|
+
layout.direction: row
|
|
1182
1885
|
layout.align: center
|
|
1183
1886
|
interaction.cursor: pointer
|
|
1184
1887
|
edges:
|
|
1185
1888
|
- node:
|
|
1186
1889
|
role: control
|
|
1187
1890
|
styles:
|
|
1188
|
-
size.width:
|
|
1189
|
-
size.height:
|
|
1891
|
+
size.width: dimension.300
|
|
1892
|
+
size.height: dimension.300
|
|
1190
1893
|
relation:
|
|
1191
1894
|
cardinality: "1"
|
|
1192
1895
|
- node:
|