Three fixes that all land on the same journey: sign in, paste a prompt, and have the terminal behave the way every other Claude Code host does. Shift+Enter inserts a newline ----------------------------- xterm.js does not consult `shiftKey` for Enter (`Keyboard.ts`, case 13), so Shift+Enter was byte-identical to Enter and submitted the prompt. Both terminals now send `\x1b\r` (ESC+CR) instead, which Claude Code parses as return+meta — the same bytes its own `/terminal-setup` writes into the VS Code, Cursor, Alacritty and Zed keymaps, so this is in-band rather than a guess. Not `\n`: Claude Code accepts it, but a shell would run the line, so the two session types would diverge. Bound in Claude sessions only for that reason. `entrypoint.sh` sets `shiftEnterKeyBindingInstalled` in `~/.claude.json` so the CLI stops printing its "run /terminal-setup" tip. Purely cosmetic — the decoding is unconditional either way. Alt+Enter has always done the same thing (xterm ESC-prefixes on altKey) and was simply never documented. It is now, along with the rest. OAuth login URL truncation -------------------------- Two producers wrote one toast slot, last-writer-wins. The OSC 7777 relay delivers the URL base64-encoded and therefore exact; ~300 ms later the screen-scraper's debounce fired and overwrote it with a truncated guess at the same link — a URL that parses, points at the right host, and authorises nothing. The user is the one who has to notice. Why the scraper truncated: `ANSI_RE` strips OSC sequences wholesale, including the OSC 8 hyperlink whose parameter carries the complete URL. Claude Code slices the *visible* text of that hyperlink to the terminal width while every emission carries the whole URL in its parameter. The backend already knew this (`commands/auth_token_commands.rs`); the frontend did not. - `urlDetector` now reads OSC 8 targets out of the raw buffer before stripping, filtered by a port of `usable_sign_in_link`, and tags every candidate with its provenance. - The prompt slot gained `supersedes`: better provenance always wins, worse never does, and between equals only a candidate that *extends* what is showing may replace it. That last rule is `extendsUrl`, factored out of `pickSignInUrl` rather than copied — same rule, same reason, one implementation. - `flatten` splits on a bare `\r` as well as on `\r?\n`, so a `\r`-repainted TUI frame no longer inflates a line past the width and suppresses a join that should have happened; and the width is now sampled at `feed()` rather than read at `scan()`, so a resize inside the 300 ms debounce cannot reassemble 80-column text against a 120-column rule. Also corrects the comment claiming `acquire_claude_token` enables the auth bridge. It deliberately does not, and the module comment in `auth_token_commands.rs` explains at length why not. The auth bridge toggle ---------------------- `setAuthBridgeEnabled` and `getAuthBridgeStatus` had zero call sites: the Rust was complete, the IPC wrapper shipped, and there was nowhere to click — so the docs told users to "enable the Auth Bridge" for a switch that did not exist. `AuthBridgeRow` is that switch, in Config → Runtime. It deliberately does not go through the tab's stopped-only save: the dedicated command exists so the bridge can be flipped while a login is hanging in a running container, which is the only moment anyone reaches for it. It also subscribes to `auth-bridge-changed`, which the poller has been emitting to nobody — so a host port the bridge could not take was a completely silent failure, indistinguishable from a login that hung. `tunnel.rs` promotes the best-effort `::1` bind failure from debug to a warning recorded on the port. Half-bound is the failure mode that looks like success: the status says bridged, and a client that resolves `localhost` to `::1` without falling back is still refused. Finally, for a recognised Anthropic sign-in URL the toast now leads with "In container" and demotes the host "Open". The callback listener is inside the container, so the container-side browser closes the loop with no host round trip and no auth bridge; the host button stays as the fallback. Ordinary URLs are unchanged. Tests: 402 frontend (was 359), 285 Rust (unchanged). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GBq2rGum6GX7xXgsas1fDc
84 lines
2.8 KiB
TypeScript
84 lines
2.8 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,
|
|
terminalAtBottom, scrollActiveToBottom,
|
|
} = useAppState(
|
|
useShallow(s => ({
|
|
projects: s.projects,
|
|
sessions: s.sessions,
|
|
terminalHasSelection: s.terminalHasSelection,
|
|
activeSessionId: s.activeSessionId,
|
|
sttEnabled: s.appSettings?.stt?.enabled,
|
|
terminalAtBottom: s.terminalAtBottom,
|
|
scrollActiveToBottom: s.scrollActiveToBottom,
|
|
}))
|
|
);
|
|
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: Jump to Current + STT mic */}
|
|
<div className="ml-auto flex items-center gap-3 pl-2">
|
|
{activeSessionId && !terminalAtBottom && (
|
|
<button
|
|
onClick={() => scrollActiveToBottom()}
|
|
className="text-[var(--accent)] hover:text-[var(--accent-hover)] cursor-pointer"
|
|
title="Scroll the terminal to the latest output"
|
|
>
|
|
Jump to Current ↓
|
|
</button>
|
|
)}
|
|
{sttEnabled && activeSessionId && (
|
|
<SttButton
|
|
state={stt.state}
|
|
error={stt.error}
|
|
onToggle={stt.toggle}
|
|
onCancel={stt.cancelRecording}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|