feat(framework): VirtualList — the im thread pattern as a component#165
Draft
doodlewind wants to merge 4 commits into
Draft
feat(framework): VirtualList — the im thread pattern as a component#165doodlewind wants to merge 4 commits into
doodlewind wants to merge 4 commits into
Conversation
framework/src/virtual-list.ts (@pocketjs/framework/virtual-list): the apps/im two-node contract (untransformed overflow-hidden viewport + full- height translateY canvas) with O(1) fixed-row windowing, a reference-stable visible memo (idle frames skip the <For> diff), and input layered by what the host delivers rather than by target: - touch: pan/fling via gesture layer + kinetic scroller; down press- highlights the row (native active: variant), a pan claims the contact and cancels the highlight, a tap routes through pressNode() so touch taps, d-pad CIRCLE, and cursor clicks land in the same onPress. A down arrests an in-flight fling. On PSP the recognizer never sees contacts — the same component degrades to pure d-pad with zero dead code paths. - d-pad: focusRows (default) drives a focused index through a focus controller with keep-in-view chase scrolling; rows unmounting off-window never strand focus (controller holds the authoritative index, re-asserts on mount, and a per-frame sync adopts focus that entered via linear traversal or removal repair). focusRows:false binds the im d-pad pump. - cursor: rows are focusable — hover-is-focus and click work unchanged. Invariants from im kept explicit: prepend rebase (rebase/rebaseRows shift offset + in-flight anchors so backfill never moves the viewport), stick-to- bottom judged on the scroller INTENT against the PREVIOUS max (kinetics gains intent()), and near-edge fetch callbacks with app-side guards. No JSX and no class strings on purpose: direct component calls with live getters (testable under plain bun test) and style-object geometry, so nothing depends on the pass-1 class harvest. 13 renderer-level tests over the real frame pump: window math, reference stability, d-pad walk + chase landing (offset exactly 20), CIRCLE/tap/pan/ fling/arrest journeys via packed touch frames, rebase, stickToBottom, and near-end triggers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The fresh-results entry point apps need: after a search replaces the list, focusRow(0) scrolls the row into view and focuses it through the same path the d-pad walk takes (pending-focus latch included), so the next CIRCLE press lands on a row instead of nothing. Clamped; no-op with focusRows off. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
focusRow/scrollToIndex/rebaseRows run in CALLER reactive scopes; focusRow's props.count read silently subscribed the caller's effect to the list length, so an effect doing "on fresh search, focusRow(0)" re-fired on every append and yanked focus back to the top (caught by the pocket-youtube LOAD MORE journey). Handle methods now untrack all props reads; regression test pins that a focusRow-in-effect gains no count subscription. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A plain View is a flex ROW; an unsized viewport inside one lays out at width 0 and the entire list silently paints nothing. The mock-host unit tests could never catch it (no layout); a real-core repro did. The viewport now defaults width to SIZE_FULL (overridable via props.style). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 4/6 of the touch-input series (stacked on #164; uses the gesture layer from #161 and the scroller from #163).
What
@pocketjs/framework/virtual-list— generalizesapps/im/thread.tsx:overflow-hiddenviewport (scissor from its own world box) + full-content-height canvas bound totranslateY: -offset()(paint-only). O(1) windowing for fixed row heights, reference-stablevisiblememo so idle frames skip the<For>diff. A thousand-row list costs the core a dozen nodes — less memory than the current materialize-everything lists.setActiveNode; pan claims +beginDrag/drag/endDrag→ fling; tap =pressNode()→ sameonPressas CIRCLE/cursor; down arrests a fling; cancel (modal open) ends tracking without a fling.focusRows(default) = focus-controller-driven index + keep-in-view chase; off-window unmounts never strand focus (authoritative index + pending-focus re-assert + per-frame adoption of linear-traversal entries).focusRows: false= the exact im d-pad pump.rebase/rebaseRows(backfill never moves the viewport),stickToBottomjudged on the scroller intent against the previous max (Scroller.intent()added to kinetics), near-start/end fetch callbacks (app guards, the im convention).Tests
13 renderer-level cases through the public
render()+ real frame pump with packed-touch frames: windowing/stability/clamping, d-pad walk with exact chase landing, CIRCLE + tap + pan/fling/arrest journeys, rebase, stickToBottom follow/hold, near-end. Full regression: renderer/cursor/kinetics/im-sim green,tsc --noEmitclean.🤖 Generated with Claude Code