diff --git a/web/src/client/components/session-list.ts b/web/src/client/components/session-list.ts
index 154f3ae8..38f7e979 100644
--- a/web/src/client/components/session-list.ts
+++ b/web/src/client/components/session-list.ts
@@ -145,8 +145,9 @@ export class SessionList extends LitElement {
const session = this.sessions.find(s => s.id === sessionId);
if (!session) return;
- // Create renderer with smaller dimensions and font for preview
- const renderer = new Renderer(playerElement, 40, 12, 10000, 8); // 40x12 chars, 8px font
+ // Create renderer with smaller dimensions for preview
+ // Use responsive font sizing, starting with smaller font for previews
+ const renderer = new Renderer(playerElement, 40, 12, 10000, 6, true); // 40x12 chars, 6px base font, isPreview=true
this.renderers.set(sessionId, renderer);
// Terminal is already configured with disableStdin: true in renderer constructor
@@ -159,6 +160,11 @@ export class SessionList extends LitElement {
// Let the renderer handle the URL
await renderer.loadFromUrl(url, isStream);
+
+ // Disable pointer events so clicks pass through to the card (after terminal is rendered)
+ requestAnimationFrame(() => {
+ renderer.setPointerEventsEnabled(false);
+ });
} catch (error) {
console.error('Error creating renderer:', error);
}
diff --git a/web/src/client/components/session-view.ts b/web/src/client/components/session-view.ts
index 01bb2e9c..f634ef45 100644
--- a/web/src/client/components/session-view.ts
+++ b/web/src/client/components/session-view.ts
@@ -107,6 +107,17 @@ export class SessionView extends LitElement {
if (changedProperties.has('session') && this.session) {
this.createInteractiveTerminal();
+ // Adjust terminal spacing after creating terminal
+ requestAnimationFrame(() => {
+ this.adjustTerminalForMobileButtons();
+ });
+ }
+
+ // Adjust terminal height for mobile buttons after render
+ if (changedProperties.has('showMobileInput') || changedProperties.has('isMobile')) {
+ requestAnimationFrame(() => {
+ this.adjustTerminalForMobileButtons();
+ });
}
}
@@ -230,13 +241,13 @@ export class SessionView extends LitElement {
this.showMobileInput = !this.showMobileInput;
if (this.showMobileInput) {
// Focus the textarea after a short delay to ensure it's rendered
- setTimeout(() => {
+ requestAnimationFrame(() => {
const textarea = this.querySelector('#mobile-input-textarea') as HTMLTextAreaElement;
if (textarea) {
textarea.focus();
this.adjustTextareaForKeyboard();
}
- }, 100);
+ });
} else {
// Clean up viewport listener when closing overlay
const textarea = this.querySelector('#mobile-input-textarea') as HTMLTextAreaElement;
@@ -315,7 +326,7 @@ export class SessionView extends LitElement {
}
// Initial adjustment
- setTimeout(adjustLayout, 300);
+ requestAnimationFrame(adjustLayout);
}
private handleMobileInputChange(e: Event) {
@@ -403,6 +414,11 @@ export class SessionView extends LitElement {
}
}
+ private adjustTerminalForMobileButtons() {
+ // Disabled for now to avoid viewport issues
+ // The mobile buttons will overlay the terminal
+ }
+
private startSessionStatusPolling() {
if (this.sessionStatusInterval) {
clearInterval(this.sessionStatusInterval);
@@ -468,7 +484,7 @@ export class SessionView extends LitElement {
box-shadow: none !important;
}
-
+
@@ -479,14 +495,11 @@ export class SessionView extends LitElement {
BACK
-
${this.session.command}
-
(${this.session.id.substring(0, 8)}...)
+
${this.session.command}
+
${this.session.workingDir}
-
- ${this.session.workingDir}
-
${this.session.status.toUpperCase()}
@@ -494,15 +507,13 @@ export class SessionView extends LitElement {
-
+
- ${this.isMobile ? html`
-
- ${!this.showMobileInput ? html`
-
+ ${this.isMobile && !this.showMobileInput ? html`
+
- ` : ''}
+
+ ` : ''}
-
- ${this.showMobileInput ? html`
+
+ ${this.isMobile && this.showMobileInput ? html`
@@ -627,7 +638,6 @@ export class SessionView extends LitElement {
- ` : ''}
` : ''}
`;
diff --git a/web/src/client/renderer.ts b/web/src/client/renderer.ts
index eceae76c..e30099de 100644
--- a/web/src/client/renderer.ts
+++ b/web/src/client/renderer.ts
@@ -24,10 +24,12 @@ export class Renderer {
private terminal: Terminal;
private fitAddon: FitAddon;
private webLinksAddon: WebLinksAddon;
+ private isPreview: boolean;
- constructor(container: HTMLElement, width: number = 80, height: number = 20, scrollback: number = 1000000, fontSize: number = 14) {
+ constructor(container: HTMLElement, width: number = 80, height: number = 20, scrollback: number = 1000000, fontSize: number = 14, isPreview: boolean = false) {
this.container = container;
-
+ this.isPreview = isPreview;
+
// Create terminal with options similar to the custom renderer
this.terminal = new Terminal({
cols: width,
@@ -65,13 +67,13 @@ export class Renderer {
convertEol: true,
altClickMovesCursor: false,
rightClickSelectsWord: false,
- disableStdin: true // We handle input separately
+ disableStdin: true, // We handle input separately
});
// Add addons
this.fitAddon = new FitAddon();
this.webLinksAddon = new WebLinksAddon();
-
+
this.terminal.loadAddon(this.fitAddon);
this.terminal.loadAddon(this.webLinksAddon);
@@ -84,7 +86,7 @@ export class Renderer {
this.container.style.padding = '10px';
this.container.style.backgroundColor = '#1e1e1e';
this.container.style.overflow = 'hidden';
-
+
// Create terminal wrapper
const terminalWrapper = document.createElement('div');
terminalWrapper.style.width = '100%';
@@ -93,10 +95,10 @@ export class Renderer {
// Open terminal in the wrapper
this.terminal.open(terminalWrapper);
-
- // Fit terminal to container
+
+ // Just use FitAddon
this.fitAddon.fit();
-
+
// Handle container resize
const resizeObserver = new ResizeObserver(() => {
this.fitAddon.fit();
@@ -104,6 +106,7 @@ export class Renderer {
resizeObserver.observe(this.container);
}
+
// Public API methods - maintain compatibility with custom renderer
async loadCastFile(url: string): Promise
{
@@ -115,16 +118,16 @@ export class Renderer {
parseCastFile(content: string): void {
const lines = content.trim().split('\n');
let header: CastHeader | null = null;
-
+
// Clear terminal
this.terminal.clear();
-
+
for (const line of lines) {
if (!line.trim()) continue;
-
+
try {
const parsed = JSON.parse(line);
-
+
if (parsed.version && parsed.width && parsed.height) {
// Header
header = parsed;
@@ -136,7 +139,7 @@ export class Renderer {
type: parsed[1],
data: parsed[2]
};
-
+
if (event.type === 'o') {
this.processOutput(event.data);
} else if (event.type === 'r') {
@@ -173,11 +176,8 @@ export class Renderer {
}
resize(width: number, height: number): void {
- this.terminal.resize(width, height);
- // Fit addon will handle the visual resize
- setTimeout(() => {
- this.fitAddon.fit();
- }, 0);
+ // Ignore session resize and just use FitAddon
+ this.fitAddon.fit();
}
clear(): void {
@@ -192,13 +192,13 @@ export class Renderer {
// Connect to any SSE URL
connectToUrl(url: string): EventSource {
const eventSource = new EventSource(url);
-
+
// Don't clear terminal for live streams - just append new content
-
+
eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
-
+
if (data.version && data.width && data.height) {
// Header
console.log('Received header:', data);
@@ -221,11 +221,11 @@ export class Renderer {
console.warn('Failed to parse stream event:', event.data);
}
};
-
+
eventSource.onerror = (error) => {
console.error('Stream error:', error);
};
-
+
return eventSource;
}
@@ -238,7 +238,7 @@ export class Renderer {
this.eventSource.close();
this.eventSource = null;
}
-
+
if (isStream) {
// It's a stream URL, connect via SSE (don't clear - append to existing content)
this.eventSource = this.connectToUrl(url);
@@ -250,7 +250,7 @@ export class Renderer {
}
// Additional methods for terminal control
-
+
focus(): void {
this.terminal.focus();
}
@@ -303,4 +303,12 @@ export class Renderer {
});
}
}
+
+ // Disable all pointer events for previews so clicks pass through to parent
+ setPointerEventsEnabled(enabled: boolean): void {
+ const terminalElement = this.container.querySelector('.xterm') as HTMLElement;
+ if (terminalElement) {
+ terminalElement.style.pointerEvents = enabled ? 'auto' : 'none';
+ }
+ }
}
\ No newline at end of file