Skip to content

fix(app): restore scroll position when navigating between parent and child sessions - #42809

Closed
aryaminus wants to merge 1 commit into
anomalyco:devfrom
aryaminus:restore-session-scroll
Closed

aryaminus wants to merge 1 commit into
anomalyco:devfrom
aryaminus:restore-session-scroll

Conversation

@aryaminus

Copy link
Copy Markdown

Issue for this PR

Closes #42806

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

The web app lost scroll position when navigating between parent and child sessions. Two primary paths were affected:

  1. "Back to main session" click in the composer area
  2. Breadcrumb parent title click in the timeline header

Both called navigate(parentHref), which mounted the parent session fresh, and autoScroll.resume() scrolled straight to the bottom. No scroll position was ever saved.

How it works:

The timelineCache (a per-session-key cache that already cached virtualizer measurements and tool open/close state) now also saves scrollOffset on cleanup via listRoot()?.scrollTop. When the same session is remounted, three guards prevent the scroll-to-bottom:

  1. timelineCache in the virtualizer — initialOffset uses the cached offset instead of MAX_SAFE_INTEGER. onMount and maybeAnchorBottom skip their scrollToEnd() calls when restoring.

  2. session.tsx session-switch effect — on(() => params.id) skips autoScroll.resume() when the target session has a cached offset.

  3. session.tsx scroll-ref callback — setScrollRef calls autoScroll.pause() when restoring from cache, preventing the auto-scroll resize observer from overriding the restored position. The user can re-engage auto-scroll by manually scrolling to the bottom.

  4. useSessionHashScroll.forceScrollToBottom — returns early when the cache has an offset, so applyHash with no hash does not force-scroll away from the restored position.

How did you verify your code works?

  • bun typecheck from packages/app — passes
  • bun run test from packages/app — 721 pass, 1 fail (pre-existing desktop-native.test.ts Unicode locale test, unchanged)
  • bun typecheck from packages/session-ui — passes
  • bun typecheck from packages/tui — passes
  • bun typecheck from packages/opencode — passes
  • Pre-push hook bun turbo typecheck — 30/30 successful

Screenshots / recordings

No screenshot; this is a navigation scroll behavior fix.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@Enough1122

Copy link
Copy Markdown

AI code review — automated review for reference; please use your judgment.

Review of the scroll-position restoration fix:

  1. packages/app/src/pages/session.tsx (~L1509) / packages/app/src/pages/session/timeline/message-timeline.tsx (~L273) — restoration mode has no exit condition: isRestoring is fixed at setup, and the live checks skip anchoring whenever a cached scrollOffset exists — so after returning to a session, bottom-anchoring stays suppressed for the whole mount, including forceScrollToBottom (~L1989): send a message in a restored session and the view will not follow the new reply — clear the cached offset once it has been consumed (first post-mount observation) or on a user scroll gesture (props.hasScrollGesture()) so normal anchor behavior resumes.
  2. packages/app/src/pages/session/timeline/message-timeline.tsx (~L424) — the cached offset is applied verbatim as initialOffset even though content may have changed while away (new child-session output, reverted messages shrinking the list) — an offset beyond the new extent either clamps oddly or lands far from the same semantic position — validate/clamp the restored offset against current total size (or fall back to anchor-bottom when measurements mismatch beyond a tolerance).
  3. packages/app/src/pages/session.tsx (~L1509, ~L1578, ~L1989) — timelineCache.get(sessionKey())?.scrollOffset !== undefined is spelled out three times plus the timeline-side copy — extract one hasCachedScroll(sessionKey) helper next to timelineCache so the "restoring?" definition lives in exactly one place.
  4. Test coverage — the decision matrix (cold mount anchor-bottom / restore-with-cache / restore-then-send-message) is pure enough to unit test if the helpers from points 1 and 3 are extracted — worth doing before this interaction surface grows more branches.

@github-actions

Copy link
Copy Markdown
Contributor

Automated PR Cleanup

Thank you for contributing to opencode.

Due to the high volume of PRs from users and AI agents, we periodically close older PRs using automated criteria so maintainers can focus review time on the most active and community-supported contributions.

This PR was closed because it matched the following cleanup criteria:

  • The PR was created more than 1 month ago
  • The PR had fewer than 2 positive reactions
  • Positive reactions are counted as thumbs-up, heart, celebration, or rocket reactions on the PR

PRs created within the last month are not affected by this cleanup.

If you believe this PR was closed incorrectly, or if you are still actively working on it, please leave a comment explaining why it should be reopened. A maintainer can review and reopen it if appropriate.

Thanks again for taking the time to contribute.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: scroll position lost when navigating between parent and child sessions in web app

2 participants