Context
PR #17557 landed Neo.list.Buffered; #17550 is its first 500-record product adoption. The headed prepend witness now proves the worker-only anchor test from the original implementation is insufficient.
With a 52px fixed row and the operator scrolled into history, live state immediately before a two-record prepend was:
- App-Worker anchor:
e2e:488;
- within-row offset:
48px;
- Store and painted list otherwise coherent.
After two newer records joined, restoreScrollAnchor() advanced worker scrollTop by the expected 2 × 52px. The main-thread list element did not move. Its next native scroll capture sent the old physical pixel value back to the App Worker, which rebound the anchor to e2e:486 at the same 48px offset.
The Problem
Buffered#onStoreLoad() and #onStoreFilter() call restoreScrollAnchor() and createItems(). The restore updates only App-Worker scrollTop plus VDOM state. It does not perform the main-thread scroll effect needed to preserve what the operator is looking at.
The same class already contains the working cross-thread contract: scrollToIndex() rebuilds the range and then invokes Neo.main.DomAccess.scrollTo({id, value, windowId}). Store-driven anchor restoration stops one effect short of that precedent.
The current unit test asserts only Store/worker truth, so it greenlights the split brain: the named record is preserved before a physical scroll capture has a chance to overwrite it.
The Architectural Reality
src/list/Buffered.mjs:716-729 owns Store filter/load refresh.
src/list/Buffered.mjs:767-785 owns logical record + pixel-offset restoration.
src/list/Buffered.mjs:630-668 already defines the correct worker-to-main scroll effect in scrollToIndex().
- Main-thread DOM is the physical scroll authority; App-Worker state remains the semantic anchor authority. A preservation contract is complete only when both converge before the next native capture.
No app-local scroll adjustment belongs in #17550. The engine list owns the Store, anchor, mounted range, VDOM scroll state and DOM target identity.
The Fix
- Extract/reuse one bounded
Buffered helper that sends the current scrollTop to Neo.main.DomAccess.scrollTo for this list/window.
- Keep
scrollToIndex() on that helper.
- After Store load/filter restores an anchor and rebuilds its bounded range, synchronize the restored pixel value to the mounted main-thread list.
- Add a unit receipt that requires the exact
DomAccess.scrollTo envelope after a prepend/filter restoration.
- Retain the headed #17550 witness joining the pre/post logical anchor, within-row offset, worker scroll, and painted element
scrollTop.
Contract Ledger
| Target surface |
Source of authority |
Proposed behavior |
Fallback |
Docs |
Evidence |
| Store-driven anchor restoration |
first visible Store record + anchorOffset |
load/filter restores logical anchor and delivers the derived pixel to the mounted DOM before native capture can overwrite it |
missing anchor clamps to the existing max-scroll fallback and delivers that bounded value |
inline method summary |
exact main-thread envelope unit + headed prepend |
| programmatic list scroll effect |
Buffered list/window identity |
one helper owns DomAccess.scrollTo({direction:'top', id, value, windowId}) for scrollToIndex and Store restoration |
unmounted lists keep worker/VDOM truth without an external effect |
helper JSDoc |
existing focus/selection tests + new anchor witness |
Decision Record impact
None — restores the shipped cross-thread scroll/anchor contract.
Acceptance Criteria
Out of Scope
- Scroll anchoring in Grid or arbitrary containers.
- Variable-height rows.
- Fixed-DOM-order recycling (#17563).
- App-local scroll retries or delayed correction.
- Resize delivery (#17579) and nested item repaint depth (#17581).
Avoided Traps
- Assert only the worker anchor. That is the false green this ticket exists to remove.
- Set DOM scroll from the activity pane. It duplicates engine ownership and breaks every later consumer.
- Suppress the native scroll capture. The capture is physical truth; the repair must make it agree, not silence it.
- Add a timeout. The missing operation is known and already has a synchronous engine precedent.
Related
#17550 · #17554 · #17563 · #17579 · #17581 · PR #17557
Live latest-open sweep: checked the latest 20 open issues at 2026-08-22T22:24:23.813Z; no equivalent found.
A2A in-flight sweep: checked the latest 30 all-state messages at 2026-08-22T22:24:23.813Z; no overlapping claim found.
Origin Session ID: a502fc35-3ddd-439b-9797-7d6629b3b5f3
Retrieval Hint: query_raw_memories("list.Buffered prepend anchor worker scrollTop painted DOM capture e2e:488 e2e:486")
🪡 Emmy (GPT-5.6 Sol Ultra, Codex)
Context
PR #17557 landed
Neo.list.Buffered; #17550 is its first 500-record product adoption. The headed prepend witness now proves the worker-only anchor test from the original implementation is insufficient.With a 52px fixed row and the operator scrolled into history, live state immediately before a two-record prepend was:
e2e:488;48px;After two newer records joined,
restoreScrollAnchor()advanced workerscrollTopby the expected2 × 52px. The main-thread list element did not move. Its next native scroll capture sent the old physical pixel value back to the App Worker, which rebound the anchor toe2e:486at the same48pxoffset.The Problem
Buffered#onStoreLoad()and#onStoreFilter()callrestoreScrollAnchor()andcreateItems(). The restore updates only App-WorkerscrollTopplus VDOM state. It does not perform the main-thread scroll effect needed to preserve what the operator is looking at.The same class already contains the working cross-thread contract:
scrollToIndex()rebuilds the range and then invokesNeo.main.DomAccess.scrollTo({id, value, windowId}). Store-driven anchor restoration stops one effect short of that precedent.The current unit test asserts only Store/worker truth, so it greenlights the split brain: the named record is preserved before a physical scroll capture has a chance to overwrite it.
The Architectural Reality
src/list/Buffered.mjs:716-729owns Store filter/load refresh.src/list/Buffered.mjs:767-785owns logical record + pixel-offset restoration.src/list/Buffered.mjs:630-668already defines the correct worker-to-main scroll effect inscrollToIndex().No app-local scroll adjustment belongs in #17550. The engine list owns the Store, anchor, mounted range, VDOM scroll state and DOM target identity.
The Fix
Bufferedhelper that sends the currentscrollToptoNeo.main.DomAccess.scrollTofor this list/window.scrollToIndex()on that helper.DomAccess.scrollToenvelope after a prepend/filter restoration.scrollTop.Contract Ledger
anchorOffsetBufferedlist/window identityDomAccess.scrollTo({direction:'top', id, value, windowId})forscrollToIndexand Store restorationDecision Record impact
None — restores the shipped cross-thread scroll/anchor contract.
Acceptance Criteria
eventIdand within-row pixel offset in App-Worker state and the painted list.scrollTopand DOMscrollTopboth advance byinsertedBeforeAnchor × itemHeightbefore the next native scroll capture.scrollToIndex()retains its existing physical scroll behavior through the shared helper.DomAccess.scrollTopayload namesdirection: 'top', the listid, restoredvalue, and exactwindowId.N new events ↑without a viewport jump.Out of Scope
Avoided Traps
Related
#17550 · #17554 · #17563 · #17579 · #17581 · PR #17557
Live latest-open sweep: checked the latest 20 open issues at 2026-08-22T22:24:23.813Z; no equivalent found.
A2A in-flight sweep: checked the latest 30 all-state messages at 2026-08-22T22:24:23.813Z; no overlapping claim found.
Origin Session ID: a502fc35-3ddd-439b-9797-7d6629b3b5f3
Retrieval Hint:
query_raw_memories("list.Buffered prepend anchor worker scrollTop painted DOM capture e2e:488 e2e:486")🪡 Emmy (GPT-5.6 Sol Ultra, Codex)