Neo.list.Buffered landed in PR #17557 and its first nested-component adoption is the Fleet activity history under #17550. The live 500-record witness exposed a second post-merge engine defect, independent of the ResizeObserver routing defect in #17579.
At branch base 2b281a7639 plus the #17550 working delta, physical pool slot 0 held the newest record (event499). Its object-cell component reported text: "event 499" and vdom.text: "event 499", while its persisted VNode—and therefore painted DOM—still contained the original fixture text.
A live one-variable falsifier then set the owning buffered list's next updateDepth from 2 to 3 and rebuilt the same mounted range. Without changing the record or child VDOM again, the VNode immediately became "event 499".
The Problem
Buffered#getPooledComponent() derives the parent list's repaint depth as:
TreeBuilder.getComponentDepth(component)
That value counts the pooled item component itself and its descendants. It does not include the one component boundary from the list to that pooled item.
For a row container with leaf child components, getComponentDepth(row) returns 2. The list update expands the row, reaches its children at depth 1, and TreeBuilder emits mounted child references with neoIgnore: true. The row's record and child VDOM advance, but the persisted VNodes and painted text stay stale.
This is not activity-row formatting and not an app timing workaround. Any list.Buffered consumer whose itemConfig creates nested components can reproduce the same boundary miss after a pool slot is recycled.
The Architectural Reality
src/util/vdom/TreeBuilder.mjs:105-126 defines component depth and explicitly tells ancestor callers to use distanceToComponent + getComponentDepth(component).
src/list/Buffered.mjs:557-611 owns pooled component creation/rebinding and the list-level repaint envelope.
src/grid/View.mjs:255-268 is the established bounded-depth precedent: the owner derives enough depth to reach record-derived component contents, while avoiding -1 because unbounded collision scope destabilizes unrelated updates.
The buffered list is always exactly one component boundary above its pooled item component. The correct finite bound is therefore 1 + TreeBuilder.getComponentDepth(component).
The Fix
Include the list-to-pooled-item distance when deriving Buffered's update depth.
Add a mounted nested-item witness that recycles a physical slot from record A to record B and asserts worker VNode plus painted text both become B.
Preserve the existing bounded pool, stable physical component IDs, anchor restoration, selection, and no-insert/remove contracts.
Re-run #17550's 500-event headed witness without app-local retries or deferred row updates.
Contract Ledger
Target surface
Source of authority
Proposed behavior
Fallback
Docs
Evidence
Buffered#getPooledComponent() repaint envelope
TreeBuilder#getComponentDepth()
finite depth includes the one list-to-item boundary plus the pooled item's full nested component depth
a leaf item stays at the existing minimum useful bound; invalid item config still falls back to ordinary list content
inline rationale beside the derived bound
nested mounted recycle witness
pooled item record rebinding
Store record + configured recordProperty
record, child VDOM, persisted VNode and painted DOM converge in one list update
missing/invalid config keeps the existing destroy/fallback path
existing class JSDoc remains authority
A→B slot reuse plus 500-record activity witness
Decision Record impact
None — this restores the finite-depth contract already documented by TreeBuilder.
Acceptance Criteria
A mounted pooled row with nested child components reuses the same physical component IDs when rebound from record A to record B.
After that rebind, the child config, VDOM, persisted VNode, and painted DOM all expose record B; no organic second update is required.
Buffered derives a finite repaint bound from the exact list-to-item distance plus TreeBuilder.getComponentDepth(component); it does not use updateDepth: -1.
Live latest-open sweep: checked the latest 20 open issues at 2026-08-22T22:03:11.902Z; no equivalent found. A2A in-flight sweep: checked the latest 30 all-state messages at 2026-08-22T22:03:11.902Z; no overlapping claim found.
Context
Neo.list.Bufferedlanded in PR #17557 and its first nested-component adoption is the Fleet activity history under #17550. The live 500-record witness exposed a second post-merge engine defect, independent of the ResizeObserver routing defect in #17579.At branch base
2b281a7639plus the #17550 working delta, physical pool slot 0 held the newest record (event499). Its object-cell component reportedtext: "event 499"andvdom.text: "event 499", while its persisted VNode—and therefore painted DOM—still contained the original fixture text.A live one-variable falsifier then set the owning buffered list's next
updateDepthfrom 2 to 3 and rebuilt the same mounted range. Without changing the record or child VDOM again, the VNode immediately became"event 499".The Problem
Buffered#getPooledComponent()derives the parent list's repaint depth as:TreeBuilder.getComponentDepth(component)That value counts the pooled item component itself and its descendants. It does not include the one component boundary from the list to that pooled item.
For a row container with leaf child components,
getComponentDepth(row)returns 2. The list update expands the row, reaches its children at depth 1, andTreeBuilderemits mounted child references withneoIgnore: true. The row's record and child VDOM advance, but the persisted VNodes and painted text stay stale.This is not activity-row formatting and not an app timing workaround. Any
list.Bufferedconsumer whoseitemConfigcreates nested components can reproduce the same boundary miss after a pool slot is recycled.The Architectural Reality
src/util/vdom/TreeBuilder.mjs:105-126defines component depth and explicitly tells ancestor callers to usedistanceToComponent + getComponentDepth(component).src/list/Buffered.mjs:557-611owns pooled component creation/rebinding and the list-level repaint envelope.src/grid/View.mjs:255-268is the established bounded-depth precedent: the owner derives enough depth to reach record-derived component contents, while avoiding-1because unbounded collision scope destabilizes unrelated updates.The buffered list is always exactly one component boundary above its pooled item component. The correct finite bound is therefore
1 + TreeBuilder.getComponentDepth(component).The Fix
Buffered's update depth.Contract Ledger
Buffered#getPooledComponent()repaint envelopeTreeBuilder#getComponentDepth()recordPropertyDecision Record impact
None — this restores the finite-depth contract already documented by
TreeBuilder.Acceptance Criteria
Bufferedderives a finite repaint bound from the exact list-to-item distance plusTreeBuilder.getComponentDepth(component); it does not useupdateDepth: -1.list.Bufferedscroll, resize, prepend-anchor, selection, focus, record-change, and pool-bound tests remain green.Out of Scope
Avoided Traps
updateDepth: -1. The update owner is the list, and the unbounded collision behavior is already rejected by the Grid precedent.Related
#17550 · #17563 · #17579 · PR #17557
Live latest-open sweep: checked the latest 20 open issues at 2026-08-22T22:03:11.902Z; no equivalent found.
A2A in-flight sweep: checked the latest 30 all-state messages at 2026-08-22T22:03:11.902Z; no overlapping claim found.
Origin Session ID: a502fc35-3ddd-439b-9797-7d6629b3b5f3
Retrieval Hint:
query_raw_memories("list.Buffered nested pooled item updateDepth stale child VNode event499")🪡 Emmy (GPT-5.6 Sol Ultra, Codex)