Skip to content

WIP: try legends on native again#29361

Open
chrisnojima wants to merge 2 commits into
masterfrom
nojima/HOTPOT-legends-310
Open

WIP: try legends on native again#29361
chrisnojima wants to merge 2 commits into
masterfrom
nojima/HOTPOT-legends-310

Conversation

@chrisnojima

Copy link
Copy Markdown
Contributor

No description provided.

@chrisnojima chrisnojima force-pushed the nojima/HOTPOT-legends-310 branch 2 times, most recently from c11edb7 to b7612ce Compare July 9, 2026 19:16
@chrisnojima chrisnojima changed the base branch from master to nojima/HOTPOT-message-fixes July 9, 2026 19:17
…ights

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
Replaces the inverted FlatList + KeyboardChatScrollView native thread with a
non-inverted KeyboardAwareLegendList, on top of the list-agnostic row fixes in
the parent commit.

- non-inverted list: rows render in natural order, so the sent-message
  animation, separators, and header/footer components swap ends. Bottom
  clearance for the input bar is reserved statically via contentContainerStyle,
  with the keyboard composer inset seeded to 0 so the two don't stack.

- pagination: onStartReached/onEndReached replace the viewability-window
  heuristic (useNativeSafeOnViewableItemsChanged is deleted).

- prepend jump: maintainScrollAtEnd's dataChange trigger re-pins on ANY data
  change within maintainScrollAtEndThreshold of the end, so on short threads a
  load-older prepend yanked the view to the bottom. The threshold stays wide
  (0.5) because initialScrollAtEnd positions from estimatedItemSize and lands
  short on our tall rows; instead the re-pin is suspended while a prepend is in
  flight (prependActive).

- initial position: the list is not mounted until the thread is loaded, so its
  first render always has data and initialScrollAtEnd lands at the newest
  message. Previously, arriving from the inbox mounted the list empty and the
  initial scroll ran against no data and never re-fired.

- centering: scrollToItem(viewPosition: 0.5) lands accurately here, so the
  closed-loop offset corrector (viewable-range feedback, damped item-delta
  scrolls) is deleted in favor of re-asserting across the pagination settle.
  onScrollToIndexFailed goes with it — LegendList has no such prop.

- maintainVisibleContentPosition is on from mount so prepends hold position.

- fling cost: experimental_adaptiveRender lets rows shed their swipe pan
  handlers during fast scroll, via useAdaptiveRender in long-pressable driving
  the SwipeableRow `enabled` prop added in the parent commit. useAdaptiveRender
  reads LegendList's state context and throws outside a LegendList, so it can
  only be wired up here. The Swipeable stays mounted — toggling its tree would
  remount children and flash images.

- stable renderItem: NativeRow reads the centered highlight itself, so
  renderItem identity never changes and a highlight change doesn't re-render
  every visible row.

Includes a temporary [LISTDBG] dump for the initial-load settle; remove before
merging.
@chrisnojima chrisnojima force-pushed the nojima/HOTPOT-message-fixes branch from a621b5c to 32a520c Compare July 9, 2026 19:23
@chrisnojima chrisnojima force-pushed the nojima/HOTPOT-legends-310 branch from b7612ce to 5e96b94 Compare July 9, 2026 19:23
Base automatically changed from nojima/HOTPOT-message-fixes to master July 9, 2026 20:34
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