@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
|
@@ -7,54 +7,66 @@ profile: application
|
|
|
7
7
|
|
|
8
8
|
# Building React Components
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
The frontend is composed from **Cratis Components** (`@cratis/components` **4.x**). Components owns its markup, styling contract and public types; React Aria supplies focus, overlay, collection and date behavior *internally* — you never import or style it. Components declares **no** PrimeReact, PrimeIcons or PrimeUI dependency or peer, and it ships no vendor CSS. PrimeReact (11 and 10) and MUI exist only as optional *presentation adapters* (`@cratis/components.primereact`, `.primereact10`, `.mui`) that adapt nine primitive slots — button, icon-button, text input, text area, checkbox, radio, switch, progress bar, surface — and nothing else. Dialogs, dropdowns, tables, date pickers, toasts and steppers are always Components-owned.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
## Import from subpaths — the root is setup-only
|
|
13
|
+
|
|
14
|
+
The package root exports **only** setup (`CratisComponentsProvider`, configuration and message types). Every component ships from its own subpath; the Components 3 root namespaces are gone, and `@cratis/eslint-plugin-components`' `no-root-barrel-import` keeps them gone.
|
|
13
15
|
|
|
14
16
|
| Need | Use | Subpath |
|
|
15
17
|
|---|---|---|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
18
|
+
| App root | `CratisComponentsProvider` | `@cratis/components` |
|
|
19
|
+
| Page chrome, buttons, basic controls | `Page`, `Button`, `IconButton`, `TextInput`, `TextArea`, `NumberInput`, `Checkbox`, `Radio`, `Switch`, `Tooltip`, `ErrorBoundary`, `FormElement`, `ComboBox` (≥ 4.6.0 — single-selection, text-searching entity picker) | `@cratis/components/Common` |
|
|
20
|
+
| Icon | `Icon` (type) / `IconDisplay` | `@cratis/components/Common` |
|
|
21
|
+
| Query list page | `DataPage`, `MenuItem`, `Column` | `@cratis/components/DataPage` |
|
|
22
|
+
| Standalone query table | `DataTableForQuery` / `DataTableForObservableQuery`, `Column` | `@cratis/components/DataTables` |
|
|
21
23
|
| Dropdown | `Dropdown` | `@cratis/components/Dropdown` |
|
|
22
|
-
| Command dialog | `CommandDialog
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
24
|
+
| Command dialog | `CommandDialog`, `StepperCommandDialog`, `StepperPanel` | `@cratis/components/CommandDialog` |
|
|
25
|
+
| Standalone stepper | `CommandStepper` | `@cratis/components/CommandStepper` |
|
|
26
|
+
| Data / confirmation / busy dialog | `Dialog`, `ConfirmationDialog`, `BusyIndicatorDialog` | `@cratis/components/Dialogs` |
|
|
27
|
+
| Command form fields | `InputTextField`, `NumberField`, `DropdownField`, `CheckboxField`, `CalendarField`, …, `AutoCommandForm` | `@cratis/components/CommandForm` |
|
|
28
|
+
| Notifications | `Toaster`, `toast`, `toastCommandResult` | `@cratis/components/Notifications` |
|
|
29
|
+
| Status & display | `Tag`, `Badge`, `Chip`, `Skeleton`, `Avatar`, `ProgressBar`, `ProgressSpinner`, `Message` | `@cratis/components/Display` |
|
|
30
|
+
| Canvas tool palette | `Toolbar`, `ToolbarButton`, … | `@cratis/components/Toolbar` |
|
|
31
|
+
| Shared types | `JsonSchema`, `Json`, … | `@cratis/components/types` |
|
|
28
32
|
|
|
29
|
-
Use `Dropdown` from `@cratis/components/Dropdown
|
|
33
|
+
Use `Dropdown` from `@cratis/components/Dropdown`, never a vendor select: it renders through the Components overlay environment, so it stacks correctly above dialogs, and it follows the WAI-ARIA button/listbox (or combobox, when `filter` is on) pattern. Its `onChange` receives the **value**, not an event — as do every CommandForm field's.
|
|
30
34
|
|
|
31
35
|
### Notifications — feedback for commands run outside a dialog
|
|
32
36
|
|
|
33
37
|
`CommandDialog` handles success/error feedback itself. For a command executed
|
|
34
|
-
**programmatically** (`command.execute()` outside a dialog), mount
|
|
35
|
-
`<Toaster />` near the
|
|
36
|
-
|
|
37
|
-
`ICommandResult` flags to the right toast (success, not-authorized, validation
|
|
38
|
-
|
|
38
|
+
**programmatically** (`command.execute()` outside a dialog), mount the toast region once —
|
|
39
|
+
`toaster` on `CratisComponentsProvider`, or one `<Toaster />` near the root — and surface the result
|
|
40
|
+
with `toastCommandResult` (`@cratis/components/Notifications`). It maps the granular
|
|
41
|
+
`ICommandResult` flags to the right toast (success, not-authorized, validation with per-field
|
|
42
|
+
messages, exceptions — never stack traces):
|
|
39
43
|
|
|
40
44
|
```tsx
|
|
41
45
|
const result = await command.execute();
|
|
42
46
|
if (toastCommandResult(result, { successTitle: 'Author registered' })) refresh();
|
|
43
47
|
```
|
|
44
48
|
|
|
45
|
-
For ad-hoc notifications, call the imperative `toast.success/info/warn/error(...)`
|
|
46
|
-
|
|
49
|
+
For ad-hoc notifications, call the imperative `toast.success/info/warn/error(...)` — each takes an
|
|
50
|
+
**options object**, not a bare string: `toast.info({ title: 'Saved', description: '…' })`.
|
|
47
51
|
|
|
48
52
|
### Column filtering & display components
|
|
49
53
|
|
|
50
|
-
`<Column>` supports `filter` (a per-column filter
|
|
51
|
-
`DataPage` / the data tables show a
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
`<Column>` supports `sortable` and `filter` (a per-column filter popup with match modes), and
|
|
55
|
+
`DataPage` / the data tables show a search box when `globalFilterFields` is set. **Sorting and
|
|
56
|
+
filtering apply to the currently loaded page**; complete-result filtering belongs in the query's
|
|
57
|
+
arguments and the server-side query, before paging (`clientFiltering` is a deprecated no-op). Use the
|
|
58
|
+
`Display` components (`Tag`, `Badge`, `Skeleton`, …) for status indicators and loading states.
|
|
54
59
|
|
|
55
60
|
### `DataPage` — query list pages
|
|
56
61
|
|
|
57
|
-
`DataPage` (from `@cratis/components/DataPage`) owns the data table's subscription, paging,
|
|
62
|
+
`DataPage` (from `@cratis/components/DataPage`) owns the data table's subscription, paging,
|
|
63
|
+
selection, action menubar, and details split — **do not pre-fetch rows and pass an `items` array**.
|
|
64
|
+
Required props: `title`, `query` (`Constructor<TQuery>`; snapshot and observable queries are
|
|
65
|
+
auto-detected), `emptyMessage`, and `children`. Other props: `queryArguments`, `dataKey` (pass
|
|
66
|
+
whenever the read model has an identity), `selection` / `onSelectionChange`, `globalFilterFields` /
|
|
67
|
+
`defaultFilters`, `detailsComponent` (`React.FC<IDetailsComponentProps<T>>` = `{ item, onRefresh? }`),
|
|
68
|
+
`onRefresh`, and the Cratis part props `tablePt: DataTableParts` / `paginatorPt: TablePaginatorParts`
|
|
69
|
+
/ `menubarPt: ButtonParts` (plus `tableClassName` / `menubarClassName`).
|
|
58
70
|
|
|
59
71
|
Columns and toolbar actions are compositional children:
|
|
60
72
|
|
|
@@ -71,7 +83,10 @@ import { DataPage, MenuItem, Column } from '@cratis/components/DataPage';
|
|
|
71
83
|
</DataPage>
|
|
72
84
|
```
|
|
73
85
|
|
|
74
|
-
`MenuItem` is a
|
|
86
|
+
`MenuItem` is a declarative Components element read by `DataPage.MenuItems` (use `command`, not
|
|
87
|
+
`onClick`); `disableOnUnselected` greys it out until a row is selected; its `icon` is a **React
|
|
88
|
+
component type** (e.g. a `react-icons` icon), not an `Icon`. See the **cratis-arc-react-page** skill
|
|
89
|
+
for the full page workflow.
|
|
75
90
|
|
|
76
91
|
## Composition over Monoliths
|
|
77
92
|
|
|
@@ -100,33 +115,48 @@ Add an `index.ts` that re-exports the public surface so import paths stay stable
|
|
|
100
115
|
|
|
101
116
|
## Styling
|
|
102
117
|
|
|
103
|
-
Consistent styling comes from discipline: static styles in CSS files, dynamic values inline, and colors always from
|
|
118
|
+
Consistent styling comes from discipline: static styles in CSS files, dynamic values inline, and colors always from the `--cratis-*` design tokens. This ensures theming works automatically and no component breaks the visual language.
|
|
104
119
|
|
|
105
120
|
### App setup — the imports Cratis Components needs
|
|
106
121
|
|
|
107
|
-
|
|
122
|
+
An app does three things, once, at its root — or the components render unstyled:
|
|
108
123
|
|
|
109
|
-
1. **
|
|
110
|
-
2. **Import the stylesheets explicitly**, in this order — components no longer import their own CSS:
|
|
124
|
+
1. **Import the stylesheets, in this order** — components do not import their own CSS:
|
|
111
125
|
|
|
112
126
|
```ts
|
|
113
|
-
import '@cratis/components/tokens'; // the --cratis-*
|
|
114
|
-
import '@cratis/components/styles'; // every component
|
|
115
|
-
import '@cratis/components/theme'; // optional: the
|
|
127
|
+
import '@cratis/components/tokens'; // the semantic --cratis-* variables every component reads
|
|
128
|
+
import '@cratis/components/styles'; // structural rules for every component, in low-priority Cratis layers
|
|
129
|
+
import '@cratis/components/theme'; // optional: the baseline light/dark look (auto, or `cratis-dark`/`cratis-light`)
|
|
116
130
|
```
|
|
117
131
|
|
|
118
|
-
|
|
132
|
+
A product with its own design system omits `theme`, defines the `--cratis-*` variables itself, and imports those values *after* the two Components entries so they win the cascade.
|
|
133
|
+
|
|
134
|
+
2. **Mount `CratisComponentsProvider`** (from the package root) around the app. Its `value` carries `locale` (BCP 47 — drives dates, numbers, keyboard behavior and announcements) and `messages` (the Components-owned labels for `paginator`, `datePicker`, `dropdown`, `dialog`, `stepper`, `notifications`, `dataTable`, `columnFilter`, `toolbar`); `toaster` mounts the toast region. Renderer keys from Components 3 — `license`, `theme`, `pt`, `ripple`, `unstyled`, z-index — are a **type error**, on purpose. Keep the `value` object stable when it carries a large message catalog.
|
|
119
135
|
|
|
120
|
-
|
|
136
|
+
```tsx
|
|
137
|
+
import { CratisComponentsProvider } from '@cratis/components';
|
|
138
|
+
|
|
139
|
+
export const App = () => (
|
|
140
|
+
<CratisComponentsProvider value={{ locale: 'en-US' }} toaster>
|
|
141
|
+
<Application />
|
|
142
|
+
</CratisComponentsProvider>
|
|
143
|
+
);
|
|
144
|
+
```
|
|
121
145
|
|
|
122
|
-
|
|
123
|
-
|
|
146
|
+
3. **Install nothing else.** No PrimeReact, no PrimeIcons, no theme preset, no vendor provider. Only if the product wants a vendor's *look* on the nine primitive slots does it install one adapter package and pass its manifest to the provider's `library` prop — and the vendor's own provider, theme and license then stay the application's, outside Components.
|
|
147
|
+
|
|
148
|
+
### Writing styles
|
|
149
|
+
|
|
150
|
+
- Use **CSS classes in co-located `.css` files** for static styles; each component has its own CSS file — never add sub-component styles to the parent's CSS.
|
|
124
151
|
- The composition root's CSS only contains layout/grid rules for positioning children — it should not style the children themselves.
|
|
125
152
|
- Use inline `style` props **only** for runtime-dynamic values (pixel positions, computed sizes).
|
|
126
|
-
- Use
|
|
127
|
-
- `var(--surface-0)`
|
|
128
|
-
- `var(--text-color)`, `var(--text-color-secondary)`, `var(--primary-color)`, `var(--primary-color-text)`, `var(--highlight-bg)`
|
|
129
|
-
-
|
|
153
|
+
- Use the **`--cratis-*` tokens** for every color, background, border, shadow and radius, so theming and dark mode work automatically:
|
|
154
|
+
- surfaces — `var(--cratis-surface-0)`, `var(--cratis-surface-100)`, `var(--cratis-surface-card)`, `var(--cratis-surface-ground)`, `var(--cratis-surface-section)`, `var(--cratis-surface-overlay)`, `var(--cratis-surface-hover)`, `var(--cratis-surface-border)`
|
|
155
|
+
- text & accent — `var(--cratis-text-color)`, `var(--cratis-text-color-secondary)`, `var(--cratis-primary-color)`, `var(--cratis-primary-color-text)`, `var(--cratis-highlight-bg)`, `var(--cratis-highlight-text-color)`, `var(--cratis-focus-ring)`
|
|
156
|
+
- status — `var(--cratis-success-background)`/`-text`, `var(--cratis-warning-*)`, `var(--cratis-danger-*)`, `var(--cratis-info-*)`
|
|
157
|
+
- shape — `var(--cratis-border-radius)`, `var(--cratis-control-height)`, `var(--cratis-shadow-subtle)`/`-overlay`/`-dialog`
|
|
158
|
+
- Never hard-code hex or `rgb()` for UI chrome. Only hard-code colors that are intentionally theme-independent (brand accent dots, traffic-light indicators).
|
|
159
|
+
- **Reach inside a component through its parts, not its DOM.** Every meaningful element carries `data-cratis-part="<name>"` and state attributes (`data-selected`, `data-invalid`, `data-disabled`, `data-active`, `data-position`). Style them with those selectors, or pass per-part attributes through the component's `pt` prop typed by its `*Parts` type (`DialogParts`, `StepperParts`, `DataTableParts`, `TablePaginatorParts`, `ButtonParts`, the `Toolbar*Parts` family). Never target React Aria class names or the internal element structure — they are not a contract.
|
|
130
160
|
- Name CSS classes with a BEM-like prefix matching the component name.
|
|
131
161
|
|
|
132
162
|
## Props
|
|
@@ -136,16 +166,19 @@ Props are a component's public API. They should be clear, minimal, and well-docu
|
|
|
136
166
|
- Each sub-component declares its own `*Props` interface with JSDoc on every prop.
|
|
137
167
|
- Pass only needed props — avoid threading large prop bags through component trees.
|
|
138
168
|
- Event handlers follow `on*` naming: `onPointerDown`, `onSelect`.
|
|
169
|
+
- Model visual variation the way `Button` does — one typed `variant` (`solid | outline | ghost | link`), one `tone` (`neutral | accent | positive | caution | critical`), one `shape` — not a pile of boolean flags.
|
|
139
170
|
|
|
140
171
|
## Dialogs
|
|
141
172
|
|
|
142
173
|
See [dialogs.md](./dialogs.md) for the full dialog guide.
|
|
143
174
|
|
|
144
|
-
**Summary:**
|
|
175
|
+
**Summary:** Use `CommandDialog` from `@cratis/components/CommandDialog` for command-executing dialogs and `Dialog` from `@cratis/components/Dialogs` for data-collection dialogs; confirmations and busy indicators are raised through hooks, never built. Never import a vendor dialog. Do not render manual `<Button>` components for dialog actions — the dialog components handle footers.
|
|
145
176
|
|
|
146
177
|
## Icons
|
|
147
178
|
|
|
148
|
-
|
|
179
|
+
Components accepts an **`Icon`** (`@cratis/components/Common`) wherever a control takes an icon: either a React node — preferred — or a complete, consumer-owned icon-font class string. Components installs **no icon font**: a string is rendered on an `<i>` unchanged, so the product must load that stylesheet itself. `react-icons` is a Components dependency and the natural source of React icon nodes (`<FaHouse aria-hidden="true" />`). Not every `icon` prop has that shape — `DataPage.MenuItem.icon` is a React component *type* because the page instantiates it; follow the exported prop type.
|
|
180
|
+
|
|
181
|
+
For the application's own SVG icons:
|
|
149
182
|
|
|
150
183
|
- **Distinguish icons from status/interactive components.** A pure SVG icon is a simple presentational element. A component that wraps an icon with interactive behavior (e.g. a dropdown, tooltip, or complex state) is a _component_, not an icon — name it accordingly (e.g. `SliceStatus`, not `SliceStatusIcon`).
|
|
151
184
|
- **Store each SVG as a separate `.svg` file** inside the icon's folder. Do not embed SVG markup directly in `.tsx` files.
|
|
@@ -7,14 +7,14 @@ paths:
|
|
|
7
7
|
|
|
8
8
|
# Using Dialogs
|
|
9
9
|
|
|
10
|
-
The Cratis
|
|
10
|
+
The Cratis dialogs handle command execution, validation timing, busy state, focus, dismissal and footer buttons consistently. A vendor dialog, or a hand-rolled modal, bypasses all of this and leads to inconsistent UX — and a second focus trap around a Components dialog breaks keyboard and screen-reader behavior.
|
|
11
11
|
|
|
12
12
|
## Choose the Correct Dialog Type
|
|
13
13
|
|
|
14
14
|
- If confirm executes a command, use `CommandDialog` from `@cratis/components/CommandDialog`.
|
|
15
15
|
- If no command is executed on confirm, use `Dialog` from `@cratis/components/Dialogs`.
|
|
16
16
|
- If you are **asking the user to confirm** or **showing that something is in progress**, do not build a dialog at all — raise the host-rendered one through its hook (below).
|
|
17
|
-
- **Never**
|
|
17
|
+
- **Never** use a vendor or hand-rolled modal. Dialogs are Components-owned; the optional renderer adapters do not replace them.
|
|
18
18
|
|
|
19
19
|
## Confirmations and busy indicators are host-rendered — register once, raise by hook
|
|
20
20
|
|
|
@@ -109,24 +109,24 @@ if (result === DialogResult.Ok) {
|
|
|
109
109
|
|
|
110
110
|
### `onBeforeExecute` is a transformer
|
|
111
111
|
|
|
112
|
-
It receives the current command values and **must return them** (mutated or not). **Returning `void`
|
|
112
|
+
It receives the current command values and **must return them** (mutated or not). **Returning `void` does not execute with `undefined`** — the current values are kept and a `console.warn` is logged; still, always return the values. It runs only on submit — never use it to seed *required* values (validation runs against pre-transform state, so a value seeded here never makes the form valid and the submit button stays permanently disabled). Seed required values via `initialValues`; reserve `onBeforeExecute` for transforms that don't affect validity (e.g. a generated id).
|
|
113
113
|
|
|
114
114
|
### CommandForm fields
|
|
115
115
|
|
|
116
|
-
Use built-in `CommandForm` fields (from `@cratis/components/CommandForm`) for every user-input value — a raw
|
|
116
|
+
Use built-in `CommandForm` fields (from `@cratis/components/CommandForm`) for every user-input value — a raw control (a `Common` `TextInput`, a native `<input>`) inside a command dialog is not bound to the command, so validation never re-runs and the submit button stays **permanently disabled**. Catalog: `InputTextField`, `PasswordField`, `NumberField`, `NumberInputField`, `DropdownField`, `CheckboxField`, `ToggleSwitchField`, `TextAreaField`, `CalendarField`, `RadioButtonField`, `RadioGroupField`, `ChipsField`, `MultiSelectField`, `ColorPickerField`, `SliderField`, `RatingField`. `AutoCommandForm` generates the field list from the command's properties (`string` → `InputTextField`, `number` → `NumberField`, `boolean` → `CheckboxField`, `Date` → `CalendarField`; other types need a registered field-type provider) — use it for plain forms, hand-write fields when the layout or a concept type needs it. Several `RadioButtonField`s bound to one property need the same explicit `name`.
|
|
117
117
|
|
|
118
|
-
- The `value={c => c.name}` **accessor lambda doubles as the binding and type-checked field selection** — renaming a command property surfaces a compile error at every binding.
|
|
118
|
+
- The `value={c => c.name}` **accessor lambda doubles as the binding and type-checked field selection** — renaming a command property surfaces a compile error at every binding. The binding is inferred from the accessor's *source text*, so a computed accessor (`c => c[descriptor.name]`) cannot be inferred — name it explicitly with `fieldName="…"` (Arc ≥ 22.16.0).
|
|
119
119
|
- **`RadioGroupField<T>`** renders a whole group from data (`options`/`optionLabel`/`optionValue`, `layout='horizontal'|'vertical'`); **`RadioButtonField<T>`** is one component per option (each takes a `buttonValue`). Both infer the value type from the accessor — no `as string` casts.
|
|
120
120
|
- **`asCommandFormField(Component, opts)`** (`asCommandFormField`, `WrappedFieldProps` from `@cratis/arc.react/commands`) wraps a custom input so it participates in `CommandForm` like a built-in. `WrappedFieldProps<T>` gives `{ value, onChange, invalid, required, errors }` (`errors` is `string[]` → `errors.join(', ')`); options are `{ defaultValue, extractValue: e => ... }`.
|
|
121
|
-
- **`useCommandInstance(
|
|
121
|
+
- **`useCommandInstance<TCommand>()`** (`@cratis/arc.react/commands`, no argument — reads the enclosing form's context) returns the live reactive instance the form is bound to — **read** it to drive dependent fields (e.g. read `command.country` to choose a `DropdownField`'s options); never mutate (mutations go through field bindings).
|
|
122
122
|
|
|
123
123
|
### Opening dialogs — `useDialog` / `useDialogContext`
|
|
124
124
|
|
|
125
|
-
`useDialog<TResponse, TInput>(Component)` returns `[Wrapper, showFn]
|
|
125
|
+
`useDialog<TResponse, TInput>(Component)` returns `[Wrapper, showFn, context]` (the third element is rarely needed): render `<Wrapper />` in JSX and call `showFn(input)` to open it; it resolves to `[DialogResult, TResponse?]` when the dialog closes. For a new dialog, prefer reading input as **plain typed props** (`<Name>Input`) and obtaining `closeDialog` from **`useDialogContext<TRequest, TResponse>()`** (request type first) — rather than declaring a props interface that extends `DialogProps` to thread both input and `closeDialog`. (Existing dialogs that destructure `closeDialog` from `DialogProps` remain valid.) Signal the outcome with `closeDialog(DialogResult.Ok | Cancelled, response?)`.
|
|
126
126
|
|
|
127
127
|
### Multi-step wizards — `StepperCommandDialog`
|
|
128
128
|
|
|
129
|
-
For a command split across named steps use `StepperCommandDialog` (`@cratis/components/CommandDialog`) — see the **stepper-command-dialog** skill. Conditional steps written as `{condition && <StepperPanel/>}` are supported
|
|
129
|
+
For a command split across named steps use `StepperCommandDialog` (`@cratis/components/CommandDialog`) — see the **cratis-components-stepper-command-dialog** skill. Conditional steps written as `{condition && <StepperPanel/>}` are supported: only the steps that actually render are counted, so Next and Submit appear where the user expects them, and the active step is clamped if a late-resolving condition removes a step the user had passed. ⚠️ A `<>…</>` fragment wrapping several panels counts as **one** step — give each step its own `StepperPanel` child. Stepper parts are Cratis-owned (`root`, `list`, `step`, `header`, `number`, `title`, `separator`, `panels`, `panel`; `StepperParts` from `@cratis/components/CommandDialog`). When the wizard belongs inline in a page region rather than in a modal, use the sibling `CommandStepper` (`@cratis/components/CommandStepper`) — same steps, same command execution, no dialog chrome.
|
|
130
130
|
|
|
131
131
|
## When Using `Dialog`
|
|
132
132
|
|
|
@@ -136,7 +136,7 @@ Use this for dialogs that collect data and return it without executing a command
|
|
|
136
136
|
import { useState } from 'react';
|
|
137
137
|
import { DialogProps, DialogResult } from '@cratis/arc.react/dialogs';
|
|
138
138
|
import { Dialog } from '@cratis/components/Dialogs';
|
|
139
|
-
import {
|
|
139
|
+
import { TextInput } from '@cratis/components/Common';
|
|
140
140
|
|
|
141
141
|
export const AddProject = ({ closeDialog }: DialogProps<{ name: string }>) => {
|
|
142
142
|
const [name, setName] = useState('');
|
|
@@ -150,10 +150,10 @@ export const AddProject = ({ closeDialog }: DialogProps<{ name: string }>) => {
|
|
|
150
150
|
onConfirm={() => closeDialog(DialogResult.Ok, { name })}
|
|
151
151
|
onCancel={() => closeDialog(DialogResult.Cancelled)}
|
|
152
152
|
>
|
|
153
|
-
<
|
|
153
|
+
<TextInput
|
|
154
154
|
value={name}
|
|
155
|
-
onChange={
|
|
156
|
-
|
|
155
|
+
onChange={value => setName(value)}
|
|
156
|
+
aria-label="Project name"
|
|
157
157
|
/>
|
|
158
158
|
</Dialog>
|
|
159
159
|
);
|
|
@@ -249,16 +249,20 @@ Use `buttons={null}` for dialogs that contain their own internal actions (e.g. a
|
|
|
249
249
|
|
|
250
250
|
| Prop | Type | Notes |
|
|
251
251
|
|---|---|---|
|
|
252
|
-
| `title` | `string` | Header text
|
|
253
|
-
| `visible` | `boolean` |
|
|
252
|
+
| `title` | `string` | Header text |
|
|
253
|
+
| `visible` | `boolean` | Controlled open state; defaults to `true` |
|
|
254
254
|
| `buttons` | `DialogButtons \| ReactNode \| null` | Prefer `DialogButtons` enum; `null` for no footer |
|
|
255
255
|
| `isValid` | `boolean` | Disables the confirm button when `false` |
|
|
256
|
-
| `
|
|
257
|
-
| `
|
|
256
|
+
| `isBusy` | `boolean` | Disables every action **and every dismissal path** while work is in flight |
|
|
257
|
+
| `initialFocus` | `DialogInitialFocus` | Where focus lands on open; defaults to the confirmation action — the **cratis-components-accessibility** skill covers the armed-Enter hazard |
|
|
258
|
+
| `okLabel` / `cancelLabel` / `yesLabel` / `noLabel` | `string` | Per-dialog label; falls back to the provider's `messages.dialog`, then English |
|
|
259
|
+
| `closeAriaLabel` | `string` | Accessible name of the header close button; same fallback chain |
|
|
258
260
|
| `onConfirm` | `() => boolean \| void \| Promise<boolean> \| Promise<void>` | Called when Ok is clicked; return `false` to keep dialog open, `true` to close |
|
|
259
|
-
| `onCancel` | `() => void \| Promise<void>` | Called when Cancel is clicked |
|
|
261
|
+
| `onCancel` | `() => boolean \| void \| Promise<boolean> \| Promise<void>` | Called when Cancel is clicked |
|
|
260
262
|
| `onClose` | `(result: DialogResult) => boolean \| void \| Promise<...>` | Combined handler for both Ok and Cancel |
|
|
261
|
-
| `width` | `string` | Dialog width (e.g. `'50vw'`)
|
|
262
|
-
| `
|
|
263
|
+
| `width` | `string` | Dialog width (e.g. `'50vw'`); defaults to `450px` |
|
|
264
|
+
| `dismissable` | `boolean` | Header close, Escape and backdrop dismissal (when not busy) |
|
|
265
|
+
| `style` / `contentStyle` / `className` | | Modal-root and content-region styling |
|
|
266
|
+
| `pt` | `DialogParts` | Per-part attributes: `backdrop`, `positioner`, `root`, `header`, `title`, `close`, `content`, `footer`, `confirm`, `cancel` |
|
|
263
267
|
|
|
264
|
-
`
|
|
268
|
+
`resizable`, `ptOptions` and `unstyled` are accepted for source compatibility and do nothing. There is no `modal`, `draggable`, `footer` or `onHide` — the dialog is always modal, the footer comes from `buttons`, and closing is reported through `onCancel`/`onClose`. Style the dialog through its parts (`pt`, or `[data-cratis-part='root']` selectors), never through React Aria class names.
|
|
@@ -10,226 +10,74 @@ profile: application
|
|
|
10
10
|
> **⚠️ APPLIES ONLY TO PROJECTS USING ENTITY FRAMEWORK CORE**
|
|
11
11
|
> If your project does not reference `Microsoft.EntityFrameworkCore` or any EF Core packages, **ignore this entire file**. These rules are irrelevant outside of EF Core contexts.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
`Cratis.Arc.EntityFrameworkCore` gives an Arc application EF Core-backed read models: two `DbContext` base types, convention-based registration, provider selection from the connection string, cross-database column helpers for migrations, concept-aware value conversion, and live observation of a `DbSet`. **That is the whole of what Arc ships** — it has no migration runner, no table-name constant convention, no design-time factory and no prescribed project split. Where an application adds one of those, it is that application's local convention, not Cratis; keep such conventions in the repository's own instructions, never present them as framework. The step-by-step workflow, the exact signatures and the traps live in the **cratis-arc-ef-core-migration** skill; this rule states the invariants.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
## DbContext base types **[contract]**
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|---------|----------------|
|
|
19
|
-
| `Database` | Migrations only – no entities, no DbContexts |
|
|
20
|
-
| `Core` | Entities and feature DbContexts (co-located with features) |
|
|
21
|
-
| `Infrastructure` | DbContext registration, migration runner, cross-cutting EF setup |
|
|
17
|
+
Always derive from one of Arc's base types — never directly from `DbContext`:
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
```
|
|
26
|
-
Core → Infrastructure → Database
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## DbContext Base Types
|
|
30
|
-
|
|
31
|
-
Always use the Cratis Arc base types — never inherit directly from `DbContext`:
|
|
32
|
-
|
|
33
|
-
- **`ReadOnlyDbContext`** — for all read model / projection contexts (the vast majority)
|
|
34
|
-
- **`BaseDbContext`** — only for writable contexts that own state (e.g. device state, infrastructure state)
|
|
19
|
+
- **`ReadOnlyDbContext`** — for every read model / projection context (the vast majority). Its save-changes interceptor **throws** on any `SaveChanges`, so a projection cannot be bypassed by accident.
|
|
20
|
+
- **`BaseDbContext`** — only for a writable context that genuinely owns state (device state, infrastructure state).
|
|
35
21
|
|
|
36
22
|
```csharp
|
|
37
|
-
// ✅ Read model context
|
|
38
23
|
public class StartupPhaseDbContext(DbContextOptions<StartupPhaseDbContext> options)
|
|
39
24
|
: ReadOnlyDbContext(options)
|
|
40
25
|
{
|
|
41
26
|
public DbSet<StartupPhase> StartupPhases => Set<StartupPhase>();
|
|
42
|
-
public DbSet<PersonnelAssignment> PersonnelAssignments => Set<PersonnelAssignment>();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// ✅ Writable (state-owning) context
|
|
46
|
-
public class DeviceStateDbContext(DbContextOptions<DeviceStateDbContext> options)
|
|
47
|
-
: BaseDbContext(options)
|
|
48
|
-
{
|
|
49
|
-
public DbSet<DeviceState> DeviceStates => Set<DeviceState>();
|
|
50
27
|
}
|
|
51
28
|
```
|
|
52
29
|
|
|
53
|
-
Use the primary constructor
|
|
30
|
+
Use the primary constructor; expose each `DbSet<T>` as an expression-bodied property over `Set<T>()`. Co-locate the context with the feature whose read models it holds.
|
|
54
31
|
|
|
55
|
-
## Feature
|
|
32
|
+
## Feature contexts — not god contexts **[convention]**
|
|
56
33
|
|
|
57
|
-
|
|
34
|
+
One focused `DbContext` per feature or tightly related feature group. Never aggregate unrelated entities into a single context: a context's shape is a cohesion boundary, and a god context couples every feature's schema change to every other's.
|
|
58
35
|
|
|
59
|
-
|
|
60
|
-
// ❌ God context
|
|
61
|
-
public class AppDbContext : DbContext
|
|
62
|
-
{
|
|
63
|
-
public DbSet<Mission> Missions { get; set; }
|
|
64
|
-
public DbSet<User> Users { get; set; }
|
|
65
|
-
public DbSet<Station> Stations { get; set; }
|
|
66
|
-
// ... many more
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// ✅ Focused feature context
|
|
70
|
-
public class StartupPhaseDbContext(DbContextOptions<StartupPhaseDbContext> options)
|
|
71
|
-
: ReadOnlyDbContext(options)
|
|
72
|
-
{
|
|
73
|
-
public DbSet<StartupPhase> StartupPhases => Set<StartupPhase>();
|
|
74
|
-
public DbSet<PersonnelAssignment> PersonnelAssignments => Set<PersonnelAssignment>();
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
Co-locate the DbContext file with its feature:
|
|
79
|
-
|
|
80
|
-
```
|
|
81
|
-
Missions/Ongoing/StartupPhase/
|
|
82
|
-
├── StartupPhase.cs
|
|
83
|
-
├── StartupPhaseDbContext.cs
|
|
84
|
-
└── ...
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
## State Mutation — The Golden Rule
|
|
36
|
+
## State mutation — the golden rule **[convention]**
|
|
88
37
|
|
|
89
38
|
> **Never mutate state directly through a DbContext.**
|
|
90
39
|
|
|
91
|
-
All state changes
|
|
92
|
-
|
|
93
|
-
```csharp
|
|
94
|
-
// ❌ Direct mutation — forbidden
|
|
95
|
-
dbContext.StartupPhases.Add(new StartupPhase(...));
|
|
96
|
-
await dbContext.SaveChangesAsync();
|
|
97
|
-
|
|
98
|
-
// ✅ Correct: emit an event, let the projection handle writes
|
|
99
|
-
[Command]
|
|
100
|
-
public record UpdateStartupPhase(MissionId MissionId, ...) { ... }
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
Only infrastructure projection code, the Chronicle event engine, and reference data sync may write through DbContexts.
|
|
40
|
+
All state changes flow through events and Chronicle projections; a direct write bypasses the event log and the audit trail. Only projection infrastructure, the Chronicle engine, and deliberate reference-data synchronization write through a context — and those are `BaseDbContext`s, never `ReadOnlyDbContext`s.
|
|
104
41
|
|
|
105
|
-
## Registration
|
|
106
|
-
|
|
107
|
-
Use the Cratis Arc `Cratis.Arc.EntityFrameworkCore` extension methods:
|
|
108
|
-
|
|
109
|
-
```csharp
|
|
110
|
-
// Register a single writable DbContext
|
|
111
|
-
services.AddDbContextWithConnectionString<DeviceStateDbContext>(connectionString, optionalConfigure);
|
|
112
|
-
|
|
113
|
-
// Auto-discover and register ALL ReadOnlyDbContext subtypes from given assemblies
|
|
114
|
-
services.AddReadModelDbContextsWithConnectionStringFromAssemblies(
|
|
115
|
-
connectionString,
|
|
116
|
-
configureOptions,
|
|
117
|
-
[Assembly.GetExecutingAssembly()]);
|
|
118
|
-
```
|
|
42
|
+
## Registration **[contract]**
|
|
119
43
|
|
|
120
|
-
|
|
44
|
+
Wire EF Core through the Arc builder and give it the connection string:
|
|
121
45
|
|
|
122
46
|
```csharp
|
|
123
|
-
|
|
47
|
+
builder.AddCratisArc(arc => arc.WithEntityFrameworkCore(
|
|
48
|
+
options => options.ConnectionString = connectionString));
|
|
124
49
|
```
|
|
125
50
|
|
|
126
|
-
|
|
51
|
+
With a non-empty connection string and `AutoDiscoverDbContexts` (the default), Arc discovers every public `BaseDbContext` subtype not marked `[IgnoreAutoRegistration]`, registering `ReadOnlyDbContext` subtypes read-only and the rest read-write. ⚠️ `WithEntityFrameworkCore()` with **no** connection string discovers nothing. Explicit registration remains available (`AddDbContextWithConnectionString<T>`, `AddReadOnlyDbContextWithConnectionString<T>`, `AddReadModelDbContextsFromAssemblies`, `AddReadModelDbContextsWithConnectionStringFromAssemblies`).
|
|
127
52
|
|
|
128
|
-
|
|
53
|
+
Select the provider with **`UseDatabaseFromConnectionString(connectionString)`** — never hard-code `UseSqlite` / `UseNpgsql` / `UseSqlServer`. It infers `DatabaseType` (`Sqlite`, `SqlServer`, `PostgreSql`) from the connection string, ensures a SQLite file's directory exists, and installs Arc's migrations SQL generator for that provider (which is what adds the JSON validation constraint for `[Json]` columns). An unsupported connection string throws `UnsupportedDatabaseType`.
|
|
129
54
|
|
|
130
|
-
|
|
55
|
+
## Migrations **[contract]/[convention]**
|
|
131
56
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
## Migrations
|
|
135
|
-
|
|
136
|
-
Migrations live exclusively in the **`Database`** project, never in `Core` or `Infrastructure`.
|
|
137
|
-
|
|
138
|
-
### Organization
|
|
139
|
-
|
|
140
|
-
Each entity category has its own folder with versioned migration files:
|
|
141
|
-
|
|
142
|
-
```
|
|
143
|
-
Database/
|
|
144
|
-
├── Missions/
|
|
145
|
-
│ ├── v1_0_0.cs
|
|
146
|
-
│ └── v1_1_0.cs
|
|
147
|
-
├── Users/
|
|
148
|
-
│ └── v1_0_0.cs
|
|
149
|
-
└── WellKnownTables.cs
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### Naming
|
|
153
|
-
|
|
154
|
-
Version files using the pattern `v{major}_{minor}_{patch}.cs` and place them inside a namespace matching their folder:
|
|
155
|
-
|
|
156
|
-
```csharp
|
|
157
|
-
namespace Database.Missions;
|
|
158
|
-
|
|
159
|
-
public class v1_0_0 : Migration
|
|
160
|
-
{
|
|
161
|
-
protected override void Up(MigrationBuilder migrationBuilder) { ... }
|
|
162
|
-
protected override void Down(MigrationBuilder migrationBuilder) { ... }
|
|
163
|
-
}
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
The migration ID is composed as `{folder}_{ClassName}` (e.g. `Missions_v1_0_0`).
|
|
167
|
-
|
|
168
|
-
### Cross-Database Column Helpers
|
|
169
|
-
|
|
170
|
-
Always use the Cratis Arc `MigrationBuilder` extension helpers to define columns. These abstract over PostgreSQL and SQLite type differences:
|
|
57
|
+
Migrations are created and applied with the **standard EF Core tooling** (`dotnet ef migrations add`, `dotnet ef database update`, or `Database.Migrate()` at startup). Arc adds only the column helpers that keep one migration valid across all three providers — use them instead of raw `table.Column<T>()`:
|
|
171
58
|
|
|
172
59
|
| Helper | Use for |
|
|
173
|
-
|
|
174
|
-
| `table.StringColumn(migrationBuilder)` |
|
|
175
|
-
| `table.GuidColumn(migrationBuilder)` |
|
|
176
|
-
| `table.NumberColumn<T>(migrationBuilder)` |
|
|
177
|
-
| `table.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
// ✅ Cross-database migration
|
|
183
|
-
migrationBuilder.CreateTable(
|
|
184
|
-
name: WellKnownTables.Missions,
|
|
185
|
-
columns: table => new
|
|
186
|
-
{
|
|
187
|
-
Id = table.StringColumn(migrationBuilder, nullable: false),
|
|
188
|
-
Title = table.StringColumn(migrationBuilder, maxLength: 200, nullable: false),
|
|
189
|
-
ResourceId = table.NumberColumn<int>(migrationBuilder, nullable: true),
|
|
190
|
-
DispatchTime = table.DateTimeOffsetColumn(migrationBuilder),
|
|
191
|
-
UrgencyId = table.GuidColumn(migrationBuilder)
|
|
192
|
-
},
|
|
193
|
-
constraints: table =>
|
|
194
|
-
{
|
|
195
|
-
table.PrimaryKey("PK_Missions", x => x.Id);
|
|
196
|
-
table.ForeignKey("FK_Missions_Urgency", x => x.UrgencyId,
|
|
197
|
-
WellKnownTables.MissionUrgencies, "Id", onDelete: ReferentialAction.SetNull);
|
|
198
|
-
});
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
### Table Names — WellKnownTables
|
|
202
|
-
|
|
203
|
-
Always reference table names from `WellKnownTables` constants. Never use magic strings directly in migrations.
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| `table.StringColumn(migrationBuilder, …)` | text |
|
|
62
|
+
| `table.GuidColumn(migrationBuilder, …)` | GUID / UUID |
|
|
63
|
+
| `table.NumberColumn<T>(migrationBuilder, …)` | integers, longs, decimals |
|
|
64
|
+
| `table.BoolColumn(migrationBuilder, …)` | booleans |
|
|
65
|
+
| `table.DateTimeOffsetColumn(migrationBuilder, …)` | timestamps with offset |
|
|
66
|
+
| `table.AutoIncrementColumn(migrationBuilder, …)` | identity columns |
|
|
67
|
+
| `table.JsonColumn<TProperty>(migrationBuilder, …)` | a `[Json]`-mapped property |
|
|
68
|
+
| `migrationBuilder.Add{String,Guid,Number,Bool,DateTimeOffset,AutoIncrement,Json,Point,LineString,Polygon}Column(…)` | adding a column to an existing table |
|
|
204
69
|
|
|
205
|
-
|
|
206
|
-
// ❌ Magic string
|
|
207
|
-
migrationBuilder.CreateTable(name: "Missions", ...);
|
|
208
|
-
|
|
209
|
-
// ✅ Constant
|
|
210
|
-
migrationBuilder.CreateTable(name: WellKnownTables.Missions, ...);
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
Add new table names to `Database/WellKnownTables.cs` before writing the migration.
|
|
70
|
+
Where a repository organizes its migrations (a dedicated project, a versioned file naming scheme, a table-name constants class) is its own convention: follow what the repository already does, and do not introduce one as if Arc required it.
|
|
214
71
|
|
|
215
|
-
|
|
72
|
+
## Concepts and JSON **[contract]**
|
|
216
73
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
```csharp
|
|
220
|
-
// ASP.NET mode
|
|
221
|
-
await app.ApplyAllMigrations(connectionString);
|
|
222
|
-
|
|
223
|
-
// MAUI / IServiceProvider mode
|
|
224
|
-
await services.ApplyAllMigrations(connectionString);
|
|
225
|
-
```
|
|
74
|
+
`ConceptAs<T>` properties map to their underlying primitive without per-property value converters: `BaseDbContext.OnModelCreating` applies the concept conversions, and every Arc registration path calls `AddConceptAsSupport()` so concepts also work inside LINQ predicates. (A context you register with plain EF `AddDbContext` must call `options.AddConceptAsSupport()` itself.) Mark a property `[Json]` to store it as a JSON column with the provider's validation constraint.
|
|
226
75
|
|
|
227
|
-
|
|
76
|
+
## Observation **[contract]**
|
|
228
77
|
|
|
229
|
-
|
|
78
|
+
An EF Core read model can back an observable query: `dbSet.Observe(...)`, `ObserveById(...)` and `ObserveSingle(...)` (extension methods declared in the `Microsoft.EntityFrameworkCore` namespace, so they are in scope wherever the `DbSet` is) push changes through the provider's change notifier — see the skill for what each provider supports.
|
|
230
79
|
|
|
231
|
-
|
|
80
|
+
## See also
|
|
232
81
|
|
|
233
|
-
-
|
|
234
|
-
-
|
|
235
|
-
- To isolate **writable** contexts, filter the discovered set on assignability — `types.Where(t => !typeof(ReadOnlyDbContext).IsAssignableFrom(t))` — rather than a dedicated extension.
|
|
82
|
+
- the **cratis-arc-ef-core-migration** skill — wiring, making an EF read model injectable into a command, writing a migration, observing, and specs.
|
|
83
|
+
- [efcore.specs.md](./efcore.specs.md) — `DbContext` specs with SQLite in-memory.
|
|
@@ -24,7 +24,7 @@ Each framework repo is organized by what it builds, not by feature slices:
|
|
|
24
24
|
- **Fundamentals** — the base library. `ConceptAs<T>`, type discovery (`IInstancesOf<T>` / `IImplementationsOf<T>`), serialization, common primitives. `Source/DotNET` (C#) + `Source/JavaScript` (`@cratis/fundamentals`) + the shared ESLint config.
|
|
25
25
|
- **Arc** — the CQRS + model-binding + proxy-generation engine. `Source/DotNET` (the command/query pipeline, validation, authorization, identity, the Roslyn **proxy generator**) + `Source/JavaScript` (`@cratis/arc`, `@cratis/arc.react`, `@cratis/arc.react.mvvm`). Arc.Core does **not** depend on Chronicle.
|
|
26
26
|
- **Chronicle** — the event-sourcing engine. `Source/Kernel` (the engine: **Orleans grains**, event sequences, observers/projections/reducers, storage providers — MongoDB and others), `Source/Clients` (the client SDKs incl. `DotNET` and `Testing`), `Infrastructure`, `Tools`, `Workbench`. The kernel is the deep, performance- and consistency-critical core.
|
|
27
|
-
- **Components** — the React component library
|
|
27
|
+
- **Components** — the React component library (Components-owned markup and styling contract; React Aria internally; optional PrimeReact/MUI presentation adapters under `Adapters/`). `Source/<Component>/` folder per component (`CommandDialog`, `DataPage`, `DataTables`, …) with Storybook stories; published as `@cratis/components`. (Application rules *consume* these components; here you *build* them.)
|
|
28
28
|
|
|
29
29
|
Repo conventions follow from this: `Source/DotNET` + `Source/JavaScript` for dual-stack libraries; `Kernel` vs `Clients` for Chronicle; a folder-per-component library layout for Components. Match the structure of the area you are editing — do not introduce app-style layouts.
|
|
30
30
|
|
|
@@ -29,7 +29,7 @@ Stop-and-fix signals during implementation and review:
|
|
|
29
29
|
|---|---|---|
|
|
30
30
|
| New component added without checking the shared component surface and `@cratis/components` | Duplicates fragment the design system | Reuse an existing primitive/wrapper, or document the missing one |
|
|
31
31
|
| Slice-local helper promoted to the shared component folder after one use | Freezes a one-off as a shared API | Keep it beside the slice until a second real consumer appears |
|
|
32
|
-
|
|
|
32
|
+
| A vendor UI control (any `primereact/*`, `@mui/*`, … import) where a Cratis component exists | Bypasses Arc binding, the overlay environment and validation; adds a second focus owner | Use the `@cratis/components` subpath component |
|
|
33
33
|
| Import from the `@cratis/components` root barrel in new code | Pulls optional-peer-heavy exports, hides intent | Import from subpaths (`CommandDialog`, `DataPage`, `DataTables`, `Dialogs`, `Dropdown`, `Toolbar`, `Common`) |
|
|
34
34
|
| Component API has many boolean visual flags (`primary`, `selected`, `muted`, …) | State combinations become undefined | One typed `variant`/`tone` union, or split components |
|
|
35
35
|
| A reusable component reaches into domain strings, generated proxies, identity, query, command, or navigation | It is no longer a primitive | Move it to the slice, or make it explicitly Cratis-aware and test/story it as such |
|