
Recovering web dev. @opencode at @anomalyco
Copenhagen
Joined July 2009
- Tweets721
- Following344
- Followers1.7K
- Likes2.6K
Simon Klee retweeted
what if your tui could build little interactive UIs inline?
now in (my local) opencode
This was one of the main reasons I wanted plugins using opentui/keymap to be this powerful.
OpenCode 2 plugins are really powerful. Vim mode has been requested for a while. Here’s a plugin that gives OpenCode more Vim functionality than any other harness out there.
github.com/Tarquinen/opencod…
Simon Klee retweeted
OpenCode 2 plugins are really powerful. Vim mode has been requested for a while. Here’s a plugin that gives OpenCode more Vim functionality than any other harness out there.
github.com/Tarquinen/opencod…
Simon Klee retweeted
OpenCode has a "mini" mode where it shows a small TUI instead of the full screen one
'opencode mini'
Do NOT watch this.
Good thing I said Thursday for stream in that podcast.
I interviewed Sebastian Herrlinger (@kmdrfx) about building TUIs with Zig & Typescript! 💯
We talked about OpenTUI: the framework behind @opencode
🎬 youtube.com/watch?v=ohfWF6SA…
Next up: Tris from @NoBoilerplate shows us his terminal setup on September 22! 🦀
Join our Discord: terminalcollective.org
#terminal #tui #opensource #podcast #opencode #opentui
Simon Klee retweeted
exploring how we can make oc mini feel more consistent with the terminal app
┣ compact start screen
┣ similar design language
┣ prompt/search same line
┗ native terminal scrollback
Simon Klee retweeted
I built a TUI design tool for my work at @opencode and I’ve been using it for the last 2 weeks
→ Design directly on the terminal grid
→ ASCII borders that join automatically
→ Flexbox, multiplayer, sections etc
tooey.design
Native image support is in the latest OpenTUI version. Now you can easily vibe your own xdotcom TUI client.
Try this one with: bunx xtooey
◈ Supports PNG, JPEG, WebP, GIF, and raw RGBA images
◈ Native decoding via Wuffs (PNG/GIF), stb_image (JPEG), and libwebp
◈ ICC color management and sRGB conversion via LittleCMS
◈ Native resizing via stb_image_resize2
◈ Supports resize, crop, padding, rotation, flipping, and compositing
◈ Handles alpha, EXIF orientation, and PNG color metadata
◈ Renders through Kitty, Sixel, or Unicode block graphics
◈ Lazy PNG decoding with direct Kitty PNG passthrough
◈ Loads from files, URLs, blobs, responses, and byte buffers
◈ Portable native implementation for Bun and Node across Linux, macOS, and Windows
Native audio capture now in OpenTUI.
◈ Device enumeration and selection
◈ Backpressured PCM ReadableStream
◈ Interleaved mono, stereo, and multichannel
◈ Float32Array data
◈ Bounded buffering with dropped-frame statistics
◈ Stop-and-drain, cancellation, and disposal lifecycle
◈ PCM16 WAV recording
◈ Microphone permission and signal-loss diagnostics
Available with the next release.
What's next for OpenTUI? Here's a technical write-up.
Over the last few months OpenTUI gained a lot of stability improvements, new unnecessary but fun features like live audio streaming, and useful features like rendering to the scrollback buffer mixed with a live TUI, called footer mode. Overall the feature set enables building large and complex applications. React and Solid make it super simple and convenient. There is still so much to do though.
Three big milestones we have set out to achieve are:
- Moving most of the behavioural logic currently living in TypeScript down to the native Zig core
- Node compatibility
- Optimizing the hell out of primitives like text rendering
The render tree mechanisms are currently only usable from TypeScript. Think of the DOM, but controllable like a scene graph. Elements in the render tree are called renderables. They can expose a render method to draw themselves. All renderables are derived from a BaseRenderable. Renderables and the render tree will become native primitives. Building blocks usable from any language bindings. Reducing the TypeScript bindings to a very thin layer, with all the behavioural logic living in the native binary.
Moving this down is not just a matter of porting TypeScript classes to Zig. TypeScript currently owns the tree, dirty-state propagation, layout reads, culling, and render ordering. If it still has to walk every node and call into native code for each step, we keep most of the complexity and add FFI overhead. Whole passes and their state need to move together.
We took a big step towards this recently by building yoga-layout into the native binary. It exposes part of the official yoga-layout TypeScript package via FFI. Only the API surface that is actually used by OpenTUI. Covered by the test suite of the original yoga-layout package. This already gave a median speedup of ~2.5x, and up to 30x for narrow scenarios. The yoga-layout integration is useful beyond the speedup. Built-in text and editor measurement can now happen entirely in native code during layout instead of calling back into JavaScript.
I ran an experiment last month taking this even further, having GPT 5.6 port yoga-layout from C++ to Zig, which gave extremely good results. It would be a burden to maintain right now though, so that's off the table for now. I might come back to it.
@simonklee is working relentlessly on Node compatibility and already has a full Node version of OpenCode running. Node got FFI support in v26.4.0, thanks to help from the Node community, namely @matteocollina and @p_insogna. Behaviour and interfaces seem similar between Node and Bun, but there are some major differences. To get the best performance out of the Node FFI implementation, its usage has to follow some rules.
Node has three ways to call native functions: the generic C++/libffi path, the SharedBuffer path, and the V8 Fast API.
The generic path converts every argument in Node's C++ layer and then calls the function through libffi. It is flexible, but also the slowest option for frequently called functions.
The SharedBuffer path is a middle ground. JavaScript writes scalar values and BigInt pointers into a small per-function buffer, reducing some conversion work. The actual native call still goes through libffi though. Typed arrays used as pointers cannot be packed into this buffer and fall back to the generic path.
The path we really want is the V8 Fast API. Node generates a small machine-code trampoline for the exact function signature, allowing optimized JavaScript to call the native function without going through the generic converter or libffi. This only applies to JavaScript-to-native calls. Callbacks from native code into JavaScript still use libffi closures.
Getting onto this path is quite strict. A signature can have at most eight arguments and everything must fit into CPU registers. x86-64 Unix systems have room for six GP (general-purpose) and eight FP (floating-point) arguments. AArch64 has room for seven GP and eight FP arguments. Anything that spills onto the stack falls back to a slower path.
These are Node fast-path restrictions, not general FFI restrictions. Bun also does not support passing structs by value through its current FFI API. OpenTUI uses bun-ffi-structs to pack ABI-aligned struct data into an ArrayBuffer and passes a pointer instead. Despite the name, the package also works with Node.
Pointers need some care too. Typed arrays and ArrayBuffers normally have to be resolved into BigInt addresses first. Eligible functions with exactly one pointer argument get another Fast API entrypoint that can extract the address directly from the buffer.
An eligible signature is still not enough. V8 has to optimize a direct call with a fixed number of consistently typed arguments. Wrappers that collect arguments and forward them using spread or Reflect.apply can hide that call shape and keep the function on a slower path.
The practical rules are: keep hot signatures within register limits, use direct fixed-arity calls with stable argument types, reuse owned buffers safely, and batch small operations. Then measure the real call site, because eligibility only makes a function fast-capable.
We have to design the ABI around these constraints where it makes sense and gives the expected performance improvement.
The third big area is text rendering. Today a Text renderable accepts a string, StyledText, or a tree of TextNodes. Before rendering, the TextNode tree is walked and flattened into styled chunks. Those chunks are packed in TypeScript, sent through FFI, copied into a native TextBuffer, and stored in a rope. Styles are represented separately as highlights. A TextBufferView then wraps the rope into visual lines, which are drawn into the visible buffer.
This works, but updates are much more expensive than they should be. setStyledText effectively throws away and rebuilds the rope, copies and reparses all text and recreates the style highlights. Changing one TextNode also walks and flattens the complete tree before going through this path again. Text and style segments should instead live directly in the rope and support incremental replacement.
Memory ownership is split between retained JavaScript buffers, the native memory registry, rope arenas, wrapping caches, styled-text storage, and highlights. Different operations preserve or reset different parts of that state. This is hard to reason about and can retain memory for much longer than expected. Text storage needs clearer ownership, with fewer lifetimes split across JavaScript and native code.
The public API reflects the same split. The t template literal is convenient, but creates another intermediate chunk representation that is mutable, not cached, and not merged. Text also maintains both StyledText content and a special TextNode tree, which do not compose properly. TextNode is only a style scope, not a normal layout primitive, so Text renderables cannot naturally compose inside each other. I think this should become one Text primitive backed directly by rope segments. The template literal API might disappear or become a very thin helper around those native segments.
Editing has another temporary layer in TypeScript. Extmarks currently monkey-patch editing operations, scan and adjust all marks after changes, maintain their own undo state, and recreate native highlights. They should become native marks anchored directly in the rope. A proper mark tree, similar to Neovim's marktree, could update marks together with edits, undo, and redo, and provide the foundation for highlights and concealment.
Text wrapping has also become too complex. Supporting CJK, emoji, combining characters, ZWJ sequences, tabs, and different terminal width rules currently mixes byte offsets, grapheme indexes, and display-cell columns across several custom algorithms. Dirty views rewrap the complete document. Measurement and drawing can repeat some of the same work.
The wrapping implementation needs an overhaul, but the exact shape is still open. The goal is to make Unicode handling easier to maintain, avoid repeated full-document work, and clearly separate byte offsets, graphemes, and terminal display cells.
None of this will happen as one big rewrite. We will replace pieces when we understand the problem well enough and when the result is clearly simpler, faster, or more useful.
To achieve all of this we might break public interfaces. Thanks to OpenCode and a lot of good models, migration to a new version with breaking changes mostly is not an issue anymore.
What do you want to see next for OpenTUI?
Simon Klee retweeted
Lately the narrative of model capability hitting a "good enough" saturation point and its implications has been a talking point.
I used to go back and forth on this, but now lean towards the "640K should be enough for anybody" analogy being correct.
Reasoning is -
Simon Klee retweeted
Continuing my 'i have a problem' series.
A demonstration of the dispersion of light by a prism
Simon Klee retweeted
taking advantage of the new inline 𝚘𝚙𝚎𝚗𝚝𝚞𝚒 rendering system for some @opencode micro-animations ⭐
Simon Klee retweeted
terminal-control lets your agents... control any TUI, record a video of it, and edit the output so you get those nice little section titles in the bottom-left corner.


















