Skip to content

Commit 1be0c32

Browse files
[feat] scroll-to-bottom button + status indicator redesign (#360)
1 parent e7b4535 commit 1be0c32

18 files changed

+1027
-396
lines changed

cli/knowledge.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,55 @@ The cleanest solution is to use a direct ternary with separate `<text>` elements
360360

361361
**Note:** Helper components like `ConditionalText` are not recommended as they add unnecessary abstraction without providing meaningful benefits. The direct ternary pattern is clearer and easier to maintain.
362362

363+
### Combining ShimmerText with Other Inline Elements
364+
365+
**Problem**: When you need to display multiple inline elements alongside a dynamically updating component like `ShimmerText` (e.g., showing elapsed time + shimmer text), using `<box>` causes reconciliation errors.
366+
367+
**Why `<box>` fails:**
368+
369+
```tsx
370+
// ❌ PROBLEMATIC: ShimmerText in a <box> with other elements causes reconciliation errors
371+
<box style={{ gap: 1 }}>
372+
<text fg={theme.secondary}>{elapsedSeconds}s</text>
373+
<text wrap={false}>
374+
<ShimmerText text="working..." />
375+
</text>
376+
</box>
377+
```
378+
379+
The issue occurs because:
380+
1. ShimmerText constantly updates its internal state (pulse animation)
381+
2. Each update re-renders with different `<span>` structures
382+
3. OpenTUI's reconciler struggles to match up the changing children inside the `<box>`
383+
4. Results in "Component of type 'span' must be created inside of a text node" error
384+
385+
**✅ Solution: Use a Fragment with inline spans**
386+
387+
Instead of using `<box>`, return a Fragment containing all inline elements:
388+
389+
```tsx
390+
// Component returns Fragment with inline elements
391+
if (elapsedSeconds > 0) {
392+
return (
393+
<>
394+
<span fg={theme.secondary}>{elapsedSeconds}s </span>
395+
<ShimmerText text="working..." />
396+
</>
397+
)
398+
}
399+
400+
// Parent wraps in <text>
401+
<text style={{ wrapMode: 'none' }}>{statusIndicatorNode}</text>
402+
```
403+
404+
**Key principles:**
405+
- Avoid wrapping dynamically updating components (like ShimmerText) in `<box>` elements
406+
- Use Fragments to group inline elements that will be wrapped in `<text>` by the parent
407+
- Include spacing as part of the text content (e.g., `"{elapsedSeconds}s "` with trailing space)
408+
- Let the parent component provide the `<text>` wrapper for proper rendering
409+
410+
This pattern works because all elements remain inline within a single stable `<text>` container, avoiding the reconciliation issues that occur when ShimmerText updates inside a `<box>`.
411+
363412
### The "Text Must Be Created Inside of a Text Node" Error
364413

365414
**Error message:**

0 commit comments

Comments
 (0)