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

View file

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

View file

@ -1,71 +1,71 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Simple Monaco Diff Test</title> <title>Simple Monaco Diff Test</title>
<style> <style>
body { body {
margin: 0; margin: 0;
height: 100vh; height: 100vh;
background: #1e1e1e; background: #1e1e1e;
} }
#container { #container {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
</style> </style>
</head> </head>
<body> <body>
<div id="container"></div> <div id="container"></div>
<script src="/monaco-editor/vs/loader.js"></script> <script src="/monaco-editor/vs/loader.js"></script>
<script> <script>
require.config({ paths: { 'vs': '/monaco-editor/vs' } }); require.config({ paths: { vs: '/monaco-editor/vs' } });
require(['vs/editor/editor.main'], function() { require(['vs/editor/editor.main'], function () {
// Create original content // Create original content
const originalText = `function hello() { const originalText = `function hello() {
console.log("Hello World"); console.log("Hello World");
}`; }`;
// Create modified content // Create modified content
const modifiedText = `function hello() { const modifiedText = `function hello() {
console.log("Hello VibeTunnel!"); console.log("Hello VibeTunnel!");
return true; return true;
}`; }`;
// Create the diff editor // Create the diff editor
const diffEditor = monaco.editor.createDiffEditor(document.getElementById('container'), { const diffEditor = monaco.editor.createDiffEditor(document.getElementById('container'), {
theme: 'vs-dark', theme: 'vs-dark',
automaticLayout: true, automaticLayout: true,
renderSideBySide: true, renderSideBySide: true,
ignoreTrimWhitespace: false, ignoreTrimWhitespace: false,
renderIndicators: true, renderIndicators: true,
originalEditable: false, originalEditable: false,
diffAlgorithm: 'advanced' 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);
}); });
// 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> </script>
</body> </body>
</html> </html>

View file

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

View file

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

View file

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

View file

@ -168,7 +168,10 @@ export class AuthLogin extends LitElement {
${ ${
this.error this.error
? html` ? 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} ${this.error}
<button <button
@click=${() => { @click=${() => {
@ -210,7 +213,10 @@ export class AuthLogin extends LitElement {
<!-- Password Login Section (Primary) --> <!-- Password Login Section (Primary) -->
<div class="p-5 sm:p-8"> <div class="p-5 sm:p-8">
<div class="flex flex-col items-center mb-4 sm:mb-6"> <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 this.userAvatar
? html` ? html`
@ -223,8 +229,14 @@ export class AuthLogin extends LitElement {
/> />
` `
: html` : html`
<div class="w-full h-full bg-dark-bg-secondary flex items-center justify-center"> <div
<svg class="w-12 h-12 sm:w-14 sm:h-14 text-dark-text-muted" fill="currentColor" viewBox="0 0 20 20"> 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" /> <path d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" />
</svg> </svg>
</div> </div>
@ -269,7 +281,9 @@ export class AuthLogin extends LitElement {
<!-- Avatar for SSH-only mode --> <!-- Avatar for SSH-only mode -->
<div class="ssh-key-item p-6 sm:p-8"> <div class="ssh-key-item p-6 sm:p-8">
<div class="flex flex-col items-center mb-4 sm:mb-6"> <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 this.userAvatar
? html` ? html`
@ -282,8 +296,14 @@ export class AuthLogin extends LitElement {
/> />
` `
: html` : html`
<div class="w-full h-full bg-dark-bg-secondary flex items-center justify-center"> <div
<svg class="w-8 h-8 sm:w-10 sm:h-10 text-dark-text-muted" fill="currentColor" viewBox="0 0 20 20"> 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" /> <path d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" />
</svg> </svg>
</div> </div>
@ -326,7 +346,11 @@ export class AuthLogin extends LitElement {
<div class="w-2 h-2 rounded-full bg-accent-green"></div> <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> <span class="font-mono text-xs sm:text-sm">SSH Key Management</span>
</div> </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 Manage Keys
</button> </button>
</div> </div>

View file

@ -667,10 +667,18 @@ export class FileBrowser extends LitElement {
${ ${
file.isSymlink file.isSymlink
? html` ? html`
<svg class="w-3 h-3 text-dark-text-muted absolute -bottom-1 -right-1" fill="currentColor" viewBox="0 0 20 20"> <svg
<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"/> class="w-3 h-3 text-dark-text-muted absolute -bottom-1 -right-1"
</svg> 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> </span>
@ -739,10 +747,18 @@ export class FileBrowser extends LitElement {
${ ${
this.selectedFile.isSymlink this.selectedFile.isSymlink
? html` ? html`
<svg class="w-3 h-3 text-dark-text-muted absolute -bottom-1 -right-1" fill="currentColor" viewBox="0 0 20 20"> <svg
<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"/> class="w-3 h-3 text-dark-text-muted absolute -bottom-1 -right-1"
</svg> 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> </span>

View file

@ -67,7 +67,6 @@ export class FullHeader extends HeaderBase {
</button> </button>
${this.renderUserMenu()} ${this.renderUserMenu()}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@ -101,9 +100,7 @@ export class FullHeader extends HeaderBase {
<div <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" 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 <div class="px-3 py-2 text-sm text-dark-text-muted border-b border-dark-border">
class="px-3 py-2 text-sm text-dark-text-muted border-b border-dark-border"
>
${this.authMethod || 'authenticated'} ${this.authMethod || 'authenticated'}
</div> </div>
<button <button

View file

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

View file

@ -227,7 +227,9 @@ export class SessionCard extends LitElement {
? 'shadow-[0_0_0_2px_#00ff88] shadow-glow-green-sm' ? '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}" data-session-id="${this.session.id}"
@click=${this.handleCardClick} @click=${this.handleCardClick}
> >

View file

@ -48,10 +48,7 @@ describe('SessionCreateForm', () => {
// Create component // Create component
element = await fixture<SessionCreateForm>(html` element = await fixture<SessionCreateForm>(html`
<session-create-form <session-create-form .authClient=${mockAuthClient} .visible=${true}></session-create-form>
.authClient=${mockAuthClient}
.visible=${true}
></session-create-form>
`); `);
await element.updateComplete; await element.updateComplete;
@ -81,10 +78,7 @@ describe('SessionCreateForm', () => {
}); });
const newElement = await fixture<SessionCreateForm>(html` const newElement = await fixture<SessionCreateForm>(html`
<session-create-form <session-create-form .authClient=${mockAuthClient} .visible=${true}></session-create-form>
.authClient=${mockAuthClient}
.visible=${true}
></session-create-form>
`); `);
expect(newElement.workingDir).toBe('/home/user/projects'); 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="mb-4 flex items-center justify-between">
<div class="flex-1 pr-4"> <div class="flex-1 pr-4">
<span class="text-dark-text text-sm">Spawn window</span> <span class="text-dark-text text-sm">Spawn window</span>
<p class="text-xs text-dark-text-muted mt-1"> <p class="text-xs text-dark-text-muted mt-1">Opens native terminal window</p>
Opens native terminal window
</p>
</div> </div>
<button <button
role="switch" role="switch"

View file

@ -281,9 +281,19 @@ export class SessionList extends LitElement {
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
<div <div
class="text-sm font-mono text-accent-green truncate" 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>
<div class="text-xs text-dark-text-muted truncate"> <div class="text-xs text-dark-text-muted truncate">
${formatPathForDisplay(session.workingDir)} ${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-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' : '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"> <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="hidden sm:inline"
<span class="sm:hidden">${this.hideExited ? 'Show' : 'Hide'} (${exitedSessions.length})</span> >${this.hideExited ? 'Show' : 'Hide'} Exited (${exitedSessions.length})</span
>
<span class="sm:hidden"
>${this.hideExited ? 'Show' : 'Hide'} (${exitedSessions.length})</span
>
<div <div
class="w-8 h-4 rounded-full transition-colors duration-200 ${ class="w-8 h-4 rounded-full transition-colors duration-200 ${
this.hideExited ? 'bg-dark-surface' : 'bg-dark-bg' this.hideExited ? 'bg-dark-surface' : 'bg-dark-bg'
@ -444,7 +461,11 @@ export class SessionList extends LitElement {
@click=${this.handleCleanupExited} @click=${this.handleCleanupExited}
?disabled=${this.cleaningExited} ?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> <span class="sm:hidden">${this.cleaningExited ? 'Cleaning...' : 'Clean'}</span>
</button> </button>
` `

View file

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

View file

@ -143,11 +143,13 @@ export class SidebarHeader extends HeaderBase {
${ ${
this.killingAll this.killingAll
? html` ? html`
<div class="flex items-center justify-center gap-2"> <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> <div
<span>Killing...</span> class="w-3 h-3 border-2 border-dark-bg border-t-transparent rounded-full animate-spin"
</div> ></div>
` <span>Killing...</span>
</div>
`
: `Kill All (${runningSessions.length})` : `Kill All (${runningSessions.length})`
} }
</button> </button>
@ -165,7 +167,9 @@ export class SidebarHeader extends HeaderBase {
title="User menu" title="User menu"
> >
<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"> <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> </svg>
</button> </button>
${ ${

View file

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

View file

@ -341,30 +341,66 @@ body.sessions-showing .session-flex-responsive > session-card {
} }
/* Stagger animation when showing exited sessions */ /* 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(1) {
body.sessions-showing .session-flex-responsive > session-card:nth-child(2) { animation-delay: 0.05s; } animation-delay: 0s;
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(2) {
body.sessions-showing .session-flex-responsive > session-card:nth-child(5) { animation-delay: 0.2s; } animation-delay: 0.05s;
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(3) {
body.sessions-showing .session-flex-responsive > session-card:nth-child(8) { animation-delay: 0.35s; } animation-delay: 0.1s;
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(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 */ /* Compact mode animations */
body.sessions-showing .space-y-2 > div { body.sessions-showing .space-y-2 > div {
animation: sessionFlow 0.4s ease-out backwards; 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(1) {
body.sessions-showing .space-y-2 > div:nth-child(2) { animation-delay: 0.05s; } animation-delay: 0s;
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(2) {
body.sessions-showing .space-y-2 > div:nth-child(5) { animation-delay: 0.2s; } animation-delay: 0.05s;
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(3) {
body.sessions-showing .space-y-2 > div:nth-child(8) { animation-delay: 0.35s; } animation-delay: 0.1s;
body.sessions-showing .space-y-2 > div:nth-child(n+9) { animation-delay: 0.4s; } }
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 { @keyframes sessionFlow {
from { from {
@ -393,15 +429,33 @@ body.initial-session-load .session-flex-responsive > session-card {
} }
/* Stagger animation for initial load */ /* 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(1) {
body.initial-session-load .session-flex-responsive > session-card:nth-child(2) { animation-delay: 0.05s; } animation-delay: 0s;
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(2) {
body.initial-session-load .session-flex-responsive > session-card:nth-child(5) { animation-delay: 0.2s; } animation-delay: 0.05s;
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(3) {
body.initial-session-load .session-flex-responsive > session-card:nth-child(8) { animation-delay: 0.35s; } animation-delay: 0.1s;
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(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 specific styles for initial load */
::view-transition-old(root) { ::view-transition-old(root) {
@ -417,13 +471,21 @@ body.initial-session-load ::view-transition-new(root) {
} }
@keyframes fade-in { @keyframes fade-in {
from { opacity: 0; } from {
to { opacity: 1; } opacity: 0;
}
to {
opacity: 1;
}
} }
@keyframes fade-out { @keyframes fade-out {
from { opacity: 1; } from {
to { opacity: 0; } opacity: 1;
}
to {
opacity: 0;
}
} }
/* View transition names for smoother morphing */ /* View transition names for smoother morphing */