diff --git a/frontend/src/components/FloatingTerminal.vue b/frontend/src/components/FloatingTerminal.vue
index 276ba02..9f92fff 100644
--- a/frontend/src/components/FloatingTerminal.vue
+++ b/frontend/src/components/FloatingTerminal.vue
@@ -506,6 +506,13 @@ function sendClear() {
}
}
+function clearServerBuffer() {
+ if (socket?.readyState === WebSocket.OPEN) {
+ socket.send(JSON.stringify({ type: 'clear-buffer' }))
+ renderer.reset()
+ }
+}
+
function requestToken() {
if (socket?.readyState === WebSocket.OPEN) {
tokenBuffer = ''
@@ -725,6 +732,7 @@ defineExpose({
@@ -862,6 +870,7 @@ defineExpose({
}
.window-controls button:hover { background: rgba(255,255,255,0.5); }
.window-controls button.x:hover { background: linear-gradient(180deg, #e66 0%, #c33 100%); border-color: #a22; color: #fff; }
+.window-controls button.clear-buf:hover { background: linear-gradient(180deg, #f90 0%, #c60 100%); border-color: #a50; color: #fff; }
.window-controls button.waiting { background: rgba(16, 185, 129, 0.3); border-color: #10b981; animation: pulse 0.8s infinite; }
.content {
diff --git a/frontend/src/composables/useTerminalRenderer.ts b/frontend/src/composables/useTerminalRenderer.ts
index fbb0248..288acec 100644
--- a/frontend/src/composables/useTerminalRenderer.ts
+++ b/frontend/src/composables/useTerminalRenderer.ts
@@ -171,16 +171,23 @@ export function useTerminalRenderer(options: TerminalRendererOptions): TerminalR
terminal.value.open(options.container.value)
+ // Don't fit here - wait for onBecameVisible() when container is fully sized
nextTick(() => {
- fit()
isReady.value = true
})
- // Setup resize observer
+ // Setup resize observer (ignore tiny sizes during transitions)
resizeObserver = new ResizeObserver(() => {
if (fitAddon.value && terminal.value) {
+ const prevCols = terminal.value.cols
+ const prevRows = terminal.value.rows
fitAddon.value.fit()
- options.onResize?.(terminal.value.cols, terminal.value.rows)
+ // Only notify if size is reasonable (not during CSS transitions)
+ if (terminal.value.cols >= 20 && terminal.value.rows >= 5) {
+ if (terminal.value.cols !== prevCols || terminal.value.rows !== prevRows) {
+ options.onResize?.(terminal.value.cols, terminal.value.rows)
+ }
+ }
}
})
resizeObserver.observe(options.container.value)
@@ -351,15 +358,42 @@ export function useTerminalRenderer(options: TerminalRendererOptions): TerminalR
/**
* Handle replay data from server.
- * Simple: write + refresh + scrollToBottom
+ * Writes in chunks to avoid overwhelming xterm.js with large buffers.
*/
function handleReplay(data: string): void {
if (!terminal.value) return
- terminal.value.write(data, () => {
- terminal.value?.refresh(0, terminal.value.rows - 1)
- terminal.value?.scrollToBottom()
- })
+ const CHUNK_SIZE = 8192 // 8KB chunks
+ const CHUNK_DELAY = 10 // 10ms between chunks
+
+ // Small data: write directly
+ if (data.length <= CHUNK_SIZE) {
+ terminal.value.write(data, () => {
+ terminal.value?.refresh(0, terminal.value.rows - 1)
+ terminal.value?.scrollToBottom()
+ })
+ return
+ }
+
+ // Large data: write in chunks
+ let offset = 0
+ const writeNextChunk = () => {
+ if (!terminal.value || offset >= data.length) {
+ // Done - final refresh and scroll
+ terminal.value?.refresh(0, terminal.value.rows - 1)
+ terminal.value?.scrollToBottom()
+ return
+ }
+
+ const chunk = data.slice(offset, offset + CHUNK_SIZE)
+ offset += CHUNK_SIZE
+
+ terminal.value.write(chunk, () => {
+ setTimeout(writeNextChunk, CHUNK_DELAY)
+ })
+ }
+
+ writeNextChunk()
}
// ==========================================================================
diff --git a/server/services/terminal.ts b/server/services/terminal.ts
index e5a9b4a..5b3fdcc 100644
--- a/server/services/terminal.ts
+++ b/server/services/terminal.ts
@@ -187,6 +187,10 @@ export function startTerminalServer() {
} else if (msg.type === 'resize' && msg.cols && msg.rows) {
session.pty.resize(msg.cols, msg.rows)
console.log(`[Terminal] Session ${sessionId} resized to ${msg.cols}x${msg.rows}`)
+ } else if (msg.type === 'clear-buffer') {
+ session.outputBuffer = []
+ console.log(`[Terminal] Buffer cleared for session ${sessionId}`)
+ ws.send(JSON.stringify({ type: 'buffer-cleared' }))
} else if (msg.type === 'request-replay') {
// Client requests fresh replay (used when terminal becomes visible)
console.log(`[Terminal] Replay requested, buffer has ${session.outputBuffer.length} chunks`)