A faithful, native Windows Notepad clone built in Zig using raw Win32 APIs — no .NET runtime, no GUI framework, no installer. Single .exe, zero dependencies.
Why Zig? To demonstrate that agentic AI absorbs implementation complexity. The binary is ~1.1 MB vs 154 MB for the C# WinForms sibling project (LFNotepad). The human effort to direct both was identical.
Features
Matches classic Windows Notepad (pre-AI era) feature-for-feature:
- File: New · Open · Save · Save As (with encoding selection) · Page Setup · Print · Exit
- Edit: Undo · Cut · Copy · Paste · Delete · Find · Find Next · Replace · Go To · Select All · Time/Date
- Format: Word Wrap · Font
- View: Zoom In / Out / Restore · Status Bar toggle
- Status Bar: Ln/Col · Zoom % · Encoding · Line Endings
- Behavior: Dirty tracking (title asterisk) · Save-changes dialog · Drag-and-drop file open · Command-line file argument · Right-click context menu
Requirements
| Requirement | Detail |
|---|---|
| OS | Windows 10 or later (x86_64) |
| Zig | 0.15.2 (tested) |
| Dependencies | None at runtime — single .exe |
| Build deps | zigwin32 (fetched automatically via zig fetch) |
Quick Start
# Clone git clone <repo-url> lfznotepad cd lfznotepad # Fetch zigwin32 dependency (already recorded in build.zig.zon) zig fetch # Build zig build # Run zig build run # or directly: .\zig-out\bin\LFZNotepad.exe
The output binary is at zig-out\bin\LFZNotepad.exe (~1.1 MB, no installer needed).
Build Reference
zig build # Compile only zig build run # Compile + launch zig build test # Run unit tests
Capturing build errors (recommended — PowerShell terminal can truncate stderr):
zig build 2>&1 | Set-Content -Encoding UTF8 build_errors.txt # then open build_errors.txt
Project Structure
lfznotepad/
├── build.zig # Zig 0.15 build script
├── build.zig.zon # Package manifest (zigwin32 dependency + hash)
├── src/
│ ├── main.zig # Entry point — GetModuleHandle, load riched20.dll, init comctl32
│ ├── window.zig # Main window class, WndProc, AppState, layout
│ ├── menu.zig # Full Win32 menu bar (CreateMenu/AppendMenuW)
│ ├── menu_ids.zig # All WM_COMMAND IDs as a Zig enum (no magic numbers)
│ ├── statusbar.zig # comctl32 status bar — 4 parts: Ln/Col, Zoom%, Encoding, CRLF
│ ├── helpers/
│ │ ├── file.zig # File I/O, BOM-based encoding detection, UTF-8/16 conversion
│ │ ├── edit.zig # Edit menu helpers (clipboard, select-all, time/date insert)
│ │ ├── format.zig # Word wrap toggle, font dialog (ChooseFontW), zoom (EM_SETZOOM)
│ │ └── print.zig # Win32 printing (PrintDlgExW, GDI pagination)
│ └── dialogs/
│ ├── find.zig # Modeless Find dialog (CreateDialogParamW)
│ ├── replace.zig # Modeless Replace dialog
│ ├── goto.zig # Modal Go To Line dialog (DialogBoxParamW)
│ ├── opensave.zig # Open/Save As file dialogs (GetOpenFileNameW / GetSaveFileNameW)
│ └── about.zig # About dialog
└── tests/
└── file_tests.zig # Encoding detection and file round-trip tests
Key Win32 / Zig Conventions
These are critical to know before modifying the code:
| Rule | Detail |
|---|---|
| Wide APIs only | Never use ANSI (*A) Win32 functions. Always use *W variants. |
| Load riched20.dll first | LoadLibraryW("riched20.dll") must be called before creating any RichEdit20W window. |
| No magic numbers | All WM_COMMAND IDs come from the MenuCmd enum in menu_ids.zig. |
| zigwin32 flags are packed structs | Use .{ .STRING = 1 } not raw u32. E.g. MENU_ITEM_FLAGS{ .STRING = 1 }, MESSAGEBOX_STYLE{ .ICONHAND = 1 }. |
| Module name | zigwin32 exports as "win32" — use dep.module("win32"), not "zigwin32". |
| Callconv | WndProc uses callconv(.winapi) (lowercase, Zig 0.15). |
| String literals | All Win32 strings are UTF-16: std.unicode.utf8ToUtf16LeStringLiteral("text"). |
| AppState | Single module-level var g: AppState in window.zig — no global scattered state. |
AgentFlow Documentation
This project uses the AgentFlow methodology for human-AI collaboration. These files support autonomous AI execution and cross-session/cross-agent continuity:
| File | Purpose |
|---|---|
AGENTS.md |
Read first. Rules of engagement, tech stack, Win32 conventions, guardrails, autonomy mode. |
context.md |
Current session state. Sprint status, decisions locked, next actions. Updated each session. |
WHERE_AM_I.md |
Milestone tracker — product goals vs completion. |
product-definition.md |
Full feature list (P0/P1), non-goals, success criteria. |
design.md |
Win32 architecture, AppState struct, encoding strategy, Win32↔WinForms mapping. |
sprint-plan.md |
5 sprints with Win32 concepts, task checklists, definitions of done. |
AgentFlow Development Loop
CODE → BUILD (zig build) → TEST → TEST AS LEE (verify .exe runs) → FIX → LOOP → DOCUMENT → COMMIT
Sprint end-gate (required before moving to next sprint):
zig build testpasses- Manual verify: launch
.exe, exercise all new features - Update
context.mdandWHERE_AM_I.md git commit
Continuing Work in Another Agent Harness
Orientation (do this first)
1. Read AGENTS.md — rules and conventions
2. Read context.md — current state, what's done, what's next
3. Read WHERE_AM_I.md — milestone view
4. Read sprint-plan.md — sprint tasks and definitions of done
Getting the build working
zig build 2>&1 | Set-Content -Encoding UTF8 build_errors.txt # Read build_errors.txt for full error output (do NOT rely on terminal — it truncates)
Autonomy level
Mode 3 — Autonomous. Build, test, commit without asking for approval.
Only interrupt the human for: breaking design decisions, ambiguous requirements, blocked on user input.
Known gotchas for new agents
- Terminal truncation: PowerShell stderr output is cut off. Always redirect to file.
- zigwin32 packed structs: All flag types are
packed struct(u32). Use struct literal syntax, never cast raw integers. Check field names in the zigwin32 source at%LOCALAPPDATA%\zig\p\zigwin32-*\win32\. EM_SETEVENTMASK: Must be sent to the RichEdit control after creation to receiveEN_SELCHANGEviaWM_NOTIFY.- Modeless dialogs (Find/Replace): Must be given the RichEdit
HWND, not the main windowHWND, for text operations. - Word Wrap: Implemented via
EM_SETTARGETDEVICEon the RichEdit, not by changing window styles directly.
Current Status
| Attribute | Value |
|---|---|
| Version | v1.0.0 |
| Phase | All sprints complete — UAT stabilization |
| Binary | zig-out\bin\LFZNotepad.exe (~1.1 MB) |
| Pending | Full regression pass (see context.md) |
Session History
This project was built across multiple AI agent sessions on different platforms. All features were verified and tested by the human (Lee).
| Session | Agent | Model | Contribution |
|---|---|---|---|
| 1 | Antigravity | Gemini (Google DeepMind) | Project scaffolding, AgentFlow docs, Sprint 1 start |
| 2 | Kimi Code | Kimi K2.5 | Sprint 1 completion, Sprints 2–5 |
| 3 | OpenCode | GPT-5.3 Codex | UAT fixes, accelerator wiring, crash fixes |
Raw session notes: antigravity_session_raw.md · kimi_session_raw.md · opencode_session_raw.md
Relationship to LFNotepad (C# version)
| LFNotepad | LFZNotepad | |
|---|---|---|
| Language | C# | Zig |
| Framework | WinForms | Raw Win32 |
| Binary size | ~154 MB | ~1.1 MB |
| Runtime deps | .NET runtime | None |
| Features | Same (P0/P1) | Same (P0/P1) |
| Human effort | Same | Same |
Both projects are part of the same case study demonstrating supervisor-mode agentic AI development.