demo.mp4
Simple TUI library written using Rust and TypeScript
Core dependencies:
TODO:
- Will SIMD work if I wanna implement caching for serialization. For example, when comparing trees I used SIMD (idk what i'm talking about)
- Hover support
- Add render caching - skip serialize/layout if signals unchanged (pi-mono pattern)
- Incremental tree updates - don't rebuild entire Taffy tree each frame, cache structure and update only changed nodes
- Visibility culling - skip
paint()for off-screen nodes (OpenTUI's_getVisibleChildrenpattern) - Move paint to Rust (currently 81% of frame time @ 1.7ms avg)
- Why: Eliminates JS per-cell loops, staging buffer, and BigInt conversions
- New FFI function:
paint(node_data, text_data, focused_id, pressed_id, colors...) - Rust side:
- Reuse parsed node tree from
calculate_layout(already has frames) - Add
PaintNodestruct with: frame, bg, fg, border_color, border_style, text range, node_type - Implement
draw_background(),draw_border(),draw_text(),draw_cursor()writing directly toCURRENT_BUFFER - Recursive
paint_node()traversal matching JS logic
- Reuse parsed node tree from
- TS side:
- Add
isFocusedandisPressedfields to serialization (FIELDS_PER_NODE: 13 → 15) - Replace JS
paint()call withapi.paint(...)FFI call - Keep
registerHit()in JS for mouse hit testing (cheap traversal) - Remove
stagingBufferandflushStagingBuffer()(no longer needed)
- Add
- Expected result: Paint phase from 1.7ms → ~0.1-0.2ms
- Batch signal updates - ensure
whenSettledis used in hot paths to prevent redundant renders - Try to optimizatize the shit out of everything
- Add logging and debugging utilities
- Improve overall architecture ("make it work" is done, now "make it right, make it fast" is left)
- Refactor flush function with BatchWriter pattern to reduce nesting
- BatchWriter struct holds stdout ref, char_seq, batch_start_x/y, prev_fg/bg
new()initializes with sentinel colors (u64::MAX) to force first color emitpush(x, y, ch, fg, bg)handles gap detection, color changes, and accumulates charsflush_pending()emits MoveTo + Print for accumulated batch- Encapsulates all batching logic, main loop just calls push() for changed cells
- Add scrollable containers
- Add performance stats overlay that update independently from rest of the app (can i use a separate thread?)
- Add
flexGrowsupport for dynamic width components (e.g., progress bars)- TypeScript side:
- Add
flexGrow?: numbertoStylePropsintypes.ts - Update
createStyleSignals()incomponents.tsto includeflexGrow: $(input.flexGrow) - Update serialization in
runtime.tsto passflexGrowvalue to Rust (add toFIELDS_PER_NODE)
- Add
- Rust side:
- Increment
FIELDS_PER_NODEfrom 12 to 13 inlib.rs - Add
flex_grow: f32field toNodestruct - Parse
flex_growinparse_node()function - Apply
style.flex_grow = node.flex_growinget_styles()for all node types
- Increment
- Progress bar update:
- Remove fixed
widthprop fromProgressBar - Instead of
" ".repeat(n), use a single space" "for text - Set
flexGrow: progress / 100on filled node,flexGrow: (100 - progress) / 100on unfilled node - Layout engine distributes space proportionally - bar auto-sizes to container
- Remove fixed
- Benefits: No fixed width needed, bar fills available space, cleaner API
- TypeScript side:
- push your changes
- update versions in
Cargo.tomlandpackage.json git tag v0.0.1- tag a commitgit push origin v0.0.1- push the tag- release action will build and deploy it as package