@opetope/react 0.10.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,12 +19,12 @@ No GitHub access is needed to read those installed guides.
19
19
 
20
20
  ```tsx
21
21
  import { defineModel } from '@opetope/core';
22
- import type { Call, Readable } from '@opetope/core';
22
+ import type { Command, Readable } from '@opetope/core';
23
23
  import { defineSlot, requiresModels, Slot, useModel } from '@opetope/react';
24
24
 
25
25
  const CounterModel = defineModel<{
26
26
  readonly count: Readable<number>;
27
- readonly increment: Call<void, void>;
27
+ readonly increment: Command<void, void>;
28
28
  }>('example.counter.model');
29
29
  const CounterSlot = defineSlot<{ readonly label: string }>('example.counter.slot');
30
30
 
@@ -39,7 +39,7 @@ const CounterButton = requiresModels([CounterModel])(({ label }: { readonly labe
39
39
 
40
40
  return (
41
41
  <button disabled={inFlight} onClick={() => void run()}>
42
- {outcome?.status === 'failed' ? 'Retry' : `${label}: ${count}`}
42
+ {outcome?.kind === 'failed' ? 'Retry' : `${label}: ${count}`}
43
43
  </button>
44
44
  );
45
45
  });
@@ -57,10 +57,10 @@ rejection (D116, D292).
57
57
  `outcome` is the last settled non-cancelled outcome of this consumer: `ok` takes the place of a previous `failed`
58
58
  and a `failed` the place of a previous `ok`, a `cancelled` outcome moves nothing, and starting a run clears nothing,
59
59
  so a failure stays readable while the retry is in flight. Before the first settle it is `undefined`. Read it through
60
- its discriminant — `outcome?.status === 'failed' ? outcome.error : null`.
60
+ its discriminant — `outcome?.kind === 'failed' ? outcome.error : null`.
61
61
 
62
62
  A command without input binds to an event through an arrow: `onClick={() => void logout.run()}`. The arrow is what
63
- keeps the React event out of the Call and the promise from hanging; `onClick={logout.run}` does not compile, because
63
+ keeps the React event out of the Command and the promise from hanging; `onClick={logout.run}` does not compile, because
64
64
  a `MouseEvent` is not a `void` input. Use `run(input, options?)` for data, an outcome, callbacks or a per-run signal.
65
65
  Where a lint config bans an inline arrow prop (`react-perf/jsx-no-new-function-as-prop`, `react/jsx-no-bind`), hoist
66
66
  it with `useCallback(() => void logout.run(), [logout.run])`: the dependency is `run`, never the hook object (D290).
@@ -70,39 +70,25 @@ changes as `inFlight` or `outcome` changes, so an effect or a memo depends on `r
70
70
  `opetope/no-command-in-deps` holds this for the author (D290). A `run` captured from a replaced or unmounted
71
71
  consumer is fenced; it does not start work on the replacement.
72
72
 
73
- The hook schedules nothing. Every `run` reaches the call, and the policy the call was created with decides what
74
- happens to it (D203). For an absolute value setter the model declares `policy: 'latest'` in `context.call`, and the
75
- newest input then replaces the waiting one there (D185). What the consumer keeps is its own: an already-aborted
76
- input signal is refused as `cancelled` without reaching the call, a run of an unmounted consumer is refused the same
77
- way, `inFlight` is true while any run this consumer started is unsettled, and every run carries its own callbacks
78
- and signal.
73
+ The hook schedules nothing. Every `run` reaches the command, and the order the command was declared with decides
74
+ what happens to it (D203). For an absolute value setter the model declares `concurrency: 'latest'` in
75
+ `context.command`, and the newest input then replaces the waiting one there (D185, D387). What the consumer keeps
76
+ is its own: an already-aborted input signal is refused as `cancelled` without reaching the command, a run of an
77
+ unmounted consumer is refused the same way, `inFlight` is true while any run this consumer started is unsettled,
78
+ and every run carries its own callbacks and signal.
79
79
 
80
80
  To share equivalent pending or running work, the model declares `dedupe: true` or `dedupe: input => key`
81
- on `context.call`. Sharing keeps the first input and each caller's independent cancellation; neither form may
82
- be combined with `latest` (D277). These are Call options, not hook options.
81
+ on `context.command`. Sharing keeps the first input and each caller's independent cancellation; neither form may
82
+ be combined with `latest` (D277). These are Command options, not hook options.
83
83
 
84
- For several commands use an object selection (D174):
84
+ For several commands a component writes `useCommand` per command, or one `useModel` selection, which already
85
+ answers ready hooks. `useCommands` is gone: a record over the one `CommandHook` was sugar that saved hook calls,
86
+ not a second mechanism, and the set that is really wanted is a selection in the model, where `ctx.select` gives it
87
+ an owner and a lifetime (D388).
85
88
 
86
- ```tsx
87
- const { save, remove } = useCommands({ save: model.save, remove: other.remove });
88
-
89
- void save.run(input);
90
- const saving = save.inFlight;
91
- ```
92
-
93
- Import `useCommands` from `@opetope/react`. Each selected field returns the same `CommandHook` as `useCommand`,
94
- with its own input/output types and its own `inFlight` and `outcome`. Keys are local UI names; commands may come from
95
- different granted models. There is no second argument: each call carries its own policy. Its own case is a key set
96
- the component does not know in advance — hooks are not called in a loop; a fixed set is `useCommand` per word or one
97
- `useModel` selection. It is proven by the packaged consumer of `ci:pack` and is a candidate to leave this entry
98
- until a second application is measured (D301).
99
-
100
- An inline object needs no `useMemo`. Reordering keys or updating a sibling preserves an unchanged field's object
101
- and `.run`. Adding or removing keys does not change the number of React hooks. Removing a key or replacing its
102
- command starts a fresh local status for that field; abandoned renders leave the
103
- committed selection intact. Two aliases of one Call have independent local statuses, just like two `useCommand`
104
- consumers; they use the same Call policy and mount command record (D197, D203). A group introduces no shared busy state, queue or transaction; controls that overwrite one value still
105
- need one semantic command. Only own enumerable properties are selected, including symbol keys.
89
+ Two aliases of one Command have independent local statuses, just like two `useCommand` consumers; they use the same
90
+ Command concurrency and mount command record (D197, D203). A selection introduces no shared busy state, queue or
91
+ transaction; controls that overwrite one value still need one semantic command.
106
92
 
107
93
  ## Selecting data and commands
108
94
 
@@ -110,10 +96,22 @@ need one semantic command. Only own enumerable properties are selected, includin
110
96
 
111
97
  A model selection can use a named interface without an index signature (D217). Its result is a flat data record;
112
98
  arrays, functions, constructors and built-in collection/date/promise objects are not selection records.
113
- `read(source)` returns its snapshot; `read(source, project)` returns a projection. Authentic Calls selected as record
114
- fields become the same `CommandHook` as `useCommands`, including independent alias statuses and stable `.run`.
99
+ `read(source)` returns its snapshot; `read(source, project)` returns a projection. Authentic Commands selected as
100
+ record fields become the same `CommandHook` as `useCommand`, including independent alias statuses and stable `.run`.
115
101
  A single selected command needs no nested hooks:
116
102
 
103
+ <!--example
104
+ import type { Command } from '@opetope/core';
105
+ import { defineModel } from '@opetope/core';
106
+ import { useModel } from '@opetope/react';
107
+
108
+ const AuthModel = defineModel<{ readonly logout: Command<void, void> }>('example.auth.model');
109
+
110
+ const LogOutButton = () => {
111
+ /* … */
112
+ };
113
+ -->
114
+
117
115
  ```tsx
118
116
  const { logout } = useModel(AuthModel, auth => ({ logout: auth.logout }));
119
117
  return (
@@ -136,13 +134,26 @@ values — that field answers a `ResourceHook`, and one lease per Resource ident
136
134
  keys name one Resource (D321, D359) — and nothing else: a
137
135
  `read(resource.state)` leases nothing, and neither does the one-argument form. Combining hooks does not promise fewer
138
136
  source subscriptions or faster renders; a raw `useModel` import now also includes the selection implementation.
139
- There is no additional Call scheduler.
137
+ There is no additional Command scheduler.
140
138
 
141
139
  ## Application host
142
140
 
143
141
  The host opens a feature graph with `openApplication`. Ready features publish their UI contributions atomically;
144
142
  the ordinary `Slot` mounts them into consumer-owned targets:
145
143
 
144
+ <!--example
145
+ import { defineSlot, Slot } from '@opetope/react';
146
+
147
+ const exampleFooterSlot = defineSlot('example.footer');
148
+ const exampleSettingsSlot = defineSlot('example.settings');
149
+
150
+ const ExampleShell = () => (
151
+ <>
152
+ /* … */
153
+ </>
154
+ );
155
+ -->
156
+
146
157
  ```tsx
147
158
  <Slot target={exampleFooterSlot} />
148
159
  <Slot target={exampleSettingsSlot} />
@@ -152,6 +163,26 @@ The application owns feature lifetimes; each feature's UI mounts through its con
152
163
 
153
164
  ## Feature demand boundary
154
165
 
166
+ <!--example
167
+ import type { ReactNode } from 'react';
168
+ import type { Resource } from '@opetope/core';
169
+ import { defineSlot } from '@opetope/react';
170
+ import type { FeatureDemandSource } from '@opetope/react/integration';
171
+
172
+ type Tasks = Readonly<{ id: string }>;
173
+ type ConfirmActionProps = Readonly<{ itemId: string; onConfirmed: () => void }>;
174
+
175
+ declare const host: FeatureDemandSource<Readonly<{ exports: Readonly<{ tasks: Resource<Tasks, 'none'> }> }>>;
176
+ declare const itemId: string;
177
+ declare const onConfirmed: () => void;
178
+ declare const children: ReactNode;
179
+ declare const Spinner: () => null;
180
+ declare const Failure: (props: Readonly<{ error: unknown; onRetry: () => void }>) => null;
181
+ declare const TasksResourceLease: (props: Readonly<{ children: ReactNode; resource: Resource<Tasks, 'none'> }>) => null;
182
+
183
+ const confirmActionContentSlot = defineSlot<ConfirmActionProps>('example.confirmAction.content');
184
+ -->
185
+
155
186
  ```tsx
156
187
  import { Slot } from '@opetope/react';
157
188
  import { FeatureBoundary, useFeatureRetry } from '@opetope/react/integration';
@@ -211,27 +242,30 @@ frame of the instance that gave the contribution. The order and the stable key c
211
242
  React introduces neither a second comparator nor a second lifetime registry. The packaged consumer of `ci:pack`
212
243
  exercises that caching and routes one contribution through it (D301).
213
244
 
214
- `useResource(resource)` answers `{ invalidate, pagination, refresh, retry, state }`: the `ResourceState` through
245
+ `useResource(resource)` answers `{ pagination, refresh, reset, retry, state }`: the `ResourceState` through
215
246
  `useSyncExternalStore`, the lease `resource.acquire()` answers taken in a commit effect for as long as the component
216
247
  is mounted, and the three verbs as ordinary `CommandHook` consumers (D359). An interrupted render therefore opens
217
248
  nothing, and when the reference changes the state follows the new Resource while the effect cleanup releases the
218
249
  previous lease. The number of React hooks does not depend on the Resource: a declaration without `pagination`
219
250
  subscribes to a constant source and keeps a `loadNext` that answers `skipped`, while a declaration that carried the
220
251
  capability answers `pagination` as `{ loadNext, state }` (D356). A field of a `useModel` selection whose value is a
221
- Resource answers the same hook and takes the same lease, so a model declares no `Call<void, void>` around
252
+ Resource answers the same hook and takes the same lease, so a model declares no `Command<void, void>` around
222
253
  `resource.refresh()` to give a button its `inFlight`. `useReadable(resource)` is a compile error, because a Resource
223
254
  is not a `Readable`; `useReadable(resource.state)` and a `read(resource.state)` in a selector stay passive and lease
224
255
  nothing — observing and holding are independent questions, so a selector that reads the state and a `useResource`
225
256
  beside it subscribe twice (D349). The compile-checked example of specification §2.13 shows both sides of that
226
257
  (D301, D321).
227
258
 
228
- `refresh.run()`, `retry.run()` and `invalidate.run()` reach the Resource, which is owned by the model that declared
259
+ `refresh.run()`, `retry.run()` and `reset.run()` reach the Resource, which is owned by the model that declared
229
260
  it: `inFlight` is true until the outcome of the operation settles, `outcome` carries that `ResourceOperationOutcome`
230
261
  as the `value` of an `ok` status, and `run(undefined, { signal })` cancels the wait of this consumer and not the work
231
262
  the model owns. Two consumers of one Resource keep separate statuses, and `run` keeps its identity while the Resource
232
- does. A render that caught up with retirement answers the last state it saw — what `useReadable` does with a source
233
- its owner closed — and a selection answers a closed record instead of throwing mid-render, so the tree unmounts and
234
- releases its lease rather than breaking the render (D359).
263
+ does. A render that caught up with retirement renders the terminal record — `idle` with reason `retired` for a Resource
264
+ a model owns, `unleased` for a feature's facade, which is a reference that outlives the instance — and unmounts,
265
+ releasing its lease (D359, D364). Nothing here refuses a read because a lifetime ended: a source whose owner retired
266
+ stops and keeps answering its last value, so `useReadable`, `useSelector` with an inline selector and a selection's
267
+ `read` each read on and need no memory of their own. A refusal that remains is work that failed — a state that
268
+ failed, a broken pagination cursor — and it still reaches the render.
235
269
 
236
270
  Direct component props, a contribution's `props` adapter and its model's props `Readable` share the same
237
271
  mount-owned snapshot. Incoming slot props publish in layout before paint, so direct and adapted renders cannot
@@ -248,6 +282,14 @@ Every mount is an error boundary of its own contribution (D256). A render or com
248
282
  reported to the feature that published the contribution, and leaves the other mounts of the target untouched.
249
283
  `ContributionBoundary` states once, above every slot, what a failed mount shows:
250
284
 
285
+ <!--example
286
+ import { defineSlot, Slot } from '@opetope/react';
287
+
288
+ declare const FailedContribution: (props: Readonly<{ error: unknown; onRetry: () => void }>) => null;
289
+
290
+ const applicationSurface = defineSlot('example.surface');
291
+ -->
292
+
251
293
  ```tsx
252
294
  import { ContributionBoundary } from '@opetope/react/integration';
253
295
 
@@ -273,7 +315,7 @@ dependency. `scenario.mount(target, { props })` uses the published Slot contribu
273
315
  while targets without props omit it, exactly as with `Slot` (D217). Fixture commands do not bypass authority.
274
316
 
275
317
  As with `openApplication`, omit `conditions` when no enabled feature requires a host-bound condition. Conditions
276
- computed through `from`/`select` bind automatically; external conditions still require their sources (D271).
318
+ computed through `source`/`select` bind automatically; external conditions still require their sources (D271).
277
319
 
278
320
  The synchronous constructor exposes `ready`, so a test can inspect a pending lazy body before readiness.
279
321
  `waitFor(snapshot => predicate, { label, timeoutMs, pollIntervalMs })` wakes on inspection changes and also polls
@@ -292,17 +334,18 @@ a scoped zero-count assertion, without proving absence of arbitrary host, UI or
292
334
  application imports and internal renderer references. A failed cleanup promise can retain original errors and retry
293
335
  capabilities; a caller that keeps `mounted.host` also keeps its own renderer result.
294
336
 
295
- The inspection schemas are `opetope.devtools-graph/5` and `opetope.devtools-frame/4`, with optional
337
+ The inspection schemas are `opetope.devtools-graph/5` and `opetope.devtools-frame/5`, with optional
296
338
  `opetope.runtime-activity/4` snapshots (D266, D275, D344, D358). Within one session,
297
339
  a frame without `activity` preserves the previous activity; a full snapshot/reset without it clears that observation
298
340
  (D216). Activity-bearing frames replace the previous activity in full.
299
341
  Use matching runtime/devtools versions: previous graph/frame and activity revisions are rejected; weak-edge presence requires graph `/5` and frame `/4`. Activity identifies the execution,
300
- actual feature generation, physical Calls, exact current lane blockers, and for every registered Resource its
342
+ actual feature generation, physical calls, exact current lane blockers, and for every registered Resource its
301
343
  `kind`, `lifetime`, `epoch`, `state`, `activity` with its `operation`, `leases`, `subscribers`, `pagination` and the
302
344
  `epoch` of each physical load — there is no `attempt` and no numeric generation, because a Resource runs one logical
303
345
  attempt and recovery belongs to the transport (D358). Host demand and UI models are unknown. `freshness`
304
346
  and `truncated` distinguish a complete live view from a partial or detached one. A closed session is stale;
305
- `closed: true` requires successful physical application drain. No control authority or product payload is added.
347
+ `closed: true` says that the close of the application finished, whether its physical drain succeeded or refused, and
348
+ the snapshot of a closed application names no feature and no Resource (D427). No control authority or product payload is added.
306
349
  Activity output is bounded by record capacity. Snapshot collection still visits registered owners, executors and
307
350
  resources, so capacity does not bound traversal cost. Collection stops once truncation is proven;
308
351
  idle executors may still require traversal to establish completeness. Normal call dispatch allocates no diagnostic record with
@@ -315,14 +358,14 @@ observation disabled. Graph frames remain bounded by the existing ring capacity.
315
358
  (D285):
316
359
 
317
360
  - `renderSlot(target, { contribution, models, props })` mounts the published target or one fixture contribution;
318
- - `command(run)` produces an authentic `Call` for a model fixture, plus the contribution binding a mount reads;
319
- - `runCall(target, input?, options?)` executes an existing authentic `Call` without mounting React (D261);
361
+ - `command(run)` produces an authentic `Command` for a model fixture, plus the contribution binding a mount reads;
362
+ - `runCommand(target, input?, options?)` executes an existing authentic `Command` without mounting React (D261);
320
363
  - `testReadable(initial)`, `testResourceEpoch()`, `openModel`, `settled`, `waitFor`, `yieldTurn` and `eventually`
321
364
  come from the entries below, for the part of a test that has no UI in it — `testResourceEpoch()` is what a
322
365
  component fixture writing a `ResourceState` by hand puts in its `epoch` (D362).
323
366
 
324
367
  A test that renders nothing should import `@opetope/runtime/testing` directly: `renderSlot` and the binding half of
325
- `command` are all that needs React here. The Call a fixture mints is the `command` of `@opetope/core/testing`
368
+ `command` are all that needs React here. The Command a fixture mints is the `command` of `@opetope/core/testing`
326
369
  (D300), and this word is the one name of the re-export chain that does not pass through: the React `command`
327
370
  shadows it with the stronger one.
328
371
 
@@ -331,21 +374,21 @@ application as contributions (D85). It is not a test spelling of `<Slot>` — it
331
374
  `reporter`, because a component test has no feature to report to — and the packaged consumer of `ci:pack` mounts it
332
375
  from the real archive (D301).
333
376
 
334
- A test that previously mounted `useCommand` only to invoke a Call can use `runCall` directly. The result is the
335
- Call's output, while a UI test still checks the `CommandOutcome` returned by the mounted consumer:
377
+ A test that previously mounted `useCommand` only to invoke a Command can use `runCommand` directly. The result is the
378
+ Command's output, while a UI test still checks the `CommandOutcome` returned by the mounted consumer:
336
379
 
337
380
  ```ts
338
381
  import assert from 'node:assert/strict';
339
382
  import { defineModel } from '@opetope/core';
340
- import type { Call } from '@opetope/core';
383
+ import type { Command, ModelCommandContext } from '@opetope/core';
341
384
  import { defineFeature, openFeature } from '@opetope/runtime';
342
- import { runCall } from '@opetope/react/testing';
385
+ import { runCommand } from '@opetope/react/testing';
343
386
 
344
- const Arithmetic = defineModel<{ readonly double: Call<number, number> }>('example.testing.model');
387
+ const Arithmetic = defineModel<{ readonly double: Command<number, number> }>('example.testing.model');
345
388
  const arithmetic = defineFeature('example.testing.arithmetic', {
346
389
  own: ({ model }) => ({
347
390
  arithmetic: model(Arithmetic, context => ({
348
- double: context.call((input: number) => input * 2),
391
+ double: context.command(({ input }: ModelCommandContext<number>) => input * 2),
349
392
  })),
350
393
  }),
351
394
  exports: ({ own }) => ({ double: own.arithmetic.double }),
@@ -353,35 +396,35 @@ const arithmetic = defineFeature('example.testing.arithmetic', {
353
396
  const instance = openFeature(arithmetic, { imports: {}, reporter: () => undefined });
354
397
  try {
355
398
  const ready = await instance.ready;
356
- const result = await runCall(ready.exports.double, 3);
399
+ const result = await runCommand(ready.exports.double, 3);
357
400
  assert.equal(result, 6);
358
401
  } finally {
359
402
  await instance.close();
360
403
  }
361
404
  ```
362
405
 
363
- `runCall` returns `Promise<Output>` and preserves the Call's original errors and cancellation rejections. It adds no
364
- UI status or outcome wrapper, scheduling policy, model creation or cleanup lifetime. The existing Call retains its
406
+ `runCommand` returns `Promise<Output>` and preserves the Command's original errors and cancellation rejections. It adds no
407
+ UI status or outcome wrapper, scheduling policy, model creation or cleanup lifetime. The existing Command retains its
365
408
  owner, lane and retirement rules; the test closes the instance it opened, including after failed readiness.
366
- A void Call can use `runCall(target)`. Options always occupy the third argument:
367
- `runCall(target, input, { signal })`, or `runCall(voidTarget, undefined, { signal })`.
368
- `RunCallOptions` contains only the optional `signal`; a pre-aborted signal rejects before the body runs.
409
+ A void Command can use `runCommand(target)`. Options always occupy the third argument:
410
+ `runCommand(target, input, { signal })`, or `runCommand(voidTarget, undefined, { signal })`.
411
+ `RunCommandOptions` contains only the optional `signal`; a pre-aborted signal rejects before the body runs.
369
412
 
370
- `run`, `runCall` and nested `invoke` use the same no-input rule (D273): `void` and `undefined` allow omission;
413
+ `run`, `runCommand` and nested `invoke` use the same no-input rule (D273): `void` and `undefined` allow omission;
371
414
  other inputs, including `T | undefined`, require an argument. `never` cannot be invoked without input.
372
415
  Generic helpers may forward the target and its explicit input without losing their types.
373
416
 
374
417
  ## Word map and entries
375
418
 
376
- | Entry | What it holds |
377
- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
378
- | `@opetope/react` | `useModel`, `useCommand`, `useCommands`, `useReadable`, `useSelector`, `useResource`, `requiresModels`, `defineSlot`, `defineSwitchSlot`, `Slot`, `ContributionError`; types `SlotTarget`, `SwitchSlotTarget`, `SlotContribution`, `CommandHook`, `CommandOutcome`, `ResourceHook`, `PaginatedResourceHook`, `ResourcePaginationHook` |
379
- | `@opetope/react/integration` | `FeatureBoundary`, `ContributionBoundary`, `useFeature`, `useFeatureRetry`, `FeatureBoundaryError` for the integration layer; types `FeatureBoundaryProps`, `ContributionBoundaryProps`, `ContributionErrorContent`, `ContributionFailure`, `FeatureDemandSource`, `FeatureDemandState`, `FeatureDemandResult`, `FeatureDemandLease` |
380
- | `@opetope/react/testing` | `renderSlot`, `command`, `createScenario`, `ScenarioTimeoutError` and fixture/scenario types, plus everything `@opetope/runtime/testing` and `@opetope/core/testing` publish; tests only |
419
+ | Entry | What it holds |
420
+ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
421
+ | `@opetope/react` | `useModel`, `useCommand`, `useReadable`, `useSelector`, `useResource`, `requiresModels`, `defineSlot`, `defineSwitchSlot`, `Slot`, `ContributionError`; types `SlotTarget`, `SwitchSlotTarget`, `SlotContribution`, `CommandHook`, `CommandOutcome`, `ResourceHook`, `PaginatedResourceHook`, `ResourcePaginationHook` |
422
+ | `@opetope/react/integration` | `FeatureBoundary`, `ContributionBoundary`, `useFeature`, `useFeatureRetry`, `FeatureBoundaryError` for the integration layer; types `FeatureBoundaryProps`, `ContributionBoundaryProps`, `ContributionErrorContent`, `ContributionFailure`, `FeatureDemandSource`, `FeatureDemandState`, `FeatureDemandResult`, `FeatureDemandLease` |
423
+ | `@opetope/react/testing` | `renderSlot`, `command`, `createScenario`, `ScenarioTimeoutError` and fixture/scenario types, plus everything `@opetope/runtime/testing` and `@opetope/core/testing` publish; tests only |
381
424
 
382
- `Call<Input, Output>` is imported from `@opetope/core`; UI `CommandOutcome<Output>` is imported from `@opetope/react`. The former `Command` synonym and the `CommandOutcome` re-export from `@opetope/react/integration` are removed (D270).
425
+ `Command<Input, Output>` is imported from `@opetope/core`; UI `CommandOutcome<Output>` is imported from `@opetope/react`. The former `Command` synonym and the `CommandOutcome` re-export from `@opetope/react/integration` are removed (D270).
383
426
 
384
- `Model` is a typed key for a record of `Readable`, `Call` and readable factories that UI components consume.
427
+ `Model` is a typed key for a record of `Readable`, `Command` and readable factories that UI components consume.
385
428
  The constructors of the integration entry are not re-exported from the safe entry and cannot come back into
386
429
  `ui/models/data/contracts` through a local barrel of a feature.
387
430
 
@@ -391,7 +434,7 @@ The constructors of the integration entry are not re-exported from the safe entr
391
434
  - one committed contribution owns its model frame until unmount or retire;
392
435
  - an abandoned concurrent render holds no frame;
393
436
  - `useCommand` does not subscribe to the invoker: the observable state of a call lies in a `Readable` of the model;
394
- - the `cancelled` outcome does not move `outcome`, and it is only the `CallError` codes `cancelled`
437
+ - the `cancelled` outcome does not move `outcome`, and it is only the `CommandError` codes `cancelled`
395
438
  and `closed`; a call to a weak port with no provider (`unavailable`) and a rejected publication
396
439
  (`publication-rejected`) arrive as the `failed` outcome and settle in `outcome` (D138, D292);
397
440
  - a contribution that fails to render or to commit is contained by its own mount and reported to its feature;