@rlabs-inc/tui 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rlabs-inc/tui",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "The Terminal UI Framework for TypeScript/Bun - Blazing-fast, fine-grained reactive terminal UI with complete flexbox layout",
5
5
  "module": "index.ts",
6
6
  "main": "index.ts",
@@ -26,6 +26,10 @@ import * as ansi from './ansi'
26
26
  * Erases previous active content and renders fresh.
27
27
  *
28
28
  * Like InlineRenderer but only erases the active area (preserves history).
29
+ *
30
+ * OVERFLOW HANDLING:
31
+ * - If active area fits in viewport: erase only active lines (preserves history)
32
+ * - If active area exceeds viewport: clear everything (can't partially erase scrollback)
29
33
  */
30
34
  export class AppendRegionRenderer {
31
35
  // Cell rendering state (for ANSI optimization)
@@ -38,19 +42,38 @@ export class AppendRegionRenderer {
38
42
 
39
43
  /**
40
44
  * Render frame buffer as active content.
41
- * Erases exactly the previous content, then writes fresh.
45
+ * Uses sync mode for flicker-free rendering.
46
+ *
47
+ * SMART ERASE STRATEGY:
48
+ * - If previous height <= viewport: erase only active lines (O(1) for history)
49
+ * - If previous height > viewport: clear terminal (history is in scrollback anyway)
42
50
  */
43
51
  render(buffer: FrameBuffer): void {
44
52
  const output = this.buildOutput(buffer)
53
+ const viewportHeight = process.stdout.rows || 24
54
+
55
+ // Begin synchronized output for flicker-free rendering
56
+ process.stdout.write(ansi.beginSync)
45
57
 
46
- // Erase previous active content (move up and clear each line)
58
+ // Decide erase strategy based on whether content exceeds viewport
47
59
  if (this.previousHeight > 0) {
48
- process.stdout.write(ansi.eraseLines(this.previousHeight))
60
+ if (this.previousHeight <= viewportHeight) {
61
+ // Active area fits in viewport - erase only active lines (preserves history)
62
+ process.stdout.write(ansi.cursorUp(this.previousHeight))
63
+ process.stdout.write(ansi.eraseDown)
64
+ } else {
65
+ // Active area exceeded viewport - clear terminal
66
+ // History that scrolled up is already in scrollback, so this is fine
67
+ process.stdout.write(ansi.clearTerminal)
68
+ }
49
69
  }
50
70
 
51
71
  // Render active content
52
72
  process.stdout.write(output)
53
73
 
74
+ // End synchronized output
75
+ process.stdout.write(ansi.endSync)
76
+
54
77
  // Track height for next render
55
78
  // +1 because buildOutput adds trailing newline which moves cursor down one line
56
79
  this.previousHeight = buffer.height + 1
@@ -62,7 +85,16 @@ export class AppendRegionRenderer {
62
85
  */
63
86
  eraseActive(): void {
64
87
  if (this.previousHeight > 0) {
65
- process.stdout.write(ansi.eraseLines(this.previousHeight))
88
+ const viewportHeight = process.stdout.rows || 24
89
+
90
+ if (this.previousHeight <= viewportHeight) {
91
+ // Active area fits in viewport - erase only active lines
92
+ process.stdout.write(ansi.cursorUp(this.previousHeight))
93
+ process.stdout.write(ansi.eraseDown)
94
+ } else {
95
+ // Active area exceeded viewport - clear terminal
96
+ process.stdout.write(ansi.clearTerminal)
97
+ }
66
98
  this.previousHeight = 0
67
99
  }
68
100
  }
@@ -191,8 +191,8 @@ export class DiffRenderer {
191
191
  const prev = this.previousBuffer
192
192
  let hasChanges = false
193
193
 
194
- // NOTE: Synchronized output (CSI?2026h) disabled - was causing row 0 issues
195
- // this.output.write(ansi.beginSync)
194
+ // Begin synchronized output for flicker-free rendering
195
+ this.output.write(ansi.beginSync)
196
196
 
197
197
  // Reset cell renderer state at start of frame
198
198
  this.cellRenderer.reset()
@@ -341,8 +341,10 @@ export class InlineRenderer {
341
341
  return
342
342
  }
343
343
 
344
- // Clear everything and render fresh
344
+ // Synchronized output: wrap in begin/end sync for flicker-free rendering
345
+ this.output.write(ansi.beginSync)
345
346
  this.output.write(ansi.clearTerminal + output)
347
+ this.output.write(ansi.endSync)
346
348
  this.output.flushSync()
347
349
 
348
350
  this.previousOutput = output