Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(chat): desktop thread search jumps to the right message#29399

Merged
chrisnojima merged 1 commit into
masterfrom
nojima/HOTPOT-search-broken
Jul 9, 2026
Merged

fix(chat): desktop thread search jumps to the right message#29399
chrisnojima merged 1 commit into
masterfrom
nojima/HOTPOT-search-broken

Conversation

@chrisnojima

Copy link
Copy Markdown
Contributor

Clicking a thread-search hit (or a reply-quote / pinned-message jump) left the desktop thread blank, then landed in the wrong place once unblanked, then blinked the yellow highlight. Three separate causes:

Blank thread: centered loads call messagesClear() before refetching, so LegendList's data goes non-empty -> empty -> non-empty. That transition trips shouldResetFreshDataLayout, which resets its layout state and then waits for a container layout event that never arrives, leaving readyToRender false forever. Bump a clearVersion on every messagesClear and key the list off it, so a cleared thread remounts and takes the fresh-mount path instead of the broken reset path.

Wrong position: the scroll-to-center was a one-shot scrollIntoView, which writes scrollTop behind LegendList's back and desyncs its internal scroll state; its next position recompute then snapped somewhere unrelated, worst with tall image rows. Correct through its own scrollToOffset instead, in a closed loop that measures the row's real offset from the viewport center until it holds still. Also stop disabling maintainVisibleContentPosition while centered: the full thread response lands after the cached one and re-measures rows above the target, sliding it out of view unless the content is anchored.

Highlight blink: LegendList's useDOMOrder re-sorts container elements 500ms after positions settle so DOM order matches visual order. A DOM move restarts CSS animations on the moved subtree, replaying the highlight fade from transparent long after it finished. Freeze the row into the animation's end state once it has played, so there is nothing left to restart.

Clicking a thread-search hit (or a reply-quote / pinned-message jump) left
the desktop thread blank, then landed in the wrong place once unblanked,
then blinked the yellow highlight. Three separate causes:

Blank thread: centered loads call messagesClear() before refetching, so
LegendList's data goes non-empty -> empty -> non-empty. That transition trips
shouldResetFreshDataLayout, which resets its layout state and then waits for a
container layout event that never arrives, leaving readyToRender false forever.
Bump a clearVersion on every messagesClear and key the list off it, so a cleared
thread remounts and takes the fresh-mount path instead of the broken reset path.

Wrong position: the scroll-to-center was a one-shot scrollIntoView, which writes
scrollTop behind LegendList's back and desyncs its internal scroll state; its
next position recompute then snapped somewhere unrelated, worst with tall image
rows. Correct through its own scrollToOffset instead, in a closed loop that
measures the row's real offset from the viewport center until it holds still.
Also stop disabling maintainVisibleContentPosition while centered: the full
thread response lands after the cached one and re-measures rows above the
target, sliding it out of view unless the content is anchored.

Highlight blink: LegendList's useDOMOrder re-sorts container elements 500ms
after positions settle so DOM order matches visual order. A DOM move restarts
CSS animations on the moved subtree, replaying the highlight fade from
transparent long after it finished. Freeze the row into the animation's end
state once it has played, so there is nothing left to restart.
@chrisnojima chrisnojima merged commit 7be3980 into master Jul 9, 2026
1 check was pending
@chrisnojima chrisnojima deleted the nojima/HOTPOT-search-broken branch July 9, 2026 15:53
chrisnojima added a commit that referenced this pull request Jul 9, 2026
dataKey is not a substitute for the key={listKey} remount that #29399 added.
A dataKey change sets shouldResetFreshDataLayout, which calls
resetInitialRenderState({resetLayout: true}): readyToRender drops to false and
the list then waits on a container layout event that never arrives, so the
thread renders blank forever. A remount escapes that because the reset is
guarded on !isFirst.

Desktop cannot use dataKey here at all, not just on clear: the layoutReady rAF
gate deliberately feeds an empty data array on every listKey change, and
previousDataLength === 0 is the other trigger for the same reset.

Repro: search a thread and jump to a hit (a centered load clears the thread
before refetching, bumping clearVersion). Fixed and confirmed on desktop.
chrisnojima added a commit that referenced this pull request Jul 9, 2026
…ights (#29401)

* fix(chat): stop recycled message rows from leaking state and stale heights

These are list-agnostic row fixes, split out of the native LegendList work.
Desktop already renders the thread with a recycling LegendList, so each of
these is a live bug there today; on the native FlatList they are inert
guards.

- per-row state (sent animation, reaction picker, exploding retained height,
  ash tower) is now keyed to messageKey. A recycled container reuses the
  component instance for a different message, so mount-captured state leaked
  across rows: the picker stayed open on the wrong row, the sent-animation
  wrapper stuck on, and a measured retain height was applied to the wrong
  message (and could not self-correct, since retainHeight forces the style
  height so onLayout only ever reports the forced value back)

- recycle-pool suffixes are limited to ones that are stable for the message's
  lifetime (:failed, :reply). The pool label is recorded when a container is
  allocated and never updated in place, so :pending (flips on every send
  confirmation) and :reactions (toggles) left stale labels behind and recycled
  containers painted at the wrong pooled height

- getItemType splits headered rows into their own pool (:hdr). A message that
  leads its author group is ~40px taller than a grouped follow-on of the same
  render type, so a shared pool paints recycled views at the wrong height for
  a frame. It reads the same sticky username cache the rows render with, else
  a row that keeps its header after a scroll-back load is typed headerless and
  poisons the headerless pool's height average

- useSyncRowLayout: when a row's content settles to a new height after first
  paint (flip result streams in, reactions appear, an unfurl loads), flush the
  row measure synchronously so the list's bottom re-pin uses the final height
  on the same frame instead of a frame late, which otherwise parks the thread
  above the newest message

- SwipeableRow takes an `enabled` prop that conditionally spreads its pan
  handlers, so a list can shed per-row touch evaluation during a fast fling
  without unmounting the row. Toggling the Swipeable's subtree instead would
  remount its children and flash images. No caller passes it yet

- desktop LegendList: dataKey replaces the key remount on conversation switch,
  and maintainScrollAtEnd opts back into footerLayout, which 3.x stopped
  implying once the trigger set is given explicitly

* fix(chat): keep the desktop LegendList remount on clear

dataKey is not a substitute for the key={listKey} remount that #29399 added.
A dataKey change sets shouldResetFreshDataLayout, which calls
resetInitialRenderState({resetLayout: true}): readyToRender drops to false and
the list then waits on a container layout event that never arrives, so the
thread renders blank forever. A remount escapes that because the reset is
guarded on !isFirst.

Desktop cannot use dataKey here at all, not just on clear: the layoutReady rAF
gate deliberately feeds an empty data array on every listKey change, and
previousDataLength === 0 is the other trigger for the same reset.

Repro: search a thread and jump to a hit (a centered load clears the thread
before refetching, bumping clearVersion). Fixed and confirmed on desktop.
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.

1 participant