@canonical/pragma-cli 0.35.0 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/README.md +2 -2
  2. package/bundled-skills/add-standard/SKILL.md +338 -0
  3. package/bundled-skills/adoption-a1-styles/SKILL.md +269 -0
  4. package/bundled-skills/adoption-a2-components/SKILL.md +291 -0
  5. package/bundled-skills/adoption-a3-forms/SKILL.md +260 -0
  6. package/bundled-skills/anatomy-author/ANATOMY_DSL_SPEC.md +406 -0
  7. package/bundled-skills/anatomy-author/SKILL.md +1226 -0
  8. package/bundled-skills/design-auditor/SKILL.md +455 -0
  9. package/bundled-skills/implement-component/SKILL.md +333 -0
  10. package/bundled-skills/specify-component/SKILL.md +331 -0
  11. package/bundled-skills/specify-pattern/SKILL.md +292 -0
  12. package/dist/package.json +16 -15
  13. package/dist/pragma.conf.js +236 -34
  14. package/dist/pragma.conf.js.map +1 -1
  15. package/dist/src/bin.js +1 -1
  16. package/dist/src/bin.js.map +1 -1
  17. package/dist/src/capabilities/colophon/colophon.render.js +1 -1
  18. package/dist/src/capabilities/colophon/colophon.render.js.map +1 -1
  19. package/dist/src/capabilities/create/mount.js +4 -3
  20. package/dist/src/capabilities/create/mount.js.map +1 -1
  21. package/dist/src/capabilities/doctor/checks/checkPackageRefs.js +60 -4
  22. package/dist/src/capabilities/doctor/checks/checkPackageRefs.js.map +1 -1
  23. package/dist/src/capabilities/doctor/checks/checkShellCompletions.js +8 -8
  24. package/dist/src/capabilities/doctor/checks/checkShellCompletions.js.map +1 -1
  25. package/dist/src/capabilities/doctor/checks/harnessInventory.js +195 -0
  26. package/dist/src/capabilities/doctor/checks/harnessInventory.js.map +1 -0
  27. package/dist/src/capabilities/doctor/checks/index.js +6 -6
  28. package/dist/src/capabilities/doctor/checks/targetHealth.js +194 -50
  29. package/dist/src/capabilities/doctor/checks/targetHealth.js.map +1 -1
  30. package/dist/src/capabilities/doctor/doctor.render.js +43 -64
  31. package/dist/src/capabilities/doctor/doctor.render.js.map +1 -1
  32. package/dist/src/capabilities/doctor/doctor.verb.js +6 -2
  33. package/dist/src/capabilities/doctor/doctor.verb.js.map +1 -1
  34. package/dist/src/capabilities/doctor/runChecks.js +14 -11
  35. package/dist/src/capabilities/doctor/runChecks.js.map +1 -1
  36. package/dist/src/capabilities/graph/query.render.js +2 -2
  37. package/dist/src/capabilities/graph/query.render.js.map +1 -1
  38. package/dist/src/capabilities/info/collectInfo.js +8 -2
  39. package/dist/src/capabilities/info/collectInfo.js.map +1 -1
  40. package/dist/src/capabilities/info/info.render.js +6 -2
  41. package/dist/src/capabilities/info/info.render.js.map +1 -1
  42. package/dist/src/capabilities/prompt/prompt.render.js +2 -2
  43. package/dist/src/capabilities/prompt/prompt.render.js.map +1 -1
  44. package/dist/src/capabilities/setup/buildPlan.js +43 -39
  45. package/dist/src/capabilities/setup/buildPlan.js.map +1 -1
  46. package/dist/src/capabilities/setup/mode.js +2 -2
  47. package/dist/src/capabilities/setup/operations/index.js +4 -4
  48. package/dist/src/capabilities/setup/operations/index.js.map +1 -1
  49. package/dist/src/capabilities/setup/operations/setupGenerator.js +92 -45
  50. package/dist/src/capabilities/setup/operations/setupGenerator.js.map +1 -1
  51. package/dist/src/capabilities/setup/operations/setupLsp.js +88 -36
  52. package/dist/src/capabilities/setup/operations/setupLsp.js.map +1 -1
  53. package/dist/src/capabilities/setup/operations/setupMcp.js +97 -51
  54. package/dist/src/capabilities/setup/operations/setupMcp.js.map +1 -1
  55. package/dist/src/capabilities/setup/operations/setupSkills.js +325 -47
  56. package/dist/src/capabilities/setup/operations/setupSkills.js.map +1 -1
  57. package/dist/src/capabilities/setup/plan.js.map +1 -1
  58. package/dist/src/capabilities/setup/plan.render.js +144 -66
  59. package/dist/src/capabilities/setup/plan.render.js.map +1 -1
  60. package/dist/src/capabilities/setup/setup.render.js +1 -1
  61. package/dist/src/capabilities/setup/setup.verb.js +86 -33
  62. package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
  63. package/dist/src/capabilities/setup/targets.js +110 -45
  64. package/dist/src/capabilities/setup/targets.js.map +1 -1
  65. package/dist/src/capabilities/shared/index.js +6 -5
  66. package/dist/src/capabilities/shared/index.js.map +1 -1
  67. package/dist/src/capabilities/shared/packageManager.js +300 -54
  68. package/dist/src/capabilities/shared/packageManager.js.map +1 -1
  69. package/dist/src/capabilities/skill/discover.js +97 -11
  70. package/dist/src/capabilities/skill/discover.js.map +1 -1
  71. package/dist/src/capabilities/skill/render.js +2 -2
  72. package/dist/src/capabilities/skill/render.js.map +1 -1
  73. package/dist/src/capabilities/sources/installSkills.js +145 -3
  74. package/dist/src/capabilities/sources/installSkills.js.map +1 -1
  75. package/dist/src/capabilities/sources/runUpdate.js +68 -2
  76. package/dist/src/capabilities/sources/runUpdate.js.map +1 -1
  77. package/dist/src/capabilities/sources/update.verb.js +3 -3
  78. package/dist/src/capabilities/sources/update.verb.js.map +1 -1
  79. package/dist/src/capabilities/upgrade/runUpgrade.js +43 -7
  80. package/dist/src/capabilities/upgrade/runUpgrade.js.map +1 -1
  81. package/dist/src/capabilities/upgrade/upgrade.render.js +10 -2
  82. package/dist/src/capabilities/upgrade/upgrade.render.js.map +1 -1
  83. package/dist/src/constants.js +6 -1
  84. package/dist/src/constants.js.map +1 -1
  85. package/dist/src/kernel/index.js +11 -2
  86. package/dist/src/kernel/index.js.map +1 -1
  87. package/dist/src/kernel/interactivity.js +68 -0
  88. package/dist/src/kernel/interactivity.js.map +1 -0
  89. package/dist/src/kernel/packs/renderPack.js +3 -3
  90. package/dist/src/kernel/packs/renderPack.js.map +1 -1
  91. package/dist/src/kernel/packs/resolveEntity.js +63 -12
  92. package/dist/src/kernel/packs/resolveEntity.js.map +1 -1
  93. package/dist/src/kernel/packs/runBodies.js +57 -4
  94. package/dist/src/kernel/packs/runBodies.js.map +1 -1
  95. package/dist/src/kernel/packs/schema.js +54 -2
  96. package/dist/src/kernel/packs/schema.js.map +1 -1
  97. package/dist/src/kernel/packs/sparql/applyFilters.js +104 -5
  98. package/dist/src/kernel/packs/sparql/applyFilters.js.map +1 -1
  99. package/dist/src/kernel/packs/sparql/buildLookupQuery.js +306 -43
  100. package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -1
  101. package/dist/src/kernel/packs/types.js.map +1 -1
  102. package/dist/src/kernel/project/cli/buildProgram.js +1 -1
  103. package/dist/src/kernel/project/cli/buildProgram.js.map +1 -1
  104. package/dist/src/kernel/project/cli/dispatch.js +23 -23
  105. package/dist/src/kernel/project/cli/dispatch.js.map +1 -1
  106. package/dist/src/kernel/project/cli/globalFlags.js +4 -1
  107. package/dist/src/kernel/project/cli/globalFlags.js.map +1 -1
  108. package/dist/src/kernel/project/cli/helpFormat.js +1 -2
  109. package/dist/src/kernel/project/cli/helpFormat.js.map +1 -1
  110. package/dist/src/kernel/project/cli/rootHelp.js +34 -4
  111. package/dist/src/kernel/project/cli/rootHelp.js.map +1 -1
  112. package/dist/src/kernel/project/mcp/registerVerb.js +16 -1
  113. package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -1
  114. package/dist/src/kernel/render/renderers.js +1 -1
  115. package/dist/src/kernel/render/style.js +29 -9
  116. package/dist/src/kernel/render/style.js.map +1 -1
  117. package/dist/src/kernel/render/vocabulary.js +174 -0
  118. package/dist/src/kernel/render/vocabulary.js.map +1 -0
  119. package/dist/src/kernel/spec/emitReference.js +1 -1
  120. package/dist/src/kernel/spec/emitReference.js.map +1 -1
  121. package/package.json +16 -15
  122. package/pragma.conf.ts +241 -34
  123. package/src/bin.ts +8 -1
  124. package/src/capabilities/block.parity.test.ts +25 -11
  125. package/src/capabilities/block.shipped.exec.test.ts +282 -0
  126. package/src/capabilities/block.tierRank.exec.test.ts +300 -0
  127. package/src/capabilities/colophon/colophon.render.ts +1 -2
  128. package/src/capabilities/colophon/colophon.test.ts +2 -2
  129. package/src/capabilities/create/createGrammar.test.ts +1 -1
  130. package/src/capabilities/create/mount.ts +3 -3
  131. package/src/capabilities/distribution.parity.test.ts +4 -0
  132. package/src/capabilities/doctor/checks/checkPackageRefs.ts +69 -4
  133. package/src/capabilities/doctor/checks/checkShellCompletions.test.ts +6 -4
  134. package/src/capabilities/doctor/checks/checkShellCompletions.ts +8 -8
  135. package/src/capabilities/doctor/checks/harnessInventory.test.ts +219 -0
  136. package/src/capabilities/doctor/checks/harnessInventory.ts +298 -0
  137. package/src/capabilities/doctor/checks/index.ts +6 -6
  138. package/src/capabilities/doctor/checks/targetHealth.ts +229 -59
  139. package/src/capabilities/doctor/doctor.render.test.ts +108 -32
  140. package/src/capabilities/doctor/doctor.render.ts +56 -80
  141. package/src/capabilities/doctor/doctor.test.ts +247 -35
  142. package/src/capabilities/doctor/doctor.verb.ts +7 -2
  143. package/src/capabilities/doctor/runChecks.ts +14 -11
  144. package/src/capabilities/doctor/types.ts +15 -24
  145. package/src/capabilities/emptyRecovery.test.ts +10 -2
  146. package/src/capabilities/graph/query.render.ts +2 -2
  147. package/src/capabilities/info/collectInfo.ts +8 -1
  148. package/src/capabilities/info/info.render.ts +6 -2
  149. package/src/capabilities/info/info.test.ts +37 -1
  150. package/src/capabilities/info/types.ts +10 -2
  151. package/src/capabilities/prompt/prompt.render.ts +2 -2
  152. package/src/capabilities/renderVocabularySync.test.ts +82 -0
  153. package/src/capabilities/scopeTypeSync.test.ts +29 -15
  154. package/src/capabilities/setup/buildPlan.ts +45 -41
  155. package/src/capabilities/setup/index.ts +1 -1
  156. package/src/capabilities/setup/mode.ts +2 -2
  157. package/src/capabilities/setup/operations/index.ts +6 -1
  158. package/src/capabilities/setup/operations/setupGenerator.ts +144 -70
  159. package/src/capabilities/setup/operations/setupLsp.ts +129 -37
  160. package/src/capabilities/setup/operations/setupMcp.ts +132 -61
  161. package/src/capabilities/setup/operations/setupSkills.ts +392 -58
  162. package/src/capabilities/setup/plan.render.ts +171 -76
  163. package/src/capabilities/setup/plan.ts +17 -12
  164. package/src/capabilities/setup/setup.mcp.correctness.test.ts +9 -9
  165. package/src/capabilities/setup/setup.render.test.ts +50 -36
  166. package/src/capabilities/setup/setup.render.ts +1 -1
  167. package/src/capabilities/setup/setup.test.ts +881 -50
  168. package/src/capabilities/setup/setup.verb.ts +91 -33
  169. package/src/capabilities/setup/targets.ts +120 -52
  170. package/src/capabilities/setup/types.ts +10 -18
  171. package/src/capabilities/shared/index.ts +17 -6
  172. package/src/capabilities/shared/packageManager.test.ts +421 -6
  173. package/src/capabilities/shared/packageManager.ts +394 -59
  174. package/src/capabilities/skill/bundledSkills.test.ts +347 -0
  175. package/src/capabilities/skill/discover.ts +100 -11
  176. package/src/capabilities/skill/render.ts +2 -2
  177. package/src/capabilities/skill/skill.test.ts +17 -4
  178. package/src/capabilities/sources/installSkills.ts +181 -3
  179. package/src/capabilities/sources/runUpdate.ts +78 -2
  180. package/src/capabilities/sources/sources.test.ts +228 -0
  181. package/src/capabilities/sources/update.verb.ts +3 -3
  182. package/src/capabilities/surface.test.ts +2 -2
  183. package/src/capabilities/upgrade/runUpgrade.ts +45 -6
  184. package/src/capabilities/upgrade/types.ts +10 -2
  185. package/src/capabilities/upgrade/upgrade.render.ts +9 -2
  186. package/src/capabilities/upgrade/upgrade.test.ts +72 -0
  187. package/src/constants.ts +6 -1
  188. package/src/identity.test.ts +18 -5
  189. package/src/kernel/completion/entitySource.test.ts +15 -2
  190. package/src/kernel/completion/safety.test.ts +16 -9
  191. package/src/kernel/config/defaults.test.ts +2 -1
  192. package/src/kernel/index.ts +11 -2
  193. package/src/kernel/interactivity.test.ts +76 -0
  194. package/src/kernel/interactivity.ts +69 -0
  195. package/src/kernel/packs/compile.test.ts +92 -0
  196. package/src/kernel/packs/renderPack.ts +3 -3
  197. package/src/kernel/packs/resolveEntity.test.ts +75 -14
  198. package/src/kernel/packs/resolveEntity.ts +88 -24
  199. package/src/kernel/packs/runBodies.ts +84 -19
  200. package/src/kernel/packs/schema.ts +60 -4
  201. package/src/kernel/packs/sparql/applyFilters.ts +125 -4
  202. package/src/kernel/packs/sparql/buildLookupQuery.test.ts +290 -0
  203. package/src/kernel/packs/sparql/buildLookupQuery.ts +323 -45
  204. package/src/kernel/packs/toolDescription.test.ts +35 -1
  205. package/src/kernel/packs/types.ts +125 -2
  206. package/src/kernel/project/cli/buildProgram.test.ts +73 -2
  207. package/src/kernel/project/cli/buildProgram.ts +8 -1
  208. package/src/kernel/project/cli/dispatch.test.ts +5 -4
  209. package/src/kernel/project/cli/dispatch.ts +25 -24
  210. package/src/kernel/project/cli/globalFlags.ts +4 -1
  211. package/src/kernel/project/cli/helpFormat.ts +1 -2
  212. package/src/kernel/project/cli/rootHelp.ts +36 -3
  213. package/src/kernel/project/mcp/registerVerb.ts +20 -1
  214. package/src/kernel/render/renderers.ts +1 -1
  215. package/src/kernel/render/style.ts +32 -9
  216. package/src/kernel/render/vocabulary.ts +276 -0
  217. package/src/kernel/spec/emitReference.ts +1 -1
  218. package/src/kernel/spec/types.ts +35 -6
  219. package/src/testing/PARITY_GAPS.ts +9 -3
  220. package/src/testing/behavioral/docExamples.test.ts +4 -2
  221. package/src/testing/behavioral/errorMatrix.mcp.test.ts +110 -7
  222. package/src/testing/behavioral/journeys.firstInstall.test.ts +7 -2
  223. package/src/testing/behavioral/journeys.packageStories.test.ts +6 -3
  224. package/src/testing/eval/__snapshots__/eval.test.ts.snap +26 -1
  225. package/src/testing/eval/cases/readNouns.ts +245 -9
  226. package/src/testing/fixtures/blockGraph.ts +28 -13
  227. package/src/testing/fixtures/graph/canonical.ts +95 -12
  228. package/dist/src/capabilities/shared/bands.js +0 -16
  229. package/dist/src/capabilities/shared/bands.js.map +0 -1
  230. package/src/capabilities/shared/bands.ts +0 -16
@@ -0,0 +1,291 @@
1
+ ---
2
+ name: adoption-a2-components
3
+ description: Swap an application's UI to Canonical design-system components one component type per PR (adoption track A2); also runs as a guided tutorial on a worked example
4
+ ---
5
+
6
+ # Adoption A2 — Components
7
+
8
+ The second adoption track: replace the app's existing UI elements with design-system
9
+ components. Prerequisite: track A1 is done — styles, fonts, and icons are live, so
10
+ swapped components land on the right foundation. That includes the root context and
11
+ density classes (`class="app comfortable"` — A1's "Declare the surface" section):
12
+ component geometry reads the `--density-*` channel those classes emit, and a swap onto
13
+ a class-less root looks subtly wrong rather than broken.
14
+
15
+ ## When to Use
16
+
17
+ - Track A1 is done and the app's UI elements should become design-system components
18
+ - Planning or executing a component swap PR
19
+
20
+ ## When NOT to Use
21
+
22
+ - The visual foundation is not in place — run `adoption-a1-styles` first
23
+ - Migrating forms — a different programming model: `adoption-a3-forms`
24
+
25
+ (An uncovered component TYPE is not a reason to skip the track — it is a per-type
26
+ finding: see "The covered set is queried live" below.)
27
+
28
+ ## Opening move: ask, or offer the tutorial
29
+
30
+ Activation is an opening, not a starting gun. Unless the first message already names
31
+ the work, ask for the starting point — as a suggestion carrying an example, not as a
32
+ form to fill in:
33
+
34
+ > To get started, point me at the app and say which component type you'd like swapped
35
+ > first — buttons, say — or let me inventory the app and suggest one.
36
+
37
+ The inventory is cheap, so no preference is a fine answer. Ask again only for what the
38
+ next step genuinely blocks on.
39
+
40
+ If the activation message already carries the starting point, do not re-ask — say what
41
+ you took it to be, and go.
42
+
43
+ Offer the tutorial in the same breath, because this track doubles as one:
44
+
45
+ > Or if you'd rather see what a swap looks like before touching your app, I can run it
46
+ > as a tutorial: I'll take a plausible example — buttons in a small React app — and
47
+ > walk you through one type from inventory to a finished PR.
48
+
49
+ Tutorial mode is the step narration below, turned up: the same outcome-path-conclusion
50
+ frame, with the reasoning behind each change made explicit. It runs on a scratch
51
+ example, not on the person's app — nothing in their repo changes unless they ask.
52
+
53
+ ## Every step is narrated: outcome, path, conclusion
54
+
55
+ Every step is addressed to the person — once before it runs, once when it ends. This is
56
+ not tutorial manner. It holds in the ordinary flow too: the person is the design
57
+ authority, and they can only steer a step they saw coming. A decision gate met cold is
58
+ a decision they cannot really make.
59
+
60
+ **Open** the step with what it is FOR — the outcome it should leave behind — and how
61
+ you are about to get there. **Close** it with a sentence saying what it established and
62
+ what that means for the step after. One sentence each is enough; the failure mode is
63
+ silence, not length, and a step whose outcome looks obvious still gets its sentence
64
+ rather than a shrug.
65
+
66
+ Do:
67
+
68
+ > **Inventory first.** The outcome is a real count of what the app uses, per component
69
+ > type, so the first swap PR is chosen from evidence rather than instinct. The path is
70
+ > the greps below over `src/`, deduped with counts.
71
+ >
72
+ > …
73
+ >
74
+ > So: 43 button call sites against 6 cards — buttons are the widest swap and the
75
+ > obvious first PR, and two of those call sites use a variant the system has no
76
+ > modifier for, which is a finding to report rather than a blocker.
77
+
78
+ Don't:
79
+
80
+ > Inventory: 43 buttons, 6 cards, 3 tooltips.
81
+
82
+ Both carry the same facts. Only the first says what the step was trying to achieve and
83
+ what it settled — and only the first lets the person cut in with the thing they know
84
+ and the graph does not, which is the whole reason they are here.
85
+
86
+ ## Asked about the skill: methodology and outcomes first
87
+
88
+ A question about what this skill does — what it covers, what its steps are, how it
89
+ works — is answered in that order: the METHOD it applies and the OUTCOMES it leaves
90
+ the person holding come first, the step-by-step breakdown comes after.
91
+
92
+ Two or three sentences of method is enough: what the skill treats as its object, the
93
+ discipline that makes it work, and what exists at the end that did not exist before.
94
+ The enumeration then reads as steps in service of something, rather than as a list to
95
+ be got through.
96
+
97
+ What this prevents is a table of contents standing in for an answer. A reply opening
98
+ with the step names tells someone who already knows the skill nothing new, and someone
99
+ who does not, nothing at all.
100
+
101
+ So, asked what this skill is:
102
+
103
+ > It replaces the app's UI elements with design-system components, one component type
104
+ > per PR, with the order chosen from a real inventory rather than instinct. You end
105
+ > with each swapped type sitting on A1's foundation, accessibility warnings treated as
106
+ > contract, and any type the system does not cover reported as a finding rather than
107
+ > treated as a blocker.
108
+
109
+ Then, and only then, the breakdown.
110
+
111
+ ## Install
112
+
113
+ ```bash
114
+ bun add @canonical/react-ds-global
115
+ ```
116
+
117
+ That is the universal (global) tier package. App-tier siblings, where they exist,
118
+ follow a naming convention — the mapping is convention, not a graph field:
119
+ `@canonical/<framework>-ds-global` for the `global` tier, `-ds-app` for the shared
120
+ `apps` tier, and `-ds-app-<app>` for an app-specific tier (so the `apps_lxd` tier
121
+ installs `@canonical/react-ds-app-lxd`). The `<app>` segment is the package's own
122
+ short name, not always the tier suffix (the `apps_workplaceengineering` tier's
123
+ package is `@canonical/svelte-ds-app-wpe`) — `pragma tier list` shows which tiers
124
+ exist, and the derived package name must resolve before you install it. Tiers
125
+ outside `global`/`apps*` have no package convention today: there the target package
126
+ is a decision to raise, not to derive.
127
+
128
+ ```tsx
129
+ import { Button, Badge, Card, Tooltip } from "@canonical/react-ds-global";
130
+ ```
131
+
132
+ Named imports; components accept the underlying element's native HTML attributes, so
133
+ existing `onClick`/`aria-*`/`data-*` plumbing carries over.
134
+
135
+ ## Code standards are the gate
136
+
137
+ Every swap writes component code, and that code falls under the live code standards —
138
+ the target framework's category (`react`, `svelte`, `lit`), plus `testing` and
139
+ `storybook` where the swap touches those (take the actual set from the live output,
140
+ not from this list). Pull them BEFORE the first swap of a PR and hold the Do/Don't
141
+ pairs open while writing:
142
+
143
+ ```bash
144
+ pragma standard categories # the live category set — never from memory
145
+ pragma standard list --category react # swap react for the target framework
146
+ pragma standard lookup <name> --detail detailed
147
+ ```
148
+
149
+ Apps follow the code standards as closely as the swap allows. Where swapped code has
150
+ to deviate from a pulled standard, record the deviation in the PR next to the
151
+ standard's name — "the old code did it this way" is a deviation to record, not a
152
+ reason to stay silent — and ideally file it as an issue, either to bring the app into
153
+ line later or to propose a change to the standard itself. The standards apply even
154
+ when the app has no pragma dependency, and they are open to contribution: a missing
155
+ or wrong standard is something to propose a change for, not to silently work around.
156
+
157
+ ## Inventory first (grep, don't guess)
158
+
159
+ Build the swap list from the code, not from memory:
160
+
161
+ ```bash
162
+ grep -rhoE '\bp-[a-z0-9-]+' src/ | sort | uniq -c | sort -rn # Vanilla class inventory, deduped with counts
163
+ grep -rn 'from "@canonical/react-components"' src/ # or your current component library's import
164
+ ```
165
+
166
+ Group the hits by component TYPE — all buttons, all cards, all tooltips — because the
167
+ type is the unit of work below. When you get to one type, locate its call sites with an
168
+ anchored grep:
169
+
170
+ ```bash
171
+ grep -rnE '\bp-button\b' src/ # swap p-button for the type at hand
172
+ ```
173
+
174
+ Anchor on the class TOKEN, not on the attribute — a `class(Name)?="…"` pattern
175
+ under-collects, missing template-literal (`` className={`p-button …`} ``), `clsx(…)`,
176
+ and single-quoted call sites.
177
+
178
+ ## The covered set is queried live
179
+
180
+ Before swapping a type, confirm the design system covers it TODAY:
181
+
182
+ ```bash
183
+ pragma block list # what exists, with tiers
184
+ pragma block lookup <Name> # anatomy, modifiers, properties (MCP: block_lookup)
185
+ ```
186
+
187
+ A bare name can match blocks in several tiers, and `block lookup` silently picks
188
+ one — it resolves `ds:name` globally and cannot be steered to a tier.
189
+ Confirm the tier the lookup picked from its own `- Tier:` line; the name query below lists every tier that carries the name.
190
+ The name query
191
+ `pragma graph query "SELECT ?b WHERE { ?b ds:name ?n . FILTER(LCASE(?n) = LCASE('<Name>')) }"`
192
+ prints every tier's IRI for `pragma graph inspect <IRI>`. If the lookup picked the wrong
193
+ tier's block, read the one you want with `pragma graph inspect <IRI from that row>`
194
+ instead.
195
+
196
+ **A block being in the graph does not mean the package exports it.** The graph
197
+ records what the design system has DOCUMENTED; the package ships what has been
198
+ BUILT, and the two run ahead of each other in both directions. So before you
199
+ swap a type, confirm the named export in the package you installed:
200
+
201
+ ```bash
202
+ node -e "console.log(Object.keys(require('@canonical/react-ds-global')))" | tr ',' '\n' | grep -i '<Name>'
203
+ ```
204
+
205
+ If the export is absent, the block is documented but not available — file the
206
+ gap and leave the existing markup in place. Do not infer availability from a
207
+ Storybook story either: a story can exist for a block that ships no
208
+ implementation and no export, so a story CORROBORATES an export you have
209
+ already confirmed, and never substitutes for it.
210
+
211
+ If the name has no row and the lookup errors, the block may be a Group — `block list`/`block lookup` cover no groups; the name query above returns its IRI for `pragma graph inspect <IRI>`.
212
+ Run it before calling a type uncovered: `Cards` and `KeyboardKeys` are groups, and both
213
+ ship from the `@canonical/react-ds-global` this track installs. The export check above
214
+ applies to them exactly as it does to any other block.
215
+
216
+ Then check the component in a running Storybook — your app's own (track A1 set it up
217
+ via `@canonical/storybook-config`), your team's hosted hub, or `bun run storybook`
218
+ inside the component's package in the pragma monorepo (e.g. `packages/react/ds-global`
219
+ — the monorepo root has no such script).
220
+
221
+ > The covered set is whatever the graph answers today. Query it live — never copy its
222
+ > output into documentation, PRs, or this skill.
223
+
224
+ A gap found is a FINDING to report (and a `specify-component` candidate) — skip that
225
+ type and keep swapping the types the system does cover; never hand-roll a lookalike
226
+ silently.
227
+
228
+ ## One component type per PR
229
+
230
+ The swap unit is a component TYPE across the app — all buttons in one PR — not a page.
231
+
232
+ - Diffs stay reviewable: one decision, applied everywhere.
233
+ - Accessibility regressions stay attributable to the one swap that caused them.
234
+ - One PR reverts one decision, cleanly.
235
+
236
+ ## Names and boundaries changed vs Vanilla
237
+
238
+ Do NOT assume a 1:1 mapping from Vanilla names and `p-*` classes. Boundaries moved:
239
+ what was one blob may now be a component plus subcomponents (e.g. `Accordion` with
240
+ `Accordion.Item`), and icon semantics changed to `currentColor`. The block's `ds:usage`
241
+ When-to-use / When-not-to-use guidance decides what replaces what — read it with
242
+ `pragma graph inspect <block IRI>` (the IRI from the block's `pragma block list` row;
243
+ `block lookup` does not render the usage field today) — often the honest swap is a
244
+ DIFFERENT component than the old name suggests.
245
+
246
+ ## Accessibility warnings are contract
247
+
248
+ The components warn in dev mode when misused — for example, an icon-only `Button`
249
+ without `aria-label`/`aria-labelledby` logs a console warning, and icons are
250
+ decorative/aria-hidden unless labelled. Treat every such warning as a FAILED acceptance
251
+ criterion of the swap PR: fix the usage, never silence the warning.
252
+
253
+ Modifier props (e.g. `anticipation="constructive"` on `Button`) take their values from
254
+ modifier families — read them live, list first:
255
+
256
+ ```bash
257
+ pragma modifier list # every family with its values
258
+ pragma modifier lookup <Family> # one family in full — take the name from the list output
259
+ ```
260
+
261
+ The families a block draws from print as the last column of its `pragma block list`
262
+ row, and in full on the block itself via `pragma graph inspect <IRI from that row>`
263
+ (the `ds:hasModifierFamily` triples). Mapping a family to code follows two rules: the
264
+ React prop IS the family name lowercased (`Anticipation` → `anticipation`), and the
265
+ prop's values are the family's values lowercased with spaces as underscores
266
+ (`In Progress` → `in_progress`) — the lookups print display casing; the code takes
267
+ the lowercase form. A family listed on the block may have no prop on the component
268
+ yet — check the component's types before writing it.
269
+
270
+ ## Verify per PR
271
+
272
+ - [ ] Root context and density classes present (track A1) — swapped components size
273
+ from the `--density-*` channel, not from luck.
274
+ - [ ] Zero dev-console warnings from the swapped components.
275
+ - [ ] Keyboard pass on each swapped instance (focus, activation, escape where relevant).
276
+ - [ ] Visual pass against the component's Storybook stories.
277
+
278
+ ## Next
279
+
280
+ Forms are their own programming model — continue with `adoption-a3-forms`.
281
+
282
+ ## Support
283
+
284
+ If this skill leads somewhere broken — a command that errors, guidance that
285
+ contradicts what the live system answers, a gap the flow cannot cover — you are not
286
+ stuck:
287
+
288
+ - Raise an issue in the pragma repo: https://github.com/canonical/pragma/issues —
289
+ include the skill name, what was run, and expected vs. actual outcome.
290
+ - Or contact the design-system team owners directly through your organization's
291
+ professional messaging channels for assistance.
@@ -0,0 +1,260 @@
1
+ ---
2
+ name: adoption-a3-forms
3
+ description: Migrate application forms to the Canonical form system built on react-hook-form (adoption track A3); also runs as a guided tutorial on a worked example
4
+ ---
5
+
6
+ # Adoption A3 — Forms
7
+
8
+ The third adoption track: move the app's forms onto the design-system form package.
9
+ Prerequisite: track A1 (A2 recommended). Understand what this track is before starting:
10
+ the form system is a DIFFERENT PROGRAMMING MODEL built on react-hook-form, not a restyle
11
+ of your existing inputs.
12
+
13
+ ## When to Use
14
+
15
+ - Track A1 is done and the app's forms should move onto the design-system form package
16
+ - A form is being built or rewritten and should use the `Form`/`Field` model from the
17
+ start
18
+ - A form contains an input type the system does not cover — the `custom` escape hatch
19
+ below keeps it inside the `Form` (and the gap is a finding to report)
20
+
21
+ ## When NOT to Use
22
+
23
+ - The visual foundation is not in place yet — run `adoption-a1-styles` first
24
+ - Swapping non-form components — that is `adoption-a2-components`
25
+
26
+ ## Opening move: ask, or offer the tutorial
27
+
28
+ Activation is an opening, not a starting gun. Unless the first message already names
29
+ the work, ask for the starting point — as a suggestion carrying an example, not as a
30
+ form to fill in:
31
+
32
+ > To get started, point me at the app and say which form you'd like moved first — or
33
+ > let me find the forms and suggest a starting one.
34
+
35
+ The smallest real form is usually the best first move: the programming-model switch is
36
+ easier to see on a short one. Ask again only for what the next step genuinely blocks
37
+ on.
38
+
39
+ If the activation message already carries the starting point, do not re-ask — say what
40
+ you took it to be, and go.
41
+
42
+ Offer the tutorial in the same breath, because this track doubles as one:
43
+
44
+ > Or if you'd rather see the model before touching your forms, I can run it as a
45
+ > tutorial: I'll take a plausible example — a couple of text fields and a select — and
46
+ > walk you through the migration on it.
47
+
48
+ Tutorial mode is the step narration below, turned up: the same outcome-path-conclusion
49
+ frame, with the reasoning behind each change made explicit. It runs on a scratch
50
+ example, not on the person's app — nothing in their repo changes unless they ask.
51
+
52
+ ## Every step is narrated: outcome, path, conclusion
53
+
54
+ Every step is addressed to the person — once before it runs, once when it ends. This is
55
+ not tutorial manner. It holds in the ordinary flow too: the person is the design
56
+ authority, and they can only steer a step they saw coming. A decision gate met cold is
57
+ a decision they cannot really make.
58
+
59
+ **Open** the step with what it is FOR — the outcome it should leave behind — and how
60
+ you are about to get there. **Close** it with a sentence saying what it established and
61
+ what that means for the step after. One sentence each is enough; the failure mode is
62
+ silence, not length, and a step whose outcome looks obvious still gets its sentence
63
+ rather than a shrug.
64
+
65
+ Do:
66
+
67
+ > **The first field conversion.** The outcome is one field moved onto the `Form`/`Field`
68
+ > model, proving the wiring end to end before the rest follow. The path is to replace
69
+ > the input with a `<Field>` and let `Form` own the state `useState` was holding.
70
+ >
71
+ > …
72
+ >
73
+ > So: the field validates and submits through react-hook-form and the local `useState`
74
+ > is gone — every remaining field is the same move, except the file upload, which needs
75
+ > the `custom` escape hatch.
76
+
77
+ Don't:
78
+
79
+ > Converted the email field. Works.
80
+
81
+ Both carry the same facts. Only the first says what the step was trying to achieve and
82
+ what it settled — and only the first lets the person cut in with the thing they know
83
+ and the graph does not, which is the whole reason they are here.
84
+
85
+ ## Asked about the skill: methodology and outcomes first
86
+
87
+ A question about what this skill does — what it covers, what its steps are, how it
88
+ works — is answered in that order: the METHOD it applies and the OUTCOMES it leaves
89
+ the person holding come first, the step-by-step breakdown comes after.
90
+
91
+ Two or three sentences of method is enough: what the skill treats as its object, the
92
+ discipline that makes it work, and what exists at the end that did not exist before.
93
+ The enumeration then reads as steps in service of something, rather than as a list to
94
+ be got through.
95
+
96
+ What this prevents is a table of contents standing in for an answer. A reply opening
97
+ with the step names tells someone who already knows the skill nothing new, and someone
98
+ who does not, nothing at all.
99
+
100
+ So, asked what this skill is:
101
+
102
+ > It moves forms onto the design-system form package, and the thing to understand
103
+ > before starting is that this is a DIFFERENT PROGRAMMING MODEL built on
104
+ > react-hook-form — fields as data, `Form` owning the state — not a restyle of the
105
+ > existing inputs. You end with forms on the `Form`/`Field` model, and anything the
106
+ > system does not cover kept inside the `Form` through the `custom` escape hatch.
107
+
108
+ Then, and only then, the breakdown.
109
+
110
+ ## Install + the forms styles import
111
+
112
+ ```bash
113
+ bun add @canonical/react-ds-global-form @canonical/styles
114
+ ```
115
+
116
+ Then BOTH stylesheets in the root stylesheet:
117
+
118
+ ```css
119
+ @import url("@canonical/styles");
120
+ @import url("@canonical/react-ds-global-form/dist/esm/index.css");
121
+ ```
122
+
123
+ The first brings the global reset and tokens; the second the input chrome and field
124
+ layout. The package builds on top of `@canonical/react-ds-global`.
125
+
126
+ Forms are the heaviest consumer of the density system: control heights, field spacing,
127
+ and baseline seating all read the `--density-*` channel, which only emits real values
128
+ when the app root carries its context and density classes
129
+ (`class="app comfortable"` — track A1's "Declare the surface" section). Confirm those
130
+ classes before judging any form layout: without them the primitives silently fall back
131
+ to app-comfortable values, and a `dense` or site-surface form renders with the wrong
132
+ geometry.
133
+
134
+ ## Code standards are the gate
135
+
136
+ Form migration is component code, and it falls under the live code standards — the
137
+ `react` category at minimum, plus `testing` where the migration touches tests (take
138
+ the actual set from the live output, not from this list). Pull them BEFORE the first
139
+ field conversion and hold the Do/Don't pairs open while writing:
140
+
141
+ ```bash
142
+ pragma standard categories # the live category set — never from memory
143
+ pragma standard list --category react
144
+ pragma standard lookup <name> --detail detailed
145
+ ```
146
+
147
+ Apps follow the code standards as closely as the migration allows. Where a
148
+ conversion has to deviate from a pulled standard, record the deviation in the PR next
149
+ to the standard's name — and ideally file it as an issue, either to bring the app
150
+ into line later or to propose a change to the standard itself. The standards apply
151
+ even when the app has no pragma dependency, and they are open to contribution: a
152
+ missing or wrong standard is something to propose a change for, not to silently work
153
+ around.
154
+
155
+ ## The react-hook-form switch
156
+
157
+ Form state moves INTO the form system:
158
+
159
+ - `<Form onSubmit={…}>` wraps a react-hook-form `FormProvider`. Pass your own `useForm`
160
+ return via the `methods` prop when you need external control; otherwise `defaultValues`
161
+ and `mode` are enough and the Form calls `useForm` internally.
162
+ - `Field` reads the form context via `useFormContext` — **a `Field` outside a `Form` is
163
+ a bug**, not a styling choice.
164
+ - Migrating a form means DELETING its `useState`/`onChange` handler plumbing, not
165
+ wrapping it. If the old state code survives the migration, the migration is not done.
166
+
167
+ ## Fields as data
168
+
169
+ Each field is a declaration, not an assembly of label + input + error markup:
170
+
171
+ ```tsx
172
+ <Form onSubmit={handleSubmit}>
173
+ <Field name="email" inputType="email" label="Email address" description="Work address preferred" />
174
+ </Form>
175
+ ```
176
+
177
+ `inputType` is a discriminated union selecting the input subcomponent — text, textarea,
178
+ select, checkbox, radio-style choices, date/time, phone, combobox, and more — and the
179
+ label, description, and error wiring come with the frame.
180
+
181
+ > The covered set is whatever the graph answers today. Query it live — never copy its
182
+ > output into documentation, PRs, or this skill.
183
+
184
+ Enumerate the input types that exist TODAY from the package's `Field` types and its
185
+ Storybook stories — not from this skill.
186
+
187
+ ## The custom escape hatch
188
+
189
+ A one-off input stays INSIDE the system:
190
+
191
+ ```tsx
192
+ <Field name="tags" inputType="custom" CustomComponent={TagPicker} label="Tags" />
193
+ ```
194
+
195
+ `CustomComponent` is a `React.ComponentType<InputProps>`: it keeps react-hook-form
196
+ registration, the label/description/error framing, and validation. The escape hatch is
197
+ for the INPUT, never for leaving the `Form`.
198
+
199
+ ## WIP-stage warnings
200
+
201
+ Input maturity is graph data, not folklore. Before building on an input type, read its
202
+ documentation stage — the stage lives on the block's graph entity:
203
+
204
+ ```bash
205
+ pragma block list # find the input's row — it carries the block's IRI
206
+ pragma graph inspect <block IRI> # every triple, including ds:documentationStage
207
+ ```
208
+
209
+ Look the row up under the PACKAGE COMPONENT's name, not the `inputType` token — the
210
+ union maps several tokens onto one component (`"text"`, `"email"`, `"tel"` and `"url"`
211
+ all render `TextField`/`TextInput`; `"file"` renders `FileUploadField`; `"choices"`
212
+ renders `ChoicesField`). Read the component a token selects from the package's `Field`
213
+ types above, then find THAT name's row.
214
+
215
+ An input name can be declared at more than one tier, so the list can carry a row per
216
+ tier — names resolve globally, not within one tier. To list every tier that declares
217
+ the name, the name query
218
+ `pragma graph query "SELECT ?b WHERE { ?b ds:name ?n . FILTER(LCASE(?n) = LCASE('<Name>')) }"`
219
+ prints every tier's IRI. Inspect the one whose tier matches the package you
220
+ installed — `global` for `@canonical/react-ds-global-form`'s inputs; the stage
221
+ below is per-block, so another tier's row answers about a different block.
222
+
223
+ Read whatever `ds:documentationStage` tag comes back — the tag vocabulary is the
224
+ graph's, not this skill's, so do not expect a fixed value list; if the predicate is
225
+ absent, no stage is recorded for that block. The tag's MEANING is graph data too —
226
+ inspect the tag entity itself:
227
+
228
+ ```bash
229
+ pragma graph inspect ds:tag.<name> # the tag the stage read returned; ds:whenToApply says what it means
230
+ ```
231
+
232
+ Split the action on what `ds:whenToApply` says. A tag meaning the block was rejected
233
+ or triaged out, or that it is only PROPOSED or postponed — not yet accepted into the
234
+ system: do NOT build on that input — raise it as a gap (the `custom` escape hatch
235
+ covers the form meanwhile). A tag meaning the documentation is unfinished: the block
236
+ may change under you — SAY SO in the migration PR rather than discovering it in
237
+ production. A tag whose `ds:whenToApply` comes back blank: the stage's meaning is
238
+ unrecorded — treat it as unknown, and say so in the PR.
239
+
240
+ Debug aid: `@canonical/storybook-addon-form-state` adds a Storybook panel showing the
241
+ live react-hook-form state while you exercise a story.
242
+
243
+ ## Verify
244
+
245
+ - [ ] Root context and density classes present (track A1) — field spacing and control
246
+ seating read the `--density-*` channel.
247
+ - [ ] Submit round-trip works, with validation errors surfaced per field.
248
+ - [ ] The whole form is completable keyboard-only.
249
+ - [ ] Zero dev-console warnings from the form components.
250
+
251
+ ## Support
252
+
253
+ If this skill leads somewhere broken — a command that errors, guidance that
254
+ contradicts what the live system answers, a gap the flow cannot cover — you are not
255
+ stuck:
256
+
257
+ - Raise an issue in the pragma repo: https://github.com/canonical/pragma/issues —
258
+ include the skill name, what was run, and expected vs. actual outcome.
259
+ - Or contact the design-system team owners directly through your organization's
260
+ professional messaging channels for assistance.