@cratis/pi 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/README.md +43 -0
  2. package/package.json +24 -0
  3. package/skills/cratis-application-react-specifications/LICENSE +2 -0
  4. package/skills/cratis-application-react-specifications/SKILL.md +192 -0
  5. package/skills/cratis-application-slice-specifications/LICENSE +2 -0
  6. package/skills/cratis-application-slice-specifications/SKILL.md +222 -0
  7. package/skills/cratis-arc-authentication-authorization-and-identity/LICENSE +2 -0
  8. package/skills/cratis-arc-authentication-authorization-and-identity/SKILL.md +233 -0
  9. package/skills/cratis-arc-authentication-authorization-and-identity/references/authentication.md +140 -0
  10. package/skills/cratis-arc-authentication-authorization-and-identity/references/authorization.md +112 -0
  11. package/skills/cratis-arc-authentication-authorization-and-identity/references/frontend.md +102 -0
  12. package/skills/cratis-arc-authentication-authorization-and-identity/references/local-development.md +85 -0
  13. package/skills/cratis-arc-authentication-authorization-and-identity/references/tenancy.md +87 -0
  14. package/skills/cratis-arc-command/LICENSE +2 -0
  15. package/skills/cratis-arc-command/SKILL.md +286 -0
  16. package/skills/cratis-arc-command/references/command-result.md +111 -0
  17. package/skills/cratis-arc-command/references/handler-shapes.md +135 -0
  18. package/skills/cratis-arc-command/references/proxy-generation.md +87 -0
  19. package/skills/cratis-arc-command/references/read-model-injection.md +97 -0
  20. package/skills/cratis-arc-command-execution/LICENSE +2 -0
  21. package/skills/cratis-arc-command-execution/SKILL.md +148 -0
  22. package/skills/cratis-arc-command-validation/LICENSE +2 -0
  23. package/skills/cratis-arc-command-validation/SKILL.md +230 -0
  24. package/skills/cratis-arc-ef-core-migration/LICENSE +2 -0
  25. package/skills/cratis-arc-ef-core-migration/SKILL.md +231 -0
  26. package/skills/cratis-arc-observable-query-http/LICENSE +2 -0
  27. package/skills/cratis-arc-observable-query-http/SKILL.md +157 -0
  28. package/skills/cratis-arc-query-paging/LICENSE +2 -0
  29. package/skills/cratis-arc-query-paging/SKILL.md +173 -0
  30. package/skills/cratis-arc-react-page/LICENSE +2 -0
  31. package/skills/cratis-arc-react-page/SKILL.md +291 -0
  32. package/skills/cratis-arc-react-page/references/data-page.md +125 -0
  33. package/skills/cratis-arc-react-page/references/data-tables.md +136 -0
  34. package/skills/cratis-arc-react-page/references/dialogs.md +238 -0
  35. package/skills/cratis-arc-react-page/references/mvvm.md +150 -0
  36. package/skills/cratis-arc-react-page/references/queries-and-commands.md +200 -0
  37. package/skills/cratis-chronicle-cli-operations/LICENSE +2 -0
  38. package/skills/cratis-chronicle-cli-operations/SKILL.md +199 -0
  39. package/skills/cratis-chronicle-client-dotnet/LICENSE +2 -0
  40. package/skills/cratis-chronicle-client-dotnet/SKILL.md +433 -0
  41. package/skills/cratis-chronicle-client-elixir/LICENSE +2 -0
  42. package/skills/cratis-chronicle-client-elixir/SKILL.md +429 -0
  43. package/skills/cratis-chronicle-client-kotlin/LICENSE +2 -0
  44. package/skills/cratis-chronicle-client-kotlin/SKILL.md +498 -0
  45. package/skills/cratis-chronicle-client-typescript/LICENSE +2 -0
  46. package/skills/cratis-chronicle-client-typescript/SKILL.md +396 -0
  47. package/skills/cratis-chronicle-compliance/LICENSE +2 -0
  48. package/skills/cratis-chronicle-compliance/SKILL.md +246 -0
  49. package/skills/cratis-chronicle-event-constraints/LICENSE +2 -0
  50. package/skills/cratis-chronicle-event-constraints/SKILL.md +227 -0
  51. package/skills/cratis-chronicle-event-modeling/LICENSE +2 -0
  52. package/skills/cratis-chronicle-event-modeling/SKILL.md +157 -0
  53. package/skills/cratis-chronicle-event-specifications/LICENSE +2 -0
  54. package/skills/cratis-chronicle-event-specifications/SKILL.md +200 -0
  55. package/skills/cratis-chronicle-event-type-migration/LICENSE +2 -0
  56. package/skills/cratis-chronicle-event-type-migration/SKILL.md +235 -0
  57. package/skills/cratis-chronicle-mcp-inspection/LICENSE +2 -0
  58. package/skills/cratis-chronicle-mcp-inspection/SKILL.md +79 -0
  59. package/skills/cratis-chronicle-mcp-inspection/references/blocked-tools.md +12 -0
  60. package/skills/cratis-chronicle-mcp-inspection/references/observational-tools.md +8 -0
  61. package/skills/cratis-chronicle-multi-tenancy/LICENSE +2 -0
  62. package/skills/cratis-chronicle-multi-tenancy/SKILL.md +198 -0
  63. package/skills/cratis-chronicle-projection/LICENSE +2 -0
  64. package/skills/cratis-chronicle-projection/SKILL.md +162 -0
  65. package/skills/cratis-chronicle-projection/references/fluent-builder.md +126 -0
  66. package/skills/cratis-chronicle-projection/references/model-bound-attributes.md +118 -0
  67. package/skills/cratis-chronicle-reactor/LICENSE +2 -0
  68. package/skills/cratis-chronicle-reactor/SKILL.md +344 -0
  69. package/skills/cratis-chronicle-read-model/LICENSE +2 -0
  70. package/skills/cratis-chronicle-read-model/SKILL.md +193 -0
  71. package/skills/cratis-chronicle-read-model/references/queries.md +142 -0
  72. package/skills/cratis-chronicle-read-model-specifications/LICENSE +2 -0
  73. package/skills/cratis-chronicle-read-model-specifications/SKILL.md +176 -0
  74. package/skills/cratis-chronicle-reducer/LICENSE +2 -0
  75. package/skills/cratis-chronicle-reducer/SKILL.md +173 -0
  76. package/skills/cratis-chronicle-web-workbench/LICENSE +2 -0
  77. package/skills/cratis-chronicle-web-workbench/SKILL.md +177 -0
  78. package/skills/cratis-cli-terminal-workbench/LICENSE +2 -0
  79. package/skills/cratis-cli-terminal-workbench/SKILL.md +156 -0
  80. package/skills/cratis-code-review/LICENSE +2 -0
  81. package/skills/cratis-code-review/SKILL.md +177 -0
  82. package/skills/cratis-components-accessibility/LICENSE +2 -0
  83. package/skills/cratis-components-accessibility/SKILL.md +176 -0
  84. package/skills/cratis-components-schema-editor/LICENSE +2 -0
  85. package/skills/cratis-components-schema-editor/SKILL.md +238 -0
  86. package/skills/cratis-components-stepper-command-dialog/LICENSE +2 -0
  87. package/skills/cratis-components-stepper-command-dialog/SKILL.md +209 -0
  88. package/skills/cratis-components-styling/LICENSE +2 -0
  89. package/skills/cratis-components-styling/SKILL.md +221 -0
  90. package/skills/cratis-components-toolbar/LICENSE +2 -0
  91. package/skills/cratis-components-toolbar/SKILL.md +259 -0
  92. package/skills/cratis-documentation-writing/LICENSE +2 -0
  93. package/skills/cratis-documentation-writing/SKILL.md +121 -0
  94. package/skills/cratis-event-model-diagram/LICENSE +2 -0
  95. package/skills/cratis-event-model-diagram/SKILL.md +142 -0
  96. package/skills/cratis-fundamentals-concept/LICENSE +2 -0
  97. package/skills/cratis-fundamentals-concept/SKILL.md +198 -0
  98. package/skills/cratis-fundamentals-type-discovery/LICENSE +2 -0
  99. package/skills/cratis-fundamentals-type-discovery/SKILL.md +152 -0
  100. package/skills/cratis-governed-release-methodology/LICENSE +2 -0
  101. package/skills/cratis-governed-release-methodology/SKILL.md +297 -0
  102. package/skills/cratis-lens-browser-extension/LICENSE +2 -0
  103. package/skills/cratis-lens-browser-extension/SKILL.md +239 -0
  104. package/skills/cratis-performance-review/LICENSE +2 -0
  105. package/skills/cratis-performance-review/SKILL.md +139 -0
  106. package/skills/cratis-screenplay-model-authoring/LICENSE +2 -0
  107. package/skills/cratis-screenplay-model-authoring/SKILL.md +231 -0
  108. package/skills/cratis-security-review/LICENSE +2 -0
  109. package/skills/cratis-security-review/SKILL.md +141 -0
  110. package/skills/cratis-specification-by-example/LICENSE +2 -0
  111. package/skills/cratis-specification-by-example/SKILL.md +188 -0
  112. package/skills/cratis-specifications-csharp/LICENSE +2 -0
  113. package/skills/cratis-specifications-csharp/SKILL.md +230 -0
  114. package/skills/cratis-specifications-csharp/references/application-scenarios.md +191 -0
  115. package/skills/cratis-specifications-csharp/references/csharp-patterns.md +142 -0
  116. package/skills/cratis-specifications-csharp/references/integration-specs.md +126 -0
  117. package/skills/cratis-specifications-typescript/LICENSE +2 -0
  118. package/skills/cratis-specifications-typescript/SKILL.md +202 -0
  119. package/skills/cratis-specifications-typescript/references/typescript-patterns.md +168 -0
  120. package/skills/cratis-stage-rendering-and-sandbox/LICENSE +2 -0
  121. package/skills/cratis-stage-rendering-and-sandbox/SKILL.md +230 -0
  122. package/skills/cratis-studio-mcp-safety-guidance/LICENSE +2 -0
  123. package/skills/cratis-studio-mcp-safety-guidance/SKILL.md +69 -0
  124. package/skills/cratis-studio-mcp-safety-guidance/references/blocked-tools.md +12 -0
  125. package/skills/cratis-studio-mcp-safety-guidance/references/observational-tools.md +8 -0
@@ -0,0 +1,238 @@
1
+ # Dialogs reference
2
+
3
+ The Cratis dialog wrappers own command execution, validation timing, busy
4
+ state, and footer buttons. Never import `Dialog` from `primereact/dialog`.
5
+
6
+ The **components** live in `@cratis/components`; the **hooks, enums, and
7
+ context** live in `@cratis/arc.react/dialogs`. `@cratis/components` exports no
8
+ dialog hooks at all.
9
+
10
+ ```tsx
11
+ import { CommandDialog } from '@cratis/components/CommandDialog';
12
+ import { BusyIndicatorDialog, ConfirmationDialog, Dialog } from '@cratis/components/Dialogs';
13
+ import { DialogInitialFocus } from '@cratis/components/Dialogs';
14
+ import { InputTextField } from '@cratis/components/CommandForm';
15
+ import {
16
+ DialogButtons, DialogComponents, DialogResult, useDialog, useDialogContext,
17
+ } from '@cratis/arc.react/dialogs';
18
+ ```
19
+
20
+ ## Choose the dialog
21
+
22
+ - Confirm runs a command → `CommandDialog`.
23
+ - Confirm returns data without a command → `Dialog`.
24
+ - Asking the user to confirm, or blocking them while work runs → do not build a
25
+ dialog; raise the host-registered one by hook.
26
+ - A command split over named steps → `StepperCommandDialog`, see the
27
+ **cratis-components-stepper-command-dialog** skill.
28
+
29
+ ## Opening a dialog
30
+
31
+ ```tsx
32
+ const [AccountDialog, showAccountDialog] = useDialog(CreateAccountDialog);
33
+ // render <AccountDialog /> once in the tree
34
+ const [result, response] = await showAccountDialog({ accountId });
35
+ if (result === DialogResult.Ok) { /* ... */ }
36
+ ```
37
+
38
+ `useDialog<TResponse, TInput>(Component)` returns a three-element tuple — the
39
+ wrapper component, the show function, and the dialog context. Destructuring the
40
+ first two is the normal case. `showDialog(input?)` resolves to
41
+ `[DialogResult, TResponse?]` when the dialog closes.
42
+
43
+ Inside the dialog, prefer plain typed props for input and take `closeDialog`
44
+ from `useDialogContext<TResponse>()`:
45
+
46
+ ```tsx
47
+ const { request, closeDialog } = useDialogContext<AccountCreated>();
48
+ closeDialog(DialogResult.Ok, response);
49
+ ```
50
+
51
+ Dialogs that destructure `closeDialog` from a props interface extending
52
+ `DialogProps` remain valid.
53
+
54
+ ## The enums
55
+
56
+ ```ts
57
+ enum DialogResult { None = 0, Yes = 1, No = 2, Ok = 3, Cancelled = 4 }
58
+ enum DialogButtons { Ok = 1, OkCancel = 2, YesNo = 3, YesNoCancel = 4 }
59
+ enum DialogInitialFocus { Confirm = 1, Cancel = 2, Content = 3 }
60
+ ```
61
+
62
+ Branch on the enum member, never on button text.
63
+
64
+ ## `CommandDialog`
65
+
66
+ `CommandDialog` combines the command form and the dialog: its props are every
67
+ `Dialog` prop except `children`, plus every command-form prop except `children`
68
+ and `onBeforeExecute`, plus its own `onBeforeExecute` and `children`. `buttons`
69
+ defaults to `DialogButtons.OkCancel`.
70
+
71
+ The most-used props:
72
+
73
+ | Prop | Purpose |
74
+ | --- | --- |
75
+ | `command` | the generated command class |
76
+ | `title` | dialog header |
77
+ | `okLabel` / `cancelLabel` | button labels (`'Ok'` / `'Cancel'` by default) |
78
+ | `initialValues` | synchronous baseline; also the change-tracking baseline |
79
+ | `currentValues` | reactive overlay for late-loading values |
80
+ | `isValid` | extra validity gate on top of field validation |
81
+ | `onBeforeExecute` | transform values just before execute |
82
+ | `onSuccess` | receives the typed command response after a successful execute |
83
+ | `onValidationFailure` / `onFailed` | failure branches |
84
+ | `onConfirm` / `onCancel` / `onClose` | close gates |
85
+ | `validateOn` | `'blur' \| 'change' \| 'both'` |
86
+ | `validateOnInit` | validate on mount so pre-filled invalid values show immediately |
87
+ | `initialFocus` | which control receives focus when the dialog opens |
88
+ | `dismissable` | X, Escape, and backdrop dismissal |
89
+ | `pt` / `ptOptions` / `unstyled` | PrimeReact pass-through |
90
+
91
+ `CommandDialog.Column` exists for multi-column layouts:
92
+
93
+ ```tsx
94
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1.5rem' }}>
95
+ <CommandDialog.Column>
96
+ <InputTextField value={(c: UpdateProfile) => c.firstName} title='First name' />
97
+ </CommandDialog.Column>
98
+ <CommandDialog.Column>
99
+ <InputTextField value={(c: UpdateProfile) => c.lastName} title='Last name' />
100
+ </CommandDialog.Column>
101
+ </div>
102
+ ```
103
+
104
+ ### Execution flow
105
+
106
+ On confirm, `CommandDialog` applies `onBeforeExecute`, sets busy, executes the
107
+ command, and on failure calls `onValidationFailure(result.validationResults)`
108
+ or `onFailed(result)` and **keeps the dialog open**. On success it calls
109
+ `onSuccess(result.response)` and then the close gate.
110
+
111
+ ### `initialValues` versus `onBeforeExecute`
112
+
113
+ Validation runs against the pre-transform values, so a required value seeded in
114
+ `onBeforeExecute` never makes the form valid and the submit button stays
115
+ permanently disabled. Seed required values with `initialValues`; reserve
116
+ `onBeforeExecute` for transforms that do not affect validity, such as a
117
+ generated id.
118
+
119
+ `onBeforeExecute` is a **transformer** — it receives the current values and must
120
+ return them (a `TCommand` or a `Promise<TCommand>`). Returning `undefined`
121
+ keeps the current values and logs a console warning rather than executing with
122
+ undefined values, but it is still a bug: always return the values.
123
+
124
+ ### `onSuccess` versus `onConfirm`
125
+
126
+ `onSuccess(response)` fires only after the command succeeds and carries the
127
+ typed response — use it to close, refresh, or toast. `onConfirm()` receives no
128
+ command result; it is a close gate that must return exactly `true` to let the
129
+ wrapper close. Do not use `onConfirm` as a result handler.
130
+
131
+ ## `CommandForm` fields
132
+
133
+ Every user-entered command value must be bound through a `CommandForm` field
134
+ from `@cratis/components/CommandForm` (also published as
135
+ `@cratis/components/CommandForm/fields`, the same module). A raw PrimeReact
136
+ control bypasses the field wrapper, so validation never re-runs and the submit
137
+ button stays disabled.
138
+
139
+ The full catalog: `InputTextField`, `PasswordField`, `NumberField`,
140
+ `TextAreaField`, `CheckboxField`, `ToggleSwitchField`, `DropdownField`,
141
+ `MultiSelectField`, `RadioButtonField`, `RadioGroupField`, `CalendarField`,
142
+ `ChipsField`, `ColorPickerField`, `SliderField`, `RatingField`.
143
+
144
+ Shared field props: `value` (the accessor), `title` (the **label**),
145
+ `description`, `required`, `icon` (a `React.ReactElement`), `initialValue`,
146
+ `noInitialValue`, `populationKey`. `CheckboxField`, `ToggleSwitchField`,
147
+ `RadioButtonField`, and `RadioGroupField` additionally take their own `label`.
148
+
149
+ ```tsx
150
+ <InputTextField<CreateAccount> value={c => c.name} title='Account name' placeholder='Acme' />
151
+ <NumberField<CreateAccount> value={c => c.limit} title='Limit' min={0} />
152
+ <DropdownField<CreateAccount> value={c => c.status} title='Status' options={statuses} optionLabel='name' optionValue='id' />
153
+ <CheckboxField<CreateAccount> value={c => c.isActive} label='Active' />
154
+ <CalendarField<CreateAccount> value={c => c.opensOn} title='Opens on' showIcon />
155
+ ```
156
+
157
+ `DropdownField` requires `options`, `optionLabel`, and `optionValue`;
158
+ `RadioGroupField` requires all three too and takes `layout='horizontal' | 'vertical'`;
159
+ `RadioButtonField` is one component per option and requires `buttonValue`.
160
+
161
+ The `value` accessor doubles as the binding and as type-checked field
162
+ selection — renaming a command property produces a compile error at every
163
+ binding.
164
+
165
+ `AutoCommandForm` renders a field per command property from the generated
166
+ property descriptors, with `exclude` for the ones you do not want:
167
+
168
+ ```tsx
169
+ <AutoCommandForm command={RegisterInvoice} exclude={['invoiceId']} />
170
+ ```
171
+
172
+ ## `Dialog` — data without a command
173
+
174
+ ```tsx
175
+ export const RenameDialog = () => {
176
+ const { closeDialog } = useDialogContext<{ name: string }>();
177
+ const [name, setName] = useState('');
178
+
179
+ return (
180
+ <Dialog
181
+ title='Rename'
182
+ width='32rem'
183
+ isValid={name.trim().length > 0}
184
+ onConfirm={() => closeDialog(DialogResult.Ok, { name })}
185
+ onCancel={() => closeDialog(DialogResult.Cancelled)}>
186
+ <InputText value={name} onChange={event => setName(event.target.value)} autoFocus />
187
+ </Dialog>
188
+ );
189
+ };
190
+ ```
191
+
192
+ `DialogProps` defaults: `visible` `true`, `buttons` `DialogButtons.OkCancel`,
193
+ `initialFocus` `DialogInitialFocus.Confirm`, `width` `'450px'`, `isValid` true
194
+ when omitted, `isBusy` `false`, `okLabel` `'Ok'`, `cancelLabel` `'Cancel'`,
195
+ `yesLabel` `'Yes'`, `noLabel` `'No'`, `closeAriaLabel` `'Close'`.
196
+
197
+ Two behaviors worth knowing:
198
+
199
+ - `resizable` is accepted for compatibility but has **no effect** — the
200
+ PrimeReact 11 headless dialog has no resize.
201
+ - Passing a custom node to `buttons` instead of a `DialogButtons` member
202
+ removes the header close control, disables Escape, and means `onClose`,
203
+ `onCancel`, and `onConfirm` are never invoked. Close through
204
+ `useDialogContext().closeDialog(...)`, or set `dismissable` explicitly.
205
+
206
+ ### Keeping the dialog open on failure
207
+
208
+ `onConfirm` must return exactly `true` to close. Returning `false` keeps it
209
+ open. Annotate the handler as `Promise<boolean>` so TypeScript does not infer
210
+ `Promise<false | void>`:
211
+
212
+ ```tsx
213
+ const handleConfirm = async (): Promise<boolean> => {
214
+ const result = await command.execute();
215
+ return result.isSuccess;
216
+ };
217
+ ```
218
+
219
+ `onClose(result)` is the combined handler for both outcomes and closes unless it
220
+ returns `false`.
221
+
222
+ ### Initial focus
223
+
224
+ `DialogInitialFocus.Confirm` is the default and *arms* the confirm button — a
225
+ browser fires `click` from the `keydown` of Enter, so a held Enter confirms.
226
+ For a destructive dialog use `DialogInitialFocus.Cancel`, which focuses the
227
+ least destructive action and degrades to `Content` when the button set has
228
+ nothing to dismiss with. `DialogInitialFocus.Content` focuses the title so
229
+ nothing is armed. There is deliberately no "focus nothing" option — a modal
230
+ must move focus into itself.
231
+
232
+ ## Host-registered confirmation and busy indicators
233
+
234
+ `ConfirmationDialog` takes no props at all; it reads its title, message, and
235
+ buttons from the dialog request. `BusyIndicatorDialog` takes the busy-indicator
236
+ request. Register both once at the app root through `DialogComponents` and
237
+ raise them by hook — see the skill's step 5. From a view model, use the
238
+ injectable `IDialogs` abstraction instead of the hooks.
@@ -0,0 +1,150 @@
1
+ # MVVM reference
2
+
3
+ The Arc MVVM layer keeps page logic in plain TypeScript classes and leaves the
4
+ component declarative. `@cratis/arc.react.mvvm` builds on tsyringe for
5
+ injection and MobX for reactivity.
6
+
7
+ ## When to use it
8
+
9
+ Extract a view model as soon as a component has three or more `useState` calls,
10
+ any state-synchronizing `useEffect`, derived values computed from other state,
11
+ or state shared by prop drilling. A trivial presentational leaf needs none.
12
+
13
+ ## Setup
14
+
15
+ The app needs `experimentalDecorators`, `emitDecoratorMetadata`, and
16
+ `reflect-metadata` wired for the decorators to work.
17
+
18
+ Wrap the app (or the route root) in the MVVM provider — it configures MobX and
19
+ registers the shared bindings:
20
+
21
+ ```tsx
22
+ import { MVVM } from '@cratis/arc.react.mvvm';
23
+
24
+ <MVVM>
25
+ <App />
26
+ </MVVM>
27
+ ```
28
+
29
+ `<Arc>` does **not** include `<MVVM>`; both are needed.
30
+
31
+ The provider's initialization registers `IMessenger`, `INavigation`,
32
+ `IIdentityProvider`, and `ILocalStorage` into the root container.
33
+
34
+ ## The view model
35
+
36
+ ```ts
37
+ import { injectable } from 'tsyringe';
38
+
39
+ @injectable()
40
+ export class AccountsViewModel {
41
+ selected: AccountSummary | null = null;
42
+
43
+ select(account: AccountSummary | null) { this.selected = account; }
44
+
45
+ get selectedName() { return this.selected?.name ?? ''; }
46
+ }
47
+ ```
48
+
49
+ - `@injectable()` is tsyringe's — there is no Cratis-specific view-model
50
+ decorator.
51
+ - **`withViewModel` applies `makeAutoObservable` for you.** Do not call it in
52
+ the constructor and do not decorate plain assignments.
53
+ - Derived state is a getter, not a `useMemo` in the component.
54
+ - No JSX, no React state, no React hooks in a view model — inject the
55
+ abstraction instead.
56
+ - A non-trivial view model must be constructible directly in a spec.
57
+
58
+ ## `withViewModel`
59
+
60
+ ```tsx
61
+ import { withViewModel } from '@cratis/arc.react.mvvm';
62
+
63
+ export const AccountsPage = withViewModel(AccountsViewModel, ({ viewModel, props }) => (
64
+ /* read viewModel state inside JSX; call viewModel methods from handlers */
65
+ ));
66
+ ```
67
+
68
+ The render function receives `{ viewModel, props }`. Behind the scenes
69
+ `withViewModel` creates a child DI container per component instance, registers
70
+ the props, route parameters, query parameters, dialog context, command scope,
71
+ query scope, messenger, and dialogs into it, resolves the view model, makes it
72
+ observable, and renders the body inside an observer.
73
+
74
+ Any query, observable query, or command resolved through that container is
75
+ automatically configured with the microservice, API base path, and origin from
76
+ the Arc context.
77
+
78
+ ### Dereference late
79
+
80
+ Read `viewModel.property` **inside** JSX. Destructuring an observable at the top
81
+ of the component body captures the value and stops tracking changes.
82
+
83
+ `useCallback` and `useMemo` are unnecessary inside a `withViewModel` component —
84
+ the render is already wrapped in an observer, and their presence signals state
85
+ that belongs in the view model.
86
+
87
+ When passing an observable array into a raw non-observer child, materialize it
88
+ with `.slice()`. In practice prefer the observable data table, which handles
89
+ reactivity internally.
90
+
91
+ ## Props, route parameters, and query parameters
92
+
93
+ | Need | Mechanism |
94
+ | --- | --- |
95
+ | Props set once at mount | `@props readonly componentProps: TProps` |
96
+ | Props that change after mount | implement `IHandleProps<TProps>` → `handleProps(props)` |
97
+ | Route parameters | `@params readonly routeParams: RouteParams` |
98
+ | Route parameters that change | implement `IHandleParams<T>` → `handleParams(params)` |
99
+ | Query-string parameters | `@queryParams readonly query: QueryParams` |
100
+ | Query parameters that change | implement `IHandleQueryParams<T>` → `handleQueryParams(queryParams)` |
101
+
102
+ The handler method names are `handleProps`, `handleParams`, and
103
+ `handleQueryParams`. Each is invoked on mount and again whenever the incoming
104
+ value actually differs.
105
+
106
+ ```ts
107
+ @injectable()
108
+ export class AccountDetailsViewModel implements IHandleProps<AccountDetailsProps> {
109
+ account: AccountSummary | null = null;
110
+
111
+ handleProps(props: AccountDetailsProps) { this.account = props.account; }
112
+ }
113
+ ```
114
+
115
+ Route and query parameters arrive from the URL as strings. `withViewModel`
116
+ deserializes them into the class you declared on the decorated constructor
117
+ parameter, so declare a real parameter class with typed properties rather than
118
+ an inline object type — that class is what drives the conversion.
119
+
120
+ ## Injectable abstractions
121
+
122
+ Never touch a browser or React global from a view model.
123
+
124
+ | Abstraction | Package | Replaces |
125
+ | --- | --- | --- |
126
+ | `IMessenger` | `@cratis/arc.react.mvvm/messaging` | cross-component selection, not React context |
127
+ | `IDialogs` | `@cratis/arc.react.mvvm/dialogs` | imperative confirmation and busy dialogs |
128
+ | `IIdentityProvider` | `@cratis/arc/identity` | identity, instead of the `useIdentity` hook |
129
+ | `INavigation`, `ILocalStorage` | `@cratis/arc.react.mvvm/browser` | URL navigation, `localStorage` |
130
+ | `IViewModelDetached` | `@cratis/arc.react.mvvm` | teardown via `detached()` |
131
+
132
+ `IDialogs` exposes `show(input)`, `showConfirmation(title, message, buttons)`
133
+ returning a `DialogResult`, and `showBusyIndicator(title, message)` returning a
134
+ busy indicator with a `close()` method. For a user-triggered dialog that opens
135
+ a React command dialog, use `useDialog` in the component instead.
136
+
137
+ `IViewModelDetached.detached()` runs on unmount — unsubscribe subscriptions and
138
+ clear timers there.
139
+
140
+ ## `observer`
141
+
142
+ Import `observer` from `@cratis/arc.react.mvvm`, never directly from
143
+ `mobx-react`. The package re-exports it deliberately so the observer boundary
144
+ stays a single, swappable dependency.
145
+
146
+ ## Testing
147
+
148
+ A view model is a plain class: construct it directly, pass small typed fakes or
149
+ stubs for its dependencies, and assert on state transitions and getters. See the
150
+ **cratis-application-react-specifications** skill.
@@ -0,0 +1,200 @@
1
+ # Queries and commands reference
2
+
3
+ A Debug build generates one typed proxy class per query and per command. The
4
+ proxy exposes static React hooks; the page calls those. Never edit a generated
5
+ file — fix the C# source and rebuild.
6
+
7
+ ## Query proxy hooks
8
+
9
+ | Static | When |
10
+ | --- | --- |
11
+ | `<Query>.use(args?, sorting?)` | snapshot query, re-renders on change |
12
+ | `<Query>.useWithPaging(pageSize, args?, sorting?)` | server-side paging |
13
+ | `<Query>.useSuspense(...)` | suspense-aware; must render inside a query boundary |
14
+ | `<Query>.useSuspenseWithPaging(pageSize, ...)` | suspense plus paging |
15
+ | `<ObservableQuery>.use(...)` | live query pushed over the query hub |
16
+ | `<ObservableQuery>.useChangeStream(args?, getKey?, sorting?)` | item-level deltas |
17
+ | `<Query>.when(condition).use(args)` | conditional — never wrap a hook in `if` |
18
+
19
+ ### Return tuples
20
+
21
+ These shapes are load-bearing. The observable variants have **no perform
22
+ function**:
23
+
24
+ | Hook | Returns |
25
+ | --- | --- |
26
+ | `useQuery(query, args?, sorting?, isEnabled?)` | `[result, perform, setSorting]` |
27
+ | `useQueryWithPaging(query, paging, args?, sorting?, isEnabled?)` | `[result, perform, setSorting, setPage, setPageSize]` |
28
+ | `useSuspenseQuery(...)` | `[result, perform, setSorting]` |
29
+ | `useSuspenseQueryWithPaging(...)` | `[result, perform, setSorting, setPage, setPageSize]` |
30
+ | `useObservableQuery(query, args?, sorting?, isEnabled?)` | `[result, setSorting]` |
31
+ | `useObservableQueryWithPaging(query, paging, ...)` | `[result, setSorting, setPage, setPageSize]` |
32
+ | `useChangeStream(query, args?, getKey?, sorting?, isEnabled?)` | `ChangeSet<T>` |
33
+
34
+ `setSorting`, `setPage`, and `setPageSize` all return a promise.
35
+
36
+ The underlying hooks live in `@cratis/arc.react/queries` if you need them
37
+ directly.
38
+
39
+ ### The result object
40
+
41
+ Every query hook returns a result carrying:
42
+
43
+ `data`, `paging`, `isSuccess`, `isAuthorized`, `isValid`, `isPerforming`,
44
+ `isReady`, `hasData`, `hasExceptions`, `validationResults`,
45
+ `exceptionMessages`, `exceptionStackTrace`, `changeSet`.
46
+
47
+ Guard rendering with `hasData` rather than checking `data.length` — a
48
+ conditional query that has not fired returns an empty result with
49
+ `hasData: false`.
50
+
51
+ ### Paging
52
+
53
+ ```ts
54
+ class Paging { constructor(page?: number, pageSize?: number); page: number; pageSize: number; get hasPaging(): boolean }
55
+ class Sorting { constructor(field: string, direction: SortDirection); get hasSorting(): boolean }
56
+ enum SortDirection { unspecified = 0, ascending = 1, descending = 2 }
57
+ ```
58
+
59
+ The result's paging block is a different shape from the request's — note
60
+ `size`, not `pageSize`:
61
+
62
+ ```ts
63
+ class PagingInfo { page: number; size: number; totalItems: number; totalPages: number }
64
+ ```
65
+
66
+ `page` is zero-based. `totalItems` is the total count. A read model returning a
67
+ queryable gets server-side paging and sorting for free — use it whenever a list
68
+ can grow. Over the wire the parameters are `page`, `pageSize`, `sortBy`, and
69
+ `sortDirection`.
70
+
71
+ ### Change streams
72
+
73
+ ```ts
74
+ class ChangeSet<T> { readonly added: T[]; readonly replaced: T[]; readonly removed: T[] }
75
+ ```
76
+
77
+ Without `getKey` removed items cannot be identified and everything arrives as
78
+ `added`; `getKey` is what enables `replaced` and `removed` detection. Change
79
+ streams exist only on observable queries returning a collection.
80
+
81
+ ### Suspense
82
+
83
+ `useSuspense` throws a promise while loading and a typed error on failure, so it
84
+ must render inside `QueryBoundary` (`@cratis/arc.react/queries`), which combines
85
+ suspense with the query error boundary. It re-suspends on refresh, sorting, and
86
+ paging changes because the cache entry is cleared. In test teardown call
87
+ `clearSuspenseQueryCache()` and `clearSuspenseObservableQueryCache()`.
88
+
89
+ `QueryFailed` and `QueryUnauthorized` are the thrown error types; `QueryFailed`
90
+ carries `exceptionMessages` for logging.
91
+
92
+ ### Query scope
93
+
94
+ `QueryScope` plus `useQueryScope()` aggregates one `isPerforming` flag across
95
+ the queries rendered inside it. Members register automatically and scopes nest,
96
+ with an inner scope reporting to the nearest outer one.
97
+
98
+ ## Observable query transport
99
+
100
+ The `<Arc>` provider defaults `queryTransportMethod` to server-sent events;
101
+ the library-level default without the provider is WebSocket. Both run through a
102
+ multiplexed hub by default (`queryDirectMode` is `false`), so many observable
103
+ queries share one connection. Server-sent-event hub connections are capped —
104
+ raising `queryConnectionCount` above four warns, because HTTP/1.1 limits
105
+ concurrent SSE streams.
106
+
107
+ `observableQueryTransferMode` defaults to delta: the server sends a change set
108
+ and the client reconstructs the collection from the previously cached result.
109
+ Set it to full to receive every item as `added` on each push.
110
+
111
+ Identical subscriptions share one connection through the query instance cache,
112
+ which retains an unsubscribed entry briefly (30 seconds by default) so a
113
+ remount does not re-subscribe. After login or logout call `reconnectQueries()`
114
+ from the Arc context to force re-subscription with the new identity.
115
+
116
+ ## Command proxy
117
+
118
+ ```tsx
119
+ const [command, setCommandValues, clearCommandValues] = RegisterAccount.use();
120
+ command.name = 'Acme'; // or setCommandValues({ name: 'Acme' })
121
+ const result = await command.execute();
122
+ ```
123
+
124
+ `use(initialValues?)` returns a three-element tuple. The instance also exposes
125
+ `validate()`, `clear()`, `setInitialValues(values)`, `revertChanges()`, and
126
+ `hasChanges`.
127
+
128
+ ### Read the result by the granular flag
129
+
130
+ | Flag | Meaning | Response |
131
+ | --- | --- | --- |
132
+ | `isSuccess` | authorized, valid, and no exception | happy path |
133
+ | `isAuthorized` | roles or policy rejected the call | redirect to login, or "not allowed" |
134
+ | `isValid` | validation failed — `validationResults` carries the messages | render inline field errors |
135
+ | `hasExceptions` | the handler threw — `exceptionMessages` carries diagnostics | generic error, and log it |
136
+
137
+ The result also carries `correlationId`, `authorizationFailureReason`,
138
+ `exceptionStackTrace`, and the typed `response`. Fluent callbacks
139
+ `onSuccess`, `onFailed`, `onException`, `onUnauthorized`, and
140
+ `onValidationFailure` each return the result for chaining.
141
+
142
+ ```tsx
143
+ const result = await command.execute();
144
+ if (!result.isAuthorized) { redirectToLogin(); return; }
145
+ if (!result.isValid) { /* validationResults -> inline field errors */ return; }
146
+ if (result.hasExceptions) { console.error(result.exceptionMessages); return; }
147
+ ```
148
+
149
+ A validation result is:
150
+
151
+ ```ts
152
+ class ValidationResult {
153
+ severity: ValidationResultSeverity; // Unknown 0, Information 1, Warning 2, Error 3
154
+ message: string;
155
+ members: string[]; // the properties the message applies to
156
+ state: unknown;
157
+ reason: ValidationResultReason; // 'rule' by default
158
+ reasonDetail?: string;
159
+ }
160
+ ```
161
+
162
+ Render per-field errors from `members` and `message`. Never branch on the raw
163
+ message text. `exceptionMessages` and `exceptionStackTrace` are for logging,
164
+ never for users.
165
+
166
+ `toastCommandResult(result, options)` from `@cratis/components/Notifications`
167
+ collapses the whole branch into one call — mount a `<Toaster />` near the app
168
+ root and it maps every flag to the right toast without showing stack traces. It
169
+ returns `true` when the command succeeded:
170
+
171
+ ```tsx
172
+ if (toastCommandResult(result, { successTitle: 'Account registered' })) refresh();
173
+ ```
174
+
175
+ For ad-hoc notifications the imperative `toast.success/info/warn/error` each
176
+ take an **options object**, not a bare string:
177
+ `toast.info({ title: 'Saved', description: 'Your changes were saved.' })`.
178
+
179
+ `CommandDialog` performs all of this itself — the flags matter most when
180
+ executing a command outside a dialog.
181
+
182
+ ### Command scope
183
+
184
+ `CommandScope` plus `useCommandScope()` (`@cratis/arc.react/commands`)
185
+ aggregates change and execution state across several commands on one screen.
186
+ Members register automatically, scopes nest, and the scope is injectable into a
187
+ view model as `ICommandScope`. It exposes `hasChanges`, `isPerforming`,
188
+ `hasValidationFailures`, `hasExceptions`, per-command and aggregated failures,
189
+ `execute()` (which runs only the commands that have changes), and
190
+ `revertChanges()`.
191
+
192
+ ## `<Arc>` configuration
193
+
194
+ The top-level provider configures the microservice name, API base path, and
195
+ query transport. Its props include `microservice`, `origin`, `basePath`,
196
+ `apiBasePath`, `httpHeadersCallback` (merged into every request — bearer and
197
+ tenant headers), `detailsType`, `queryTransportMethod`, `queryConnectionCount`,
198
+ `queryDirectMode`, `observableQueryTransferMode`, and `queryCacheRetentionMs`.
199
+ `QueryTransportMethod` and `ObservableQueryTransferMode` import from
200
+ `@cratis/arc`; `Arc` imports from `@cratis/arc.react`.
@@ -0,0 +1,2 @@
1
+ Copyright (c) Cratis. All rights reserved.
2
+ Licensed under the MIT license. See LICENSE file in the project root for full license information.