@cratis/pi 7.36.1 → 7.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/package/corpus/harnesses/copilot/instructions/ai-distribution.instructions.md +109 -0
  2. package/package/corpus/harnesses/copilot/instructions/application-profile.instructions.md +81 -0
  3. package/package/corpus/harnesses/copilot/instructions/capability-is-not-authority.instructions.md +45 -0
  4. package/package/corpus/harnesses/copilot/instructions/code-quality.csharp.instructions.md +92 -0
  5. package/package/corpus/harnesses/copilot/instructions/code-quality.instructions.md +83 -0
  6. package/package/corpus/harnesses/copilot/instructions/code-quality.typescript.instructions.md +90 -0
  7. package/package/corpus/harnesses/copilot/instructions/components.instructions.md +300 -0
  8. package/package/corpus/harnesses/copilot/instructions/concepts.instructions.md +138 -0
  9. package/package/corpus/harnesses/copilot/instructions/cratis-folder-is-configuration.instructions.md +29 -0
  10. package/package/corpus/harnesses/copilot/instructions/csharp.instructions.md +357 -0
  11. package/package/corpus/harnesses/copilot/instructions/dialogs.instructions.md +278 -0
  12. package/package/corpus/harnesses/copilot/instructions/documentation-structure-and-formatting.instructions.md +162 -0
  13. package/package/corpus/harnesses/copilot/instructions/documentation.instructions.md +90 -0
  14. package/package/corpus/harnesses/copilot/instructions/editing-cratis-docs.instructions.md +74 -0
  15. package/package/corpus/harnesses/copilot/instructions/efcore.instructions.md +84 -0
  16. package/package/corpus/harnesses/copilot/instructions/efcore.specs.instructions.md +45 -0
  17. package/package/corpus/harnesses/copilot/instructions/elixir.instructions.md +50 -0
  18. package/package/corpus/harnesses/copilot/instructions/exit-codes-and-wrappers.instructions.md +34 -0
  19. package/package/corpus/harnesses/copilot/instructions/framework.instructions.md +55 -0
  20. package/package/corpus/harnesses/copilot/instructions/frontend-quality.instructions.md +60 -0
  21. package/package/corpus/harnesses/copilot/instructions/frontend-testing.instructions.md +127 -0
  22. package/package/corpus/harnesses/copilot/instructions/git-commits.instructions.md +139 -0
  23. package/package/corpus/harnesses/copilot/instructions/github-actions.instructions.md +87 -0
  24. package/package/corpus/harnesses/copilot/instructions/glossary.instructions.md +63 -0
  25. package/package/corpus/harnesses/copilot/instructions/go-engineering.instructions.md +61 -0
  26. package/package/corpus/harnesses/copilot/instructions/go.instructions.md +116 -0
  27. package/package/corpus/harnesses/copilot/instructions/guards-and-fuses.instructions.md +61 -0
  28. package/package/corpus/harnesses/copilot/instructions/java.instructions.md +138 -0
  29. package/package/corpus/harnesses/copilot/instructions/kotlin.instructions.md +175 -0
  30. package/package/corpus/harnesses/copilot/instructions/local-work-artifacts.instructions.md +56 -0
  31. package/package/corpus/harnesses/copilot/instructions/new-repository-intake.instructions.md +35 -0
  32. package/package/corpus/harnesses/copilot/instructions/orleans.instructions.md +97 -0
  33. package/package/corpus/harnesses/copilot/instructions/profiles.instructions.md +403 -0
  34. package/package/corpus/harnesses/copilot/instructions/pull-requests.instructions.md +176 -0
  35. package/package/corpus/harnesses/copilot/instructions/react.instructions.md +203 -0
  36. package/package/corpus/harnesses/copilot/instructions/reactors.instructions.md +336 -0
  37. package/package/corpus/harnesses/copilot/instructions/rtk.instructions.md +48 -0
  38. package/package/corpus/harnesses/copilot/instructions/specs.csharp.instructions.md +147 -0
  39. package/package/corpus/harnesses/copilot/instructions/specs.instructions.md +133 -0
  40. package/package/corpus/harnesses/copilot/instructions/specs.scenarios.csharp.instructions.md +281 -0
  41. package/package/corpus/harnesses/copilot/instructions/specs.typescript.instructions.md +141 -0
  42. package/package/corpus/harnesses/copilot/instructions/storybook.instructions.md +85 -0
  43. package/package/corpus/harnesses/copilot/instructions/typescript.instructions.md +151 -0
  44. package/package/corpus/harnesses/copilot/instructions/verification-discipline.instructions.md +27 -0
  45. package/package/corpus/harnesses/copilot/instructions/vertical-slices.instructions.md +414 -0
  46. package/package/corpus/harnesses/copilot/instructions/web-fetching.instructions.md +12 -0
  47. package/package/corpus/harnesses/copilot/instructions/writing-correct-examples.instructions.md +57 -0
  48. package/package/corpus/harnesses/copilot/instructions/writing-cratis-docs.instructions.md +72 -0
  49. package/package/corpus/hooks/scripts/cratis-nuget-pins.txt +6 -0
  50. package/package/corpus/product-sync.json +7 -3
  51. package/package/corpus/rules/orleans.md +4 -0
  52. package/package/corpus/rules/profiles.md +3 -3
  53. package/package/corpus/skills/cratis-arc-observable-query-http/SKILL.md +17 -0
  54. package/package/corpus/skills/cratis-arc-observable-query-http/verification.json +12 -0
  55. package/package/corpus/skills/cratis-arc-react-page/SKILL.md +5 -1
  56. package/package/corpus/skills/cratis-arc-react-page/verification.json +3 -0
  57. package/package/corpus/skills/cratis-chronicle-client-kotlin/SKILL.md +2 -2
  58. package/package/corpus/skills/cratis-components-common-controls/SKILL.md +149 -0
  59. package/package/corpus/skills/cratis-components-common-controls/verification.json +15 -0
  60. package/package/corpus/skills/cratis-components-data-surfaces/SKILL.md +185 -0
  61. package/package/corpus/skills/cratis-components-data-surfaces/verification.json +16 -0
  62. package/package/corpus/skills/cratis-fundamentals-telemetry/SKILL.md +125 -0
  63. package/package/corpus/skills/cratis-fundamentals-telemetry/verification.json +15 -0
  64. package/package/corpus/skills/cratis-orleans-jobs/SKILL.md +115 -0
  65. package/package/corpus/skills/cratis-orleans-jobs/references/state-machines.md +64 -0
  66. package/package/corpus/skills/cratis-orleans-jobs/verification.json +20 -0
  67. package/package/corpus/skills/cratis-screenplay-discovery/SKILL.md +11 -6
  68. package/package/corpus/skills/cratis-screenplay-discovery/references/facilitation.md +9 -5
  69. package/package/corpus/skills/cratis-screenplay-discovery/references/human-workshop.md +1 -1
  70. package/package/corpus/skills/cratis-screenplay-discovery/references/plotting-and-handoff.md +28 -4
  71. package/package/corpus/skills/cratis-screenplay-discovery/references/storming-loop.md +2 -1
  72. package/package/corpus/skills/cratis-screenplay-discovery/verification.json +5 -0
  73. package/package/corpus/skills/cratis-screenplay-model-authoring/SKILL.md +23 -29
  74. package/package/corpus/skills/cratis-screenplay-model-authoring/references/mcp-loop.md +15 -1
  75. package/package/corpus/skills/cratis-screenplay-model-authoring/references/model-notes.md +208 -0
  76. package/package/corpus/skills/cratis-screenplay-model-authoring/verification.json +20 -1
  77. package/package/corpus/skills/cratis-screenplay-model-review/SKILL.md +19 -36
  78. package/package/corpus/skills/cratis-screenplay-model-review/references/report-template.md +26 -0
  79. package/package/corpus/skills/cratis-screenplay-model-review/verification.json +7 -0
  80. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/SKILL.md +14 -16
  81. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/references/core-rules-digest.md +8 -3
  82. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/references/handoff-template.md +5 -2
  83. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/references/intake.md +21 -0
  84. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/references/reasoning-notes.md +10 -6
  85. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/references/stop-or-assume.md +32 -1
  86. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/references/worked-example.md +5 -2
  87. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/verification.json +5 -0
  88. package/package/corpus/skills/cratis-screenplay-toolchain/SKILL.md +4 -0
  89. package/package/corpus/skills/cratis-screenplay-toolchain/references/screenplay-contract.json +1716 -183
  90. package/package/corpus/skills/cratis-screenplay-toolchain/references/screenplay-contract.version +1 -1
  91. package/package/corpus/skills/cratis-screenplay-toolchain/references/versions.md +40 -4
  92. package/package/corpus/skills/cratis-screenplay-toolchain/verification.json +6 -0
  93. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/sandbox-and-specrunner.md +26 -0
  94. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/verification.json +3 -0
  95. package/package/profile-catalog.json +6 -2
  96. package/package.json +1 -1
@@ -0,0 +1,176 @@
1
+ ---
2
+ applyTo: "**/*"
3
+ ---
4
+ <!-- cratis-ai: generated Copilot instructions. Do not edit; rules/ is the source. -->
5
+
6
+ # How to Do Pull Requests
7
+
8
+ ## The description is the release note
9
+
10
+ **The PR description is published verbatim as the GitHub release. In repositories released by `cratis/release-action`, it also closes every issue written as `(#n)`; elsewhere, follow the repository's own release and issue-closing process.** Write the note the person upgrading should read, not a development write-up. The same contract applies to release notes typed into a manual workflow run or written straight into a release. Where it is installed, the `verify-release-notes` check enforces it on release-bound PRs into the default branch carrying `major`/`minor`/`patch` (it re-runs when the label is added); it warns on `no-release` and unlabelled PRs, which must still be written this way because any of them may become releasable. `release-action` does not run for a `no-release` PR, so `(#n)` closes nothing there. Editing the description re-runs the check. HTML comments are not shown on the release page and the check ignores them for headings, keywords and placeholders, and `release-action` removes them before publishing, so a `(#n)` only inside a comment closes nothing; the check fails it, so never put an issue reference in a comment. The template's own comment can stay: its `(#123)` examples are inline code, and the comment is not published.
11
+
12
+ ### Allowed shape
13
+
14
+ - Follow the repository's pull request template (`.github/pull_request_template.md`) within this contract.
15
+ - An optional summary, first, only when one cohesive theme spans the bullets: **either** a `## Summary` section (short, user-facing prose) **or** one unheaded lead paragraph (1–3 sentences), never both. `Summary` is a level-2 heading; `# Summary` is the wrong level. No bullets before the first section.
16
+ - Then only these `##` sections, each at most once, in this order after any `## Summary`: `## Added`, `## Changed`, `## Fixed`, `## Removed`, `## Security`, `## Deprecated`. Keep only the sections that have bullets: a section holding only prose, a code example or `None.` fails the check, so delete it. `###` sub-headings inside a section are fine unless they use a forbidden name; inside a section even `### Added` is only a sub-heading, and a section itself is always level 2.
17
+ - A `major`/`minor`/`patch` PR needs at least one bullet under an allowed section.
18
+ - Bullets are short, self-contained and user-facing: what a consumer compiles against, runs or observes. A user-visible fix may add one sentence of root cause or regression guard, stated as observable behavior. Credit an external contributor by name or handle, unless they asked not to be named.
19
+ - Breaking changes and upgrade actions are bullets in `## Changed` or `## Removed` that state the action. A longer migration story goes in the docs, linked by absolute URL.
20
+ - Do not list internal plumbing (storage, converters, gRPC internals, specs, file lists, refactor narration).
21
+
22
+ ### Issue references
23
+
24
+ | Write | Meaning |
25
+ | --- | --- |
26
+ | `(#351)` at the **end of the bullet that delivers it** | Delivered: in a repository released by `cratis/release-action`, it closes it at release |
27
+ | `(part of #351)` or prose `see #351` | Related, partial or follow-up: never closes |
28
+ | `Cratis/Repo#351` | Other repository: never closed |
29
+ | one `(#351)` per issue, never `(#351, #352)` | `(#351, #352)` closes nothing: write `(#351) (#352)` |
30
+
31
+ - Nothing but more issue references (also `(part of #n)`, `(see #n)` and `(Cratis/Repo#n)`), closing emphasis, `<br>` and sentence punctuation (a `:` may introduce nested bullets) may follow the delivering `(#n)`. Write one `(#n)` per issue: release-action only matches `(#n)`, so `(#56, #57)` closes nothing; write `(#56) (#57)`.
32
+ - Never use a linking or closing keyword before a number, anywhere: `Close`, `Closes`, `Closed`, `Fix`, `Fixes`, `Fixed`, `Resolve`, `Resolves`, `Resolved`, `Refs`, `Ref`, `References`, also as `Keyword: #n`, with `owner/repo#n`, followed by an issue URL, and wrapped in bold, italics or a link (`**Closes** #n`, `Closes [#n](url)`, `[Closes #n](url)`, and `[Closes](url)` where the link starts a clause). A `Refs #93` line closes nothing and is not a delivery marker: write `(#93)` at the end of the delivering bullet, or `(part of #93)` if it must stay open.
33
+ - Comment on or close an issue when the user's request includes that effect; otherwise prepare a bounded post-merge disposition without performing it. In repositories released by `cratis/release-action`, a delivered `(#n)` is closed by release-action at release, so do not close it by hand; for a `no-release` PR release-action does not run, so any disposition follows this same rule. Elsewhere, follow the repository's own release and issue-closing process.
34
+ - Use a bare `(#n)` only for an issue this PR fully delivers. No issue means no reference. Never use a placeholder such as `(#issue)` or the template's `(#123)`, and verify every number exists in the right repository; never guess.
35
+
36
+ ### Forbidden anywhere outside code
37
+
38
+ - **Headings** (any level, also written as HTML `<h2>`, `<b>`, `<i>` or a possibly multi-line `<summary>`, or as an `*italic*` line; a heading line is checked like any other, so a keyword, relative link or `(#n)` in one fails too) other than a first `## Summary`, such as Overview, Description, What, Why, How, Context, Changes, What changed, Test plan, Testing, Tests, Verification, Verified, Validation, Quality, Review, Notes, Notes for reviewers, Limitations, Known follow-up, Acceptance, Details, and any `#`/`##` heading not in the allowed list.
39
+ - **Review, verification, testing and provenance notes**: `Review:`, `Reviewed:`, a stand-alone `Reviewed by` line, `Verification:`, `Tested:`, `Testing:`, `Validation:` lines that stand alone or report a result (`Tests: 400 passed`, `Review: approved`); same-provider, cross-provider, Opus-only or Anthropic-only review remarks; the review workflow having passed, returned or run; CI or gate results (`CI green`, `all tests passed`); and a line stating which agent or model wrote the description (`Generated with Claude Code`, `Co-Authored-By:`). A bullet that describes a product change and only mentions review, validation, tests or an AI model (`- Validation: rules now apply to commands (#3)`, `- Reviewed by status is now shown on the dashboard (#4)`) is fine: inside a bullet, a summary or the lead paragraph the check reads a note only when it ends the clause (`- Cross-provider review pending`).
40
+ - **Verification and scope notes**: how or where something was checked (`verified by building …`, `verified in Storybook`, `tested locally`, `Storybook only`), what was not checked (`not yet verified`, `Not verified against a real kernel`), and results (`492/492`, `9 of 9 runs`, `Tier 1 PASS`, `0 warnings, 0 errors`, a `Local:`, `CI:`, `Checks:` or `Checked locally:` line).
41
+ - **Reviewer, merge and deploy instructions**: `Stacked on #207`, `retarget to main once #207 merges`, `This PR should be deployed separately`, `Draft:`, `do not merge`, merge or deploy order. If a consumer must upgrade in a set order, write that as an upgrade bullet ("Upgrade the Chronicle kernel to 19.25 before this client").
42
+ - **Provenance**: which branch, commit or build something was checked against (`verified against Arc.TypeScript main (v0.34.0)`). If compatibility matters to the reader, state it as a fact about the release: "Requires `@cratis/arc` 0.34 or later."
43
+ - **Internal state** that is not a consumer change (for example "npm publication remains disabled").
44
+ - **Relative links** (`](Documentation/x.md)`, `](./x)`, `](Source/...)`): they 404 on the release page. Use `https://github.com/Cratis/<Repo>/blob/main/<path>`, a `#anchor` or `mailto:`.
45
+ - **Placeholders and transcripts**: template text, empty sections, Copilot "Original prompt" blocks, agent transcripts.
46
+
47
+ Reviewer-facing information (test plan, verification, what was not verified, review provenance, stacking, merge or deploy order) goes in a **PR comment** posted right after creating the PR: `gh pr comment <n> --body-file .ai-work/pr-notes.md`. Never put it in the description, even when the PR is labelled `no-release`.
48
+
49
+ ### Keep the note true to the diff
50
+
51
+ After merging or rebasing the base branch, and before every body edit, run `git diff --stat origin/main...HEAD`. Delete each bullet whose change is now on main through another PR, or was dropped in conflict resolution. Check every version, package name and registry against the diff and the current `.github/workflows/publish*.yml`; never name a registry the publish workflow does not push to.
52
+
53
+ ### Bad to good
54
+
55
+ Bad (Cratis/Arc.TypeScript v0.48.0 as first published, bullets abridged):
56
+
57
+ ```markdown
58
+ Arc can now construct Chronicle reactors and reducers through its own dependency injection, one scope per delivered batch, as an opt-in preview. npm publication remains disabled.
59
+
60
+ ## Added
61
+
62
+ - Preview option `withChronicle({ ..., activateArtifactsInScopes: true })` ... (#93)
63
+
64
+ See [Activate reactors and reducers in Arc scopes](Documentation/chronicle/reactors/scoped-activation.md).
65
+
66
+ Review: Opus-only (same-provider) review.
67
+
68
+ Refs #93
69
+ ```
70
+
71
+ Good (as corrected):
72
+
73
+ ```markdown
74
+ Arc can now construct Chronicle reactors and reducers through its own dependency injection, one scope per delivered batch, as an opt-in preview.
75
+
76
+ ## Added
77
+
78
+ - Preview option `withChronicle({ ..., activateArtifactsInScopes: true })` ... See [Activate reactors and reducers in Arc scopes](https://github.com/Cratis/Arc.TypeScript/blob/main/Documentation/chronicle/reactors/scoped-activation.md). (part of #93)
79
+ ```
80
+
81
+ "npm publication remains disabled" is internal status, the link was relative (it 404s on the release page), the review line is provenance for reviewers, and `Refs #93` is a keyword line. #93 was only partly delivered by this release, so its bullets say `(part of #93)`; `(#93)` would have closed it.
82
+
83
+ ### Before you create or edit a PR
84
+
85
+ 1. Run `node .cratis/ai/hooks/scripts/cratis-check-pr.mjs --body-file <file> --label <intent>`. It runs the exact `verify-release-notes` rules plus the label and diff checks, and the Bash hook runs it on `gh pr create` and `gh pr edit`.
86
+ 2. Read the body against the forbidden list and the issue table above; delete every hit and verify every issue number.
87
+ 3. Sections in order, none empty, at most one summary form; every bullet user-facing.
88
+ 4. Test plan, verification and review notes are in a PR comment.
89
+
90
+ If `verify-release-notes` fails, fix it by editing the description, not by pushing code.
91
+
92
+ ## Commits
93
+
94
+ See the full [Git Commits guide](./git-commits.instructions.md) for rules on logical grouping, message format, and staging discipline.
95
+
96
+ Quick reminders:
97
+
98
+ - Imperative mood: "Add author registration" not "Added author registration".
99
+ - Each commit = one logical unit of work. No WIP commits in the final PR.
100
+ - Never mix unrelated changes in a single commit.
101
+
102
+ ## Labels
103
+
104
+ Confirm the current repository workflow contract before selecting release intent because release-intent labels can trigger publication. A direct request to ship with a named label authorizes applying that label and completing the repository's standard pull-request workflow, including the release it normally triggers. Do not ask for separate authorization at each step. If the user did not request shipping or publication, a proposed semantic label describes impact but does not grant authority.
105
+
106
+ - Exactly one of `major`/`minor`/`patch`/`no-release`, set in the creating command (`gh pr create --label patch`), in every repository, including samples, docs and tooling repositories (`no-release` when nothing is shipped). To change intent, swap labels in one command: `gh pr edit <n> --remove-label minor --add-label patch`. Never add a second: with more than one of them, release-action releases nothing and reports `reason=error`. Re-running a `pull_request` workflow replays the original label payload, so fix a label mistake by editing the PR or pushing; a `workflow_dispatch` run needs an explicit `version` and the PR description as `release-notes`, and without notes it closes no issues.
107
+ - Dependabot PRs carry only `no-release`. release-action never releases them, and the `major`/`minor`/`patch` Dependabot adds describe the dependency's version, not this product's.
108
+ - Label the PR according to semantic versioning impact:
109
+ - **major** — breaking changes to public APIs
110
+ - **minor** — new features, new slices, non-breaking additions
111
+ - **patch** — bug fixes, refactoring with identical behavior
112
+
113
+ ### A major release needs a human's explicit go-ahead
114
+
115
+ A `major` label is the one release-intent label a ship request does not, by
116
+ itself, authorize through to merge. Breaking a public API is the most
117
+ consequential and hardest-to-reverse thing a release does — every downstream
118
+ consumer eventually has to act on it — so it gets a checkpoint the other two
119
+ intents do not.
120
+
121
+ - Prepare the branch, commits, push, and PR carrying the `major` label exactly
122
+ as any other ship request would.
123
+ - Before merging, stop and ask a human to confirm the major bump specifically:
124
+ name the exact breaking change(s), who is affected and how, and the
125
+ resulting version number. A generic "ready to ship?" is not this checkpoint —
126
+ say plainly that this release breaks compatibility and needs a yes.
127
+ - Proceed to merge only on an explicit, affirmative answer to that question.
128
+ A prior general instruction to "ship" or "land this" does not answer it,
129
+ even when it named `major` as the intended label.
130
+ - This checkpoint is per release, not per conversation — a human confirming
131
+ one major release does not pre-authorize the next one.
132
+
133
+ This narrows the general ship-changes authorization in
134
+ [`ship-changes.prompt.md`](../../../prompts/ship-changes.prompt.md) for exactly this
135
+ label; every other step of that workflow proceeds under its existing
136
+ authority.
137
+
138
+ ### A pull request that changes nothing outward-facing carries `no-release`
139
+
140
+ **If nothing in the PR can change what a consumer compiles against, runs, or observes, propose repository-supported non-release intent, ordinarily `no-release`** — not `patch`. Confirm that the current workflow supports the label, requires exactly one release-intent label, and suppresses publication as intended before applying it with authorization.
141
+
142
+ Where the repository requires release intent, `no-release` is a decision, not an omission. Missing required labels are blockers, not a reason to bypass the check.
143
+
144
+ This covers, whenever the PR touches *only* these:
145
+
146
+ - **Documentation** — anything under `Documentation/**`, READMEs, the `.cratis/ai/` corpus.
147
+ - **CI and repository automation** — `.github/workflows/**`, `.github/scripts/**`, `.github/CODEOWNERS`, issue/PR templates.
148
+ - **Tests and specs** — `*.Specs/**`, `when_*/**`, `for_*/**`, `Integration/**`, and test-only fixtures.
149
+ - **Build and tooling configuration** that produces no shipped artifact difference — lint config, editor config, local scripts.
150
+
151
+ The test is **outward-facing effect, not file location**. A change under `Source/**` that only touches specs is not shippable; a one-line change to a published package's behavior is, however small. If a consumer could not tell the difference by upgrading, there is nothing to version. When genuinely unsure, ask rather than defaulting to `patch` — an unnecessary release is not free: it burns a version number, ships release notes describing nothing, and buries the releases that matter.
152
+
153
+ A non-release pull request must satisfy the relevant required checks like any other. Confirm label acceptance and publication suppression against the current workflow contract; do not assume external CI or API state.
154
+
155
+ ### Group small related changes into one pull request
156
+
157
+ Do not open a pull request per task when the tasks belong to the same body of work. Several small merged PRs become several releases, and a stream of near-empty patch releases makes the release history useless for the people it is written for. Collect related work — a set of CI gates, a group of fixes in one area, the steps of one refactor — onto **one branch, as separate commits**, and open **one** pull request. Commits stay one-logical-unit-each; the pull request is the release boundary, and the release boundary should be a coherent, describable change.
158
+
159
+ **Before consolidating open PRs, review each PR’s release intent and workflow effects.** Integration may trigger completion/publication behavior on an absorbed PR. Use supported non-release intent where appropriate. A direct request to consolidate the named pull requests authorizes the necessary relabeling and merge; it does not authorize unrelated notifications.
160
+
161
+ Split into separate pull requests when the changes are genuinely unrelated, when one is urgent and the others are not, or when one is risky enough to want its own revert.
162
+
163
+ ## Quality Gates
164
+
165
+ Documentation-only changes use repository-supported non-release intent, ordinarily `no-release`; confirm the workflow contract rather than assuming a label or API state. Run relevant content, link, frontmatter, and corpus checks instead of unrelated application builds, and satisfy every repository-required check, including release-intent checks where supported. Documentation is never a blanket exemption from red CI.
166
+
167
+ **`no-release` does not otherwise excuse a PR from this section.** A CI, tooling, or spec-only pull request ships nothing, but it is exactly the kind of change that can break the build or the pipeline for everyone else — a broken workflow or a deleted spec does its damage without ever being released. Hold it to every gate below.
168
+
169
+ Before marking code/automation work ready, select the affected-project gates that apply from the following list; run wider checks for cross-cutting changes and repository-required merge/release gates:
170
+
171
+ - `dotnet build` — zero errors, zero warnings
172
+ - `dotnet test` — all specs pass
173
+ - `yarn lint` — zero errors
174
+ - `npx tsc -b` — zero TypeScript errors
175
+ - Code follows all project coding standards and conventions
176
+ - **Required CI checks pass.** After an authorized push, inspect checks and failure logs. Diagnose within a bounded attempt, fix in-scope causes, and re-run relevant gates after each fix. Report unrelated/environmental failures and missing authority as blockers rather than retrying indefinitely or silently waiving required checks. If a release job fails after a concurrent merge (the release for the tag exists for another commit), re-run it: it releases under the next version (release-action v1.4.2 fails the job rather than reporting success).
@@ -0,0 +1,203 @@
1
+ ---
2
+ applyTo: "**/*.tsx, **/Components/**/*.ts"
3
+ profile: application
4
+ paths:
5
+ - "**/*.tsx"
6
+ - "**/Components/**/*.ts"
7
+ ---
8
+ <!-- cratis-ai: generated Copilot instructions. Do not edit; rules/ is the source. -->
9
+
10
+ # React + Arc + Cratis Components
11
+
12
+ The frontend is React + TypeScript in MVVM style, composed from **Cratis Components** on top of **Arc-generated proxies**. Cratis Components (4.x) owns its markup, styling contract and behavior — there is no vendor component library underneath to reach for; React Aria is an internal detail you never import. For component structure, styling, and icons see [components.md](./components.instructions.md); for dialogs see [dialogs.md](./dialogs.instructions.md). This rule covers MVVM, queries, and commands.
13
+
14
+ ## The proxy boundary
15
+
16
+ `dotnet build` (Debug) generates a typed TypeScript proxy per command and query, emitted next to the slice's `.cs`. React imports those proxies and calls their static hooks. **Backend before frontend, always** — the proxies don't exist until the backend compiles. **Never edit a generated proxy** (`// @generated by Cratis` header) — fix the C# source and rebuild.
17
+
18
+ ## MVVM
19
+
20
+ Move behavior out of the render function. Two accepted idioms:
21
+
22
+ - **`withViewModel`** from `@cratis/arc.react.mvvm` — for command/query orchestration, long-lived UI state, and workflow components that benefit from observable class state.
23
+ - **A tested pure state module/helper** — for deterministic page/form state that doesn't need MobX. The component stays rendering + event wiring; the module owns derived state, filtering/sorting, wizard steps, command-value derivation, and has BDD specs (see [frontend-testing.md](./frontend-testing.instructions.md)).
24
+
25
+ Don't leave complex behavior inline. Trivial presentational leaf components need no view model.
26
+
27
+ ```ts
28
+ import { injectable } from 'tsyringe';
29
+
30
+ @injectable()
31
+ export class <Entity>ListingViewModel {
32
+ selectedId: string | null = null;
33
+ select(id: string) { this.selectedId = id; }
34
+ }
35
+ ```
36
+
37
+ ```tsx
38
+ import { withViewModel } from '@cratis/arc.react.mvvm';
39
+
40
+ export const Listing = withViewModel(<Entity>ListingViewModel, ({ viewModel }) => (
41
+ /* read viewModel state inside JSX; call viewModel methods */
42
+ ));
43
+ ```
44
+
45
+ **View-model rules:**
46
+ - Pure TypeScript classes — no JSX, never store React state, never call React hooks (`useIdentity`, `useNavigate`) inside one — inject the Cratis abstraction instead.
47
+ - Fields are auto-observable (`makeAutoObservable` is applied by `withViewModel`); no `@observable` on plain assignments.
48
+ - Inject services via the primary constructor (`@injectable` / tsyringe).
49
+ - A non-trivial view model must be constructible directly in a spec and covered with BDD specs.
50
+
51
+ ### When to extract a view model
52
+
53
+ Extract as soon as a component has **any** of: 3+ `useState`, any `useCallback` beyond a trivial inline handler, any `useEffect` that synchronizes state, derived values computed from other state (use a getter), or state shared via prop drilling.
54
+
55
+ ### MobX reactivity
56
+
57
+ - **Dereference late** — read `viewModel.property` *inside* JSX; never destructure observables at the top of the component body (the captured value won't track changes).
58
+ - **Computed getters** for derived state (auto-`computed`) — not `useMemo` in the component.
59
+ - **`useCallback`/`useMemo` are unnecessary in `withViewModel` components** — the render is wrapped in `Observer`. Their presence signals state that belongs in the view model. (This does *not* apply to plain functional components using proxy hooks directly. A plain component reaching for `useCallback` to stabilize a handler is a signal that handler belongs in a view model or tested state module.)
60
+ - **Observable arrays into raw children** — passing `viewModel.items` (an observable array) to a *raw non-observer* child needs `viewModel.items.slice()` to materialize a plain array. Uncommon in practice — prefer `DataTableForObservableQuery`, which handles reactivity internally.
61
+
62
+ ### Props / route / query params in view models
63
+
64
+ | Need | Mechanism |
65
+ |---|---|
66
+ | Props that change after mount | implement `IHandleProps<TProps>` → `handleProps(props)` (called on initial mount **and** every props change) |
67
+ | Props set once at mount | `@props readonly componentProps: TProps` |
68
+ | Route params | `@params readonly routeParams: RouteParams` — put `@field` on each property |
69
+ | Query-string params | `@queryParams` |
70
+
71
+ `@field` on each route/query-param property is what makes Arc's JSON serializer convert the URL **strings** to the property's real type — **without `@field`, the values stay strings**.
72
+
73
+ ### Injectable abstractions (never touch browser/React globals in a view model)
74
+
75
+ | Interface | Package | Replaces |
76
+ |---|---|---|
77
+ | `IMessenger` | `@cratis/arc.react.mvvm/messaging` | cross-component selection (list↔detail) — not React Context |
78
+ | `IDialogs` | `@cratis/arc.react.mvvm/dialogs` | imperative confirmation/busy dialogs from a view model |
79
+ | `IIdentityProvider` | `@cratis/arc/identity` | identity in a view model (not the `useIdentity` hook) |
80
+ | `ILocalStorage`, `INavigation` | `@cratis/arc.react.mvvm/browser` | `localStorage`, URL navigation |
81
+ | `IViewModelDetached` | `@cratis/arc.react.mvvm` | teardown (`detached()`) for subscriptions/timers |
82
+
83
+ These are registered automatically by `withViewModel` and injected via the primary constructor. Concrete usage:
84
+
85
+ - **`IMessenger`** — messages are plain classes; `_messenger.publish(new <Entity>Selected(id))` and `messenger.subscribe(<Entity>Selected, ({ id }) => ...)`. For scoped delivery, the messenger from `@cratis/arc/messaging` (distinct from `…mvvm/messaging`) resolves the nearest scope and falls back to root: a `<MessengerScope>` trickles `publish()` **down** into nested scopes by default (`trickleDownToChildren`, default `true`) and bubbles **up** only when `bubbleToParent` is set (default `false`) — both are props on `MessengerScope` / properties on the messenger, not options on `publish()`. Use the default `IMessenger` unless you genuinely need scoping.
86
+ - **`IDialogs`** — `await _dialogs.showConfirmation(title, message, DialogButtons.YesNo)` returns a `DialogResult`. For user-triggered dialogs that open React command dialogs, use `useDialog` in the component, not `IDialogs`.
87
+ - **`IIdentityProvider`** — `await _identityProvider.getCurrent<TDetails>()`. Use the `useIdentity()` hook only in functional components without a view model.
88
+ - **`ILocalStorage`** — `setItem(key, value)` / `getItem(key)`; **`IViewModelDetached.detached()`** runs on unmount (`detached() { this._subscription?.unsubscribe(); }`).
89
+
90
+ **Decorator prerequisite:** `experimentalDecorators` + `emitDecoratorMetadata` + `reflect-metadata` must be wired (tsconfig + the build) for `@injectable`/`@props`/`@params`/`@field` to work — don't change them.
91
+
92
+ ## Queries
93
+
94
+ A typed proxy class is generated per query. Pick the hook:
95
+
96
+ | Hook | When |
97
+ |---|---|
98
+ | `<Query>.use(args?, sorting?)` | standard — returns `[result, perform, setSorting]`, re-renders on change |
99
+ | `<Query>.useSuspense(...)` | suspense-aware — throws while loading; wrap in `<QueryBoundary>` |
100
+ | `<Query>.when(condition).use(args)` | conditional — only fires when `condition` is true (don't wrap a hook in `if`) |
101
+ | `<Query>.useWithPaging(pageSize, args?, sorting?)` | server-side paging (backend returns `IQueryable<T>`) |
102
+ | `<Query>.useSuspenseWithPaging(pageSize, ...)` | suspense + paging |
103
+ | `<ObservableQuery>.use(...)` | live observable (`ISubject<T>` backend) pushed over SSE/WebSocket |
104
+ | `<ObservableQuery>.useChangeStream(args?, getKey?, ...)` | item-level add/replace/remove deltas |
105
+
106
+ **Return-tuple shapes** (load-bearing — the observable variants have no `perform`):
107
+
108
+ | Hook | Returns |
109
+ |---|---|
110
+ | `<Query>.use(args?, sorting?)` | `[result, perform, setSorting]` |
111
+ | `<Query>.useSuspense(...)` | `[result, perform, setSorting]` |
112
+ | `<Query>.useWithPaging(pageSize, args?, sorting?)` | `[result, perform, setSorting, setPage, setPageSize]` |
113
+ | `<Query>.useSuspenseWithPaging(pageSize, ...)` | `[result, perform, setSorting, setPage, setPageSize]` |
114
+ | `<ObservableQuery>.use(...)` | `[result, setSorting]` for an enumerable query; `[result]` for a single-instance one — **no `perform`** |
115
+ | `<ObservableQuery>.useChangeStream(args?, getKey?, sorting?)` | `ChangeSet<T> { added, replaced, removed }` |
116
+
117
+ `result.paging` = `{ page, size, totalItems, totalPages }` (zero-based `page`). Read models returning `IQueryable<TReadModel>` get automatic server-side paging/sorting — use it whenever a list can grow.
118
+
119
+ - **`useChangeStream` `getKey`:** without `getKey`, removed items can't be identified — everything shows up as `added`. `getKey` enables `replaced` detection. Change streams exist only on observable queries returning `IEnumerable<T>`.
120
+ - **Conditional:** `<Query>.when(!!id).use({ id: id ?? '' })` — when `condition` is false no request/subscription is made and the hook returns `QueryResultWithState.empty()` (`hasData: false`, empty `data`); guard with `result.hasData`. `.when()` works with paging, suspense, and observable variants too.
121
+ - **Suspense:** `useSuspense()` throws a promise while loading and a typed error on failure — it **must** render inside a `<QueryBoundary>` (combines `<Suspense>` + `QueryErrorBoundary`) or React surfaces the thrown promise as an unhandled rejection. `useSuspense()` **re-suspends** on `refresh()`/`setSorting()`/`setPage()` (the cache entry is cleared). `<QueryBoundary onError={({ isQueryFailed, isQueryUnauthorized, error, reset }) => ...}>` exposes the failure; `(error as QueryFailed).exceptionMessages` carries diagnostics. In test teardown clear the caches with `clearSuspenseQueryCache()` / `clearSuspenseObservableQueryCache()` (`@cratis/arc.react/queries`).
122
+ - **`QueryScope`** + `useQueryScope()` (`@cratis/arc.react/queries`): `<QueryScope setIsPerforming={...}>` aggregates one `isPerforming` flag across the queries inside it — they register automatically (no manual add); scopes nest (inner registers with the nearest outer).
123
+
124
+ ## Commands
125
+
126
+ A typed proxy class is generated per command. For programmatic execution:
127
+
128
+ ```tsx
129
+ const [command, setCommandValues, clearCommandValues] = Command.use(); // 3-tuple
130
+ command.someField = value; // or setCommandValues({ ... })
131
+ const result = await command.execute();
132
+ ```
133
+
134
+ Inside dialogs, use `CommandDialog` + CommandForm fields rather than driving the command by hand — see [dialogs.md](./dialogs.instructions.md).
135
+
136
+ ### Read `CommandResult` by the granular flag — not just `isSuccess`
137
+
138
+ | Flag | Meaning | UX response |
139
+ |---|---|---|
140
+ | `isSuccess` | `isAuthorized && isValid && !hasExceptions` | happy path |
141
+ | `isAuthorized` | `[Roles]`/policy rejected | redirect to login / "not allowed" |
142
+ | `isValid` | validation failed — `validationResults` has per-field messages | render inline field errors |
143
+ | `hasExceptions` | `Provide()`/`Handle()` threw — `exceptionMessages` carry diagnostics | generic error toast + log; never show stack traces |
144
+
145
+ `CommandDialog` handles all of this automatically; the flags matter most when executing a command **outside** a dialog. `validate()` returns the same shape with `response == null` (handler did not run). `result.validationResults` is a `ValidationResult[]` of `{ severity, message, members: string[], state, reason, reasonDetail? }` — `members` names the affected properties — render per-field UI from it, but **never branch on raw `.message` text**. `exceptionMessages`/`exceptionStackTrace` are for logging, never for users.
146
+
147
+ Out-of-dialog execution branches in this order:
148
+
149
+ ```tsx
150
+ const result = await command.execute();
151
+ if (!result.isAuthorized) { redirectToLogin(); return; }
152
+ if (!result.isValid) { /* validationResults → inline field errors */ return; }
153
+ if (result.hasExceptions) { toast.error({ title: 'Something went wrong' }); console.error(result.exceptionMessages); return; }
154
+ // happy path — refresh queries, close, etc.
155
+ ```
156
+
157
+ `toastCommandResult(result, opts)` from `@cratis/components/Notifications` collapses this whole branch into one call (with a `<Toaster />` mounted) — success/not-authorized/validation/exception → the right toast, no stack traces shown.
158
+
159
+ ### Command helpers
160
+
161
+ - **`useCommandInstance<TCommand>()`** — inside a `CommandForm`/`CommandDialog`, read the live reactive command instance from context for dependent fields (no argument; the type parameter is the command type) (read it; never mutate — mutations go through field bindings).
162
+ - **`onBeforeExecute`** is a **transformer**: it receives the current values and **must return them** (mutated or not). It runs only on submit — never use it to seed required values (use `initialValues`).
163
+ - **`initialValues`** = synchronous baseline (also drives change tracking); **`currentValues`** = reactive overlay for async/late-loading values (e.g. from a query).
164
+ - **`asCommandFormField<P>(Component, opts)`** — wrap a custom input (rich text, address picker) so it participates in `CommandForm` with validation/error wiring.
165
+ - **`CommandScope`** + `useCommandScope()` (`@cratis/arc.react/commands`) — aggregate state across multiple commands (and queries) on one screen; commands and queries rendered inside the scope register themselves (the `ICommandScope` API also exposes `addCommand`/`addQuery`/`addChildScope` for manual registration); scopes nest (a child reports up to its parent); injectable as `ICommandScope` (nearest enclosing scope). Props: `setHasChanges`, `setIsPerforming`, `onBeforeExecute`, `onSuccess(cmd, result)`, `onFailed`, `onException`, `onUnauthorized`. `ICommandScope` members: `hasChanges`, `isPerforming`, `hasValidationFailures`, `hasExceptions`, `validationFailures`/`exceptions` (this scope), `aggregatedValidationFailures`/`aggregatedExceptions` (scope + children), `execute()` (runs only commands with changes), `revertChanges()`, `parent`.
166
+
167
+ At Arc v22.75.0 `CommandForm` defaults to safe exception text rather than raw
168
+ server messages; customize with `exceptionMessage` or `exceptionDisplayComponent`
169
+ (`ExceptionDisplayProps`). `showErrors={false}` hides built-in display. Dynamic
170
+ accessors need explicit `fieldName`; custom field validation failures block submit.
171
+
172
+ ### `<Arc>` global configuration
173
+
174
+ Top-level provider for microservice name and API base path. Config props (with defaults):
175
+
176
+ | Prop | Default | Notes |
177
+ |---|---|---|
178
+ | `apiBasePath` | — | Backend API prefix; `<Arc basePath>` is deprecated, not router configuration |
179
+ | `detailsType` | — | Constructor for typed identity details |
180
+ | `httpHeadersCallback` | — | returns `HeadersInit` merged into every request (bearer/tenant headers) |
181
+ | `queryTransportMethod` | `ServerSentEvents` | SSE is capped at 4 connections on HTTP/1.1 |
182
+ | `observableQueryTransferMode` | `Delta` | `Delta` = server ChangeSet/client diff; `Full` = all items as `added` |
183
+ | `queryConnectionCount` | `1` | hub connection slots |
184
+ | `queryDirectMode` | `false` | direct per-query connection — dev use only |
185
+
186
+ `QueryTransportMethod` imports from `@cratis/arc/queries` and `ObservableQueryTransferMode` from `@cratis/arc`; `Arc` from `@cratis/arc.react`. Reach `arc.reconnectQueries()` via `useContext(ArcContext)` and call it after login/logout to re-establish observable connections.
187
+
188
+ ## Composition
189
+
190
+ - Single responsibility — if you write a `// Section` comment inside a component, that section is its own component. Parent owns state; children receive props.
191
+ - Single-file component → in the slice folder; multi-file → a folder named after the component with an `index.ts` re-export.
192
+ - A `.tsx` belongs in the slice folder it implements — never one level up. A feature's pass-through layout renders only `<Outlet />`.
193
+
194
+ ## Styling default
195
+
196
+ Default to **Cratis Components with the `--cratis-*` tokens** (`var(--cratis-surface-*)`, `var(--cratis-primary-color)`, `var(--cratis-text-color)`; `pt`/`data-cratis-part` when you must reach a part) — see [components.md](./components.instructions.md). Tailwind is **not** the Cratis default: it is one supported way to *write* the CSS that maps a product's design onto those tokens. Never hard-code hex/`rgb()` for chrome.
197
+
198
+ ## See also
199
+
200
+ - [components.md](./components.instructions.md) — component structure, CSS, `--cratis-*` tokens and parts, icons, Storybook.
201
+ - [dialogs.md](./dialogs.instructions.md) — `CommandDialog` / `Dialog` / `StepperCommandDialog` rules.
202
+ - [frontend-quality.md](./frontend-quality.instructions.md) — engineering bar; [frontend-testing.md](./frontend-testing.instructions.md) — BDD specs.
203
+ - [typescript.md](./typescript.instructions.md) — TS style.