Skip to content

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

Open
DragonnZhang wants to merge 2 commits into
mainfrom
loop/scroll-to-bottom
Open

Add a "jump to latest" (scroll-to-bottom) button to the chat transcript#47
DragonnZhang wants to merge 2 commits into
mainfrom
loop/scroll-to-bottom

Conversation

@DragonnZhang

Copy link
Copy Markdown
Collaborator

Closes #46

What & why

Every comparable AI desktop chat surface — Claude Code Desktop, ChatGPT desktop, Codex desktop — shows a floating "jump to latest" control when you scroll up through a long conversation. OpenWork had none: once you scrolled up to read history you had to drag all the way back down, and during streaming that meant fighting the growing content.

The transcript already tracked everything needed for it — ChatDisplay's handleScroll computes distanceFromBottom, un-sticks auto-scroll when you scroll up and re-sticks at the bottom (isStickToBottomRef), and messagesEndRef is the scroll anchor. Only the UI trigger was missing.

This adds a floating circular down-chevron button centered near the bottom of the transcript that:

  • appears only when the user is well away from the bottom (200px hysteresis so it doesn't flicker near the end),
  • on click, re-enables sticky-bottom auto-scroll (isStickToBottomRef = true) and smooth-scrolls to the latest message,
  • hides itself once back at the bottom and on session switch,
  • fades in/out with the motion / AnimatePresence primitives already used throughout ChatDisplay.

Frontend-only. No backend / qwen-code change. One new i18n key chat.scrollToBottom (accessible label + tooltip) added to all six locales.

Changes

  • components/app-shell/ChatDisplay.tsxshowScrollToBottom state toggled in the existing handleScroll (distanceFromBottom > 200); a scrollToBottom callback; reset on session switch; the floating AnimatePresence button; a data-testid="chat-transcript" hook on the transcript ScrollArea.
  • packages/shared/src/i18n/locales/*.json — new chat.scrollToBottom key in all 6 locales, inserted in sorted position.
  • e2e/app.ts + e2e/runner.ts — a reusable optional seed(profileDirs) hook on the Assertion/LaunchOptions contract, run after the isolated profile is created and before Electron launches, so assertions can pre-seed on-disk session state (backend-independent). This unblocks this assertion and any future transcript-dependent one.
  • e2e/assertions/scroll-to-bottom.assert.ts — new CDP assertion (below).

Verification (DoD)

  • bun run typecheck:all — introduces no new errors. Delta vs main is zero (11 pre-existing errors only: auto-update.ts, a settings-default-thinking test tuple, two test files importing vitest). None in the files this PR touches.
  • bun test — the failing set is identical to main: 56 pre-existing failures (BrowserCDP, BrowserPaneManager, RPC handler profiles, RoutedClient, i18n sorted checks, resource-bundle, startWebuiHttpServer, …), same names on both branches (verified by diffing the sorted failure lists — only per-test timing differs). This change adds zero new failures.
  • bun run lint:i18n:parity — OK (6 locales, 1543 keys each). The new key is added to every locale; the pre-existing i18n keys are sorted alphabetically failures are unchanged (their first out-of-order key is menu.homepage, well after the chat.* section this PR touches).
  • Renderer build (bun run electron:build:renderer, where this change lives) — ✅ builds cleanly.
  • CDP assertion transpiles (bun build e2e/...) — ✅, along with the harness changes.

CDP e2e assertion (scroll-to-bottom.assert.ts)

Drives the real built app over CDP end-to-end:

  1. Seeds a 40-message session on disk (a plain session.jsonl under the isolated profile's default-workspace root — no backend) via the new harness seed hook, then opens it from the sidebar (the row selects on mousedown, dispatched accordingly).
  2. Asserts the transcript renders and overflows the viewport (the real conversation loaded, not the empty/draft state).
  3. Asserts that on open the transcript is at the bottom and the button is hidden.
  4. Scrolls to the top and asserts the button appears.
  5. Clicks it and asserts the transcript returns to the bottom (distanceFromBottom < 40) and the button disappears — proving the button actually scrolls, not merely renders.

⚠️ Local CDP run was blocked by this sandbox's egress policy, not by the feature. The Electron binary download (github release host) returns 403 from the egress proxy, so the app can't be built/launched here (xvfb is present; only the binary is missing). The assertion and harness changes transpile and are included so CI / a reviewer can run bun run e2e in an environment with normal network access. Everything that doesn't require launching Electron (typecheck, unit tests, i18n parity, renderer build) passes with zero delta vs main.

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


Generated by Claude Code

Show a floating down-chevron button near the bottom of the transcript when
the user has scrolled well away from the latest message (200px hysteresis).
Clicking it re-enables sticky-bottom auto-scroll and smooth-scrolls back to
the newest message; it hides itself at the bottom and on session switch.

Reuses ChatDisplay's existing scroll state (distanceFromBottom,
isStickToBottomRef, messagesEndRef) and motion/AnimatePresence primitives.
Frontend-only; one new i18n key chat.scrollToBottom across all six locales.

Adds a reusable seed(profileDirs) hook to the e2e harness so assertions can
pre-seed an on-disk session before launch (backend-independent), and a CDP
assertion that seeds a 40-message session, opens it, scrolls up, asserts the
button appears, clicks it, and asserts the transcript returns to the bottom
and the button hides.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants