fix: 6 UX regressions — popups, themes, cursor, selection, status bar
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m51s

Popup windows (tools/editor/help):
- CSP script-src 'self' blocked Tauri's inline IPC bridge scripts in
  child WebviewWindows. Added 'unsafe-inline' to script-src. Still
  restrictive (was null before SEC-4).

Theme application:
- Watcher on sessionStore.activeTheme needed { deep: true } — Pinia
  reactive proxy identity doesn't change on object replacement
- LocalTerminalView.vue had ZERO theme support — added full applyTheme()
  with watcher and mount-time application
- Container background now syncs with theme (was stuck on CSS variable)

Cursor blink:
- terminal.focus() after mount in useTerminal.ts — terminal opened
  without focus, xterm.js rendered static outline instead of blinking block

Selection highlighting:
- applyTheme() was overwriting theme without selectionBackground/
  selectionForeground/selectionInactiveBackground — selection invisible
  after any theme change
- Removed !important from terminal.css that overrode canvas selection
- Bumped default selection opacity 0.3 → 0.4

Status bar:
- h-6 text-[10px] → h-8 text-xs (24px/10px → 32px/12px)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vantz Stockwell 2026-03-30 09:52:43 -04:00
parent 6acd674905
commit aa2ef88ed7
7 changed files with 71 additions and 7 deletions

View File

@ -23,7 +23,7 @@
}
],
"security": {
"csp": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' asset: https://asset.localhost data:; connect-src 'self' ipc: http://ipc.localhost"
"csp": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' asset: https://asset.localhost data:; connect-src 'self' ipc: http://ipc.localhost"
},
"withGlobalTauri": false
},

View File

@ -20,9 +20,11 @@
height: 100%;
}
/* Selection styling */
/* Selection styling let xterm.js theme handle selection colors.
The !important override was removed because it conflicts with
theme-driven selectionBackground set via terminal.options.theme. */
.terminal-container .xterm-selection div {
background-color: rgba(88, 166, 255, 0.3) !important;
background-color: rgba(88, 166, 255, 0.3);
}
/* Cursor styling */

View File

@ -1,5 +1,5 @@
<template>
<div class="h-6 flex items-center justify-between px-4 bg-[var(--wraith-bg-secondary)] border-t border-[var(--wraith-border)] text-[10px] text-[var(--wraith-text-muted)] shrink-0">
<div class="h-8 flex items-center justify-between px-4 bg-[var(--wraith-bg-secondary)] border-t border-[var(--wraith-border)] text-xs text-[var(--wraith-text-muted)] shrink-0">
<!-- Left: connection info -->
<div class="flex items-center gap-3">
<template v-if="sessionStore.activeSession">

View File

@ -112,6 +112,8 @@ export interface ThemeDefinition {
brightMagenta: string;
brightCyan: string;
brightWhite: string;
selectionBackground?: string;
selectionForeground?: string;
isBuiltin?: boolean;
}

View File

@ -12,6 +12,7 @@
import { ref, onMounted, onBeforeUnmount, watch } from "vue";
import { invoke } from "@tauri-apps/api/core";
import { useTerminal } from "@/composables/useTerminal";
import { useSessionStore } from "@/stores/session.store";
import "@/assets/css/terminal.css";
const props = defineProps<{
@ -19,13 +20,55 @@ const props = defineProps<{
isActive: boolean;
}>();
const sessionStore = useSessionStore();
const containerRef = ref<HTMLElement | null>(null);
const { terminal, mount, fit, destroy } = useTerminal(props.sessionId, "pty");
/** Apply the session store's active theme to this local terminal instance. */
function applyTheme(): void {
const theme = sessionStore.activeTheme;
if (!theme) return;
terminal.options.theme = {
background: theme.background,
foreground: theme.foreground,
cursor: theme.cursor,
cursorAccent: theme.background,
selectionBackground: theme.selectionBackground ?? "rgba(88, 166, 255, 0.4)",
selectionForeground: theme.selectionForeground ?? "#ffffff",
selectionInactiveBackground: theme.selectionBackground ?? "rgba(88, 166, 255, 0.2)",
black: theme.black,
red: theme.red,
green: theme.green,
yellow: theme.yellow,
blue: theme.blue,
magenta: theme.magenta,
cyan: theme.cyan,
white: theme.white,
brightBlack: theme.brightBlack,
brightRed: theme.brightRed,
brightGreen: theme.brightGreen,
brightYellow: theme.brightYellow,
brightBlue: theme.brightBlue,
brightMagenta: theme.brightMagenta,
brightCyan: theme.brightCyan,
brightWhite: theme.brightWhite,
};
if (containerRef.value) {
containerRef.value.style.backgroundColor = theme.background;
}
}
onMounted(() => {
if (containerRef.value) {
mount(containerRef.value);
}
// Apply current theme immediately if one is already active
if (sessionStore.activeTheme) {
applyTheme();
}
setTimeout(() => {
fit();
terminal.focus();
@ -56,6 +99,11 @@ watch(
},
);
// Watch for theme changes and apply to this local terminal
watch(() => sessionStore.activeTheme, (newTheme) => {
if (newTheme) applyTheme();
}, { deep: true });
onBeforeUnmount(() => {
destroy();
});

View File

@ -185,6 +185,10 @@ function applyTheme(): void {
background: theme.background,
foreground: theme.foreground,
cursor: theme.cursor,
cursorAccent: theme.background,
selectionBackground: theme.selectionBackground ?? "rgba(88, 166, 255, 0.4)",
selectionForeground: theme.selectionForeground ?? "#ffffff",
selectionInactiveBackground: theme.selectionBackground ?? "rgba(88, 166, 255, 0.2)",
black: theme.black,
red: theme.red,
green: theme.green,
@ -202,12 +206,19 @@ function applyTheme(): void {
brightCyan: theme.brightCyan,
brightWhite: theme.brightWhite,
};
// Sync the container background so areas outside the canvas match the theme
if (containerRef.value) {
containerRef.value.style.backgroundColor = theme.background;
}
}
// Watch for theme changes in the session store and apply to this terminal
// Watch for theme changes in the session store and apply to this terminal.
// Uses deep comparison because the theme is an object a shallow watch may miss
// updates if Pinia returns the same reactive proxy wrapper after reassignment.
watch(() => sessionStore.activeTheme, (newTheme) => {
if (newTheme) applyTheme();
});
}, { deep: true });
onBeforeUnmount(() => {
if (resizeDisposable) {

View File

@ -14,7 +14,7 @@ const defaultTheme = {
foreground: "#e0e0e0",
cursor: "#58a6ff",
cursorAccent: "#0d1117",
selectionBackground: "rgba(88, 166, 255, 0.3)",
selectionBackground: "rgba(88, 166, 255, 0.4)",
selectionForeground: "#ffffff",
black: "#0d1117",
red: "#f85149",
@ -155,6 +155,7 @@ export function useTerminal(sessionId: string, backend: 'ssh' | 'pty' = 'ssh'):
// cell widths — producing tiny dashes and 200+ column terminals.
document.fonts.ready.then(() => {
fitAddon.fit();
terminal.focus();
});
// Right-click paste on the terminal's DOM element