fix(chat): drop viewport-height spacer when no anchor (Codex feedback on #32583)#32596
Merged
dvargasfuertes merged 1 commit intoMay 29, 2026
Merged
Conversation
… on #32583) Two pieces of Codex feedback on #32583. One was a legitimate bug, the other a misread of React reconciliation that this PR locks in as a regression test. ## Codex #1 — Legitimate bug, fixed The merged code applies `minHeight: viewportMinHeight` to the latest-edge wrapper whenever `renderAvatar` is present, even when `partition.anchorMessage` is null (assistant-only history: recovered conversations whose user message was lost, onboarding before the first submit). `useViewportMinHeight` reports the scroll container's `clientHeight` ≈ a full viewport. `refactor(web): scroll to bottom on transcript container DOM attach (#32239)` snaps the scroll to bottom on conversation switch. With no anchor at the top of the spacer, the bottom-pinned viewport lands on blank space + the avatar — the actual latest assistant message sits one viewport above and is invisible until the user scrolls up. Fix: gate both `minHeight: viewportMinHeight` and the `flex-1` spacer on `partition.anchorMessage`. Without an anchor, the avatar renders inline directly below the last history item. ## Codex #2 — Misread, locked in as a regression test Codex claimed that inserting `<LatestTurnRow>` at slot 0 (was `false`) on the no-anchor → anchor transition would cause React to reconcile the following `<div>` siblings by index, reusing the current avatar wrapper as the spacer and remounting `ChatAvatar` — replaying the entrance-spring animation #32583 is trying to avoid. Empirically verified that this does not happen. React's `reconcileChildrenArray` tracks `fiber.index` (the previous render's position in the children array, INCLUDING `false`-skipped slots). When slot 0 transitions from `false` to `<LatestTurnRow>`, the existing avatar fiber at `fiber.index=2` still matches `newIdx=2` in the next render — same `<div>` type, fiber reused, `ChatAvatar` is not unmounted. Locked in with a DOM-lifecycle regression test that: 1. Renders the transcript with no anchor + a mount-tracking avatar. 2. Asserts mount=1, unmount=0. 3. Rerenders with the first user message inserted (no-anchor → anchor). 4. Asserts mount=1, unmount=0 (no remount). 5. Rerenders back to no-anchor. 6. Asserts mount=1, unmount=0 again. A sanity-check version of the test (forcing a conversationId change, which re-keys the scroll container) was used to verify the mount tracker actually detects remounts — confirmed mount=2/unmount=1 when remount really happens, so the assertions can fail. ## Tests - 13 transcript.test.tsx tests pass. - 3 new Codex #1 cases: no-anchor has no min-height; anchor has min-height; anchor-without-avatar still has min-height (regression guard for the original viewport-pinning behavior). - 1 new Codex #2 case: the mount-tracker test above. - Typecheck clean (`tsc --noEmit --skipLibCheck` in `apps/web`). - ESLint clean on touched files. - 4 pre-existing `transcript-message-body.test.tsx` failures in the directory-wide run are also present on `main` — caused by mock bleed between test files, not by this PR.
dvargasfuertes
approved these changes
May 29, 2026
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.
Follow-up to #32583 addressing both Codex review comments. One was a legitimate bug; the other was a misread of React reconciliation, locked in as a regression test instead.
Codex P2 #1 — Legitimate bug, fixed
The merged code applied
style={{ minHeight: viewportMinHeight }}to the latest-edge wrapper wheneverrenderAvatarwas present — even withpartition.anchorMessage === null(assistant-only history: recovered conversations whose user message was lost, onboarding before the first submit).useViewportMinHeightreports the scroll container'sclientHeight≈ a full viewport.refactor(web): scroll to bottom on transcript container DOM attach (#32239)snaps the scroll to bottom on conversation switch. With no anchor at the top of the spacer, the bottom-pinned viewport lands on blank space + the avatar — the actual latest assistant message sits one viewport above and is invisible until the user scrolls up.Fix: gate both
minHeight: viewportMinHeightand theflex-1spacer onpartition.anchorMessage. Without an anchor, the avatar renders inline directly below the last history item.Codex P2 #2 — Misread, locked in as a regression test
Empirically verified that this does not happen. React's
reconcileChildrenArraytracksfiber.index(the previous render's position in the children array, includingfalse-skipped slots). When slot 0 transitions fromfalseto<LatestTurnRow>, the existing avatar fiber atfiber.index=2still matchesnewIdx=2in the next render — same<div>type, fiber reused,ChatAvataris not unmounted.Locked in with a DOM-lifecycle regression test that:
mount=1, unmount=0.mount=1, unmount=0(no remount).mount=1, unmount=0again.A sanity check (forcing a
conversationIdchange, which re-keys the scroll container) was used to confirm the mount tracker actually detects remounts when they happen (mount=2, unmount=1). The test framework can fail; the assertions are real.Tests
transcript.test.tsxtests pass.min-height; anchor wrapper hasmin-height; anchor-without-avatar still hasmin-height(regression guard for the original viewport-pinning behavior).tsc --noEmit --skipLibCheckinapps/web).transcript-message-body.test.tsxfailures in the directory-wide run reproduce onmaintoo — caused by mock bleed between test files, not by this PR.Why this is a follow-up rather than amending the original
#32583 was already merged before Codex's review surfaced. Per the SE-skill
pr-lifecycle.mdrule on bot reviewers being real reviewers, every comment gets a response — either an iteration or a rationale. This PR is both: iterates on #1, rationalizes #2 with empirical evidence.