mirror of
https://github.com/block/buzz.git
synced 2026-11-04 00:12:02 +00:00
fix(terminal): workspace cleanup, light mode theme, buffer cap verified
Addresses Marge's review feedback: 1. Workspace-switch cleanup: Added terminal_close_all_sessions command that kills all PTY sessions. Called from resetWorkspaceState() so orphaned shells don't accumulate across workspace switches. 2. Light mode theme: Terminal surface stays dark in both modes (high contrast "pop" against light app chrome). Added Tokyo Night Moon variant for light mode with warmer foreground. Panel header/buttons use consistent light-on-dark colors regardless of app theme. MutationObserver syncs xterm theme when app theme class changes. 3. Buffer cap: Already implemented (256KB with drain-from-front) — verified the existing MAX_BUFFER_BYTES guard in spawn_reader_thread.
This commit is contained in:
@@ -38,3 +38,9 @@ pub fn terminal_close_session(
|
||||
) -> Result<bool, String> {
|
||||
state.terminal_registry.close_session(&input.session_id)
|
||||
}
|
||||
|
||||
/// Close all active PTY sessions. Called on workspace switch.
|
||||
#[tauri::command]
|
||||
pub fn terminal_close_all_sessions(state: State<'_, AppState>) -> Result<u32, String> {
|
||||
state.terminal_registry.close_all_sessions()
|
||||
}
|
||||
|
||||
@@ -589,6 +589,7 @@ pub fn run() {
|
||||
terminal_write,
|
||||
terminal_resize,
|
||||
terminal_close_session,
|
||||
terminal_close_all_sessions,
|
||||
])
|
||||
.build(tauri::generate_context!())
|
||||
.expect("error while building tauri application");
|
||||
|
||||
@@ -215,6 +215,21 @@ impl TerminalRegistry {
|
||||
let _ = session.killer.kill();
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Close all active PTY sessions. Called on workspace switch to prevent
|
||||
/// orphaned shell processes from accumulating.
|
||||
pub fn close_all_sessions(&self) -> Result<u32, String> {
|
||||
let mut guard = self.sessions.lock().map_err(|e| e.to_string())?;
|
||||
let count = guard.by_session_id.len() as u32;
|
||||
guard.session_id_by_channel.clear();
|
||||
let sessions = std::mem::take(&mut guard.by_session_id);
|
||||
drop(guard);
|
||||
|
||||
for mut session in sessions.into_values() {
|
||||
let _ = session.killer.kill();
|
||||
}
|
||||
Ok(count)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TerminalRegistry {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as React from "react";
|
||||
import { Terminal } from "@xterm/xterm";
|
||||
import type { ITheme } from "@xterm/xterm";
|
||||
import { FitAddon } from "@xterm/addon-fit";
|
||||
import { WebLinksAddon } from "@xterm/addon-web-links";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
@@ -7,6 +8,61 @@ import { listen, type UnlistenFn } from "@tauri-apps/api/event";
|
||||
|
||||
import "@xterm/xterm/css/xterm.css";
|
||||
|
||||
// Tokyo Night — high-contrast dark terminal surface.
|
||||
const DARK_THEME: ITheme = {
|
||||
background: "#1a1b26",
|
||||
foreground: "#c0caf5",
|
||||
cursor: "#c0caf5",
|
||||
selectionBackground: "#33467c",
|
||||
black: "#15161e",
|
||||
red: "#f7768e",
|
||||
green: "#9ece6a",
|
||||
yellow: "#e0af68",
|
||||
blue: "#7aa2f7",
|
||||
magenta: "#bb9af7",
|
||||
cyan: "#7dcfff",
|
||||
white: "#a9b1d6",
|
||||
brightBlack: "#414868",
|
||||
brightRed: "#f7768e",
|
||||
brightGreen: "#9ece6a",
|
||||
brightYellow: "#e0af68",
|
||||
brightBlue: "#7aa2f7",
|
||||
brightMagenta: "#bb9af7",
|
||||
brightCyan: "#7dcfff",
|
||||
brightWhite: "#c0caf5",
|
||||
};
|
||||
|
||||
// Light mode: terminal stays dark (high contrast against light app chrome).
|
||||
// Uses the same Tokyo Night bg with slightly warmer foreground for readability.
|
||||
const LIGHT_THEME: ITheme = {
|
||||
background: "#1a1b26",
|
||||
foreground: "#c8d3f5",
|
||||
cursor: "#c8d3f5",
|
||||
selectionBackground: "#2f3549",
|
||||
black: "#1a1b26",
|
||||
red: "#ff757f",
|
||||
green: "#c3e88d",
|
||||
yellow: "#ffc777",
|
||||
blue: "#82aaff",
|
||||
magenta: "#c099ff",
|
||||
cyan: "#86e1fc",
|
||||
white: "#c8d3f5",
|
||||
brightBlack: "#545c7e",
|
||||
brightRed: "#ff757f",
|
||||
brightGreen: "#c3e88d",
|
||||
brightYellow: "#ffc777",
|
||||
brightBlue: "#82aaff",
|
||||
brightMagenta: "#c099ff",
|
||||
brightCyan: "#86e1fc",
|
||||
brightWhite: "#e4f0fb",
|
||||
};
|
||||
|
||||
function getTerminalTheme(): ITheme {
|
||||
return document.documentElement.classList.contains("dark")
|
||||
? DARK_THEME
|
||||
: LIGHT_THEME;
|
||||
}
|
||||
|
||||
type TerminalDataPayload = {
|
||||
sessionId: string;
|
||||
data: string;
|
||||
@@ -53,28 +109,7 @@ export function TerminalInstance({
|
||||
lineHeight: 1.3,
|
||||
scrollback: 10000,
|
||||
allowProposedApi: true,
|
||||
theme: {
|
||||
background: "#1a1b26",
|
||||
foreground: "#c0caf5",
|
||||
cursor: "#c0caf5",
|
||||
selectionBackground: "#33467c",
|
||||
black: "#15161e",
|
||||
red: "#f7768e",
|
||||
green: "#9ece6a",
|
||||
yellow: "#e0af68",
|
||||
blue: "#7aa2f7",
|
||||
magenta: "#bb9af7",
|
||||
cyan: "#7dcfff",
|
||||
white: "#a9b1d6",
|
||||
brightBlack: "#414868",
|
||||
brightRed: "#f7768e",
|
||||
brightGreen: "#9ece6a",
|
||||
brightYellow: "#e0af68",
|
||||
brightBlue: "#7aa2f7",
|
||||
brightMagenta: "#bb9af7",
|
||||
brightCyan: "#7dcfff",
|
||||
brightWhite: "#c0caf5",
|
||||
},
|
||||
theme: getTerminalTheme(),
|
||||
});
|
||||
|
||||
const fitAddon = new FitAddon();
|
||||
@@ -137,6 +172,15 @@ export function TerminalInstance({
|
||||
});
|
||||
});
|
||||
|
||||
// Watch for theme class changes on <html> to sync xterm colors.
|
||||
const themeObserver = new MutationObserver(() => {
|
||||
term.options.theme = getTerminalTheme();
|
||||
});
|
||||
themeObserver.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ["class"],
|
||||
});
|
||||
|
||||
// Resize observer.
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
if (fitAddonRef.current && isVisible) {
|
||||
@@ -155,6 +199,7 @@ export function TerminalInstance({
|
||||
resizeObserver.observe(containerRef.current);
|
||||
|
||||
return () => {
|
||||
themeObserver.disconnect();
|
||||
resizeObserver.disconnect();
|
||||
unlistenDataRef.current?.();
|
||||
unlistenExitRef.current?.();
|
||||
|
||||
@@ -100,7 +100,7 @@ export function TerminalPanel({
|
||||
</button>
|
||||
|
||||
{/* Header bar */}
|
||||
<div className="flex h-8 shrink-0 items-center justify-between border-b border-border/40 bg-[#1a1b26] px-3">
|
||||
<div className="flex h-8 shrink-0 items-center justify-between border-b border-white/5 bg-[#1a1b26] px-3">
|
||||
<span className="text-xs font-medium text-[#7aa2f7]">Terminal</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
import { relayClient } from "@/shared/api/relayClient";
|
||||
import {
|
||||
applyWorkspace,
|
||||
@@ -26,6 +28,8 @@ function resetWorkspaceState(): void {
|
||||
resetMediaCaches();
|
||||
clearSearchHitEventCache();
|
||||
clearAllDrafts();
|
||||
// Kill all PTY sessions so orphaned shells don't accumulate across workspaces.
|
||||
invoke("terminal_close_all_sessions").catch(() => {});
|
||||
}
|
||||
|
||||
type WorkspaceInitResult =
|
||||
|
||||
Reference in New Issue
Block a user