refactor(chat): move ChatPage pre-virtualizer transcript state into a hook - #9079
Conversation
Design Review (Fable 5) — ✅ PASSDesign-level review of All hunks check out: the move is byte-faithful with dependency-array-only edits, the feature-map doc was updated in the same commit, and the test changes match the stated scope. This is a sound, well-constrained mechanical extraction. Design-Verdict: PASS A faithful ordering-preserving extraction with a verifiable "main's bytes" claim, honest test motivation, and docs updated in-commit — no design-level concerns. Suggestions
[DESIGN-REVIEWED] e1acd70 |
UX Review (Fable 5) — ✅ PASSUX-level review of The unmatched lines are exactly the boilerplate a hook extraction requires: imports with adjusted relative paths, the hook's interface/signature/return, and dependency-array edits adding refs that are stable across the hook boundary. No user-facing string, JSX structure, or rendered element changes anywhere in the diff — the remaining files are a doc index line and tests. This is a pure refactor with no user-visible surface, so the absent blind read and screenshots are not evidence gaps. UX-Verdict: PASS Pure hook extraction — no string, JSX, or rendered-pixel change; nothing for a first-time user to see differently. [UX-REVIEWED] e1acd70 |
a8dc303 to
4dd6fae
Compare
First Principles Review (Fable 5) — ✅ PASSPremise-level review of All checks ran clean. Composing the review. First-Principles-Verdict: PASS A 10k-line page sheds 440 lines into a named hook under a measured CI constraint; every moved surface has counted consumers and every rider is gate-mandated. What this change shipsIntent: make ChatPage small enough for CI's review lane to finish one pass, by moving pre-virtualizer transcript state into a named hook — a FIX for a measured structural cost (recorded plan: the #7255 split, slice 4 of 4).
No duplicate mechanism exists: the two prior splits ( [FIRST-PRINCIPLES-REVIEWED] e1acd70 |
07aa7ed to
b812dc0
Compare
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
Opus 4.8 Review — ✅ no blocking findingsReviewed Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
b812dc0 to
e01d1be
Compare
|
Local gate run on the stack top
|
4dd6fae to
3c23c1a
Compare
e01d1be to
a8630b0
Compare
3c23c1a to
d6f126f
Compare
a8630b0 to
b1ef68a
Compare
|
Drop
Agreed on both counts: the page never read |
Claiming the carried Design suggestion from #8978The stack-bottom slice #8978 merged with one unclaimed Design suggestion, and it
Verified, accepted, and tracked as #9095 (labelled It is not fixed in this stack, deliberately. #9072, #9078 and this PR each The suggestion is also already cementing, which is why it gets an issue rather #9095 records the two remedies (re-home |
|
Review-ready on Stacked: this PR stays targeted at its parent's branch until the parent merges; then it is retargeted to |
… hook The transcript state ChatPage.tsx creates before the virtualizer exists -- scrollBottom, the auto-follow gate, the composer-band ResizeObserver and its survey-layout tick, the nav-scroll poll, displayItemsRef, and the pinned-prompt banner (pinned / pinExpanded, updatePinnedPrompt, onScrollPin, scrollToPinnedPrompt) -- moves into pages/chat/useChatPageTranscriptController.tsx as useChatPageTranscriptEarlyController. The page calls it once where that code used to sit and destructures the seventeen values it still reads. The moved text is main's bytes. The only edit is to the dependency arrays, which now name the refs the hook receives as arguments (referentially stable, so nothing re-fires); five page-side arrays add the returned refs and setters for the same reason. ChatPage.queueBandReanchor.test.tsx's source contract now reads the composerBandRef callback from the controller and the JSX attribute from the page. Fourth slice of the ChatPage split proposed in #7255; the post- virtualizer transcript hook follows in its own slice. Co-authored-by: Kiro Crew <noreply@kiro.dev>
The base branch was changed.
b1ef68a to
e1acd70
Compare
|
Now the bottom of the stack (base |
|
Review-ready on This is now the first PR in the stack to merge. The previous approval was dismissed by the rebase, so it needs a fresh one from a maintainer other than the last pusher. |
Problem / Motivation
website/src/pages/ChatPage.tsxis a 10k-line page. About 440 lines of it set up transcript state that has to exist before the virtualizer is created: scroll-to-bottom, the auto-follow gate, the composer-bandResizeObserver, nav scrolling,displayItemsRef, and the whole pinned-prompt banner. They sit in one contiguous run in the middle of the component with no name.Why it matters
The whole-page split in #7255 grew past the point where CI's Opus review lane can finish one pass and collided with
main's chat-core transport work. It lands as slices that each fit one review pass. This slice owns the pre-virtualizer transcript state. It does not touch the send path.What changed (motivation → approach → change)
The moved code takes a few refs the virtualizer fills later (
scrollerRef,scrollToDisplayIndex,mountIndexRef,vGetFollowRef,vScrollToBottomRef,slotRunningRef) plusactiveTip, and returns the handlers and refs the rest of the page reads. That is a custom hook.ChatPage.tsxlines 3862–4304 move intopages/chat/useChatPageTranscriptController.tsxasuseChatPageTranscriptEarlyController. The page calls it at the spot the first moved line used to be and destructures the seventeen values it still reads, so effect registration order and ref creation order are unchanged.The moved text is
main's bytes. The only edits are to dependency arrays: five in the hook name the refs it now receives as arguments, and five in the page add the returned refs and setters.react-hooks/exhaustive-depscannot see those as stable once they cross a hook boundary; they are, so no callback is recreated and no effect re-fires.The hook deliberately does not carry the pinned-prompt "minimize to chip" state that #7255's version of this controller had —
mainreverted that feature in #8714, and this slice followsmain.Tests
useChatPageTranscriptEarlyController.coverage.test.tsx(new): drives the hook directly with a fake scroller, fold and card geometry and a queued-framerequestAnimationFrame. It locks the machine-prompt (nudge) banner derivation and the rAF throttle ononScrollPin, the live fold + cardpinnedJumpChromereserve, and both pinned-jump modes — the near self-driven glide and the far mounted-row poll throughnavToDisplayIndex. A fullChatPagemount under happy-dom (no layout) cannot reach any of this, which is why the file measured 57% on the whole suite before this test and 98.8% with it — the per-file floor is 80% for a new file.ChatPage.queueBandReanchor.test.tsx: the source contract now reads thecomposerBandRefcallback (the observer body, its disconnect and its follow gate) from the controller and the JSX attribute from the page.ChatPage*spec plus the pinned-prompt, pins, search-scroll, older-history and pagination specs pass unchanged: 82 files / 954 tests targeted. Full suite result in the first comment.tsc -b,eslint(0 warnings),jscpd(0 clones),lint:phantom-classes,check_feature_map.py,check_brand_name.py,docs-lint.shall exit 0.Manual verification
N/A — the moved effects are covered by the
ChatPage.fadeClearance,ChatPage.statusStackAboveMask,ChatPage.queueBandReanchor,ChatPage.navFarJumpandchatPinscharacterizations, which pass unchanged.Screenshots / video
Why no screenshot: hook extraction; nothing renders differently.
Related Issues
Part of the split of #7255. no linked issue: the parent PR has no tracking issue.
Checklist
feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)