@cratis/pi 2.22.0 → 2.23.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.
- package/package/corpus/agents/backend-developer.md +6 -5
- package/package/corpus/agents/code-reviewer.md +7 -7
- package/package/corpus/agents/coordinator.md +5 -4
- package/package/corpus/agents/frontend-developer.md +34 -40
- package/package/corpus/agents/orchestrator.md +5 -4
- package/package/corpus/agents/performance-reviewer.md +5 -4
- package/package/corpus/agents/planner.md +5 -4
- package/package/corpus/agents/repository-investigation-reviewer.md +2 -1
- package/package/corpus/agents/repository-investigator.md +2 -1
- package/package/corpus/agents/security-reviewer.md +5 -4
- package/package/corpus/agents/slice-implementer.md +8 -2
- package/package/corpus/agents/spec-writer.md +6 -4
- package/package/corpus/harnesses/pi/extensions/cratis-rules/index.ts +233 -10
- package/package/corpus/harnesses/pi/extensions/subagent/agents.ts +52 -7
- package/package/corpus/harnesses/pi/extensions/subagent/index.ts +14 -0
- package/package/corpus/hooks/README.md +29 -27
- package/package/corpus/hooks/agent-stop.md +1 -5
- package/package/corpus/hooks/scripts/cratis-patterns.json +1 -1
- package/package/corpus/hooks/scripts/validate-package-subpaths.sh +1 -2
- package/package/corpus/prompts/ship-changes.prompt.md +4 -0
- package/package/corpus/prompts/verify-ai-setup.prompt.md +17 -7
- package/package/corpus/rules/ai-distribution.md +60 -0
- package/package/corpus/rules/application-profile.md +73 -0
- package/package/corpus/rules/components.md +77 -44
- package/package/corpus/rules/dialogs.md +24 -20
- package/package/corpus/rules/efcore.md +35 -187
- package/package/corpus/rules/framework.md +1 -1
- package/package/corpus/rules/frontend-quality.md +1 -1
- package/package/corpus/rules/frontend-testing.md +4 -4
- package/package/corpus/rules/general.md +21 -162
- package/package/corpus/rules/guards-and-fuses.md +24 -10
- package/package/corpus/rules/local-work-artifacts.md +6 -0
- package/package/corpus/rules/new-repository-intake.md +34 -0
- package/package/corpus/rules/orleans.md +13 -27
- package/package/corpus/rules/profiles.md +6 -0
- package/package/corpus/rules/react.md +12 -12
- package/package/corpus/rules/reactors.md +28 -5
- package/package/corpus/rules/rtk.md +13 -3
- package/package/corpus/rules/specs.scenarios.csharp.md +18 -3
- package/package/corpus/rules/specs.typescript.md +1 -1
- package/package/corpus/rules/storybook.md +2 -2
- package/package/corpus/rules/typescript.md +1 -0
- package/package/corpus/rules/verification-discipline.md +7 -1
- package/package/corpus/rules/vertical-slices.md +20 -15
- package/package/corpus/rules/writing-correct-examples.md +1 -0
- package/package/corpus/skills/cratis-application-react-specifications/SKILL.md +4 -4
- package/package/corpus/skills/cratis-application-slice-specifications/SKILL.md +16 -8
- package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/SKILL.md +6 -4
- package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/references/authentication.md +1 -1
- package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/references/authorization.md +1 -1
- package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/references/frontend.md +1 -1
- package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/references/local-development.md +1 -1
- package/package/corpus/skills/cratis-arc-authentication-authorization-and-identity/references/tenancy.md +7 -4
- package/package/corpus/skills/cratis-arc-command/SKILL.md +9 -7
- package/package/corpus/skills/cratis-arc-command/references/command-result.md +1 -1
- package/package/corpus/skills/cratis-arc-command/references/handler-shapes.md +2 -2
- package/package/corpus/skills/cratis-arc-command/references/proxy-generation.md +6 -3
- package/package/corpus/skills/cratis-arc-command/references/read-model-injection.md +1 -1
- package/package/corpus/skills/cratis-arc-command-execution/SKILL.md +12 -2
- package/package/corpus/skills/cratis-arc-command-validation/SKILL.md +5 -3
- package/package/corpus/skills/cratis-arc-ef-core-migration/SKILL.md +7 -4
- package/package/corpus/skills/cratis-arc-observable-query-http/SKILL.md +10 -3
- package/package/corpus/skills/cratis-arc-query-paging/SKILL.md +13 -8
- package/package/corpus/skills/cratis-arc-react-page/SKILL.md +27 -24
- package/package/corpus/skills/cratis-arc-react-page/references/data-page.md +15 -11
- package/package/corpus/skills/cratis-arc-react-page/references/data-tables.md +9 -2
- package/package/corpus/skills/cratis-arc-react-page/references/dialogs.md +11 -9
- package/package/corpus/skills/cratis-arc-react-page/references/queries-and-commands.md +26 -2
- package/package/corpus/skills/cratis-chronicle-cli-operations/SKILL.md +19 -13
- package/package/corpus/skills/cratis-chronicle-client-dotnet/SKILL.md +17 -15
- package/package/corpus/skills/cratis-chronicle-client-elixir/SKILL.md +24 -20
- package/package/corpus/skills/cratis-chronicle-client-kotlin/SKILL.md +26 -15
- package/package/corpus/skills/cratis-chronicle-client-typescript/SKILL.md +15 -11
- package/package/corpus/skills/cratis-chronicle-compliance/SKILL.md +14 -3
- package/package/corpus/skills/cratis-chronicle-event-constraints/SKILL.md +13 -6
- package/package/corpus/skills/cratis-chronicle-event-modeling/SKILL.md +2 -2
- package/package/corpus/skills/cratis-chronicle-event-specifications/SKILL.md +11 -5
- package/package/corpus/skills/cratis-chronicle-event-type-migration/SKILL.md +2 -2
- package/package/corpus/skills/cratis-chronicle-multi-tenancy/SKILL.md +2 -2
- package/package/corpus/skills/cratis-chronicle-projection/SKILL.md +23 -4
- package/package/corpus/skills/cratis-chronicle-projection/references/fluent-builder.md +1 -1
- package/package/corpus/skills/cratis-chronicle-projection/references/model-bound-attributes.md +2 -2
- package/package/corpus/skills/cratis-chronicle-reactor/SKILL.md +22 -8
- package/package/corpus/skills/cratis-chronicle-read-model/SKILL.md +1 -1
- package/package/corpus/skills/cratis-chronicle-read-model/references/queries.md +1 -1
- package/package/corpus/skills/cratis-chronicle-read-model-specifications/SKILL.md +3 -1
- package/package/corpus/skills/cratis-chronicle-reducer/SKILL.md +1 -1
- package/package/corpus/skills/cratis-chronicle-web-workbench/SKILL.md +19 -11
- package/package/corpus/skills/cratis-cli-terminal-workbench/SKILL.md +11 -6
- package/package/corpus/skills/cratis-code-review/SKILL.md +8 -6
- package/package/corpus/skills/cratis-components-accessibility/SKILL.md +112 -82
- package/package/corpus/skills/cratis-components-schema-editor/SKILL.md +12 -10
- package/package/corpus/skills/cratis-components-stepper-command-dialog/SKILL.md +11 -12
- package/package/corpus/skills/cratis-components-styling/SKILL.md +204 -170
- package/package/corpus/skills/cratis-components-toolbar/SKILL.md +32 -26
- package/package/corpus/skills/cratis-event-model-diagram/SKILL.md +1 -1
- package/package/corpus/skills/cratis-fundamentals-concept/SKILL.md +2 -2
- package/package/corpus/skills/cratis-fundamentals-type-discovery/SKILL.md +2 -2
- package/package/corpus/skills/cratis-lens-browser-extension/SKILL.md +4 -2
- package/package/corpus/skills/cratis-performance-review/SKILL.md +4 -2
- package/package/corpus/skills/cratis-screenplay-model-authoring/SKILL.md +15 -5
- package/package/corpus/skills/cratis-security-review/SKILL.md +20 -3
- package/package/corpus/skills/cratis-specifications-csharp/SKILL.md +3 -3
- package/package/corpus/skills/cratis-specifications-csharp/references/application-scenarios.md +12 -8
- package/package/corpus/skills/cratis-specifications-csharp/references/csharp-patterns.md +1 -1
- package/package/corpus/skills/cratis-specifications-csharp/references/integration-specs.md +7 -3
- package/package/corpus/skills/cratis-specifications-typescript/SKILL.md +6 -3
- package/package/corpus/skills/cratis-specifications-typescript/references/typescript-patterns.md +21 -4
- package/package/corpus/skills/cratis-stage-rendering-and-sandbox/SKILL.md +29 -17
- package/package.json +1 -1
- package/package/corpus/rules/terminal-commands.md +0 -19
|
@@ -69,7 +69,7 @@ Behavior that can regress without TypeScript catching it:
|
|
|
69
69
|
- Command orchestration outside `CommandDialog`: handling of unauthorized / invalid / exception / success outcomes.
|
|
70
70
|
- React component rendering only when markup, wrapper behavior, disabled state, or integration with a wrapped component is the point.
|
|
71
71
|
|
|
72
|
-
**Do not test:** generated Cratis proxies; framework/
|
|
72
|
+
**Do not test:** generated Cratis proxies; framework/Cratis Components internals; CSS pixel-perfection; trivial presentational pass-throughs with no branch/derived behavior.
|
|
73
73
|
|
|
74
74
|
## View models
|
|
75
75
|
|
|
@@ -90,9 +90,9 @@ import React from 'react';
|
|
|
90
90
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
91
91
|
import { vi } from 'vitest';
|
|
92
92
|
|
|
93
|
-
vi.mock('
|
|
94
|
-
Dialog: (props: {
|
|
95
|
-
React.createElement('div', null, props.
|
|
93
|
+
vi.mock('@cratis/components/Dialogs', () => ({
|
|
94
|
+
Dialog: (props: { buttons?: React.ReactNode; children?: React.ReactNode }) =>
|
|
95
|
+
React.createElement('div', null, props.children, props.buttons),
|
|
96
96
|
}));
|
|
97
97
|
|
|
98
98
|
describe('when rendered while busy', () => {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Cratis repositories come in **two profiles**, and the rules are scoped to them. **Identify your profile first** — it decides which rules apply.
|
|
4
4
|
|
|
5
|
-
- **Application profile (default)** — you are *building an application on Cratis*: event-sourced CQRS with **Cratis Chronicle** + **Cratis Arc**, vertical slices, read models persisted to MongoDB/EF Core, and a React + Cratis Components (
|
|
5
|
+
- **Application profile (default)** — you are *building an application on Cratis*: event-sourced CQRS with **Cratis Chronicle** + **Cratis Arc**, vertical slices, read models persisted to MongoDB/EF Core, and a React + Cratis Components (4.x) frontend in MVVM. Most of this corpus targets this profile.
|
|
6
6
|
- **Framework profile** — you are *contributing to a Cratis framework repository itself* (Arc, Chronicle, Fundamentals, Components, …). These are **libraries** — source generators, the Chronicle kernel (Orleans grains + storage), client SDKs, a React component library — **not** vertical-slice event-sourced apps. The application-architecture rules here **do not apply**; follow **[framework.md](./framework.md)**.
|
|
7
7
|
|
|
8
8
|
**How to tell:** if the repo's own package is `Cratis.*` / `@cratis/*` and it *builds* the framework, you are in the framework profile. If it *consumes* Cratis to build a product, you are in the application profile.
|
|
@@ -76,66 +76,17 @@ immediately before acting and stop only when drift invalidates the authorized sc
|
|
|
76
76
|
or recovery plan. Git history rewrites remain prohibited unless the user explicitly
|
|
77
77
|
requests one.
|
|
78
78
|
|
|
79
|
-
## New Repository Strategy Intake
|
|
80
|
-
|
|
81
|
-
When a repository is created in the Cratis organization, prepare one transient,
|
|
82
|
-
no-effect Strategy intake proposal. Do not create, comment on, assign, mention,
|
|
83
|
-
link, or otherwise mutate a GitHub issue unless a current repository profile and
|
|
84
|
-
the exact operation are separately accepted. Tool access and a request to create
|
|
85
|
-
the repository do not supply that issue-effect authority.
|
|
86
|
-
|
|
87
|
-
The proposal should include only the bounded facts needed for Strategy triage:
|
|
88
|
-
|
|
89
|
-
- repository name, URL, visibility, and creation state;
|
|
90
|
-
- purpose, intended users, lifecycle, and whether it is canonical, generated,
|
|
91
|
-
experimental, operational, or scheduled for retirement;
|
|
92
|
-
- accountable owner or explicit vacancy and cross-repository boundaries;
|
|
93
|
-
- upstream/downstream dependencies and current owning records;
|
|
94
|
-
- release, distribution, credential, security, privacy, compliance, and data
|
|
95
|
-
expectations; and
|
|
96
|
-
- requested Strategy identity, portfolio, metadata, ownership, and local AI
|
|
97
|
-
context review.
|
|
98
|
-
|
|
99
|
-
First decide whether no record, an existing Strategy record, a bounded update
|
|
100
|
-
proposal, a new proposal, or a sensitive human route is appropriate. Treat the
|
|
101
|
-
result as Strategy intake, not strategic approval. Do not invent Strategy
|
|
102
|
-
metadata or copy private Strategy content into a public repository. Repository
|
|
103
|
-
creation does not require an issue URL; unresolved Strategy reconciliation is an
|
|
104
|
-
explicit next action for the authorized human/owning process.
|
|
105
|
-
|
|
106
79
|
## Shared AI Distribution
|
|
107
80
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
retirement evidence exists.
|
|
118
|
-
|
|
119
|
-
Shared public product and `engineering-*` packages contain only public-safe
|
|
120
|
-
Cratis behavior. The `engineering-` prefix identifies the maintainer audience;
|
|
121
|
-
it does not imply confidential package contents or a private registry.
|
|
122
|
-
|
|
123
|
-
The consuming repository owns its project facts, confidential behavior, local
|
|
124
|
-
skills, and minimal host bootstraps. Use repository-owned documentation as canonical
|
|
125
|
-
project context when that migration is active, `.agents/skills/` for private or
|
|
126
|
-
repository-specific local workflows, and repository-owned documentation only as the
|
|
127
|
-
documented legacy context fallback. Never merge, overwrite, or remove these
|
|
128
|
-
local files as a side effect of installing, updating, rolling back, or
|
|
129
|
-
uninstalling shared AI capabilities.
|
|
130
|
-
|
|
131
|
-
Keep confidential and repository-specific behavior local. Generalize and remove
|
|
132
|
-
private facts before proposing a reusable improvement to `Cratis/AI`; never
|
|
133
|
-
reverse-sync a private repository's AI tree or generated adapters.
|
|
134
|
-
|
|
135
|
-
Update shared AI by changing a version pin through the approved organization or
|
|
136
|
-
host mechanism. Canary the new version, observe its behavior and gates, and roll
|
|
137
|
-
back by version when needed. Never patch generated distribution bytes or
|
|
138
|
-
marketplace wrappers by hand.
|
|
81
|
+
Two invariants; the mechanics of every channel (`cratis ai install`, `@cratis/pi`,
|
|
82
|
+
the plugin marketplaces) are in [ai-distribution.md](./ai-distribution.md):
|
|
83
|
+
|
|
84
|
+
- Never copy or synchronize `.cratis/ai`, `.agents`, `.claude`, `.github`, or `.pi`
|
|
85
|
+
trees between repositories, and never patch managed files under `.cratis/ai/`
|
|
86
|
+
by hand — update through the channel that installed them.
|
|
87
|
+
- The consuming repository owns its project facts, confidential behavior, and
|
|
88
|
+
local skills; installing, updating, or uninstalling shared AI never merges,
|
|
89
|
+
overwrites, or removes them.
|
|
139
90
|
|
|
140
91
|
## Verification Discipline
|
|
141
92
|
|
|
@@ -144,98 +95,13 @@ A claim is only as good as the signal behind it — a build result, a test run,
|
|
|
144
95
|
- **Confirm "done"/"fixed"/"correct" against a fresh signal — never self-assessment.** Run the relevant gate and observe it pass *this time*.
|
|
145
96
|
- **After a fix, re-run the gate that failed.** Don't argue yourself to green.
|
|
146
97
|
- **A green build is not behavioral correctness.** Compilation proves it builds, not that the slice does the right thing — that's what specs and exercising the UI are for.
|
|
147
|
-
- **Report
|
|
98
|
+
- **Report the conclusion and what you didn't verify, in a line or two.** Show the output when asked, when a claim is contested, or when the check failed — see [verification-discipline.md](./verification-discipline.md).
|
|
148
99
|
|
|
149
100
|
---
|
|
150
101
|
|
|
151
102
|
# Application profile
|
|
152
103
|
|
|
153
|
-
>
|
|
154
|
-
|
|
155
|
-
## Project Layout (Cratis Application convention)
|
|
156
|
-
|
|
157
|
-
The framework discovers commands and read models by attributes and static methods — **the folder shape is a convention, not a requirement.** The house default keeps everything for one behavior together, with **no top-level `Features/` wrapper**: the domain hierarchy lives directly under the app source root.
|
|
158
|
-
|
|
159
|
-
```
|
|
160
|
-
<AppSourceRoot>/ e.g. Source, Source/Core (app-defined)
|
|
161
|
-
├── Common/ shared ConceptAs<T> / EventSourceId<T> types
|
|
162
|
-
├── Identity/ Components/ ... cross-cutting / shared concerns, at the top level
|
|
163
|
-
└── <Module>/ top-level domain area — natural for most apps, NOT required
|
|
164
|
-
└── <Feature>/ grouping within the domain area
|
|
165
|
-
├── <Feature>.tsx pass-through layout (renders <Outlet/>)
|
|
166
|
-
├── <Concept>.cs feature-level concept types
|
|
167
|
-
└── <Slice>/ one folder per behavior — the invariant unit
|
|
168
|
-
├── <Slice>.cs backend artifacts for the slice in one file
|
|
169
|
-
├── *.tsx React component(s) for the slice
|
|
170
|
-
└── when_*/ spec folders
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
**The slice is the invariant unit** — one behavior (command + events + projection + component + specs), created/renamed/deleted together. Features group related slices. A `<Module>` is the natural top-level domain grouping for larger areas (e.g. `Accounts`, `Admin`, `Requests`) but is **not required** — a feature may sit directly under the source root when no module grouping is natural; depth follows what fits the application. Cross-cutting concerns (shared concepts in `Common/`, shared components, identity) live at the top level. Namespace mirrors the path under `<AppSourceRoot>` (`<RootNamespace>.<Module>.<Feature>.<Slice>`, dropping any level that isn't present). Splitting the backend file is allowed when a slice grows large or shared concepts move upward; the single-file shape is the default, not a mandate.
|
|
174
|
-
|
|
175
|
-
> **No top-level `Features/` wrapper** — modules/features live directly under the app source root. The framework enforces no layout; this nested domain hierarchy is the chosen Cratis Application convention.
|
|
176
|
-
|
|
177
|
-
## Slice Types
|
|
178
|
-
|
|
179
|
-
Pick exactly one type per slice folder — determined by what the slice *does*.
|
|
180
|
-
|
|
181
|
-
| Type | What it does | Contents |
|
|
182
|
-
| --- | --- | --- |
|
|
183
|
-
| **State Change** | Accepts a command, appends events | Command + validator + event(s); optional `[Passive]` read model for command-side decisions |
|
|
184
|
-
| **State View** | Projects events into a queryable read model | `[ReadModel]` + model-bound projection + static query method(s) |
|
|
185
|
-
| **Automation** | Reacts to events, calls external systems / `ICommandPipeline` | Reactor only |
|
|
186
|
-
| **Translation** | Reacts to events and appends follow-up events to another stream | Reactor only |
|
|
187
|
-
|
|
188
|
-
## Slice Naming (convention)
|
|
189
|
-
|
|
190
|
-
Commands are imperative intents (`Register`, `Create`); the slice folder is the action only, never repeating a noun the Feature already establishes. **`[EventType]` records are past-tense facts** and must be self-describing (`AuthorRegistered`, never `Created`) — this past-tense, one-purpose naming is a Chronicle framework recommendation. Static query methods are descriptive reads (`AllAuthors`, `AuthorById`, `AuthorsByName`).
|
|
191
|
-
|
|
192
|
-
## Rules
|
|
193
|
-
|
|
194
|
-
Tagged **[contract]** (framework-enforced) or **[convention]** (house default). Mechanics and examples live in `vertical-slices.md`.
|
|
195
|
-
|
|
196
|
-
1. **[contract] Model-bound — no controllers.** Commands are `[Command]` records with a public instance `Handle()` (Arc analyzers enforce this); queries are `static` methods on `[ReadModel]` records; projections/constraints/authorization use attributes. Arc generates the HTTP surface. Drop to fluent (`IProjectionFor<T>`, `IConstraint`) only when model-bound can't express the rule.
|
|
197
|
-
2. **[contract] Command validation & data flow.** Put command rejection in `CommandValidator<T>`, global value invariants in `ConceptValidator<T>`, and fetched/computed handler data in **`Provide()`** (runs after validation/authorization; may short-circuit with `ValidationResult.Error` / `Result<TProvided, ValidationResult>`). Keep `Handle()` focused on event construction. For a state-dependent rule that must hold **under concurrency**, inject the read model into `Handle()` and return a typed error via `Result<TEvent, ValidationResult>`. **Throwing from `Provide()`/`Handle()` is an exception (HTTP 500), not a validation rejection** — throw only for genuinely exceptional conditions, never for normal business rejection.
|
|
198
|
-
3. **[contract] Event-source id resolution order:** `ICanProvideEventSourceId` → an `EventSourceId`/`EventSourceId<T>`-derived value → `[Key]` → else Arc/Chronicle generates one. A value actually used as a Chronicle stream identity derives from `EventSourceId<T>` with the underlying `IComparable` primitive — never `ConceptAs<Guid>` for that stream identity. `NotSet`, `New()`, and primitive→derived-id operators are optional domain/API conveniences, not Chronicle requirements; typed empty/zero values are real specified stream IDs, not `EventSourceId.Unspecified`.
|
|
199
|
-
4. **[contract] Events never carry the event-source id** — it is implicit in the event context.
|
|
200
|
-
5. **[contract] `[Key]` / `[Subject]` are distinct.** `[Key]` is for event-source/read-model/projection key resolution; `[Subject]` is compliance identity only. Don't put either on an `EventSourceId<T>` value (it already is both); use them only for non-`EventSourceId<T>` values.
|
|
201
|
-
6. **[contract] Avoid nullable event properties** — Chronicle's analyzer warns on them. Model optional facts as a separate event; resolve nullable command inputs to a non-null sentinel before constructing the event.
|
|
202
|
-
7. **[contract] `[EventType]` takes no arguments for new events** — the type name is the identifier. Use `generation:`/id only when evolving an existing contract; schema changes get a new generation + an `EventTypeMigration<TUpgrade,TPrevious>` (never edit stored-event semantics silently). An enum that only gains a member, or has one renamed, is the exception — Chronicle accepts that in place; a *removed* or *renumbered* member still needs a generation, plus a value map saying what the old values became.
|
|
203
|
-
8. **[convention] Every `[EventType]` has an XML `<summary>`** — a Cratis C# documentation convention (not a Chronicle rule); events live in the log forever, so record why they exist.
|
|
204
|
-
9. **[convention] `[ReadModel]` properties carry no default values** except semantically meaningful enum initial states and `[SetValue<T>]`-driven `bool` flags. False defaults hide missing projection wiring.
|
|
205
|
-
10. **[contract] AutoMap is on by default — never call `.AutoMap()`.** Match property names so AutoMap wires them; diverge with `[SetFrom<T>]` / fluent `.Set().To()` only for genuine name differences. Re-enable `.AutoMap()` only inside a scope where it was disabled with `.NoAutoMap()`.
|
|
206
|
-
11. **[contract] Projections consume events and event context — never other read models.** Default to model-bound attributes; use fluent `IProjectionFor<T>` for joins/nested/context/transforms; use a reducer when the model is "current state + event → next state" (a valid style, not a failure mode).
|
|
207
|
-
12. **[contract] Model-bound query custom paths use `[Path("...")]`** (`PathAttribute`), not ASP.NET `[Route]`. Reserve `[Route]` for controller-based endpoints (which this convention avoids).
|
|
208
|
-
13. **[convention] Cross-slice access is read-only through Chronicle** — inject another slice's read model or reference its events; never instantiate or DI another slice's command/handler/service.
|
|
209
|
-
14. **[contract] Never inject `IEventLog` into `Handle()`** — express appends through return types (`IEnumerable<object>` with `EventForEventSourceId` wrappers for cross-stream). In application reactors, return side-effect events or use `ICommandPipeline`; don't reach for `IEventLog` directly.
|
|
210
|
-
15. **[contract] Never edit a generated file** — proxies carry a `// @generated by Cratis` header. Fix the C# source and rebuild.
|
|
211
|
-
16. **[convention] Use the Cratis dialog wrappers** — never import `Dialog` from `primereact/dialog`; use `CommandDialog` / `Dialog` from `@cratis/components`. The default frontend stack is Cratis Components on PrimeReact theming/tokens/`pt` — **not** Tailwind (Tailwind is one supported unstyled path, not the generic default).
|
|
212
|
-
17. **[convention] One slice is one unit** — creating/renaming/moving/deleting a slice means doing the same to every artifact (the `.cs`, every `when_*/`, every `.tsx`, the composition import/JSX, the route).
|
|
213
|
-
|
|
214
|
-
## Implementation Workflow
|
|
215
|
-
|
|
216
|
-
- **Phase 0 — Model the request.** Confirm Module/Feature, Slice name, slice type, domain rules. For new behavior or unclear event vocabulary, run the **event-modeling** skill before writing code.
|
|
217
|
-
- **Phase 1 — Backend.** Implement a coherent slice change. **Gate:** incrementally build the affected Debug project to regenerate proxies and compile spec code; add Release verification when required for cross-cutting or merge/release gates (see the proxy-generation note below).
|
|
218
|
-
- **Phase 2 — Specs.** Mandatory for every slice type, in-process scenario family first: `CommandScenario<T>` (commands), `EventScenario` (constraints/append), `ReadModelScenario<T>` (projections/reducers), `ReactorScenario<T>` (reactors). Reserve out-of-process integration specs for host/infra/transport boundaries. **Gate:** tests pass.
|
|
219
|
-
- **Phase 3 — Frontend.** Proxies now exist. Build React components from generated proxies, register in the composition page, wire routing. **Gate:** lint, conditional test, and build all clean.
|
|
220
|
-
|
|
221
|
-
**Backend before frontend, always** — the frontend depends on proxies that only exist after a successful Debug build. After a coherent set of changes, incrementally build/compile the affected project and run targeted regression checks before proceeding; do not build after every file.
|
|
222
|
-
|
|
223
|
-
**Proxy generation runs on Debug, not Release.** `dotnet build -c Debug` is the canonical trigger for regenerating TypeScript proxies — it carries the fullest, most reliably-emitted PDB debug information the proxy generator relies on to place generated files. Generate proxies with a Debug build first; when you (or an agent) subsequently build Release purely to verify the app compiles in that configuration, skip proxy regeneration so the second build can't re-run the generator against a different compilation and touch already-correct generated files: `dotnet build -c Release -p:CratisProxiesOutputPath=`. The empty override clears the output path property the generator's MSBuild target is conditioned on, so the target no-ops for that invocation — no generated file is read or written.
|
|
224
|
-
|
|
225
|
-
## Quality Gates
|
|
226
|
-
|
|
227
|
-
| Phase | Command (app-pinned) | Pass criteria |
|
|
228
|
-
| --- | --- | --- |
|
|
229
|
-
| Backend | build (Debug) | zero errors, zero warnings — validates `#if DEBUG` spec code and regenerates proxies |
|
|
230
|
-
| Backend | build (Release) | zero errors, zero warnings — build-only check; pass `-p:CratisProxiesOutputPath=` to skip re-running proxy generation |
|
|
231
|
-
| Specs | test | zero failures |
|
|
232
|
-
| Frontend | lint | zero errors |
|
|
233
|
-
| Frontend | test | zero failures when frontend specs/behavior changed |
|
|
234
|
-
| Frontend | build | zero errors |
|
|
235
|
-
|
|
236
|
-
Run affected-project incremental checks after a coherent change, then targeted regression tests for the changed behavior. Re-run a failed gate after a relevant fix. Reserve wider matrices and clean/Release builds for cross-cutting changes, demonstrated stale outputs, or required merge/release gates. Documentation/rule-only edits need relevant Markdown, frontmatter, link, and corpus checks, not an application build. Diagnose unrelated or environmental failures within a bounded attempt; report the evidence and blocker instead of broadening scope or retrying indefinitely. Required gates remain blocking until satisfied; never silently waive red CI.
|
|
237
|
-
|
|
238
|
-
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. See [pull-requests.md](./pull-requests.md).
|
|
104
|
+
> **Building an application on Cratis?** Project layout, slice types, slice naming, the seventeen slice rules, the implementation workflow and the application quality gates are in [application-profile.md](./application-profile.md), which loads only for repositories that select an application profile. If you are contributing to a Cratis framework repo, see **Framework profile** below.
|
|
239
105
|
|
|
240
106
|
---
|
|
241
107
|
|
|
@@ -258,7 +124,7 @@ Documentation-only changes use repository-supported non-release intent, ordinari
|
|
|
258
124
|
| For | Location |
|
|
259
125
|
| --- | --- |
|
|
260
126
|
| **AI Corpus Profiles** — what profiles exist and how to configure them | `profiles.md` |
|
|
261
|
-
| **Contributing to a Cratis framework repo** (framework profile) | `framework.md` |
|
|
127
|
+
| **Contributing to a Cratis framework repo** (framework profile) | `framework.md`; creating a repository in the Cratis organization: `new-repository-intake.md` |
|
|
262
128
|
| Slice anatomy (commands, `Provide()`, validators, events, projections, read models, reactors, constraints, compliance, cross-slice) | `vertical-slices.md` |
|
|
263
129
|
| C# / TypeScript style | `csharp.md`, `typescript.md` |
|
|
264
130
|
| Service lifetimes — why anything taking a scoped dependency is scoped or transient, never a singleton | `csharp.md` |
|
|
@@ -268,18 +134,18 @@ Documentation-only changes use repository-supported non-release intent, ordinari
|
|
|
268
134
|
| Spec patterns — the four `*Scenario` helpers (application only) | `specs.scenarios.csharp.md` |
|
|
269
135
|
| Strongly-typed values (`ConceptAs<T>`, `EventSourceId<T>`) | `concepts.md` |
|
|
270
136
|
| Shared term definitions (event, projection, reducer, reactor, observer, DCB, …) | `glossary.md` |
|
|
271
|
-
| Diagnosing a misbehaving slice
|
|
272
|
-
| Inspecting or operating a **running** Chronicle store (failed partitions, replays, browsing events) with the `cratis` CLI | the **
|
|
137
|
+
| Diagnosing a misbehaving slice | read model stale or startup crash: **cratis-chronicle-projection** (*Startup-crash traps*); reactor paused or quarantined: **cratis-chronicle-reactor** (*Failure behavior*); proxy missing: **cratis-arc-command** (*Generate the TypeScript proxy*) |
|
|
138
|
+
| Inspecting or operating a **running** Chronicle store (failed partitions, replays, browsing events) with the `cratis` CLI | the **cratis-chronicle-cli-operations** and **cratis-cli-terminal-workbench** skills |
|
|
273
139
|
| EF Core read models / migrations | `efcore.md`, `efcore.specs.md` |
|
|
274
140
|
| PRs / commits | `pull-requests.md`, `git-commits.md` |
|
|
275
|
-
| Reading, citing and superseding a decision record |
|
|
141
|
+
| Reading, citing and superseding a decision record | the **cratis-engineering-decision-record** skill |
|
|
276
142
|
| Whether you are allowed to do the thing you are able to do | `capability-is-not-authority.md` |
|
|
277
|
-
| What must stop and ask a human | `
|
|
143
|
+
| What must stop and ask a human | `capability-is-not-authority.md` (absent authority for a consequential effect: stop and ask) |
|
|
278
144
|
| What counts as evidence that something works | `verification-discipline.md` |
|
|
279
|
-
|
|
|
145
|
+
| Where session notes, plans and handovers may live | `local-work-artifacts.md` |
|
|
280
146
|
| Exit-code meaning and wrappers that lose a verdict | `exit-codes-and-wrappers.md` |
|
|
281
147
|
| Writing a scan, allowlist or destructive pass that cannot pass vacuously | `guards-and-fuses.md` |
|
|
282
|
-
|
|
|
148
|
+
| How the shared corpus is installed, updated and rolled back | `ai-distribution.md` |
|
|
283
149
|
| Event modeling / schema migration / calling commands from code / paging / cross-cutting metadata / multi-tenancy | the matching skills |
|
|
284
150
|
| Step-by-step recipes | `.cratis/ai/skills/` |
|
|
285
151
|
|
|
@@ -289,7 +155,7 @@ Documentation-only changes use repository-supported non-release intent, ordinari
|
|
|
289
155
|
- **Skills and rules are the authoritative answer.** If not answered there, ask. Don't infer Cratis behavior from package internals.
|
|
290
156
|
- Only make high-confidence suggestions.
|
|
291
157
|
- Don't change dependency manifests / lockfiles / `global.json` / NuGet config unless explicitly asked.
|
|
292
|
-
- When asked to commit, push,
|
|
158
|
+
- When asked to **ship** or **land** changes, use the **ship-changes** prompt (`.cratis/ai/prompts/ship-changes.prompt.md`); invoking it is what authorizes the branch → commits → push → PR → merge → cleanup chain it describes. A request to only commit, only push, or only open a PR authorizes exactly that step, under [Git commits](./git-commits.md) and [Pull requests](./pull-requests.md) — do not route it through ship-changes and do not add the later steps.
|
|
293
159
|
|
|
294
160
|
## General
|
|
295
161
|
|
|
@@ -305,11 +171,4 @@ Documentation-only changes use repository-supported non-release intent, ordinari
|
|
|
305
171
|
|
|
306
172
|
## Local AI work artifacts — `.ai-work/` only
|
|
307
173
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
- Create every such artifact inside **`.ai-work/`** at the repository root — never at the repository root itself, never under documentation folders, never anywhere else.
|
|
311
|
-
- `.ai-work/` is gitignored and must stay untracked. Never commit anything inside it, never `git add -f` anything inside it, and never remove the ignore entry.
|
|
312
|
-
- These artifacts must never enter git history or reach GitHub — not on any branch. If you find an unrelated tracked work record, report its path and obtain explicit authorization before moving it into `.ai-work/`, removing it from tracking, or making a dedicated cleanup commit. Discovery alone does not authorize unrelated changes or a commit.
|
|
313
|
-
- A genuine follow-up that must survive the session is **not** a work record — suggest opening a GitHub issue for it (or open one when asked) so future work is tracked where everyone can see it, instead of leaving a planning file behind.
|
|
314
|
-
- Knowledge that must outlive the session belongs in the repository's documentation structure through normal review, not in a work record.
|
|
315
|
-
- **A decision log is not a work record.** A decision — a durable choice with a decider and a date — is documentation: it lives in **`decisions/`** (or the repository's documented decisions folder) and is reviewed like any other documentation. A handover may summarize decisions; it never holds the only copy.
|
|
174
|
+
Plans, handovers, session notes, scratch analyses and research dumps are **work records, not documentation**: they live only in the gitignored `.ai-work/` at the repository root, never enter git history, and are never the only copy of a durable decision (those go in `decisions/`). A follow-up that must outlive the session is an issue, not a file. Full rule: [local-work-artifacts.md](./local-work-artifacts.md).
|
|
@@ -8,6 +8,11 @@ A guard that cannot fail is worse than no guard: it converts "nobody looked" int
|
|
|
8
8
|
check. Every line is tagged **[contract]** (binding) or **[convention]** (the house
|
|
9
9
|
default) per the Three Levels of Authority in [`general.md`](./general.md).
|
|
10
10
|
|
|
11
|
+
**Scope.** This rule governs two situations: *authoring* a scanner, guard, allowlist or
|
|
12
|
+
checker, and *running a pass* that mutates a computed set of subjects rather than the
|
|
13
|
+
targets the request named. An ordinary edit, review, fix, or a change to the one file the
|
|
14
|
+
user pointed at is outside it and carries none of the machinery below.
|
|
15
|
+
|
|
11
16
|
## Non-vacuity
|
|
12
17
|
|
|
13
18
|
- **[contract] A scan over a possibly-empty population carries a non-vacuity check.**
|
|
@@ -28,19 +33,28 @@ default) per the Three Levels of Authority in [`general.md`](./general.md).
|
|
|
28
33
|
- **[convention] Prefer an expiry to a permanent exemption.** An entry nobody revisits is
|
|
29
34
|
a rule quietly deleted.
|
|
30
35
|
|
|
31
|
-
##
|
|
36
|
+
## Bulk and irreversible passes
|
|
37
|
+
|
|
38
|
+
These contracts are keyed to the *risk class* of the effect — bulk deletion, history
|
|
39
|
+
rewriting, cross-repository migration, anything irreversible or high-fanout — not to
|
|
40
|
+
whether a person is watching. An autonomous session runs them exactly as an interactive
|
|
41
|
+
one does.
|
|
32
42
|
|
|
33
43
|
- **[contract] Distinguish "subject set empty" from "qualifying set empty".** Finding no
|
|
34
44
|
candidates at all is a different situation from finding candidates that none qualified;
|
|
35
|
-
|
|
36
|
-
- **[contract] Every
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
a pass must refuse to proceed on the first.
|
|
46
|
+
- **[contract] Every pass carries a per-pass fuse** — a maximum number of subjects it may
|
|
47
|
+
act on in one run, which stops the run rather than trimming the work silently.
|
|
48
|
+
- **[contract] Know the recovery before the forward action.** For a reversible effect,
|
|
49
|
+
know how it is undone. For an irreversible one, preserve what the recovery needs first —
|
|
50
|
+
a backup ref, a copy under `.ai-work/keep/`, the list of targets — and stop if nothing
|
|
51
|
+
can be preserved and the request did not name the targets. Do not demand an exact
|
|
52
|
+
inverse where none can exist.
|
|
41
53
|
- **[contract] Re-read preconditions immediately before each mutation and stop when drift
|
|
42
54
|
invalidates the authorized scope, safety assumptions, or recovery plan.** Benign drift
|
|
43
55
|
within an already authorized bounded pass does not require another confirmation.
|
|
44
|
-
- **[convention]
|
|
45
|
-
|
|
46
|
-
and authorized those targets is not asked to approve the same pass again.
|
|
56
|
+
- **[convention] When the targets were not already established in the conversation, show
|
|
57
|
+
the dry-run list and act on it once the user has answered.** A user who has reviewed
|
|
58
|
+
and authorized those targets is not asked to approve the same pass again. The list is a
|
|
59
|
+
message in the conversation, not a retained artifact: do not write receipts, ledgers,
|
|
60
|
+
snapshots or escrow copies of it.
|
|
@@ -21,6 +21,12 @@ dumps, and similar coordination files. These are **work records, not documentati
|
|
|
21
21
|
- Knowledge that must outlive the session (real documentation, ADRs, operator
|
|
22
22
|
guides) is written deliberately into the repository's documentation structure
|
|
23
23
|
through normal review — not left behind as a work record.
|
|
24
|
+
- **A worktree is a work record too.** Remove the worktree you created for a task
|
|
25
|
+
once its branch is merged to the default branch — pushed is not merged. Do it from
|
|
26
|
+
the parent checkout, after `git status --ignored` in the worktree, because a nested
|
|
27
|
+
`.ai-work/`, `.env` files and local databases are ignored and are deleted with it.
|
|
28
|
+
If the branch must outlive the task unmerged, leave the worktree and say so in the
|
|
29
|
+
handoff. Never `--force` a removal; a refusal means look, not push harder.
|
|
24
30
|
- **A decision log is not a work record.** A decision — a durable choice with a
|
|
25
31
|
decider and a date — is documentation: it lives in **`decisions/`** (or the
|
|
26
32
|
repository's documented decisions folder) and is reviewed like any other
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "**/*"
|
|
3
|
+
profile: framework
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# New Repository Strategy Intake
|
|
7
|
+
|
|
8
|
+
This is Cratis organization process for maintainers creating repositories under the
|
|
9
|
+
Cratis organization. It does not apply to applications built on Cratis.
|
|
10
|
+
|
|
11
|
+
When a repository is created in the Cratis organization, prepare one transient,
|
|
12
|
+
no-effect Strategy intake proposal. Do not create, comment on, assign, mention,
|
|
13
|
+
link, or otherwise mutate a GitHub issue unless a current repository profile and
|
|
14
|
+
the exact operation are separately accepted. Tool access and a request to create
|
|
15
|
+
the repository do not supply that issue-effect authority.
|
|
16
|
+
|
|
17
|
+
The proposal should include only the bounded facts needed for Strategy triage:
|
|
18
|
+
|
|
19
|
+
- repository name, URL, visibility, and creation state;
|
|
20
|
+
- purpose, intended users, lifecycle, and whether it is canonical, generated,
|
|
21
|
+
experimental, operational, or scheduled for retirement;
|
|
22
|
+
- accountable owner or explicit vacancy and cross-repository boundaries;
|
|
23
|
+
- upstream/downstream dependencies and current owning records;
|
|
24
|
+
- release, distribution, credential, security, privacy, compliance, and data
|
|
25
|
+
expectations; and
|
|
26
|
+
- requested Strategy identity, portfolio, metadata, ownership, and local AI
|
|
27
|
+
context review.
|
|
28
|
+
|
|
29
|
+
First decide whether no record, an existing Strategy record, a bounded update
|
|
30
|
+
proposal, a new proposal, or a sensitive human route is appropriate. Treat the
|
|
31
|
+
result as Strategy intake, not strategic approval. Do not invent Strategy
|
|
32
|
+
metadata or copy private Strategy content into a public repository. Repository
|
|
33
|
+
creation does not require an issue URL; unresolved Strategy reconciliation is an
|
|
34
|
+
explicit next action for the authorized human/owning process.
|
|
@@ -10,41 +10,27 @@ paths:
|
|
|
10
10
|
> **⚠️ APPLIES ONLY TO PROJECTS USING MICROSOFT ORLEANS**
|
|
11
11
|
> If your project does not reference `Microsoft.Orleans` or any Orleans packages, **ignore this entire file**. These rules are irrelevant outside of Orleans contexts.
|
|
12
12
|
|
|
13
|
-
These conventions
|
|
13
|
+
In the Cratis organization, Orleans is the Chronicle kernel's actor runtime (`Source/Kernel/`). These conventions describe how that kernel is shaped, verified against `Cratis/Chronicle` **v18.3.0**; follow them in any Cratis repository that hosts Orleans grains.
|
|
14
14
|
|
|
15
15
|
## General
|
|
16
16
|
|
|
17
|
-
- Do **not** split
|
|
18
|
-
-
|
|
19
|
-
- Do **not** add `[Alias]`
|
|
17
|
+
- Do **not** split grain interfaces into a separate project. Grain interfaces and their implementations live together in `Kernel/Core`; the `Contracts` project holds the gRPC contracts, not grain interfaces.
|
|
18
|
+
- Clustering is chosen by the host, not the grain code: the server uses `UseLocalhostClustering(siloPort, gatewayPort, serviceId, clusterId)` for local development and the MongoDB storage integration switches on `UseMongoDBClustering` (`MongoDBMembershipStrategy.Multiple`) when clustering is enabled. A silo pointed at non-local shared storage while still on localhost clustering is logged as a **warning** at startup (each such node forms its own single-node cluster over the same data); `Cratis__Chronicle__Clustering__Type=MongoDB` on every node is what joins them.
|
|
19
|
+
- Do **not** add `[Alias]` to grain interfaces or grain methods — rely on the default alias Orleans generates. `[Alias(nameof(Type))]` is used only on `[GenerateSerializer]` **state and definition records** in `Kernel/Concepts` (`ReadModelDefinition`, `IndexDefinition`, `ReadModelChangeContext`, `ProjectionFuture`), where a stable serialization alias must survive a rename.
|
|
20
20
|
|
|
21
|
-
##
|
|
21
|
+
## Grain storage providers
|
|
22
22
|
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
23
|
+
- Storage provider names are constants in `WellKnownGrainStorageProviders` (`Kernel/Core`), **one name per concern** — 25 of them at v18.3.0 (`Namespaces`, `EventSequences`, `ObserverDefinitions`, `ObserverState`, `FailedPartitions`, `Jobs`, `JobSteps`, `Recommendations`, `Projections`, `ProjectionsManager`, `ProjectionFutures`, `Reactors`, `Reducers`, `ReducersManager`, `ReadModels`, `ReadModelsManager`, `ReadModelReplayManager`, `Constraints`, `EventStoreSubscriptionsManager`, `EventSeeding`, `Webhooks`, `WebhooksManager`, `DataProtectionKeys`, `PatchManager`, `System`). Reference a provider only through its constant — never a magic string, and there is no "default" provider.
|
|
24
|
+
- Each concern has its own `IGrainStorage` implementation (`JobGrainStorageProvider`, `EventSequencesStorageProvider`, `NamespacesStateStorageProvider`, …) taking the kernel's `IStorage` abstraction — **not** a MongoDB collection. The storage backend (MongoDB, SQL, in-memory) is selected behind `IStorage`, so a grain storage provider never references a database driver.
|
|
25
|
+
- Every provider is registered once, in `StorageProviderExtensions.AddStorageProviders(this ISiloBuilder)`, as a keyed singleton wrapped in `ResilientGrainStorage` (a Polly resilience pipeline configured from `Chronicle:ResilientStorage`). A new concern adds a constant, an `IGrainStorage` over `IStorage`, and one `AddKeyedSingleton(WellKnownGrainStorageProviders.<Name>, CreateResilientStorageFor<…>)` line there — nothing else.
|
|
26
|
+
- Grains declare their state with `[PersistentState(nameof(<StateType>), WellKnownGrainStorageProviders.<Name>)]`.
|
|
26
27
|
|
|
27
28
|
```csharp
|
|
28
|
-
public
|
|
29
|
+
public class JobGrainStorageProvider(IStorage storage) : IGrainStorage
|
|
29
30
|
{
|
|
30
|
-
|
|
31
|
+
// ReadStateAsync / WriteStateAsync / ClearStateAsync over
|
|
32
|
+
// storage.GetEventStore(key.EventStore).GetNamespace(key.Namespace).Jobs
|
|
31
33
|
}
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
- Annotate grains with `[StorageProvider(ProviderName = WellKnownStorageProviders.Default)]` when they need persistent state.
|
|
35
|
-
|
|
36
|
-
## MongoDB Grain Storage
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
- Register it as a keyed singleton in the Orleans silo builder:
|
|
40
|
-
|
|
41
|
-
```csharp
|
|
42
|
-
builder.Host.UseOrleans(siloBuilder =>
|
|
43
|
-
{
|
|
44
|
-
siloBuilder.UseLocalhostClustering();
|
|
45
|
-
siloBuilder.Services.AddKeyedSingleton<IGrainStorage>(
|
|
46
|
-
WellKnownStorageProviders.Default,
|
|
47
|
-
(sp, _) => new MongoDbGrainStorage<MyState>(
|
|
48
|
-
sp.GetRequiredService<IMongoCollection<MyState>>()));
|
|
49
|
-
});
|
|
35
|
+
services.AddKeyedSingleton(WellKnownGrainStorageProviders.Jobs, CreateResilientStorageFor<JobGrainStorageProvider>);
|
|
50
36
|
```
|
|
@@ -8,7 +8,7 @@ paths:
|
|
|
8
8
|
|
|
9
9
|
# React + Arc + Cratis Components
|
|
10
10
|
|
|
11
|
-
The frontend is React + TypeScript in MVVM style, composed from **Cratis Components** on top of **Arc-generated proxies**. Cratis Components is
|
|
11
|
+
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.md); for dialogs see [dialogs.md](./dialogs.md). This rule covers MVVM, queries, and commands.
|
|
12
12
|
|
|
13
13
|
## The proxy boundary
|
|
14
14
|
|
|
@@ -81,7 +81,7 @@ Extract as soon as a component has **any** of: 3+ `useState`, any `useCallback`
|
|
|
81
81
|
|
|
82
82
|
These are registered automatically by `withViewModel` and injected via the primary constructor. Concrete usage:
|
|
83
83
|
|
|
84
|
-
- **`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: `publish()`
|
|
84
|
+
- **`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.
|
|
85
85
|
- **`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`.
|
|
86
86
|
- **`IIdentityProvider`** — `await _identityProvider.getCurrent<TDetails>()`. Use the `useIdentity()` hook only in functional components without a view model.
|
|
87
87
|
- **`ILocalStorage`** — `setItem(key, value)` / `getItem(key)`; **`IViewModelDetached.detached()`** runs on unmount (`detached() { this._subscription?.unsubscribe(); }`).
|
|
@@ -94,7 +94,7 @@ A typed proxy class is generated per query. Pick the hook:
|
|
|
94
94
|
|
|
95
95
|
| Hook | When |
|
|
96
96
|
|---|---|
|
|
97
|
-
| `<Query>.use(args?, sorting?)` | standard — returns `[result]`, re-renders on change |
|
|
97
|
+
| `<Query>.use(args?, sorting?)` | standard — returns `[result, perform, setSorting]`, re-renders on change |
|
|
98
98
|
| `<Query>.useSuspense(...)` | suspense-aware — throws while loading; wrap in `<QueryBoundary>` |
|
|
99
99
|
| `<Query>.when(condition).use(args)` | conditional — only fires when `condition` is true (don't wrap a hook in `if`) |
|
|
100
100
|
| `<Query>.useWithPaging(pageSize, args?, sorting?)` | server-side paging (backend returns `IQueryable<T>`) |
|
|
@@ -106,12 +106,12 @@ A typed proxy class is generated per query. Pick the hook:
|
|
|
106
106
|
|
|
107
107
|
| Hook | Returns |
|
|
108
108
|
|---|---|
|
|
109
|
-
| `<Query>.use(args?, sorting?)` | `[result, perform]` |
|
|
109
|
+
| `<Query>.use(args?, sorting?)` | `[result, perform, setSorting]` |
|
|
110
110
|
| `<Query>.useSuspense(...)` | `[result, perform, setSorting]` |
|
|
111
111
|
| `<Query>.useWithPaging(pageSize, args?, sorting?)` | `[result, perform, setSorting, setPage, setPageSize]` |
|
|
112
112
|
| `<Query>.useSuspenseWithPaging(pageSize, ...)` | `[result, perform, setSorting, setPage, setPageSize]` |
|
|
113
|
-
| `<ObservableQuery>.use(...)` | `[result, setSorting]` — **no `perform`** |
|
|
114
|
-
| `<ObservableQuery>.useChangeStream(args?, getKey?, sorting
|
|
113
|
+
| `<ObservableQuery>.use(...)` | `[result, setSorting]` for an enumerable query; `[result]` for a single-instance one — **no `perform`** |
|
|
114
|
+
| `<ObservableQuery>.useChangeStream(args?, getKey?, sorting?)` | `ChangeSet<T> { added, replaced, removed }` |
|
|
115
115
|
|
|
116
116
|
`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.
|
|
117
117
|
|
|
@@ -141,7 +141,7 @@ Inside dialogs, use `CommandDialog` + CommandForm fields rather than driving the
|
|
|
141
141
|
| `isValid` | validation failed — `validationResults` has per-field messages | render inline field errors |
|
|
142
142
|
| `hasExceptions` | `Provide()`/`Handle()` threw — `exceptionMessages` carry diagnostics | generic error toast + log; never show stack traces |
|
|
143
143
|
|
|
144
|
-
`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 `{
|
|
144
|
+
`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.
|
|
145
145
|
|
|
146
146
|
Out-of-dialog execution branches in this order:
|
|
147
147
|
|
|
@@ -157,11 +157,11 @@ if (result.hasExceptions) { toast.error('Something went wrong'); console.error(r
|
|
|
157
157
|
|
|
158
158
|
### Command helpers
|
|
159
159
|
|
|
160
|
-
- **`useCommandInstance(
|
|
160
|
+
- **`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).
|
|
161
161
|
- **`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`).
|
|
162
162
|
- **`initialValues`** = synchronous baseline (also drives change tracking); **`currentValues`** = reactive overlay for async/late-loading values (e.g. from a query).
|
|
163
163
|
- **`asCommandFormField<P>(Component, opts)`** — wrap a custom input (rich text, address picker) so it participates in `CommandForm` with validation/error wiring.
|
|
164
|
-
- **`CommandScope`** + `useCommandScope()` (`@cratis/arc.react/commands`) — aggregate state across multiple commands (and queries) on one screen;
|
|
164
|
+
- **`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`.
|
|
165
165
|
|
|
166
166
|
### `<Arc>` global configuration
|
|
167
167
|
|
|
@@ -175,7 +175,7 @@ Top-level provider for microservice name and API base path. Config props (with d
|
|
|
175
175
|
| `queryConnectionCount` | `1` | hub connection slots |
|
|
176
176
|
| `queryDirectMode` | `false` | direct per-query connection — dev use only |
|
|
177
177
|
|
|
178
|
-
`QueryTransportMethod
|
|
178
|
+
`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.
|
|
179
179
|
|
|
180
180
|
## Composition
|
|
181
181
|
|
|
@@ -185,11 +185,11 @@ Top-level provider for microservice name and API base path. Config props (with d
|
|
|
185
185
|
|
|
186
186
|
## Styling default
|
|
187
187
|
|
|
188
|
-
Default to **Cratis Components
|
|
188
|
+
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.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.
|
|
189
189
|
|
|
190
190
|
## See also
|
|
191
191
|
|
|
192
|
-
- [components.md](./components.md) — component structure, CSS,
|
|
192
|
+
- [components.md](./components.md) — component structure, CSS, `--cratis-*` tokens and parts, icons, Storybook.
|
|
193
193
|
- [dialogs.md](./dialogs.md) — `CommandDialog` / `Dialog` / `StepperCommandDialog` rules.
|
|
194
194
|
- [frontend-quality.md](./frontend-quality.md) — engineering bar; [frontend-testing.md](./frontend-testing.md) — BDD specs.
|
|
195
195
|
- [typescript.md](./typescript.md) — TS style.
|