@opetope/react 0.11.0 → 0.12.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/CHANGELOG.md CHANGED
@@ -1,5 +1,400 @@
1
1
  # @opetope/react
2
2
 
3
+ ## 0.12.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 997137b: **Documentation only: four statements that had drifted from the tree are corrected, and the copies they drifted
8
+ out of are replaced by the one place each law is written (D460).** No source file changes, no public name moves,
9
+ no gate budget moves and no compiled example is edited. The root README gains a map, «Where each contract is
10
+ written once», naming for every subject the one normative section, the worked examples over it and nothing else.
11
+
12
+ - **The runtime word map promised two Resource names and the entry publishes nineteen.** `Resource` and
13
+ `ResourceState` were described as «the two words this entry publishes for a materialization», with the rest of
14
+ the family «named from `@opetope/core`». The entry re-exports the family whole — every `Resource*` word plus
15
+ `EventDelivery`, `LiveDelivery`, `PaginationOutcome` and `PaginationState` — and the comment above that block
16
+ names the decision that made it so. The row now says what the file does, which is what lets a feature author
17
+ write one import line for a declaration and for the `ResourceData<Data>` its `apply.change` annotates.
18
+ - **`@opetope/core` said it stays `private`, and its manifest carries no such field.** The manifest has
19
+ `publishConfig.access: public` and no `private`. The sentence keeps what was true — the API is experimental
20
+ while the design is under review — and says what that costs an upgrader: an alpha removes a superseded name
21
+ instead of keeping it beside its replacement, so an upgrade is read through the release notes. It makes no claim
22
+ about what is or is not on npm today.
23
+ - **Three pages still taught the pre-`LostWrite` law.** The runtime README said a dropped write is lost «in
24
+ silence rather than thrown at the caller or recorded by the reporter»; the primitives reference said «nothing is
25
+ reported»; and the spec's own model-layer section said «dropped without a reporter record». The law changed two
26
+ decisions ago and the normative sections that state it — §2.11 and §4 «Failures after abort» — were already
27
+ right: the first write an authority loses after ending inside a write into a cell its source reads, its own or
28
+ one of a command it invoked, reaches the reporter as `LostWrite`, once per run, and every other drop is silent.
29
+ All three now say that and defer to §4 for which is which. `update` is still `void`: nothing here promises a
30
+ throw at the caller. The primitives tables that listed a dropped write as always silent are corrected with them.
31
+ - **Two code tuples had already lost codes, and both are replaced by a link.** The authoring guide listed the
32
+ codes of every error class and was missing two of the six of `ContributionError`; the core README's boundary
33
+ list was missing the `CancellationError` its own word map names one paragraph above. Which class carries which
34
+ code, and which codes are cancellations rather than product answers, is the spec's «Errors»; which class is
35
+ thrown by whom is the how-it-works error table. The React README's outcome bullet, a third copy of the same law
36
+ plus the spec's «Command outcomes», goes the same way.
37
+ - **The rule that looked stale is not, and its reason was.** The authoring guide requires a result type on a model
38
+ factory, because an inline arrow handed straight to `openModel(Decl, ctx => …)` is not instantiated when its
39
+ body is checked. A probe over the current sources confirms the rule and refutes the explanation: a body that
40
+ names no input is inferred as `Command<void, void>`, taking the `void` default instead of the `never` the guide
41
+ claimed — so the declaration's input never arrives, a body that reads `input` is refused on the read with the
42
+ four-position instruction, and a body that reads nothing is refused against the record the factory returns. The
43
+ reason is rewritten to that; the rule stands.
44
+ - **The repetitions removed, and what was left in their place.** The primitives reference's shared-laws section
45
+ stated the fence-and-drain, writer, cancellation and reporter laws in full while declaring the spec normative
46
+ for all four; it now states what a primitive owes each law and links. The runtime README restated the reason
47
+ behind `opetope/no-snapshot-in-update`, which that rule's own README writes out, and restated the model
48
+ context's lanes and its `invoke`-from-an-effect verbatim from the core README, which declares them. Each task
49
+ page keeps its short explanation and its warnings.
50
+
51
+ - 8aec982: **A command's one waiting place is written as the record that names it: `concurrency: { pending: 'latest' }`
52
+ (D459).** The scalar `concurrency: 'latest'` named the input that wins and never named what it wins — «the latest»
53
+ reads as a place in a queue, as the body already running and as a cached answer, and only the first was true. The
54
+ shared form of the same fact never had that gap: `{ lane, pending: 'latest' }` says the place out loud, with the
55
+ `pending` an event and a live Resource already write for their own one waiting place. One fact was being written in
56
+ two vocabularies, and the shorter one left out the part that mattered. The word is removed rather than kept beside
57
+ the record, which makes this a breaking change to the authoring surface of a model command, of a model selection
58
+ and of a feature command.
59
+
60
+ - **Migrate by writing the place.** `concurrency: 'latest'` becomes `concurrency: { pending: 'latest' }` in
61
+ `ctx.command`, `ctx.select` and `own.command`. `concurrency: { lane, pending: 'latest' }` is unchanged: the lane
62
+ is the one parameter this record takes, and it is optional now instead of required. Nothing else about the option
63
+ moves — the same record, the same siblings, the same `dedupe` prohibition beside it.
64
+ - **No law moved with the spelling.** While call 1 runs, calls 2 and 3 take one waiting place: the body of 2 never
65
+ starts, its wait ends as `CommandError('cancelled', 'Command <id> replaced a waiting input.')`, and 3 runs when 1
66
+ finishes. A newer request never reaches the call in flight. The regression that holds this reads `signal.aborted`
67
+ inside the running body — after both newer requests were admitted and before the body returns — rather than
68
+ counting abort events, because the abort that follows a call's own end belongs to its cleanup and says nothing
69
+ about the policy. It was proved by substitution: with the kernel changed to cancel whatever is in flight, the
70
+ test fails on that line.
71
+ - **The retired word is answered with its replacement, not with a list of everything else.** The runtime throws
72
+ `Model command concurrency latest is now a record: write { pending: 'latest' }, or { lane, pending: 'latest' }.`
73
+ — and `Model select`, `Feature command` the same. Two neighbouring messages move with it: the choice sentence
74
+ loses the word — `concurrency must be queue, parallel, a lane of this surface or a record.` — and the record's own
75
+ refusal now says the lane is optional. A record that names `lane` and hands it `undefined` is still refused, by
76
+ the type under `exactOptionalPropertyTypes` and by the runtime, which reads the shape of the record by its keys.
77
+ - **The compiler names the replacement too, because the literal stays in the union.** The options record of a
78
+ command gained a third member — the retired word beside a key an author cannot write — so a refusal arrives as a
79
+ missing property carrying the instruction. Without it the union held no string at all, every word written there
80
+ widened to `string`, and one sentence answered a typo and a retired word alike. What the two now print, the same
81
+ on a model and on a feature: `{ concurrency: 'quee' }` is a `TS2820` that names the word and suggests `'queue'`,
82
+ which is better than it was before this change, and `{ concurrency: 'latest' }` is a `TS2345` whose elaboration
83
+ carries «`concurrency: "latest"` is now a record: write `{ pending: "latest" }`, or `{ lane, pending: "latest" }`».
84
+ A selection is the one surface where the compiler names the word written and not its replacement — its options
85
+ record is not a union — and there the runtime and the lint rule carry it.
86
+ - **`opetope/no-retired-vocabulary` reports it.** `concurrency: 'latest'` is a retired _value_ of a live key, the
87
+ shape `overflow: 'reject'` already had, so the rule now reads the values of a command's options record as well as
88
+ its keys and reports on the word that was written. A project that pins the rule's output by its own fixtures gains
89
+ one row.
90
+ - **What the gates read.** Instantiations are unchanged — 11 756, 87 710 and 160 268 against ceilings of 12 200,
91
+ 99 062 and 181 237 — because a scalar became a record that already existed. The tightest size row, the headless
92
+ session consumer of `@opetope/devtools`, stays at 6.95 kB of 7.1 kB. The two runtime rows carry the new branch and
93
+ its message: the application graph consumer moves 48.56 → 48.66 kB of 49 kB and the public feature consumer
94
+ 39.88 → 39.92 kB of 41 kB, measured by rebuilding the validator both ways. No budget moves.
95
+
96
+ - 4e65393: **The published archive of every package loses `README.ru.md`, and `@opetope/runtime` loses the Russian pages of
97
+ its `docs/` as well (D456).** The Russian half of the documentation is removed: nineteen `*.ru.md` files,
98
+ **2 027 997 bytes**, which is 39.0 % of what `ci:docs` counts as a document and 35.1 % of every `*.md` in the tree.
99
+ Documentation is written once, in English, from here on. No public name, no `exports` entry and no subpath moves —
100
+ a `.ru.md` was never a resolvable specifier, only a file read by its path — and no line of shipped JavaScript
101
+ changes, which is why this is a patch. The composition of what is published does change, and this is the line that
102
+ says so.
103
+
104
+ - **What goes.** `CONTRIBUTING.ru.md`, `README.ru.md`, the README of the minimal React example, the nine guides of
105
+ `docs/` — agent guide, cookbook, devtools, how-it-works, primitives, releases, both migration guides and the
106
+ spec — and the `README.ru.md` of all seven packages. `docs/decisions.md` stays Russian and keeps no pair, as it
107
+ never had one. The nine copies under `packages/runtime/docs/` are generated by packaging and leave on their own.
108
+ - **The rule that required a pair is cancelled, with its gate.** `CLAUDE.md` said «README and user guides have
109
+ English/Russian pairs, updated together» with four exceptions; it now says a document is written once, in
110
+ English, and gets no second copy. `ci:docs` no longer looks for a `*.ru.md` beside a document and no longer
111
+ compares the heading skeleton of a pair, and its report reads «links and example section order passed».
112
+ - **One gate is lost outright, and nothing replaces it.** The fixture generator held every English example against
113
+ its Russian twin as the same program — syntax without comments and without JSX text — which is how D441 caught a
114
+ real drift in a spec §2 block, a string literal that differed in one copy. The defect class that stops being
115
+ caught is a silent edit to a detail of an example that still compiles: a string literal, a number, the order of
116
+ two arguments of the same type. `ci:type` accepts any literal of the right type, `ci:docs` reads section order
117
+ and links rather than values, `ci:asserted-refusals` holds pragmas, and the generator's own `--check` compares a
118
+ fixture with the very document it was lifted from, so one `fixtures:generate` makes any such edit the new
119
+ baseline. The twin was the only place in this repository where one program was written twice and the two
120
+ writings were compared. It also never knew which copy was right — in the one case it fired, the wrong copy was
121
+ the Russian one — and it protected above all the copy that nothing compiled.
122
+ - **Where the archive change is written down, so it cannot drift from its gate.** The composition of an archive is
123
+ stated in six places and all six are rewritten: the `files` of the seven manifests; the required-file list and
124
+ the archive-member allowlist of the pack archive check; the fixture of that check's own unit test; the
125
+ allowlists of the two real pack smoke tests; and what packaging demands of each package before it packs. The
126
+ requirement that `@opetope/runtime` ship `docs/spec.ru.md` is gone; `docs/spec.md` is required exactly as before.
127
+ - **The numbers the gates print.** `ci:docs` reads 28 documents instead of 46 — nineteen leave and this changeset
128
+ is itself a document — with 4 pending changesets instead of 3 and 424 decision identifiers instead of 423. Its
129
+ feature examples halve, 64 to 32: that gate lints every fenced example that declares a feature, and it was
130
+ linting both copies of each one. Packaging prepares 10 documents instead of 19, and the merge-marker scan reads
131
+ 1101 text files instead of 1117 — seventeen of the nineteen removed files live under a scanned root, and this
132
+ changeset is one file back. The document examples do not move at all: 38 spec §2 blocks and 132 blocks of 11
133
+ other documents, 2 fragments, 14 asserted refusals, exactly as before, because only the English block was ever
134
+ lifted into a fixture.
135
+
136
+ - Updated dependencies [997137b]
137
+ - Updated dependencies [ba75469]
138
+ - Updated dependencies [d09568e]
139
+ - Updated dependencies [8aa4d85]
140
+ - Updated dependencies [8aec982]
141
+ - Updated dependencies [be0613e]
142
+ - Updated dependencies [4e65393]
143
+ - Updated dependencies [e12b0b6]
144
+ - @opetope/core@0.12.1
145
+ - @opetope/runtime@0.12.1
146
+
147
+ ## 0.12.0
148
+
149
+ ### Minor Changes
150
+
151
+ - 34bdbfb: **An options record is exact by its keys, and the guard that was weaker than no guard is gone (D406).** Exactness is
152
+ checked against the set of keys the argument carries, never against the record the compiler infers from it. The old
153
+ form asked the record to witness itself — `Actual & NoExtraKeys<Actual, Shape> & Shape` — and a record assigned to a
154
+ name first and sharing no key with the shape gave that inference nothing: the guard collapsed and took the weak-type
155
+ check down with it, so the guarded surface admitted what the bare shape refused.
156
+
157
+ ```ts
158
+ // the record a migration carries: assigned first, sharing no key with the options of this node
159
+ const carriedOver = { concurency: 'queue' as const };
160
+
161
+ // before: `own.command`, `own.effect`, `own.event` and both `own.scope` forms accepted this in silence
162
+ // after: refused where it is written, and the message names the key
163
+ command(orders, () => undefined, carriedOver);
164
+ ```
165
+
166
+ - Measured on the auditor's own probe: six misspelled records on six feature nodes, **one refusal before and six
167
+ after**, with no correct declaration in this repository changing.
168
+ - The gate now stands on every authoring record that had one, on `ctx.command`, `ctx.effect`, `ctx.event` and both
169
+ `ctx.scope` forms, which had none, and on the top-level record of `resource.load`/`resource.live` on both
170
+ surfaces. The nested records of the Resource family (`identity`, `apply`, `delivery`, `pagination`) keep the
171
+ runtime refusal of D345 and no type gate: the decision measures why.
172
+ - `queueBy` on a feature used to print `concurrency: { by }`, which is a member of a model command and of nothing
173
+ else, so the author walked from one refusal into the next. It now names the queue a feature has and the surface
174
+ that mints the keyed one, and a negative beside the positive holds that the advice compiles.
175
+ - A stack navigator refuses `persistence` and `defaultScreen` — the words of switch mode — instead of accepting and
176
+ ignoring them. The union of the two option records had no forbidding field, so an assigned record simply matched
177
+ the branch with fewer words and the host persistence contract was never called. `createNavigator` refuses the same
178
+ keys at run time, so a caller without types hears it too.
179
+ - Breaking: `ExactInput` and `NoExtraKeys` are gone from `@opetope/core/internal`; `ExactKeys` is the one gate. A
180
+ record with a key its node does not name stops compiling, as do a feature `queueBy` and a stack navigator carrying
181
+ switch-mode words.
182
+ - Measured: both runtime size rows are byte-identical — 48 067 B for the application graph consumer and 39 500 B for
183
+ the public feature consumer. The navigator's run-time refusal is isolated at **+70 B**: the navigator consumer is
184
+ 1 943 B without it and 2 013 B with it.
185
+ - Measured: the compiler does less work than before, not more. Type-stress instantiations are 11 647 / 85 822 /
186
+ 156 926 over the three slices against a recorded baseline of 11 582 / 90 074 / 165 874 — inferring a key set is
187
+ cheaper than inferring the record, and the decision records which spelling of the gate costs what.
188
+
189
+ - 34bdbfb: **A word of a Resource declaration is its key, not its value — this is a breaking change (D407).** Every option of
190
+ `resource.load` and `resource.live`, and the host `grace` of `openFeature` and `openApplication`, is now read from
191
+ the key the record carries. A record keeps a key whose value is `undefined`, so a record built by a conditional
192
+ spread was until now indistinguishable from one that never wrote the word — and it was accepted in silence.
193
+
194
+ ```ts
195
+ // refused now, and silently a singleton before: the key is written, so the request is written
196
+ ctx.resource.load({ ...shared, request: keyed ? account : undefined });
197
+
198
+ // write the two declarations instead; the half they share is still written once
199
+ keyed ? ctx.resource.load({ ...shared, request: account }) : ctx.resource.load(shared);
200
+ ```
201
+
202
+ What a written `undefined` used to mean, and what it means now:
203
+
204
+ | Written | Was, in silence | Is now |
205
+ | ------------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------- |
206
+ | `request: undefined` | a singleton, and the key of every cache entry with it | `… request must be a value or a Readable.` |
207
+ | `enabled: undefined` | a Resource nothing gates | `… enabled must be a Readable.` |
208
+ | `grace: undefined` | the host's window, and accepted beside `lifetime: 'owner'` | the refusal of the word beside an owner, of the number otherwise |
209
+ | `identity: undefined` | no key and no scope: one shared materialization | `… identity must be a plain object record.` |
210
+ | `identity: { key: undefined }` | the request naming itself, or nothing on a singleton | `… identity.key must be a function.`, or the refusal of the word |
211
+ | `identity: { scope: undefined }` | a materialization shared where one was scoped | `… identity.scope must be a Readable.` |
212
+ | `load: undefined` on `live` | no load channel, and `apply.load` refused for it | `… load must be a function.` |
213
+ | `cache: undefined` | no cache | `… cache must be a plain object record.` |
214
+ | `pagination: undefined` | no pagination capability | `… pagination must be a plain object record.` |
215
+ | `delivery: undefined` | the default delivery | `… delivery must be a plain object record.` |
216
+ | `delivery.overflow: undefined` | the answer of a full capacity D351 wrote | `… delivery overflow must be flush-oldest or reject.` |
217
+ | `delivery.pending: undefined` | the field the record owes, reported as missing | `Unknown model resource delivery undefined.` |
218
+ | `apply: { load: undefined }` beside `load` | `apply.load is required beside load.` | `… apply.load must be a function.` |
219
+ | `grace: undefined` on `openFeature` / `openApplication` | the `0` of a host without the word | the refusal of the number |
220
+
221
+ Each refusal is this node's own sentence for a value the word cannot take — the sentence the request of an `event`
222
+ has printed for the same mistake since D395 — followed by the clause that names the remedy the sentence alone
223
+ cannot: `A key written as undefined is a word this declaration wrote: leave the key out where the word is not
224
+ meant.` Where the key is owed rather than optional — `apply.load` beside a load channel, `pending` inside a
225
+ `delivery` — the refusal names the debt and offers no such remedy.
226
+
227
+ `createScenario` of `@opetope/react` forwards the same word, and forwarded it by value: a `grace` written as
228
+ `undefined` there was dropped into the `0` of a host that wrote no window at all. It now travels to
229
+ `openApplication` and is refused there, beside the `conditions` and `imports` of the same record, which already
230
+ asked the key.
231
+
232
+ **Rewrite by building the record, not the value.** Branch on the record (`flag ? f({ ...shared, word }) : f(shared)`),
233
+ or spread a record that carries the key only when it is meant (`...(flag ? { word } : {})`). A conditional value
234
+ (`word: flag ? v : undefined`) is the form that breaks, and it is the form this decision exists to refuse.
235
+
236
+ - aaaa92c: **One word for a command, one for its order (D385–D389).** The primitive is `Command<Input, Output>`: the type was
237
+ the last place the vocabulary still said `Call`, while the hook was already `useCommand`, the record `CommandHook`,
238
+ the outcome `CommandOutcome` and the fixture `command(...)`. `CallError` is `CommandError`, `runCall` is
239
+ `runCommand`, `RunCallOptions` is `RunCommandOptions`, `CallTimeoutError` is `CommandTimeoutError` and
240
+ `CallInputArgs` is `CommandInputArgs`. There are no aliases.
241
+
242
+ - **A body takes one argument, its context (D386).** `ctx.command(({ input, update }) => …)` and
243
+ `own.command(within, ({ input, source, signal }) => …)`. `Input` and `Output` default to `void`, so the
244
+ `_input: void` placeholder is gone from every command that takes none. A typed input is declared once, by
245
+ annotating the destructured context: `({ input, update }: ModelCommandContext<Deposit>)` or
246
+ `({ input, source }: FeatureCommandContext<typeof polling, Deposit>)`. Both types are new public exports. Give a
247
+ model factory a result type — `function createCounter(context: ModelContext): ModelOf<typeof Counter>`, the form
248
+ the examples use: inside an inline arrow handed straight to `openModel(Decl, ctx => …)` or `model(Decl, ctx => …)`
249
+ the expected type is not instantiated yet, so a body that names no input reads as `Command<never, …>` instead of
250
+ taking the `void` default.
251
+ - **`concurrency` replaces `policy`, `queueBy` and `lane` (D387).** One word that also takes the queue node itself:
252
+ `'parallel' | 'queue' | 'latest' | lane | { by } | { by, lane } | { lane, pending: 'latest' }`. A lane is a
253
+ reference commands share, which is why the word accepts it; `'parallel'` beside a lane and a bare `by` without a
254
+ queue are now unwritable rather than refused. `dedupe` stays a sibling option and `'latest'` still refuses it.
255
+ A selection writes the same word with the members it can answer for — the keyed record is not one of them, and
256
+ a feature has no keyed queue either, so both are refused with the reason. The retired `policy`, `queueBy`, `lane`
257
+ and `once` are named by each options record, so they are refused in a written literal and in an assigned record
258
+ alike — on `ctx.command`, on `own.command` and on `ctx.select`, which also refuses `memoize` and `dedupe`.
259
+ - **Declaring and selecting are two words (D388).** `ctx.command(run, options?)` declares;
260
+ `ctx.select(source, 'name' | ['a', 'b'], options?)` selects what a dependency already declares — one name answers
261
+ one command, a list answers a record. `ctx.calls` and `own.calls` are gone, and so is `useCommands`: a record of command consumers
262
+ comes from `useModel(Declaration, select)`, which has an owner and a lifetime. `bind` of `@opetope/runtime` is
263
+ unchanged.
264
+ - **`once` is `memoize` (D389).** The cache keeps its behaviour and loses a name the platform already owns:
265
+ `addEventListener(type, handler, { once: true })` means "then stop", which is not what this option ever did.
266
+
267
+ `@opetope/lint` follows the vocabulary: `opetope/capture-command-cleanup` reads the context off the body's one
268
+ parameter and recognises `concurrency: 'parallel'`, and `opetope/no-command-in-deps` names the selection a record
269
+ comes from.
270
+
271
+ - 34bdbfb: **One word for one thing, and one sentence for one question (D410, D411).** A vocabulary pass over the public API:
272
+ the refusals that asked one question in five sentences now ask it in one, the position a signature calls `source`
273
+ is called `source` everywhere, and eight words that named one behaviour twice are one word apiece. No branch of the
274
+ runtime changes: the same checks run in the same order.
275
+
276
+ **Breaking, word by word.** Every row is a source change with no alias; `opetope/no-retired-vocabulary` reports each
277
+ one and names the replacement.
278
+
279
+ | Before | After |
280
+ | -------------------------------------------------------------- | --------------------------------------------------- |
281
+ | `defineCondition(id, { from, select })` | `defineCondition(id, { select, source })` |
282
+ | `slot(target, value, { when })`, same at `pipe`, `register` | `slot(target, value, { enabled })` |
283
+ | `resource.live({ delivery: { overflow: 'reject' } })` | `resource.live({ delivery: { overflow: 'drop' } })` |
284
+ | `outcome.status === 'ok' \| 'failed' \| 'cancelled'` | `outcome.kind === 'ok' \| 'failed' \| 'cancelled'` |
285
+ | `ResourceActivity` from `@opetope/devtools` | `ResourceNodeActivity` from `@opetope/devtools` |
286
+ | `ResourceDisposer` from `@opetope/core/internal` | `Disposer` from `@opetope/core/internal` |
287
+ | `NavigatorPolicy` from `@opetope/navigation` | `NavigatorCatalogue` |
288
+ | `surface.policy` | `surface.catalogue` |
289
+ | `ApplicationConditionBinding` from `@opetope/runtime/internal` | the same name, from `@opetope/runtime` |
290
+
291
+ `@opetope/lint` renames the rule that read that field: `opetope/when-predicate` is `opetope/enabled-predicate`,
292
+ and its message ids are `enabledIsAsync`, `enabledReturnsBareSource` and `enabledReturnsSource`. A configuration
293
+ that named the old rule has to name the new one; `configs.recommended` and the shipped `oxlintrc.json` fragment
294
+ already do.
295
+
296
+ Two of these are record keys, so a source with `sort-keys` reorders the literal as well as renaming the key:
297
+ `{ from, select }` becomes `{ select, source }`, and `{ Component, when }` becomes `{ Component, enabled }`.
298
+ `{ status: 'ok', value }` becomes `{ kind: 'ok', value }`, which sorts unchanged, while
299
+ `{ reason, status: 'cancelled' }` becomes `{ kind: 'cancelled', reason }`, which does not.
300
+
301
+ **Why each one.** `source` is the word every context already answers the position with, and `from*` stays the
302
+ prefix of a factory that names where a `Readable` comes from (`fromExternal`, `fromMaybe`). `enabled` is the truth
303
+ the runtime reads while a node exists — one word at a contribution and at a Resource — while `when` keeps the other
304
+ mechanism, the declared `Condition`s an application resolves before the node exists; `scope.while(source, open,
305
+ { when })` and an effect's `filter` are untouched. `drop` is what a full capacity does with the value that does not
306
+ fit, at both nodes that admit a producer; `flush-oldest` is still the default of a keyed delivery and still the
307
+ answer for a producer that may lose nothing. `kind` is the discriminant eleven settled answers already carried.
308
+ `NavigatorCatalogue` is named for its job, beside `NavigatorPersistence`, because a policy is data (D291) and this
309
+ is four callbacks a navigator asks.
310
+
311
+ **Not breaking, and worth reading.** Every options record now refuses an unknown field with the sentence spec §3
312
+ promised — `<subject> <node> <record> contains unknown field <key>.` — and says which field it owes with
313
+ `<subject> <node> <record> requires <key>.` Five sentences became one, and the three that never printed the field
314
+ at all (`requires exact data fields`, `own.attach takes open, or open and close, and nothing else`) now print it. A
315
+ node that reads a source says so in two sentences per surface instead of three, and the word of the position is the
316
+ author's. `own.select` lost two of its four overloads: a call that writes the options record has one candidate of
317
+ its arity now, so a fault in that record is reported as the record's own refusal instead of
318
+ `Argument of type '"echo"' is not assignable to parameter of type 'never'`, and the record names the five words a
319
+ selection does not take — `concurrency`, `dedupe`, `lane`, `memoize`, `policy` — each with what to write instead.
320
+
321
+ **Measured.** Runtime size consumers, rebuilt: `application graph consumer` 48 248 B against a ceiling of 49 000
322
+ (48 318 before), `public feature consumer` 39 579 B against 41 000 (39 712 before). Type stress: 11 695 / 87 068 /
323
+ 159 371 instantiations, unchanged to the digit. No ceiling moved.
324
+
325
+ - aaaa92c: **The destructive verb is `reset()` (D373).** `Resource.invalidate()` is now `Resource.reset()`, and the verb of the
326
+ React hook is renamed with it: `useResource(resource)` answers `{ pagination, refresh, reset, retry, state }`. Nothing
327
+ about the operation changes — it clears the whole cache, drops the current data, opens a new epoch and restarts every
328
+ configured channel, exactly as D355 wrote it.
329
+
330
+ The old name promised the opposite of what it did. `invalidate` means «mark stale, keep the data» everywhere else —
331
+ TanStack Query's `invalidateQueries` keeps the cached data until a new answer arrives, and Apollo's `INVALIDATE`
332
+ marks a field stale without removing its value — so an author who came from there wrote it expecting a background
333
+ refresh over the rows still on screen and got an empty state instead.
334
+
335
+ - `resource.reset()` and `useResource(resource).reset.run()` are the current names; there is no alias, and a
336
+ Resource object has no `invalidate` member at all.
337
+ - A host that implements the `Resource` contract itself renames its member too: the runtime recognises a Resource
338
+ field of a model shape by the names of its verbs.
339
+ - `invalidate` now names no operation of a Resource. It is left free for a future operation that would mark data
340
+ stale and keep them — an intent recorded in D373, not a promise of one.
341
+
342
+ ### Patch Changes
343
+
344
+ - Updated dependencies [bb31983]
345
+ - Updated dependencies [bb31983]
346
+ - Updated dependencies [bb31983]
347
+ - Updated dependencies [dae5079]
348
+ - Updated dependencies [dae5079]
349
+ - Updated dependencies [bb31983]
350
+ - Updated dependencies [aaaa92c]
351
+ - Updated dependencies [34bdbfb]
352
+ - Updated dependencies [dae5079]
353
+ - Updated dependencies [34bdbfb]
354
+ - Updated dependencies [bb05f21]
355
+ - Updated dependencies [dae5079]
356
+ - Updated dependencies [34bdbfb]
357
+ - Updated dependencies [aaaa92c]
358
+ - Updated dependencies [34bdbfb]
359
+ - Updated dependencies [aaaa92c]
360
+ - Updated dependencies [34bdbfb]
361
+ - Updated dependencies [bb05f21]
362
+ - Updated dependencies [aaaa92c]
363
+ - Updated dependencies [aaaa92c]
364
+ - Updated dependencies [bb31983]
365
+ - Updated dependencies [34bdbfb]
366
+ - Updated dependencies [dae5079]
367
+ - Updated dependencies [aaaa92c]
368
+ - Updated dependencies [aaaa92c]
369
+ - Updated dependencies [aaaa92c]
370
+ - Updated dependencies [aaaa92c]
371
+ - Updated dependencies [dae5079]
372
+ - Updated dependencies [bb05f21]
373
+ - Updated dependencies [aaaa92c]
374
+ - Updated dependencies [dae5079]
375
+ - Updated dependencies [dae5079]
376
+ - Updated dependencies [aaaa92c]
377
+ - Updated dependencies [aaaa92c]
378
+ - Updated dependencies [34bdbfb]
379
+ - Updated dependencies [bb05f21]
380
+ - Updated dependencies [aaaa92c]
381
+ - Updated dependencies [bb31983]
382
+ - Updated dependencies [34bdbfb]
383
+ - Updated dependencies [dae5079]
384
+ - Updated dependencies [aaaa92c]
385
+ - Updated dependencies [aaaa92c]
386
+ - Updated dependencies [dae5079]
387
+ - Updated dependencies [bb05f21]
388
+ - Updated dependencies [dae5079]
389
+ - Updated dependencies [bb31983]
390
+ - Updated dependencies [bb31983]
391
+ - Updated dependencies [34bdbfb]
392
+ - Updated dependencies [bb05f21]
393
+ - Updated dependencies [34bdbfb]
394
+ - Updated dependencies [34bdbfb]
395
+ - @opetope/runtime@0.12.0
396
+ - @opetope/core@0.12.0
397
+
3
398
  ## 0.11.0
4
399
 
5
400
  ### Minor Changes
@@ -274,7 +669,7 @@
274
669
 
275
670
  **`ResourceHook` (D321).** `useResource(resource)` answers `{ snapshot, refresh, retry }`, and a field of a
276
671
  `useModel` selection whose value is a request answers the same hook and takes the same lease. Nine models of one
277
- application had wrapped `resource.refresh()` in a `Call<void, void>` for nothing but the `inFlight` of a button.
672
+ application had wrapped `resource.refresh()` in a `Command<void, void>` for nothing but the `inFlight` of a button.
278
673
  Recognition asks the same question the type asks: `Resource` is a structural contract (D199), so a host adapter that
279
674
  satisfies it is classified and leased exactly like a materialization the runtime built, which stays the fast path.
280
675
  `read(resource)` still answers the snapshot and leases nothing. One pair of controllers per resource identity keeps
@@ -306,11 +701,11 @@
306
701
 
307
702
  ### Patch Changes
308
703
 
309
- - b964772: Модель подтверждает мутации принадлежащего ей Resource, получает явный исход обновления, выполняет Call из
704
+ - b964772: Модель подтверждает мутации принадлежащего ей Resource, получает явный исход обновления, выполняет Command из
310
705
  effect и сериализует независимые объекты через очередь по ключу. Stream умеет накапливать кадры в границах
311
706
  открытия. Контекст данных отделён от ключа, а ёмкость кэша перенесена из retention в самостоятельную опцию cache.
312
707
  Retention теперь задаётся литералом, model resource/stream принимает готовый Readable, а `queueBy` создаёт
313
- приватную keyed queue команды или использует явно переданную общую lane. Effect переносит отмену вложенного Call
708
+ приватную keyed queue команды или использует явно переданную общую lane. Effect переносит отмену вложенного Command
314
709
  через `rethrowIfCancelled`.
315
710
  - Updated dependencies [b964772]
316
711
  - @opetope/core@0.9.3
@@ -330,7 +725,7 @@
330
725
 
331
726
  - d7f346a: A command hook is `{ run, inFlight, outcome }`, and an owned attachment step is `step(key, run, cleanup)`.
332
727
 
333
- `CommandHook.handler` is removed, from `useCommand`, from every key of `useCommands` and from the `Call` fields of a
728
+ `CommandHook.handler` is removed, from `useCommand`, from every key of `useCommands` and from the `Command` fields of a
334
729
  `useModel` selection. It bought one arrow in JSX and nothing the types did not already hold: `onClick={logout.run}`
335
730
  never compiled, because a `MouseEvent` is not a `void` input. A command without input now binds as
336
731
  `onClick={() => void logout.run()}` (D292).
@@ -405,17 +800,17 @@ after <attempts> attempts — ` and carrying a non-enumerable `eventually` recor
405
800
  `timeoutMs`, so an expiry does not read like a single failed assertion. A throw that is not an `Error` is rethrown
406
801
  untouched.
407
802
 
408
- `runCall(target, input?, { signal, timeoutMs })` takes a deadline on that one run. `run` is the top-level execution
803
+ `runCommand(target, input?, { signal, timeoutMs })` takes a deadline on that one run. `run` is the top-level execution
409
804
  the consumer owns (D292), so the deadline cancels the way that consumer cancels — by aborting the signal this run
410
- supplied — and rejects with a `CallTimeoutError` naming the call and the deadline. A `signal` stands beside it and
805
+ supplied — and rejects with a `CommandTimeoutError` naming the call and the deadline. A `signal` stands beside it and
411
806
  the first of the two to abort supplies the reason, so a cancelled outcome is never reported as a timeout.
412
807
 
413
- `command(run)` is now published by `@opetope/core/testing` as a `Call` fixture with no binding: the body is
808
+ `command(run)` is now published by `@opetope/core/testing` as a `Command` fixture with no binding: the body is
414
809
  positional and receives the signal of the run, and there is no lane, queue, dedupe or retirement — so no `inFlight`
415
810
  to read, no fence, and two runs of one fixture overlap. A model opened with `openModel` takes it as a dependency,
416
- which removes the second model a test used to open for one Call. `@opetope/react/testing` keeps its own `command`,
811
+ which removes the second model a test used to open for one Command. `@opetope/react/testing` keeps its own `command`,
417
812
  which is this one plus the contribution binding a mount reads, and also re-exports `yieldTurn`, `eventually` and
418
- `CallTimeoutError`.
813
+ `CommandTimeoutError`.
419
814
 
420
815
  `settled` and `waitFor` accept the `ApplicationExecution` an `openApplication` returned. An application contributes
421
816
  the models of every generation it holds right now and a fifth fact of its own: the instances whose retirement has
@@ -425,7 +820,7 @@ after <attempts> attempts — ` and carrying a non-enumerable `eventually` recor
425
820
 
426
821
  Migration: `flush()` (a `setImmediate` or `setTimeout(0)` helper) → `yieldTurn()`; a local generic
427
822
  `waitFor<Value>(read, predicate)` → the library `waitFor`, which returns the value; a local `eventually` → the
428
- library one, whose expiry carries the last assertion failure; `AbortSignal.timeout(500)` in `runCall` →
823
+ library one, whose expiry carries the last assertion failure; `AbortSignal.timeout(500)` in `runCommand` →
429
824
  `{ timeoutMs: 500 }`; `command` from `@opetope/react/testing` in a test that renders nothing →
430
825
  `command` from `@opetope/core/testing`; and a hand-written poll loop for an application → `settled(application)`.
431
826
 
@@ -453,9 +848,9 @@ auth.getTokens()) === null)` — belongs in `eventually`, which awaits its block
453
848
 
454
849
  - 0318472: Give every package a testing entry, and open a model without a feature.
455
850
 
456
- `@opetope/core/testing` publishes `testReadable` (the state cell a model receives as `ctx.state`), `runCall` — moved
851
+ `@opetope/core/testing` publishes `testReadable` (the state cell a model receives as `ctx.state`), `runCommand` — moved
457
852
  here from `@opetope/react/testing`, because it reaches the existing invoker and knows about neither a renderer nor a
458
- feature — and `CallInputArgs`, so a test can wrap `runCall` without naming `./internal` (D285).
853
+ feature — and `CommandInputArgs`, so a test can wrap `runCommand` without naming `./internal` (D285).
459
854
 
460
855
  `@opetope/runtime/testing` adds `openModel(Decl, dependencies?, create, { reporter })`, which opens one model with
461
856
  the same kernel, lanes and retirement the feature path builds and none of what a feature adds (D286), and
@@ -526,9 +921,9 @@ auth.getTokens()) === null)` — belongs in `eventually`, which awaits its block
526
921
 
527
922
  Keep derive({ from, select }) for explicit sources and preserve the existing reactive scheduling and lifetime behavior. Migrate framework consumers and add a compile-checked model example in the English and Russian guides.
528
923
 
529
- - 538cdfe: Replace singleFlight with dedupe on custom Calls. Use dedupe: true to share one pending or running execution per Call and generation, or a key function to share equivalent inputs. Retain the first input and independent caller cancellation. Remove the former name without a compatibility alias.
924
+ - 538cdfe: Replace singleFlight with dedupe on custom Calls. Use dedupe: true to share one pending or running execution per Command and generation, or a key function to share equivalent inputs. Retain the first input and independent caller cancellation. Remove the former name without a compatibility alias.
530
925
 
531
- Reject both forms of dedupe with policy: latest in types and at runtime. Different keys keep one FIFO under queue and run independently under parallel. Preserve once caching before deduplication and keep scheduling options on Call declarations rather than React hooks. Add a compile-checked model example and update the English and Russian guides.
926
+ Reject both forms of dedupe with policy: latest in types and at runtime. Different keys keep one FIFO under queue and run independently under parallel. Preserve once caching before deduplication and keep scheduling options on Command declarations rather than React hooks. Add a compile-checked model example and update the English and Russian guides.
532
927
 
533
928
  - Updated dependencies [538cdfe]
534
929
  - Updated dependencies [538cdfe]
@@ -566,14 +961,14 @@ auth.getTokens()) === null)` — belongs in `eventually`, which awaits its block
566
961
  runtime nodes or adding ownership relations. Preserve diagnostic group names in the tooltip and inspector.
567
962
 
568
963
  - a6a5f73: Remove the redundant `Command` type synonym and the `CommandOutcome` re-export from
569
- `@opetope/react/integration`. Import `Call` from `@opetope/core` and the UI `CommandOutcome`
964
+ `@opetope/react/integration`. Import `Command` from `@opetope/core` and the UI `CommandOutcome`
570
965
  from `@opetope/react`. Alpha releases keep one current API without compatibility aliases.
571
966
 
572
967
  ### Patch Changes
573
968
 
574
969
  - a6a5f73: Allow nested `invoke(target)` without an input placeholder for Calls accepting `void` or `undefined`, including
575
970
  model, feature and attachment execution contexts. Keep required inputs inferred from the target, preserve generic
576
- forwarding, and share the no-input rule with React `run` and testing `runCall`. Calls accepting `never` cannot be
971
+ forwarding, and share the no-input rule with React `run` and testing `runCommand`. Calls accepting `never` cannot be
577
972
  invoked without input.
578
973
  - a6a5f73: Allow omitting `conditions` from `openApplication` and `createScenario` when no enabled feature requires a host-bound condition. Missing external bindings and explicitly invalid condition records still fail validation.
579
974
  - Updated dependencies [a6a5f73]
@@ -587,7 +982,7 @@ auth.getTokens()) === null)` — belongs in `eventually`, which awaits its block
587
982
 
588
983
  ### Minor Changes
589
984
 
590
- - 410f2f0: Run authentic Calls in headless model tests with `runCall` from `@opetope/react/testing`, preserving their existing policies, cancellation, errors and owner lifetime.
985
+ - 410f2f0: Run authentic Calls in headless model tests with `runCommand` from `@opetope/react/testing`, preserving their existing policies, cancellation, errors and owner lifetime.
591
986
 
592
987
  Add the recommended `opetope/no-internal-imports` rule for static imports, re-exports, literal dynamic imports, unshadowed `require` and TypeScript import types. `configs.internalImports` uses this rule for JavaScript and TypeScript, including tests, without replacing the host's `no-restricted-imports`. The redundant `internalImportPattern` export is removed; allow trusted implementation files through the config or an explicit rule override.
593
988