@cratis/pi 0.0.1 → 2.0.1

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 (234) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +18 -37
  3. package/package/corpus/agents/backend-developer.md +125 -0
  4. package/package/corpus/agents/code-reviewer.md +165 -0
  5. package/package/corpus/agents/coordinator.md +163 -0
  6. package/package/corpus/agents/frontend-developer.md +246 -0
  7. package/package/corpus/agents/orchestrator.md +196 -0
  8. package/package/corpus/agents/performance-reviewer.md +109 -0
  9. package/package/corpus/agents/planner.md +145 -0
  10. package/package/corpus/agents/repository-investigation-reviewer.md +45 -0
  11. package/package/corpus/agents/repository-investigator.md +50 -0
  12. package/package/corpus/agents/security-reviewer.md +118 -0
  13. package/package/corpus/agents/slice-implementer.md +59 -0
  14. package/package/corpus/agents/spec-writer.md +149 -0
  15. package/package/corpus/harnesses/pi/extensions/cratis-hooks/index.ts +213 -0
  16. package/package/corpus/harnesses/pi/extensions/cratis-rules/index.ts +27 -0
  17. package/package/corpus/harnesses/pi/extensions/package.json +4 -0
  18. package/package/corpus/harnesses/pi/extensions/subagent/agents.ts +167 -0
  19. package/package/corpus/harnesses/pi/extensions/subagent/index.ts +352 -0
  20. package/package/corpus/hooks/README.md +434 -0
  21. package/package/corpus/hooks/agent-stop.md +49 -0
  22. package/package/corpus/hooks/pre-commit.md +47 -0
  23. package/package/corpus/hooks/scripts/cratis-guard-writes.sh +87 -0
  24. package/package/corpus/hooks/scripts/cratis-nuget-pins.txt +11 -0
  25. package/package/corpus/hooks/scripts/cratis-pattern-scan.sh +197 -0
  26. package/package/corpus/hooks/scripts/cratis-patterns.json +84 -0
  27. package/package/corpus/hooks/scripts/cratis-quality-gate.sh +219 -0
  28. package/package/corpus/hooks/scripts/hook-lib.sh +152 -0
  29. package/package/corpus/hooks/scripts/quality-gates.json +261 -0
  30. package/package/corpus/hooks/scripts/type-references-allowlist.txt +71 -0
  31. package/package/corpus/hooks/scripts/validate-package-imports.sh +166 -0
  32. package/package/corpus/hooks/scripts/validate-package-subpaths.sh +120 -0
  33. package/package/corpus/hooks/scripts/validate-type-references.sh +308 -0
  34. package/package/corpus/hooks/settings.template.json +40 -0
  35. package/package/corpus/prompts/add-business-rule.prompt.md +22 -0
  36. package/package/corpus/prompts/add-concept.prompt.md +17 -0
  37. package/package/corpus/prompts/add-ef-migration.prompt.md +24 -0
  38. package/package/corpus/prompts/add-projection.prompt.md +20 -0
  39. package/package/corpus/prompts/add-reactor.prompt.md +22 -0
  40. package/package/corpus/prompts/add-reducer.prompt.md +20 -0
  41. package/package/corpus/prompts/audit-hooks.prompt.md +15 -0
  42. package/package/corpus/prompts/check-doc-drift.prompt.md +21 -0
  43. package/package/corpus/prompts/code-review.prompt.md +9 -0
  44. package/package/corpus/prompts/new-feature.prompt.md +9 -0
  45. package/package/corpus/prompts/new-vertical-slice.prompt.md +18 -0
  46. package/package/corpus/prompts/review-pr.prompt.md +35 -0
  47. package/package/corpus/prompts/review-skill.prompt.md +16 -0
  48. package/package/corpus/prompts/scaffold-feature.prompt.md +16 -0
  49. package/package/corpus/prompts/ship-changes.prompt.md +20 -0
  50. package/package/corpus/prompts/verify-ai-setup.prompt.md +19 -0
  51. package/package/corpus/prompts/write-documentation.prompt.md +21 -0
  52. package/package/corpus/prompts/write-specs.prompt.md +22 -0
  53. package/package/corpus/rules/capability-is-not-authority.md +31 -0
  54. package/package/corpus/rules/code-quality.csharp.md +91 -0
  55. package/package/corpus/rules/code-quality.md +82 -0
  56. package/package/corpus/rules/code-quality.typescript.md +89 -0
  57. package/package/corpus/rules/components.md +207 -0
  58. package/package/corpus/rules/concepts.md +115 -0
  59. package/package/corpus/rules/csharp.md +269 -0
  60. package/package/corpus/rules/dialogs.md +264 -0
  61. package/package/corpus/rules/documentation-structure-and-formatting.md +148 -0
  62. package/package/corpus/rules/documentation.md +90 -0
  63. package/package/corpus/rules/editing-cratis-docs.md +69 -0
  64. package/package/corpus/rules/efcore.md +235 -0
  65. package/package/corpus/rules/efcore.specs.md +44 -0
  66. package/package/corpus/rules/exit-codes-and-wrappers.md +33 -0
  67. package/package/corpus/rules/framework.md +52 -0
  68. package/package/corpus/rules/frontend-quality.md +59 -0
  69. package/package/corpus/rules/frontend-testing.md +126 -0
  70. package/package/corpus/rules/general.md +305 -0
  71. package/package/corpus/rules/git-commits.md +138 -0
  72. package/package/corpus/rules/github-actions.md +92 -0
  73. package/package/corpus/rules/glossary.md +61 -0
  74. package/package/corpus/rules/guards-and-fuses.md +45 -0
  75. package/package/corpus/rules/local-work-artifacts.md +33 -0
  76. package/package/corpus/rules/managing-ai-rules.md +40 -0
  77. package/package/corpus/rules/orleans.md +50 -0
  78. package/package/corpus/rules/pull-requests.md +78 -0
  79. package/package/corpus/rules/react.md +195 -0
  80. package/package/corpus/rules/reactors.md +238 -0
  81. package/package/corpus/rules/rtk.md +37 -0
  82. package/package/corpus/rules/specs.csharp.md +139 -0
  83. package/package/corpus/rules/specs.md +132 -0
  84. package/package/corpus/rules/specs.scenarios.csharp.md +172 -0
  85. package/package/corpus/rules/specs.typescript.md +139 -0
  86. package/package/corpus/rules/storybook.md +84 -0
  87. package/package/corpus/rules/terminal-commands.md +19 -0
  88. package/package/corpus/rules/typescript.md +149 -0
  89. package/package/corpus/rules/verification-discipline.md +21 -0
  90. package/package/corpus/rules/vertical-slices.md +338 -0
  91. package/package/corpus/rules/web-fetching.md +11 -0
  92. package/package/corpus/rules/writing-correct-examples.md +35 -0
  93. package/package/corpus/rules/writing-cratis-docs.md +70 -0
  94. package/package/corpus/skills/cratis-engineering-csharp-conventions/LICENSE +2 -0
  95. package/package/corpus/skills/cratis-engineering-csharp-conventions/SKILL.md +131 -0
  96. package/package/corpus/skills/cratis-engineering-csharp-conventions/references/code-style.md +187 -0
  97. package/package/corpus/skills/cratis-engineering-csharp-conventions/references/domain-philosophy.md +91 -0
  98. package/package/corpus/skills/cratis-engineering-csharp-conventions/references/exceptions-logging-and-di.md +223 -0
  99. package/package/corpus/skills/cratis-engineering-decision-record/LICENSE +2 -0
  100. package/package/corpus/skills/cratis-engineering-decision-record/SKILL.md +133 -0
  101. package/package/corpus/skills/cratis-engineering-decision-record/references/record-format.md +107 -0
  102. package/package/corpus/skills/cratis-engineering-docs-authoring/LICENSE +2 -0
  103. package/package/corpus/skills/cratis-engineering-docs-authoring/SKILL.md +86 -0
  104. package/package/corpus/skills/cratis-engineering-docs-authoring/references/site-format.md +46 -0
  105. package/package/corpus/skills/cratis-engineering-effect-boundaries/LICENSE +2 -0
  106. package/package/corpus/skills/cratis-engineering-effect-boundaries/SKILL.md +130 -0
  107. package/package/corpus/skills/cratis-engineering-effect-boundaries/references/failure-archetypes.md +133 -0
  108. package/package/corpus/skills/cratis-fundamentals-concept/verification.json +8 -0
  109. package/package/profile-catalog.json +821 -0
  110. package/package.json +35 -13
  111. package/src/index.ts +80 -0
  112. /package/{skills → package/corpus/skills}/cratis-application-react-specifications/LICENSE +0 -0
  113. /package/{skills → package/corpus/skills}/cratis-application-react-specifications/SKILL.md +0 -0
  114. /package/{skills → package/corpus/skills}/cratis-application-slice-specifications/LICENSE +0 -0
  115. /package/{skills → package/corpus/skills}/cratis-application-slice-specifications/SKILL.md +0 -0
  116. /package/{skills → package/corpus/skills}/cratis-arc-authentication-authorization-and-identity/LICENSE +0 -0
  117. /package/{skills → package/corpus/skills}/cratis-arc-authentication-authorization-and-identity/SKILL.md +0 -0
  118. /package/{skills → package/corpus/skills}/cratis-arc-authentication-authorization-and-identity/references/authentication.md +0 -0
  119. /package/{skills → package/corpus/skills}/cratis-arc-authentication-authorization-and-identity/references/authorization.md +0 -0
  120. /package/{skills → package/corpus/skills}/cratis-arc-authentication-authorization-and-identity/references/frontend.md +0 -0
  121. /package/{skills → package/corpus/skills}/cratis-arc-authentication-authorization-and-identity/references/local-development.md +0 -0
  122. /package/{skills → package/corpus/skills}/cratis-arc-authentication-authorization-and-identity/references/tenancy.md +0 -0
  123. /package/{skills → package/corpus/skills}/cratis-arc-command/LICENSE +0 -0
  124. /package/{skills → package/corpus/skills}/cratis-arc-command/SKILL.md +0 -0
  125. /package/{skills → package/corpus/skills}/cratis-arc-command/references/command-result.md +0 -0
  126. /package/{skills → package/corpus/skills}/cratis-arc-command/references/handler-shapes.md +0 -0
  127. /package/{skills → package/corpus/skills}/cratis-arc-command/references/proxy-generation.md +0 -0
  128. /package/{skills → package/corpus/skills}/cratis-arc-command/references/read-model-injection.md +0 -0
  129. /package/{skills → package/corpus/skills}/cratis-arc-command-execution/LICENSE +0 -0
  130. /package/{skills → package/corpus/skills}/cratis-arc-command-execution/SKILL.md +0 -0
  131. /package/{skills → package/corpus/skills}/cratis-arc-command-validation/LICENSE +0 -0
  132. /package/{skills → package/corpus/skills}/cratis-arc-command-validation/SKILL.md +0 -0
  133. /package/{skills → package/corpus/skills}/cratis-arc-ef-core-migration/LICENSE +0 -0
  134. /package/{skills → package/corpus/skills}/cratis-arc-ef-core-migration/SKILL.md +0 -0
  135. /package/{skills → package/corpus/skills}/cratis-arc-observable-query-http/LICENSE +0 -0
  136. /package/{skills → package/corpus/skills}/cratis-arc-observable-query-http/SKILL.md +0 -0
  137. /package/{skills → package/corpus/skills}/cratis-arc-query-paging/LICENSE +0 -0
  138. /package/{skills → package/corpus/skills}/cratis-arc-query-paging/SKILL.md +0 -0
  139. /package/{skills → package/corpus/skills}/cratis-arc-react-page/LICENSE +0 -0
  140. /package/{skills → package/corpus/skills}/cratis-arc-react-page/SKILL.md +0 -0
  141. /package/{skills → package/corpus/skills}/cratis-arc-react-page/references/data-page.md +0 -0
  142. /package/{skills → package/corpus/skills}/cratis-arc-react-page/references/data-tables.md +0 -0
  143. /package/{skills → package/corpus/skills}/cratis-arc-react-page/references/dialogs.md +0 -0
  144. /package/{skills → package/corpus/skills}/cratis-arc-react-page/references/mvvm.md +0 -0
  145. /package/{skills → package/corpus/skills}/cratis-arc-react-page/references/queries-and-commands.md +0 -0
  146. /package/{skills → package/corpus/skills}/cratis-chronicle-cli-operations/LICENSE +0 -0
  147. /package/{skills → package/corpus/skills}/cratis-chronicle-cli-operations/SKILL.md +0 -0
  148. /package/{skills → package/corpus/skills}/cratis-chronicle-client-dotnet/LICENSE +0 -0
  149. /package/{skills → package/corpus/skills}/cratis-chronicle-client-dotnet/SKILL.md +0 -0
  150. /package/{skills → package/corpus/skills}/cratis-chronicle-client-elixir/LICENSE +0 -0
  151. /package/{skills → package/corpus/skills}/cratis-chronicle-client-elixir/SKILL.md +0 -0
  152. /package/{skills → package/corpus/skills}/cratis-chronicle-client-kotlin/LICENSE +0 -0
  153. /package/{skills → package/corpus/skills}/cratis-chronicle-client-kotlin/SKILL.md +0 -0
  154. /package/{skills → package/corpus/skills}/cratis-chronicle-client-typescript/LICENSE +0 -0
  155. /package/{skills → package/corpus/skills}/cratis-chronicle-client-typescript/SKILL.md +0 -0
  156. /package/{skills → package/corpus/skills}/cratis-chronicle-compliance/LICENSE +0 -0
  157. /package/{skills → package/corpus/skills}/cratis-chronicle-compliance/SKILL.md +0 -0
  158. /package/{skills → package/corpus/skills}/cratis-chronicle-event-constraints/LICENSE +0 -0
  159. /package/{skills → package/corpus/skills}/cratis-chronicle-event-constraints/SKILL.md +0 -0
  160. /package/{skills → package/corpus/skills}/cratis-chronicle-event-modeling/LICENSE +0 -0
  161. /package/{skills → package/corpus/skills}/cratis-chronicle-event-modeling/SKILL.md +0 -0
  162. /package/{skills → package/corpus/skills}/cratis-chronicle-event-specifications/LICENSE +0 -0
  163. /package/{skills → package/corpus/skills}/cratis-chronicle-event-specifications/SKILL.md +0 -0
  164. /package/{skills → package/corpus/skills}/cratis-chronicle-event-type-migration/LICENSE +0 -0
  165. /package/{skills → package/corpus/skills}/cratis-chronicle-event-type-migration/SKILL.md +0 -0
  166. /package/{skills → package/corpus/skills}/cratis-chronicle-mcp-inspection/LICENSE +0 -0
  167. /package/{skills → package/corpus/skills}/cratis-chronicle-mcp-inspection/SKILL.md +0 -0
  168. /package/{skills → package/corpus/skills}/cratis-chronicle-mcp-inspection/references/blocked-tools.md +0 -0
  169. /package/{skills → package/corpus/skills}/cratis-chronicle-mcp-inspection/references/observational-tools.md +0 -0
  170. /package/{skills → package/corpus/skills}/cratis-chronicle-multi-tenancy/LICENSE +0 -0
  171. /package/{skills → package/corpus/skills}/cratis-chronicle-multi-tenancy/SKILL.md +0 -0
  172. /package/{skills → package/corpus/skills}/cratis-chronicle-projection/LICENSE +0 -0
  173. /package/{skills → package/corpus/skills}/cratis-chronicle-projection/SKILL.md +0 -0
  174. /package/{skills → package/corpus/skills}/cratis-chronicle-projection/references/fluent-builder.md +0 -0
  175. /package/{skills → package/corpus/skills}/cratis-chronicle-projection/references/model-bound-attributes.md +0 -0
  176. /package/{skills → package/corpus/skills}/cratis-chronicle-reactor/LICENSE +0 -0
  177. /package/{skills → package/corpus/skills}/cratis-chronicle-reactor/SKILL.md +0 -0
  178. /package/{skills → package/corpus/skills}/cratis-chronicle-read-model/LICENSE +0 -0
  179. /package/{skills → package/corpus/skills}/cratis-chronicle-read-model/SKILL.md +0 -0
  180. /package/{skills → package/corpus/skills}/cratis-chronicle-read-model/references/queries.md +0 -0
  181. /package/{skills → package/corpus/skills}/cratis-chronicle-read-model-specifications/LICENSE +0 -0
  182. /package/{skills → package/corpus/skills}/cratis-chronicle-read-model-specifications/SKILL.md +0 -0
  183. /package/{skills → package/corpus/skills}/cratis-chronicle-reducer/LICENSE +0 -0
  184. /package/{skills → package/corpus/skills}/cratis-chronicle-reducer/SKILL.md +0 -0
  185. /package/{skills → package/corpus/skills}/cratis-chronicle-web-workbench/LICENSE +0 -0
  186. /package/{skills → package/corpus/skills}/cratis-chronicle-web-workbench/SKILL.md +0 -0
  187. /package/{skills → package/corpus/skills}/cratis-cli-terminal-workbench/LICENSE +0 -0
  188. /package/{skills → package/corpus/skills}/cratis-cli-terminal-workbench/SKILL.md +0 -0
  189. /package/{skills → package/corpus/skills}/cratis-code-review/LICENSE +0 -0
  190. /package/{skills → package/corpus/skills}/cratis-code-review/SKILL.md +0 -0
  191. /package/{skills → package/corpus/skills}/cratis-components-accessibility/LICENSE +0 -0
  192. /package/{skills → package/corpus/skills}/cratis-components-accessibility/SKILL.md +0 -0
  193. /package/{skills → package/corpus/skills}/cratis-components-schema-editor/LICENSE +0 -0
  194. /package/{skills → package/corpus/skills}/cratis-components-schema-editor/SKILL.md +0 -0
  195. /package/{skills → package/corpus/skills}/cratis-components-stepper-command-dialog/LICENSE +0 -0
  196. /package/{skills → package/corpus/skills}/cratis-components-stepper-command-dialog/SKILL.md +0 -0
  197. /package/{skills → package/corpus/skills}/cratis-components-styling/LICENSE +0 -0
  198. /package/{skills → package/corpus/skills}/cratis-components-styling/SKILL.md +0 -0
  199. /package/{skills → package/corpus/skills}/cratis-components-toolbar/LICENSE +0 -0
  200. /package/{skills → package/corpus/skills}/cratis-components-toolbar/SKILL.md +0 -0
  201. /package/{skills → package/corpus/skills}/cratis-documentation-writing/LICENSE +0 -0
  202. /package/{skills → package/corpus/skills}/cratis-documentation-writing/SKILL.md +0 -0
  203. /package/{skills → package/corpus/skills}/cratis-event-model-diagram/LICENSE +0 -0
  204. /package/{skills → package/corpus/skills}/cratis-event-model-diagram/SKILL.md +0 -0
  205. /package/{skills → package/corpus/skills}/cratis-fundamentals-concept/LICENSE +0 -0
  206. /package/{skills → package/corpus/skills}/cratis-fundamentals-concept/SKILL.md +0 -0
  207. /package/{skills → package/corpus/skills}/cratis-fundamentals-type-discovery/LICENSE +0 -0
  208. /package/{skills → package/corpus/skills}/cratis-fundamentals-type-discovery/SKILL.md +0 -0
  209. /package/{skills → package/corpus/skills}/cratis-governed-release-methodology/LICENSE +0 -0
  210. /package/{skills → package/corpus/skills}/cratis-governed-release-methodology/SKILL.md +0 -0
  211. /package/{skills → package/corpus/skills}/cratis-lens-browser-extension/LICENSE +0 -0
  212. /package/{skills → package/corpus/skills}/cratis-lens-browser-extension/SKILL.md +0 -0
  213. /package/{skills → package/corpus/skills}/cratis-performance-review/LICENSE +0 -0
  214. /package/{skills → package/corpus/skills}/cratis-performance-review/SKILL.md +0 -0
  215. /package/{skills → package/corpus/skills}/cratis-screenplay-model-authoring/LICENSE +0 -0
  216. /package/{skills → package/corpus/skills}/cratis-screenplay-model-authoring/SKILL.md +0 -0
  217. /package/{skills → package/corpus/skills}/cratis-security-review/LICENSE +0 -0
  218. /package/{skills → package/corpus/skills}/cratis-security-review/SKILL.md +0 -0
  219. /package/{skills → package/corpus/skills}/cratis-specification-by-example/LICENSE +0 -0
  220. /package/{skills → package/corpus/skills}/cratis-specification-by-example/SKILL.md +0 -0
  221. /package/{skills → package/corpus/skills}/cratis-specifications-csharp/LICENSE +0 -0
  222. /package/{skills → package/corpus/skills}/cratis-specifications-csharp/SKILL.md +0 -0
  223. /package/{skills → package/corpus/skills}/cratis-specifications-csharp/references/application-scenarios.md +0 -0
  224. /package/{skills → package/corpus/skills}/cratis-specifications-csharp/references/csharp-patterns.md +0 -0
  225. /package/{skills → package/corpus/skills}/cratis-specifications-csharp/references/integration-specs.md +0 -0
  226. /package/{skills → package/corpus/skills}/cratis-specifications-typescript/LICENSE +0 -0
  227. /package/{skills → package/corpus/skills}/cratis-specifications-typescript/SKILL.md +0 -0
  228. /package/{skills → package/corpus/skills}/cratis-specifications-typescript/references/typescript-patterns.md +0 -0
  229. /package/{skills → package/corpus/skills}/cratis-stage-rendering-and-sandbox/LICENSE +0 -0
  230. /package/{skills → package/corpus/skills}/cratis-stage-rendering-and-sandbox/SKILL.md +0 -0
  231. /package/{skills → package/corpus/skills}/cratis-studio-mcp-safety-guidance/LICENSE +0 -0
  232. /package/{skills → package/corpus/skills}/cratis-studio-mcp-safety-guidance/SKILL.md +0 -0
  233. /package/{skills → package/corpus/skills}/cratis-studio-mcp-safety-guidance/references/blocked-tools.md +0 -0
  234. /package/{skills → package/corpus/skills}/cratis-studio-mcp-safety-guidance/references/observational-tools.md +0 -0
@@ -0,0 +1,115 @@
1
+ ---
2
+ applyTo: "**/*.cs"
3
+ paths:
4
+ - "**/*.cs"
5
+ ---
6
+
7
+ # Concepts — Strongly Typed Domain Values
8
+
9
+ ## Why
10
+
11
+ A primitive such as `Guid` or `string` does not tell the compiler whether a
12
+ value is an `AuthorId`, `UserId`, or `InvoiceNumber`. A Cratis concept gives a
13
+ meaningful value its own type and keeps that meaning in method signatures,
14
+ serialization, validation, events, and read models.
15
+
16
+ Use a concept for a real domain value, not mechanically for every primitive in a
17
+ DTO or framework API. An enum already represents a closed domain concept and
18
+ does not need a `ConceptAs<T>` wrapper.
19
+
20
+ ## Product contracts
21
+
22
+ - A value concept derives from `ConceptAs<T>` in `Cratis.Fundamentals`.
23
+ - A value actually used as a Chronicle event-source/stream identity derives from
24
+ `EventSourceId<T>` in `Cratis.Chronicle`.
25
+ - An arbitrary entity ID that does not identify a Chronicle stream remains a
26
+ value concept; do not derive it from `EventSourceId<T>` merely because its name
27
+ ends in `Id`.
28
+ - Both generic bases require an underlying type implementing `IComparable`.
29
+
30
+ ## Value concept rules
31
+
32
+ - Use a positional record containing exactly one wrapped value. Fundamentals
33
+ serialization assumes a single-value concept; do not add extra properties.
34
+ - `ConceptAs<T>` supplies concept → `T` conversion.
35
+ - Add `T` → derived concept conversion only when it improves the domain API. It
36
+ is optional, not a framework requirement.
37
+ - `ConceptAs<T>` rejects a null wrapped value. Use a nullable concept reference
38
+ such as `AuthorName?` when absence is valid.
39
+ - `NotSet` or `Empty` is optional domain policy. Add one only when the backing
40
+ value is impossible or explicitly reserved in that domain. Do not assume
41
+ empty string, zero, or `Guid.Empty` is universally invalid.
42
+ - Mark a concept that holds **personal data** `[PII]` and one that holds a
43
+ **secret** `[NotAudited]`. Both markings travel with the type, so marking it
44
+ once covers every command and event that uses it - which is the point of
45
+ having the concept. A command's property values are written to the causation
46
+ chain of every event it appends, so an unmarked `ApiKey` or `AccessToken`
47
+ concept reaches the event log in the clear and stays there. The two are not
48
+ interchangeable: `[PII]` also encrypts and enrolls the value in erasure, which
49
+ is wrong for a password, and `[NotAudited]` does nothing for an erasure
50
+ request, which is wrong for a name.
51
+
52
+ ```csharp
53
+ public record AuthorName(string Value) : ConceptAs<string>(Value)
54
+ {
55
+ public static implicit operator AuthorName(string value) => new(value);
56
+ }
57
+ ```
58
+
59
+ ## Chronicle stream identity rules
60
+
61
+ ```csharp
62
+ public record AuthorId(Guid Value) : EventSourceId<Guid>(Value)
63
+ {
64
+ public static AuthorId New() => new(Guid.NewGuid());
65
+ public static implicit operator AuthorId(Guid value) => new(value);
66
+ }
67
+ ```
68
+
69
+ - `New()`, primitive → derived ID conversion, and sentinels are optional domain
70
+ conveniences; `EventSourceId<T>` does not create an arbitrary derived type.
71
+ - The exact `EventSourceId<T>` base supports conversions among its underlying
72
+ `T`, string, typed ID, and untyped `EventSourceId`, but those operators do not
73
+ construct your derived domain record from every source form.
74
+ - String and Guid are the safest round-trip primitives. Other comparable values
75
+ rely on Chronicle conversion behavior and require focused verification.
76
+ - Pass the identity explicitly to Chronicle append/read operations. Declaring an
77
+ `EventSourceId<T>` property does not select the stream.
78
+ - Do not put `[Key]` or `[Subject]` on an `EventSourceId<T>`-derived member;
79
+ Chronicle analyzer `CHR0026` reports it.
80
+ - Do not put `[PII]` on an event-source ID; analyzer `CHR0034` rejects it.
81
+ Sensitive natural identifiers use a random surrogate stream ID and a separate
82
+ compliance-managed value.
83
+ - `EventSourceId.Unspecified` is the untyped string-backed sentinel. Typed empty
84
+ or zero values convert to real specified stream IDs and are not equivalent to
85
+ `Unspecified`.
86
+
87
+ ## Application placement convention
88
+
89
+ In a Cratis application, place a concept with the feature/module that owns its
90
+ meaning rather than in a generic `Concepts/` folder. Put genuinely cross-feature
91
+ concepts in `Common/`. Do not introduce a top-level `Features/` wrapper.
92
+
93
+ This is a Cratis application convention, not a Fundamentals or Chronicle API
94
+ contract. Framework and client repositories follow their own structure.
95
+
96
+ ## Promote a value deliberately
97
+
98
+ Promote a value when its meaning or cross-cutting characteristics must travel
99
+ with it—for example validation, compliance classification, or a domain-specific
100
+ format. Reuse an existing shared concept when it already owns that meaning.
101
+
102
+ ## Call-site guidance
103
+
104
+ Use the constructors, conversions, factories, or sentinels the domain type
105
+ actually provides. Do not require `NotSet`, `New()`, or a reverse conversion on
106
+ every concept. If a sentinel exists, reference the named sentinel rather than
107
+ reconstructing its backing primitive.
108
+
109
+ ## Geospatial values
110
+
111
+ Use GeoJSON types from `Cratis.Geospatial`: `Point` for a location,
112
+ `LineString` for a route, and `Polygon` for an area. Do not use the removed
113
+ experimental `Coordinate` type. Model geospatial absence according to the
114
+ owning domain contract; do not invent a nullable event payload that conflicts
115
+ with Chronicle event rules.
@@ -0,0 +1,269 @@
1
+ ---
2
+ applyTo: "**/*.cs"
3
+ paths:
4
+ - "**/*.cs"
5
+ ---
6
+
7
+
8
+ # C# Conventions
9
+
10
+ The goal is minimal ceremony, maximum clarity. Modern C# (13+) gives us records, primary constructors, pattern matching, and file-scoped namespaces — use them everywhere. The less boilerplate in a file, the faster a reader can understand what it *does*.
11
+
12
+ ## Building
13
+
14
+ - Use `dotnet build` from the command line.
15
+ - Use `dotnet format` to format code.
16
+ - Use `dotnet test` to run tests.
17
+
18
+ ## Formatting
19
+
20
+ These rules exist so that every file in the codebase reads the same way. When formatting is consistent, code review focuses on logic, not style.
21
+
22
+ - Apply code-formatting style defined in `.editorconfig`.
23
+ - Use file-scoped namespace declarations — one less level of indentation for the entire file.
24
+ - Use single-line `using` directives, sorted alphabetically.
25
+ - Never qualify a type that is already unambiguously in scope via a `using` directive. When two `using` directives introduce conflicting type names, qualify only the conflicting occurrences using the shortest unambiguous path (e.g. `Concepts.Events.Foo` or `Contracts.Events.Foo`) — do not add `using` aliases for every conflicting type.
26
+ - Insert a blank line before the opening `{` of every code block (`if`, `for`, `foreach`, `try`, `using`, etc.).
27
+ - Ensure the final `return` statement of a method is on its own line.
28
+ - Use pattern matching and switch expressions wherever possible — they are more readable and the compiler verifies exhaustiveness.
29
+ - Use `nameof` instead of string literals — it survives refactoring.
30
+ - Place private class declarations at the bottom of the file — public API first, implementation details last.
31
+
32
+ ## Language — American English Only
33
+
34
+ All identifiers, comments, XML docs, and string literals must use **American English** spelling (initialize, serialize, behavior, color, organization, center, modeling, dialog, license, judgment, gray). See [general.md](./general.md) for the full guidance.
35
+
36
+ ## Naming
37
+
38
+ - PascalCase for type names, method names, and public members.
39
+ - camelCase for private fields and local variables.
40
+ - Prefix private fields with `_` (e.g. `_myField`).
41
+ - Prefix interface names with `I` (e.g. `IMyService`).
42
+
43
+ ## Code Style
44
+
45
+ Every rule here reduces noise. `var` avoids redundant type repetition. Expression bodies eliminate braces for trivial members. Primary constructors remove the constructor-plus-field ceremony.
46
+
47
+ - Prefer `var` over explicit types — the right side of the assignment already tells you the type.
48
+ - Use expression-bodied members for simple methods and properties.
49
+ - Favor primary constructors for all types — they eliminate field declarations for injected dependencies.
50
+ - Use string interpolation instead of `string.Format()` or concatenation.
51
+ - Favor collection initializers and object initializers.
52
+ - Use `IEnumerable<T>` for collections that are not modified; never return mutable collections from public APIs.
53
+ - Prefer LINQ (`.Where`, `.Any`, `.Select`, `.FirstOrDefault`) over a `foreach` that filters inside its body with an `if`/`continue` or an early `return` — put the filter in the query so the intent is explicit (`items.Where(predicate)`, `return items.Any(predicate);`). Reserve `foreach` for genuine iteration with side effects. (Filtering inside a loop is what the analyzers flag as a "missed opportunity to use Where".)
54
+ - Don't use regions — they hide code instead of organizing it. If a file needs regions, it needs refactoring.
55
+ - Never add postfixes like `Async`, `Impl`, `Service` to class names — they add noise without information.
56
+ - For types with no implementation body, omit the braces (e.g. `public interface IMyInterface;`).
57
+ - Prefer `record` types for immutable data structures (events, commands, read models, concepts) — they give you value equality, immutability, and concise syntax for free.
58
+
59
+ ## Nullable Reference Types
60
+
61
+ Embrace the type system — it is the first line of defense against null-related bugs. When it says something cannot be null, trust it.
62
+
63
+ - Use `is null` / `is not null` — never `== null` / `!= null`.
64
+ - Trust the C# null annotations; don't add defensive null checks when the type system guarantees a value.
65
+ - Add `!` operator where nullability warnings occur and you are certain the value is non-null.
66
+ - Use `is not null` checks before dereferencing potentially null values.
67
+
68
+ ## XML Documentation
69
+
70
+ XML doc comments are the public API's first impression. They must be multiline — never cram `<summary>` onto a single line. Every public type, method, property, and operator must have XML docs.
71
+
72
+ - Always use **multiline** `<summary>` tags — opening and closing tags on their own lines:
73
+
74
+ ```csharp
75
+ /// <summary>
76
+ /// Represents the unique identifier of a project.
77
+ /// </summary>
78
+ ```
79
+
80
+ - **Never** use single-line summaries:
81
+
82
+ ```csharp
83
+ // ❌ Wrong
84
+ /// <summary>Represents the unique identifier of a project.</summary>
85
+ ```
86
+
87
+ - Every method or operator with parameters **must** include `<param name="...">` for each parameter.
88
+ - Every method or operator that returns a value (non-void) **must** include `<returns>`.
89
+ - Every method that throws must document the exception with `<exception cref="...">` tags.
90
+ - Use `<see cref="..."/>` and `<paramref name="..."/>` to cross-reference types and parameters.
91
+ - Keep summaries concise and purposeful — only document when it adds understanding beyond the name itself.
92
+
93
+ Example:
94
+
95
+ ```csharp
96
+ /// <summary>
97
+ /// Represents an instance of <see cref="ICommandFilters"/>.
98
+ /// </summary>
99
+ /// <param name="filters">The collection of <see cref="ICommandFilter"/> to use for filtering commands.</param>
100
+ [Singleton]
101
+ public class CommandFilters(IInstancesOf<ICommandFilter> filters) : ICommandFilters
102
+ {
103
+ /// <summary>
104
+ /// Filters the command execution through all registered command filters.
105
+ /// </summary>
106
+ /// <param name="context">The <see cref="CommandContext"/> to filter.</param>
107
+ /// <returns>A <see cref="CommandResult"/> representing the aggregated filter outcome.</returns>
108
+ public async Task<CommandResult> OnExecution(CommandContext context)
109
+ {
110
+ // ...
111
+ }
112
+ }
113
+ ```
114
+
115
+ ## Exceptions
116
+
117
+ Every exception type in the codebase should communicate *what went wrong* in domain terms. Built-in types like `InvalidOperationException` tell you nothing about the problem — a custom `AuthorAlreadyRegistered` tells you everything.
118
+
119
+ - Use exceptions for exceptional situations only — never for control flow.
120
+ - Always create a custom exception type that derives from `Exception`.
121
+ - Never use built-in exception types (`InvalidOperationException`, `ArgumentException`, etc.).
122
+ - Never suffix exception class names with `Exception` — `AuthorNotFound` reads better than `AuthorNotFoundException`.
123
+ - Always provide a meaningful message when throwing.
124
+ - Add XML doc on the exception type starting with "The exception that is thrown when ...".
125
+ - Never write an empty or silently-swallowing `catch` block. Handle the exception, log it, or let it propagate. When ignoring is genuinely correct, use an exception filter (`catch (SomeException) when (…)`) with a body that states the decision (a comment and/or a fallback) — never a bare `catch { }`.
126
+
127
+ ## Dependency Injection
128
+
129
+ The framework discovers and wires dependencies by convention. Explicit registration is the exception, not the rule.
130
+
131
+ - Prefer constructor injection; avoid `IServiceProvider` directly (service locator anti-pattern).
132
+ - For singletons, use the `[Singleton]` attribute — no explicit registration needed.
133
+ - Systems with a convention of `IFoo → Foo` do not need to be registered explicitly.
134
+ - Command/query `Handle()` method parameters are automatically resolved from DI by type.
135
+
136
+ ### Service lifetimes — `[Singleton]` is a narrow choice, not the default
137
+
138
+ **Assume every application you build is multi-tenant.** Not "design for it later" — assume it now, even when the deployment ships with a single tenant and no tenant resolution configured. A single-tenant application is a multi-tenant one with one tenant in it, and the code shape that serves both is the same shape. The code shape that serves only one has to be found and rewritten later, from the far side of a data migration, under production. The same reasoning applies to the signed-in user: an application always has one, and a service that remembers *which* one will eventually answer for the wrong person.
139
+
140
+ That gives one rule with two faces:
141
+
142
+ > **A singleton may not depend on anything that belongs to a tenant, a user, or a request.**
143
+
144
+ These resolve **per scope**, and the scope is what carries the tenant — so none of them may be injected into a `[Singleton]`:
145
+
146
+ | Off limits in a singleton | Why |
147
+ | --- | --- |
148
+ | `IEventStore` — and everything off it: `IEventLog`, `IReadModels`, `IConstraints`, `IEventTypes`, `IProjections`, `IReducers`, `IPII` | resolved for the scope's namespace |
149
+ | `IMongoCollection<T>`, `IMongoDatabase`, `IMongoClient` | the database name is resolved per scope from the current tenant |
150
+ | An EF Core `DbContext` | scoped for the same reason, plus it is not thread-safe |
151
+ | A read model injected directly by key | same scope, same binding |
152
+ | Any held tenant, principal, claims, correlation id, or `HttpContext` **value** | belongs to one request and outlives it in a singleton |
153
+
154
+ A `[Singleton]` taking one of these is a **captive dependency**: the container hands it the *root* scope's instance and keeps it for process lifetime. The root scope has no request, so it resolves no tenant — every read and write goes to the default namespace forever, regardless of who is asking.
155
+
156
+ **It does not throw. It returns nothing.** A query against the wrong namespace hits a database that exists and is empty, so the caller gets an empty collection, a `null` read model, or a default-valued options object, and carries on. The application starts, the pages render, the build is green, and the configuration a tenant spent an afternoon entering is simply not there. It is also invisible while there is only one tenant — every symptom appears on the day a second one arrives.
157
+
158
+ **What to use instead.** Default to the convention (transient), which inherits the resolving scope's tenant for free, or `[Scoped]` when a service must be shared within one request. Reserve `[Singleton]` for things that are genuinely process-wide and hold no tenant-, user-, or request-bound state: `IInstancesOf<T>` aggregators, HTTP client wrappers, `IOptions<T>` readers, pure computation, framework plumbing.
159
+
160
+ When something must be a singleton and still needs data — a hosted service, a dispatcher, a poller — inject `IServiceScopeFactory` and open a scope per unit of work:
161
+
162
+ ```csharp
163
+ // ❌ Wrong — IEventStore is scoped; this captures the root scope's default namespace forever.
164
+ [Singleton]
165
+ public class DigestSources(IEventStore eventStore) : IDigestSources
166
+ {
167
+ public Task<DigestConfiguration?> GetCurrent() =>
168
+ eventStore.ReadModels.GetInstanceById<DigestConfiguration>(DigestId.Default);
169
+ }
170
+
171
+ // ✅ Right — a scope per call, so the collaborators bind to the caller's tenant.
172
+ [Singleton]
173
+ public class DigestSources(IServiceScopeFactory scopeFactory) : IDigestSources
174
+ {
175
+ public async Task<DigestConfiguration?> GetCurrent()
176
+ {
177
+ using var scope = scopeFactory.CreateScope();
178
+ var eventStore = scope.ServiceProvider.GetRequiredService<IEventStore>();
179
+ return await eventStore.ReadModels.GetInstanceById<DigestConfiguration>(DigestId.Default);
180
+ }
181
+ }
182
+ ```
183
+
184
+ `IChronicleClient` **is** singleton-safe, and is the right collaborator when a flow knows which namespace it means and has no scope to resolve one from — it names the event store and namespace explicitly: `await chronicleClient.GetEventStore("MyStore", tenantId.Value)`. Naming the namespace is a deliberate, readable statement that this code crosses a tenant boundary; capturing a scoped service is the same crossing made by accident.
185
+
186
+ **The current user is not process-wide either.** Never keep the signed-in user, their principal, claims, roles, or anything derived from them in a singleton. The distinction that matters: *the accessor is fine, the value is not.* `IHttpContextAccessor` is itself a singleton and safe to inject; reading a value out of it once and keeping it is not. A current-user service may be a singleton only when every method reads through the accessor on each call and stores nothing. Anything that derives something per user and wants to keep it holds a cache **keyed by the user**, never a single field.
187
+
188
+ **Off-request work carries its tenant.** Reactors, hosted services, background dispatch and scheduled jobs run with no HTTP request, so there is nothing for a tenant resolver to read. Chronicle observers are themselves instantiated per namespace, but the collaborators they call are not — a reactor that reaches a tenant-blind singleton has left its namespace behind without saying so. Such a flow states its tenant explicitly rather than inheriting whatever the root scope happens to be.
189
+
190
+ **Caching.** A process-wide cache of tenant data is the same bug wearing a performance justification. If a singleton caches, the tenant (and where relevant the user) is part of the key. The same holds for `static` fields: a `static` cache of anything tenant-scoped is shared by every tenant in the process.
191
+
192
+ **Enforce it, do not remember it.** This failure is silent, so review will not reliably catch it. Add an architecture spec that reflects over the assembly, finds every `[Singleton]` whose constructor takes a scope-bound service, and asserts the set is empty. It is a few dozen lines, it runs on every build, and it is the only thing that keeps the rule true a year from now.
193
+
194
+ > .NET's own captive-dependency detection (`ServiceProviderOptions.ValidateScopes`, which Arc deliberately leaves on in Development) exists to catch exactly this. If a singleton in your codebase holds a scoped service and Development startup is not complaining, that path is not being exercised in Development — worth knowing on its own.
195
+
196
+ ### Discovering multiple implementations — use `IInstancesOf<T>`, never `IEnumerable<T>`
197
+
198
+ When a type needs every implementation of an abstraction (handlers, strategies, filters, validators, formatters), inject `IInstancesOf<TInterface>` from `Cratis.Types`. The framework discovers and instantiates every implementation by convention — no `services.AddSingleton<TInterface, Impl1>()` calls anywhere.
199
+
200
+ ```csharp
201
+ // ❌ Wrong — requires hand-maintained registrations for every implementation.
202
+ // Adding a new IReactorSideEffectHandler somewhere else in the codebase silently
203
+ // does nothing until someone remembers to register it here, and dead registrations
204
+ // linger after types are removed.
205
+ services.AddSingleton<IReactorSideEffectHandler, EventResultHandler>();
206
+ services.AddSingleton<IReactorSideEffectHandler, EventsResultHandler>();
207
+ services.AddSingleton<IReactorSideEffectHandlers, ReactorSideEffectHandlers>();
208
+
209
+ public class ReactorSideEffectHandlers(IEnumerable<IReactorSideEffectHandler> handlers) : IReactorSideEffectHandlers { ... }
210
+
211
+ // ✅ Right — implementations discovered automatically. Mark singletons with [Singleton].
212
+ [Singleton]
213
+ public class EventResultHandler(IEventTypes eventTypes) : IReactorSideEffectHandler { ... }
214
+
215
+ [Singleton]
216
+ public class EventsResultHandler(IEventTypes eventTypes) : IReactorSideEffectHandler { ... }
217
+
218
+ [Singleton]
219
+ public class ReactorSideEffectHandlers(IInstancesOf<IReactorSideEffectHandler> handlers) : IReactorSideEffectHandlers { ... }
220
+ ```
221
+
222
+ **Rules:**
223
+
224
+ - Never inject `IEnumerable<TInterface>` to enumerate implementations of an abstraction. That signature only works if every implementation is hand-registered, which defeats convention-based discovery. Use `IInstancesOf<TInterface>` instead.
225
+ - Never write `services.AddSingleton<TInterface, Impl>()` or `services.AddTransient<TInterface, Impl>()` for a type that exists to be discovered. Mark the implementation with `[Singleton]` (for singletons) or rely on the `IFoo → Foo` convention (for transients) and remove the registration line.
226
+ - `IInstancesOf<T>` resolves at the point of access — implementations added later in the assembly become available without touching the consumer or any composition root.
227
+ - `IEnumerable<T>` is still the right type to **return** from a method that yields a sequence of values. The rule applies only when the goal is to enumerate **implementations** of an abstraction.
228
+
229
+ ## Logging
230
+
231
+ - Use structured logging with named parameters.
232
+ - Use `ILogger<T>` where `T` is the class name.
233
+ - Keep log messages in a separate `<ClassName>Logging.cs` partial static internal class.
234
+ - Use `[LoggerMessage]` attribute (without `eventId`).
235
+
236
+ ## Async
237
+
238
+ - Use `async`/`await` for asynchronous programming.
239
+ - Use `Task` and `Task<T>` for asynchronous methods.
240
+
241
+ ## Chronicle & Arc — Key API Types
242
+
243
+ These are the building blocks. Each type has a specific role in the vertical slice architecture — using the right type in the right place means the framework handles discovery, wiring, and proxy generation automatically.
244
+
245
+ | Type | Purpose |
246
+ | --- | --- |
247
+ | `ConceptAs<T>` | Strongly-typed domain *value* wrapper (see [concepts.md](./concepts.md)) |
248
+ | `EventSourceId<T>` | Strongly-typed *identity* base — derive event-source ids from this, not `ConceptAs<T>` |
249
+ | `[EventType]` | Marks a record as a Chronicle event — **never** pass arguments for a new event |
250
+ | `[Command]` | Marks a record as a model-bound command — define `Handle()` directly on the record |
251
+ | `[ReadModel]` | Marks a record as a model-bound query — define static query methods on the record |
252
+ | `CommandValidator<T>` | FluentValidation validator for commands |
253
+ | `IProjectionFor<T>` | Fluent projection definition — AutoMap is on by default, never call `.AutoMap()` |
254
+ | `IReducerFor<T>` | Imperative reducer — receives current state, returns new state |
255
+ | `IReactor` | Marker interface for side-effect observers — method dispatch by event type parameter |
256
+ | `IConstraint` | Constraint definition — enforced server-side by Chronicle at append time |
257
+ | `AggregateRoot` | Chronicle aggregate root with `Apply()` and `Commit()` |
258
+ | `ICommandPipeline` | Programmatic command execution from reactors or other code |
259
+ | `EventContext` | Event metadata: `Occurred`, `SequenceNumber`, `CorrelationId`, `EventSourceId`, etc. |
260
+ | `ISubject<T>` | Observable query return type — enables real-time push |
261
+ | `IMongoCollection<T>` | MongoDB collection — use `.Observe()` for reactive queries |
262
+
263
+ **Key conventions:**
264
+
265
+ - Prefer `ConceptAs<T>` over raw primitives in all domain models, commands, events, and queries; derive identity concepts from `EventSourceId<T>`. See [concepts.md](./concepts.md) for details.
266
+ - Projections join **events**, never read models — projections rebuild state from the event stream, not from other projections.
267
+ - For fluent projections, AutoMap is on by default — call `.From<EventType>()` without `.AutoMap()` and without manually mapping every matching property.
268
+ - Use model-bound projection attributes (`[FromEvent<T>]`, `[SetFrom<T>]`, etc.) when possible; fall back to `IProjectionFor<T>` for complex cases.
269
+ - Full slice anatomy lives in [vertical-slices.md](./vertical-slices.md).
@@ -0,0 +1,264 @@
1
+ ---
2
+ applyTo: "**/*.tsx"
3
+ profile: application
4
+ paths:
5
+ - "**/*.tsx"
6
+ ---
7
+
8
+ # Using Dialogs
9
+
10
+ The Cratis dialog wrappers handle command execution, validation timing, loading states, and footer buttons consistently. Using PrimeReact's raw `Dialog` bypasses all of this and leads to inconsistent UX.
11
+
12
+ ## Choose the Correct Dialog Type
13
+
14
+ - If confirm executes a command, use `CommandDialog` from `@cratis/components/CommandDialog`.
15
+ - If no command is executed on confirm, use `Dialog` from `@cratis/components/Dialogs`.
16
+ - If you are **asking the user to confirm** or **showing that something is in progress**, do not build a dialog at all — raise the host-rendered one through its hook (below).
17
+ - **Never** import `Dialog` from `primereact/dialog` directly.
18
+
19
+ ## Confirmations and busy indicators are host-rendered — register once, raise by hook
20
+
21
+ `ConfirmationDialog` and `BusyIndicatorDialog` are **not** instantiated in a slice's JSX. They are
22
+ registered once at the app root and raised from anywhere through a hook, so every confirmation and
23
+ every busy indicator in the application looks and behaves identically:
24
+
25
+ ```tsx
26
+ import { DialogComponents } from '@cratis/arc.react/dialogs';
27
+ import { BusyIndicatorDialog, ConfirmationDialog } from '@cratis/components/Dialogs';
28
+
29
+ export const App = () => (
30
+ <DialogComponents confirmation={ConfirmationDialog} busyIndicator={BusyIndicatorDialog}>
31
+ <YourApp />
32
+ </DialogComponents>
33
+ );
34
+ ```
35
+
36
+ ```tsx
37
+ import { DialogButtons, DialogResult, useConfirmationDialog, useBusyIndicator } from '@cratis/arc.react/dialogs';
38
+
39
+ const [confirm] = useConfirmationDialog();
40
+ const answer = await confirm('Delete this alert?', `"${alert.title}" disappears permanently.`, DialogButtons.YesNo);
41
+ if (answer !== DialogResult.Yes) return;
42
+
43
+ const [showBusy, closeBusy] = useBusyIndicator('Importing', 'This takes a moment.');
44
+ showBusy();
45
+ try { await doTheSlowThing(); } finally { closeBusy(); }
46
+ ```
47
+
48
+ **Rules:**
49
+ - Register both in **exactly one** place — the app root. A second registration, or a slice building its own confirm/busy dialog, is how two of them end up looking different.
50
+ - Reach them only via `useConfirmationDialog` / `useBusyIndicator`. Never hand-roll a Yes/No `Dialog`, and never use `window.confirm`.
51
+ - `showConfirm()` resolves to a `DialogResult` — branch on the enum member, never on button text.
52
+ - Always pair `showBusy()` with `closeBusy()` in a `finally`; the busy dialog is deliberately non-dismissible, so a missed close leaves the user stuck.
53
+ - **A busy indicator is a modal for work that blocks the user.** For a quick command behind a button, an in-flight/disabled button (eventual-consistency rule 9) is the better control — a modal that flashes for 200 ms is worse than no modal. Use the busy dialog when the user genuinely cannot proceed.
54
+ - From a view model, use the injectable `IDialogs` abstraction (`@cratis/arc.react.mvvm/dialogs`) rather than the hooks — see [react.md](./react.md).
55
+
56
+ ## When Using `CommandDialog`
57
+
58
+ - Pass the command constructor to `command={}`. `CommandDialog` handles instantiation, execution, and confirm/cancel buttons.
59
+ - Use command form fields (`InputTextField`, `TextAreaField`, etc. from `@cratis/components/CommandForm`) for user-input values.
60
+ - `CommandDialog` automatically disables confirm while the command executes.
61
+ - Any value that must be present for the form to be considered valid (i.e. passes `validateRequiredProperties`) must be supplied via `initialValues`, **not** via `onBeforeExecute`.
62
+ - `onBeforeExecute` fires only at execution time — the command is already validated before it runs, so values set there never influence `isValid` and the OK/Submit button will remain permanently disabled.
63
+ - Use `initialValues` for injected context values (e.g. a parent entity id passed as a prop).
64
+ - Use `onBeforeExecute` only for transformations that should not affect form validity (e.g. generated IDs).
65
+
66
+ ```tsx
67
+ import { DialogProps, DialogResult } from '@cratis/arc.react/dialogs';
68
+ import { CommandDialog } from '@cratis/components/CommandDialog';
69
+ import { InputTextField } from '@cratis/components/CommandForm';
70
+ import { RegisterProject } from './Registration';
71
+ import { Guid } from '@cratis/fundamentals';
72
+
73
+ export const AddProject = ({ closeDialog }: DialogProps) => {
74
+ return (
75
+ <CommandDialog<RegisterProject>
76
+ command={RegisterProject}
77
+ title="Add Project"
78
+ okLabel="Add"
79
+ cancelLabel="Cancel"
80
+ onBeforeExecute={(values) => {
81
+ values.projectId = Guid.create(); // generated, not user input
82
+ return values;
83
+ }}>
84
+ <InputTextField<RegisterProject>
85
+ value={instance => instance.name}
86
+ title="Project name"
87
+ placeholder="My Project"
88
+ />
89
+ </CommandDialog>
90
+ );
91
+ };
92
+ ```
93
+
94
+ To await the result from the parent:
95
+
96
+ ```tsx
97
+ const [AddProjectDialog, showAddProjectDialog] = useDialog(AddProject);
98
+
99
+ const [result] = await showAddProjectDialog();
100
+ if (result === DialogResult.Ok) {
101
+ // Dialog confirmed and command executed successfully
102
+ }
103
+ ```
104
+
105
+ ### `onSuccess` vs `onConfirm`
106
+
107
+ - **`onSuccess(response)`** fires only after the command succeeds and receives the typed command response — use it for `closeDialog(DialogResult.Ok, response)`, refreshing a query, or a toast.
108
+ - **`onConfirm()`** receives **no** command result. It is a close gate after successful execution: return `true` to let the wrapper close, `false`/`undefined` to keep it open. **Do not use `onConfirm` as a command-result handler.**
109
+
110
+ ### `onBeforeExecute` is a transformer
111
+
112
+ It receives the current command values and **must return them** (mutated or not). **Returning `void` executes the command with `undefined` values.** It runs only on submit — never use it to seed *required* values (validation runs against pre-transform state, so a value seeded here never makes the form valid and the submit button stays permanently disabled). Seed required values via `initialValues`; reserve `onBeforeExecute` for transforms that don't affect validity (e.g. a generated id).
113
+
114
+ ### CommandForm fields
115
+
116
+ Use built-in `CommandForm` fields (from `@cratis/components/CommandForm`) for every user-input value — a raw PrimeReact control inside a command dialog bypasses `CommandFormFieldWrapper`, so validation never re-runs and the submit button stays **permanently disabled**. Catalog: `InputTextField`, `PasswordField`, `NumberField`, `DropdownField`, `CheckboxField`, `ToggleSwitchField`, `TextAreaField`, `CalendarField`, `RadioButtonField`, `RadioGroupField`, `ChipsField`, `MultiSelectField`, `ColorPickerField`, `SliderField`, `RatingField`.
117
+
118
+ - The `value={c => c.name}` **accessor lambda doubles as the binding and type-checked field selection** — renaming a command property surfaces a compile error at every binding.
119
+ - **`RadioGroupField<T>`** renders a whole group from data (`options`/`optionLabel`/`optionValue`, `layout='horizontal'|'vertical'`); **`RadioButtonField<T>`** is one component per option (each takes a `buttonValue`). Both infer the value type from the accessor — no `as string` casts.
120
+ - **`asCommandFormField(Component, opts)`** (`asCommandFormField`, `WrappedFieldProps` from `@cratis/arc.react/commands`) wraps a custom input so it participates in `CommandForm` like a built-in. `WrappedFieldProps<T>` gives `{ value, onChange, invalid, required, errors }` (`errors` is `string[]` → `errors.join(', ')`); options are `{ defaultValue, extractValue: e => ... }`.
121
+ - **`useCommandInstance(Command)`** (`@cratis/arc.react/commands`) returns the live reactive instance the form is bound to — **read** it to drive dependent fields (e.g. read `command.country` to choose a `DropdownField`'s options); never mutate (mutations go through field bindings).
122
+
123
+ ### Opening dialogs — `useDialog` / `useDialogContext`
124
+
125
+ `useDialog<TResponse, TInput>(Component)` returns `[Wrapper, showFn]`: render `<Wrapper />` in JSX and call `showFn(input)` to open it; it resolves to `[DialogResult, TResponse?]` when the dialog closes. For a new dialog, prefer reading input as **plain typed props** (`<Name>Input`) and obtaining `closeDialog` from **`useDialogContext<TResponse>()`** — rather than declaring a props interface that extends `DialogProps` to thread both input and `closeDialog`. (Existing dialogs that destructure `closeDialog` from `DialogProps` remain valid.) Signal the outcome with `closeDialog(DialogResult.Ok | Cancelled, response?)`.
126
+
127
+ ### Multi-step wizards — `StepperCommandDialog`
128
+
129
+ For a command split across named steps use `StepperCommandDialog` (`@cratis/components/CommandDialog`) — see the **stepper-command-dialog** skill. Conditional steps written as `{condition && <StepperPanel/>}` are supported **from 2.7.1**: only the steps that actually render are counted, so Next and Submit appear where the user expects them. ⚠️ On earlier versions a hidden step is still counted — Submit never appears on the real last step and a dead Next takes its place, so check what you are pinned to before relying on this. ⚠️ A `<>…</>` fragment wrapping several panels still counts as **one** step — give each step its own `StepperPanel` child. When a step needs non-CommandForm inputs, or cross-step state is complex, fall back to a manual `Dialog` + PrimeReact `Stepper`.
130
+
131
+ ## When Using `Dialog`
132
+
133
+ Use this for dialogs that collect data and return it without executing a command (e.g. confirmation prompts, pure data-entry dialogs). `Dialog` defaults to OK + Cancel buttons. Use `isValid` to control confirm button state, `okLabel`/`cancelLabel` to customize button text.
134
+
135
+ ```tsx
136
+ import { useState } from 'react';
137
+ import { DialogProps, DialogResult } from '@cratis/arc.react/dialogs';
138
+ import { Dialog } from '@cratis/components/Dialogs';
139
+ import { InputText } from 'primereact/inputtext';
140
+
141
+ export const AddProject = ({ closeDialog }: DialogProps<{ name: string }>) => {
142
+ const [name, setName] = useState('');
143
+ const isValid = name.trim().length > 0;
144
+
145
+ return (
146
+ <Dialog
147
+ title="Add Project"
148
+ width='32rem'
149
+ isValid={isValid}
150
+ onConfirm={() => closeDialog(DialogResult.Ok, { name })}
151
+ onCancel={() => closeDialog(DialogResult.Cancelled)}
152
+ >
153
+ <InputText
154
+ value={name}
155
+ onChange={event => setName(event.target.value)}
156
+ autoFocus
157
+ />
158
+ </Dialog>
159
+ );
160
+ };
161
+ ```
162
+
163
+ ## Prefer `DialogButtons` over Custom Button JSX
164
+
165
+ Use the built-in `DialogButtons` enum instead of rendering manual `<Button>` elements in the `buttons` prop:
166
+
167
+ ```tsx
168
+ import { DialogButtons, DialogResult, useDialogContext } from '@cratis/arc.react/dialogs';
169
+ ```
170
+
171
+ ### Available Button Sets
172
+
173
+ | `buttons` value | Shows |
174
+ |---|---|
175
+ | `DialogButtons.OkCancel` | Ok + Cancel |
176
+ | `DialogButtons.YesNo` | Yes + No |
177
+ | `DialogButtons.YesNoCancel` | Yes + No + Cancel |
178
+ | `DialogButtons.Ok` | Ok only |
179
+ | `null` | No buttons (content-only dialog) |
180
+
181
+ ## Customizing Built-in Buttons
182
+
183
+ Use `okLabel`/`cancelLabel` to rename the buttons, and `isValid` to disable the confirm button:
184
+
185
+ ```tsx
186
+ <Dialog
187
+ title="Import Orders"
188
+ visible={true}
189
+ buttons={DialogButtons.OkCancel}
190
+ okLabel="Upload"
191
+ isValid={!!file && !isUploading}
192
+ onConfirm={handleUpload}
193
+ onCancel={() => closeDialog(DialogResult.Cancelled)}
194
+ >
195
+ ```
196
+
197
+ ## Validation Guard — Keep Dialog Open on Failure
198
+
199
+ When `onConfirm` needs to keep the dialog open (e.g. a command fails), annotate the handler as `Promise<boolean>` and return `false` to block the close. Return `true` to let the Dialog close itself:
200
+
201
+ ```tsx
202
+ const handleConfirm = async (): Promise<boolean> => {
203
+ const result = await myCommand.execute();
204
+ if (!result.isSuccess) return false; // dialog stays open
205
+ return true; // dialog closes
206
+ };
207
+ ```
208
+
209
+ > **TypeScript note:** Always annotate the function as `Promise<boolean>`. Without it TypeScript infers `Promise<false | void>` which does not satisfy the `ConfirmCallback` type.
210
+
211
+ ## Passing Result Data on Confirm
212
+
213
+ When the dialog must return data to its caller (e.g. a postal code lookup result), use `onClose` and call `closeDialog` manually, returning `false` to prevent the Dialog from calling it a second time:
214
+
215
+ ```tsx
216
+ <Dialog
217
+ title="Confirm Location"
218
+ visible={true}
219
+ buttons={DialogButtons.OkCancel}
220
+ isValid={isValid}
221
+ onClose={(result) => {
222
+ if (result === DialogResult.Ok) {
223
+ closeDialog(DialogResult.Ok, { postalCode, city, latitude, longitude } as MyResult);
224
+ return false; // prevent Dialog from calling closeDialog(Ok) again
225
+ }
226
+ // Cancelled: return undefined so the Dialog calls closeDialog(Cancelled)
227
+ }}
228
+ >
229
+ ```
230
+
231
+ ## Content-only Dialogs (No Action Buttons)
232
+
233
+ Use `buttons={null}` for dialogs that contain their own internal actions (e.g. a menu + data table) and don't need a confirm/cancel footer:
234
+
235
+ ```tsx
236
+ <Dialog
237
+ title="Hubs"
238
+ visible={true}
239
+ width="50vw"
240
+ buttons={null}
241
+ onCancel={() => closeDialog(DialogResult.Cancelled)}
242
+ >
243
+ <Menubar model={menuItems} />
244
+ <Listing configurationId={configurationId} />
245
+ </Dialog>
246
+ ```
247
+
248
+ ## Props Reference
249
+
250
+ | Prop | Type | Notes |
251
+ |---|---|---|
252
+ | `title` | `string` | Header text (replaces PrimeReact `header`) |
253
+ | `visible` | `boolean` | Controls visibility |
254
+ | `buttons` | `DialogButtons \| ReactNode \| null` | Prefer `DialogButtons` enum; `null` for no footer |
255
+ | `isValid` | `boolean` | Disables the confirm button when `false` |
256
+ | `okLabel` | `string` | Override the Ok/Confirm button label |
257
+ | `cancelLabel` | `string` | Override the Cancel button label |
258
+ | `onConfirm` | `() => boolean \| void \| Promise<boolean> \| Promise<void>` | Called when Ok is clicked; return `false` to keep dialog open, `true` to close |
259
+ | `onCancel` | `() => void \| Promise<void>` | Called when Cancel is clicked |
260
+ | `onClose` | `(result: DialogResult) => boolean \| void \| Promise<...>` | Combined handler for both Ok and Cancel |
261
+ | `width` | `string` | Dialog width (e.g. `'50vw'`) — replaces PrimeReact `style={{ width }}` |
262
+ | `resizable` | `boolean` | Default `false` |
263
+
264
+ `style`, `contentStyle`, and `dismissable` **are** supported. The other v10 PrimeReact Dialog props (`modal`, `dismissableMask`, `draggable`, `footer`, `onHide`) are **not** available — do not use them. (`resizable` is accepted for compatibility but is a no-op in PrimeReact 11.)