fix: Improve terminal buffer handling and replay performance
- Add chunked replay (8KB chunks with 10ms delay) to avoid overwhelming xterm.js - Add clear-buffer server command to reset terminal history - Add clear buffer button in terminal header - Filter out tiny resize events during CSS transitions (ignore < 20x5) - Remove premature fit() call from init - wait for onBecameVisible
This commit is contained in:
@@ -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({
|
||||
<div class="window-controls">
|
||||
<button @click="requestToken" :class="{ waiting: waitingForToken }" title="Connect MCP"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></button>
|
||||
<button @click="runClaude" title="Claude"><svg width="8" height="8" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg></button>
|
||||
<button @click="clearServerBuffer" class="clear-buf" title="Clear Buffer"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2m3 0v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6h14"/></svg></button>
|
||||
<button @click="refreshTerminal" title="Refresh Screen"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg></button>
|
||||
<button class="x" @click="close" title="Close"><svg width="8" height="8" viewBox="0 0 10 10"><line x1="0" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="1.5"/><line x1="10" y1="0" x2="0" y2="10" stroke="currentColor" stroke-width="1.5"/></svg></button>
|
||||
</div>
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
|
||||
@@ -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`)
|
||||
|
||||
Reference in New Issue
Block a user