NIMBL — Devlog #2: TUI Design(Hell)
This devlog covers how I actually built the thing, and the nasty bugs I had to fix. Also that I should probably pause this to further learn frontend. Cause iam trash at frontend
Choosing OpenTUI (and Fighting It)
I needed a terminal UI framework that could run under Bun on Windows. Textual was too heavy. Ink (React) had reconciler conflicts. OpenTUI was the right call. It’s what OpenCode uses in production, it’s fast, and its SolidJS works well with the reactive state model. But it wasn’t plug and play. Which I didn’t expect.
Out of the 9 hours I spent on this phase, maybe 2 were actual feature work. The rest was bug fixing.
First wall: the native DLL kept crashing. OpenTUI tries to pass color objects to a Windows native DLL, but the way they format the data doesn’t match what the DLL expects. Every time the TUI tried to draw anything, Bun would crash. I couldn’t figure this one out on my own. Had to get help from an AI coding agent (the newly bought OpenCode Go) to trace through the crash logs and find the fix. The solution was simple once found: use color hex strings like "#06402b" everywhere instead of color objects. OpenTUI handles the conversion internally and the DLL stops crashing.
Second wall: wrong import path. OpenTUI’s SolidJS build tries to import from a file path that Bun 1.3.14 can’t load properly, causing a different kind of crash. One line change in the library file. Change solid-js/dist/solid.js to solid-js and it worked. Small fix, but took hours to find because the error message just said “Cell” with no explanation. Probally should have used Ai for that
Studying OpenCode’s Architecture
Instead of guessing the layout, I got AI to pull anomalyco/opencode from GitHub and read through their TUI package. Two key discoveries:
The prompt input works like OpenCode’s. I tried using the single-line input component first, but it wouldn’t submit on Enter. OpenCode uses a multi-line textarea with a trick: they grab the text directly from the component on every keystroke, and when you press Enter (without Shift or Ctrl), they intercept it, stop it from making a new line, and send the message instead. I do the exact same thing now and it actually works.
The green bar on the left. Every input box and every message in OpenCode has a thin vertical colored line on its left edge. It’s just a simple border, but it makes the whole interface feel structured and intentional. I borrowed that. Green accent bar for user prompts, dimmer gray for NIMBL replies. Cheap to render, huge visual upgrade.
TUI Structure
Two screens, one signal: view() toggles between "home" and "chat".
Home screen – Centered layout with the ASCII NIMBL logo, tagline, and a bordered textarea. The green left-border accent matches my brand.
Chat screen – Scrollbox of message bubbles (each has an accent bar + label), with a persistent input at the bottom. Token count and estimated cost update in the status bar on every response. /quit and /clear are wired in both views.
What’s Next
the app works. I type a prompt, Enter submits it, the API calls, and the response renders in the chat view with token usage shown. The next plan is adding all the modes, AI provider switching, plan and build mode, and letting the AI read and write files. Plus the /compact command from OpenCode. The screenshots show how it looks now and how it looked before. Heavily inspired by OpenCode and some elements are even copied, so the readme will fully credit them. Also I’m allowed to use them as they are under MIT license. Bless OpenCode.
Stack: Bun 1.3.14 · OpenTUI 0.4.5 · SolidJS 1.9.10 · TypeScript strict · Vercel AI SDK 7