Emojig: Chronology of a Zig Endeavour
This is the story of six weeks across the late spring and early summer of 2026 (May 29 – July 5), told week by week, commit by commit: how an emoji picker — the most unserious of programs — became a serious exercise in raw terminal engineering, spec-driven architecture, and agentic development. It is a chronicle, not a tutorial: the numbers are real, the dead ends are kept in, and three side projects are born along the way because the tooling we needed did not exist yet.
In a hurry? The Recipe Index maps every technique in this book to the week that paid for it, so you can jump straight to what you need without reading a chronology. Starting a TUI of your own? Skip to Starting a New Zig TUI.
The Why: why emojig + Zig?
Every desktop environment has an emoji picker, but for terminal power users on Linux, the options were historically frustrating:
- Bloated electron apps or heavy graphical utilities that take seconds to load and consume hundreds of megabytes of RAM.
- Constantly running resident background daemons eating CPU cycles just to wait for a hotkey.
- Basic CLI tools that dump text lists but lack real-time 2D keyboard navigation or hover/click mouse support inside raw terminals.
emojig was born out of a desire for a radical, minimal, instant-popup emoji picker that functions seamlessly as an inline TUI or a floating GUI window.
To achieve this, we selected Zig:
- Musl Static Binaries: Compiles into a single, zero-dependency static binary under 900 KB.
- Absolute Memory Control: Zero runtime allocations on search loops, keeping memory usage strictly under 5 MB RSS (self-reported via
/proc/self/statm). - Compile-Time Packing: Leveraging Zig’s
@embedFileto serialize and embed 2,249 emojis directly into the binary, achieving zero runtime disk reads.
Everything the picker knows that is not code — strings, layout, colours, key bindings — lives in spec files. Week 2 moved them out of Zig into JSON; Week 5 migrated that JSON to YAML for comments and cleaner diffs. The finished project ships YAML specs, generated down to JSON at build time and embedded at compile time. When Weeks 2 through 4 say “JSON spec”, read it as the format of the day: the architecture is the point, and it did not change.
Here is emojig executing in floating GUI mode and inline TUI mode:
Figure 1: GUI Popup Mode over a dark Zed session.
Figure 2: Inline TUI Mode inside a Tilix terminal.
The Ubunatic Ecosystem: Side Projects
As emojig evolved, standard development tools proved insufficient for verifying low-level terminal alignments and visual render pipelines. This led to the creation of three companion side projects — each born the week a wall was hit, each kept alive ever since:
1. Wayreel (The Recorder)
We built Wayreel first because we wanted a cool website and didn’t want to constantly re-record demos. Its main purpose is to express app usage as scripts (.reel files) and play them in a terminal (by accident, it can also command other apps now). Later, it showed it could be used for testing because we could spot visual bugs in the recordings.
2. Cati (The Visual Tester)
We needed a way to test visual render outputs and golden-file regressions of terminal pixel art (like our winking about-screen animation). We created Cati (cat for images) which renders PNG/JPEG/SVG files directly to the terminal using Unicode halfblocks, quadblocks, sextants, and sparklines. Cati’s aspect ratio fitting math and golden-file verification loop became the standard for our codebases.
3. Uman (The Orchestrator)
Managing separate repos, git worktrees, and schemas across emojig, cati, and books became a maintenance burden. We built Uman (Uman Manages Apps Nicely) — a Go CLI for managing a whole directory of local git projects: bulk status, guarded push, releases, and syncing project websites into one publishing repo.
Chronology Index
Reading the Curve
Week 1 produced 171 commits — very nearly as many as the other five weeks put together (187). That is not productivity; it is exploration. Nothing was known yet, so every commit was a probe: does this termios flag matter, does this terminal honour that escape code, does the teardown leave a blank line. Commits were cheap because each one was small and reversible, and that is exactly what you want when the problem is still being discovered.
The rate then collapses by a factor of five and stays flat for three weeks (35, 53, 49). This is consolidation: the shape of the program is settled, so work arrives in larger, riskier units — a spec migration, a virtualized viewport, a release. Fewer commits, more thought per commit. The single most valuable commit in the whole book falls in this stretch, and it is the one that removed 2,450 lines.
By Week 6 the rate is 12 across three days. Maintenance looks like failure on a contribution graph and is actually the goal: the codebase had stopped needing to change, and the remaining work was making it navigable for whoever came next. A curve that stays at 171 for six weeks is not a healthier project — it is one that never converged.
Two caveats before you compare this to your own graph. These are agent-sized commits: an agentic session commits at the granularity of a verified step, not a human’s end-of-day, so the absolute height of the first bar means little. And Week 6 covers three days, not seven — the floor is real, but it is not a like-for-like week. The shape — steep, then flat, then low — is the part that transfers.
Check it yourself. Every bar above is one command against the repository, so these numbers are falsifiable rather than merely asserted:
git rev-list --count --since=2026-05-29T00:00:00+02:00 \
--until=2026-06-05T00:00:00+02:00 HEAD # Week 1 → 171
Counted on main, half-open windows [start, end), matching each chapter’s stated date range.
Give git an explicit offset: with a bare --since=2026-05-29 it resolves the boundary in UTC and
quietly moves commits near midnight into the neighbouring week. The six windows sum to 358; the
repository holds 359, the one stray being a maintenance commit in August, after the chronicle
closes.
The following chapters chronicle each week of this Zig endeavour:
- Week 1: Foundations & Shell widgets
- Week 2: Simulator & Spec Decoupling
- Week 3: Virtualization & PNG Art Compilers
- Week 4: Search Optimization & Command Interception
- Week 5: GNOME CSD Rendering & Binary Search
- Week 6: Codebase Modularization & Key Merging
And two chapters that are not chronology at all:
- The Recipe Index — every technique, by problem instead of by date.
- Starting a New Zig TUI — the bootstrap kit, extracted from all six weeks.