@@ -156,7 +156,7 @@ ctor also switches `CreateMessage` to its `forLanguageStatistics` overload, whic
156156here: ` UpdateLanguageStatistics ` returns early on outgoing messages and every scheduled message
157157is outgoing.
158158
159- ### M. ` ProcessMessages ` / ` ProcessAlbums ` mutate a slice by index
159+ ### M. DONE - ` ProcessMessages ` / ` ProcessAlbums ` mutate a slice by index
160160
161161Both take ` IList<MessageViewModel> ` and are handed a ` MessageCollection ` at four call sites, then
162162walk it with an index while mutating it:
@@ -173,6 +173,20 @@ Neither reaches the live `Items` today - `ProcessMessages` is only ever given a
173173` List ` - so the stale map is thrown away before it matters. Latent, but it is the third thing the
174174tightened signature points at.
175175
176+ Fixed by deferring instead of walking backwards. Both loops now collect the items to drop and
177+ remove them by identity once the walk is over, so no index survives a removal and nothing has to
178+ reason about how many items ` RemoveItem ` took. The ` List ` is only allocated when something is
179+ actually dropped, which is the rare case. Deferral also makes ` ProcessAlbums ` ' ` slice[i] = group `
180+ trivially valid, since the slice no longer shrinks underneath it.
181+
182+ The removals were placed immediately after the loop and before the ` groups ` block, which reads
183+ ` first.IsFirst ` and ` album.Messages[^1].IsLast ` : removing there replays the same ` UpdateAttach `
184+ sequence the interleaved removals produced, so that block still sees what it saw.
185+
186+ ` SetItem ` is overridden and maintains the id map only. Its one caller swaps an album root in over
187+ the child that seeded it, so neither the day nor the neighbours change and there is no attach
188+ state to recompute - worth knowing before a second caller appears.
189+
176190### N. DONE - ` OnAttachChanged ` gave up after the first neighbour
177191
178192` ChatView.OnAttachChanged ` looped over the reported items, and the `if
@@ -222,21 +236,11 @@ worth landing alone so the diff is reviewable against the three existing copies.
222236once A is in: seam flag, drop ` index ` , delete ` RawRemoveAt ` , scope the flags, comment the
223237` ReplaceSlice ` invariant.
224238
225- ** 3. Stop index-walking a slice while mutating it.** Finding M, agreed 2026-08-27. Two parts:
226-
227- - ` ProcessMessages ` and ` ProcessAlbums ` must not assume ` RemoveAt(i) ` removes exactly one item.
228- Either walk backwards (` for (int i = slice.Count - 1; i >= 0; i--) ` ), which is immune to
229- anything the removal does below ` i ` , or re-read ` slice.Count ` and re-find the position after
230- each removal the way ` MoveMessageInOrder ` does. Walking backwards is the cheaper fix and
231- works for both loops, but check ` ProcessAlbums ` first: it accumulates album children into
232- ` album.Messages ` in encounter order, so reversing the walk reverses the album.
233- - ` MessageCollection ` should override ` SetItem ` to keep ` _messages ` in step, so
234- ` slice[i] = group ` maps the album's id and its children rather than leaving the replaced
235- message's id pointing at the old item. Cheap and it closes the hole rather than relying on
236- every caller knowing about it.
237-
238- Do this before phase 4: it is the only correctness item left, and ` SetItem ` is the last
239- mutation path that does not maintain the id map.
239+ ** 3. DONE. Stop index-walking a slice while mutating it.** Finding M. Both loops defer their
240+ removals and remove by identity; ` SetItem ` is overridden to maintain the id map. Walking
241+ backwards was the other candidate and was rejected: it revisits the item below a removal that
242+ also took a separator, and ` ProcessAlbums ` accumulates ` album.Messages ` in encounter order, so
243+ reversing it would reverse the album.
240244
241245** 4. DONE. Stop allocating per message.** Findings B and N. ` AttachChanged ` is
242246` Action<MessageViewModel, MessageViewModel> ` , and the suppressed paths write into ` Items `
0 commit comments