+ {state === "recording" && (
+
{formatTime(elapsed)}
+ )}
+ {state === "error" && error && (
+
+ {error}
+
+ )}
{hovered && state !== "recording" && (
-
+
{state === "transcribing" ? "Transcribing..." : (
<>Speech to text Ctrl+Shift+M>
)}
)}
- {state === "recording" && (
-
- {formatTime(elapsed)}
-
- )}
- {state === "error" && error && (
-
- {error}
-
- )}
);
}
diff --git a/app/src/components/terminal/TerminalView.tsx b/app/src/components/terminal/TerminalView.tsx
index 083b556..36b13eb 100644
--- a/app/src/components/terminal/TerminalView.tsx
+++ b/app/src/components/terminal/TerminalView.tsx
@@ -7,8 +7,6 @@ import { openUrl } from "@tauri-apps/plugin-opener";
import "@xterm/xterm/css/xterm.css";
import { useTerminal } from "../../hooks/useTerminal";
import { useAppState } from "../../store/appState";
-import { useSTT } from "../../hooks/useSTT";
-import SttButton from "./SttButton";
import { awsSsoRefresh } from "../../lib/tauri-commands";
import { UrlDetector } from "../../lib/urlDetector";
import UrlToast from "./UrlToast";
@@ -29,10 +27,8 @@ export default function TerminalView({ sessionId, active }: Props) {
const detectorRef = useRef
(null);
const { sendInput, pasteImage, resize, onOutput, onExit } = useTerminal();
const setTerminalHasSelection = useAppState(s => s.setTerminalHasSelection);
- const sttEnabled = useAppState(s => s.appSettings?.stt?.enabled);
- const stt = useSTT(sessionId, sendInput);
- const sttToggleRef = useRef(stt.toggle);
- sttToggleRef.current = stt.toggle;
+ const setTerminalAtBottom = useAppState(s => s.setTerminalAtBottom);
+ const setScrollActiveToBottom = useAppState(s => s.setScrollActiveToBottom);
const ssoBufferRef = useRef("");
const ssoTriggeredRef = useRef(false);
@@ -111,9 +107,10 @@ export default function TerminalView({ sessionId, active }: Props) {
}
return false; // prevent xterm from processing this key
}
- // Ctrl+Shift+M toggles speech-to-text recording
+ // Ctrl+Shift+M toggles speech-to-text recording (mic lives in the status
+ // bar, bound to the active session; trigger it via the store).
if (event.type === "keydown" && event.ctrlKey && event.shiftKey && event.key === "M") {
- sttToggleRef.current();
+ useAppState.getState().sttToggle();
return false;
}
return true;
@@ -393,6 +390,14 @@ export default function TerminalView({ sessionId, active }: Props) {
}
}, []);
+ // Surface this terminal's scroll state to the status bar's "Jump to Current"
+ // control, but only while it's the active (visible) terminal.
+ useEffect(() => {
+ if (!active) return;
+ setTerminalAtBottom(isAtBottom);
+ setScrollActiveToBottom(handleScrollToBottom);
+ }, [active, isAtBottom, handleScrollToBottom, setTerminalAtBottom, setScrollActiveToBottom]);
+
const writeSelection = useCallback((mode: "trimmed" | "raw") => {
const term = termRef.current;
if (!term) return;
@@ -457,23 +462,19 @@ export default function TerminalView({ sessionId, active }: Props) {
>
{isAutoFollow ? "▼ Following" : "▽ Paused"}
- {/* STT mic button - bottom left */}
- {sttEnabled && }
- {/* Jump to Current - bottom right, when scrolled up */}
- {!isAtBottom && (
-
- )}
-
+ {/* Padding lives on this wrapper, NOT on the xterm host element. xterm's
+ FitAddon measures the host element it's mounted into; padding there
+ causes the grid to overhang and clip the rightmost column / bottom
+ row. The host below fills this wrapper's content box with no padding.
+ Kept tight so the terminal claims as much area as possible; right side
+ leaves a little room beside the scrollbar. */}
+
{contextMenu && (
void;
+ // STT toggle for the active session, registered by App so the terminal's
+ // Ctrl+Shift+M shortcut can trigger the single status-bar mic instance.
+ sttToggle: () => void;
+ setSttToggle: (fn: () => void) => void;
+ // Active terminal scroll state, surfaced so the status bar can host the
+ // "Jump to Current" control. Only the active TerminalView writes these.
+ terminalAtBottom: boolean;
+ setTerminalAtBottom: (v: boolean) => void;
+ scrollActiveToBottom: () => void;
+ setScrollActiveToBottom: (fn: () => void) => void;
sidebarView: "projects" | "mcp" | "settings";
setSidebarView: (view: "projects" | "mcp" | "settings") => void;
sidebarCollapsed: boolean;
@@ -125,6 +135,12 @@ export const useAppState = create((set) => ({
// UI state
terminalHasSelection: false,
setTerminalHasSelection: (has) => set({ terminalHasSelection: has }),
+ sttToggle: () => {},
+ setSttToggle: (fn) => set({ sttToggle: fn }),
+ terminalAtBottom: true,
+ setTerminalAtBottom: (v) => set({ terminalAtBottom: v }),
+ scrollActiveToBottom: () => {},
+ setScrollActiveToBottom: (fn) => set({ scrollActiveToBottom: fn }),
sidebarView: "projects",
setSidebarView: (view) => set({ sidebarView: view }),
sidebarCollapsed: loadSidebarCollapsed(),