doxum 0.1.29 → 0.1.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doxum",
3
- "version": "0.1.29",
3
+ "version": "0.1.30",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "description": "Doxum is a typed runtime for complex mutable documents.",
@@ -82,7 +82,8 @@
82
82
  "build": "tsdown --config tsdown.config.ts && node scripts/check-build-exports.mjs && pnpm run check:declaration-portability",
83
83
  "typecheck": "tsc -p tsconfig.json --noEmit",
84
84
  "test": "vitest run --config vitest.config.ts",
85
- "check": "pnpm run format:check && pnpm run lint && pnpm run typecheck && pnpm run check:public-types && pnpm run test && pnpm run check:projection-surface && pnpm run check:runtime-imports",
85
+ "check": "pnpm run format:check && pnpm run lint && pnpm run typecheck && pnpm run check:public-types && pnpm run test && pnpm run check:runtime-skill && pnpm run check:projection-surface && pnpm run check:runtime-imports",
86
+ "check:runtime-skill": "node scripts/check-runtime-skill.mjs",
86
87
  "check:projection-surface": "node scripts/check-projection-surface.mjs",
87
88
  "check:runtime-imports": "node scripts/check-runtime-imports.mjs",
88
89
  "check:public-types": "tsc -p tsconfig.public-api.json --noEmit",
@@ -1,88 +1,70 @@
1
1
  ---
2
2
  name: doxum-runtime
3
- description: 'Use Doxum core, advanced projections, React, and local-sync for schemas, document state, ChangeSets, history, projections, subscriptions, persistence, and adapters.'
3
+ description: Use Doxum public APIs to design, implement, and review schemas, document mutations, projections, React bindings, local sync, and advanced processors without reading implementation source. Read maintainer invariants only when changing Doxum itself.
4
4
  ---
5
5
 
6
6
  # Doxum Runtime
7
7
 
8
- Read the [English guide](references/guide.en.md) or [中文指南](references/guide.zh-CN.md).
9
- For examples read [patterns](references/patterns.en.md) or [中文模式](references/patterns.zh-CN.md).
10
- For exact public call shapes, exported package surface, callback contexts, result
11
- types, and supported errors, read the [API reference](references/api.en.md) or
12
- [中文 API 参考](references/api.zh-CN.md) instead of implementation source.
13
- For any projection design or implementation, especially custom processors or
14
- cross-collection dependencies, read the [projection reference](references/projections.en.md)
15
- or [中文 Projection 参考](references/projections.zh-CN.md).
16
- Before runtime changes read [invariants](references/invariants.en.md) or
17
- [中文不变量](references/invariants.zh-CN.md).
18
-
19
- - Root object defines schema identity; Infer describes readonly data with shared immutable payloads.
20
- - createDocument owns canonical state. update, apply and replace share one session.
21
- - Draft and trusted internal readWith scopes are borrowed for synchronous callbacks;
22
- their proxies must not escape. Public values should use snapshot when they need to
23
- outlive the callback.
24
- - Atomic fields are deeply readonly during access and replaced whole.
25
- - Maps use get/has/ids/put/remove/replace; table/list/tree use overloaded replace
26
- for member and whole-container replacement. Use replace(parent, key, value) for
27
- object or variant members whose Draft type contains collection tools.
28
- - Ordered table/list Drafts use move(key | readonly key[], anchor?) for relative
29
- movement and reorder(keys) for an exact full-membership permutation.
30
- - Trees may be empty. `tree(field(...))` requires every existing node to own `value`;
31
- use `tree(optional(field(...)))` only when node payload absence is part of the model.
32
- `optional(tree(...))` controls whole-tree presence independently.
33
- - Tree projection paths reuse ordinary source protocols: `tree.rootId` is scalar,
34
- `tree.nodes` is keyed, and `tree.nodes.item(id)` is a single-node value.
35
- - Expected business failure throws TransactionRejected. Other exceptions roll back
36
- and rethrow unchanged. Callback returns carry business values and notices.
37
- - Commits contain final reversible ChangeSets. History and impact share those facts.
38
- - ChangeSets group member transitions by container, with explicit added/removed/updated
39
- kinds, optional before/after order in the same group, and a separate root reset.
40
- Standalone order records and duplicate groups are invalid. Grouping preserves field-level impact.
41
- - Paths belong in subscription, impact and collection source callbacks.
42
- - `Schema` / `ObjectSchema` are portable schema handles. Export inferred schemas
43
- directly; application code should not need `ReturnType<typeof object>` or internal
44
- node types to make declarations portable.
45
- - Projection definitions are lazy; one ProjectionRuntime owns all materialized
46
- producers, outputs, scheduling and source attachments for that Runtime.
47
- `KeyedProjection<K,V>` is the single public keyed projection handle. Collection
48
- `observe`, `derive.keyed`, `incremental.collection`, group collection leaves and
49
- `CollectionInput<K,V>` all use that same keyed capability.
50
- Use named-object `derive` for pure aggregate values. The `derive.keyed` family owns
51
- keyed derivation: ordinary `derive.keyed` preserves driver membership/order;
52
- `keys`/`values`/`entries` expose standard ordered read shapes; `get` is the precise
53
- scalar keyed lookup; `subset`/`filter`/`compact` own membership-changing derivation;
54
- `groupBy` is the generic reverse-index primitive and `singleton` is optional-scalar
55
- to 0/1 keyed composition.
56
- Per-entry equality suppresses unchanged generated values. Its dependency form uses a named object: plain Projection members
57
- invalidate the driver key set, while `{ source, key }` / `{ source, keys }` members let the Runtime own
58
- singular/plural per-output-key source bindings and reverse lookup. Selectors receive
59
- `(entry, key)` or `(entry, key, dependencies)`. The producer graph remains explicit and static;
60
- processors never perform imperative Runtime reads to discover dependencies.
61
- Use `runtime.items(keyedProjection)` / `scope.items(...)` for Runtime-owned ordered
62
- membership plus stable per-membership item Readables; do not rebuild that cache in adapters.
63
- `input.collection` edits have a strong exception boundary: callback or per-entry
64
- equality failure leaves both the published value and the next draft unchanged.
65
- A `ProjectionScope` only adds lifecycle ownership through `scope.own(...)`; create
66
- definitions with the root declaration APIs, then own a projection or static output tree
67
- before that definition is first materialized as a root.
68
- Use advanced `incremental` only for retained state or cross-key coordination that
69
- cannot be expressed by the `derive.keyed` family. `incremental.keyed` owns independent
70
- per-driver-key retained state while preserving driver membership/order and reuses the
71
- same dynamic keyed dependency routing. `collectionChange.keys` is the
72
- transport-level iterable for incremental added/updated/removed keys; callers handle
73
- reset and order semantics themselves. React selector tracking remains a consumer
74
- concern through `ProjectionProvider` and `useProjection(projection, selector)`.
75
- - Advanced incremental definitions always declare `process`; `state()` is present only
76
- when retained state is actually needed. `incremental.group` declares its static
77
- output tree through the synchronous `output` callback. `define.value` and
78
- `define.collection` are callback methods, not separate imports; every returned leaf
79
- is a `Projection<T>` or `KeyedProjection<K,V>` from the same producer.
80
- - Observer errors leave commits accepted. Do not retry as if they rolled back.
81
- - Core stays framework-neutral; adapters consume standard `Readable`,
82
- document `select`, and projection readables without internal target/address protocols.
83
- - Local sync owns browser persistence/leadership. Network conflict policy and
84
- collaborative undo belong at a separate boundary.
85
-
86
- Update examples and tests when behavior changes. Never add compatibility APIs,
87
- a second path grammar, a parallel join/dependency protocol, or another writable
88
- derived cache.
8
+ Use this skill as the public contract for Doxum. When Doxum is a dependency, work from the public API and these references. Do not inspect `core/src`, `react/src`, tests, source maps, or built JavaScript to discover how application code should use Doxum. If a public behavior is missing here while working in the Doxum repository, treat that as a skill documentation defect and update the skill together with the API change.
9
+
10
+ Answer and write examples in the user's language. The canonical references are intentionally single-source so API semantics cannot drift between translations.
11
+
12
+ ## Ownership spine
13
+
14
+ Choose the owner before choosing an API:
15
+
16
+ - **Canonical application data** belongs to one `DocumentRuntime` created by `createDocument`.
17
+ - **Pure derived data** belongs to lazy `Projection` definitions and one materializing `ProjectionRuntime`.
18
+ - **Runtime-local UI/application state** belongs to `input` or `input.collection`, not to the canonical document unless it must participate in document history, persistence, replay, or impact.
19
+ - **Framework state** stays behind adapters. React consumes `Readable`, document selectors, projections, and projection inputs.
20
+ - **Browser durability/leadership** belongs to `doxum/local-sync`; network collaboration and distributed conflict policy are separate boundaries.
21
+ - **Retained incremental state** belongs to `doxum/advanced` only when ordinary `derive` / `derive.keyed` cannot express the work clearly.
22
+
23
+ ## Reference routing
24
+
25
+ Read the smallest complete reference for the task before implementing it.
26
+
27
+ | Task | Read |
28
+ | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------- |
29
+ | Exact imports, signatures, callback fields, result types, errors, and all public exports | [Public API](references/public-api.md) |
30
+ | Schema modeling, validators, Draft operations, reads, snapshots, commits, history, apply, impact, trees | [Document runtime](references/document-runtime.md) |
31
+ | `observe`, `derive`, the `derive.keyed` family, dynamic keyed dependencies, Runtime/scope/items, batching, advanced processors | [Projections](references/projections.md) |
32
+ | React, external sources, `Readable`, and local-sync lifecycle | [Integrations](references/integrations.md) |
33
+ | End-to-end usage patterns that can be adapted directly | [Recipes](references/recipes.md) |
34
+ | Editing or reviewing Doxum internals, ownership boundaries, scheduler/mutation architecture, release-facing invariants | [Maintainer invariants](references/invariants.md) |
35
+
36
+ For application work, do not read the maintainer reference unless the task is actually changing Doxum itself.
37
+
38
+ ## Default API decisions
39
+
40
+ Follow these choices unless the requirement says otherwise:
41
+
42
+ 1. Model stable document structure with `object`, `variant`, `map`, `table`, `list`, and `tree`; use `field` for atomic payloads that are replaced as a whole.
43
+ 2. Mutate canonical state only through `document.update`, `document.apply`, `document.replace`, or history operations. Draft/read proxies are synchronous borrowed views and must not escape their callback.
44
+ 3. Use `read` for one synchronous document read and `select` for a document `Readable` with dynamic dependency tracking.
45
+ 4. Use `observe` to bring a document/readable/external source into the projection graph.
46
+ 5. Use named-object `derive` for pure aggregate values.
47
+ 6. Use `derive.keyed` when one keyed source owns output membership/order. Prefer its built-in `keys`, `values`, `entries`, `get`, `subset`, `filter`, `compact`, `groupBy`, and `singleton` primitives over hand-written incremental collection patches.
48
+ 7. Express per-output-key joins through `{ source, key }` or `{ source, keys }` dependencies. Let the Runtime own binding and reverse invalidation; do not maintain an application `Map` just to route dependency changes.
49
+ 8. Use `runtime.items(keyedProjection)` or `scope.items(...)` for stable per-membership item `Readable`s.
50
+ 9. Use `incremental.keyed` for independent retained state per driver key. Use `incremental.collection` or `incremental.group` only when direct incremental output patching or shared retained state is required.
51
+ 10. Use `ProjectionScope` only for lifecycle ownership. Define projections normally, then `scope.own(...)` before that definition is first materialized as a root.
52
+
53
+ ## Failure and lifecycle rules
54
+
55
+ - Doxum callbacks are synchronous unless the public API explicitly returns a Promise.
56
+ - `TransactionRejected` expresses expected application rejection; ordinary thrown exceptions roll back and rethrow unchanged.
57
+ - A committed document write remains committed even if projection or listener notification later fails; observer errors are reported on the result or through projection error handling.
58
+ - `CollectionChange` is an advanced processor transport contract. Reset, membership transitions, value updates, and order are distinct facts.
59
+ - `input.collection` has a strong exception boundary: callback or equality failure installs no partial next state.
60
+ - Dispose the owning `ProjectionRuntime`, `ProjectionScope`, and `LocalSync` with the application/service lifecycle that created them.
61
+
62
+ ## Completion contract
63
+
64
+ Before finishing Doxum application work, verify that:
65
+
66
+ - every import comes from `doxum`, `doxum/advanced`, `doxum/react`, or `doxum/local-sync`;
67
+ - no application behavior depends on an internal path, private brand, generated chunk, or source implementation detail;
68
+ - canonical, projection, input, adapter, and persistence responsibilities have one owner each;
69
+ - advanced APIs are used only for requirements that the ordinary public projection family cannot express;
70
+ - examples follow the documented public failure and lifecycle semantics.
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: 'Doxum Runtime'
3
- short_description: 'Build and review Doxum core, projections, React, and local-sync.'
4
- default_prompt: 'Use $doxum-runtime to implement this Doxum feature while preserving its runtime invariants.'
3
+ short_description: 'Use Doxum public APIs without reading implementation source.'
4
+ default_prompt: 'Use $doxum-runtime to implement this with the documented public Doxum APIs and lifecycle semantics.'
@@ -0,0 +1,370 @@
1
+ # Doxum Document Runtime
2
+
3
+ Use this reference for schema design, canonical document mutation, reads, subscriptions, history, replay, impact, validation, and tree behavior. Exact signatures and exported types are in [Public API](public-api.md).
4
+
5
+ ## Mental model
6
+
7
+ A Doxum document has one canonical owner: `DocumentRuntime`. The schema defines structure and identity; the runtime owns canonical data, revision, mutation, ChangeSets, history, and commit publication.
8
+
9
+ Use these representation choices consistently:
10
+
11
+ | Requirement | Model |
12
+ | ------------------------------------------------------- | ----------------------------- |
13
+ | Fixed named structure | `object({...})` |
14
+ | Tagged union of object branches | `variant(tag, variants)` |
15
+ | Arbitrary payload replaced as one value | `field<T>()` |
16
+ | Dynamic keyed values without explicit order | `map(valueSchema)` |
17
+ | Ordered keyed object/variant entities | `table(entitySchema)` |
18
+ | Ordered atomic items with stable identity from the item | `list(field<T>(), { keyOf })` |
19
+ | Parent/child topology | `tree(field<T>())` |
20
+ | Optional member | `optional(schema)` |
21
+
22
+ Do not model dynamic keys as undeclared object properties. Object/variant input is closed to declared own members; dynamic domains belong in maps or fields.
23
+
24
+ ## Atomic payloads and structure
25
+
26
+ `field<T>` is atomic. If `T` is an object, array, `Map`, `Set`, or `Date`, Doxum still treats the whole payload as one value. Access is deeply readonly through `ReadonlyValue<T>` and mutation replaces the payload whole.
27
+
28
+ Use structure when Doxum must understand and address nested edits. For example:
29
+
30
+ ```ts
31
+ const point = object({
32
+ x: field<number>(),
33
+ y: field<number>(),
34
+ });
35
+
36
+ const stroke = field<readonly { x: number; y: number }[]>();
37
+ ```
38
+
39
+ `point.x` can change independently and participates in path impact. `stroke` changes as one atomic payload.
40
+
41
+ Published atomic payload references are shared across snapshots, commits, and history. Treat them as immutable through every alias. Doxum does not deep-freeze application payloads and does not clone them merely to defend against mutation.
42
+
43
+ ## Schema handles and inferred types
44
+
45
+ Use `Schema<T>` / `ObjectSchema<T>` as the portable schema boundary and `Infer<typeof schema>` for the canonical value type.
46
+
47
+ ```ts
48
+ const task = object({
49
+ title: field<string>(),
50
+ done: field<boolean>(),
51
+ });
52
+
53
+ const model = object({
54
+ tasks: map(task),
55
+ });
56
+
57
+ type DocumentValue = Infer<typeof model>;
58
+ ```
59
+
60
+ Export inferred schema constants directly. Do not expose internal node types or wrap public schemas in `ReturnType<typeof object>` to make declarations portable.
61
+
62
+ ## Optionality
63
+
64
+ Optionality is structural presence, not an ordinary value convention.
65
+
66
+ ```ts
67
+ const model = object({
68
+ nickname: optional(field<string>()),
69
+ settings: optional(map(field<string>())),
70
+ });
71
+ ```
72
+
73
+ Absent and present-with-`undefined` are distinct where the schema permits an atomic `undefined` payload. Use the schema's optional shape rather than inventing sentinel members.
74
+
75
+ Tree optionality has two separate meanings:
76
+
77
+ - `tree(optional(field<T>()))`: each existing node may omit `value`.
78
+ - `optional(tree(field<T>()))`: the whole tree member may be absent.
79
+
80
+ ## Validators and parse
81
+
82
+ Validators are synchronous and pure. A function validator may be a predicate or assertion:
83
+
84
+ ```ts
85
+ const taskId = (value: unknown): value is TaskId =>
86
+ typeof value === 'string' && value.startsWith('task:');
87
+ ```
88
+
89
+ Returning `true` or `undefined` means success; returning `false` rejects. An assertion may throw. Standard Schema v1 validators are also accepted, but successful validation must return the same input by identity. Doxum does not accept validator transforms.
90
+
91
+ Transform external values before they enter Doxum. Use `parse(schema, unknownValue)` to validate unknown input and build schema-owned structure while sharing readonly atomic payloads.
92
+
93
+ Map/table key validators define the public key type and apply at parse/mutation boundaries:
94
+
95
+ ```ts
96
+ const model = object({
97
+ tasks: map(task, { key: taskId }),
98
+ });
99
+ ```
100
+
101
+ Branded key types then flow through collection methods, typed paths, impact, and keyed projections.
102
+
103
+ ## Creating the canonical runtime
104
+
105
+ ```ts
106
+ const document = createDocument({
107
+ schema: model,
108
+ initial,
109
+ history: { capacity: 100 },
110
+ });
111
+ ```
112
+
113
+ `createDocument` is the only canonical state owner. Do not mirror canonical document state in a second writable store and synchronize both manually.
114
+
115
+ `document.readonly()` returns a read/subscription-only alias bound to the same runtime identity.
116
+
117
+ ## Update lifecycle
118
+
119
+ ```ts
120
+ const result = document.update(draft => {
121
+ const task = draft.tasks.get(taskId);
122
+ if (!task) throw new TransactionRejected({ code: 'missing-task', message: 'Task missing' });
123
+ task.done = true;
124
+ return taskId;
125
+ });
126
+ ```
127
+
128
+ A transaction is synchronous and atomic:
129
+
130
+ - reads inside the callback see preceding writes in the same transaction;
131
+ - all accepted writes publish together as one commit;
132
+ - an ordinary thrown value rolls back all work and is rethrown unchanged;
133
+ - `TransactionRejected` rolls back and returns `status: 'rejected'` with application diagnostics;
134
+ - returning `false`, `undefined`, or any other ordinary value is just a callback result, not a rejection signal;
135
+ - a net-zero transaction returns `status: 'unchanged'` and publishes no commit.
136
+
137
+ The callback may not return a Promise.
138
+
139
+ ### Source and history options
140
+
141
+ `update` accepts `{ source?: 'local' | 'system', history?: boolean }`.
142
+
143
+ - `local` is the default application-authored commit source.
144
+ - `system` is for application/system work that is still local canonical mutation.
145
+ - `history: false` prevents the commit from entering local undo history.
146
+
147
+ ## Borrowed Draft/Read lifetime
148
+
149
+ Draft/read access objects are synchronous borrowed capabilities. Do not retain any Draft/Read object, collection accessor, or method past its callback.
150
+
151
+ Wrong:
152
+
153
+ ```ts
154
+ let tasks;
155
+ document.update(draft => {
156
+ tasks = draft.tasks;
157
+ });
158
+ tasks.put('later', value);
159
+ ```
160
+
161
+ Use a durable value or a fresh callback instead. When a read must outlive the callback, use `snapshot`:
162
+
163
+ ```ts
164
+ const tasks = read(document, state => snapshot(state.tasks));
165
+ ```
166
+
167
+ `snapshot` copies schema structure and shares atomic payload references under the readonly ownership contract.
168
+
169
+ ## Collection mutation rules
170
+
171
+ ### Map
172
+
173
+ A map is unordered dynamic membership.
174
+
175
+ ```ts
176
+ draft.tasks.put(id, value); // upsert
177
+ draft.tasks.remove(id); // missing is a no-op
178
+ draft.tasks.replace(nextRecord);
179
+ ```
180
+
181
+ ### Table
182
+
183
+ A table is ordered membership plus object/variant entities.
184
+
185
+ ```ts
186
+ draft.rows.create(id, value, { at: 'start' });
187
+ draft.rows.create([{ id, value }, ...], { after: existingId });
188
+ draft.rows.remove(idOrIds);
189
+ draft.rows.move(idOrIds, { before: anchorId });
190
+ draft.rows.reorder(exactCurrentMembership);
191
+ draft.rows.replace(id, value);
192
+ draft.rows.replace({ ids, byId });
193
+ ```
194
+
195
+ `replace(id,value)` preserves that member's order position. `reorder` requires an exact permutation of current membership.
196
+
197
+ ### List
198
+
199
+ A list stores atomic items and derives stable identity from `keyOf`.
200
+
201
+ ```ts
202
+ draft.rows.insert(value, { after: id });
203
+ draft.rows.remove(id);
204
+ draft.rows.move(idOrIds, { at: 'end' });
205
+ draft.rows.reorder(exactIds);
206
+ draft.rows.replace(id, replacementWithSameKey);
207
+ draft.rows.replace(nextArray);
208
+ ```
209
+
210
+ Member replacement must retain the addressed stable key.
211
+
212
+ ### Move semantics
213
+
214
+ For table/list `move(selection, anchor?)`, Doxum first removes the selection, preserves the selected members' current relative order, then resolves the anchor against the remaining sequence. Omitting the anchor means end.
215
+
216
+ ### Tree
217
+
218
+ Trees are empty or one connected single-root tree. Nodes have reciprocal parent/children topology and no cycles.
219
+
220
+ ```ts
221
+ draft.outline.insert(id, value, { parentId, index });
222
+ draft.outline.move(id, { parentId, index });
223
+ draft.outline.remove(id);
224
+ draft.outline.replace(id, newPayload);
225
+ draft.outline.replace(wholeTree);
226
+ ```
227
+
228
+ `index` is the final child position after removal from the old location. A tree member replacement must satisfy full topology invariants at the boundary.
229
+
230
+ Do not edit `rootId`, `parentId`, `children`, or `nodes` as raw records through application code; use tree operations.
231
+
232
+ ## Replacing complex object/variant members
233
+
234
+ Draft object/variant members that contain collection nodes expose collection capabilities rather than plain assignable data. To replace one such member from a plain `Infer` value, use top-level `replace`:
235
+
236
+ ```ts
237
+ document.update(draft => {
238
+ replace(draft.settings, 'rows', {
239
+ ids: ['a'],
240
+ byId: { a: { title: 'A' } },
241
+ });
242
+ });
243
+ ```
244
+
245
+ A collection's own `replace(next)` remains the normal whole-collection replacement when the collection itself is the mutation target.
246
+
247
+ ## Reading canonical state
248
+
249
+ Use `document.snapshot()` for a durable whole-document value.
250
+
251
+ Use `read` for one synchronous selector read:
252
+
253
+ ```ts
254
+ const done = read(document, state => state.tasks.get(taskId)?.done);
255
+ ```
256
+
257
+ Use `select` for a revisioned `Readable`:
258
+
259
+ ```ts
260
+ const selected = select(document, state => state.tasks.get(taskId)?.done);
261
+ const stop = selected.subscribe(() => render(selected.current()));
262
+ ```
263
+
264
+ `select` tracks actual reads, including branching, and rebinds dependencies when the selector's read set changes. Equality defaults to `Object.is`; supply custom equality only for the projected result, not to redefine document semantics.
265
+
266
+ ## Typed paths and subscriptions
267
+
268
+ Path callbacks describe schema locations without reading current data:
269
+
270
+ ```ts
271
+ const stop = document.subscribe(
272
+ path => path.tasks.item(taskId).done,
273
+ commit => handle(commit)
274
+ );
275
+ ```
276
+
277
+ You can subscribe to one path or a non-empty array of paths. Paths compile when the subscription/impact query is registered. They remain meaningful even when an optional/member path is currently absent.
278
+
279
+ Tree paths expose:
280
+
281
+ ```ts
282
+ path.outline.rootId;
283
+ path.outline.nodes;
284
+ path.outline.nodes.item(nodeId);
285
+ ```
286
+
287
+ ## Commits and observer errors
288
+
289
+ An accepted commit contains:
290
+
291
+ - monotonic local `revision`;
292
+ - `source` (`local`, `system`, `history`, `remote`);
293
+ - exact normalized reversible `changes`;
294
+ - exact `impact` queries.
295
+
296
+ Commit listeners and projection processing occur after canonical acceptance. Their failures do not turn an accepted mutation into a rejected one. `TransactionResult` / `OperationResult` report post-commit observer failures in `observerErrors`.
297
+
298
+ ## ChangeSet semantics
299
+
300
+ A member group owns one container address:
301
+
302
+ ```ts
303
+ {
304
+ kind: 'members',
305
+ at: ['tasks', taskId],
306
+ members: [
307
+ { key: 'title', kind: 'updated', before: 'A', after: 'B' },
308
+ { key: 'done', kind: 'updated', before: false, after: true },
309
+ ],
310
+ }
311
+ ```
312
+
313
+ An ordered container may include `order: { before, after }` in the same group. Order-only groups have an empty `members` array. There is no standalone order change kind.
314
+
315
+ Tree changes use one tree-container address plus changed node transitions and before/after root id. Whole-document reset is explicit.
316
+
317
+ ChangeSets are final net changes, not an operation log. Multiple writes that cancel out do not publish a change.
318
+
319
+ ## Applying external ChangeSets
320
+
321
+ ```ts
322
+ const result = document.apply(changes, {
323
+ expectedRevision: document.revision(),
324
+ source: 'remote',
325
+ });
326
+ ```
327
+
328
+ `expectedRevision` is mandatory. External `before` data is transport input, not authority: Doxum checks local baseline/state and records actual local before values for reversible local history.
329
+
330
+ A remote commit invalidates local history. Treat local revision as a local concurrency baseline, not as a globally distributed clock.
331
+
332
+ ## History
333
+
334
+ ```ts
335
+ const group = document.history.group();
336
+ try {
337
+ document.update(...);
338
+ document.update(...);
339
+ group.end();
340
+ } catch (error) {
341
+ group.cancel();
342
+ throw error;
343
+ }
344
+ ```
345
+
346
+ `undo()` / `redo()` travel complete ChangeSets atomically. `group().end()` groups completed history entries; `cancel()` restores the group's start through history travel.
347
+
348
+ A local whole-document replace is reversible. Remote commits clear/invalidate local history because the old local history baseline no longer describes the canonical branch.
349
+
350
+ ## Impact
351
+
352
+ Use commit impact instead of manually parsing ChangeSets for ordinary subscription/application invalidation questions:
353
+
354
+ ```ts
355
+ if (commit.impact.affects(path => path.tasks.item(id).done)) { ... }
356
+
357
+ const impact = commit.impact.collection(path => path.tasks);
358
+ if (impact.kind === 'incremental') {
359
+ impact.added;
360
+ impact.removed;
361
+ impact.updated;
362
+ impact.orderChanged;
363
+ }
364
+ ```
365
+
366
+ Use raw `ChangeSet` only when the exact reversible transport is itself the requirement.
367
+
368
+ ## Disposal
369
+
370
+ `document.dispose()` ends the runtime. Later access throws `DocumentDisposedError`. Own the document runtime at the application/service boundary that owns its canonical state.