mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-04 14:39:42 +02:00
fix: clear screen initialization blank space after attaching
GNU screen creates blank space at the top when initializing sessions. This is now handled after attaching to the screen: - Claude sessions: emit clearTerminal event after 100ms, client clears xterm - Shell sessions: send 'clear' command after 100ms Also updates CLAUDE.md with documentation of the fix. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -52,7 +52,9 @@ export class ScreenManager extends EventEmitter {
|
||||
const screenName = `claudeman-${sessionId.slice(0, 8)}`;
|
||||
|
||||
// Create screen in detached mode with the appropriate command
|
||||
const cmd = mode === 'claude' ? 'claude --dangerously-skip-permissions' : '$SHELL';
|
||||
const cmd = mode === 'claude'
|
||||
? 'claude --dangerously-skip-permissions'
|
||||
: '$SHELL';
|
||||
|
||||
try {
|
||||
// Start screen in detached mode
|
||||
|
||||
@@ -50,6 +50,7 @@ export interface SessionEvents {
|
||||
exit: (code: number | null) => void;
|
||||
completion: (result: string, cost: number) => void;
|
||||
terminal: (data: string) => void; // Raw terminal data
|
||||
clearTerminal: () => void; // Signal client to clear terminal (after screen attach)
|
||||
// Background task events
|
||||
taskCreated: (task: BackgroundTask) => void;
|
||||
taskUpdated: (task: BackgroundTask) => void;
|
||||
@@ -327,6 +328,13 @@ export class Session extends EventEmitter {
|
||||
cwd: this.workingDir,
|
||||
env: { ...process.env, TERM: 'xterm-256color' },
|
||||
});
|
||||
|
||||
// Screen creates blank space when initializing. After attaching, wait for
|
||||
// the initial burst then clear the buffer and tell clients to clear their terminal.
|
||||
setTimeout(() => {
|
||||
this._terminalBuffer = '';
|
||||
this.emit('clearTerminal');
|
||||
}, 100);
|
||||
} catch (err) {
|
||||
console.error('[Session] Failed to create screen session, falling back to direct PTY:', err);
|
||||
this._useScreen = false;
|
||||
@@ -448,6 +456,15 @@ export class Session extends EventEmitter {
|
||||
cwd: this.workingDir,
|
||||
env: { ...process.env, TERM: 'xterm-256color' },
|
||||
});
|
||||
|
||||
// Screen creates blank space when initializing. After attaching, wait for
|
||||
// the initial burst then clear by sending 'clear' command to the shell.
|
||||
setTimeout(() => {
|
||||
if (this.ptyProcess) {
|
||||
this._terminalBuffer = '';
|
||||
this.ptyProcess.write('clear\n');
|
||||
}
|
||||
}, 100);
|
||||
} catch (err) {
|
||||
console.error('[Session] Failed to create screen session, falling back to direct PTY:', err);
|
||||
this._useScreen = false;
|
||||
|
||||
+45
-2
@@ -254,6 +254,15 @@ class ClaudemanApp {
|
||||
}
|
||||
});
|
||||
|
||||
this.eventSource.addEventListener('session:clearTerminal', (e) => {
|
||||
const data = JSON.parse(e.data);
|
||||
if (data.id === this.activeSessionId) {
|
||||
// Clear terminal after screen attach to remove initialization blank space
|
||||
this.terminal.clear();
|
||||
this.terminal.reset();
|
||||
}
|
||||
});
|
||||
|
||||
this.eventSource.addEventListener('session:completion', (e) => {
|
||||
const data = JSON.parse(e.data);
|
||||
this.totalCost += data.cost || 0;
|
||||
@@ -552,7 +561,12 @@ class ClaudemanApp {
|
||||
try {
|
||||
const res = await fetch(`/api/sessions/${sessionId}/terminal`);
|
||||
const data = await res.json();
|
||||
|
||||
// Aggressively clear the terminal before switching sessions
|
||||
// This ensures no residual content from previous display
|
||||
this.terminal.clear();
|
||||
this.terminal.reset();
|
||||
|
||||
if (data.terminalBuffer) {
|
||||
// Strip leading ANSI escape sequences and whitespace to prevent gaps
|
||||
// This handles:
|
||||
@@ -560,8 +574,16 @@ class ClaudemanApp {
|
||||
// - OSC sequences: ESC ] ... BEL or ESC \
|
||||
// - Simple sequences: ESC followed by single char
|
||||
// - Whitespace, CR, LF
|
||||
// - Screen initialization sequences
|
||||
let cleanBuffer = data.terminalBuffer;
|
||||
cleanBuffer = cleanBuffer.replace(/^(\x1b\[[0-9;?]*[A-Za-z@`]|\x1b\][^\x07]*\x07|\x1b[()][AB012]|\x1b[DEMNOP78>=c]|\s|\r|\n)*/g, '');
|
||||
|
||||
// First, strip any leading screen/terminal initialization artifacts
|
||||
// Including cursor positioning that could leave blank space
|
||||
cleanBuffer = cleanBuffer.replace(/^(\x1b\[[0-9;?]*[A-Za-z@`]|\x1b\][^\x07]*\x07|\x1b[()][AB012]|\x1b[=>DEMNOP78c]|\x1b[^\x1b]|\s|\r|\n)*/g, '');
|
||||
|
||||
// Also strip any DCS sequences (screen might send these)
|
||||
cleanBuffer = cleanBuffer.replace(/^\x1bP[^\x1b]*\x1b\\/g, '');
|
||||
|
||||
this.terminal.write(cleanBuffer);
|
||||
}
|
||||
|
||||
@@ -797,7 +819,28 @@ class ClaudemanApp {
|
||||
|
||||
// Auto-switch to the new session
|
||||
if (firstSessionId) {
|
||||
await this.selectSession(firstSessionId);
|
||||
// IMPORTANT: Clear terminal BEFORE setting activeSessionId
|
||||
// This prevents SSE events from writing during the transition
|
||||
this.terminal.clear();
|
||||
this.terminal.reset();
|
||||
|
||||
// Small delay to ensure terminal is fully reset before SSE events can write
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
|
||||
// NOW set the active session so SSE events start populating
|
||||
this.activeSessionId = firstSessionId;
|
||||
this.renderSessionTabs();
|
||||
|
||||
// Send resize to the new session
|
||||
const dims = this.fitAddon.proposeDimensions();
|
||||
if (dims) {
|
||||
fetch(`/api/sessions/${firstSessionId}/resize`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ cols: dims.cols, rows: dims.rows })
|
||||
});
|
||||
}
|
||||
|
||||
this.loadQuickStartCases();
|
||||
}
|
||||
|
||||
|
||||
@@ -813,6 +813,11 @@ export class WebServer extends EventEmitter {
|
||||
this.batchTerminalData(session.id, data);
|
||||
});
|
||||
|
||||
session.on('clearTerminal', () => {
|
||||
// Tell clients to clear their terminal (after screen attach)
|
||||
this.broadcast('session:clearTerminal', { id: session.id });
|
||||
});
|
||||
|
||||
session.on('message', (msg: ClaudeMessage) => {
|
||||
this.broadcast('session:message', { id: session.id, message: msg });
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user