Skip to content

Commit 5f6fa49

Browse files
committed
feat: huge flickering improvements by removing clear_screen calls and
writing full frames at a time (i.e. iframes)
1 parent 53a6ddd commit 5f6fa49

File tree

1 file changed

+35
-26
lines changed

1 file changed

+35
-26
lines changed

npm-app/src/cli-handlers/chat.ts

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -596,11 +596,13 @@ function updateContentLines() {
596596
chatState.contentLines = lines
597597
}
598598

599-
function renderChat() {
600-
// Clear screen and move cursor to top
601-
process.stdout.write(CLEAR_SCREEN)
602-
process.stdout.write(MOVE_CURSOR(1, 1))
599+
function padLine(line: string, width: number): string {
600+
const visibleWidth = stringWidth(line)
601+
const padding = Math.max(0, width - visibleWidth)
602+
return line + ' '.repeat(padding)
603+
}
603604

605+
function renderChat() {
604606
const metrics = getTerminalMetrics()
605607
const inputAreaHeight = calculateInputAreaHeight(metrics)
606608
const maxContentLines = computeMaxContentLines(metrics)
@@ -616,14 +618,22 @@ function renderChat() {
616618
// Don't reset userHasScrolled flag here - let user keep control
617619
}
618620

621+
// Build the complete screen content
622+
const screenLines: string[] = []
623+
619624
// Display chat content
620625
const visibleLines = chatState.contentLines.slice(
621626
chatState.scrollOffset,
622627
chatState.scrollOffset + maxContentLines,
623628
)
624-
process.stdout.write(visibleLines.join('\n'))
625629

626-
// Position input area and status at bottom of terminal
630+
// Pad visible lines to fill the available content area
631+
for (let i = 0; i < maxContentLines; i++) {
632+
const line = visibleLines[i] || ''
633+
screenLines.push(padLine(line, metrics.width))
634+
}
635+
636+
// Add input area lines
627637
let currentLine = metrics.height - inputAreaHeight
628638

629639
// Display queued message preview if there are queued messages
@@ -639,26 +649,24 @@ function renderChat() {
639649
queueCount,
640650
metrics,
641651
)
642-
643-
process.stdout.write(MOVE_CURSOR(currentLine, 1))
644-
process.stdout.write(' '.repeat(metrics.sidePadding) + gray(previewText))
652+
const queueLine = ' '.repeat(metrics.sidePadding) + gray(previewText)
653+
screenLines.push(padLine(queueLine, metrics.width))
645654
currentLine++
646655
}
647656

648657
// Display separator line
649-
process.stdout.write(MOVE_CURSOR(currentLine, 1))
650-
process.stdout.write(
658+
const separatorContent =
651659
' '.repeat(metrics.sidePadding) +
652-
gray(SEPARATOR_CHAR.repeat(metrics.contentWidth)),
653-
)
660+
gray(SEPARATOR_CHAR.repeat(metrics.contentWidth))
661+
screenLines.push(padLine(separatorContent, metrics.width))
654662
currentLine++
655663

656664
// Show placeholder or user input
657665
if (chatState.currentInput.length === 0) {
658666
// Show placeholder text
659667
const placeholder = `\x1b[2m${gray(PLACEHOLDER_TEXT)}\x1b[22m`
660-
process.stdout.write(MOVE_CURSOR(currentLine, 1))
661-
process.stdout.write(' '.repeat(metrics.sidePadding) + placeholder)
668+
const placeholderContent = ' '.repeat(metrics.sidePadding) + placeholder
669+
screenLines.push(padLine(placeholderContent, metrics.width))
662670
currentLine++
663671
} else {
664672
// Show user input
@@ -668,15 +676,23 @@ function renderChat() {
668676
)
669677

670678
wrappedInputLines.forEach((line, index) => {
671-
process.stdout.write(MOVE_CURSOR(currentLine, 1))
672-
process.stdout.write(' '.repeat(metrics.sidePadding) + line)
679+
const inputContent = ' '.repeat(metrics.sidePadding) + line
680+
screenLines.push(padLine(inputContent, metrics.width))
673681
currentLine++
674682
})
675683
}
676684

685+
// Pad remaining input area with empty lines, leaving one for the status bar
686+
while (screenLines.length < metrics.height - 1) {
687+
screenLines.push(' '.repeat(metrics.width))
688+
}
689+
677690
// Status line with side padding - position at very bottom of screen
678-
process.stdout.write(MOVE_CURSOR(metrics.height, 1))
679-
process.stdout.write(' '.repeat(metrics.sidePadding) + gray(STATUS_TEXT))
691+
const statusContent = ' '.repeat(metrics.sidePadding) + gray(STATUS_TEXT)
692+
screenLines.push(padLine(statusContent, metrics.width))
693+
694+
// Write the entire screen content at once
695+
process.stdout.write(MOVE_CURSOR(1, 1) + screenLines.join('\n'))
680696

681697
// Position the real cursor at input location
682698
positionRealCursor()
@@ -875,13 +891,6 @@ async function sendMessage(message: string, addToChat: boolean = true) {
875891
)
876892
} finally {
877893
chatState.isWaitingForResponse = false
878-
879-
// Auto-focus the latest assistant message if it has subagents
880-
const latestMessageId = findLatestAssistantMessageWithChildren()
881-
if (latestMessageId) {
882-
autoFocusLatestToggle()
883-
}
884-
885894
renderChat()
886895

887896
// Process queued messages

0 commit comments

Comments
 (0)