mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-02 21:49:42 +02:00
docs: add terminal anti-flicker system documentation
- Document 6-layer anti-flicker pipeline (server batching, DEC 2026, SSE broadcast, client rAF, sync parser, chunked loading) - Add detailed implementation notes for server and client sides - Document responsiveness considerations and latency sources - Fix cross-session data bleed: clear pendingWrites and syncWaitTimeout on session switch and SSE reconnect - Improve flicker filter to detect cursor-up patterns (ESC[nA) - Add adaptive batching: extend batch window during rapid-fire events - Update BATCH_FLUSH_THRESHOLD from 1KB to 32KB for effective batching Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+24
-4
@@ -1626,18 +1626,23 @@ class ClaudemanApp {
|
||||
const session = this.activeSessionId ? this.sessions.get(this.activeSessionId) : null;
|
||||
const flickerFilterEnabled = session?.flickerFilterEnabled ?? false;
|
||||
|
||||
// Flicker filter: detect screen clear patterns and buffer output
|
||||
// Flicker filter: detect screen clear and cursor-up patterns and buffer output
|
||||
if (flickerFilterEnabled) {
|
||||
// Detect Ink's screen clear patterns:
|
||||
// Detect Ink's redraw patterns:
|
||||
// - ESC[2J (clear entire screen)
|
||||
// - ESC[H ESC[J (cursor home + clear to end)
|
||||
// - ESC[?25l ESC[H (hide cursor + home - common Ink pattern)
|
||||
// - ESC[nA (cursor up n lines - Ink's line redraw pattern)
|
||||
const hasScreenClear = data.includes('\x1b[2J') ||
|
||||
data.includes('\x1b[H\x1b[J') ||
|
||||
(data.includes('\x1b[H') && data.includes('\x1b[?25l'));
|
||||
|
||||
if (hasScreenClear) {
|
||||
// Screen clear detected - activate flicker filter
|
||||
// Detect cursor-up patterns (ESC[nA where n >= 2) - indicates Ink redrawing multiple lines
|
||||
// Use regex to find ESC[nA where n is 2 or more digits
|
||||
const hasCursorUpRedraw = /\x1b\[\d{1,2}A/.test(data);
|
||||
|
||||
if (hasScreenClear || hasCursorUpRedraw) {
|
||||
// Redraw pattern detected - activate flicker filter
|
||||
this.flickerFilterActive = true;
|
||||
this.flickerFilterBuffer += data;
|
||||
|
||||
@@ -2888,6 +2893,13 @@ class ClaudemanApp {
|
||||
}
|
||||
this.flickerFilterBuffer = '';
|
||||
this.flickerFilterActive = false;
|
||||
// Clear pending terminal writes
|
||||
if (this.syncWaitTimeout) {
|
||||
clearTimeout(this.syncWaitTimeout);
|
||||
this.syncWaitTimeout = null;
|
||||
}
|
||||
this.pendingWrites = '';
|
||||
this.writeFrameScheduled = false;
|
||||
// Clear pending hooks
|
||||
this.pendingHooks.clear();
|
||||
// Clear tab alerts
|
||||
@@ -3522,6 +3534,14 @@ class ClaudemanApp {
|
||||
this.flickerFilterBuffer = '';
|
||||
this.flickerFilterActive = false;
|
||||
|
||||
// Clean up pending terminal writes to prevent old session data from appearing in new session
|
||||
if (this.syncWaitTimeout) {
|
||||
clearTimeout(this.syncWaitTimeout);
|
||||
this.syncWaitTimeout = null;
|
||||
}
|
||||
this.pendingWrites = '';
|
||||
this.writeFrameScheduled = false;
|
||||
|
||||
this.activeSessionId = sessionId;
|
||||
this.hideWelcome();
|
||||
// Clear idle hooks on view, but keep action hooks until user interacts
|
||||
|
||||
+27
-5
@@ -115,8 +115,9 @@ const STATS_COLLECTION_INTERVAL_MS = 2000;
|
||||
const SESSION_LIMIT_WAIT_MS = 5000;
|
||||
// Pause between scheduled run iterations (2 seconds)
|
||||
const ITERATION_PAUSE_MS = 2000;
|
||||
// SSE batch flush threshold (number of items)
|
||||
const BATCH_FLUSH_THRESHOLD = 1024;
|
||||
// Terminal batch flush threshold - flush immediately if batch exceeds this size
|
||||
// Set high (32KB) to allow effective batching; avg Ink events are ~14KB
|
||||
const BATCH_FLUSH_THRESHOLD = 32 * 1024;
|
||||
// Pre-compiled regex for terminal buffer cleaning (avoids per-request compilation)
|
||||
const CLAUDE_BANNER_PATTERN = /\x1b\[1mClaud/;
|
||||
const CTRL_L_PATTERN = /\x0c/g;
|
||||
@@ -316,6 +317,9 @@ export class WebServer extends EventEmitter {
|
||||
// Terminal batching for performance
|
||||
private terminalBatches: Map<string, string> = new Map();
|
||||
private terminalBatchTimer: NodeJS.Timeout | null = null;
|
||||
// Adaptive batching: track rapid events to extend batch window
|
||||
private lastTerminalEventTime: Map<string, number> = new Map();
|
||||
private adaptiveBatchInterval: number = TERMINAL_BATCH_INTERVAL;
|
||||
// Scheduled runs cleanup timer
|
||||
private scheduledCleanupTimer: NodeJS.Timeout | null = null;
|
||||
// SSE event batching
|
||||
@@ -4365,7 +4369,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
}
|
||||
|
||||
// Batch terminal data for better performance (60fps)
|
||||
// Flushes immediately if batch > 1KB for snappier response to large outputs
|
||||
// Uses adaptive batching: extends batch window when events are rapid-fire
|
||||
private batchTerminalData(sessionId: string, data: string): void {
|
||||
// Skip if server is stopping
|
||||
if (this._isStopping) return;
|
||||
@@ -4374,6 +4378,22 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
const newBatch = existing + data;
|
||||
this.terminalBatches.set(sessionId, newBatch);
|
||||
|
||||
// Adaptive batching: detect rapid events and extend batch window
|
||||
const now = Date.now();
|
||||
const lastEvent = this.lastTerminalEventTime.get(sessionId) || 0;
|
||||
const eventGap = now - lastEvent;
|
||||
this.lastTerminalEventTime.set(sessionId, now);
|
||||
|
||||
// Adjust batch interval based on event frequency
|
||||
// Rapid events (<10ms gap) = 50ms batch, moderate (<20ms) = 32ms, else 16ms
|
||||
if (eventGap > 0 && eventGap < 10) {
|
||||
this.adaptiveBatchInterval = 50;
|
||||
} else if (eventGap > 0 && eventGap < 20) {
|
||||
this.adaptiveBatchInterval = 32;
|
||||
} else {
|
||||
this.adaptiveBatchInterval = TERMINAL_BATCH_INTERVAL;
|
||||
}
|
||||
|
||||
// Flush immediately if batch is large for responsiveness
|
||||
if (newBatch.length > BATCH_FLUSH_THRESHOLD) {
|
||||
if (this.terminalBatchTimer) {
|
||||
@@ -4384,12 +4404,14 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Start batch timer if not already running (16ms = 60fps)
|
||||
// Start batch timer if not already running (uses adaptive interval)
|
||||
if (!this.terminalBatchTimer) {
|
||||
this.terminalBatchTimer = setTimeout(() => {
|
||||
this.flushTerminalBatches();
|
||||
this.terminalBatchTimer = null;
|
||||
}, TERMINAL_BATCH_INTERVAL);
|
||||
// Reset adaptive interval after flush
|
||||
this.adaptiveBatchInterval = TERMINAL_BATCH_INTERVAL;
|
||||
}, this.adaptiveBatchInterval);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user