fix: lazy-connect terminal and cap reconnect attempts (#521)

TerminalPanel was connecting on mount even when the drawer was closed
and the terminal tab was inactive. Combined with reconnectAttempts
resetting on every ws.onopen, this caused infinite reconnection loops
that spawned PTY processes until system limits were hit.

- Pass `visible` prop to TerminalPanel, only connect when terminal tab
  is actually shown
- Move reconnectAttempts reset from ws.onopen to "created" handler so
  only successful PTY creation resets the counter
- Remove unused onMounted import

Fixes #509

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
ekko
2026-05-07 19:33:15 +08:00
committed by GitHub
parent f1839db473
commit 2a390e96b9
2 changed files with 13 additions and 7 deletions
@@ -63,7 +63,7 @@ function handleClose() {
<FilesPanel />
</div>
<div v-show="activeTab === 'terminal'" class="drawer-pane">
<TerminalPanel />
<TerminalPanel :visible="activeTab === 'terminal' && show" />
</div>
</div>
</div>
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from "vue";
import { ref, onUnmounted, computed, watch } from "vue";
import { Terminal } from "@xterm/xterm";
import { FitAddon } from "@xterm/addon-fit";
import { WebLinksAddon } from "@xterm/addon-web-links";
@@ -12,6 +12,8 @@ import type { ITheme } from "@xterm/xterm";
const { t } = useI18n();
const message = useMessage();
const props = defineProps<{ visible?: boolean }>();
// ─── Terminal themes ────────────────────────────────────────────
const TERMINAL_THEMES: Record<string, { label: string; theme: ITheme }> = {
@@ -164,10 +166,8 @@ function connect() {
ws = new WebSocket(url);
ws.onopen = () => {
reconnectAttempts = 0;
isConnecting.value = false;
connectionError.value = null;
// Server auto-creates the first session
};
ws.onmessage = (event) => {
@@ -210,6 +210,7 @@ function send(data: object | string) {
function handleControl(msg: any) {
switch (msg.type) {
case "created":
reconnectAttempts = 0;
sessions.value.push({
id: msg.id,
shell: msg.shell,
@@ -376,9 +377,14 @@ function formatTime(ts: number) {
// ─── Lifecycle ──────────────────────────────────────────────────
onMounted(() => {
connect();
});
let hasConnected = false;
watch(() => props.visible, (visible) => {
if (visible && !hasConnected && !ws) {
hasConnected = true;
connect();
}
}, { immediate: true });
onUnmounted(() => {
unmountActiveTerminal();