diff --git a/web/src/client/app.ts b/web/src/client/app.ts index 769dd858..b20f653b 100644 --- a/web/src/client/app.ts +++ b/web/src/client/app.ts @@ -113,6 +113,11 @@ export class VibeTunnelApp extends LitElement { this.hasActiveOverlay = this.showFileBrowser || this.showCreateModal || this.showSSHKeyManager || this.showSettings; } + + // Force re-render when sessions change or view changes to update log button position + if (changedProperties.has('sessions') || changedProperties.has('currentView')) { + this.requestUpdate(); + } } disconnectedCallback() { @@ -1155,6 +1160,54 @@ export class VibeTunnelApp extends LitElement { return /iPad|iPhone|iPod/.test(navigator.userAgent) && !('MSStream' in window); } + private getLogButtonPosition(): string { + // Check if we're in grid view and not in split view + const isGridView = !this.showSplitView && this.currentView === 'list'; + + if (isGridView) { + // Calculate if we need to move the button up + const runningSessions = this.sessions.filter((s) => s.status === 'running'); + const viewportHeight = window.innerHeight; + + // Grid layout: auto-fill with 360px min width, 400px height, 1.25rem gap + const gridItemHeight = 400; + const gridGap = 20; // 1.25rem + const containerPadding = 16; // Approximate padding + const headerHeight = 200; // Approximate header + controls height + + // Calculate available height for grid + const availableHeight = viewportHeight - headerHeight; + + // Calculate how many rows can fit + const rowsCanFit = Math.floor( + (availableHeight - containerPadding) / (gridItemHeight + gridGap) + ); + + // Calculate grid columns based on viewport width + const viewportWidth = window.innerWidth; + const gridItemMinWidth = 360; + const sidebarWidth = this.sidebarCollapsed + ? 0 + : this.mediaState.isMobile + ? 0 + : this.sidebarWidth; + const availableWidth = viewportWidth - sidebarWidth - containerPadding * 2; + const columnsCanFit = Math.floor(availableWidth / (gridItemMinWidth + gridGap)); + + // Calculate total items that can fit in viewport + const itemsInViewport = rowsCanFit * columnsCanFit; + + // If we have more running sessions than can fit in viewport, items will be at bottom + if (runningSessions.length >= itemsInViewport && itemsInViewport > 0) { + // Move button up to avoid overlapping with kill buttons + return 'bottom-20'; // ~80px up + } + } + + // Default position with equal margins + return 'bottom-4'; + } + private get isInSidebarDismissMode(): boolean { if (!this.mediaState.isMobile || !this.shouldShowMobileOverlay) return false; @@ -1372,13 +1425,13 @@ export class VibeTunnelApp extends LitElement { @error=${this.handleError} > - + ${ this.showLogLink ? html` -
+
Logs - v${VERSION} + v${VERSION}
` : '' diff --git a/web/src/client/components/auth-login.ts b/web/src/client/components/auth-login.ts index 31904158..8fbd4698 100644 --- a/web/src/client/components/auth-login.ts +++ b/web/src/client/components/auth-login.ts @@ -176,7 +176,7 @@ export class AuthLogin extends LitElement {

VibeTunnel

Please authenticate to continue

@@ -233,7 +233,7 @@ export class AuthLogin extends LitElement {
${ this.userAvatar @@ -361,7 +361,7 @@ export class AuthLogin extends LitElement {
-
+
SSH Key Management
` } -
+
-
+
-
- +
+ -
- +
+ -
- -
+
+ +
-
+
- Spawn window -

Opens native terminal window

+ Spawn window +

Opens native terminal window

-
+
- Terminal Title Mode -

+ Terminal Title Mode +

${this.getTitleModeDescription()}

@@ -489,14 +494,14 @@ export class SessionCreateForm extends LitElement {
@@ -507,8 +512,8 @@ export class SessionCreateForm extends LitElement {
-
-
-
+
+
+
+ ` + : html` + + + + ` + } + ` + )} +
+
+ ` + : '' + } ` } +
${this.renderExitedControls()}
@@ -427,45 +574,25 @@ export class SessionList extends LitElement { if (exitedSessions.length === 0 && runningSessions.length === 0) return ''; return html` -
- +
+ ${ exitedSessions.length > 0 ? html` -
@@ -473,23 +600,15 @@ export class SessionList extends LitElement { !this.hideExited ? html` ` : '' } -
` : '' } @@ -499,10 +618,10 @@ export class SessionList extends LitElement { runningSessions.length > 0 ? html` ` : '' diff --git a/web/src/client/components/session-view.ts b/web/src/client/components/session-view.ts index 334bce2f..6cd87d8b 100644 --- a/web/src/client/components/session-view.ts +++ b/web/src/client/components/session-view.ts @@ -761,7 +761,16 @@ export class SessionView extends LitElement { } private handleOpenFilePicker() { - this.showImagePicker = true; + if (!this.isMobile) { + // On desktop, directly open the file picker without showing the dialog + const filePicker = this.querySelector('file-picker') as FilePicker | null; + if (filePicker && typeof filePicker.openFilePicker === 'function') { + filePicker.openFilePicker(); + } + } else { + // On mobile, show the file picker dialog + this.showImagePicker = true; + } } private handleCloseFilePicker() { @@ -1028,12 +1037,12 @@ export class SessionView extends LitElement { box-shadow: none !important; } session-view:focus { - outline: 2px solid #00ff88 !important; + outline: 2px solid rgb(16 185 129) !important; outline-offset: -2px; }
@@ -1066,7 +1075,7 @@ export class SessionView extends LitElement {
-
-
📁
-
Drop files here
-
Files will be uploaded and the path sent to terminal
-
Or press CMD+V to paste from clipboard
+
+
+
+
+ + + +
+
+
+

Drop files here

+

Files will be uploaded and the path sent to terminal

+
+ Or press + ⌘V + to paste from clipboard +
` diff --git a/web/src/client/components/session-view/ctrl-alpha-overlay.ts b/web/src/client/components/session-view/ctrl-alpha-overlay.ts index 47421aff..fadc6174 100644 --- a/web/src/client/components/session-view/ctrl-alpha-overlay.ts +++ b/web/src/client/components/session-view/ctrl-alpha-overlay.ts @@ -49,10 +49,10 @@ export class CtrlAlphaOverlay extends LitElement { style="background: black; border: 1px solid #569cd6; border-radius: 8px; padding: 10px; margin-bottom: ${this.keyboardHeight > 0 ? `${this.keyboardHeight + 180}px` : 'calc(env(keyboard-inset-height, 0px) + 180px)'};/* 180px = estimated quick keyboard height (3 rows) */" @click=${(e: Event) => e.stopPropagation()} > -
Ctrl + Key
+
Ctrl + Key
-
+
Build sequences like ctrl+c ctrl+c
@@ -60,9 +60,9 @@ export class CtrlAlphaOverlay extends LitElement { ${ this.ctrlSequence.length > 0 ? html` -
-
Current sequence:
-
+
+
Current sequence:
+
${this.ctrlSequence.map((letter) => `Ctrl+${letter}`).join(' ')}
@@ -112,7 +112,7 @@ export class CtrlAlphaOverlay extends LitElement {
-
+
Common: C=interrupt, X=exit, O=save, W=search
diff --git a/web/src/client/components/session-view/session-header.ts b/web/src/client/components/session-view/session-header.ts index 722a2a1c..40651d31 100644 --- a/web/src/client/components/session-view/session-header.ts +++ b/web/src/client/components/session-view/session-header.ts @@ -77,8 +77,8 @@ export class SessionHeader extends LitElement { return html`
@@ -118,7 +118,7 @@ export class SessionHeader extends LitElement { this.showBackButton ? html` ` )} -
-
Custom (20-500)
-
+
+
Custom (20-500)
+
e.stopPropagation()} - class="flex-1 bg-dark-bg border border-dark-border rounded px-2 py-1 text-xs font-mono text-dark-text" + class="flex-1 bg-dark-bg-secondary border border-dark-border rounded-md px-3 py-2 text-sm font-mono text-dark-text placeholder:text-dark-text-dim focus:border-accent-primary focus:shadow-glow-primary-sm transition-all" />
-
-
Font Size
-
+
+
Font Size
+
- + ${this.terminalFontSize}px - -
+ +
this.dispatchEvent(new CustomEvent('open-settings'))} @@ -63,7 +63,7 @@ export class SidebarHeader extends HeaderBase { - +