Week 2: Simulator & Spec Decoupling
Theme — Configuration is data, not code: strings, layout and theme leave Zig for
spec/.Skip this if — your project already treats its config as an embedded, validated asset.
Takeaways
- Copy this: embed specs at compile time (
@embedFile) instead of reading them at startup. You keep the single static binary and get the edit-a-file workflow.- Avoid this: waiting for a “config epic”. Every string you hardcode this week is one you will grep for later — the migration cost is linear in how long you postpone it.
- The check that caught it: schema validation at build time. A spec file that parses is not a spec file that is correct, and a compile-time embed turns a typo into a build error instead of a runtime blank screen.
Decoupling Logic from Definition
Week 1 ended with a confession: the picker worked, but changing a border color meant recompiling Zig. Week 2 is where emojig learned the lesson this whole ecosystem is built on — configuration is data, not code — and where two side projects were born out of sheer necessity.
During Week 2 (June 5 – June 11, 2026), the core focus shifted from basic terminal compliance to structural flexibility. In Week 1, layouts, text captions, keyboard mappings, and color values were hardcoded inside the Zig loops. Adding a new language or changing a border color required recompiling the codebase.
We initiated the Spec-Driven Architecture—moving all configurations out of Zig files and into static JSON files under spec/.
Week 2 by the Numbers
- Total Commits: 35 — a fivefold drop from Week 1, and the start of the plateau described in Reading the Curve.
- Codebase Refactoring Speed: Refactored 340 LoC of UI string arrays and layout coordinates out of
main.ziginto 5 spec files in under 2 hours. - Localization Expansion: Added 10 UI languages (including English, German, French, Spanish, Japanese, Chinese) by editing static spec configurations without a single line of Zig code changing.
Daily Chronology
June 7, 2026: The Simulator & Screen Recorder
We created a browser-based JavaScript TUI simulator (website/simulator.js) for the web demo, though it is not used for any real testing.
- Features Added: JavaScript TUI simulator (
website/simulator.js) and X11-based virtual display session recorder (scripts/record_demo.go). - Side Project Spawning (Wayreel):
- The Problem: We wanted a cool website and didn’t want to manually re-record demos every time the UI changed.
- The Solution: We spawned Wayreel as a standalone tool to express app usage as scripts (
.reelfiles) and play them in a terminal. While built for demos, it showed it could be used for testing because we could spot visual bugs in the recordings later. (By accident, we can also command other apps now!)
June 8, 2026: The Spec Migration Epic
We eliminated hardcoded UI strings, layout metrics, and color theme tables from src/main.zig and migrated them to spec/strings.json, spec/layout.json, and spec/theme.json.
- LoC Shift:
src/main.zig: Shrunk by 180 LoC.spec/*.jsonfiles created: 420 LoC.
- Features Added: Compile-time schema embeds (
@embedFile("spec/strings.json")) and standard validation checks.
Code Archaeology: Moving Strings to Spec
Before this epic, drawing the search prompt or help text in src/main.zig utilized hardcoded formatting:
// ❌ Old Hardcoded Rendering in main.zig
const search_prompt = try std.fmt.bufPrint(&line_buf, "🔍 Search: {s}", .{query});
try writeAll(stdout_fd, search_prompt);
After the spec decoupling, all strings were defined declaratively in spec/strings/en.json and loaded via compile-time JSON parsing:
{
"search_prompt": "🔍:",
"help_text": "Type to search, arrows to navigate, Enter to pick",
"theme_toggle": "Tab: Toggle Theme"
}
The rendering loop was simplified to reference the parsed structure:
// ✓ Simplified rendering in main.zig
const prompt = g_spec.strings.search_prompt;
const search_line = try std.fmt.bufPrint(&line_buf, "{s} {s}\x1b[K\n", .{prompt, query});
These spec files were JSON in Week 2 and became YAML in Week 5; the loading and embedding pattern survived the format change untouched, which is the whole argument for having one.
June 9, 2026: Parallel Go Port & Visual Testing
To verify search scoring and keypress latency differences between compiling runtimes, we initiated a Go port called mojigo.
- Side Project Spawning (Cati):
- The Problem: In
mojigo, we wanted to render detailed visual about-screens and help diagrams. We needed to ensure that block-characters mapped perfectly across different terminal font sizes. - The Solution: We built Cati as a CLI image and media renderer. By utilizing Unicode halfblocks (
▀,▄), quadblocks, and sextants combined with 24-bit true color, Cati let us test layout rendering outputs and establish visual golden-file regression checks.
- The Problem: In
June 10 – June 11, 2026: Onboarding and Custom Sizing
We added automatic shell installer scripts (emojig --install) to configure Zsh/Bash widgets and supported custom GUI dimensions (EMOJIG_COLS, EMOJIG_ROWS) when spawning foot popup overlays.
- LoC Added:
scripts/check_install.sh: 145 LoC.src/host.zigmodifications (+62).
Week 2 verdict: the architecture was now spec-driven and the tooling gaps had names (Wayreel, Cati). What it could not yet do was scale: the picker still rendered a hand-picked subset of icons. Week 3 opens the floodgates to the full Unicode catalog.