Repository navigation
feat(conversation): add useHistoryWindow and ConversationEarlier - #122
pratikbuilds wants to merge 2 commits into
Conversation
|
Triage: Pass No changes needed from you. A maintainer will review this pull request. Stacked on #121: only this pull request's own changes were checked. Notes:
Triage will label. |
628cf5e to
a6ac8b7
Compare
a6ac8b7 to
a613233
Compare
TheGreatAxios
left a comment
There was a problem hiding this comment.
Automated multi-agent review: independent review + second-agent validation; only confirmed items listed. The hook itself is well-built and the windowing-ceiling doc is real; one verified race below.
Verified (executed harness running the real hook + real engine)
- The render-phase trim reads the rAF-throttled context
atBottom: an append committing between the reader's scroll-up and the engine's per-frame pass removes the first shown item while the reader is scrolled up — violating the plan's hard constraint "never removes items while the reader is scrolled up" (src/hooks/use-history-window.ts:156-162vssrc/ui/conversation.tsx:389-399; a deterministic ordering harness reproduces it, and a frame-first control run proves nothing is removed when ordering is safe). Fix shape: gate the trim on a fresh position signal — trim in thesubscribeScrolllistener whensnapshot.distanceFromEnd <= threshold, or read the last snapshot's distance from a ref in the render-phase trim. The suggested regression test currently fails on this code.
Minor
e2e/history-window.spec.ts:3-51duplicatese2e/conversation.spec.ts:3-44's harness helpers line-for-line — extract a sharede2e/helper before the next PR copies it.- A sync throw from
onLoadEarlieris settled then rethrown out ofreachStart(), which runs inside the marker's IntersectionObserver callback → uncaught exception in an observer callback (use-history-window.ts:246-253). Document, or route the rethrow through settled state only. - nits: the hook harness differs from the plan's
renderHookprescription without a stated deviation (add a PR-body note);conversation-earlier.tsx:92-101hand-writes the ref-merge idiom.
a613233 to
91acbf0
Compare
91acbf0 to
255751e
Compare
255751e to
4dfa1be
Compare
|
Thanks for the review. Here's what happened to each point. Fixed1. A new message could remove the oldest one while the reader was scrolled up (the verified race)
2. The two e2e specs had duplicate helpers (minor)
3. A loader that throws crashed inside the observer (minor)
Not changed, and why
Commit: |
TheGreatAxios
left a comment
There was a problem hiding this comment.
Review · Request changes
Adds useHistoryWindow and ConversationEarlier. The window holds its tail while a scroll has not been read yet, except when the trim anchor disappears in the same update that grows the list.
Findings
src/hooks/use-history-window.ts:72— A missingthroughid slides the window during render, before the scroll pass can freeze it. Whenstart.kind === "latest"andstart.throughis gone,startIndexreturnsitems.length - windowSize. The layout effect at:204-211only skips a latersetState; it does not undo that fallback, and the next snapshot freezes the slid start. With 300 items, a window of 120 trimmed throughr299, and a scroll that has moved but not yet been read, replacingr299and appendingr300in one update dropsr180. The pinned-start path already keeps the first surviving id of the previous window (:78-80).throughshould do the same when it is missing, instead of jumping to the new tail.
useHistoryWindow keeps a window of the latest items, adds earlier ones as the reader nears the top, freezes while the reader has scrolled away and trims back once they return to the end. It works with history the host pages in asynchronously, and the item in view stays put within a pixel as earlier items load. ConversationEarlier marks the top with a loading state and a retry when a page brings nothing. The doc comment records why this windows rather than virtualizes, and its ceiling.
An item committed synchronously in the frame the reader scrolled up in, before the conversation's scroll pass, slid or trimmed the window: atBottom and the freeze were still a frame old, so the oldest item was removed while the reader was scrolled up. A new item now commits with nothing removed. A layout effect then trims or slides the window as before, unless the view has moved towards the top since the last scroll snapshot; the next pass then freezes it if the scroll was the reader's. The snapshot subscription moves to a layout effect so the one sent when the container mounts is not missed. A loader that throws now stalls like one that rejects, and its error is reported once instead of escaping the marker's observer callback. The scroll helpers both conversation specs copied now live in one module.
4dfa1be to
202d6df
Compare
useHistoryWindow keeps a window of the latest items, adds earlier ones as the reader nears the top, freezes while the reader has scrolled away and trims back once they return to the end. It works with history the host pages in asynchronously, and the item in view stays put within a pixel as earlier items load. ConversationEarlier marks the top with a loading state and a retry when a page brings nothing. The doc comment records why this windows rather than virtualizes, and its ceiling.
Stacked on #121: review only this commit. Part of #94.
Notes for review
stalledonly applies while the window starts at the first held item (the only place the host is asked); a failed or empty page never blocks revealing items already held above the window.Verification
bun run typecheckandbun run linton its own (the stack is checked commit by commit).bun run check,bun run build,bun run dep-guard,bun run contrast-testandbun run test:e2e(Playwright specs and the Storybook axe sweep in light and dark) pass.Closes #100