diff --git a/app/src/components/layout/StatusBar.tsx b/app/src/components/layout/StatusBar.tsx
index 01e915a..2ff04fc 100644
--- a/app/src/components/layout/StatusBar.tsx
+++ b/app/src/components/layout/StatusBar.tsx
@@ -2,8 +2,8 @@ import { useShallow } from "zustand/react/shallow";
import { useAppState } from "../../store/appState";
export default function StatusBar() {
- const { projects, sessions } = useAppState(
- useShallow(s => ({ projects: s.projects, sessions: s.sessions }))
+ const { projects, sessions, terminalHasSelection } = useAppState(
+ useShallow(s => ({ projects: s.projects, sessions: s.sessions, terminalHasSelection: s.terminalHasSelection }))
);
const running = projects.filter((p) => p.status === "running").length;
@@ -20,6 +20,12 @@ export default function StatusBar() {
{sessions.length} terminal{sessions.length !== 1 ? "s" : ""}
+ {terminalHasSelection && (
+ <>
+ |
+ Ctrl+Shift+C to copy
+ >
+ )}
);
}
diff --git a/app/src/components/terminal/TerminalView.tsx b/app/src/components/terminal/TerminalView.tsx
index a4102fc..2113e4e 100644
--- a/app/src/components/terminal/TerminalView.tsx
+++ b/app/src/components/terminal/TerminalView.tsx
@@ -7,6 +7,7 @@ import { openUrl } from "@tauri-apps/plugin-opener";
import "@xterm/xterm/css/xterm.css";
import { useTerminal } from "../../hooks/useTerminal";
import { useAppState } from "../../store/appState";
+import { useShallow } from "zustand/react/shallow";
import { awsSsoRefresh } from "../../lib/tauri-commands";
import { UrlDetector } from "../../lib/urlDetector";
import UrlToast from "./UrlToast";
@@ -24,6 +25,7 @@ export default function TerminalView({ sessionId, active }: Props) {
const webglRef = useRef(null);
const detectorRef = useRef(null);
const { sendInput, pasteImage, resize, onOutput, onExit } = useTerminal();
+ const setTerminalHasSelection = useAppState(s => s.setTerminalHasSelection);
const ssoBufferRef = useRef("");
const ssoTriggeredRef = useRef(false);
@@ -136,6 +138,11 @@ export default function TerminalView({ sessionId, active }: Props) {
setIsAtBottom(buf.viewportY >= buf.baseY);
});
+ // Track text selection to show copy hint in status bar
+ const selectionDisposable = term.onSelectionChange(() => {
+ setTerminalHasSelection(term.hasSelection());
+ });
+
// Handle image paste: intercept paste events with image data,
// upload to the container, and inject the file path into terminal input.
const handlePaste = (e: ClipboardEvent) => {
@@ -238,6 +245,8 @@ export default function TerminalView({ sessionId, active }: Props) {
osc52Disposable.dispose();
inputDisposable.dispose();
scrollDisposable.dispose();
+ selectionDisposable.dispose();
+ setTerminalHasSelection(false);
containerRef.current?.removeEventListener("paste", handlePaste, { capture: true });
outputPromise.then((fn) => fn?.());
exitPromise.then((fn) => fn?.());
diff --git a/app/src/store/appState.ts b/app/src/store/appState.ts
index 757c11c..37de139 100644
--- a/app/src/store/appState.ts
+++ b/app/src/store/appState.ts
@@ -24,6 +24,8 @@ interface AppState {
removeMcpServerFromList: (id: string) => void;
// UI state
+ terminalHasSelection: boolean;
+ setTerminalHasSelection: (has: boolean) => void;
sidebarView: "projects" | "mcp" | "settings";
setSidebarView: (view: "projects" | "mcp" | "settings") => void;
dockerAvailable: boolean | null;
@@ -100,6 +102,8 @@ export const useAppState = create((set) => ({
})),
// UI state
+ terminalHasSelection: false,
+ setTerminalHasSelection: (has) => set({ terminalHasSelection: has }),
sidebarView: "projects",
setSidebarView: (view) => set({ sidebarView: view }),
dockerAvailable: null,