Kicking the tyres: how fictty found its stack
Aram nearly shelved fictty. So we went looking for something that already did the job, found the one piece nobody has built, and settled the stack: ratatui, caretline, and a store that keeps everyone's work safe.
Tokens≈110.5M through the model · 387k written · Claude Opus 5.5
What the work in this post took, from 10 Oct, 18:39 to 11 Oct, 05:23 UTC: 408 model calls on Claude Opus 5.5. Most of it is cache reads: every call re-sends the conversation so far, and the cache serves it. New input is what was added (messages, files read, tool output), and output is what the model wrote, its thinking included.
cache readsnew input and outputThe two context compactions in this window aren't in the transcript, so they aren't counted.Counted from the session transcripts with scripts/blog-tokens.py, each response once.
A conversation between Aram (@brancusi), who is building fictty, and Tilde, the name Claude (Anthropic’s model) goes by on this project; a tilde is home in a terminal. The quotes are snippets from one working session on 10 and 11 October 2026, lightly edited. The earlier posts asked why the terminal and what agent UI is for. This one is about the tools.
The doubt
I’m not sure about the project anymore. I think we should explore existing options first.
Then let’s try the tools most likely to make fictty unnecessary. If one of them already does the job, better to find out today than after another month of Rust.
So we kicked the tyres on three of them:
- json-render, on its Ink terminal renderer: agents write a JSON spec, the terminal draws it.
- OpenTUI, the polished toolkit behind opencode.
- ratatui and its ecosystem, the engine fictty already used.
What we found
json-render’s format is genuinely good: dashboards look right with no design work, and screens stream in as small patches. But on Ink there’s no full screen, no scrolling, no mouse, and a cap of 30 redraws a second. And its form’s Tab key was broken as published (a one-line fix we owe them upstream). OpenTUI has everything Ink lacks: mouse, scroll boxes, images, Markdown, diffs, an embedded terminal.
Then Aram named what all three had in common:
So basically we’re missing the ability to push UI, patch that UI, and get state. And therefore I can’t really get an agent to drive the state of the user’s TUI.
That’s the whole gap, in one sentence. Every one of these is a library for whoever writes the app. None has a door an outside agent can use while the screen is running.
| JSON format | Agent can attach | Full TUI | |
|---|---|---|---|
| json-render | yes | no | no |
| OpenTUI | no | no | yes |
| ratatui | no | no | with crates |
| fictty | yes | yes | yes |
That loop is fictty. The question changed from “should fictty exist?” to “which engine goes under it?”
Settling the stack
OpenTUI looked lovely, so we compared it with ratatui honestly:
ratatui is proven and fits fictty’s own rule: redraw everything from one state, every frame. What it lacks is a kit. Its ecosystem is 1,116 crates of one widget each, all styled differently. That’s a job fictty can do: wrap the best crates behind one JSON spec and one theme, with OpenTUI’s component list as the target.
For text we chose caretline over the popular tui-textarea. tui-textarea keeps its text inside the widget, where nothing can save, read back or replay it. caretline keeps it in plain state, the same way fictty does.
The question that shaped the design
Once agents could push screens and write text into them, Aram spotted the danger:
We need to figure out how we split UI state and app state, so we don’t blow away any data that was added by the user or other agents.
Split it by who owns it, and give each owner its own rules.
The details are in the state and theme proposal.
The overnight build
Get a working prototype with enough components to build a note-taking app or a financial accounting app. Build a beautiful demo, like the one from OpenTUI. Have it ready for the morning.
By morning there was a showcase: notes, a ledger, a live market, a gallery and an agent’s log. Every screen is a JSON file, and they share one store and three themes.

The ledger is my favourite, because its JSON holds no arithmetic. A small helper process reads the store and streams back the balance, the categories and the running chart.




You can run this exact showcase yourself, in one line:
$ curl -fsSL https://fictty.com/try/settling-the-stack.sh | shPaste it in a terminal: it runs right there. Needs git (Rust only on other systems); everything goes in ~/.fictty.
It’s pinned to fictty 0.1.0, the release this post describes, so it shows what you see here even
after fictty moves on. It downloads that version into ~/.fictty (a ready-made binary on macOS
and Linux; elsewhere it builds with Rust) and opens the menu. Arrows and Enter open a screen, t changes the
theme on the menu, Esc goes back. To watch an agent drive it, open the Agent screen, click
tour.sh to copy it, and paste it into a second terminal. For the latest fictty instead, use
https://fictty.com/try.sh.
The morning: one number worth the night
In the morning Tilde drove the showcase from outside, writing into Aram’s notes over the socket while Aram watched. Then Aram typed a line into Tilde’s note.

One sentence of typing took the note from revision 1 to 47. So an agent that reads a note, thinks, and then writes “based on what I read” will be refused by every keystroke in between. For text, an agent’s edit has to fit around the person’s typing, not wait for a gap. Ten minutes of using the showcase taught us that; thirty-seven passing tests didn’t.
What we’re taking with us
- The format and the loop are different problems. Good JSON UI formats exist. A runtime an agent can attach to, drive and read back didn’t, and that’s fictty’s ground.
- Pick the engine for its rules, not its demo. Polish we can add; the architecture is harder to change.
- State has owners. Most of the design came from one worry: don’t blow away what someone else added.
- Screens are data, never code. When a form needed to reject “3-” as an amount, the fix was a
behaviour of the input (
"accept": "number"), not a condition in the JSON. - The agent needs to know when you’ve acted. Aram had to tell Tilde “I wrote something”. A
watchcommand is now first in line.
What’s next
The plan runs in this order: close the agent loop (watch, and text edits that fit
around typing), then records keyed by id, then layout and the kit. Everything we tried, decided and
learned is in the prototype notes.
We went looking for a reason to stop, and came back with a reason to keep going, and a stack.