The approach you're describing (redesign the protocol) is one path. Another is to sidestep the protocol entirely by using a platform that already owns its rendering surface and happens to run in a terminal: my preference is Neovim.
Let me get the caveats out of the way first: this only makes sense if you already live in Neovim. The whole ergonomic case is muscle memory you already have. If you're not a Neovim user, you're just buying another learning curve. Also, the language is Lua (not Rust) so it won't appeal to the ratatui crowd on that front, and there's no proper layout system yet.
Neovim [0] renders to its own buffer system (extmarks, floating windows, virtual text) rather than emitting ANSI escape codes. Under the hood it's still a terminal app, but from the UI author's perspective you're not fighting terminfo, CSI sequences, or varying terminal support. You get proper cursor tracking, scroll regions, and editable text areas out of the box.
On the UI framework side, morph.nvim [1] provides a React-like component model (h(), state, reconciliation) on top of Neovim. It's not a general answer the way ratatui is, but for the slice of devs already in Neovim (there are dozens of us!), it's a pragmatic way to get GUI-framework-like ergonomics without the terminal protocol fight.
For a sample of what TUIs in this paradigm can look like, I've been working on tuis.nvim [2] (Docker, K8s, SystemD services, process management, and more all as native Neovim buffer TUIs).
I have built a pretty full version of that idea in rust (codex tui2). There's a while tonne of downsides that make it basically impossible to really get to 100% good on this.
> Under the hood it's still a terminal app, but from the UI author's perspective you're not fighting terminfo, CSI sequences, or varying terminal support. You get proper cursor tracking, scroll regions, and editable text areas out of the box.
All that is not true. Take scrolling for example. Each terminal emulator implements this subtly differently (mouse/trackpad/speed etc.), scrolling will never feel native unless you push the control of it into the terminal emulator.
There's more than that, each and every point on this becomes something that you have a gap between native and the terminal emulator that you're building to run in the terminal.
See https://github.com/openai/codex/issues/8344 and https://github.com/openai/codex/pull/9640
Claude code and gemini have both had various versions of the same idea at times. I'm unsure where they ended up.