Secret Scan / scan (push) Successful in 4s
Build App (Preview) / compute-version (pull_request) Successful in 3s
Secret Scan / scan (pull_request) Successful in 4s
Build App (Preview) / create-release (pull_request) Successful in 1s
Build App (Preview) / build-macos (pull_request) Successful in 2m44s
Build App (Preview) / build-linux (pull_request) Successful in 5m8s
Build App (Preview) / build-windows (pull_request) Successful in 6m28s
Build App (Preview) / prune-previews (pull_request) Successful in 2s
Build Container / build-container (pull_request) Failing after 14m49s
Three things the terminal was getting wrong. **A program that grabs the mouse and dies used to freeze the tab.** A TUI sets DECSET ?1000/?1002/?1003; if it exits without resetting them, xterm keeps routing clicks, drags and — under ?1003 — every pointer *move* to the PTY. Text selection dies and escape bytes flood the prompt. The only exit was closing the tab. `TerminalView` now reconciles a flag against `term.modes.mouseTrackingMode` in the `term.write()` callback — the mode only changes because the container printed a sequence, so one check per write catches every transition with no polling — and `Ctrl+Shift+X` or a status-bar button writes the resets back through `term.write`, never `sendInput`: the reset belongs to xterm's parser, and a still-live TUI told about it would just re-grab on its next repaint. The control is in the status bar deliberately. Mouse tracking is the *normal* state of htop, vim, lazygit and Claude Code, so a badge over the terminal would be on screen for the whole life of those programs and would swallow clicks aimed at their own top-right corner. `macOptionClickForcesSelection` is also on now: xterm's force-select is Shift everywhere except macOS, where it is Option and is gated behind that option, which defaults to false — so until now Mac users had no way to select text while a program held the mouse. **"Following" and "Jump to Current" are gone.** Claude Code draws on the alternate screen, which has no scrollback, so `viewportY` always equalled `baseY` and neither control could do anything. They did still work in bash tabs; xterm's native follow covers that, and the per-write `scrollToBottom()` went with them because it fought exactly that. What remains, on activate and after a refit, now samples `viewportY >= baseY` *before* the fit, so opening the Notes dock no longer yanks a reader to the tail. **`claude update` runs before every Claude session, not just at container start.** Containers here stop/start and often just keep running, so a long-lived one never re-checked. Both copies take the same flock: the entrypoint prints "container ready" only after its own update finishes, so opening a tab immediately would otherwise run two updaters against the same ~/.claude/bin, with `|| echo` hiding a half-written install one line before `exec claude` ran it. This turns the non-Bedrock path from a bare argv into a `bash -c` wrapper, so flags and session names are shell-interpolated now and must go through `shell_quote_arg`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0145mQi9NZiCDrznBUEEDE4n
95 lines
3.5 KiB
TypeScript
95 lines
3.5 KiB
TypeScript
import { useShallow } from "zustand/react/shallow";
|
|
import { useAppState } from "../../store/appState";
|
|
import SttButton from "../terminal/SttButton";
|
|
import type { useSTT } from "../../hooks/useSTT";
|
|
|
|
interface Props {
|
|
stt: ReturnType<typeof useSTT>;
|
|
}
|
|
|
|
export default function StatusBar({ stt }: Props) {
|
|
const {
|
|
projects, sessions, terminalHasSelection, activeSessionId, sttEnabled,
|
|
notesDockOpen, toggleNotesDock, terminalMouseCaptured, releaseActiveMouse,
|
|
} = useAppState(
|
|
useShallow(s => ({
|
|
projects: s.projects,
|
|
sessions: s.sessions,
|
|
terminalHasSelection: s.terminalHasSelection,
|
|
activeSessionId: s.activeSessionId,
|
|
sttEnabled: s.appSettings?.stt?.enabled,
|
|
notesDockOpen: s.notesDockOpen,
|
|
toggleNotesDock: s.toggleNotesDock,
|
|
terminalMouseCaptured: s.terminalMouseCaptured,
|
|
releaseActiveMouse: s.releaseActiveMouse,
|
|
}))
|
|
);
|
|
const running = projects.filter((p) => p.status === "running").length;
|
|
// Only in a Claude tab: the chord is bound there and nowhere else, and a hint
|
|
// for a key that does nothing is worse than no hint.
|
|
const inClaudeSession = sessions.some(
|
|
(s) => s.id === activeSessionId && s.sessionType === "claude",
|
|
);
|
|
|
|
return (
|
|
<div className="flex items-center h-6 px-4 bg-[var(--bg-tertiary)] border border-[var(--border-color)] rounded-[var(--radius-panel)] text-xs text-[var(--text-secondary)]">
|
|
<span>
|
|
{projects.length} project{projects.length !== 1 ? "s" : ""}
|
|
</span>
|
|
<span className="mx-2">|</span>
|
|
<span>
|
|
{running} running
|
|
</span>
|
|
<span className="mx-2">|</span>
|
|
<span>
|
|
{sessions.length} terminal{sessions.length !== 1 ? "s" : ""}
|
|
</span>
|
|
{terminalHasSelection && (
|
|
<>
|
|
<span className="mx-2">|</span>
|
|
<span className="text-[var(--accent)]">
|
|
Ctrl+Shift+C: copy trimmed · Ctrl+Shift+Alt+C: copy raw
|
|
</span>
|
|
</>
|
|
)}
|
|
{!terminalHasSelection && inClaudeSession && (
|
|
<>
|
|
<span className="mx-2">|</span>
|
|
<span title="Sends ESC+CR — the sequence Claude Code's own /terminal-setup installs. Alt+Enter does the same.">
|
|
Shift+Enter: newline
|
|
</span>
|
|
</>
|
|
)}
|
|
{/* Right-aligned controls: mouse release + Notes + STT mic */}
|
|
<div className="ml-auto flex items-center gap-3 pl-2">
|
|
{activeSessionId && terminalMouseCaptured && (
|
|
<button
|
|
data-mouse-release="true"
|
|
onClick={() => releaseActiveMouse()}
|
|
className="text-[var(--accent)] hover:text-[var(--accent-hover)] cursor-pointer"
|
|
title="A program in the container is reading the mouse, so clicks and drags go to it instead of selecting text. Click, or press Ctrl+Shift+X, to take it back. To select text without taking it back, hold Shift while dragging (Option on macOS)."
|
|
>
|
|
🖱 Mouse captured — release
|
|
</button>
|
|
)}
|
|
<button
|
|
onClick={toggleNotesDock}
|
|
aria-pressed={notesDockOpen}
|
|
className="text-[var(--accent)] hover:text-[var(--accent-hover)] cursor-pointer"
|
|
title="Show or hide the notes panel beside the current tab"
|
|
>
|
|
Notes
|
|
</button>
|
|
{sttEnabled && activeSessionId && (
|
|
<SttButton
|
|
state={stt.state}
|
|
error={stt.error}
|
|
onToggle={stt.toggle}
|
|
onCancel={stt.cancelRecording}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|