@canonical/pragma-cli 0.35.0 → 0.37.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 +2 -2
- package/bundled-skills/add-standard/SKILL.md +338 -0
- package/bundled-skills/adoption-a1-styles/SKILL.md +269 -0
- package/bundled-skills/adoption-a2-components/SKILL.md +291 -0
- package/bundled-skills/adoption-a3-forms/SKILL.md +260 -0
- package/bundled-skills/anatomy-author/ANATOMY_DSL_SPEC.md +406 -0
- package/bundled-skills/anatomy-author/SKILL.md +1226 -0
- package/bundled-skills/design-auditor/SKILL.md +455 -0
- package/bundled-skills/implement-component/SKILL.md +333 -0
- package/bundled-skills/specify-component/SKILL.md +331 -0
- package/bundled-skills/specify-pattern/SKILL.md +292 -0
- package/dist/package.json +24 -20
- package/dist/pragma.conf.js +266 -34
- package/dist/pragma.conf.js.map +1 -1
- package/dist/src/bin.js +1 -1
- package/dist/src/bin.js.map +1 -1
- package/dist/src/capabilities/capabilities/hints.js +1 -1
- package/dist/src/capabilities/capabilities/hints.js.map +1 -1
- package/dist/src/capabilities/colophon/colophon.render.js +1 -1
- package/dist/src/capabilities/colophon/colophon.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/create/mount.js +4 -3
- package/dist/src/capabilities/create/mount.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/checkPackageRefs.js +60 -4
- package/dist/src/capabilities/doctor/checks/checkPackageRefs.js.map +1 -1
- package/dist/src/capabilities/doctor/checks/checkShellCompletions.js +8 -8
- package/dist/src/capabilities/doctor/checks/checkShellCompletions.js.map +1 -1
- package/dist/src/capabilities/doctor/checks/harnessInventory.js +195 -0
- package/dist/src/capabilities/doctor/checks/harnessInventory.js.map +1 -0
- package/dist/src/capabilities/doctor/checks/index.js +6 -6
- 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 +194 -50
- package/dist/src/capabilities/doctor/checks/targetHealth.js.map +1 -1
- package/dist/src/capabilities/doctor/doctor.render.js +43 -64
- package/dist/src/capabilities/doctor/doctor.render.js.map +1 -1
- package/dist/src/capabilities/doctor/doctor.verb.js +6 -2
- package/dist/src/capabilities/doctor/doctor.verb.js.map +1 -1
- package/dist/src/capabilities/doctor/runChecks.js +14 -11
- package/dist/src/capabilities/doctor/runChecks.js.map +1 -1
- package/dist/src/capabilities/graph/query.render.js +2 -2
- package/dist/src/capabilities/graph/query.render.js.map +1 -1
- package/dist/src/capabilities/info/collectInfo.js +8 -2
- package/dist/src/capabilities/info/collectInfo.js.map +1 -1
- package/dist/src/capabilities/info/info.render.js +6 -2
- package/dist/src/capabilities/info/info.render.js.map +1 -1
- package/dist/src/capabilities/prompt/prompt.render.js +2 -2
- package/dist/src/capabilities/prompt/prompt.render.js.map +1 -1
- package/dist/src/capabilities/setup/buildPlan.js +43 -39
- package/dist/src/capabilities/setup/buildPlan.js.map +1 -1
- package/dist/src/capabilities/setup/mode.js +2 -2
- package/dist/src/capabilities/setup/operations/index.js +4 -4
- 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 +156 -58
- package/dist/src/capabilities/setup/operations/setupGenerator.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupLsp.js +136 -38
- package/dist/src/capabilities/setup/operations/setupLsp.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupMcp.js +101 -52
- package/dist/src/capabilities/setup/operations/setupMcp.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupSkills.js +329 -49
- package/dist/src/capabilities/setup/operations/setupSkills.js.map +1 -1
- package/dist/src/capabilities/setup/plan.js.map +1 -1
- package/dist/src/capabilities/setup/plan.render.js +144 -66
- package/dist/src/capabilities/setup/plan.render.js.map +1 -1
- package/dist/src/capabilities/setup/setup.render.js +1 -1
- package/dist/src/capabilities/setup/setup.verb.js +165 -63
- package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
- package/dist/src/capabilities/setup/targets.js +122 -51
- package/dist/src/capabilities/setup/targets.js.map +1 -1
- package/dist/src/capabilities/shared/index.js +7 -6
- package/dist/src/capabilities/shared/index.js.map +1 -1
- package/dist/src/capabilities/shared/packageManager.js +300 -54
- package/dist/src/capabilities/shared/packageManager.js.map +1 -1
- package/dist/src/capabilities/skill/discover.js +97 -11
- package/dist/src/capabilities/skill/discover.js.map +1 -1
- package/dist/src/capabilities/skill/render.js +2 -2
- package/dist/src/capabilities/skill/render.js.map +1 -1
- package/dist/src/capabilities/sources/installSkills.js +145 -3
- package/dist/src/capabilities/sources/installSkills.js.map +1 -1
- package/dist/src/capabilities/sources/runUpdate.js +68 -2
- package/dist/src/capabilities/sources/runUpdate.js.map +1 -1
- package/dist/src/capabilities/sources/update.verb.js +3 -3
- package/dist/src/capabilities/sources/update.verb.js.map +1 -1
- package/dist/src/capabilities/upgrade/runUpgrade.js +43 -7
- package/dist/src/capabilities/upgrade/runUpgrade.js.map +1 -1
- package/dist/src/capabilities/upgrade/upgrade.render.js +10 -2
- package/dist/src/capabilities/upgrade/upgrade.render.js.map +1 -1
- package/dist/src/constants.js +13 -2
- package/dist/src/constants.js.map +1 -1
- package/dist/src/kernel/index.js +11 -2
- package/dist/src/kernel/index.js.map +1 -1
- package/dist/src/kernel/interactivity.js +68 -0
- package/dist/src/kernel/interactivity.js.map +1 -0
- package/dist/src/kernel/packs/renderPack.js +3 -3
- package/dist/src/kernel/packs/renderPack.js.map +1 -1
- package/dist/src/kernel/packs/resolveEntity.js +63 -12
- package/dist/src/kernel/packs/resolveEntity.js.map +1 -1
- package/dist/src/kernel/packs/runBodies.js +57 -4
- package/dist/src/kernel/packs/runBodies.js.map +1 -1
- package/dist/src/kernel/packs/schema.js +54 -2
- package/dist/src/kernel/packs/schema.js.map +1 -1
- package/dist/src/kernel/packs/sparql/applyFilters.js +104 -5
- package/dist/src/kernel/packs/sparql/applyFilters.js.map +1 -1
- package/dist/src/kernel/packs/sparql/buildLookupQuery.js +310 -43
- package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -1
- package/dist/src/kernel/packs/types.js.map +1 -1
- package/dist/src/kernel/project/cli/buildProgram.js +1 -1
- package/dist/src/kernel/project/cli/buildProgram.js.map +1 -1
- package/dist/src/kernel/project/cli/dispatch.js +46 -24
- package/dist/src/kernel/project/cli/dispatch.js.map +1 -1
- package/dist/src/kernel/project/cli/globalFlags.js +4 -1
- package/dist/src/kernel/project/cli/globalFlags.js.map +1 -1
- package/dist/src/kernel/project/cli/helpFormat.js +1 -2
- package/dist/src/kernel/project/cli/helpFormat.js.map +1 -1
- package/dist/src/kernel/project/cli/rootHelp.js +42 -9
- package/dist/src/kernel/project/cli/rootHelp.js.map +1 -1
- package/dist/src/kernel/project/mcp/registerVerb.js +16 -1
- package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -1
- package/dist/src/kernel/render/renderers.js +1 -1
- package/dist/src/kernel/render/style.js +29 -9
- package/dist/src/kernel/render/style.js.map +1 -1
- package/dist/src/kernel/render/vocabulary.js +174 -0
- package/dist/src/kernel/render/vocabulary.js.map +1 -0
- 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 +14 -9
- 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/refs/parseRef.js +17 -1
- package/dist/src/kernel/runtime/refs/parseRef.js.map +1 -1
- package/dist/src/kernel/runtime/refs/resolve.js +9 -3
- 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 +24 -20
- package/pragma.conf.ts +272 -34
- package/src/bin.ts +8 -1
- package/src/capabilities/block.parity.test.ts +25 -11
- package/src/capabilities/block.shipped.exec.test.ts +282 -0
- package/src/capabilities/block.tierRank.exec.test.ts +300 -0
- package/src/capabilities/capabilities/hints.ts +1 -1
- package/src/capabilities/colophon/colophon.render.ts +1 -2
- package/src/capabilities/colophon/colophon.test.ts +2 -2
- package/src/capabilities/config/collectConfigShow.test.ts +1 -0
- 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 +5 -2
- package/src/capabilities/create/createSurface.generated.ts +23 -1
- package/src/capabilities/create/crossCli.subprocess.test.ts +10 -3
- package/src/capabilities/create/mount.ts +3 -3
- package/src/capabilities/distribution.parity.test.ts +4 -0
- package/src/capabilities/doctor/checks/checkNodeVersion.test.ts +49 -0
- package/src/capabilities/doctor/checks/checkNodeVersion.ts +11 -7
- package/src/capabilities/doctor/checks/checkPackageRefs.ts +69 -4
- package/src/capabilities/doctor/checks/checkShellCompletions.test.ts +6 -4
- package/src/capabilities/doctor/checks/checkShellCompletions.ts +8 -8
- package/src/capabilities/doctor/checks/harnessInventory.test.ts +219 -0
- package/src/capabilities/doctor/checks/harnessInventory.ts +298 -0
- package/src/capabilities/doctor/checks/index.ts +6 -6
- 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 +229 -59
- package/src/capabilities/doctor/doctor.render.test.ts +108 -32
- package/src/capabilities/doctor/doctor.render.ts +56 -80
- package/src/capabilities/doctor/doctor.test.ts +247 -35
- package/src/capabilities/doctor/doctor.verb.ts +7 -2
- package/src/capabilities/doctor/runChecks.ts +14 -11
- package/src/capabilities/doctor/types.ts +15 -24
- package/src/capabilities/emptyRecovery.test.ts +10 -2
- package/src/capabilities/graph/query.render.ts +2 -2
- package/src/capabilities/info/collectInfo.ts +8 -1
- package/src/capabilities/info/info.render.ts +6 -2
- package/src/capabilities/info/info.test.ts +37 -1
- package/src/capabilities/info/types.ts +10 -2
- package/src/capabilities/lazy.test.ts +26 -2
- package/src/capabilities/listLookup.shipped.exec.test.ts +340 -0
- package/src/capabilities/prompt/prompt.render.ts +2 -2
- package/src/capabilities/renderVocabularySync.test.ts +82 -0
- package/src/capabilities/resources/resources.test.ts +38 -2
- package/src/capabilities/scopeTypeSync.test.ts +29 -15
- package/src/capabilities/setup/buildPlan.ts +45 -41
- package/src/capabilities/setup/index.ts +1 -1
- package/src/capabilities/setup/mode.ts +2 -2
- package/src/capabilities/setup/operations/index.ts +6 -1
- 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 +227 -84
- package/src/capabilities/setup/operations/setupLsp.ts +203 -38
- package/src/capabilities/setup/operations/setupMcp.ts +139 -62
- package/src/capabilities/setup/operations/setupSkills.ts +399 -60
- package/src/capabilities/setup/plan.render.ts +171 -76
- package/src/capabilities/setup/plan.ts +17 -12
- package/src/capabilities/setup/setup.mcp.correctness.test.ts +9 -9
- package/src/capabilities/setup/setup.render.test.ts +50 -36
- package/src/capabilities/setup/setup.render.ts +1 -1
- package/src/capabilities/setup/setup.test.ts +1126 -50
- package/src/capabilities/setup/setup.verb.ts +174 -68
- package/src/capabilities/setup/targets.ts +138 -60
- package/src/capabilities/setup/types.ts +10 -18
- package/src/capabilities/shared/index.ts +18 -6
- package/src/capabilities/shared/packageManager.test.ts +421 -6
- package/src/capabilities/shared/packageManager.ts +394 -59
- package/src/capabilities/skill/bundledSkills.test.ts +347 -0
- package/src/capabilities/skill/discover.ts +100 -11
- package/src/capabilities/skill/render.ts +2 -2
- package/src/capabilities/skill/skill.test.ts +17 -4
- package/src/capabilities/sources/installSkills.ts +181 -3
- package/src/capabilities/sources/runUpdate.ts +78 -2
- package/src/capabilities/sources/sources.test.ts +228 -0
- package/src/capabilities/sources/update.verb.ts +3 -3
- package/src/capabilities/surface.test.ts +14 -6
- package/src/capabilities/upgrade/runUpgrade.ts +45 -6
- package/src/capabilities/upgrade/types.ts +10 -2
- package/src/capabilities/upgrade/upgrade.render.ts +9 -2
- package/src/capabilities/upgrade/upgrade.test.ts +72 -0
- package/src/constants.ts +14 -1
- package/src/identity.test.ts +18 -5
- package/src/kernel/completion/entitySource.test.ts +15 -2
- package/src/kernel/completion/safety.test.ts +16 -9
- package/src/kernel/config/defaults.test.ts +19 -3
- package/src/kernel/config/readConfig.test.ts +1 -1
- package/src/kernel/index.ts +11 -2
- package/src/kernel/interactivity.test.ts +76 -0
- package/src/kernel/interactivity.ts +69 -0
- package/src/kernel/packs/compile.test.ts +92 -0
- package/src/kernel/packs/renderPack.ts +3 -3
- package/src/kernel/packs/resolveEntity.test.ts +75 -14
- package/src/kernel/packs/resolveEntity.ts +88 -24
- package/src/kernel/packs/runBodies.ts +84 -19
- package/src/kernel/packs/schema.ts +60 -4
- package/src/kernel/packs/sparql/applyFilters.ts +125 -4
- package/src/kernel/packs/sparql/buildLookupQuery.test.ts +290 -0
- package/src/kernel/packs/sparql/buildLookupQuery.ts +327 -45
- package/src/kernel/packs/toolDescription.test.ts +35 -1
- package/src/kernel/packs/types.ts +125 -2
- package/src/kernel/project/cli/buildProgram.test.ts +73 -2
- package/src/kernel/project/cli/buildProgram.ts +8 -1
- package/src/kernel/project/cli/dispatch.test.ts +5 -4
- package/src/kernel/project/cli/dispatch.ts +48 -25
- package/src/kernel/project/cli/globalFlags.ts +4 -1
- package/src/kernel/project/cli/helpFormat.ts +1 -2
- package/src/kernel/project/cli/rootHelp.ts +44 -8
- package/src/kernel/project/mcp/registerVerb.ts +20 -1
- package/src/kernel/render/prefixes.test.ts +2 -0
- package/src/kernel/render/renderers.ts +1 -1
- package/src/kernel/render/style.ts +32 -9
- package/src/kernel/render/vocabulary.ts +276 -0
- 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 +14 -12
- package/src/kernel/runtime/loadSession.ts +3 -1
- package/src/kernel/runtime/probe.ts +1 -1
- 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 +9 -3
- package/src/kernel/runtime/types.ts +17 -0
- package/src/kernel/spec/emitReference.ts +1 -1
- package/src/kernel/spec/types.ts +35 -6
- package/src/testing/PARITY_GAPS.ts +9 -3
- package/src/testing/behavioral/docExamples.test.ts +4 -2
- package/src/testing/behavioral/errorMatrix.mcp.test.ts +110 -7
- package/src/testing/behavioral/journeys.firstInstall.test.ts +7 -2
- package/src/testing/behavioral/journeys.packageStories.test.ts +6 -3
- package/src/testing/eval/__snapshots__/eval.test.ts.snap +26 -1
- package/src/testing/eval/cases/readNouns.ts +245 -9
- package/src/testing/fixtures/blockGraph.ts +28 -13
- package/src/testing/fixtures/graph/canonical.ts +95 -12
- package/src/testing/helpers/runCli.test.ts +44 -0
- package/src/testing/helpers/runCli.ts +27 -3
- package/dist/src/capabilities/shared/bands.js +0 -16
- package/dist/src/capabilities/shared/bands.js.map +0 -1
- package/src/capabilities/shared/bands.ts +0 -16
|
@@ -0,0 +1,1226 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: anatomy-author
|
|
3
|
+
description: Write component anatomy specifications using the Design System Anatomy DSL; also runs as a guided tutorial on a worked example
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Anatomy Author
|
|
7
|
+
|
|
8
|
+
Write component anatomy specifications using the Design System Anatomy DSL. This skill combines semantic discovery (understanding existing components) with procedural authoring (creating well-formed YAML specifications).
|
|
9
|
+
|
|
10
|
+
## When to Use
|
|
11
|
+
|
|
12
|
+
- Writing anatomy specifications for new or existing components
|
|
13
|
+
- Documenting component structure before implementation
|
|
14
|
+
- Discussing component composition with the team ("these nodes should be siblings, not parent-child")
|
|
15
|
+
- Auditing a codebase against its specifications
|
|
16
|
+
- Supporting code generation from specifications
|
|
17
|
+
- Serving as step 7 of the `specify-component` flow (and of `specify-pattern`)
|
|
18
|
+
|
|
19
|
+
## Description
|
|
20
|
+
|
|
21
|
+
The Anatomy DSL represents design system components as YAML trees with:
|
|
22
|
+
- **Named nodes**: Components with URIs (`global.component.button`)
|
|
23
|
+
- **Anonymous nodes**: Structural elements with roles (`content wrapper`)
|
|
24
|
+
- **Edges**: Parent-child relationships with cardinality and slot names
|
|
25
|
+
- **Styles**: Platform-agnostic properties using CTI-inspired keys
|
|
26
|
+
|
|
27
|
+
This skill helps you author these specifications correctly and consistently.
|
|
28
|
+
|
|
29
|
+
## Working mode: assistant to a design authority
|
|
30
|
+
|
|
31
|
+
Most of the time this skill runs in collaboration with a senior designer or engineer —
|
|
32
|
+
they are the design authority, you are the assistant whose job is to make them
|
|
33
|
+
successful. Default to that mode: lay out where the flow stands, do the legwork
|
|
34
|
+
(discovery queries, template drafting, validation) and return digestible findings, and
|
|
35
|
+
bring structural decisions to the person as a recommendation plus a question rather
|
|
36
|
+
than a fait accompli. An anatomy encodes opinions the graph cannot settle alone —
|
|
37
|
+
draw the person's knowledge out and use it.
|
|
38
|
+
|
|
39
|
+
Full autonomy is the exception, not the default: run the whole flow alone only when
|
|
40
|
+
explicitly asked to. Even then, list every judgment call made unilaterally in the
|
|
41
|
+
final report so a human can revisit them.
|
|
42
|
+
|
|
43
|
+
Decision gates — in collaboration, pause at each and resolve it WITH the person:
|
|
44
|
+
|
|
45
|
+
1. Node boundaries: what is a named subcomponent vs. an anonymous structural role.
|
|
46
|
+
2. Slot names — they become API surface.
|
|
47
|
+
3. Cardinality choices, especially optional vs. required parts.
|
|
48
|
+
4. What the anatomy fixes vs. what is deliberately left free.
|
|
49
|
+
|
|
50
|
+
The full specification ships beside this file as `ANATOMY_DSL_SPEC.md` in the
|
|
51
|
+
installed skill folder — `pragma skill lookup anatomy-author` renders only this
|
|
52
|
+
SKILL.md, so open the sibling file directly when you need the complete spec (the
|
|
53
|
+
appendix below carries its type system).
|
|
54
|
+
|
|
55
|
+
## Opening move: ask, or offer the tutorial
|
|
56
|
+
|
|
57
|
+
Activation is an opening, not a starting gun. Unless the first message already names
|
|
58
|
+
the work, ask for the starting point — as a suggestion carrying an example, not as a
|
|
59
|
+
form to fill in:
|
|
60
|
+
|
|
61
|
+
> To get started, tell me the component whose anatomy we're writing — a name if it is
|
|
62
|
+
> already in the graph, or just a sketch of its parts, like "a card with an image, a
|
|
63
|
+
> title and a row of actions".
|
|
64
|
+
|
|
65
|
+
A sketch is enough to begin: discovery turns it into the real node set, and the sketch
|
|
66
|
+
is what you check that set against. Ask again only for what the next step genuinely
|
|
67
|
+
blocks on.
|
|
68
|
+
|
|
69
|
+
If the activation message already carries the starting point, do not re-ask — say what
|
|
70
|
+
you took it to be, and go.
|
|
71
|
+
|
|
72
|
+
Offer the tutorial in the same breath, because this skill doubles as one:
|
|
73
|
+
|
|
74
|
+
> Or if you'd rather see the flow first, I can run it as a tutorial: I'll take a
|
|
75
|
+
> plausible example — a card, say — and walk you through it from discovery to the
|
|
76
|
+
> emitted anatomy.
|
|
77
|
+
|
|
78
|
+
Tutorial mode is the step narration below, turned up: the same outcome-path-conclusion
|
|
79
|
+
frame, with the reasoning at each decision gate made fully explicit and a check that
|
|
80
|
+
the person is with you before the next step. Stop short of anything that lands — no
|
|
81
|
+
anatomy written into the graph, no file committed — unless they ask to keep what you
|
|
82
|
+
built.
|
|
83
|
+
|
|
84
|
+
## Every step is narrated: outcome, path, conclusion
|
|
85
|
+
|
|
86
|
+
Every step is addressed to the person — once before it runs, once when it ends. This is
|
|
87
|
+
not tutorial manner. It holds in the ordinary flow too: the person is the design
|
|
88
|
+
authority, and they can only steer a step they saw coming. A decision gate met cold is
|
|
89
|
+
a decision they cannot really make.
|
|
90
|
+
|
|
91
|
+
**Open** the step with what it is FOR — the outcome it should leave behind — and how
|
|
92
|
+
you are about to get there. **Close** it with a sentence saying what it established and
|
|
93
|
+
what that means for the step after. One sentence each is enough; the failure mode is
|
|
94
|
+
silence, not length, and a step whose outcome looks obvious still gets its sentence
|
|
95
|
+
rather than a shrug.
|
|
96
|
+
|
|
97
|
+
Do:
|
|
98
|
+
|
|
99
|
+
> **Discovery, before any DSL.** The outcome is the component's real context — what it
|
|
100
|
+
> is composed of, and what already names its parts — so the anatomy describes the
|
|
101
|
+
> system instead of inventing beside it. The path is `pragma block lookup Card`, then
|
|
102
|
+
> the subcomponent query below.
|
|
103
|
+
>
|
|
104
|
+
> …
|
|
105
|
+
>
|
|
106
|
+
> So: Card already carries a named `Card-Header` subcomponent and no named body — the
|
|
107
|
+
> anatomy inherits the first and has to decide the second, which is the first
|
|
108
|
+
> structural gate.
|
|
109
|
+
|
|
110
|
+
Don't:
|
|
111
|
+
|
|
112
|
+
> Discovery
|
|
113
|
+
>
|
|
114
|
+
> Card exists. Has Card-Header. No body node.
|
|
115
|
+
|
|
116
|
+
Both carry the same facts. Only the first says what the step was trying to achieve and
|
|
117
|
+
what it settled — and only the first lets the person cut in with the thing they know
|
|
118
|
+
and the graph does not, which is the whole reason they are here.
|
|
119
|
+
|
|
120
|
+
## Asked about the skill: methodology and outcomes first
|
|
121
|
+
|
|
122
|
+
A question about what this skill does — what it covers, what its steps are, how it
|
|
123
|
+
works — is answered in that order: the METHOD it applies and the OUTCOMES it leaves
|
|
124
|
+
the person holding come first, the step-by-step breakdown comes after.
|
|
125
|
+
|
|
126
|
+
Two or three sentences of method is enough: what the skill treats as its object, the
|
|
127
|
+
discipline that makes it work, and what exists at the end that did not exist before.
|
|
128
|
+
The enumeration then reads as steps in service of something, rather than as a list to
|
|
129
|
+
be got through.
|
|
130
|
+
|
|
131
|
+
What this prevents is a table of contents standing in for an answer. A reply opening
|
|
132
|
+
with the step names tells someone who already knows the skill nothing new, and someone
|
|
133
|
+
who does not, nothing at all.
|
|
134
|
+
|
|
135
|
+
So, asked what this skill is:
|
|
136
|
+
|
|
137
|
+
> It writes component anatomies in the Anatomy DSL, combining semantic discovery —
|
|
138
|
+
> understanding what the graph already names — with procedural authoring. The
|
|
139
|
+
> structural choices are the substance: node boundaries, cardinality, and slot names,
|
|
140
|
+
> which become API surface. You end holding a well-formed anatomy whose every
|
|
141
|
+
> deliberate decision is one someone can revisit.
|
|
142
|
+
|
|
143
|
+
Then, and only then, the breakdown.
|
|
144
|
+
|
|
145
|
+
## Anatomy falls under standards too
|
|
146
|
+
|
|
147
|
+
Emitted anatomy is Turtle, and Turtle has standards — the `turtle` category, with
|
|
148
|
+
`ui-blocks` governing block modelling (take the actual set from
|
|
149
|
+
`pragma standard categories`, not from this list). Pull them before authoring and hold
|
|
150
|
+
the Do/Don't pairs open while writing the DSL and its emitted TTL; a deviation from a
|
|
151
|
+
pulled standard is RECORDED next to the anatomy, ideally also filed as an issue. The
|
|
152
|
+
standards apply independent of any package dependency and are open to contribution:
|
|
153
|
+
a missing or wrong modelling rule is something to propose a change for, not to
|
|
154
|
+
silently work around.
|
|
155
|
+
|
|
156
|
+
## Discovery Flow
|
|
157
|
+
|
|
158
|
+
Before writing an anatomy, understand the component's context:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
pragma block list # every component, pattern, layout and subcomponent, with its type and tier
|
|
162
|
+
pragma block lookup Card # full spec: anatomy, modifiers, properties (MCP: block_lookup)
|
|
163
|
+
pragma graph inspect ds:global.component.card # every triple on the entity
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
A bare name can match blocks in several tiers, and `block lookup` silently picks one —
|
|
167
|
+
it resolves `ds:name` globally and cannot be steered to a tier.
|
|
168
|
+
Confirm the tier the lookup picked from its own `- Tier:` line; the name query below lists every tier that carries the name.
|
|
169
|
+
The name query in Workflow §1 is the one that lists them, with each block's IRI. If the
|
|
170
|
+
lookup picked the wrong tier's block, read the block you want with
|
|
171
|
+
`pragma graph inspect <IRI from that row>`, so the lookup step and the inspect step
|
|
172
|
+
describe the same block. If the name has no row at all, the block may be a Group —
|
|
173
|
+
`block list` covers no groups; the group pre-step in Workflow §1 lists them.
|
|
174
|
+
|
|
175
|
+
### Discovery Queries
|
|
176
|
+
|
|
177
|
+
Common prefixes (`ds:`, `cs:`) are applied automatically — no PREFIX preamble needed.
|
|
178
|
+
|
|
179
|
+
**Find components by tier:**
|
|
180
|
+
```bash
|
|
181
|
+
pragma graph query "SELECT ?component ?name WHERE {
|
|
182
|
+
?component a ds:Component ;
|
|
183
|
+
ds:name ?name ;
|
|
184
|
+
ds:tier ds:global .
|
|
185
|
+
}"
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Find subcomponents of a component:**
|
|
189
|
+
```bash
|
|
190
|
+
pragma graph query "SELECT ?sub ?name WHERE {
|
|
191
|
+
?sub a ds:Subcomponent ;
|
|
192
|
+
ds:name ?name ;
|
|
193
|
+
ds:parentComponent <https://ds.canonical.com/global.component.card> .
|
|
194
|
+
}"
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Inside a SPARQL body, a local name with MORE THAN ONE dot (`ds:global.component.card`)
|
|
198
|
+
does not parse — use the full IRI there: join `https://ds.canonical.com/` with the
|
|
199
|
+
dotted local name (giving `<https://ds.canonical.com/global.component.card>`), or copy
|
|
200
|
+
one from `pragma graph query` output, which prints absolute IRIs. Prefixed names with
|
|
201
|
+
no dot (`ds:global`, `ds:Component`) or a single dot (`ds:tag.needsdefinition`) work
|
|
202
|
+
as-is.
|
|
203
|
+
|
|
204
|
+
### The empty-anatomy worklist
|
|
205
|
+
|
|
206
|
+
The Coda sync emits `ds:anatomyDsl` as an empty string for blocks whose anatomy is not
|
|
207
|
+
yet written — this query IS the to-do list; pick from it, never from a copied table:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
pragma graph query "SELECT ?b ?name WHERE { ?b ds:anatomyDsl ?a ; ds:name ?name . FILTER(STR(?a) = '') }"
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The worklist rows point at blocks whose data lives under `data/` — never hand-edit
|
|
214
|
+
that tree: it is regenerated destructively from Coda by CI, and hand edits are
|
|
215
|
+
overwritten by the next sync. An authored DSL lands in the block's spec draft under
|
|
216
|
+
`specs/` (see `specs/README.md`) or is pasted into Coda by a human.
|
|
217
|
+
|
|
218
|
+
## DSL Reference
|
|
219
|
+
|
|
220
|
+
### YAML Format
|
|
221
|
+
|
|
222
|
+
All anatomy files must start with `---` (YAML document start marker):
|
|
223
|
+
|
|
224
|
+
```yaml
|
|
225
|
+
---
|
|
226
|
+
node:
|
|
227
|
+
uri: global.component.button
|
|
228
|
+
# ...
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Type System Overview
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
AnatomySpec
|
|
235
|
+
-> node: NamedNode (root must be named)
|
|
236
|
+
|
|
237
|
+
NamedNode
|
|
238
|
+
-> uri: string ("tier.type.name")
|
|
239
|
+
-> styles?: Record<string, TokenPath | TokenPath[] | PrimitiveValue>
|
|
240
|
+
-> edges?: Edge[]
|
|
241
|
+
|
|
242
|
+
AnonymousNode
|
|
243
|
+
-> role: string ("content wrapper")
|
|
244
|
+
-> styles?: Record<string, TokenPath | TokenPath[] | PrimitiveValue>
|
|
245
|
+
-> edges?: Edge[]
|
|
246
|
+
|
|
247
|
+
Edge
|
|
248
|
+
-> node?: NamedNode | AnonymousNode
|
|
249
|
+
-> switch?: Switch (mutually exclusive with node)
|
|
250
|
+
-> relation: Relation
|
|
251
|
+
|
|
252
|
+
Switch
|
|
253
|
+
-> on: "props" | "internal" | "override"
|
|
254
|
+
-> cases: SwitchCase[]
|
|
255
|
+
|
|
256
|
+
SwitchCase
|
|
257
|
+
-> uri?: string (shorthand)
|
|
258
|
+
-> node?: Node (full form)
|
|
259
|
+
-> default?: boolean (marks the default case)
|
|
260
|
+
|
|
261
|
+
Relation
|
|
262
|
+
-> cardinality: string ("1", "0..1", "0..*", "1..*")
|
|
263
|
+
-> slotName?: string ("default", "header", "icon")
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### URI Encoding Convention
|
|
267
|
+
|
|
268
|
+
URIs follow turtle conventions with dot-separated paths: `tier.type.name`
|
|
269
|
+
|
|
270
|
+
| Symbol | Meaning | Example |
|
|
271
|
+
|--------|---------|---------|
|
|
272
|
+
| `.` | Path hierarchy | `global.component.button` |
|
|
273
|
+
| `_` | Word boundary from PascalCase | `ChartLegend` → `chart_legend` |
|
|
274
|
+
| `-` | Dot in compound names | `Card.Header` → `card-header` |
|
|
275
|
+
|
|
276
|
+
**Examples:**
|
|
277
|
+
|
|
278
|
+
| Component | URI |
|
|
279
|
+
|-----------|-----|
|
|
280
|
+
| `Button` | `global.component.button` |
|
|
281
|
+
| `Accordion.Item` | `global.subcomponent.accordion-item` |
|
|
282
|
+
| `ChartLegend` | `global.subcomponent.chart_legend` |
|
|
283
|
+
| `Card.Header` | `global.subcomponent.card-header` |
|
|
284
|
+
|
|
285
|
+
**Tiers and Types:**
|
|
286
|
+
|
|
287
|
+
The tier segment comes from the live tier set — run `pragma tier list` for the tiers
|
|
288
|
+
that exist today; never assign a tier from a remembered list. The type segment is one
|
|
289
|
+
of the ontology's UIBlock classes:
|
|
290
|
+
|
|
291
|
+
| Type | Use Case |
|
|
292
|
+
|------|----------|
|
|
293
|
+
| `component` | Standalone components |
|
|
294
|
+
| `subcomponent` | Parts of components (user-instantiable) |
|
|
295
|
+
| `pattern` | UX solutions combining components |
|
|
296
|
+
| `layout` | Space-dividing containers |
|
|
297
|
+
| `group` | Repeating series of ONE sibling block (the plural of a Component) |
|
|
298
|
+
|
|
299
|
+
(`pragma block list` omits groups — read them from the graph:
|
|
300
|
+
`pragma graph query "SELECT ?b ?name WHERE { ?b a ds:Group ; ds:name ?name }"`.)
|
|
301
|
+
|
|
302
|
+
**Parent Reference in Subcomponents:**
|
|
303
|
+
|
|
304
|
+
Subcomponents must reference their parent in the name:
|
|
305
|
+
|
|
306
|
+
| Labelled Name | Turtle URI |
|
|
307
|
+
|---------------|------------|
|
|
308
|
+
| `Timeline.Event` | `global.subcomponent.timeline-event` |
|
|
309
|
+
| `Timeline.ExpansionIndicator` | `global.subcomponent.timeline-expansion_indicator` |
|
|
310
|
+
|
|
311
|
+
### Named vs Anonymous
|
|
312
|
+
|
|
313
|
+
| Type | Identifier | Use Case |
|
|
314
|
+
|------|------------|----------|
|
|
315
|
+
| Named | `uri` | User-instantiable components (things users compose in their code) |
|
|
316
|
+
| Anonymous | `role` | Internal structural elements not directly instantiated by users |
|
|
317
|
+
|
|
318
|
+
**Rule of thumb:** Ask "Can/should a user write `<ComponentName>` in their code?" If yes, use `uri`. If no, use `role`.
|
|
319
|
+
|
|
320
|
+
### DRY Principle
|
|
321
|
+
|
|
322
|
+
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.
|
|
323
|
+
|
|
324
|
+
```yaml
|
|
325
|
+
# Correct: reference only
|
|
326
|
+
edges:
|
|
327
|
+
- node:
|
|
328
|
+
uri: global.component.button
|
|
329
|
+
relation:
|
|
330
|
+
cardinality: "1"
|
|
331
|
+
|
|
332
|
+
# Incorrect: copying the child's own definition
|
|
333
|
+
edges:
|
|
334
|
+
- node:
|
|
335
|
+
uri: global.component.button
|
|
336
|
+
styles:
|
|
337
|
+
# ... full button styles duplicated here
|
|
338
|
+
relation:
|
|
339
|
+
cardinality: "1"
|
|
340
|
+
|
|
341
|
+
# Permitted: contextual style overrides (the live password_constraints → icon pair)
|
|
342
|
+
edges:
|
|
343
|
+
- node:
|
|
344
|
+
uri: global.component.icon
|
|
345
|
+
styles:
|
|
346
|
+
size.width: size/icon/small
|
|
347
|
+
size.height: size/icon/small
|
|
348
|
+
relation:
|
|
349
|
+
cardinality: "1"
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Cardinality Notation
|
|
353
|
+
|
|
354
|
+
| Notation | Meaning | Example Use |
|
|
355
|
+
|----------|---------|-------------|
|
|
356
|
+
| `"1"` or `"1..1"` | Exactly one (required) | Card body |
|
|
357
|
+
| `"0..1"` | Zero or one (optional) | Card header, Card footer |
|
|
358
|
+
| `"0..*"` | Zero or more | List items |
|
|
359
|
+
| `"1..*"` | One or more | Accordion items (need at least one) |
|
|
360
|
+
| `"2..5"` | Between 2 and 5 | Bounded repeats — live: KeyboardKeys' `"2..*"` (see the Group template below) |
|
|
361
|
+
|
|
362
|
+
### Switch Construct
|
|
363
|
+
|
|
364
|
+
The `switch` construct models positions in the anatomy tree that can be filled by one of several alternatives. It operates at the edge level, parallel to `node`, making polymorphism explicit.
|
|
365
|
+
|
|
366
|
+
#### Syntax
|
|
367
|
+
|
|
368
|
+
```yaml
|
|
369
|
+
edges:
|
|
370
|
+
- switch:
|
|
371
|
+
on: <discriminator>
|
|
372
|
+
cases:
|
|
373
|
+
- uri: <component-uri>
|
|
374
|
+
default: true # optional, marks default case
|
|
375
|
+
- uri: <component-uri>
|
|
376
|
+
- uri: $custom # reserved URI for user-provided components
|
|
377
|
+
relation:
|
|
378
|
+
cardinality: <cardinality>
|
|
379
|
+
slotName: <slot>
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
#### Discriminator Vocabulary
|
|
383
|
+
|
|
384
|
+
| Value | Meaning | Use Case |
|
|
385
|
+
|-------|---------|----------|
|
|
386
|
+
| `props` | Consumer chooses via component props | `<Field type="checkbox">` renders Checkbox vs Radio |
|
|
387
|
+
| `internal` | Component manages choice internally | AsyncButton shows Loading/Success/Error based on state (hypothetical — see the labelled example below) |
|
|
388
|
+
| `override` | Consumer can replace with custom component | Timeline accepts custom Event component via slot |
|
|
389
|
+
|
|
390
|
+
This enum is NORMATIVE. Live anatomies that predate it may carry qualified
|
|
391
|
+
discriminators (`props/<prop>`) or a `with:` map in exploratory drafts — do not
|
|
392
|
+
copy those into new anatomies.
|
|
393
|
+
|
|
394
|
+
#### Shorthand Expansion
|
|
395
|
+
|
|
396
|
+
The shorthand `- uri: X` expands to `- node: { uri: X }`. This allows cases to be expressed concisely when no additional metadata is needed:
|
|
397
|
+
|
|
398
|
+
```yaml
|
|
399
|
+
# Shorthand (common case)
|
|
400
|
+
cases:
|
|
401
|
+
- uri: global.subcomponent.checkbox_input
|
|
402
|
+
|
|
403
|
+
# Expands to full form
|
|
404
|
+
cases:
|
|
405
|
+
- node:
|
|
406
|
+
uri: global.subcomponent.checkbox_input
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
When a case requires additional properties (styles, nested edges), use the full node form:
|
|
410
|
+
|
|
411
|
+
```yaml
|
|
412
|
+
cases:
|
|
413
|
+
- node:
|
|
414
|
+
uri: global.subcomponent.textarea_input
|
|
415
|
+
styles:
|
|
416
|
+
size.height: hug
|
|
417
|
+
size.min.height: size/input/multiline
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
#### Reserved URI: `$custom`
|
|
421
|
+
|
|
422
|
+
The `$custom` URI indicates that the position accepts a user-provided component:
|
|
423
|
+
|
|
424
|
+
```yaml
|
|
425
|
+
cases:
|
|
426
|
+
- uri: global.subcomponent.timeline-event
|
|
427
|
+
- uri: $custom # consumer can provide custom component
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
#### Examples
|
|
431
|
+
|
|
432
|
+
**Field with prop-based switch:**
|
|
433
|
+
|
|
434
|
+
```yaml
|
|
435
|
+
---
|
|
436
|
+
node:
|
|
437
|
+
uri: global.pattern.field
|
|
438
|
+
edges:
|
|
439
|
+
- switch:
|
|
440
|
+
on: props
|
|
441
|
+
cases:
|
|
442
|
+
- uri: global.subcomponent.checkbox_input
|
|
443
|
+
- uri: global.subcomponent.radio_input
|
|
444
|
+
- uri: global.subcomponent.text_input
|
|
445
|
+
relation:
|
|
446
|
+
cardinality: "1"
|
|
447
|
+
slotName: input
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
**Async button with internal state switch** (the `async_button` family is hypothetical —
|
|
451
|
+
an illustration of the `internal` discriminator, not a live block):
|
|
452
|
+
|
|
453
|
+
```yaml
|
|
454
|
+
---
|
|
455
|
+
node:
|
|
456
|
+
uri: global.component.async_button
|
|
457
|
+
edges:
|
|
458
|
+
- switch:
|
|
459
|
+
on: internal
|
|
460
|
+
cases:
|
|
461
|
+
- uri: global.subcomponent.async_button-idle
|
|
462
|
+
- uri: global.subcomponent.async_button-loading
|
|
463
|
+
- uri: global.subcomponent.async_button-success
|
|
464
|
+
- uri: global.subcomponent.async_button-error
|
|
465
|
+
relation:
|
|
466
|
+
cardinality: "1"
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
**Timeline with override-capable slot:**
|
|
470
|
+
|
|
471
|
+
```yaml
|
|
472
|
+
---
|
|
473
|
+
node:
|
|
474
|
+
uri: global.pattern.timeline
|
|
475
|
+
edges:
|
|
476
|
+
- switch:
|
|
477
|
+
on: override
|
|
478
|
+
cases:
|
|
479
|
+
- uri: global.subcomponent.timeline-event
|
|
480
|
+
- uri: $custom
|
|
481
|
+
relation:
|
|
482
|
+
cardinality: "1..*"
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### Slot Names
|
|
486
|
+
|
|
487
|
+
Common slot conventions:
|
|
488
|
+
|
|
489
|
+
| Slot | Purpose |
|
|
490
|
+
|------|---------|
|
|
491
|
+
| `default` | Main content area (React children, Vue default slot) |
|
|
492
|
+
| `header` | Header content |
|
|
493
|
+
| `footer` | Footer content |
|
|
494
|
+
| `icon` | Icon placement |
|
|
495
|
+
| `label` | Text label |
|
|
496
|
+
| `media` | Image/video content |
|
|
497
|
+
| `actions` | Action buttons |
|
|
498
|
+
|
|
499
|
+
**Convention:** `slotName: default` implies the main children slot (`children` in React, default slot in Vue).
|
|
500
|
+
|
|
501
|
+
### Style Key Categories
|
|
502
|
+
|
|
503
|
+
Styles use CTI-inspired dot-notation: `category.type[.item]`
|
|
504
|
+
|
|
505
|
+
#### Layout (typically invariant)
|
|
506
|
+
```yaml
|
|
507
|
+
layout.type: stack | flow | grid
|
|
508
|
+
layout.direction: horizontal | vertical
|
|
509
|
+
layout.align: start | center | end | stretch
|
|
510
|
+
layout.justify: start | center | end | space-between | space-around
|
|
511
|
+
layout.wrap: true | false
|
|
512
|
+
layout.display: block | flex | grid | none
|
|
513
|
+
layout.flex: 1 | auto
|
|
514
|
+
layout.overflow: hidden | scroll | visible
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
#### Spacing (invariant if literal, themeable if token)
|
|
518
|
+
```yaml
|
|
519
|
+
spacing.internal: spacing/medium # padding (themeable)
|
|
520
|
+
spacing.external: spacing/large # margin (themeable)
|
|
521
|
+
spacing.gap: spacing/small # gap between children (themeable)
|
|
522
|
+
spacing.margin.bottom: spacing/small # specific margin
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
#### Appearance (typically themeable)
|
|
526
|
+
```yaml
|
|
527
|
+
appearance.background: color/surface/card
|
|
528
|
+
appearance.border: border/style/default
|
|
529
|
+
appearance.border.top: border/style/divider
|
|
530
|
+
appearance.shadow: shadow/elevated/medium
|
|
531
|
+
appearance.radius: shape/rounded/full
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
#### Size (invariant if structural, themeable if token)
|
|
535
|
+
```yaml
|
|
536
|
+
size.width: fill | hug | 100% # invariant
|
|
537
|
+
size.width: size/card/width # themeable
|
|
538
|
+
size.max.width: size/container/max
|
|
539
|
+
size.max.height: size/media/max
|
|
540
|
+
size.min.height: 48px
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
#### Typography (typically themeable)
|
|
544
|
+
```yaml
|
|
545
|
+
typography.size: font/size/body
|
|
546
|
+
typography.weight: font/weight/bold
|
|
547
|
+
typography.color: color/text/primary
|
|
548
|
+
typography.align: center | left | right # can be invariant
|
|
549
|
+
typography.line.height: 1.5
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
#### Interaction (typically invariant)
|
|
553
|
+
```yaml
|
|
554
|
+
interaction.cursor: pointer | default
|
|
555
|
+
interaction.transition.property: background
|
|
556
|
+
interaction.transition.duration: transition/duration # token = themeable
|
|
557
|
+
interaction.transition.timing: ease
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
#### Object (for media)
|
|
561
|
+
```yaml
|
|
562
|
+
object.fit: cover | contain | fill
|
|
563
|
+
object.position: center | top
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
## Templates
|
|
567
|
+
|
|
568
|
+
### Basic Component Template
|
|
569
|
+
|
|
570
|
+
```yaml
|
|
571
|
+
node:
|
|
572
|
+
uri: {tier}.component.{name}
|
|
573
|
+
styles:
|
|
574
|
+
# Structural (invariant)
|
|
575
|
+
layout.type: stack
|
|
576
|
+
layout.direction: vertical
|
|
577
|
+
|
|
578
|
+
# Themeable
|
|
579
|
+
appearance.background: color/surface/{name}
|
|
580
|
+
spacing.internal: spacing/medium
|
|
581
|
+
|
|
582
|
+
edges:
|
|
583
|
+
- node:
|
|
584
|
+
uri: {tier}.subcomponent.{name}-content
|
|
585
|
+
relation:
|
|
586
|
+
cardinality: "1..1"
|
|
587
|
+
slotName: default
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### Component with Optional Parts
|
|
591
|
+
|
|
592
|
+
```yaml
|
|
593
|
+
node:
|
|
594
|
+
uri: global.component.card
|
|
595
|
+
styles:
|
|
596
|
+
layout.type: stack
|
|
597
|
+
layout.direction: vertical
|
|
598
|
+
appearance.background: color/surface/card
|
|
599
|
+
appearance.shadow: shadow/card
|
|
600
|
+
appearance.radius: shape/rounded/full
|
|
601
|
+
|
|
602
|
+
edges:
|
|
603
|
+
# Named children reference their URI only (DRY Principle above) — each
|
|
604
|
+
# carries its own styles in its own anatomy; the live Card does the same.
|
|
605
|
+
- node:
|
|
606
|
+
uri: global.subcomponent.card-header
|
|
607
|
+
relation:
|
|
608
|
+
cardinality: "0..1" # optional
|
|
609
|
+
slotName: header
|
|
610
|
+
|
|
611
|
+
- node:
|
|
612
|
+
uri: global.subcomponent.card-content
|
|
613
|
+
relation:
|
|
614
|
+
cardinality: "1..1" # required
|
|
615
|
+
slotName: default
|
|
616
|
+
|
|
617
|
+
- node:
|
|
618
|
+
uri: global.subcomponent.card-footer
|
|
619
|
+
relation:
|
|
620
|
+
cardinality: "0..1" # optional
|
|
621
|
+
slotName: footer
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
### Nested Components (Repeating)
|
|
625
|
+
|
|
626
|
+
```yaml
|
|
627
|
+
node:
|
|
628
|
+
uri: global.component.accordion
|
|
629
|
+
styles:
|
|
630
|
+
layout.type: stack
|
|
631
|
+
layout.direction: vertical
|
|
632
|
+
appearance.border: border/style/accordion
|
|
633
|
+
|
|
634
|
+
edges:
|
|
635
|
+
# The repeating child is NAMED — reference its URI only (DRY Principle
|
|
636
|
+
# above); its subtree lives in its own anatomy. The live Accordion does
|
|
637
|
+
# the same.
|
|
638
|
+
- node:
|
|
639
|
+
uri: global.subcomponent.accordion-item
|
|
640
|
+
relation:
|
|
641
|
+
cardinality: "1..*" # one or more items
|
|
642
|
+
slotName: default
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
The child's own anatomy — a separate spec on `global.subcomponent.accordion-item`
|
|
646
|
+
(transcribed from the live block) — is where the subtree lives; its parts are
|
|
647
|
+
anonymous roles:
|
|
648
|
+
|
|
649
|
+
```yaml
|
|
650
|
+
node:
|
|
651
|
+
uri: global.subcomponent.accordion-item
|
|
652
|
+
styles:
|
|
653
|
+
layout.type: stack
|
|
654
|
+
layout.direction: vertical
|
|
655
|
+
edges:
|
|
656
|
+
- node:
|
|
657
|
+
role: header tab
|
|
658
|
+
styles:
|
|
659
|
+
layout.type: flow
|
|
660
|
+
layout.direction: horizontal
|
|
661
|
+
layout.align: center
|
|
662
|
+
interaction.cursor: pointer
|
|
663
|
+
edges:
|
|
664
|
+
- node:
|
|
665
|
+
role: control
|
|
666
|
+
styles:
|
|
667
|
+
size.width: size/icon/small
|
|
668
|
+
size.height: size/icon/small
|
|
669
|
+
relation:
|
|
670
|
+
cardinality: "1"
|
|
671
|
+
- node:
|
|
672
|
+
role: heading
|
|
673
|
+
relation:
|
|
674
|
+
cardinality: "1"
|
|
675
|
+
slotName: default
|
|
676
|
+
relation:
|
|
677
|
+
cardinality: "1"
|
|
678
|
+
slotName: header
|
|
679
|
+
|
|
680
|
+
- node:
|
|
681
|
+
role: content panel
|
|
682
|
+
styles:
|
|
683
|
+
layout.overflow: hidden
|
|
684
|
+
relation:
|
|
685
|
+
cardinality: "1"
|
|
686
|
+
slotName: default
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
### Group (repeating siblings)
|
|
690
|
+
|
|
691
|
+
A Group is the plural of ONE sibling block: a named root carrying only layout and
|
|
692
|
+
spacing styles, and a single URI-only edge whose cardinality bounds the repetition.
|
|
693
|
+
Transcribed from the live `global.group.keyboard_keys`:
|
|
694
|
+
|
|
695
|
+
```yaml
|
|
696
|
+
node:
|
|
697
|
+
uri: global.group.keyboard_keys
|
|
698
|
+
styles:
|
|
699
|
+
layout.display: inline-flex
|
|
700
|
+
layout.align: center
|
|
701
|
+
spacing.gap: spacing/horizontal/xsmall
|
|
702
|
+
edges:
|
|
703
|
+
- node:
|
|
704
|
+
uri: global.component.keyboard_key
|
|
705
|
+
relation:
|
|
706
|
+
cardinality: "2..*" # a group needs at least two keys
|
|
707
|
+
slotName: default
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
### Anonymous Wrapper Node
|
|
711
|
+
|
|
712
|
+
Use when you need a structural element without design system identity:
|
|
713
|
+
|
|
714
|
+
```yaml
|
|
715
|
+
node:
|
|
716
|
+
uri: global.pattern.modal
|
|
717
|
+
styles:
|
|
718
|
+
layout.type: stack
|
|
719
|
+
appearance.background: color/surface/modal
|
|
720
|
+
|
|
721
|
+
edges:
|
|
722
|
+
- node:
|
|
723
|
+
role: backdrop overlay # anonymous - no uri
|
|
724
|
+
styles:
|
|
725
|
+
layout.display: block
|
|
726
|
+
appearance.background: color/overlay/dark
|
|
727
|
+
interaction.cursor: pointer
|
|
728
|
+
relation:
|
|
729
|
+
cardinality: "1..1"
|
|
730
|
+
|
|
731
|
+
- node:
|
|
732
|
+
role: content container # anonymous wrapper
|
|
733
|
+
styles:
|
|
734
|
+
layout.type: stack
|
|
735
|
+
size.max.width: size/modal/max
|
|
736
|
+
spacing.internal: spacing/large
|
|
737
|
+
edges:
|
|
738
|
+
- node:
|
|
739
|
+
uri: global.subcomponent.modal-header
|
|
740
|
+
relation:
|
|
741
|
+
cardinality: "0..1"
|
|
742
|
+
slotName: header
|
|
743
|
+
- node:
|
|
744
|
+
uri: global.subcomponent.modal-content
|
|
745
|
+
relation:
|
|
746
|
+
cardinality: "1..1"
|
|
747
|
+
slotName: default
|
|
748
|
+
relation:
|
|
749
|
+
cardinality: "1..1"
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
### Simple Button Example
|
|
753
|
+
|
|
754
|
+
The live Button models its icon and label as ANONYMOUS roles — a user writes `<Button>`
|
|
755
|
+
with props and children, never a `Button.Icon` — so the children carry `role`, not `uri`
|
|
756
|
+
(the Named vs Anonymous rule above):
|
|
757
|
+
|
|
758
|
+
```yaml
|
|
759
|
+
node:
|
|
760
|
+
uri: global.component.button
|
|
761
|
+
styles:
|
|
762
|
+
# Structural
|
|
763
|
+
layout.type: flow
|
|
764
|
+
layout.direction: horizontal
|
|
765
|
+
layout.align: center
|
|
766
|
+
layout.justify: center
|
|
767
|
+
|
|
768
|
+
# Themeable
|
|
769
|
+
spacing.internal: spacing/button
|
|
770
|
+
spacing.gap: spacing/small
|
|
771
|
+
appearance.background: color/action/primary
|
|
772
|
+
appearance.radius: shape/rounded/button
|
|
773
|
+
typography.weight: font/weight/medium
|
|
774
|
+
|
|
775
|
+
# Interaction
|
|
776
|
+
interaction.cursor: pointer
|
|
777
|
+
interaction.transition.property: background, transform
|
|
778
|
+
interaction.transition.duration: transition/fast
|
|
779
|
+
|
|
780
|
+
edges:
|
|
781
|
+
- node:
|
|
782
|
+
role: icon
|
|
783
|
+
styles:
|
|
784
|
+
size.width: size/icon/small
|
|
785
|
+
size.height: size/icon/small
|
|
786
|
+
relation:
|
|
787
|
+
cardinality: "0..1"
|
|
788
|
+
slotName: icon
|
|
789
|
+
|
|
790
|
+
- node:
|
|
791
|
+
role: label
|
|
792
|
+
styles:
|
|
793
|
+
typography.size: font/size/button
|
|
794
|
+
typography.color: color/text/on-action
|
|
795
|
+
relation:
|
|
796
|
+
cardinality: "0..1"
|
|
797
|
+
slotName: default
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
## Workflow
|
|
801
|
+
|
|
802
|
+
### 1. Identify the Component
|
|
803
|
+
|
|
804
|
+
First, a pre-step: could the name be a Group? Run the group query before the
|
|
805
|
+
lookup — neither `pragma block lookup` nor `pragma block list` covers groups, and
|
|
806
|
+
a name can be BOTH a Group and another tier's block (`BlogCards` is the Group
|
|
807
|
+
`sites.group.blog_cards` AND the Component
|
|
808
|
+
`sites_webcomponentsprototype.component.blog_cards`; the lookup answers with the
|
|
809
|
+
component and never mentions the group):
|
|
810
|
+
|
|
811
|
+
```bash
|
|
812
|
+
pragma graph query "SELECT ?b ?name WHERE { ?b a ds:Group ; ds:name ?name }"
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
If the name is in the answer and the Group is the block you were asked to write,
|
|
816
|
+
it is an existing block: take the URI from that query row (no §3 derivation),
|
|
817
|
+
gather its context with `pragma graph inspect <IRI>`, and continue from §2.
|
|
818
|
+
|
|
819
|
+
Otherwise, determine if the component exists in the design system:
|
|
820
|
+
|
|
821
|
+
```bash
|
|
822
|
+
pragma block lookup Card
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
Two branches:
|
|
826
|
+
|
|
827
|
+
- **Not found** (`ENTITY_NOT_FOUND`) — this is a NEW block: derive its URI in §3
|
|
828
|
+
below, then continue from §2.
|
|
829
|
+
- **Found** — this is a draft for an existing block (the empty-anatomy worklist path):
|
|
830
|
+
take the URI from the block's `pragma block list` row and skip §3. The list carries a
|
|
831
|
+
row per block it covers, not per tier declaring the name, and the lookup above
|
|
832
|
+
resolved it globally — the name query
|
|
833
|
+
`pragma graph query "SELECT ?b WHERE { ?b ds:name ?n . FILTER(LCASE(?n) = LCASE('<Name>')) }"`
|
|
834
|
+
lists every tier's IRI, so you take the one you meant.
|
|
835
|
+
Gather the full context — anatomy, modifiers, properties come with the lookup (a
|
|
836
|
+
bare name can match several tiers: see the multi-tier note in the Discovery Flow
|
|
837
|
+
above); every raw triple with:
|
|
838
|
+
|
|
839
|
+
```bash
|
|
840
|
+
pragma graph inspect ds:global.component.card
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
### 2. Gather Structural Requirements
|
|
844
|
+
|
|
845
|
+
Ask:
|
|
846
|
+
- What are the main parts? (header, body, footer, icon, label)
|
|
847
|
+
- Which parts are required vs optional?
|
|
848
|
+
- Can any parts repeat?
|
|
849
|
+
- Are there anonymous structural wrappers needed?
|
|
850
|
+
|
|
851
|
+
### 3. Determine Tier and Type
|
|
852
|
+
|
|
853
|
+
The URI is `{tier}.{type}.{snake_name}` (see URI Encoding Convention above). The tier
|
|
854
|
+
segment comes from the live tier set — run `pragma tier list` and pick from what it
|
|
855
|
+
answers, never from a remembered list; the type segment is one of the ontology's
|
|
856
|
+
UIBlock classes:
|
|
857
|
+
|
|
858
|
+
| Question | Answer | Result |
|
|
859
|
+
|----------|--------|--------|
|
|
860
|
+
| Is it universal? | Yes | `global.component.{name}` |
|
|
861
|
+
| Is it app-specific? | Yes | `{apps_tier}.component.{name}` — the app's own tier from `pragma tier list` (e.g. `apps_lxd`) |
|
|
862
|
+
| Is it a subpart? | Yes | `{tier}.subcomponent.{parent}-{part}` |
|
|
863
|
+
| Is it a UX solution? | Yes | `{tier}.pattern.{name}` |
|
|
864
|
+
| Does it divide space? | Yes | `{tier}.layout.{name}` |
|
|
865
|
+
| Is it many instances of ONE sibling block laid out together? | Yes | `{tier}.group.{name}` |
|
|
866
|
+
|
|
867
|
+
### 4. Define Edges
|
|
868
|
+
|
|
869
|
+
For each child:
|
|
870
|
+
1. Is it named (in DS) or anonymous (structural wrapper)?
|
|
871
|
+
2. What is its cardinality?
|
|
872
|
+
3. Does it map to a slot?
|
|
873
|
+
|
|
874
|
+
### 5. Apply Styles
|
|
875
|
+
|
|
876
|
+
For each node, consider:
|
|
877
|
+
- **Layout**: How is content arranged?
|
|
878
|
+
- **Spacing**: Internal padding, gaps between children
|
|
879
|
+
- **Appearance**: Background, borders, shadows, radius
|
|
880
|
+
- **Size**: Width/height constraints
|
|
881
|
+
- **Typography**: Text styling (for text-containing nodes)
|
|
882
|
+
- **Interaction**: Cursor, transitions (for interactive nodes)
|
|
883
|
+
|
|
884
|
+
**Rule of thumb**:
|
|
885
|
+
- Structural values (stack, center, fill) = invariant
|
|
886
|
+
- Token references (spacing/medium) = themeable
|
|
887
|
+
|
|
888
|
+
### 6. Validate
|
|
889
|
+
|
|
890
|
+
Check your anatomy against:
|
|
891
|
+
- [ ] Root node is named (has `uri`)
|
|
892
|
+
- [ ] All cardinalities are valid notation
|
|
893
|
+
- [ ] Style keys follow CTI convention
|
|
894
|
+
- [ ] Token paths use `/` delimiter
|
|
895
|
+
- [ ] Anonymous nodes have `role`, not `uri`
|
|
896
|
+
- [ ] Multi-word names encode word boundaries with `_`; `-` appears only where the name carries a dot (per the URI Encoding Convention table)
|
|
897
|
+
- [ ] 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
|
+
- [ ] Named (`uri:`) children carry no copy of their own subtree — contextual style overrides only
|
|
899
|
+
- [ ] Nested components make semantic sense
|
|
900
|
+
|
|
901
|
+
## Response Format
|
|
902
|
+
|
|
903
|
+
When creating an anatomy, respond with:
|
|
904
|
+
|
|
905
|
+
```markdown
|
|
906
|
+
## Anatomy: {ComponentName}
|
|
907
|
+
|
|
908
|
+
**URI:** `{tier}.{type}.{snake_name}`
|
|
909
|
+
**Tier:** {tier} - {rationale}
|
|
910
|
+
|
|
911
|
+
### Structure Overview
|
|
912
|
+
- {Part 1} (required/optional)
|
|
913
|
+
- {Part 2} (required/optional)
|
|
914
|
+
- ...
|
|
915
|
+
|
|
916
|
+
### Specification
|
|
917
|
+
|
|
918
|
+
\`\`\`yaml
|
|
919
|
+
{yaml content}
|
|
920
|
+
\`\`\`
|
|
921
|
+
|
|
922
|
+
### Notes
|
|
923
|
+
- {Design decision 1}
|
|
924
|
+
- {Design decision 2}
|
|
925
|
+
|
|
926
|
+
### Next Steps
|
|
927
|
+
1. Land the DSL in the block's spec draft under `specs/` (see `specs/README.md`) —
|
|
928
|
+
as step 7 of a specify flow that is the step-6 spec file; never under `data/`
|
|
929
|
+
2. A human enters the content into Coda, the database of record
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
## Tips
|
|
933
|
+
|
|
934
|
+
1. **Start simple**: Begin with just structure, add styles incrementally
|
|
935
|
+
2. **Use discovery**: Query existing components for patterns and consistency
|
|
936
|
+
3. **Name thoughtfully**: URIs are identifiers - choose clear, consistent names
|
|
937
|
+
(see the URI Encoding Convention table above)
|
|
938
|
+
4. **Annotate decisions**: Use YAML comments for non-obvious choices
|
|
939
|
+
5. **Validate cardinality**: Think through edge cases (empty states, maximums)
|
|
940
|
+
6. **Separate concerns**: Structural styles vs themeable styles
|
|
941
|
+
7. **Anonymous nodes are OK**: Don't force DS identity on pure wrappers
|
|
942
|
+
|
|
943
|
+
## Limitations
|
|
944
|
+
|
|
945
|
+
- Does not handle conditional rendering (show/hide based on state)
|
|
946
|
+
- Does not represent portal content (elements rendered elsewhere in DOM)
|
|
947
|
+
- Style inheritance is not yet supported (e.g., "inherits from Card")
|
|
948
|
+
- No runtime behavior specification (only static structure)
|
|
949
|
+
|
|
950
|
+
## References
|
|
951
|
+
|
|
952
|
+
- [Open UI W3C Working Group](https://open-ui.org/) - Inspiration for code-based anatomy
|
|
953
|
+
- [Components as Data](https://medium.com/@nathanacurtis/components-as-data-2be178777f21) - Nathan Curtis's exploration
|
|
954
|
+
- [W3C Design Token Format](https://design-tokens.github.io/community-group/format/) - Token path conventions
|
|
955
|
+
|
|
956
|
+
---
|
|
957
|
+
|
|
958
|
+
## Appendix: Anatomy DSL Specification
|
|
959
|
+
|
|
960
|
+
The complete type system for the Anatomy DSL. This specification defines the formal structure for representing component anatomies.
|
|
961
|
+
|
|
962
|
+
### Abstract
|
|
963
|
+
|
|
964
|
+
This specification presents a DSL to represent design system anatomies, providing an accurate platform-agnostic markup primitive to precede implementation.
|
|
965
|
+
|
|
966
|
+
### Intended Usage
|
|
967
|
+
|
|
968
|
+
1. **Documentation**: Implementation primitive for markup and style bindings
|
|
969
|
+
2. **Specification**: Using the tree-based DSL to identify entities to implement and/or reuse
|
|
970
|
+
3. **Discussion**: Using the DSL to discuss across conversations related to DS (e.g., "I believe these nodes should be siblings, not parents of one another")
|
|
971
|
+
4. **Inference**: Supporting automated inference to audit a codebase against its specifications or to support code generation
|
|
972
|
+
|
|
973
|
+
### Requirements
|
|
974
|
+
|
|
975
|
+
**General DSL Requirements:**
|
|
976
|
+
- The DSL MUST optimize for readability
|
|
977
|
+
- The DSL MUST be easy to get started with and use a familiar language
|
|
978
|
+
- The scope of the DSL MUST be minimized to cover only the strict necessary - additional specification information being covered in the schema
|
|
979
|
+
|
|
980
|
+
**Modelling Requirements:**
|
|
981
|
+
- The children of a node MUST be modelled through a reified relation for annotation purposes
|
|
982
|
+
- The DSL MUST support anonymous nodes (nodes that are not named components). In this case, the DSL should support role annotations
|
|
983
|
+
- The DSL MUST support reified annotations of cardinality
|
|
984
|
+
- The DSL MUST support all classes of UI Blocks identified in the ontology (Layout, Pattern, Component, Subcomponent, Group)
|
|
985
|
+
|
|
986
|
+
### Type System
|
|
987
|
+
|
|
988
|
+
The children one-to-many relation follows inspiration from the [Relay connection pattern](https://relay.dev/graphql/connections.htm).
|
|
989
|
+
|
|
990
|
+
```typescript
|
|
991
|
+
/* Reusable types */
|
|
992
|
+
|
|
993
|
+
/**
|
|
994
|
+
* Styles use a CTI (Category-Type-Item) inspired flat key structure
|
|
995
|
+
* for platform-agnostic UI properties.
|
|
996
|
+
*
|
|
997
|
+
* KEY NAMING CONVENTION:
|
|
998
|
+
* Properties follow dot-notation: "category.type[.item]"
|
|
999
|
+
* - Category: Primary concern (layout, spacing, appearance, typography, size)
|
|
1000
|
+
* - Type: Specific aspect within that category
|
|
1001
|
+
* - Item: Optional further specification
|
|
1002
|
+
*
|
|
1003
|
+
* INVARIANT VS THEMEABLE:
|
|
1004
|
+
* - Invariant (structural): layout.type, layout.direction, size.width: "fill"
|
|
1005
|
+
* - Themeable (brand/theme): appearance.*, spacing.* (when using tokens)
|
|
1006
|
+
*/
|
|
1007
|
+
type Styles = Record<string, TokenPath | TokenPath[] | PrimitiveValue>;
|
|
1008
|
+
|
|
1009
|
+
/**
|
|
1010
|
+
* TokenPath: Forward-slash delimited path to a design token.
|
|
1011
|
+
* Examples: "spacing/medium", "color/surface/primary", "font/size/heading/1"
|
|
1012
|
+
*/
|
|
1013
|
+
type TokenPath = string;
|
|
1014
|
+
|
|
1015
|
+
/**
|
|
1016
|
+
* PrimitiveValue: Direct values not resolved from tokens.
|
|
1017
|
+
* Examples: "stack", "center", "fill", 1.5, true
|
|
1018
|
+
*/
|
|
1019
|
+
type PrimitiveValue = string | number | boolean;
|
|
1020
|
+
|
|
1021
|
+
/* Main types */
|
|
1022
|
+
|
|
1023
|
+
interface Relation {
|
|
1024
|
+
/**
|
|
1025
|
+
* Cardinality: Number of allowed instances.
|
|
1026
|
+
* - "1" : Exactly one (required)
|
|
1027
|
+
* - "0..1" : Zero or one (optional)
|
|
1028
|
+
* - "0..*" : Zero or more
|
|
1029
|
+
* - "1..*" : One or more
|
|
1030
|
+
* - "2..5" : Between 2 and 5
|
|
1031
|
+
*/
|
|
1032
|
+
cardinality: string;
|
|
1033
|
+
|
|
1034
|
+
/**
|
|
1035
|
+
* Maps content to a specific slot in the parent component.
|
|
1036
|
+
* Common values: "default", "header", "footer", "icon", "label"
|
|
1037
|
+
*/
|
|
1038
|
+
slotName?: string;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* Base interface for all nodes in the anatomy tree.
|
|
1043
|
+
*/
|
|
1044
|
+
interface BaseNode {
|
|
1045
|
+
/**
|
|
1046
|
+
* Style properties using CTI-inspired keys.
|
|
1047
|
+
*
|
|
1048
|
+
* Categories:
|
|
1049
|
+
* - layout.* : type, direction, align, justify, wrap, display, flex, overflow
|
|
1050
|
+
* - spacing.* : internal, external, gap, margin.*, padding.*
|
|
1051
|
+
* - appearance.*: background, border, shadow, radius
|
|
1052
|
+
* - size.* : width, height, max.*, min.*
|
|
1053
|
+
* - typography.*: size, weight, color, align, line.height
|
|
1054
|
+
* - interaction.*: cursor, transition.*
|
|
1055
|
+
* - object.* : fit, position
|
|
1056
|
+
*/
|
|
1057
|
+
styles?: Styles;
|
|
1058
|
+
|
|
1059
|
+
/** Child nodes and their relationships. */
|
|
1060
|
+
edges?: Edge[];
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
/**
|
|
1064
|
+
* Named nodes represent identifiable components in the design system.
|
|
1065
|
+
*/
|
|
1066
|
+
interface NamedNode extends BaseNode {
|
|
1067
|
+
/**
|
|
1068
|
+
* Unique identifier following "tier.type.name" pattern.
|
|
1069
|
+
* Examples: "global.component.button", "apps.layout.application_layout"
|
|
1070
|
+
*/
|
|
1071
|
+
uri: string;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
/**
|
|
1075
|
+
* Anonymous nodes represent structural elements without DS identity.
|
|
1076
|
+
*/
|
|
1077
|
+
interface AnonymousNode extends BaseNode {
|
|
1078
|
+
/**
|
|
1079
|
+
* Human-readable description of the node's purpose.
|
|
1080
|
+
* Examples: "content wrapper", "spacer element", "icon container"
|
|
1081
|
+
*/
|
|
1082
|
+
role: string;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
type Node = NamedNode | AnonymousNode;
|
|
1086
|
+
|
|
1087
|
+
/**
|
|
1088
|
+
* A switch fills one position with ONE of several alternatives.
|
|
1089
|
+
* The discriminator says who chooses: "props" (consumer chooses via
|
|
1090
|
+
* component props), "internal" (the component chooses from its own
|
|
1091
|
+
* state), "override" (the consumer may replace the default with a
|
|
1092
|
+
* custom component).
|
|
1093
|
+
*/
|
|
1094
|
+
interface Switch {
|
|
1095
|
+
on: "props" | "internal" | "override";
|
|
1096
|
+
cases: SwitchCase[];
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
/**
|
|
1100
|
+
* One switch alternative — exactly one of `uri` or `node`.
|
|
1101
|
+
* `uri` is the shorthand for `node: { uri }`; use the full `node` form
|
|
1102
|
+
* when the case carries styles or edges. The reserved URI `$custom`
|
|
1103
|
+
* marks a case filled by a user-provided component.
|
|
1104
|
+
*/
|
|
1105
|
+
interface SwitchCase {
|
|
1106
|
+
uri?: string;
|
|
1107
|
+
node?: Node;
|
|
1108
|
+
|
|
1109
|
+
/** Marks the default case. */
|
|
1110
|
+
default?: boolean;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
interface Edge {
|
|
1114
|
+
/** The child node — exactly one of `node` or `switch`. */
|
|
1115
|
+
node?: Node;
|
|
1116
|
+
|
|
1117
|
+
/** A polymorphic position (mutually exclusive with node). */
|
|
1118
|
+
switch?: Switch;
|
|
1119
|
+
|
|
1120
|
+
relation: Relation;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* Root specification for a component anatomy.
|
|
1125
|
+
* The top-level node must always be Named.
|
|
1126
|
+
*/
|
|
1127
|
+
interface AnatomySpec {
|
|
1128
|
+
node: NamedNode;
|
|
1129
|
+
}
|
|
1130
|
+
```
|
|
1131
|
+
|
|
1132
|
+
### Language Choice
|
|
1133
|
+
|
|
1134
|
+
YAML is chosen as the markup language for its:
|
|
1135
|
+
- Indentation-based structure matching tree hierarchies
|
|
1136
|
+
- Readability over JSON's verbosity
|
|
1137
|
+
- Wide adoption and tooling support
|
|
1138
|
+
|
|
1139
|
+
Alternatives considered and rejected:
|
|
1140
|
+
- JSON: Too verbose for deep nesting
|
|
1141
|
+
- TOML: Difficulty with deeply nested maps
|
|
1142
|
+
- SDLang/KDL: Promising but lacking wide adoption
|
|
1143
|
+
|
|
1144
|
+
### Complete Example: Accordion
|
|
1145
|
+
|
|
1146
|
+
```yaml
|
|
1147
|
+
node:
|
|
1148
|
+
uri: global.component.accordion
|
|
1149
|
+
styles:
|
|
1150
|
+
# Structural (invariant)
|
|
1151
|
+
layout.type: stack
|
|
1152
|
+
layout.direction: vertical
|
|
1153
|
+
|
|
1154
|
+
# Themeable
|
|
1155
|
+
appearance.border: border/style/accordion
|
|
1156
|
+
|
|
1157
|
+
edges:
|
|
1158
|
+
# The repeating child is NAMED — reference its URI only (DRY Principle);
|
|
1159
|
+
# its subtree lives in its own anatomy. The live Accordion does the same.
|
|
1160
|
+
- node:
|
|
1161
|
+
uri: global.subcomponent.accordion-item
|
|
1162
|
+
relation:
|
|
1163
|
+
cardinality: "1..*"
|
|
1164
|
+
slotName: default
|
|
1165
|
+
```
|
|
1166
|
+
|
|
1167
|
+
The child's own anatomy — a separate spec on `global.subcomponent.accordion-item`
|
|
1168
|
+
(transcribed from the live block) — carries the subtree as anonymous roles:
|
|
1169
|
+
|
|
1170
|
+
```yaml
|
|
1171
|
+
node:
|
|
1172
|
+
uri: global.subcomponent.accordion-item
|
|
1173
|
+
styles:
|
|
1174
|
+
layout.type: stack
|
|
1175
|
+
layout.direction: vertical
|
|
1176
|
+
edges:
|
|
1177
|
+
- node:
|
|
1178
|
+
role: header tab
|
|
1179
|
+
styles:
|
|
1180
|
+
layout.type: flow
|
|
1181
|
+
layout.direction: horizontal
|
|
1182
|
+
layout.align: center
|
|
1183
|
+
interaction.cursor: pointer
|
|
1184
|
+
edges:
|
|
1185
|
+
- node:
|
|
1186
|
+
role: control
|
|
1187
|
+
styles:
|
|
1188
|
+
size.width: size/icon/small
|
|
1189
|
+
size.height: size/icon/small
|
|
1190
|
+
relation:
|
|
1191
|
+
cardinality: "1"
|
|
1192
|
+
- node:
|
|
1193
|
+
role: heading
|
|
1194
|
+
relation:
|
|
1195
|
+
cardinality: "1"
|
|
1196
|
+
slotName: default
|
|
1197
|
+
relation:
|
|
1198
|
+
cardinality: "1"
|
|
1199
|
+
slotName: header
|
|
1200
|
+
|
|
1201
|
+
- node:
|
|
1202
|
+
role: content panel
|
|
1203
|
+
styles:
|
|
1204
|
+
layout.overflow: hidden
|
|
1205
|
+
relation:
|
|
1206
|
+
cardinality: "1"
|
|
1207
|
+
slotName: default
|
|
1208
|
+
```
|
|
1209
|
+
|
|
1210
|
+
### Future Work
|
|
1211
|
+
|
|
1212
|
+
The following features would require additional work:
|
|
1213
|
+
- Representation of related nodes displayed in portals
|
|
1214
|
+
- Representation of conditional display
|
|
1215
|
+
- Inheritance reference for styles (e.g., "NetworkCard inherits from Card")
|
|
1216
|
+
|
|
1217
|
+
## Support
|
|
1218
|
+
|
|
1219
|
+
If this skill leads somewhere broken — a command that errors, guidance that
|
|
1220
|
+
contradicts what the live system answers, a gap the flow cannot cover — you are not
|
|
1221
|
+
stuck:
|
|
1222
|
+
|
|
1223
|
+
- Raise an issue in the pragma repo: https://github.com/canonical/pragma/issues —
|
|
1224
|
+
include the skill name, what was run, and expected vs. actual outcome.
|
|
1225
|
+
- Or contact the design-system team owners directly through your organization's
|
|
1226
|
+
professional messaging channels for assistance.
|