fix: terminal never appears — pending session removed before WS connects

Root cause: TerminalInstance.onMounted() called sessions.removeSession()
on the pending session, dropping sessions.length to 0. SessionContainer's
v-if="hasSessions" went false, unmounting the entire terminal UI before
the WebSocket could establish and add the real session.

Fix: Added replaceSession() to session store. TerminalInstance no longer
removes the pending session — instead passes its ID to connectToHost(),
which swaps it in-place when the backend responds with the real session ID.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Vantz Stockwell 2026-03-13 11:08:34 -04:00
parent 6759327ee3
commit d74bb28960
3 changed files with 14 additions and 7 deletions

View File

@ -24,11 +24,8 @@ onMounted(() => {
termInstance = createTerminal(termContainer.value) termInstance = createTerminal(termContainer.value)
const { term, fitAddon } = termInstance const { term, fitAddon } = termInstance
// Connect useTerminal will call sessions.addSession with the real backend sessionId. // Connect useTerminal will replace the pending session with the real backend sessionId
// Remove the pending placeholder first to avoid duplicate entries. connectToHost(props.hostId, props.hostName, 'ssh', props.color, props.sessionId, term, fitAddon)
sessions.removeSession(props.sessionId)
connectToHost(props.hostId, props.hostName, 'ssh', props.color, term, fitAddon)
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {

View File

@ -55,7 +55,7 @@ export function useTerminal() {
return { term, fitAddon, searchAddon, resizeObserver } return { term, fitAddon, searchAddon, resizeObserver }
} }
function connectToHost(hostId: number, hostName: string, protocol: 'ssh', color: string | null, term: Terminal, fitAddon: FitAddon) { function connectToHost(hostId: number, hostName: string, protocol: 'ssh', color: string | null, pendingSessionId: string, term: Terminal, fitAddon: FitAddon) {
const wsUrl = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/ws/terminal?token=${auth.token}` const wsUrl = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/ws/terminal?token=${auth.token}`
ws = new WebSocket(wsUrl) ws = new WebSocket(wsUrl)
@ -67,7 +67,8 @@ export function useTerminal() {
const msg = JSON.parse(event.data) const msg = JSON.parse(event.data)
switch (msg.type) { switch (msg.type) {
case 'connected': case 'connected':
sessions.addSession({ id: msg.sessionId, hostId, hostName, protocol, color, active: true }) // Replace the pending placeholder with the real backend session
sessions.replaceSession(pendingSessionId, { id: msg.sessionId, hostId, hostName, protocol, color, active: true })
// Send initial terminal size // Send initial terminal size
ws!.send(JSON.stringify({ type: 'resize', sessionId: msg.sessionId, cols: term.cols, rows: term.rows })) ws!.send(JSON.stringify({ type: 'resize', sessionId: msg.sessionId, cols: term.cols, rows: term.rows }))
break break

View File

@ -29,6 +29,15 @@ export const useSessionStore = defineStore('sessions', {
this.activeSessionId = this.sessions.length ? this.sessions[this.sessions.length - 1].id : null this.activeSessionId = this.sessions.length ? this.sessions[this.sessions.length - 1].id : null
} }
}, },
replaceSession(oldId: string, newSession: Session) {
const idx = this.sessions.findIndex(s => s.id === oldId)
if (idx !== -1) {
this.sessions[idx] = newSession
} else {
this.sessions.push(newSession)
}
this.activeSessionId = newSession.id
},
setActive(id: string) { setActive(id: string) {
this.activeSessionId = id this.activeSessionId = id
}, },