Fork of thebanri/limoni for upstream PRs
  • Go 97.8%
  • Go Template 0.7%
  • Rust 0.7%
  • JavaScript 0.5%
  • Shell 0.2%
  • Other 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
merith-tk a62ce3aa49
Some checks failed
Limoni Platform Smoke / platform-smoke (linux, ubuntu-latest) (push) Failing after 41s
Limoni Platform Smoke / platform-smoke (macos, macos-latest) (push) Has been cancelled
Limoni Platform Smoke / platform-smoke (windows, windows-latest) (push) Has been cancelled
Re-export KeyInsert and F1-F12 at package root
Downstream keybinding files that only import the limoni root package
currently need a second import (limoni/core/driver) just for the
function keys. Re-export them alongside the other key constants.

Closes #83.
2026-10-15 17:51:23 -07:00
.agents/skills/limoni_development docs: translate documentation to English and update README with attribution and updates 2026-09-09 23:17:32 +03:00
.claude/skills docs: new widgets, markers, Viewer3D's real API, scroll regions and what the published benchmarks do not include 2026-09-24 16:53:41 +03:00
.github playground: a half-lemon logo 2026-10-14 20:02:03 +03:00
animation docs: comments in English across the library 2026-09-29 20:15:25 +03:00
apps lemonsnake: test helpers in a file every platform builds 2026-10-15 12:02:50 +03:00
assets playground: a half-lemon logo 2026-10-14 20:02:03 +03:00
automation engine: deliver command results as they finish; clicks are press and release 2026-10-05 11:25:12 +03:00
backdrop feat(backdrop): pictures and ASCII art as backgrounds 2026-09-29 14:35:59 +03:00
benchmark-results Optimize SetCell and Diff styling hot-path: inline SetCell indexing, replace strconv.AppendInt with custom appendUint8/appendUint16 helpers, bypass Downsample in truecolor mode 2026-08-12 17:11:22 +03:00
benchmarks bench: re-run the comparison with scroll regions and ICH/DCH on 2026-10-04 15:04:21 +03:00
cmd playground, zest: digits switch scenes in the zest scene too; l steps the level 2026-10-14 20:02:03 +03:00
compat/bubbletea docs: comments in English across the library 2026-09-29 20:15:25 +03:00
component test: component handlers, dividers, and ClickActions around a modal 2026-10-04 14:31:27 +03:00
core terminal, graphics: Sixel pictures after the cells, sized, with exact flat colours; release: v0.12.1 2026-10-14 17:32:57 +03:00
docs examples: file_browser and dashboard_gauges for the v0.9 widgets (#81) 2026-10-13 01:30:30 +03:00
examples playground: a half-lemon logo 2026-10-14 20:02:03 +03:00
graphics terminal, graphics: Sixel pictures after the cells, sized, with exact flat colours; release: v0.12.1 2026-10-14 17:32:57 +03:00
internal playground, zest: digits switch scenes in the zest scene too; l steps the level 2026-10-14 20:02:03 +03:00
layout docs: comments in English across the library 2026-09-29 20:15:25 +03:00
platform-results docs: include multi-platform CI evidence and update roadmap 2026-08-11 16:55:18 +03:00
scripts playground, zest: digits switch scenes in the zest scene too; l steps the level 2026-10-14 20:02:03 +03:00
session fix(engine): pointer motion is MouseMotionMsg, and the wheel knows where it is 2026-10-04 13:30:28 +03:00
testkit feat(widgets): StatusBar and rich Text in the semantic tree 2026-10-05 00:27:51 +03:00
tools/limonivet feat(limonivet): report Blocks that draw nothing 2026-10-04 14:21:02 +03:00
uitest engine: deliver command results as they finish; clicks are press and release 2026-10-05 11:25:12 +03:00
widgets perf(terminal): send each kitty picture once, then only place it 2026-10-05 00:36:51 +03:00
.gitignore feat(pikap): a record player in the terminal 2026-10-05 05:54:53 +03:00
.goreleaser.yaml pikap: Limoni v0.12.0, and release binaries for apps tagged on their own 2026-10-05 19:40:50 +03:00
app.go feat: leave the mouse to the terminal on request 2026-10-05 00:25:36 +03:00
app_backdrop_test.go feat(backdrop): animated scenes behind an application 2026-09-29 12:18:27 +03:00
app_gateway_debug.go feat(automation): verify the connecting user with the kernel, and compile the gateway out of release builds 2026-09-13 08:25:12 +03:00
app_gateway_release.go feat(automation): verify the connecting user with the kernel, and compile the gateway out of release builds 2026-09-13 08:25:12 +03:00
app_gateway_release_test.go feat: run several immediate-mode apps in one process, and stop them with a context 2026-09-19 19:24:04 +03:00
app_idle_test.go engine: a Program without a frame rate no longer ticks 2026-09-26 00:36:11 +00:00
app_isolation_test.go feat: run several immediate-mode apps in one process, and stop them with a context 2026-09-19 19:24:04 +03:00
app_test.go refactor: reorganize core packages and add component API 2026-09-09 23:02:25 +03:00
app_testhelper_test.go feat: run several immediate-mode apps in one process, and stop them with a context 2026-09-19 19:24:04 +03:00
automation_integration_test.go feat: run several immediate-mode apps in one process, and stop them with a context 2026-09-19 19:24:04 +03:00
AWESOME.md docs(awesome): list what exists, under what it is 2026-10-04 13:36:35 +03:00
builders.go feat(widgets): add Viewport, Scrollbar, Tabs and Spinner 2026-09-12 14:34:46 +00:00
CHANGELOG.md terminal, graphics: Sixel pictures after the cells, sized, with exact flat colours; release: v0.12.1 2026-10-14 17:32:57 +03:00
CLAUDE.md lemonsnake: a snake that eats lemons and fights a monster 2026-10-14 19:35:22 +03:00
CODE_OF_CONDUCT.md feat: complete English localization for examples, bilingual docs, 3D rasterizer and shadow fixes 2026-08-17 22:56:31 +03:00
components.go feat(component): add Overlay, Transform, Inline, and half-block border presets 2026-09-10 11:28:25 +03:00
CONTRIBUTING.md docs: a README that starts with the reason to care, and the depth moved to docs/ 2026-09-19 18:46:48 +03:00
doc.go docs: package overview, llms.txt, and when to choose Limoni 2026-09-24 21:27:32 +03:00
example_test.go docs: runnable examples for pkg.go.dev 2026-10-04 13:38:56 +03:00
facade_components_test.go test(root): render every facade helper and check what lands on screen 2026-09-20 10:56:44 +03:00
facade_options_test.go test(root): render every facade helper and check what lands on screen 2026-09-20 10:56:44 +03:00
go.mod ci: fix module hygiene checksums and enforce tidy across all submodules 2026-09-12 19:07:24 +03:00
go.sum ci: fix module hygiene checksums and enforce tidy across all submodules 2026-09-12 19:07:24 +03:00
inline_test.go feat: run several immediate-mode apps in one process, and stop them with a context 2026-09-19 19:24:04 +03:00
layouts.go fix: emoji unicode widths, modal drag border tearing, and root fluent API simplicity 2026-09-09 13:47:50 +03:00
LICENSE Initial commit 2026-08-06 23:25:14 +03:00
limoni.go Re-export KeyInsert and F1-F12 at package root 2026-10-15 17:51:23 -07:00
LIMONI_ROADMAP.md docs: translate documentation to English and update README with attribution and updates 2026-09-09 23:17:32 +03:00
limoni_test.go feat(component): add Spacer, Divider, Margin, BorderEdges, Constraints, Place, ForEach, Dynamic/TextRef/TextFn, OnKey/OnRune 2026-09-10 00:28:01 +03:00
llms.txt docs: package overview, llms.txt, and when to choose Limoni 2026-09-24 21:27:32 +03:00
program.go feat: leave the mouse to the terminal on request 2026-10-05 00:25:36 +03:00
program_test.go engine: deliver command results as they finish; clicks are press and release 2026-10-05 11:25:12 +03:00
README.md lemonsnake: fill the board to win 2026-10-15 11:59:49 +03:00
README_TR.md lemonsnake: fill the board to win 2026-10-15 11:59:49 +03:00
SECURITY.md docs: a README that starts with the reason to care, and the depth moved to docs/ 2026-09-19 18:46:48 +03:00
styles.go feat: OSC 8 hyperlinks, in markdown and in any style 2026-09-20 10:45:31 +03:00
title_app_test.go Merge main into contrib/title 2026-09-20 10:20:19 +03:00
TODO.md docs: document font gap issue, half-block baseline fix, and terminal settings FAQ 2026-09-10 22:06:33 +03:00

Limoni Logo

🍋 Limoni

A terminal UI engine for Go that tests can click, AI agents can drive,
and the garbage collector never sees.

Build Status Go Reference Go Version License Zero Allocations

English • Türkçe

▶ Try it in your browser — the same engine compiled to WebAssembly, no install.

Limoni rendering a shaded 3D model in the terminal


Quick start

go get github.com/thebanri/limoni
package main

import "github.com/thebanri/limoni"

func main() {
	limoni.Run(func(f *limoni.Frame, ev *limoni.Event) bool {
		if ev != nil && ev.Type == limoni.EventKey && ev.Key.Type == limoni.KeyEsc {
			return false // quit
		}
		f.RenderComponent(limoni.Border(
			limoni.Center(limoni.Label("Hello from Limoni 🍋  (Esc quits)", limoni.Bold())),
			limoni.SymbolsRounded,
			limoni.Fg(limoni.Hex("#FFCC00")),
		), f.Area())
		return true
	})
}

Or generate a project that runs straight away, with a test already written:

go run github.com/thebanri/limoni/cmd/limoni@latest new myapp    # -template counter|dashboard|form|chat|ssh
cd myapp && go mod tidy && go run .
go test ./...                                                     # a uitest test comes with every template

Next: Getting started · Widget gallery · Examples


Why Limoni

1. Your TUI has a semantic tree, so tests and agents address widgets by name

Most terminal automation, such as termwright or mcp-tui-test, parses the rendered character grid, so a test breaks when the layout moves by one column. A Limoni app builds a semantic tree every frame (the same tree a screen reader uses), and the tools below work on that tree instead.

uitest: Playwright-style tests. Checks wait instead of sleeping, and a failure prints the whole tree of the last frame.

page := uitest.Run(t, 80, 24, app.draw)            // in process: no terminal needed

page.GetByRole("input", "New task").Type("Tag v1.0")
page.GetByRole("button", "Add task").Click()
page.Expect(page.GetByRole("list-item", "").Within(page.GetByRole("list", "Tasks"))).ToHaveCount(3)
page.Expect(page.GetByID("status")).ToContainLabel("Added")

limoni-mcp: let an AI agent drive the app. It gives Claude Code, Cursor or any MCP client eight tools (tree, click, type_text, wait_for…) that work on the same tree. In one recorded run, Claude Code completed a release checklist in 17 tool calls. That run included typing a deploy token the agent could not read back.

go run -tags limoni_debug ./examples/agent_checklist
claude mcp add limoni -- limoni-mcp -socket "$XDG_RUNTIME_DIR/limoni-checklist.sock"

The automation socket only exists in -tags limoni_debug builds. It is closed by default, only the same user can connect, and secret fields are never exposed. → Semantic automation, MCP and uitest

2. Fast where you can feel it

  • Zero heap allocations per frame for the renderer, every widget's drawing, and the click handling of the common input widgets, enforced in CI, so animations don't stutter from GC pauses. That includes widgets that drag or call your code — Table, Slider, Dialog, TreeView, CommandPalette — once their state is attached. The details.
  • Few bytes per frame. Blank runs become ECH/EL and repeats become REP: a full-screen redraw is 377 bytes, and an idle app sends nothing. Bytes, not CPU, are what you feel over SSH.
  • Virtual tables and lists. One million rows scroll at ~2.7 ms a frame, because only visible rows are touched.
Measured on a Ryzen 5 5600, Go 1.27.1 Latency Allocations
Full-screen diff, 120×40, every cell changed ~50 µs 0
10% of the screen changed ~11 µs 0
Nothing changed ~2 ns 0
100 layered blocks, drawn and diffed ~68 µs 0

Absolute numbers depend on the machine. The comparisons against Ratatui 0.30.2, Ultraviolet and Bubble Tea v1.3.10, including which ratios are not meaningful, are in docs/benchmarks.md and the methodology. One earlier run showed a 4,700× lead; it came from a bug in the benchmark harness, and the methodology explains how it was caught.

3. Batteries that other TUI libraries leave to you

🕶️ 3D A software rasteriser for OBJ/STL/PLY/GLB with Lambert and Gouraud shading, drawn in terminal cells
🖼️ Images Kitty, Sixel, iTerm2 and half-block fallback
📊 Charts Braille line charts, bar charts, pie charts, sparklines
📝 Markdown Headings, lists (nested, numbered, tasks), quotes, tables, links, and fenced code highlighted by language — including a block still streaming in
♿ Accessibility A semantic tree, a screen-reader line mode, NO_COLOR, high contrast, reduced motion
🧬 Unicode UAX #29 grapheme clusters (Unicode 17.0, all 766 conformance tests), so flags and emoji families take one cell
🔗 Hyperlinks OSC 8 links, in markdown or any style — and where the terminal cannot show them, the address is printed instead
📃 Inline mode Render in a band of the normal screen, like gum, with scrollback intact
⏺️ Session replay Record a session, replay it as a regression test (docs)
🌐 Everywhere Linux, macOS, BSD, Windows, WebAssembly in the browser, SSH sessions

The only dependencies are golang.org/x/sys and golang.org/x/crypto.

TreeView with image preview Charts
go run ./examples/treeview go run ./examples/charts

Is Limoni the right choice?

Pick Limoni if you are writing a terminal app in Go and any of these matter:

  • You want to test the UI, or let an AI agent use it, by widget role and label rather than by screen coordinates (uitest, limoni-mcp).
  • It redraws a lot: dashboards, log viewers, monitoring, games, animation, anything over SSH. The draw path makes no garbage and sends few bytes.
  • It needs things other libraries leave to you: 3D models, images, charts, markdown, a million-row table, screen-reader support, WebAssembly in the browser.
  • You want both styles in one library: immediate mode (limoni.Run) for dashboards, the Elm architecture (limoni.RunProgram) for forms and wizards.
  • You want a small dependency tree: golang.org/x/sys and golang.org/x/crypto.

Pick something else if:

  • You need a stable 1.0 API today. Limoni is pre-1.0 (see Status).
  • You rely on the Charm ecosystem (Bubbles, Huh, Glamour, Wish) and its community. Bubble Tea is the larger and older project. If you already have a Bubble Tea app, compat/bubbletea runs your models on Limoni (migration guide), so you can try it without a rewrite.
  • You are writing Rust: use Ratatui.
  • The app is a one-shot prompt (a single question, a spinner): a small prompt library is less to learn.

A feature-by-feature table against Bubble Tea v1/v2 and Ratatui 0.30 is in docs/comparison.md.


Built with Limoni: zest

zest filtering a million-line log to billing errors, opening a line's details, then clearing the filter to show it in context

zest is a log viewer and Limoni's flagship app. It follows files and pipes, colours by level, and filters a million lines without stalling. A 67 MiB, 1,000,000-line log is on screen in about half a second.

go install github.com/thebanri/limoni/cmd/zest@latest
zest -demo 1000000        # or: zest app.log, kubectl logs -f pod | zest

Or try it in the browser: the "Logs · zest" scene.

And: globe

globe is a world you can turn, search, zoom into and pin — and what a Limoni application looks like from the outside, since it is a module of its own that depends on a published Limoni and uses nothing but its public API.

go install github.com/thebanri/limoni/apps/globe@latest
globe -at Türkiye

go install puts the binary in $(go env GOPATH)/bin — ~/go/bin unless you changed it — which a fresh Go install does not add to your PATH. If globe is "command not found", add that directory to PATH or run ~/go/bin/globe.

Everything on it is in the semantic tree, so "find Turkey on the world map" is something an agent can do over MCP: type into search, click the row, read image#globe back as value="39.3°N 34.5°E · zoom 2.6×". Nothing of it reaches you when you import Limoni — a directory with its own go.mod is not part of the module around it.

And your shell: backdrop-shell

backdrop-shell: a fish prompt over an animated aurora; ls lists files in front of it, backdrop-shell opacity 0.9 brightens it at once, and backdrop-shell enable -scene synthwave switches the running terminal to a striped sunset over a neon grid

backdrop-shell puts a moving scene behind your shell, in any terminal — an aurora, a night city, a starfield, a synthwave sunset, a picture, or ASCII art of your own that blinks or drifts. The text in front stays sharp, selecting copies only what the shell wrote, a narrower window keeps every line, and the opacity or the scene changes at once in every open terminal. It costs about half a percent of one core, and nothing while the window is in the background.

curl -fsSL https://raw.githubusercontent.com/thebanri/limoni/main/apps/backdrop-shell/install.sh | sh
backdrop-shell opacity 0.3                # quieter, in every open terminal
backdrop-shell enable -scene synthwave    # or -image wall.jpg, or -art my-art.txt

Drawing and animating your own: docs/backdrop-art.md. The same scenes go behind any Limoni application with limoni.WithBackdrop(backdrop.Aurora()).

And a game: Castle Lemonstein

Castle Lemonstein: the moonlit castle title screen with gold lettering and burgundy banners

Castle Lemonstein is a short first-person game. Squirt the rats and find ten lemons inside a banner-lined stone castle, and the gate to the lair lifts on Ratatui, king of the rats, who has a crown, a health bar and cheese to throw.

go install github.com/thebanri/limoni/apps/castle-lemonstein@latest
castle-lemonstein

Or play it in the browser, sound included.

It is a raycaster drawn two pixels to a cell in half blocks, with lit textures, pixel-art rats, 3D half lemons ray-traced per pixel, and sound synthesised at start-up. A frame allocates nothing, through the diff too. It holds keys down with WithKeyReleases, so walking is smooth in kitty, Ghostty and WezTerm. Runs are scored on aim and time, onto a leaderboard kept in a file, or in the browser's storage.

Lemon Drop is falling blocks where a piece that lands crumbles into sand of its colour. A run of one colour that reaches from wall to wall is the line: it flashes and goes, and what rested on it falls, sometimes into the next one.

go install github.com/thebanri/limoni/apps/lemondrop@latest
lemondrop

Or play it in the browser, with sound and a world leaderboard.

Lemon Snake is a snake that eats lemons. Every five, a monster climbs out of the far corner and hunts the head; spit the seeds your lemons bring at it, and where it steps on the body it bites the rest off. Every level is faster, and so is its monster; fill the board to win. t switches between five colour themes at any time. It glides in half-block pixels, plays soft synthesised sounds, and sends finished runs to the same shared leaderboard as Lemon Drop.

go install github.com/thebanri/limoni/apps/lemonsnake@latest
lemonsnake

Or play it in the browser, with its sound and the world leaderboard.

pikap playing its demo record: the needle is put down half-way in, the record is dragged back and forth to scratch, then it is drawn in ASCII

pikap is a record player drawn in half blocks (or ASCII). It follows whatever the computer is playing (Spotify, a browser tab, mpv, anything on MPRIS): the cover turns on the record and the tonearm moves across it. Dragging the record or the arm moves the song back and forth. Given files, it plays them itself, and then pausing slows the music down with the platter and dragging the record scratches.

go install github.com/thebanri/limoni/apps/pikap@latest
pikap            # follow what is playing
pikap ~/Music    # or play files

Two ways to write an app

Immediate mode Declarative (Elm architecture)
Entry point limoni.Run(func(f, ev) bool) limoni.RunProgram(ctx, model)
State lives in your closure a limoni.Model with Init / Update / View
Best for dashboards, 3D, games, animation forms, wizards, CRUD tools, async work
Runtime gives you a redraw on every event commands, cancellation, panic recovery, session recording

Both use the same renderer and widgets and are available from the root package. examples/counter is a complete declarative app in under 80 lines. Coming from Bubble Tea? See the migration guide.


Widgets

Category Widgets
Layout VStack / HStack / ZStack, Flex, Border, grid layout (layout.GridLayout), Block (with border merging), SplitPane (draggable), Viewport, Dialog, Popup, StatusBar
Data Table (virtual), List (virtual), TreeView, FilePicker, Calendar, Sparkline, ProgressBar, Gauge, LineGauge, RichText
Charts LineChart, BarChart, PieChart — Braille, sextant or quadrant markers
Input TextInput, Autocomplete, TextArea, Checkbox, RadioGroup, Select, Slider, ColorPicker
Navigation Tabs, Scrollbar, CommandPalette, fuzzy search, keybinding manager
Feedback Spinner, Toast, desktop notifications (OSC 9 / OSC 99)
Graphics Canvas (Braille, sextant, quadrant, block), 3D meshes as dots or as a picture over kitty/iTerm2/Sixel, Image
Text Markdown, CodeView (syntax highlighting), BigText, Label, Paragraph
Tooling DevTools HUD (F12), themes, validation

→ Widget gallery · Widget reference


Examples

Example What it shows
demo The feature trailer: a 3D lemon in ASCII, Braille and half-blocks
xray A night city under an X-ray: every cell the diff sends lights up, with measured bytes against a full repaint (-film for recording)
showcase Tabs, forms, matrix rain, 3D, command palette, DevTools (F12)
3d_viewer OBJ/STL/PLY viewer with shading and orbit controls (-fps 240)
dashboard Live CPU and memory sparklines, a process table, streaming logs
table_virtual A one-million-row table
agent_checklist An app built to be driven by an AI agent, and tested with uitest
todo A declarative todo app with tags, filters and fuzzy search
counter The smallest declarative app
composable Layout with VStack, HStack, Border, Flex
forms · layer_demo · treeview · charts Inputs, modals, file tree, charts
ssh_server · wasm Serving over SSH, running in the browser

Run any of them with go run ./examples/<name>, or without cloning: go run github.com/thebanri/limoni/examples/3d_viewer@latest. All examples: docs/examples.md.


Documentation

Getting started Install, first app, both application models
Architecture The flat cell grid, the diff, why the draw path doesn't allocate
Layout · Widgets · Core API Reference
Semantic automation The automation socket, limoni-mcp, uitest and the security model
Session recording Record and replay sessions as regression tests
Graphics · Animation · Accessibility Feature guides
Drivers and platforms Unix, Windows, WebAssembly, SSH
How it compares Against Bubble Tea v1/v2, Lip Gloss and Ratatui, with the caveats
Benchmarks Every measured number and how to reproduce it
Rendering FAQ Hairline gaps, recommended terminals, emoji
Stability · Changelog What may change before 1.0

Turkish documentation: docs/tr.


Status

Limoni is pre-1.0. Patch releases don't break the API; minor releases may, and every break is listed in the changelog. The core renderer, layout and widgets are settling; the automation, uitest and session packages are new and experimental. See docs/stability.md for what has to happen before v1.0.

Community and contributing

AI assistants (Claude, Gemini) were used during development for scaffolding, tests and documentation drafts. The architecture was designed, profiled and benchmarked by the author, and the benchmark harness exists to check claims, from people or tools.

Security policy · Code of Conduct · Apache License 2.0