Address review: pin STT transcript, clear stale scroll state
Build App / compute-version (pull_request) Successful in 3s
Build App / build-macos (pull_request) Successful in 2m15s
Build App / build-windows (pull_request) Successful in 3m49s
Build App / build-linux (pull_request) Successful in 4m48s
Build App / create-tag (pull_request) Has been skipped
Build App / sync-to-github (pull_request) Has been skipped
Build App / compute-version (pull_request) Successful in 3s
Build App / build-macos (pull_request) Successful in 2m15s
Build App / build-windows (pull_request) Successful in 3m49s
Build App / build-linux (pull_request) Successful in 4m48s
Build App / create-tag (pull_request) Has been skipped
Build App / sync-to-github (pull_request) Has been skipped
Follow-up to PR review on terminal-layout-statusbar: - [Major] Pin STT transcripts to the originating terminal. The single useSTT instance is bound to the live active session, which can change mid-recording. Capture the session id at recording start in a ref and inject the transcript there instead of the live sessionId, so text always lands in the terminal where recording began. - [Minor] Clear the status-bar scroll state when the active terminal unmounts, and null out termRef on dispose, so scrollActiveToBottom can't point at a disposed terminal. Tab switches don't unmount, so this only fires when the active session is actually closed. - [Nit] Fix the terminal padding comment to match the symmetric value. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -316,6 +316,7 @@ export default function TerminalView({ sessionId, active }: Props) {
|
||||
try { webglRef.current?.dispose(); } catch { /* may already be disposed */ }
|
||||
webglRef.current = null;
|
||||
term.dispose();
|
||||
termRef.current = null;
|
||||
};
|
||||
}, [sessionId]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
@@ -398,6 +399,21 @@ export default function TerminalView({ sessionId, active }: Props) {
|
||||
setScrollActiveToBottom(handleScrollToBottom);
|
||||
}, [active, isAtBottom, handleScrollToBottom, setTerminalAtBottom, setScrollActiveToBottom]);
|
||||
|
||||
// On unmount, if this was the active terminal, clear the status-bar scroll
|
||||
// state so it doesn't point at a disposed terminal. (Tab switches don't
|
||||
// unmount — the deactivating terminal stays mounted but hidden — so this
|
||||
// only fires when the active session is actually closed.)
|
||||
const activeRef = useRef(active);
|
||||
activeRef.current = active;
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (activeRef.current) {
|
||||
setTerminalAtBottom(true);
|
||||
setScrollActiveToBottom(() => {});
|
||||
}
|
||||
};
|
||||
}, [setTerminalAtBottom, setScrollActiveToBottom]);
|
||||
|
||||
const writeSelection = useCallback((mode: "trimmed" | "raw") => {
|
||||
const term = termRef.current;
|
||||
if (!term) return;
|
||||
@@ -466,8 +482,8 @@ export default function TerminalView({ sessionId, active }: Props) {
|
||||
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. */}
|
||||
Kept to a tight, even gutter so the terminal claims as much area as
|
||||
possible while leaving a little breathing room beside the scrollbar. */}
|
||||
<div className="w-full h-full" style={{ padding: "4px 8px 4px 8px" }}>
|
||||
<div
|
||||
ref={containerRef}
|
||||
|
||||
@@ -13,6 +13,11 @@ export function useSTT(sessionId: string, sendInput: (sessionId: string, data: s
|
||||
const streamRef = useRef<MediaStream | null>(null);
|
||||
const workletRef = useRef<AudioWorkletNode | null>(null);
|
||||
const chunksRef = useRef<Int16Array[]>([]);
|
||||
// Pin the transcript to the terminal that was active when recording STARTED.
|
||||
// The hook is bound to the live active session, which can change mid-recording
|
||||
// (the user switches tabs); without this the transcript would land in whatever
|
||||
// tab is active at stop time.
|
||||
const recordingSessionIdRef = useRef(sessionId);
|
||||
|
||||
const appSettings = useAppState((s) => s.appSettings);
|
||||
const deviceId = appSettings?.default_microphone;
|
||||
@@ -22,6 +27,7 @@ export function useSTT(sessionId: string, sendInput: (sessionId: string, data: s
|
||||
setState("recording");
|
||||
setError(null);
|
||||
chunksRef.current = [];
|
||||
recordingSessionIdRef.current = sessionId;
|
||||
|
||||
try {
|
||||
const audioConstraints: MediaTrackConstraints = {
|
||||
@@ -57,7 +63,7 @@ export function useSTT(sessionId: string, sendInput: (sessionId: string, data: s
|
||||
setError(msg);
|
||||
setState("error");
|
||||
}
|
||||
}, [state, deviceId]);
|
||||
}, [state, deviceId, sessionId]);
|
||||
|
||||
const stopRecording = useCallback(async () => {
|
||||
if (state !== "recording") return;
|
||||
@@ -102,7 +108,7 @@ export function useSTT(sessionId: string, sendInput: (sessionId: string, data: s
|
||||
|
||||
const text = await commands.transcribeAudio(audioData);
|
||||
if (text) {
|
||||
await sendInput(sessionId, text);
|
||||
await sendInput(recordingSessionIdRef.current, text);
|
||||
}
|
||||
setState("idle");
|
||||
} catch (e) {
|
||||
@@ -112,7 +118,7 @@ export function useSTT(sessionId: string, sendInput: (sessionId: string, data: s
|
||||
// Reset to idle after a brief delay so the UI shows the error
|
||||
setTimeout(() => setState("idle"), 3000);
|
||||
}
|
||||
}, [state, sessionId, sendInput]);
|
||||
}, [state, sendInput]);
|
||||
|
||||
const cancelRecording = useCallback(async () => {
|
||||
workletRef.current?.disconnect();
|
||||
|
||||
Reference in New Issue
Block a user