Multi-session tabs + home navigation:
- Tab bar with Home button persists above sessions
- Clicking Home shows the underlying page (hosts, vault, etc.)
- Clicking a session tab switches back to that session
- Header nav links also trigger home view
- Sessions stay alive in background when viewing home
Monaco editor in popup window:
- Opening a file in SFTP launches a detached popup with Monaco
- Full syntax highlighting, minimap, Ctrl+S save
- File tree stays visible while editing
- Toolbar with save/close buttons and dirty indicator
Drag-and-drop upload:
- Drop files anywhere on the SFTP sidebar to upload
- Visual overlay with dashed border on drag-over
- Supports multiple files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When replaceSession changed the session ID from pending-XXX to a
real UUID, Vue's :key="session.id" treated it as a new element,
destroyed and recreated TerminalInstance, which called connectToHost
again, got another UUID, replaced again — infinite loop.
Added a stable `key` field to sessions that never changes after
creation, used as the Vue :key instead of the mutable `id`.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>