Skip to content

fix: make "jump to solution" reach the solution reply - #921

Open
rawsun007 wants to merge 1 commit into
AnswerOverflow:mainfrom
rawsun007:fix/jump-to-solution-virtualized-list
Open

fix: make "jump to solution" reach the solution reply#921
rawsun007 wants to merge 1 commit into
AnswerOverflow:mainfrom
rawsun007:fix/jump-to-solution-virtualized-list

Conversation

@rawsun007

Copy link
Copy Markdown

Fixes #892

The bug

Clicking "Jump to solution" does nothing, and opening a page with a #solution-<id> hash does nothing either.

Replies are rendered by SnapshotInfiniteList, which is a react-virtuoso window list on top of cursor pagination. Two things follow from that:

  • the solution reply is usually not in the loaded page set yet, and
  • even after it loads, only the items near the viewport are mounted.

So #solution-<id> points at an element that is not in the DOM, and the browser's native anchor scroll has nothing to scroll to.

The same applies to the two existing code paths that tried to handle this:

  • JumpToSolution only called setShowAllMessages(true), and nothing in the app ever read showAllMessages - it was dead state - so the click relied entirely on the native anchor.
  • the ?focus= effect in message-page.tsx did a single getElementById on mount, before the replies had loaded, and silently gave up when it returned null.
  • ReplyBar's scrollToMessage had the same one-shot getElementById behaviour and fell back to a full page navigation.

The fix

Teach the list to reach an item that is not loaded or not mounted yet, and route every "scroll to this message" caller through it.

  • SnapshotInfiniteList takes an optional getItemId and registerScroller. The scroller looks the item up in what is loaded; if it is not there it keeps requesting the next page and retries as pages arrive, then calls Virtuoso.scrollToIndex so the item actually mounts. onScrolledToItem is retried for a few frames because the row mounts after the scroll.
  • MessageResultPageProvider owns scrollToMessage: it tries the DOM first (the solution card at the top of the page is not virtualized), then hands off to the list. If the click happens before the list has mounted, the target is held and flushed when the list registers, which is what makes a #solution-<id> deep link work on load.
  • JumpToSolution calls scrollToMessage and only prevents the default anchor when the scroll was handled, so nothing regresses where there is no list.
  • ?focus= handling moved into the provider, so it goes through the same path instead of its own one-shot lookup.
  • Removed showAllMessages / setShowAllMessages from the context - nothing read them.

The paging-vs-give-up decision is resolveScrollTarget in packages/ui/src/utils/scroll-target.ts, kept pure so it can be tested directly. messageIdFromHash parses the deep link and ignores anything that is not a snowflake.

Tests

  • packages/ui/src/utils/scroll-target.test.ts covers the hash parsing and the resolver, including the case this bug was: target not loaded and pagination not finished has to load more rather than resolve to nothing.
  • packages/ui/src/components/jump-to-solution.test.tsx renders the provider with a stand-in list and asserts that a click, and a #solution-<id> on load, actually reach the list.

On this branch:

✓ src/utils/scroll-target.test.ts (10 tests)
✓ src/components/jump-to-solution.test.tsx (4 tests)

Test Files  2 passed (2)
     Tests  14 passed (14)

With the component tests kept and the fix reverted, the behaviour they describe is gone:

× JumpToSolution > asks the replies list to scroll to the solution
✓ JumpToSolution > leaves the anchor alone when nothing can scroll to the solution
× MessageResultPageProvider > resolves a #solution- deep link once the list has mounted
× MessageResultPageProvider > ignores hashes that do not point at a message

Tests  3 failed | 1 passed (4)

bun run lint and tsgo --noEmit on @packages/ui and @apps/main-site are clean.

Known limit

The replies list also has crawlable cursor pagination. If the solution is on an earlier cursor page than the one being viewed, the list cannot page backwards to it, so scrollToMessage returns false and the plain anchor navigation happens as before. The common case (/m/<id> with no cursor) walks forward from the start and reaches it.

Replies render inside a virtualized, cursor paginated list, so
#solution-<id> points at an element that is not in the DOM and the
browser has nothing to scroll to. The click handler only flipped
showAllMessages, which nothing reads.

SnapshotInfiniteList now accepts getItemId/registerScroller, pages
forward until the target item is loaded and calls scrollToIndex so it
mounts. MessageResultPageProvider routes JumpToSolution, ReplyBar and
the ?focus= param through that, and holds a #solution- deep link until
the list registers itself.
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

@rawsun007 is attempting to deploy a commit to the Answer Overflow Team on Vercel.

A member of the Team first needs to authorize it.

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.

Bug: Jump to solution link broken on answeroverflow.com

1 participant