LearnNewsExamplesServices
Frontmatter
id17563
titlelist.Buffered: fixed-DOM-order row recycling
stateClosed
labels
enhancementaitestingperformancecore
assigneesneo-gpt-emmy
createdAtAug 22, 2026, 7:24 PM
updatedAtAug 23, 2026, 1:22 PM
githubUrlhttps://github.com/neomjs/neo/issues/17563
authorneo-gpt-emmy
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[x] 17554 list.Buffered: fixed-height component row windowing
blocking[]
closedAtAug 23, 2026, 1:22 PM

list.Buffered: fixed-DOM-order row recycling

Closed Backlog/active-chunk-18 enhancementaitestingperformancecore
neo-gpt-emmy
neo-gpt-emmy commented on Aug 22, 2026, 7:24 PM

Context

PR #17557 implements the #17554 fixed-height buffered component-list foundation: bounded physical slots, full-Store semantics, and zero steady-state row insertion/removal. Grid has a stronger, older pooling contract. Neo.grid.Body keeps row children in fixed physical order and repositions/rebinds them in place; Neo.grid.Row renders a dense cell pool, including hidden placeholders, so scrolling emits no structural DOM operations.

The historical progression is captured by #8992, #9002, and #9012: bounded allocation alone was insufficient while child rotation could still produce moveNode; the Grid contract became fixed physical order plus logical metadata.

Live latest-open sweep: checked the newest 20 open issues at 2026-08-22T17:23:55Z; no equivalent ticket found. The latest 30 A2A messages contained only the existing #17554 foundation claim, not this successor.

The Problem

Neo.list.Buffered#assignPoolSlots() currently preserves a surviving record's physical slot, then returns descriptors in logical Store order. createItems() writes those descriptors into vdom.cn in that logical order. When the mounted range advances, stable slot IDs can therefore rotate within the child array.

The current regression witness proves zero insertNode and zero removeNode, but it does not capture or assert moveNode. This is a source-proven contract gap, not a claimed runtime count: the first deliverable is an exact delta baseline.

The list cannot copy Grid's modulo/transform strategy blindly. A semantic <ul>/<li> must retain Store-order DOM and accessibility traversal; visual-only reordering would make the painted order and assistive reading order disagree.

The Architectural Reality

  • src/grid/Body.mjs:30-37,710-713,787-800 owns fixed-DOM-order row recycling.
  • src/grid/Row.mjs:286-305,409-421 owns the dense fixed-order cell pool.
  • src/list/Buffered.mjs:247-288 owns logical-record to physical-slot assignment.
  • src/list/Buffered.mjs:368-389 materializes top spacer, pooled rows, and bottom spacer.
  • test/playwright/unit/list/Buffered.spec.mjs:157-183 captures VDOM deltas but leaves moveNode unasserted.
  • List selection, focus, data.recordId, aria-posinset, and aria-setsize are logical-record contracts; physical slot IDs are implementation identity.

The list-specific fixed-order shape is slot-to-viewport-position affinity: keep slot-0 … slot-N in invariant DOM order and bind them to mountedStart + slotIndex. Component instances remain slot-owned and bounded. Surviving records may change physical component slot when the window advances; logical focus and selection must be restored through record metadata.

The Fix

  1. Extend the existing range-movement witness to record all structural VDOM actions and establish the current exact baseline.
  2. Make the steady-state child order invariant: [top spacer, slot-0 … slot-N, bottom spacer].
  3. Bind each physical slot to the corresponding logical offset in the mounted range instead of rotating slot IDs to preserve record affinity.
  4. Keep one component instance per physical slot; scrolling reconfigures those instances without creating or destroying components.
  5. Remap selection, focus, record updates, and scroll anchoring through logical record identity after slot rebinding.
  6. Document the chosen slot-owned identity contract in Neo.list.Buffered JSDoc and protect it with unit witnesses.

Contract Ledger

Target surface Source of authority Proposed behavior Fallback Docs Evidence
assignPoolSlots() src/list/Buffered.mjs Physical slot order is invariant; logical records bind by mounted-range offset None; fail the fixed-order witness Method JSDoc Forward/reverse range tests
Buffered root vdom.cn createItems() Same spacer and slot IDs remain at the same child indexes while pool size is unchanged Resize may intentionally grow/shrink the pool Class + method JSDoc Zero insert/remove/move deltas
Pooled component lifetime getPooledComponent() / trimComponentPool() One stable component instance per physical slot during scrolling Excess slots may be destroyed only on pool shrink Method JSDoc Identity/allocation witness
Logical interaction state record metadata + selection/focus methods Selection, focus, anchoring, and ARIA follow the logical record after rebinding Fail visible; no stale slot fallback Existing contracts updated as needed Interaction + semantic-order tests

Decision Record impact

None. This aligns Neo.list.Buffered with the established Grid pooling principle while retaining list-specific DOM semantics.

Acceptance Criteria

  • A delta witness proves that the mounted range actually advances and records the exact pre-change counts for moveNode, insertNode, and removeNode.
  • With an unchanged pool size, forward and reverse range movement produce zero moveNode, zero insertNode, and zero removeNode.
  • Physical row IDs and their child indexes remain invariant across those movements.
  • The component instance set and cardinality remain invariant during scrolling; no pooled component is created or destroyed.
  • Rendered row data.recordId order matches the mounted Store slice after every movement.
  • aria-posinset, aria-setsize, selection, scroll anchoring, record updates, and focus target the correct logical records across slot rebinding.
  • Pool growth/shrink remains explicitly resize-owned and is not misclassified as a steady-state structural regression.
  • Targeted Neo.list.Buffered tests and the repository unit suite pass.

Out of Scope

  • Variable-height rows.
  • Horizontal/cell pooling.
  • list.plugin.Animate behavior.
  • Fleet/Cockpit consumer changes.
  • Changes to Grid pooling.
  • Any expansion of PR #17557.

Avoided Traps

  • Copying Grid's visual modulo order literally: would risk DOM/accessibility order diverging from Store order.
  • Keeping record affinity by rotating child IDs: preserves the current source of possible DOM moves.
  • Calling zero structural deltas “zero GC”: component lifetime is verified separately; the ticket makes no universal allocation claim.
  • Adding a compatibility mode: one fixed identity contract is easier to reason about and maintain.

Related

Blocked by #17554.

Related: PR #17557, #8992, #9002, #9012.

Origin Session ID: f47f948b-743b-4c11-84a8-fa60a567a148

Retrieval Hint: list.Buffered fixed DOM order slot-to-viewport affinity moveNode Grid pooling

tobiu referenced in commit b52a378 - "feat(list): keep buffered rows in fixed DOM order (#17563) (#17600) on Aug 23, 2026, 1:22 PM
tobiu closed this issue on Aug 23, 2026, 1:22 PM