[GH-ISSUE #208] [BUG] Text content becomes invisible during rapid updates (streaming) #47

Closed
opened 2026-03-02 23:44:07 +03:00 by kerem · 3 comments
Owner

Originally created by @joshkotrous on GitHub (Oct 9, 2025).
Original GitHub issue: https://github.com/anomalyco/opentui/issues/208

Summary

When updating text content rapidly (e.g., streaming AI responses), the text becomes completely invisible after the initial render, though the layout space is correctly allocated. A single update works perfectly, but multiple rapid updates cause invisible text - even when throttled to 500ms intervals.

Environment

  • opentui/react version: 0.1.25
  • opentui/core version: 0.1.25
  • React version: 19.1.1
  • Node version: 20.18.1
  • Runtime: Bun
  • Platform: macOS (darwin 25.0.0)
  • Terminal: Ghostty

Expected Behavior

Text content should update and remain visible during streaming, similar to how the counter example updates every 50ms.

Actual Behavior

  • User message (first, static message) displays perfectly
  • Assistant message label displays ("← Assistant" in green)
  • Assistant message content is completely invisible (but space is allocated)
  • Console logs confirm data is correct and component is re-rendering
  • Strange semicolon-like artifacts (;;;) appear where text should be

Minimal Reproduction

import { useState } from "react";
import { render } from "@opentui/react";

function StreamingText() {
  const [text, setText] = useState("");
  const [started, setStarted] = useState(false);

  async function startStreaming() {
    setStarted(true);
    let content = "";

    // Simulate streaming chunks
    for (let i = 0; i < 50; i++) {
      content += `Chunk ${i} `;
      setText(content);
      await new Promise((resolve) => setTimeout(resolve, 100)); // Even with 100ms delay!
    }
  }

  return (
    <box flexDirection="column" gap={1}>
      {!started && (
        <text fg="cyan" content="Press Enter to start (call startStreaming)" />
      )}

      <box flexDirection="column">
        <text fg="green">Label (always visible)</text>
        <text fg="white">{text}</text>
      </box>
    </box>
  );
}

render(<StreamingText />);

Result: The label shows but the streaming text is invisible (layout space allocated but no visible characters).

What I Tested

All attempts failed except showing only the final result:

  1. Using content prop: <text content={text} /> instead of <text>{text}</text>
  2. Different key strategies:
  • Stable keys: key={index}
  • Dynamic keys: key={${index}-${content.length}}
  • Per-element keys on text component
  1. Throttled to 500ms: Only 2 updates per second still fails
  2. Throttled to 100ms: 10 updates per second still fails
  3. Single update after completion: THIS WORKS

Key Finding

// DOESN'T WORK - Text invisible during updates
for await (const chunk of stream) {
  content += chunk;
  setText(content);
  await new Promise((resolve) => setTimeout(resolve, 500)); // Even with delay!
}

// WORKS - Text visible
for await (const chunk of stream) {
  content += chunk;
}
setText(content); // Single update after all chunks

Questions

  1. Is there a recommended pattern for streaming/rapidly updating text?
  2. Any known limitations with updating text content multiple times?
Originally created by @joshkotrous on GitHub (Oct 9, 2025). Original GitHub issue: https://github.com/anomalyco/opentui/issues/208 ## Summary When updating text content rapidly (e.g., streaming AI responses), the text becomes completely invisible after the initial render, though the layout space is correctly allocated. **A single update works perfectly, but multiple rapid updates cause invisible text - even when throttled to 500ms intervals.** ## Environment - **opentui/react version**: 0.1.25 - **opentui/core version**: 0.1.25 - **React version**: 19.1.1 - **Node version**: 20.18.1 - **Runtime**: Bun - **Platform**: macOS (darwin 25.0.0) - **Terminal**: Ghostty ## Expected Behavior Text content should update and remain visible during streaming, similar to how the counter example updates every 50ms. ## Actual Behavior - User message (first, static message) displays perfectly - Assistant message label displays ("← Assistant" in green) - Assistant message **content is completely invisible** (but space is allocated) - Console logs confirm data is correct and component is re-rendering - Strange semicolon-like artifacts (;;;) appear where text should be ## Minimal Reproduction ```tsx import { useState } from "react"; import { render } from "@opentui/react"; function StreamingText() { const [text, setText] = useState(""); const [started, setStarted] = useState(false); async function startStreaming() { setStarted(true); let content = ""; // Simulate streaming chunks for (let i = 0; i < 50; i++) { content += `Chunk ${i} `; setText(content); await new Promise((resolve) => setTimeout(resolve, 100)); // Even with 100ms delay! } } return ( <box flexDirection="column" gap={1}> {!started && ( <text fg="cyan" content="Press Enter to start (call startStreaming)" /> )} <box flexDirection="column"> <text fg="green">Label (always visible)</text> <text fg="white">{text}</text> </box> </box> ); } render(<StreamingText />); ``` **Result**: The label shows but the streaming text is invisible (layout space allocated but no visible characters). ## What I Tested All attempts failed except showing only the final result: 1. **Using `content` prop**: `<text content={text} />` instead of `<text>{text}</text>` 2. **Different key strategies**: - Stable keys: `key={index}` - Dynamic keys: `key={`${index}-${content.length}`}` - Per-element keys on text component 5. **Throttled to 500ms**: Only 2 updates per second still fails 6. **Throttled to 100ms**: 10 updates per second still fails 7. **Single update after completion**: **THIS WORKS** ## Key Finding ```tsx // DOESN'T WORK - Text invisible during updates for await (const chunk of stream) { content += chunk; setText(content); await new Promise((resolve) => setTimeout(resolve, 500)); // Even with delay! } // WORKS - Text visible for await (const chunk of stream) { content += chunk; } setText(content); // Single update after all chunks ``` ## Questions 1. Is there a recommended pattern for streaming/rapidly updating text? 2. Any known limitations with updating text content multiple times?
kerem 2026-03-02 23:44:07 +03:00
  • closed this issue
  • added the
    bug
    react
    labels
Author
Owner

@kommander commented on GitHub (Oct 9, 2025):

There are no limitations I am aware of from core perspective. There is a solidjs streaming example that works well. Maybe there is something off with react, we currently mainly use solid, as that is what is used in opencode.

Our react maintainer is off currently, but maybe can have a look when he's back @msmps

<!-- gh-comment-id:3384927601 --> @kommander commented on GitHub (Oct 9, 2025): There are no limitations I am aware of from core perspective. There is a solidjs streaming example that works well. Maybe there is something off with react, we currently mainly use solid, as that is what is used in opencode. Our react maintainer is off currently, but maybe can have a look when he's back @msmps
Author
Owner

@msmps commented on GitHub (Oct 9, 2025):

I might be missing something here so forgive me but I am able to get your reproduction working? Are you able to try with v0.1.26 as this does include some bug fixes

Image

<!-- gh-comment-id:3385026064 --> @msmps commented on GitHub (Oct 9, 2025): I might be missing something here so forgive me but I am able to get your reproduction working? Are you able to try with `v0.1.26` as this does include some bug fixes ![Image](https://github.com/user-attachments/assets/0f9e3051-abf6-4d09-a78d-ffc657001834)
Author
Owner

@joshkotrous commented on GitHub (Oct 9, 2025):

It looks like this has been fixed in 0.1.26, upgrade fixed it 🚀

<!-- gh-comment-id:3386851539 --> @joshkotrous commented on GitHub (Oct 9, 2025): It looks like this has been fixed in `0.1.26`, upgrade fixed it 🚀
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/opentui#47
No description provided.