Skip to content

feat: penultimateCellIndex support, scroll-on-navigation, keyboard scoping, scroll position methods#49

Draft
christopherwxyz wants to merge 3 commits intovercel:mainfrom
officialunofficial:fix/reset-scroll-on-navigation
Draft

feat: penultimateCellIndex support, scroll-on-navigation, keyboard scoping, scroll position methods#49
christopherwxyz wants to merge 3 commits intovercel:mainfrom
officialunofficial:fix/reset-scroll-on-navigation

Conversation

@christopherwxyz
Copy link

@christopherwxyz christopherwxyz commented Feb 13, 2026

Summary

Rebased onto main (v0.6.0) and adopted the mainScrollViewID-based scroll reset from #48.

penultimateCellIndex support

calculateBlankSize now respects the penultimateCellIndex prop:

  • Negative value (e.g. -1): opts out of blank padding entirely — no gap between the last cell and the footer
  • Non-negative value: uses that index for the penultimate cell lookup instead of always using blankView.index - 1
  • nil (default): preserves existing behavior

Scroll reset on navigation

When the view leaves the screen (didMoveToWindow with window == nil), didScrollToEndInitiallyForId is reset. When returning, handleDidReturnToScreen re-triggers scroll-to-end. This handles Fabric view recycling where mainScrollViewID doesn't change when revisiting the same conversation.

Keyboard notification scoping

Adds ownsKeyboardSession flag so stacked screens (e.g. thread pushed over chat) don't interfere with each other's scroll/inset state.

Scroll position methods

  1. getFirstVisibleCellInfo() — returns the first visible cell's index, pixel offset, and near-end state
  2. scrollToCellOffset(cellIndex, offsetInCell, animated?) — scrolls to a specific cell at a given pixel offset

Changes

  • aix.nitro.ts: Added AixVisibleCellInfo interface and two new methods to AixMethods
  • HybridAix.swift: penultimateCellIndex support in calculateBlankSize, didMoveToWindow lifecycle for scroll reset, getFirstVisibleCellInfo() + scrollToCellOffset(), keyboard scoping
  • HybridAix.kt: Android stubs for new methods
  • nitrogen/generated/: Regenerated via bun run codegen

Test plan

  • Navigate to a DM chat → messages scroll to bottom, no gap above footer
  • Swipe back, navigate to different DM → scroll resets via mainScrollViewID change
  • Navigate away and back to the same conversation → scroll resets via didMoveToWindow
  • Stacked screens (thread over chat) → keyboard only affects the top screen
  • penultimateCellIndex={-1} → blank padding is 0, last message sits above composer

@vercel
Copy link
Contributor

vercel bot commented Feb 13, 2026

@christopherwxyz is attempting to deploy a commit to the Vercel Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Contributor

@vercel vercel bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Suggestion:

Missing resetInitialScroll() override in Android HybridAix.kt causes Kotlin compilation failure.

Fix on Vercel

@christopherwxyz christopherwxyz force-pushed the fix/reset-scroll-on-navigation branch 4 times, most recently from c2cb7e8 to 3cd002e Compare February 13, 2026 18:05
@richardkunkli
Copy link
Contributor

Hey, this PR looks good. I also fixed this issue in my open PR (#48). Our current solution is to reset that state based on the change of mainScrollViewId property.

}
}

func getFirstVisibleCellInfo() throws -> AixVisibleCellInfo? {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this used for?

Copy link
Author

@christopherwxyz christopherwxyz Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getFirstVisibleCellInfo is a hook that saves and restores scroll position when navigating between chats. It:

  1. On navigate away: calls getFirstVisibleCellInfo() to snapshot which message is at the top of the viewport (cell index + pixel offset within that cell + whether near bottom)

  2. On return: calls scrollToCellOffset() to restore to that exact position

Without these methods, switching between conversations would lose your scroll position. It's the counterpart to the mainScrollViewID scroll-reset — reset handles the "scroll to bottom for new conversation" case, while this handles "resume where you left off."

@nandorojo
Copy link
Collaborator

Like @richardkunkli said, we're migrating to an approach that resets scroll state when mainScrollViewId changes. This way, you can do:

<ScrollView nativeId={`chat=${chatId}`}

Would that satisfy what you did with resetInitialScroll @christopherwxyz?

christopherwxyz and others added 2 commits February 18, 2026 12:26
Add ownsKeyboardSession flag and firstResponderIsDescendant() check so
stacked screens (e.g. thread pushed over chat detail) don't interfere
with each other's scroll/inset state when the keyboard opens.

Also extract resetScrollState() helper and remove unused .didShow subscription.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add two new methods to the Aix Nitro spec for scroll position memory:
- getFirstVisibleCellInfo(): returns the first visible cell's index,
  pixel offset, and near-end state
- scrollToCellOffset(): scrolls to a specific cell at a given offset

These enable saving and restoring scroll positions when switching
between conversations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@christopherwxyz christopherwxyz force-pushed the fix/reset-scroll-on-navigation branch from 1355bc4 to ae7a532 Compare February 18, 2026 17:42
@christopherwxyz christopherwxyz changed the title fix: reset scroll-to-end state on Fabric view recycling feat: add getFirstVisibleCellInfo and scrollToCellOffset + keyboard scoping Feb 18, 2026
@christopherwxyz
Copy link
Author

@nandorojo Yes — rebased onto main and adopted the mainScrollViewID approach from #48. Dropped resetInitialScroll() and didMoveToWindow entirely. Our JS side now passes a dynamic nativeId keyed to the conversation ID, so scroll state resets automatically when switching chats.

Repurposed this PR to add two new methods we need for scroll position memory:

  • getFirstVisibleCellInfo() — snapshots the first visible cell (index + pixel offset + near-end state) so we can save scroll position on navigate-away
  • scrollToCellOffset() — restores to a saved position on navigate-back

Also kept the keyboard notification scoping fix (ownsKeyboardSession) which prevents stacked screens from interfering with each other's keyboard state.

Without this reset, switching conversations where the blank cell has
the same size would cause reportBlankViewSizeChange to skip the
initial scroll-to-end (didAlreadyUpdate guard), leaving a gap between
the last message and the input bar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@christopherwxyz christopherwxyz marked this pull request as draft February 18, 2026 18:31
@christopherwxyz christopherwxyz changed the title feat: add getFirstVisibleCellInfo and scrollToCellOffset + keyboard scoping feat: penultimateCellIndex support, scroll-on-navigation, keyboard scoping, scroll position methods Feb 18, 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

Comments