new linter

This commit is contained in:
Peter Steinberger 2025-06-25 11:47:27 +02:00
parent 60d4556a58
commit f59df3c645
18 changed files with 406 additions and 292 deletions

View file

@ -1,103 +1,109 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no"
/>
<title>VibeTunnel - Terminal Multiplexer</title>
<meta name="description" content="Interactive terminal sessions in your browser with real-time streaming and mobile support">
<meta
name="description"
content="Interactive terminal sessions in your browser with real-time streaming and mobile support"
/>
<!-- PWA and mobile optimizations -->
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="theme-color" content="#1e1e1e">
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="theme-color" content="#1e1e1e" />
<!-- Favicon -->
<link rel="icon" href="/favicon.ico">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16.png">
<link rel="icon" href="/favicon.ico" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16.png" />
<!-- Single high-res Apple/Android/Web App icon -->
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<!-- Web App Manifest (important for iOS/Android home screen apps) -->
<link rel="manifest" href="/manifest.json">
<link rel="manifest" href="/manifest.json" />
<!-- Styles -->
<link href="bundle/styles.css" rel="stylesheet">
<link href="bundle/styles.css" rel="stylesheet" />
<!-- Mobile viewport and address bar handling -->
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100vh;
height: calc(var(--vh, 1vh) * 100); /* Dynamic viewport height for mobile */
overscroll-behavior-y: none; /* Prevent pull-to-refresh */
-webkit-overflow-scrolling: touch;
}
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100vh;
height: calc(var(--vh, 1vh) * 100); /* Dynamic viewport height for mobile */
overscroll-behavior-y: none; /* Prevent pull-to-refresh */
-webkit-overflow-scrolling: touch;
}
/* Prevent pull-to-refresh only on specific elements */
body {
-webkit-touch-callout: none;
-webkit-tap-highlight-color: transparent;
}
/* Prevent pull-to-refresh only on specific elements */
body {
-webkit-touch-callout: none;
-webkit-tap-highlight-color: transparent;
}
/* Only disable touch-action on terminal components */
vibe-terminal {
touch-action: none;
}
/* Only disable touch-action on terminal components */
vibe-terminal {
touch-action: none;
}
/* Ensure app takes full viewport */
vibetunnel-app {
display: block;
width: 100%;
min-height: 100%;
}
/* Ensure app takes full viewport */
vibetunnel-app {
display: block;
width: 100%;
min-height: 100%;
}
</style>
<!-- Import Maps -->
<script type="importmap">
{
{
"imports": {
"lit": "https://cdn.skypack.dev/lit",
"lit/": "https://cdn.skypack.dev/lit/"
"lit": "https://cdn.skypack.dev/lit",
"lit/": "https://cdn.skypack.dev/lit/"
}
}
}
</script>
</head>
<body class="m-0 p-0" style="background: black;">
</head>
<body class="m-0 p-0" style="background: black">
<vibetunnel-app></vibetunnel-app>
<!-- Mobile viewport height fix -->
<script>
// Handle dynamic viewport height for mobile browsers
function setViewportHeight() {
const vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);
}
// Handle dynamic viewport height for mobile browsers
function setViewportHeight() {
const vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);
}
// Set initial height
setViewportHeight();
// Set initial height
setViewportHeight();
// Update on resize and orientation change
window.addEventListener('resize', setViewportHeight);
window.addEventListener('orientationchange', () => {
setTimeout(setViewportHeight, 100);
});
// Update on resize and orientation change
window.addEventListener('resize', setViewportHeight);
window.addEventListener('orientationchange', () => {
setTimeout(setViewportHeight, 100);
});
// Force full-screen behavior
window.addEventListener('load', () => {
// Scroll to top to hide address bar
setTimeout(() => {
window.scrollTo(0, 1);
setTimeout(() => window.scrollTo(0, 0), 10);
}, 10);
});
// Force full-screen behavior
window.addEventListener('load', () => {
// Scroll to top to hide address bar
setTimeout(() => {
window.scrollTo(0, 1);
setTimeout(() => window.scrollTo(0, 0), 10);
}, 10);
});
</script>
<script type="module" src="bundle/client-bundle.js"></script>
</body>
</html>
</body>
</html>

View file

@ -1,44 +1,48 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no"
/>
<title>VibeTunnel - System Logs</title>
<meta name="description" content="View and manage VibeTunnel system logs">
<meta name="description" content="View and manage VibeTunnel system logs" />
<!-- Mobile optimizations -->
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="theme-color" content="#1e1e1e">
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="theme-color" content="#1e1e1e" />
<!-- Favicon -->
<link rel="icon" href="/favicon.ico">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16.png">
<link rel="icon" href="/favicon.ico" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16.png" />
<!-- Styles -->
<link href="bundle/styles.css" rel="stylesheet">
<link href="bundle/styles.css" rel="stylesheet" />
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100vh;
overflow: hidden;
}
log-viewer {
display: block;
width: 100vw;
height: 100vh;
}
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100vh;
overflow: hidden;
}
log-viewer {
display: block;
width: 100vw;
height: 100vh;
}
</style>
</head>
<body class="m-0 p-0" style="background: black;">
</head>
<body class="m-0 p-0" style="background: black">
<log-viewer></log-viewer>
<script type="module" src="bundle/client-bundle.js"></script>
</body>
</html>
</body>
</html>

View file

@ -1,71 +1,71 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Simple Monaco Diff Test</title>
<style>
body {
margin: 0;
height: 100vh;
background: #1e1e1e;
}
#container {
width: 100%;
height: 100%;
}
body {
margin: 0;
height: 100vh;
background: #1e1e1e;
}
#container {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
</head>
<body>
<div id="container"></div>
<script src="/monaco-editor/vs/loader.js"></script>
<script>
require.config({ paths: { 'vs': '/monaco-editor/vs' } });
require(['vs/editor/editor.main'], function() {
// Create original content
const originalText = `function hello() {
require.config({ paths: { vs: '/monaco-editor/vs' } });
require(['vs/editor/editor.main'], function () {
// Create original content
const originalText = `function hello() {
console.log("Hello World");
}`;
// Create modified content
const modifiedText = `function hello() {
// Create modified content
const modifiedText = `function hello() {
console.log("Hello VibeTunnel!");
return true;
}`;
// Create the diff editor
const diffEditor = monaco.editor.createDiffEditor(document.getElementById('container'), {
theme: 'vs-dark',
automaticLayout: true,
renderSideBySide: true,
ignoreTrimWhitespace: false,
renderIndicators: true,
originalEditable: false,
diffAlgorithm: 'advanced'
});
// Set the models
const originalModel = monaco.editor.createModel(originalText, 'javascript');
const modifiedModel = monaco.editor.createModel(modifiedText, 'javascript');
diffEditor.setModel({
original: originalModel,
modified: modifiedModel
});
// Log some debug info
console.log('Diff editor created');
console.log('Original content:', originalText);
console.log('Modified content:', modifiedText);
// Check for changes after a delay
setTimeout(() => {
const lineChanges = diffEditor.getLineChanges();
console.log('Line changes:', lineChanges);
}, 1000);
// Create the diff editor
const diffEditor = monaco.editor.createDiffEditor(document.getElementById('container'), {
theme: 'vs-dark',
automaticLayout: true,
renderSideBySide: true,
ignoreTrimWhitespace: false,
renderIndicators: true,
originalEditable: false,
diffAlgorithm: 'advanced',
});
// Set the models
const originalModel = monaco.editor.createModel(originalText, 'javascript');
const modifiedModel = monaco.editor.createModel(modifiedText, 'javascript');
diffEditor.setModel({
original: originalModel,
modified: modifiedModel,
});
// Log some debug info
console.log('Diff editor created');
console.log('Original content:', originalText);
console.log('Modified content:', modifiedText);
// Check for changes after a delay
setTimeout(() => {
const lineChanges = diffEditor.getLineChanges();
console.log('Line changes:', lineChanges);
}, 1000);
});
</script>
</body>
</html>
</body>
</html>

View file

@ -1,23 +1,24 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Monaco Editor Test</title>
<link href="../../bundle/styles.css" rel="stylesheet">
<link href="../../bundle/styles.css" rel="stylesheet" />
<style>
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: #0a0b0d;
color: #e0e0e0;
min-height: 100vh;
}
body {
margin: 0;
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: #0a0b0d;
color: #e0e0e0;
min-height: 100vh;
}
</style>
</head>
<body>
</head>
<body>
<monaco-editor-test></monaco-editor-test>
<script type="module" src="../../bundle/test.js"></script>
</body>
</html>
</body>
</html>

View file

@ -1,23 +1,23 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Terminal Test</title>
<link href="../../bundle/styles.css" rel="stylesheet">
<link href="../../bundle/styles.css" rel="stylesheet" />
<style>
body {
margin: 0;
font-family: 'Hack Nerd Font Mono', 'Fira Code', Consolas, 'Courier New', monospace;
background: #0a0b0d;
color: #e0e0e0;
min-height: 100vh;
}
body {
margin: 0;
font-family: 'Hack Nerd Font Mono', 'Fira Code', Consolas, 'Courier New', monospace;
background: #0a0b0d;
color: #e0e0e0;
min-height: 100vh;
}
</style>
</head>
<body>
</head>
<body>
<terminal-test></terminal-test>
<script type="module" src="../../bundle/test.js"></script>
</body>
</html>
</body>
</html>

View file

@ -110,10 +110,7 @@ describe('AuthLogin', () => {
const authHandler = vi.fn();
const noAuthElement = await fixture<AuthLogin>(html`
<auth-login
.authClient=${mockAuthClient}
@auth-success=${authHandler}>
</auth-login>
<auth-login .authClient=${mockAuthClient} @auth-success=${authHandler}> </auth-login>
`);
// Wait for auto-login

View file

@ -168,7 +168,10 @@ export class AuthLogin extends LitElement {
${
this.error
? html`
<div class="bg-status-error text-dark-bg px-3 py-1.5 rounded mb-3 font-mono text-xs sm:text-sm" data-testid="error-message">
<div
class="bg-status-error text-dark-bg px-3 py-1.5 rounded mb-3 font-mono text-xs sm:text-sm"
data-testid="error-message"
>
${this.error}
<button
@click=${() => {
@ -210,7 +213,10 @@ export class AuthLogin extends LitElement {
<!-- Password Login Section (Primary) -->
<div class="p-5 sm:p-8">
<div class="flex flex-col items-center mb-4 sm:mb-6">
<div class="w-24 h-24 sm:w-28 sm:h-28 rounded-full mb-3 sm:mb-4 overflow-hidden" style="box-shadow: 0 0 25px rgba(124, 230, 161, 0.3);">
<div
class="w-24 h-24 sm:w-28 sm:h-28 rounded-full mb-3 sm:mb-4 overflow-hidden"
style="box-shadow: 0 0 25px rgba(124, 230, 161, 0.3);"
>
${
this.userAvatar
? html`
@ -223,8 +229,14 @@ export class AuthLogin extends LitElement {
/>
`
: html`
<div class="w-full h-full bg-dark-bg-secondary flex items-center justify-center">
<svg class="w-12 h-12 sm:w-14 sm:h-14 text-dark-text-muted" fill="currentColor" viewBox="0 0 20 20">
<div
class="w-full h-full bg-dark-bg-secondary flex items-center justify-center"
>
<svg
class="w-12 h-12 sm:w-14 sm:h-14 text-dark-text-muted"
fill="currentColor"
viewBox="0 0 20 20"
>
<path d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" />
</svg>
</div>
@ -269,7 +281,9 @@ export class AuthLogin extends LitElement {
<!-- Avatar for SSH-only mode -->
<div class="ssh-key-item p-6 sm:p-8">
<div class="flex flex-col items-center mb-4 sm:mb-6">
<div class="w-16 h-16 sm:w-20 sm:h-20 rounded-full mb-2 sm:mb-3 overflow-hidden border-2 border-dark-border">
<div
class="w-16 h-16 sm:w-20 sm:h-20 rounded-full mb-2 sm:mb-3 overflow-hidden border-2 border-dark-border"
>
${
this.userAvatar
? html`
@ -282,8 +296,14 @@ export class AuthLogin extends LitElement {
/>
`
: html`
<div class="w-full h-full bg-dark-bg-secondary flex items-center justify-center">
<svg class="w-8 h-8 sm:w-10 sm:h-10 text-dark-text-muted" fill="currentColor" viewBox="0 0 20 20">
<div
class="w-full h-full bg-dark-bg-secondary flex items-center justify-center"
>
<svg
class="w-8 h-8 sm:w-10 sm:h-10 text-dark-text-muted"
fill="currentColor"
viewBox="0 0 20 20"
>
<path d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" />
</svg>
</div>
@ -326,7 +346,11 @@ export class AuthLogin extends LitElement {
<div class="w-2 h-2 rounded-full bg-accent-green"></div>
<span class="font-mono text-xs sm:text-sm">SSH Key Management</span>
</div>
<button class="btn-ghost text-xs" data-testid="manage-keys" @click=${this.handleShowSSHKeyManager}>
<button
class="btn-ghost text-xs"
data-testid="manage-keys"
@click=${this.handleShowSSHKeyManager}
>
Manage Keys
</button>
</div>

View file

@ -667,10 +667,18 @@ export class FileBrowser extends LitElement {
${
file.isSymlink
? html`
<svg class="w-3 h-3 text-dark-text-muted absolute -bottom-1 -right-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z" clip-rule="evenodd"/>
</svg>
`
<svg
class="w-3 h-3 text-dark-text-muted absolute -bottom-1 -right-1"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fill-rule="evenodd"
d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z"
clip-rule="evenodd"
/>
</svg>
`
: ''
}
</span>
@ -739,10 +747,18 @@ export class FileBrowser extends LitElement {
${
this.selectedFile.isSymlink
? html`
<svg class="w-3 h-3 text-dark-text-muted absolute -bottom-1 -right-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z" clip-rule="evenodd"/>
</svg>
`
<svg
class="w-3 h-3 text-dark-text-muted absolute -bottom-1 -right-1"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fill-rule="evenodd"
d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z"
clip-rule="evenodd"
/>
</svg>
`
: ''
}
</span>

View file

@ -67,7 +67,6 @@ export class FullHeader extends HeaderBase {
</button>
${this.renderUserMenu()}
</div>
</div>
</div>
</div>
@ -101,9 +100,7 @@ export class FullHeader extends HeaderBase {
<div
class="absolute right-0 top-full mt-1 bg-dark-surface border border-dark-border rounded-lg shadow-lg py-1 z-50 min-w-36"
>
<div
class="px-3 py-2 text-sm text-dark-text-muted border-b border-dark-border"
>
<div class="px-3 py-2 text-sm text-dark-text-muted border-b border-dark-border">
${this.authMethod || 'authenticated'}
</div>
<button

View file

@ -46,10 +46,7 @@ describe('SessionCard', () => {
// Create component
element = await fixture<SessionCard>(html`
<session-card
.session=${mockSession}
.authClient=${mockAuthClient}
></session-card>
<session-card .session=${mockSession} .authClient=${mockAuthClient}></session-card>
`);
await element.updateComplete;

View file

@ -227,7 +227,9 @@ export class SessionCard extends LitElement {
? 'shadow-[0_0_0_2px_#00ff88] shadow-glow-green-sm'
: ''
}"
style="view-transition-name: session-${this.session.id}; --session-id: session-${this.session.id}"
style="view-transition-name: session-${this.session.id}; --session-id: session-${
this.session.id
}"
data-session-id="${this.session.id}"
@click=${this.handleCardClick}
>

View file

@ -48,10 +48,7 @@ describe('SessionCreateForm', () => {
// Create component
element = await fixture<SessionCreateForm>(html`
<session-create-form
.authClient=${mockAuthClient}
.visible=${true}
></session-create-form>
<session-create-form .authClient=${mockAuthClient} .visible=${true}></session-create-form>
`);
await element.updateComplete;
@ -81,10 +78,7 @@ describe('SessionCreateForm', () => {
});
const newElement = await fixture<SessionCreateForm>(html`
<session-create-form
.authClient=${mockAuthClient}
.visible=${true}
></session-create-form>
<session-create-form .authClient=${mockAuthClient} .visible=${true}></session-create-form>
`);
expect(newElement.workingDir).toBe('/home/user/projects');

View file

@ -408,9 +408,7 @@ export class SessionCreateForm extends LitElement {
<div class="mb-4 flex items-center justify-between">
<div class="flex-1 pr-4">
<span class="text-dark-text text-sm">Spawn window</span>
<p class="text-xs text-dark-text-muted mt-1">
Opens native terminal window
</p>
<p class="text-xs text-dark-text-muted mt-1">Opens native terminal window</p>
</div>
<button
role="switch"

View file

@ -281,9 +281,19 @@ export class SessionList extends LitElement {
<div class="flex-1 min-w-0">
<div
class="text-sm font-mono text-accent-green truncate"
title="${session.name || (Array.isArray(session.command) ? session.command.join(' ') : session.command)}"
title="${
session.name ||
(Array.isArray(session.command)
? session.command.join(' ')
: session.command)
}"
>
${session.name || (Array.isArray(session.command) ? session.command.join(' ') : session.command)}
${
session.name ||
(Array.isArray(session.command)
? session.command.join(' ')
: session.command)
}
</div>
<div class="text-xs text-dark-text-muted truncate">
${formatPathForDisplay(session.workingDir)}
@ -401,11 +411,18 @@ export class SessionList extends LitElement {
? 'border-dark-border bg-dark-bg-secondary text-dark-text-muted hover:bg-dark-bg-tertiary hover:text-dark-text'
: 'border-dark-border bg-dark-bg-tertiary text-dark-text hover:bg-dark-bg-secondary'
}"
@click=${() => this.dispatchEvent(new CustomEvent('hide-exited-change', { detail: !this.hideExited }))}
@click=${() =>
this.dispatchEvent(
new CustomEvent('hide-exited-change', { detail: !this.hideExited })
)}
>
<div class="flex items-center gap-2 sm:gap-3">
<span class="hidden sm:inline">${this.hideExited ? 'Show' : 'Hide'} Exited (${exitedSessions.length})</span>
<span class="sm:hidden">${this.hideExited ? 'Show' : 'Hide'} (${exitedSessions.length})</span>
<span class="hidden sm:inline"
>${this.hideExited ? 'Show' : 'Hide'} Exited (${exitedSessions.length})</span
>
<span class="sm:hidden"
>${this.hideExited ? 'Show' : 'Hide'} (${exitedSessions.length})</span
>
<div
class="w-8 h-4 rounded-full transition-colors duration-200 ${
this.hideExited ? 'bg-dark-surface' : 'bg-dark-bg'
@ -444,7 +461,11 @@ export class SessionList extends LitElement {
@click=${this.handleCleanupExited}
?disabled=${this.cleaningExited}
>
<span class="hidden sm:inline">${this.cleaningExited ? 'Cleaning...' : `Clean Exited (${exitedSessions.length})`}</span>
<span class="hidden sm:inline"
>${
this.cleaningExited ? 'Cleaning...' : `Clean Exited (${exitedSessions.length})`
}</span
>
<span class="sm:hidden">${this.cleaningExited ? 'Cleaning...' : 'Clean'}</span>
</button>
`

View file

@ -38,9 +38,7 @@ describe('SessionView', () => {
fetchMock = setupFetchMock();
// Create component
element = await fixture<SessionView>(html`
<session-view></session-view>
`);
element = await fixture<SessionView>(html` <session-view></session-view> `);
await element.updateComplete;
});
@ -69,9 +67,7 @@ describe('SessionView', () => {
configurable: true,
});
const mobileElement = await fixture<SessionView>(html`
<session-view></session-view>
`);
const mobileElement = await fixture<SessionView>(html` <session-view></session-view> `);
await mobileElement.updateComplete;

View file

@ -143,11 +143,13 @@ export class SidebarHeader extends HeaderBase {
${
this.killingAll
? html`
<div class="flex items-center justify-center gap-2">
<div class="w-3 h-3 border-2 border-dark-bg border-t-transparent rounded-full animate-spin"></div>
<span>Killing...</span>
</div>
`
<div class="flex items-center justify-center gap-2">
<div
class="w-3 h-3 border-2 border-dark-bg border-t-transparent rounded-full animate-spin"
></div>
<span>Killing...</span>
</div>
`
: `Kill All (${runningSessions.length})`
}
</button>
@ -165,7 +167,9 @@ export class SidebarHeader extends HeaderBase {
title="User menu"
>
<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor">
<path d="M10 0C4.48 0 0 4.48 0 10s4.48 10 10 10 10-4.48 10-10S15.52 0 10 0zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"/>
<path
d="M10 0C4.48 0 0 4.48 0 10s4.48 10 10 10 10-4.48 10-10S15.52 0 10 0zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"
/>
</svg>
</button>
${

View file

@ -82,12 +82,7 @@ describe('Terminal', () => {
it('should handle custom dimensions', async () => {
const customElement = await fixture<Terminal>(html`
<vibe-terminal
session-id="test-789"
cols="120"
rows="40"
font-size="16">
</vibe-terminal>
<vibe-terminal session-id="test-789" cols="120" rows="40" font-size="16"> </vibe-terminal>
`);
await customElement.updateComplete;

View file

@ -165,7 +165,7 @@
min-height: 100vh;
min-height: 100dvh; /* Dynamic viewport height for mobile */
}
@media (max-width: 768px) {
.auth-container {
@apply py-6; /* Comfortable vertical padding on mobile */
@ -281,7 +281,7 @@ body {
height: 100vh;
height: -webkit-fill-available;
}
/* Prevent rubber band scrolling in split view */
.ios-split-view {
position: fixed;
@ -292,7 +292,7 @@ body {
overflow: hidden;
-webkit-overflow-scrolling: auto;
}
.ios-split-view > * {
-webkit-overflow-scrolling: touch;
}
@ -341,30 +341,66 @@ body.sessions-showing .session-flex-responsive > session-card {
}
/* Stagger animation when showing exited sessions */
body.sessions-showing .session-flex-responsive > session-card:nth-child(1) { animation-delay: 0s; }
body.sessions-showing .session-flex-responsive > session-card:nth-child(2) { animation-delay: 0.05s; }
body.sessions-showing .session-flex-responsive > session-card:nth-child(3) { animation-delay: 0.1s; }
body.sessions-showing .session-flex-responsive > session-card:nth-child(4) { animation-delay: 0.15s; }
body.sessions-showing .session-flex-responsive > session-card:nth-child(5) { animation-delay: 0.2s; }
body.sessions-showing .session-flex-responsive > session-card:nth-child(6) { animation-delay: 0.25s; }
body.sessions-showing .session-flex-responsive > session-card:nth-child(7) { animation-delay: 0.3s; }
body.sessions-showing .session-flex-responsive > session-card:nth-child(8) { animation-delay: 0.35s; }
body.sessions-showing .session-flex-responsive > session-card:nth-child(n+9) { animation-delay: 0.4s; }
body.sessions-showing .session-flex-responsive > session-card:nth-child(1) {
animation-delay: 0s;
}
body.sessions-showing .session-flex-responsive > session-card:nth-child(2) {
animation-delay: 0.05s;
}
body.sessions-showing .session-flex-responsive > session-card:nth-child(3) {
animation-delay: 0.1s;
}
body.sessions-showing .session-flex-responsive > session-card:nth-child(4) {
animation-delay: 0.15s;
}
body.sessions-showing .session-flex-responsive > session-card:nth-child(5) {
animation-delay: 0.2s;
}
body.sessions-showing .session-flex-responsive > session-card:nth-child(6) {
animation-delay: 0.25s;
}
body.sessions-showing .session-flex-responsive > session-card:nth-child(7) {
animation-delay: 0.3s;
}
body.sessions-showing .session-flex-responsive > session-card:nth-child(8) {
animation-delay: 0.35s;
}
body.sessions-showing .session-flex-responsive > session-card:nth-child(n + 9) {
animation-delay: 0.4s;
}
/* Compact mode animations */
body.sessions-showing .space-y-2 > div {
animation: sessionFlow 0.4s ease-out backwards;
}
body.sessions-showing .space-y-2 > div:nth-child(1) { animation-delay: 0s; }
body.sessions-showing .space-y-2 > div:nth-child(2) { animation-delay: 0.05s; }
body.sessions-showing .space-y-2 > div:nth-child(3) { animation-delay: 0.1s; }
body.sessions-showing .space-y-2 > div:nth-child(4) { animation-delay: 0.15s; }
body.sessions-showing .space-y-2 > div:nth-child(5) { animation-delay: 0.2s; }
body.sessions-showing .space-y-2 > div:nth-child(6) { animation-delay: 0.25s; }
body.sessions-showing .space-y-2 > div:nth-child(7) { animation-delay: 0.3s; }
body.sessions-showing .space-y-2 > div:nth-child(8) { animation-delay: 0.35s; }
body.sessions-showing .space-y-2 > div:nth-child(n+9) { animation-delay: 0.4s; }
body.sessions-showing .space-y-2 > div:nth-child(1) {
animation-delay: 0s;
}
body.sessions-showing .space-y-2 > div:nth-child(2) {
animation-delay: 0.05s;
}
body.sessions-showing .space-y-2 > div:nth-child(3) {
animation-delay: 0.1s;
}
body.sessions-showing .space-y-2 > div:nth-child(4) {
animation-delay: 0.15s;
}
body.sessions-showing .space-y-2 > div:nth-child(5) {
animation-delay: 0.2s;
}
body.sessions-showing .space-y-2 > div:nth-child(6) {
animation-delay: 0.25s;
}
body.sessions-showing .space-y-2 > div:nth-child(7) {
animation-delay: 0.3s;
}
body.sessions-showing .space-y-2 > div:nth-child(8) {
animation-delay: 0.35s;
}
body.sessions-showing .space-y-2 > div:nth-child(n + 9) {
animation-delay: 0.4s;
}
@keyframes sessionFlow {
from {
@ -393,15 +429,33 @@ body.initial-session-load .session-flex-responsive > session-card {
}
/* Stagger animation for initial load */
body.initial-session-load .session-flex-responsive > session-card:nth-child(1) { animation-delay: 0s; }
body.initial-session-load .session-flex-responsive > session-card:nth-child(2) { animation-delay: 0.05s; }
body.initial-session-load .session-flex-responsive > session-card:nth-child(3) { animation-delay: 0.1s; }
body.initial-session-load .session-flex-responsive > session-card:nth-child(4) { animation-delay: 0.15s; }
body.initial-session-load .session-flex-responsive > session-card:nth-child(5) { animation-delay: 0.2s; }
body.initial-session-load .session-flex-responsive > session-card:nth-child(6) { animation-delay: 0.25s; }
body.initial-session-load .session-flex-responsive > session-card:nth-child(7) { animation-delay: 0.3s; }
body.initial-session-load .session-flex-responsive > session-card:nth-child(8) { animation-delay: 0.35s; }
body.initial-session-load .session-flex-responsive > session-card:nth-child(n+9) { animation-delay: 0.4s; }
body.initial-session-load .session-flex-responsive > session-card:nth-child(1) {
animation-delay: 0s;
}
body.initial-session-load .session-flex-responsive > session-card:nth-child(2) {
animation-delay: 0.05s;
}
body.initial-session-load .session-flex-responsive > session-card:nth-child(3) {
animation-delay: 0.1s;
}
body.initial-session-load .session-flex-responsive > session-card:nth-child(4) {
animation-delay: 0.15s;
}
body.initial-session-load .session-flex-responsive > session-card:nth-child(5) {
animation-delay: 0.2s;
}
body.initial-session-load .session-flex-responsive > session-card:nth-child(6) {
animation-delay: 0.25s;
}
body.initial-session-load .session-flex-responsive > session-card:nth-child(7) {
animation-delay: 0.3s;
}
body.initial-session-load .session-flex-responsive > session-card:nth-child(8) {
animation-delay: 0.35s;
}
body.initial-session-load .session-flex-responsive > session-card:nth-child(n + 9) {
animation-delay: 0.4s;
}
/* View transition specific styles for initial load */
::view-transition-old(root) {
@ -417,13 +471,21 @@ body.initial-session-load ::view-transition-new(root) {
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fade-out {
from { opacity: 1; }
to { opacity: 0; }
from {
opacity: 1;
}
to {
opacity: 0;
}
}
/* View transition names for smoother morphing */