Skip to content

refactor(chat): move ChatPage pre-virtualizer transcript state into a hook - #9079

Merged
bolichen97 merged 1 commit into
mainfrom
refactor/chat-page-transcript-early-controller
Sep 6, 2026
Merged

refactor(chat): move ChatPage pre-virtualizer transcript state into a hook#9079
bolichen97 merged 1 commit into
mainfrom
refactor/chat-page-transcript-early-controller

Conversation

@bolichen97

@bolichen97 bolichen97 commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

Stack: bottom, on main. Merge order after #8978: #9079#9072#9078 (reordered on 2026-09-06 so ChatPage.tsx stays above the 80% per-file coverage floor at every step — see the comment on #9072). #9072 targets this branch and #9078 targets #9072's branch; read every diff relative to its own base.

Problem / Motivation

website/src/pages/ChatPage.tsx is 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-band ResizeObserver, 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) plus activeTip, and returns the handlers and refs the rest of the page reads. That is a custom hook.

ChatPage.tsx lines 3862–4304 move into pages/chat/useChatPageTranscriptController.tsx as useChatPageTranscriptEarlyController. 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-deps cannot 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 — main reverted that feature in #8714, and this slice follows main.

Tests

  • useChatPageTranscriptEarlyController.coverage.test.tsx (new): drives the hook directly with a fake scroller, fold and card geometry and a queued-frame requestAnimationFrame. It locks the machine-prompt (nudge) banner derivation and the rAF throttle on onScrollPin, the live fold + card pinnedJumpChrome reserve, and both pinned-jump modes — the near self-driven glide and the far mounted-row poll through navToDisplayIndex. A full ChatPage mount 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 the composerBandRef callback (the observer body, its disconnect and its follow gate) from the controller and the JSX attribute from the page.
  • Every 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.sh all exit 0.

Manual verification

N/A — the moved effects are covered by the ChatPage.fadeClearance, ChatPage.statusStackAboveMask, ChatPage.queueBandReanchor, ChatPage.navFarJump and chatPins characterizations, 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

  • At most two commits (one is the norm), with a Conventional Commits title (feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)
  • Existing tests pass and new tests added for new functionality
  • Self-review completed; code follows project style guidelines
  • Documentation updated (if applicable)
  • No secrets, credentials, or internal references in the diff

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — ✅ PASS

Design-level review of e1acd70fc2f23e1a9ca5a208233af8da32225abf — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

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

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

UX Review (Fable 5) — ✅ PASS

UX-level review of e1acd70fc2f23e1a9ca5a208233af8da32225abf — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

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

@bolichen97
bolichen97 force-pushed the refactor/chat-page-resources-controller branch from a8dc303 to 4dd6fae Compare September 6, 2026 17:34
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5) — ✅ PASS

Premise-level review of e1acd70fc2f23e1a9ca5a208233af8da32225abf — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

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 ships

Intent: 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).

  1. Scroll/follow/pin logic moves from mid-ChatPage into useChatPageTranscriptEarlyController — justified; derived from the CI review-pass limit, not analogy.
  2. New exported hook returning 17 values — all 17 consumed by the page (grepped each symbol in ChatPage.tsx: scrollBottom ×5, navToDisplayIndex ×9, pinned/setPinned/pinExpanded in 4 effects + JSX, remainder ≥1 each; zero dead returns).
  3. Ten dependency-array entries added for now-crossing-a-hook-boundary refs — declared; refs are stable, no re-fire.
  4. Feature-map Sessions row gains the new file — mandated (check_feature_map.py + AGENTS.md same-commit doc rule).
  5. New coverage test driving the hook directly — derived: scripts/check_per_file_coverage.py runs in ci.yml:1249 with a per-file floor a happy-dom ChatPage mount cannot satisfy for this geometry code.
  6. queueBandReanchor source-contract test now reads both files — forced consequence of the move.

No duplicate mechanism exists: the two prior splits (TranscriptScrollShell.tsx, ChatPageMessageContent.tsx) own the scroller skeleton and renderers, not this state; nothing else in pages/chat/ provides it (grepped useChatPageTranscript*: 1 definition, 1 page consumer, 2 tests). Pinned-prompt "minimize to chip" correctly stays out, following main's #8714 revert. No undeclared items, no riders.

[FIRST-PRINCIPLES-REVIEWED] e1acd70

@bolichen97
bolichen97 force-pushed the refactor/chat-page-transcript-early-controller branch from 07aa7ed to b812dc0 Compare September 6, 2026 17:35
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings

GPT 5.6 completed its review of e1acd70fc2f23e1a9ca5a208233af8da32225abf and found no blocking issues.

This comment is updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] e1acd70

False positive or not applicable? A repository writer can comment:
/ai-review override gpt e1acd70fc2f23e1a9ca5a208233af8da32225abf: <one-sentence reason>

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

Reviewed e1acd70fc2f23e1a9ca5a208233af8da32225abf — this comment is updated in place on each push.

Review details

No findings.

[OPUS-REVIEWED] e1acd70

Verdict parsed from the review's SHA-scoped output markers for commit e1acd70fc2f23e1a9ca5a208233af8da32225abf.

False positive or not applicable? A repository writer can comment:
/ai-review override fable e1acd70fc2f23e1a9ca5a208233af8da32225abf: <one-sentence reason>

@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running labels Sep 6, 2026
@bolichen97
bolichen97 force-pushed the refactor/chat-page-transcript-early-controller branch from b812dc0 to e01d1be Compare September 6, 2026 18:02
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: passed Eligible automated validation passed for the current revision and removed readiness: passed Eligible automated validation passed for the current revision readiness: checking Automated validation is still running labels Sep 6, 2026
iamwhatever
iamwhatever previously approved these changes Sep 6, 2026
@bolichen97

Copy link
Copy Markdown
Collaborator Author

Local gate run on the stack top e01d1beb9 (= #8978 + #9072 + #9078 + #9079 together, i.e. this PR's merge result):

  • Full website vitest suite with coverage: 1887 files, 29707 passed, 1 expected fail, 2 skipped, exit 0.
  • Frontend line-rate 92.63% (floor 90). scripts/check_per_file_coverage.py (floor 80, frontend baseline): exit 0 — src/pages/ChatPage.tsx 80.89%, ChatPageMessageContent.tsx 90.31%, useChatPageSessionController.ts 98.96%, useChatPageResourcesController.tsx 85.34%, useChatPageTranscriptController.tsx 98.79% (de-duplicated line counts, the gate's own measure).
  • Per PR on its own base: tsc -b 0 errors outside src/stories/, eslint 0 warnings, every ChatPage* spec green (65 files / 587 tests), check_feature_map.py with FEATURE_MAP_BASE_REF set to the base branch exit 0.

@bolichen97
bolichen97 force-pushed the refactor/chat-page-resources-controller branch from 4dd6fae to 3c23c1a Compare September 6, 2026 18:38
@bolichen97
bolichen97 force-pushed the refactor/chat-page-transcript-early-controller branch from e01d1be to a8630b0 Compare September 6, 2026 18:38
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: passed Eligible automated validation passed for the current revision and removed readiness: passed Eligible automated validation passed for the current revision readiness: checking Automated validation is still running labels Sep 6, 2026
@bolichen97
bolichen97 force-pushed the refactor/chat-page-resources-controller branch from 3c23c1a to d6f126f Compare September 6, 2026 18:48
@bolichen97
bolichen97 force-pushed the refactor/chat-page-transcript-early-controller branch from a8630b0 to b1ef68a Compare September 6, 2026 18:48
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: passed Eligible automated validation passed for the current revision labels Sep 6, 2026
@bolichen97

Copy link
Copy Markdown
Collaborator Author

Drop pinnedJumpChrome from the hook's return object (useChatPageTranscriptController.tsx:506) and the direct assertion at the test's line 207fixed in b1ef68a12.

the far-jump offset: -198 assertion already pins the same computation via a consumed path.

Agreed on both counts: the page never read pinnedJumpChrome (I had re-added it to the return only so the test could call it), and the far-jump case asserts offset: -198 through scrollToPinnedPrompt, which is the consumed path. The return entry and the standalone test case are removed; the file still measures 98.79% on the same subset (163/165 lines), so the per-file floor is unaffected.

@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running labels Sep 6, 2026
@bolichen97

Copy link
Copy Markdown
Collaborator Author

Claiming the carried Design suggestion from #8978

The stack-bottom slice #8978 merged with one unclaimed Design suggestion, and it
should not be lost between slices, so recording where it lands:

The module is grouped by "doesn't touch the send path," not by theme —
ChatHeaderMenu and mintSendId are not message content; when later #7255
slices land, re-home or rename before the feature-map entry cements this as
the permanent home.

Verified, accepted, and tracked as #9095 (labelled deferred-finding,
assigned, with a due date).

It is not fixed in this stack, deliberately. #9072, #9078 and this PR each
relocate a different region of ChatPage.tsx and none of them opens
ChatPageMessageContent.tsx. Folding a re-home into any of them would cost the
one property that makes these slices reviewable in a single pass — the diff is
main's bytes, moved — and would put an unrelated file into a slice whose whole
claim is that it moves one region.

The suggestion is also already cementing, which is why it gets an issue rather
than a note: docs/feature-map/README.md's Sessions row now glosses the file as
"the header menu, row-key helpers and user-bubble renderers", and
check_feature_map.py enforces that row, so the mismatch is written into the
gate a future reader will read as intent.

#9095 records the two remedies (re-home mintSendId into whichever slice ends up
owning sendTurn's exits and ChatHeaderMenu into its own module; or rename the
module and correct the feature-map gloss) and pins the sequencing: after this
stack merges, before the composer and actions controller slices freeze their own
feature-map rows — the window in which mintSendId can move into its owner
instead of being imported back out of a module that should not hold it.

@bolichen97

Copy link
Copy Markdown
Collaborator Author

Review-ready on b1ef68a12: PR Readiness success, readiness: passed, 0 failing check-runs, and all five review lanes stamped this head with no blocking findings (Design PASS, UX PASS, First Principles PASS after its subtractions were applied, GPT 5.6 and Opus 4.8 clean). The earlier First Principles CONCERNS on the previous head are dispositioned above (all fixed).

Stacked: this PR stays targeted at its parent's branch until the parent merges; then it is retargeted to main and rebased. The approval on the previous commit predates the First Principles fix and will need refreshing then.

… 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>
@bolichen97
bolichen97 changed the base branch from refactor/chat-page-resources-controller to main September 6, 2026 19:30
@bolichen97
bolichen97 dismissed iamwhatever’s stale review September 6, 2026 19:30

The base branch was changed.

@bolichen97
bolichen97 requested a review from a team September 6, 2026 19:30
@bolichen97
bolichen97 force-pushed the refactor/chat-page-transcript-early-controller branch from b1ef68a to e1acd70 Compare September 6, 2026 19:31
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: passed Eligible automated validation passed for the current revision labels Sep 6, 2026
@bolichen97

Copy link
Copy Markdown
Collaborator Author

Now the bottom of the stack (base main, head e1acd70fc): the order was changed so that this slice lands first — see the coverage table on #9072. Content is unchanged from the approved-then-fixed b1ef68a12; only the base moved. The approval on e01d1beb9 predates the First Principles fix and this rebase, so it needs refreshing once CI is green here.

@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running labels Sep 6, 2026
@bolichen97

Copy link
Copy Markdown
Collaborator Author

Review-ready on e1acd70fc (base main): PR Readiness success, readiness: passed, 0 failing check-runs, Coverage Gate success — which confirms the reorder: with this slice first, ChatPage.tsx clears the per-file floor. All five review lanes stamped this head with no blocking findings (Design PASS, UX PASS, First Principles PASS, GPT 5.6 and Opus 4.8 clean); the earlier First Principles subtraction is dispositioned above.

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.

@bolichen97
bolichen97 enabled auto-merge (squash) September 6, 2026 21:58
@bolichen97
bolichen97 merged commit 5e5679b into main Sep 6, 2026
71 of 73 checks passed
@bolichen97
bolichen97 deleted the refactor/chat-page-transcript-early-controller branch September 6, 2026 22:05
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants