Skip to content

Fix/tabs overflow scrolling#253

Open
shiva-manu wants to merge 2 commits into
crynta:mainfrom
shiva-manu:fix/tabs-overflow-scrolling
Open

Fix/tabs overflow scrolling#253
shiva-manu wants to merge 2 commits into
crynta:mainfrom
shiva-manu:fix/tabs-overflow-scrolling

Conversation

@shiva-manu
Copy link
Copy Markdown
Contributor

What

Adds horizontal overflow support for the tab strip when many terminal/editor tabs are open.
Replaces the long native scrollbar with a small draggable scroll thumb and smooth tab
scrolling.

Why

When multiple tabs were opened, the first tabs could overflow out of view and become hard to
access. The previous scrollbar behavior was either hidden or too visually large for the
compact header.

How

The tab bar now uses a hidden native scroll container with wheel/trackpad support and a
custom capped scroll thumb. The tab area no longer uses the Tauri drag region, so dragging
the scroll thumb scrolls tabs instead of moving the window.

Testing

  • pnpm exec tsc --noEmit clean
  • Manual smoke-test of the affected feature
  • (If you touched src-tauri/) cargo check clean
  • (If UI) tested in pnpm tauri dev

Manual smoke-test:
Opened many tabs until the tab strip overflowed, verified horizontal scrolling works, the
active tab scrolls into view smoothly, and the custom thumb remains small and draggable.

Screenshots / GIFs

Before: tab overflow was difficult to access and the visible scrollbar appeared as a long
underline across many tabs.

Screencast.from.2026-05-14.22-26-58.mp4

After: tab overflow is scrollable with a compact draggable thumb around one tab wide.

Screencast.from.2026-05-14.23-35-29.mp4

Notes for reviewer

This is a focused UI change in the React tab/header area. No src-tauri/ files were
touched.
#251

Copilot AI review requested due to automatic review settings May 14, 2026 18:08
@shiva-manu shiva-manu requested a review from crynta as a code owner May 14, 2026 18:08
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR enhances the UI tab bar scrolling experience and adds support for pasting clipboard images into the terminal by saving them to a temporary file via a new Tauri backend command.

Changes:

  • Add a custom (hidden native) tab scroller style and a draggable scrollbar thumb for the TabBar.
  • Add terminal “paste image” handling that writes clipboard images to disk and inserts the path into the terminal.
  • Introduce a new Tauri command (fs_write_clipboard_image) to persist clipboard images in a temp directory.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/styles/globals.css Adds .terax-tab-scroll class to hide native scrollbars and tweak scrolling behavior.
src/modules/tabs/TabBar.tsx Implements custom scroll thumb + updated wheel/scrollIntoView behavior for tab strip.
src/modules/header/Header.tsx Removes data-tauri-drag-region on header containers (affects window dragging).
src/modules/terminal/lib/useTerminalSession.ts Binds a paste listener to detect/save clipboard images and write their path into the pty.
src/modules/terminal/lib/clipboard-image.ts Adds helpers to extract clipboard images, save via Tauri invoke, and format terminal path mentions.
src-tauri/src/modules/fs/file.rs Adds fs_write_clipboard_image command to write bytes to temp directory + mime-to-extension mapping.
src-tauri/src/lib.rs Registers the new Tauri command.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/modules/tabs/TabBar.tsx Outdated
observer.disconnect();
el.removeEventListener("scroll", updateScrollThumb);
};
}, [tabs.length, updateScrollThumb]);
Comment on lines 91 to 97
const onWheel = (e: WheelEvent) => {
if (Math.abs(e.deltaY) <= Math.abs(e.deltaX)) return;
if (el.scrollWidth <= el.clientWidth) return;
const delta =
Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : e.deltaY;
e.preventDefault();
el.scrollLeft += e.deltaY;
el.scrollLeft += delta;
};
Comment on lines +292 to +310
<div
ref={trackRef}
aria-hidden="true"
className={cn(
"pointer-events-none absolute inset-x-0 bottom-0 h-1 opacity-0 transition-opacity",
scrollThumb.visible && "opacity-100",
)}
>
<div
className="pointer-events-auto flex h-2 cursor-ew-resize items-end pb-0.5"
style={{
width: scrollThumb.width,
transform: `translateX(${scrollThumb.left}px)`,
}}
onPointerDown={onScrollbarPointerDown}
onPointerMove={onScrollbarPointerMove}
onPointerUp={onScrollbarPointerUp}
onPointerCancel={onScrollbarPointerUp}
>
Comment on lines 132 to 136
<div
ref={rootRef}
data-tauri-drag-region
className={`flex h-10 shrink-0 items-center gap-2 border-b border-border/60 bg-card select-none ${
IS_MAC ? "pr-2 pl-20" : "pr-0 pl-2"
}`}
Comment on lines +21 to +29
export async function saveClipboardImage(file: File): Promise<string> {
const bytes = Array.from(new Uint8Array(await file.arrayBuffer()));
const result = await invoke<TempImage>("fs_write_clipboard_image", {
bytes,
mime: file.type || "image/png",
workspace: currentWorkspaceEnv(),
});
return result.path;
}
Comment thread src-tauri/src/modules/fs/file.rs Outdated
Comment on lines +142 to +146
let millis = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|e| e.to_string())?
.as_millis();
let name = format!("clipboard-{millis}.{ext}");
Comment thread src-tauri/src/modules/fs/file.rs Outdated
Comment on lines +131 to +138
pub fn fs_write_clipboard_image(
bytes: Vec<u8>,
mime: String,
workspace: Option<WorkspaceEnv>,
) -> Result<TempImage, String> {
if bytes.is_empty() {
return Err("clipboard image is empty".to_string());
}
@shiva-manu shiva-manu force-pushed the fix/tabs-overflow-scrolling branch from ba0067f to 8e6132b Compare May 15, 2026 05:25
@shiva-manu shiva-manu requested a review from Copilot May 15, 2026 05:45
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (1)

src/modules/tabs/TabBar.tsx:1

  • updateScrollThumb sets React state on every scroll event, which can cause frequent re-renders and jank during smooth/trackpad scrolling. Consider throttling the state update to requestAnimationFrame, or updating thumb position via a ref/CSS variable (and only toggling visible/width in state) to reduce render frequency under continuous scroll.
import { Button } from "@/components/ui/button";

Comment on lines 132 to 136
<div
ref={rootRef}
data-tauri-drag-region
className={`flex h-10 shrink-0 items-center gap-2 border-b border-border/60 bg-card select-none ${
IS_MAC ? "pr-2 pl-20" : "pr-0 pl-2"
}`}
className="flex min-w-0 flex-1 items-center gap-2"
data-tauri-drag-region
>
<div className="flex min-w-0 flex-1 items-center gap-2">
const track = trackRef.current;
if (!el || !track || el.scrollWidth <= el.clientWidth) {
setScrollThumb((current) =>
current.visible ? { ...current, left: 0, visible: false } : current,
Comment on lines +348 to +353
aria-label="Scroll tabs"
aria-controls="terax-tab-scroll"
aria-orientation="horizontal"
aria-valuemin={0}
aria-valuemax={Math.round(scrollThumb.maxScrollLeft)}
aria-valuenow={Math.round(scrollThumb.scrollLeft)}
observer.disconnect();
el.removeEventListener("scroll", updateScrollThumb);
};
}, [tabs, updateScrollThumb]);
@shiva-manu
Copy link
Copy Markdown
Contributor Author

@crynta look into this pr. I added the scroll bar for the tabs

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.

2 participants