Skip to content

feat(session-ui): keep the code block copy button in view while scrolling - #59159

Open
opencode-agent[bot] wants to merge 2 commits into
devfrom
sticky-code-copy
Open

opencode-agent[bot] wants to merge 2 commits into
devfrom
sticky-code-copy

Conversation

@opencode-agent

Copy link
Copy Markdown
Contributor

Closes #53519

Long code blocks in assistant messages now keep their copy button in view while you scroll through them, so you can copy without scrolling back to the top. There's no new header strip; the button looks and sits exactly where it did before, it just stays pinned to the top-right of the visible part of the block.

How it works

  • The copy button's host now spans the whole code block (inset: 0) with pointer-events: none, so selecting, scrolling and clicking the code underneath still work.
  • The button itself sticks inside that host, so it follows you down a tall block and stops at the block's bottom edge instead of hanging over the text that follows. DOM order and the block's margins are unchanged.
  • It sticks below --sticky-accordion-top, so in the timeline it stays under the session header, the same offset the sticky tool group headers use.
  • Hover/focus visibility and the always-visible touch behavior are unchanged.

Testing

  • New component test in markdown.spec.ts: a 120-line block in a scroller with a 32px sticky header. The button stays 4px under the header partway down, is still clickable and copies the full block, the code under its host still receives the pointer, and at the end of the block it stops 4px above the block's bottom. It fails without the CSS change.
  • markdown.spec.ts, session-timeline-mobile.spec.ts, basic-tool.spec.ts and file-tool.spec.ts pass. The new test passed 10/10 on repeat.
  • bun typecheck (session-ui) and bun run lint:changed are clean.

Visual

<svg xmlns="http://www.w3.org/2000/svg" width="240" height="72" viewBox="0 0 240 72" fill="none" stroke="currentColor"><style>.l{animation:s 4s linear infinite}@keyframes s{to{transform:translateY(-40px)}}@media (prefers-reduced-motion:reduce){.l{animation:none}}</style><defs><clipPath id="c"><rect x="40" y="8" width="160" height="56" rx="6"/></clipPath></defs><rect x="40" y="8" width="160" height="56" rx="6" opacity=".35"/><g clip-path="url(#c)" stroke-width="3" stroke-linecap="round" opacity=".5"><g class="l"><path d="M52 18h60M52 28h84M52 38h48M52 48h72M52 58h56M52 68h80M52 78h44M52 88h68M52 98h60M52 108h84"/></g></g><g stroke-width="1.5"><rect x="179" y="16" width="9" height="10" rx="2"/><path d="M183 13.5a1.5 1.5 0 0 1 1.5-1.5h6a1.5 1.5 0 0 1 1.5 1.5v7a1.5 1.5 0 0 1-1.5 1.5"/></g></svg>

Requested by: @Brendonovich (Brendan via Slack)

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change works: in a scroller with a 48px sticky header, a tall code block's copy button now stays pinned 4px under the header while you scroll, where on the base it scrolls out of view (screenshots below). The new component test fails without the CSS change. The markdown, touch timeline and sticky tool group component tests pass, and tsc -b is clean.

Public API is unchanged.

One thing worth fixing: in nested scroll panels, such as the 240px-tall tool output panels, the button uses the timeline's header offset, so it ends up far below the top of the panel (see the inline comment).

Before

After


[data-slot="markdown-copy-button-pin"] {
position: sticky;
top: calc(var(--sticky-accordion-top, 0px) + 4px);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

position: sticky attaches to the nearest scroll container, but --sticky-accordion-top is inherited from the timeline. Some tool outputs (list/glob/grep) render <Markdown> inside [data-component="tool-output"][data-scrollable] (max-height: 240px; overflow-y: auto). A code block there pins at the session header offset inside that small box. I reproduced the button sitting ~52px down a 240px panel, and inside an open tool group it would be ~88px. Resetting --sticky-accordion-top: 0px on that scrollable output (like session-review.css already does) should keep it at 4px there.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only new commit merges dev into the branch, and the PR's own changes are the same as before. The earlier point about the button sitting too far down inside nested scroll panels (like the tool output boxes) is still open. With the merge, the markdown and touch timeline component tests pass (42 tests) and tsc -b is clean. The new copy handler for text selections from dev doesn't conflict with the pinned button.

Public API is unchanged.

@imsoul11

imsoul11 commented Oct 28, 2026 •

Copy link
Copy Markdown

Linking the alternative I opened before the bot pointed me here: #59814.

Difference in one line: this PR keeps the existing floating button and makes it stick; #59814 makes the header area a 30px sticky strip with the language name on the left and an always-visible copy button on the right.

Why I went with the header strip: #53519 asks for the ChatGPT behaviour, and in ChatGPT the language label and the copy button live in a bar pinned above the code, always visible — no hover needed, which also avoids the hover flicker where the button disappears when the pointer leaves it for the tooltip.

Your approach is less invasive (no layout change to the code block, nothing shifts) and it already has a component test, which mine does not.

I am fine with either landing. If maintainers prefer the minimal pin, I will close #59814. If the label + always-visible strip is wanted, the CSS is about 20 lines and could be folded into this PR instead of being a second PR.

This branch has not been deployed

No deployments
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.

[FEATURE]: Sticky code block header (language + copy button) while scrolling long code

2 participants