1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Li/Mikewolfli/Wei Li(mikewolfli@163.com)
// SPDX-License-Identifier: MIT
//! Row-level invalidation for a declarative list.
//!
//! # The gap this closes
//!
//! A [`View`](crate::view::View) rebuilds by describing its whole tree and diffing that against
//! the previous one. For a list that is usually the right trade: the diff stops at an unchanged
//! subtree, so an idle rebuild is cheap. It is the wrong trade for a **large list whose rows
//! arrive from a model**: inserting one row at index 400 of 10 000 makes the build walk every
//! row it did not change, and the diff walk them again to discover that.
//!
//! A [`RowSignal`] narrows that work. It says *which keyed container changed* and *how*, so the
//! engine can diff only that container's subtree. The build still describes the tree it
//! declares — the view stays a pure function of its state — but the **comparison** is scoped,
//! which is where the O(tree) cost was.
//!
//! # What the caller promises
//!
//! A signal is an assertion: "nothing outside `container`'s subtree changed since the last
//! build". The engine cannot verify that cheaply — verifying it is the full diff the signal
//! exists to avoid — so it is the producer's contract. A producer that gets it wrong sees a
//! stale tree, which is why the type is named for the assertion rather than for the mechanism.
//!
//! A signal whose container is not present in both trees (the key changed, the branch was
//! removed) falls back to a full diff. That is the safe direction: a wrong signal costs a
//! full rebuild, never a missed one.
//!
//! # Shape
//!
//! The change vocabulary mirrors what a model reports — insert, remove, move, update, reset —
//! rather than the patches the diff emits. A model knows rows were inserted; it does not know
//! which controls those rows materialised into. Translating between the two is precisely the
//! diff's job, so the signal never names an `ObjectId`.
use crate;
/// One structural change inside a keyed container.
///
/// Indices are positions in the **new** row list for [`Insert`](Self::Insert), and positions in
/// the **pre-change** list for [`Remove`](Self::Remove) and [`Move`](Self::Move), matching the
/// convention a model's own change notification carries. The engine only needs to know that the
/// container changed; it re-derives the exact patch set from the two trees.
/// A scoped invalidation: `container` changed, and this is how.
///
/// `container` is the [`Node::key`](crate::view::Node::key) of the list's host node. Keys are
/// only required to be unique among siblings, so a document with two lists that share a key
/// would make the scope ambiguous; the engine resolves the first match and the contract is the
/// same one [`Node::duplicate_sibling_keys`](crate::view::Node::duplicate_sibling_keys) reports.