mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-22 14:06:02 +00:00
fix: resolve terminal rendering issues in vibe-terminal-buffer
- Move terminal color definitions to shared CSS in input.css - Use bright color palette for dark backgrounds - Switch from Lit template rendering to direct innerHTML for terminal content - Add display: inline-block to terminal-char for proper rendering - Remove redundant style definitions from terminal.ts - Fix issue where Lit's template system was interfering with terminal output The key fix was using innerHTML directly instead of Lit's template system for the terminal content, matching the approach used in terminal.ts. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
01df54c63b
commit
3bf543e694
3 changed files with 189 additions and 281 deletions
|
|
@ -1168,196 +1168,17 @@ export class Terminal extends LitElement {
|
|||
render() {
|
||||
return html`
|
||||
<style>
|
||||
/* Terminal color variables */
|
||||
:root {
|
||||
--terminal-color-0: #000000;
|
||||
--terminal-color-1: #f14c4c;
|
||||
--terminal-color-2: #23d18b;
|
||||
--terminal-color-3: #f5f543;
|
||||
--terminal-color-4: #3b8eea;
|
||||
--terminal-color-5: #d670d6;
|
||||
--terminal-color-6: #29b8db;
|
||||
--terminal-color-7: #e5e5e5;
|
||||
--terminal-color-8: #666666;
|
||||
--terminal-color-9: #ff6b6b;
|
||||
--terminal-color-10: #5af78e;
|
||||
--terminal-color-11: #f4f99d;
|
||||
--terminal-color-12: #70a5ed;
|
||||
--terminal-color-13: #d670d6;
|
||||
--terminal-color-14: #5fb3d3;
|
||||
--terminal-color-15: #ffffff;
|
||||
}
|
||||
|
||||
/* Dynamic terminal sizing */
|
||||
.terminal-container {
|
||||
color: #d4d4d4;
|
||||
font-family: 'Fira Code', ui-monospace, SFMono-Regular, monospace;
|
||||
font-size: ${this.fontSize}px;
|
||||
line-height: ${this.fontSize * 1.2}px;
|
||||
white-space: pre;
|
||||
touch-action: none !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.terminal-line {
|
||||
display: block;
|
||||
height: ${this.fontSize * 1.2}px;
|
||||
line-height: ${this.fontSize * 1.2}px;
|
||||
}
|
||||
|
||||
.terminal-char {
|
||||
font-family: inherit;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.terminal-char.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.terminal-char.dim {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.terminal-char.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.terminal-char.underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.terminal-char.strikethrough {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.terminal-char.inverse {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
.terminal-char.invisible {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.terminal-char.cursor {
|
||||
animation: cursor-blink 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes cursor-blink {
|
||||
0%,
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
51%,
|
||||
100% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
.terminal-link {
|
||||
color: #4fc3f7;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.terminal-link:hover {
|
||||
background-color: rgba(79, 195, 247, 0.2);
|
||||
}
|
||||
|
||||
.scroll-to-bottom {
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
left: 12px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
border: 1px solid #444;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
color: #d4d4d4;
|
||||
font-size: 24px;
|
||||
transition: all 0.2s ease;
|
||||
user-select: none;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.scroll-to-bottom:hover {
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
border-color: #666;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.scroll-to-bottom:active {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
|
||||
.debug-overlay {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 8px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
border: 1px solid #444;
|
||||
border-radius: 4px;
|
||||
padding: 8px 12px;
|
||||
font-family: 'Fira Code', monospace;
|
||||
font-size: 11px;
|
||||
color: #d4d4d4;
|
||||
user-select: none;
|
||||
z-index: 10;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.debug-overlay .metric {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.debug-overlay .metric-label {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.debug-overlay .metric-value {
|
||||
font-weight: bold;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.fit-toggle {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
border: 1px solid #444;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
color: #d4d4d4;
|
||||
font-size: 20px;
|
||||
transition: all 0.2s ease;
|
||||
user-select: none;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.fit-toggle:hover {
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
border-color: #666;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.fit-toggle:active {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
|
||||
.fit-toggle.active {
|
||||
border-color: #569cd6;
|
||||
color: #569cd6;
|
||||
}
|
||||
</style>
|
||||
<div style="position: relative; width: 100%; height: 100%;">
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { LitElement, html } from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
||||
import { TerminalRenderer, type BufferCell } from '../utils/terminal-renderer.js';
|
||||
|
||||
interface BufferSnapshot {
|
||||
|
|
@ -36,10 +35,7 @@ export class VibeTerminalBuffer extends LitElement {
|
|||
private resizeObserver: ResizeObserver | null = null;
|
||||
private lastModified: string | null = null;
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.startPolling();
|
||||
}
|
||||
// Moved to render() method above
|
||||
|
||||
disconnectedCallback() {
|
||||
this.stopPolling();
|
||||
|
|
@ -59,6 +55,8 @@ export class VibeTerminalBuffer extends LitElement {
|
|||
}
|
||||
|
||||
updated(changedProperties: Map<string, unknown>) {
|
||||
super.updated(changedProperties);
|
||||
|
||||
if (changedProperties.has('sessionId')) {
|
||||
this.buffer = null;
|
||||
this.error = null;
|
||||
|
|
@ -74,6 +72,11 @@ export class VibeTerminalBuffer extends LitElement {
|
|||
if (changedProperties.has('fontSize') || changedProperties.has('fitHorizontally')) {
|
||||
this.calculateDimensions();
|
||||
}
|
||||
|
||||
// Update buffer content after any render
|
||||
if (this.container && this.buffer) {
|
||||
this.updateBufferContent();
|
||||
}
|
||||
}
|
||||
|
||||
private setupResize() {
|
||||
|
|
@ -172,6 +175,7 @@ export class VibeTerminalBuffer extends LitElement {
|
|||
this.lastModified = stats.lastModified;
|
||||
this.error = null;
|
||||
|
||||
// Request update which will trigger updated() lifecycle
|
||||
this.requestUpdate();
|
||||
} catch (error) {
|
||||
console.error('Error fetching buffer:', error);
|
||||
|
|
@ -181,51 +185,25 @@ export class VibeTerminalBuffer extends LitElement {
|
|||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.startPolling();
|
||||
}
|
||||
|
||||
render() {
|
||||
const lineHeight = this.displayedFontSize * 1.2;
|
||||
|
||||
return html`
|
||||
<style>
|
||||
/* Import terminal color variables from parent scope */
|
||||
.terminal-container {
|
||||
/* Ensure CSS variables are inherited */
|
||||
color: #d4d4d4;
|
||||
background-color: transparent;
|
||||
/* Dynamic terminal sizing for this instance */
|
||||
vibe-terminal-buffer .terminal-container {
|
||||
font-size: ${this.displayedFontSize}px;
|
||||
line-height: ${lineHeight}px;
|
||||
}
|
||||
|
||||
/* Terminal character styling */
|
||||
.terminal-char {
|
||||
font-variant-ligatures: none;
|
||||
font-feature-settings: 'liga' 0;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.terminal-char.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.terminal-char.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.terminal-char.underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.terminal-char.dim {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.terminal-char.strikethrough {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.terminal-char.cursor {
|
||||
background-color: #23d18b !important;
|
||||
}
|
||||
|
||||
/* Terminal line styling */
|
||||
.terminal-line {
|
||||
white-space: pre;
|
||||
overflow: hidden;
|
||||
vibe-terminal-buffer .terminal-line {
|
||||
height: ${lineHeight}px;
|
||||
line-height: ${lineHeight}px;
|
||||
}
|
||||
</style>
|
||||
<div class="relative w-full h-full overflow-hidden bg-[#1e1e1e]">
|
||||
|
|
@ -239,53 +217,34 @@ export class VibeTerminalBuffer extends LitElement {
|
|||
<div
|
||||
id="buffer-container"
|
||||
class="terminal-container w-full h-full overflow-x-auto overflow-y-hidden font-mono antialiased"
|
||||
style="font-size: ${this.displayedFontSize}px; line-height: 1.2;"
|
||||
>
|
||||
${this.renderBuffer()}
|
||||
</div>
|
||||
></div>
|
||||
`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderBuffer() {
|
||||
if (!this.buffer) {
|
||||
return html`<div class="terminal-line"></div>`;
|
||||
}
|
||||
private updateBufferContent() {
|
||||
if (!this.container || !this.buffer) return;
|
||||
|
||||
const lineHeight = this.displayedFontSize * 1.2;
|
||||
let html = '';
|
||||
|
||||
// In fitHorizontally mode, we show all content scaled to fit
|
||||
// Otherwise, we show from the top and let it overflow
|
||||
if (this.fitHorizontally) {
|
||||
// Render all lines - the buffer is already trimmed of blank lines from the bottom
|
||||
return this.buffer.cells.map((row, index) => {
|
||||
const isCursorLine = index === this.buffer.cursorY;
|
||||
const cursorCol = isCursorLine ? this.buffer.cursorX : -1;
|
||||
const lineContent = TerminalRenderer.renderLineFromCells(row, cursorCol);
|
||||
const cellsToRender = this.fitHorizontally
|
||||
? this.buffer.cells
|
||||
: this.buffer.cells.slice(0, this.actualRows);
|
||||
|
||||
return html`
|
||||
<div class="terminal-line" style="height: ${lineHeight}px; line-height: ${lineHeight}px;">
|
||||
${unsafeHTML(lineContent)}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
} else {
|
||||
// Show only what fits in the viewport
|
||||
const visibleCells = this.buffer.cells.slice(0, this.actualRows);
|
||||
cellsToRender.forEach((row, index) => {
|
||||
const isCursorLine = index === this.buffer.cursorY;
|
||||
const cursorCol = isCursorLine ? this.buffer.cursorX : -1;
|
||||
const lineContent = TerminalRenderer.renderLineFromCells(row, cursorCol);
|
||||
|
||||
return visibleCells.map((row, index) => {
|
||||
const isCursorLine = index === this.buffer.cursorY;
|
||||
const cursorCol = isCursorLine ? this.buffer.cursorX : -1;
|
||||
const lineContent = TerminalRenderer.renderLineFromCells(row, cursorCol);
|
||||
html += `<div class="terminal-line" style="height: ${lineHeight}px; line-height: ${lineHeight}px;">${lineContent}</div>`;
|
||||
});
|
||||
|
||||
return html`
|
||||
<div class="terminal-line" style="height: ${lineHeight}px; line-height: ${lineHeight}px;">
|
||||
${unsafeHTML(lineContent)}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
// Set innerHTML directly like terminal.ts does
|
||||
this.container.innerHTML = html;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -110,15 +110,24 @@ body {
|
|||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* Terminal line styling */
|
||||
.terminal-line {
|
||||
/* Terminal container styling */
|
||||
.terminal-container {
|
||||
color: #d4d4d4;
|
||||
white-space: pre;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Terminal line styling */
|
||||
.terminal-line {
|
||||
display: block;
|
||||
white-space: pre;
|
||||
overflow: hidden;
|
||||
color: #d4d4d4;
|
||||
}
|
||||
|
||||
/* Terminal character specific styling */
|
||||
.terminal-char {
|
||||
display: inline-block;
|
||||
font-family: inherit;
|
||||
font-variant-ligatures: none;
|
||||
font-feature-settings: 'liga' 0;
|
||||
white-space: pre;
|
||||
|
|
@ -149,12 +158,20 @@ body {
|
|||
text-decoration: overline;
|
||||
}
|
||||
|
||||
/* Cursor styling */
|
||||
.terminal-char.cursor {
|
||||
animation: blink 1s infinite;
|
||||
.terminal-char.inverse {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
.terminal-char.invisible {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Cursor styling */
|
||||
.terminal-char.cursor {
|
||||
animation: cursor-blink 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes cursor-blink {
|
||||
0%,
|
||||
50% {
|
||||
opacity: 1;
|
||||
|
|
@ -221,23 +238,23 @@ body {
|
|||
opacity: 0 !important;
|
||||
}
|
||||
|
||||
/* XTerm 256-color palette - Complete */
|
||||
/* Terminal color palette - Bright colors for dark backgrounds */
|
||||
:root {
|
||||
--terminal-color-0: #000000;
|
||||
--terminal-color-1: #800000;
|
||||
--terminal-color-2: #008000;
|
||||
--terminal-color-3: #808000;
|
||||
--terminal-color-4: #000080;
|
||||
--terminal-color-5: #800080;
|
||||
--terminal-color-6: #008080;
|
||||
--terminal-color-7: #c0c0c0;
|
||||
--terminal-color-8: #808080;
|
||||
--terminal-color-9: #ff0000;
|
||||
--terminal-color-10: #00ff00;
|
||||
--terminal-color-11: #ffff00;
|
||||
--terminal-color-12: #0000ff;
|
||||
--terminal-color-13: #ff00ff;
|
||||
--terminal-color-14: #00ffff;
|
||||
--terminal-color-1: #f14c4c;
|
||||
--terminal-color-2: #23d18b;
|
||||
--terminal-color-3: #f5f543;
|
||||
--terminal-color-4: #3b8eea;
|
||||
--terminal-color-5: #d670d6;
|
||||
--terminal-color-6: #29b8db;
|
||||
--terminal-color-7: #e5e5e5;
|
||||
--terminal-color-8: #666666;
|
||||
--terminal-color-9: #ff6b6b;
|
||||
--terminal-color-10: #5af78e;
|
||||
--terminal-color-11: #f4f99d;
|
||||
--terminal-color-12: #70a5ed;
|
||||
--terminal-color-13: #d670d6;
|
||||
--terminal-color-14: #5fb3d3;
|
||||
--terminal-color-15: #ffffff;
|
||||
--terminal-color-16: #000000;
|
||||
--terminal-color-17: #00005f;
|
||||
|
|
@ -480,3 +497,114 @@ body {
|
|||
--terminal-color-254: #e4e4e4;
|
||||
--terminal-color-255: #eeeeee;
|
||||
}
|
||||
|
||||
/* Terminal link styling */
|
||||
.terminal-link {
|
||||
color: #4fc3f7;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.terminal-link:hover {
|
||||
background-color: rgba(79, 195, 247, 0.2);
|
||||
}
|
||||
|
||||
/* Scroll to bottom button */
|
||||
.scroll-to-bottom {
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
left: 12px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
border: 1px solid #444;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
color: #d4d4d4;
|
||||
font-size: 24px;
|
||||
transition: all 0.2s ease;
|
||||
user-select: none;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.scroll-to-bottom:hover {
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
border-color: #666;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.scroll-to-bottom:active {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
|
||||
/* Debug overlay */
|
||||
.debug-overlay {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 8px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
border: 1px solid #444;
|
||||
border-radius: 4px;
|
||||
padding: 8px 12px;
|
||||
font-family: 'Fira Code', monospace;
|
||||
font-size: 11px;
|
||||
color: #d4d4d4;
|
||||
user-select: none;
|
||||
z-index: 10;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.debug-overlay .metric {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.debug-overlay .metric-label {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.debug-overlay .metric-value {
|
||||
font-weight: bold;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
/* Fit toggle button */
|
||||
.fit-toggle {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
border: 1px solid #444;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
color: #d4d4d4;
|
||||
font-size: 20px;
|
||||
transition: all 0.2s ease;
|
||||
user-select: none;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.fit-toggle:hover {
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
border-color: #666;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.fit-toggle:active {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
|
||||
.fit-toggle.active {
|
||||
border-color: #569cd6;
|
||||
color: #569cd6;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue