Simon Klee (@simonklee) header
Simon Klee (@simonklee) profile picture

Recovering web dev. @opencode at @anomalyco

Copenhagen
Joined July 2009
Photo by Simon Klee (@simonklee)Photo by Simon Klee (@simonklee)Photo by Simon Klee (@simonklee)Photo by Simon Klee (@simonklee)Photo by Simon Klee (@simonklee)Photo by Simon Klee (@simonklee)Photo by Simon Klee (@simonklee)Photo by Simon Klee (@simonklee)Photo by Simon Klee (@simonklee)Photo by Simon Klee (@simonklee)
Simon Klee retweeted
what if your tui could build little interactive UIs inline? now in (my local) opencode
4
2
88
5,172
Simon Klee retweeted
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…
4
157
12,172
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…
13
23
4
413
44,031
Simon Klee retweeted
OpenCode has a "mini" mode where it shows a small TUI instead of the full screen one 'opencode mini'
9
3
1
163
11,445
Simon Klee retweeted
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
4
2
14
1,973
Simon Klee retweeted
So @opencode v2 plugins became even better! Here's a plugin to view issues / PR's right inside the TUI Add this to your opencode.jsonc (you need the gh cli though!) { "plugins": ["[email protected]"], }
9
10
1
208
19,996
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
17
3
105
7,724
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
46
69
16
1,216
110,135
Simon Klee retweeted
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
8
10
2
117
17,040
Simon Klee retweeted
LateX for OpenTUI just dropped npm i opentui-math
1
2
24
2,589
Simon Klee retweeted
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.
10
14
2
326
96,187
Simon Klee retweeted
base ui + shadcn/ui, but for opentui terminal interfaces tuiparts.sh is out
12
26
6
454
43,431
Simon Klee retweeted
Had opencode create a small TUI to test some text-to-speech models. With option to save the generated audio. Using OpenTUI of course. These oss/open-weight TTS models have become really good.
8
13
3
341
25,795
Simon Klee retweeted
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?
23
28
3
384
31,331
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 -
gpt 5.6 ought to be enough for anybody.
1
2
5
977
Simon Klee retweeted
cleaning up our experimental tui plugin api so it can officially be part of v2 you get the full power of opentui so you can do quite a bit with it
49
21
3
910
68,140
Simon Klee retweeted
Continuing my 'i have a problem' series. A demonstration of the dispersion of light by a prism
3
1
13
1,446
Simon Klee retweeted
taking advantage of the new inline 𝚘𝚙𝚎𝚗𝚝𝚞𝚒 rendering system for some @opencode micro-animations ⭐
13
7
420
36,841
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.
issue comes in tag opencode opens a PR with a video verifying fix
5
10
169
17,426
Simon Klee retweeted
What is a TUI without proper live audio stream support? Right. Proper live audio streaming support in OpenTUI in the next release.
2
11
2
133
14,742