Skip to content

Add a "jump to latest" (scroll-to-bottom) button to the chat transcript #46

Description

@DragonnZhang

Problem or Motivation

Every comparable AI desktop chat surface — Claude Code Desktop, ChatGPT desktop, Codex desktop — shows a floating "jump to latest" / scroll-to-bottom affordance when you scroll up through a long conversation, so you can return to the newest message in one click. OpenWork has no such control.

Today OpenWork's transcript (ChatDisplay) already tracks scroll position for sticky-bottom auto-scroll (handleScroll computes distanceFromBottom, un-sticks when you scroll up, re-sticks at the bottom), but there is no visible way to jump back to the latest message: once you scroll up to read history you must manually drag all the way back down, and during active streaming that means fighting the growing content. The state needed to drive the button already exists — only the UI was missing.

Proposed Solution

Render a small floating circular button (a down-chevron) centered near the bottom of the transcript, shown only when the user has scrolled well away from the bottom (200px hysteresis so it doesn't flicker near the end). Clicking it:

  • re-enables sticky-bottom auto-scroll (isStickToBottomRef = true),
  • smooth-scrolls the transcript to the latest message,
  • hides itself again once back at the bottom.

It fades in/out with the existing motion/AnimatePresence primitives already used throughout ChatDisplay, and hides on session switch.

Frontend-only. No backend / qwen-code change. The button reads/derives from existing renderer scroll state (scrollViewportRef, distanceFromBottom, messagesEndRef.scrollIntoView). One new i18n key chat.scrollToBottom (accessible label / tooltip) is added to all six locales.

Feasibility

  • Classification: pure frontend — candidate. The scroll geometry, the sticky-bottom ref, and the scroll anchor all already live in apps/electron/src/renderer/components/app-shell/ChatDisplay.tsx. Adding a button that flips visibility on distanceFromBottom and calls scrollIntoView requires no main-process or backend involvement.
  • Confirmed genuinely missing: the ChevronDown import in ChatDisplay is used only for a details expander, not for scrolling; there is no floating jump control anywhere in the transcript.

Acceptance Criteria (what the CDP assertion checks)

A new e2e CDP assertion (e2e/assertions/scroll-to-bottom.assert.ts) drives the real built app end-to-end:

  1. Seeds a 40-message session on disk (backend-independent — a plain session.jsonl under the isolated profile) and opens it from the sidebar.
  2. The transcript renders and overflows the viewport (proof the real conversation loaded).
  3. On open the transcript is at the bottom and the button is hidden.
  4. Scrolling to the top makes the button appear.
  5. Clicking the button returns the transcript to the bottom (distanceFromBottom < 40) and the button disappears.

This required a small, reusable harness addition: an optional seed(profileDirs) hook (e2e/app.ts + e2e/runner.ts) that lets an assertion pre-seed on-disk session state before the app launches — unblocking this and future transcript-dependent assertions.

Part of the autonomous desktop-feature loop (loop-bot).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions