diff --git a/app/src-tauri/icons/128x128.png b/app/src-tauri/icons/128x128.png index 843d237..0549ced 100644 Binary files a/app/src-tauri/icons/128x128.png and b/app/src-tauri/icons/128x128.png differ diff --git a/app/src-tauri/icons/128x128@2x.png b/app/src-tauri/icons/128x128@2x.png index dc5a4a2..3235a80 100644 Binary files a/app/src-tauri/icons/128x128@2x.png and b/app/src-tauri/icons/128x128@2x.png differ diff --git a/app/src-tauri/icons/32x32.png b/app/src-tauri/icons/32x32.png index c091288..286c604 100644 Binary files a/app/src-tauri/icons/32x32.png and b/app/src-tauri/icons/32x32.png differ diff --git a/app/src-tauri/icons/icon.ico b/app/src-tauri/icons/icon.ico index d4b604f..88e60c3 100644 Binary files a/app/src-tauri/icons/icon.ico and b/app/src-tauri/icons/icon.ico differ diff --git a/app/src-tauri/icons/icon.png b/app/src-tauri/icons/icon.png index 5afc55c..cf3f6b7 100644 Binary files a/app/src-tauri/icons/icon.png and b/app/src-tauri/icons/icon.png differ diff --git a/app/src-tauri/src/docker/container.rs b/app/src-tauri/src/docker/container.rs index f3c98f6..0ff5eda 100644 --- a/app/src-tauri/src/docker/container.rs +++ b/app/src-tauri/src/docker/container.rs @@ -392,6 +392,10 @@ pub async fn stop_container(container_id: &str) -> Result<(), String> { pub async fn remove_container(container_id: &str) -> Result<(), String> { let docker = get_docker()?; + log::info!( + "Removing container {} (v=false: named volumes such as claude config are preserved)", + container_id + ); docker .remove_container( container_id, diff --git a/app/src/components/projects/ProjectCard.tsx b/app/src/components/projects/ProjectCard.tsx index 6333e98..d606820 100644 --- a/app/src/components/projects/ProjectCard.tsx +++ b/app/src/components/projects/ProjectCard.tsx @@ -269,8 +269,6 @@ export default function ProjectCard({ project }: Props) { {project.paths.map((pp, i) => (
/workspace/{pp.mount_name} - - {pp.host_path}
))} diff --git a/app/src/components/terminal/TerminalView.tsx b/app/src/components/terminal/TerminalView.tsx index 5610738..e36faab 100644 --- a/app/src/components/terminal/TerminalView.tsx +++ b/app/src/components/terminal/TerminalView.tsx @@ -7,11 +7,6 @@ import { openUrl } from "@tauri-apps/plugin-opener"; import "@xterm/xterm/css/xterm.css"; import { useTerminal } from "../../hooks/useTerminal"; -/** Strip ANSI escape sequences from a string. */ -function stripAnsi(s: string): string { - return s.replace(/\x1b\[[0-9;]*[A-Za-z]|\x1b\][^\x07]*\x07/g, ""); -} - interface Props { sessionId: string; active: boolean; @@ -84,50 +79,12 @@ export default function TerminalView({ sessionId, active }: Props) { sendInput(sessionId, data); }); - // ── URL accumulator ────────────────────────────────────────────── - // Claude Code login emits a long OAuth URL that gets split across - // hard newlines (\n / \r\n). The WebLinksAddon only joins - // soft-wrapped lines (the `isWrapped` flag), so the URL match is - // truncated and the link fails when clicked. - // - // Fix: buffer recent output, strip ANSI codes, and after a short - // debounce check for a URL that spans multiple lines. When found, - // write a single clean clickable copy to the terminal. - const textDecoder = new TextDecoder(); - let outputBuffer = ""; - let debounceTimer: ReturnType | null = null; - - const flushUrlBuffer = () => { - const plain = stripAnsi(outputBuffer); - // Reassemble: strip hard newlines and carriage returns to join - // fragments that were split across terminal lines. - const joined = plain.replace(/[\r\n]+/g, ""); - // Look for a long OAuth/auth URL (Claude login URLs contain - // "oauth" or "console.anthropic.com" or "/authorize"). - const match = joined.match(/https?:\/\/[^\s'"\x07]{80,}/); - if (match) { - const url = match[0]; - term.write("\r\n\x1b[36m🔗 Clickable login URL:\x1b[0m\r\n"); - term.write(`\x1b[4;34m${url}\x1b[0m\r\n`); - } - outputBuffer = ""; - }; - // Handle backend output -> terminal let aborted = false; const outputPromise = onOutput(sessionId, (data) => { if (aborted) return; term.write(data); - - // Accumulate for URL detection (data is a Uint8Array, so decode it) - outputBuffer += textDecoder.decode(data); - // Cap buffer size to avoid memory growth - if (outputBuffer.length > 8192) { - outputBuffer = outputBuffer.slice(-4096); - } - if (debounceTimer) clearTimeout(debounceTimer); - debounceTimer = setTimeout(flushUrlBuffer, 150); }).then((unlisten) => { if (aborted) unlisten(); return unlisten; @@ -159,7 +116,6 @@ export default function TerminalView({ sessionId, active }: Props) { return () => { aborted = true; - if (debounceTimer) clearTimeout(debounceTimer); inputDisposable.dispose(); outputPromise.then((fn) => fn?.()); exitPromise.then((fn) => fn?.());