Skip to content

feat: add jump-to-message with highlight animation#95

Open
sanasol wants to merge 2 commits intostoatchat:devfrom
sanasol:feat/jump-to-message
Open

feat: add jump-to-message with highlight animation#95
sanasol wants to merge 2 commits intostoatchat:devfrom
sanasol:feat/jump-to-message

Conversation

@sanasol
Copy link

@sanasol sanasol commented Mar 4, 2026

Summary

Adds the ability to click message links (e.g. # General › 💬 badges in markdown) and jump to the referenced message with a visual highlight.

Routing:

  • SwitchChannelToMessage action in ActionChannel for message-specific navigation
  • ChatRouterScreen routes the action, sets pendingScrollToMessageId state
  • ChannelScreen receives pending scroll ID and triggers scroll + highlight

Link parsing (JBMRenderer):

  • Parse internal channel/message URLs (/server/ID/channel/ID/ID)
  • Resolve pretty display text for links (e.g. show # General instead of full URL)
  • Background color badge styling for internal links

Scroll + highlight:

  • scrollToItem(index + 1) then animateScrollBy(-viewportHeight / 2f) to center the target in reverseLayout
  • If message not in current view, jumpToMessage() loads messages around the target from API
  • Pulse highlight overlay via drawWithContent (3 pulses at 0.55→0.20 alpha, settle at 0.30, 3s hold, 1s fade out)

ViewModel enhancements:

  • isInMiddleOfHistory / reachedLatest / newestLoadedMessageId state for bidirectional pagination
  • jumpToMessage(), returnToLatest(), loadNewerMessages() methods
  • switchChannel() now accepts optional targetMessageId for initial scroll
  • around parameter support in loadMessages() with proper sort order

Fixes #23, related to stoatchat/for-web#891

Tested and live on https://chat.sanhost.net — app at https://chat.sanhost.net/downloads/

When the WS connects (including first connection), pushReconnectEvent()
triggers loadMessages(50, ignoreExisting=true). If the initial load
already fetched the same messages, ignoreExisting filters them all out,
producing an empty list that updateItems() uses to clear the channel.

Skip the update entirely when ignoreExisting yields no new messages.

Signed-off-by: sanasol <[email protected]>
@sanasol
Copy link
Author

sanasol commented Mar 4, 2026

Note: The second commit also fixes a pre-existing bug (not introduced by this PR) where the channel would sometimes appear empty on fresh app start.

Root cause: RealtimeSocket.connect() calls pushReconnectEvent() on every WS connection, including the first one. This triggers loadMessages(50, ignoreExisting=true) in the Reconnected handler. When the initial loadMessages(50) has already loaded the same messages, ignoreExisting filters them all out → updateItems([]) wipes the channel.

Fix: Early return when ignoreExisting yields no new messages, preventing the empty updateItems() call.

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.

feature request: on a reply message, clicking on the quoted message should take you to the original message.

1 participant