feat(FN-681): add multi-tab terminal support

- Implemented multi-tab support in TerminalModal component
- Each tab maintains independent WebSocket connection and xterm instance
- Tab management: create, switch, close tabs with keyboard shortcuts (Ctrl+Tab)
- Added Plus button to create new tabs
- Added close button on tabs (except last remaining tab)
- Updated keyboard shortcuts text in status bar
- Connection status indicator per tab
- Per-tab scrollback buffers preserved when switching tabs
- Auto-cleanup of all sessions when modal closes
- Added comprehensive tests for multi-tab functionality
This commit is contained in:
gsxdsm
2026-04-02 09:48:53 -07:00
parent fe7e535edb
commit 0d71541a4d
5 changed files with 435 additions and 45 deletions

View File

@@ -177,13 +177,27 @@ export function CustomModelDropdown({
return optionsList.findIndex((opt) => opt.value === value);
}, [optionsList, value]);
// Estimated max height for dropdown (desktop default: 320px)
// Mobile uses 60-70vh via CSS, but we use 320px as the safe default estimate
const ESTIMATED_DROPDOWN_HEIGHT = 320;
const updateDropdownPosition = useCallback(() => {
const trigger = triggerRef.current;
if (!trigger) return;
const rect = trigger.getBoundingClientRect();
const viewportHeight = window.innerHeight;
// Calculate space below and above the trigger
const spaceBelow = viewportHeight - rect.bottom;
const spaceAbove = rect.top;
// Determine if we should open upward
// Open upward if: not enough space below AND enough space above
const openUpward = spaceBelow < ESTIMATED_DROPDOWN_HEIGHT && spaceAbove >= ESTIMATED_DROPDOWN_HEIGHT;
setDropdownPosition({
top: rect.bottom + 4,
top: openUpward ? rect.top - ESTIMATED_DROPDOWN_HEIGHT - 4 : rect.bottom + 4,
left: rect.left,
width: rect.width,
});