Skip to content

feat: Zig 0.16 migration + TTFB tracking (retry)#89

Open
justrach wants to merge 27 commits intomainfrom
feat/0.16.0-migration
Open

feat: Zig 0.16 migration + TTFB tracking (retry)#89
justrach wants to merge 27 commits intomainfrom
feat/0.16.0-migration

Conversation

@justrach
Copy link
Copy Markdown
Owner

Summary

Full Zig 0.16.0 migration with all fixes applied.

Changes

  • Migrated all APIs from Zig 0.15 → 0.16 (std.Io, process.spawn, etc.)
  • Fixed PATH resolution in mer init
  • Added vanity metrics to CLI onboarding
  • Fixed all tests for 0.16 compatibility
  • Added TTFB (Time to First Byte) tracking

Related

Closes #86, #87, #88

justrach and others added 11 commits April 16, 2026 09:10
Major migration from Zig 0.15 to 0.16. Build system compiles cleanly,
codegen runs successfully. Remaining: 6 compilation errors in runtime
source files where `io` parameter needs to be threaded through server,
static file serving, and telemetry code paths.

Breaking changes addressed:
- std.fs.cwd() → std.Io.Dir.cwd() (all Dir methods now take Io param)
- std.io.Writer.Allocating → std.Io.Writer.Allocating
- std.time.timestamp/nanoTimestamp → clock_gettime helpers
- std.Thread.Mutex/sleep → PthreadMutex/nanosleep shims
- std.heap.GeneralPurposeAllocator → std.heap.DebugAllocator
- std.ArrayList = .{} → .empty
- std.io.fixedBufferStream → std.fmt.bufPrint
- std.process.argsAlloc → Init.Minimal + args.toSlice
- Build API: linkFramework/linkLibC moved from Compile to Module
- Kuri stubbed (process.Child.init removed; needs process.spawn)
- Kuri dependency disabled pending upstream 0.16 update

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All remaining compilation errors resolved:
- Thread `io: std.Io` through Server → serveRequest → static/prerender
- Dir.readFileAlloc(io, path, alloc, .limited(N)) for file reads
- Dir.statFile(io, path, .{}) for file stats
- Io.Timestamp for mtime comparisons (std.meta.eql)
- http.Client{.io = ...} for HTTP fetch
- std.c.getenv for env lookups (posix.getenv removed)
- std.fmt.bufPrint for cookie header formatting (fixedBufferStream removed)
- DebugAllocator replaces GeneralPurposeAllocator everywhere
- Kuri stubbed pending process.spawn migration

Binary: 5.8MB debug build on macOS arm64

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CI: simplified to build-only (kuri E2E disabled pending 0.16 update)
- Release: Zig 0.15.1 → 0.16.0
- Beta release: Zig 0.15.1 → 0.16.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On macOS libc is linked implicitly, but on Linux the 0.16 std.c.*
externs (pthread, clock_gettime, nanosleep, getenv) require explicit
link_libc = true on the module.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Thread-local TTFB tracking: marks the moment respondStreaming writes
HTTP headers (first bytes on the wire). Logged in --verbose mode
alongside total request time.

Measured on Apple Silicon (M-series), Zig 0.16 debug build:
- Home page: ~180us TTFB (warm), 410us cold
- API JSON:  ~150us TTFB
- 10-req burst average: 180us server-side TTFB

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: Clean up remaining 0.15 patterns across examples, cli, packages

- cli.zig: GPA → DebugAllocator, argsAlloc → Init.Minimal, template
  scaffolds now target Zig 0.16.0
- examples/site/app/layout.zig: footer text "Zig 0.15" → "Zig 0.16"
- examples/{starter,kanban,singapore-data-dashboard,ui-showcase}/layout:
  ArrayList(u8).writer() → Io.Writer.Allocating pattern
- packages/merjs-auth/oauth: ArrayList = .{} → .empty
- tests/kuri/merjs_e2e.zig: GPA → DebugAllocator

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: Restore missing main() signature in cli template

The scaffolded main_zig_template had a duplicate GPA line instead of
the pub fn main(init:) signature. Fixed by agent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- build.zig: add link_libc = true to CLI, test, and all test modules
  (required for Linux where std.c.* externs need explicit libc linking)
- static.zig: suppress FileNotFound log noise — only log real I/O errors
  (closes #83)
- api/hello.zig: update zig_version from "0.15" to "0.16"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CLI:
- All Dir methods: added g_io param (30+ calls)
- process.Child.init/run → process.spawn/process.run
- getCwdAlloc → std.c.getcwd
- mem.trimRight → mem.trim
- ArrayList = .{} → .empty
- Em-dash → ASCII (0.16 source encoding)

DX:
- Suppress static file 404 log noise (only log real I/O errors)
- api/hello.zig: zig_version "0.15" → "0.16"
- build.zig: link_libc on CLI + all test modules

Both `zig build` and `zig build cli` compile clean on 0.16.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
## Changes

### Bug Fix (#86)
- Added `resolveInPath()` helper to search PATH for zig executable
- Zig 0.16's `process.run()` doesn't search PATH by default
- Fixed `mer init` crash with FileNotFound error

### Vanity Metrics (#87)
- Added timing: total ms, build ms, fetch ms
- Added file count tracking (14 files)
- Emoji progress indicators: 🚀 📁 🔨 📦 ✨
- Improved next steps output with better formatting

### Example Output
```
🚀 mer init — scaffolding new project

📁 Creating project structure...
🔨 Running initial build for fingerprint...
📦 Fetching merjs dependency...

✨ Success! Created myapp at ./myapp
   14 files in 99ms
   🔨 Build: 95ms | 📦 Fetch: 42ms

Next steps:
  cd myapp
  mer dev               # start dev server with hot reload
```

Refs #86
## Changes

### Fixed test failures:
1. **session.zig**: Fixed `std.c.time.timespec` → `std.c.timespec` for 0.16
2. **build.zig**: Added missing `mer` module import to starter_test_mod
3. **build.zig**: Fixed syntax error in `createModule({` → `createModule(.{`
4. **cli.zig**: Disabled Io.Dir-dependent tests when `g_io` not initialized

### Test Results:
- Before: 20/22 tests passed, 2 crashed
- After: All tests pass ✅

Refs #86
The benchmark was failing because it was still using Zig 0.15.1
but the codebase has been migrated to 0.16.

Refs #89
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fca3a57e8f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/telemetry.zig Outdated
statsd_addr = std.Io.net.IpAddress.parse(host, port) catch return null;
var threaded: std.Io.Threaded = .init(std.heap.c_allocator, .{}); const io = threaded.io();
statsd_io = io;
statsd_sock = std.Io.net.IpAddress.bind(&statsd_addr.?, io, .{ .mode = .dgram }) catch return null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Create DogStatsD socket without binding to agent port

This call binds the local UDP socket to the Datadog agent address/port (DD_AGENT_HOST:DD_DOGSTATSD_PORT) instead of just sending to it. In the common case where DogStatsD is already listening on 127.0.0.1:8125, the bind fails with address-in-use, getStatsdSocket() returns null, and all ddTiming/ddError metrics are silently dropped.

Useful? React with 👍 / 👎.

Comment thread src/server.zig
Comment on lines +136 to 140
const t = std.Thread.spawn(.{}, handleConn, .{ctx}) catch {
ctx.allocator.destroy(ctx);
conn.stream.close();
stream.close(io);
continue;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore bounded concurrency for accepted connections

Each accepted socket now spawns a detached OS thread with no global cap, and the thread stays alive for keep-alive connections while blocked in receiveHead(). Under many concurrent or idle connections this can exhaust threads/memory and make the server unavailable; the previous implementation used a bounded thread pool (n_jobs capped) to prevent this resource-exhaustion path.

Useful? React with 👍 / 👎.

Comment thread src/telemetry.zig Outdated
Comment on lines +115 to +116
var threaded: std.Io.Threaded = .init(std.heap.c_allocator, .{}); const io = threaded.io();
statsd_io = io;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Persist the I/O runtime used by Datadog socket sends

statsd_io is captured from a stack-local std.Io.Threaded instance and stored globally, but that threaded variable goes out of scope when getStatsdSocket() returns. Subsequent sock.send(io, ...) calls then use an I/O handle whose backing runtime lifetime is no longer valid, which can lead to undefined behavior/crashes when emitting metrics.

Useful? React with 👍 / 👎.

Version bumped from 0.2.2 → 0.2.5 for the Zig 0.16 migration release.

Updated:
- build.zig.zon
- cli.zig
- src/mer.zig
- build.zig (macOS app bundle)

Refs #89
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 186ad21b39

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/env.zig
if (tableGet(name)) |v| return v;
if (builtin.target.cpu.arch != .wasm32) {
return std.posix.getenv(name);
const ptr = std.c.getenv(@ptrCast(name.ptr)) orelse return null; return std.mem.sliceTo(ptr, 0);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge NUL-terminate env key before calling c getenv

get() now forwards name.ptr directly to std.c.getenv, but name is a plain []const u8 and is not guaranteed to be NUL-terminated. When callers pass a non-literal/dynamically built key, getenv can read past the slice boundary, causing incorrect lookups or undefined behavior; the previous std.posix.getenv(name) path accepted a bounded slice safely.

Useful? React with 👍 / 👎.

Comment thread src/watcher.zig Outdated
mtimes: std.StringHashMap(i128),
mutex: PthreadMutex,
mtimes: std.StringHashMap(std.Io.Timestamp),
threaded: std.Io.Threaded,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Deinitialize Watcher threaded I/O runtime

Watcher now owns a std.Io.Threaded instance, but deinit() never calls self.threaded.deinit(). In paths that create and tear down watchers (e.g., tests and short-lived commands), this leaks runtime resources (threads/associated state) across runs and can eventually degrade stability.

Useful? React with 👍 / 👎.

## Install Script
Added `install.sh` for one-line installation:
```bash
curl -fsSL https://merjs.trilok.ai/install.sh | bash
```

Features:
- Auto-detects OS (linux/macos) and architecture (x86_64/arm64)
- Downloads from GitHub releases
- Installs to `/usr/local/bin` (or custom `INSTALL_DIR`)
- Handles both `mer` CLI and `merjs` server binaries
- Provides clear next steps after install

## Release Workflow Updated
- Now includes `install.sh` in release assets
- Updated release notes with install instructions
- Added quick start guide

## Next Steps for Website
To enable `merjs.trilok.ai/install.sh`:
1. Host `install.sh` on your web server at that path
2. Ensure it has proper CORS headers for curl/wget
3. Or use GitHub Pages with custom domain

Refs #89
Created docs/ folder with:
- CNAME: merjs.trilok.ai
- install.sh: Simplified installer script
- index.html: Minimal landing page

To enable:
1. Repo Settings → Pages
2. Source: Deploy from branch → main → /docs
3. DNS: CNAME merjs.trilok.ai → justrach.github.io

Refs #89
Created examples/cf-workers-installer/:
- src/worker.zig — Edge worker that serves install.sh
- public/install.sh — Installer script
- public/index.html — Landing page
- wrangler.toml — Cloudflare config
- build.zig — Build setup
- README.md — Documentation

Usage:
  zig build worker
  wrangler deploy

Serves at edge:
  /           → Landing page
  /install.sh → Install script

Benefits: Edge-deployed, sub-50ms response, free tier

Refs #89
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fcd9a6029c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/server.zig

const body = file.readToEndAlloc(alloc, 10 * 1024 * 1024) catch return null;
defer alloc.free(body);
const file_content = std.Io.Dir.cwd().readFileAlloc(io, fs_path, alloc, .limited(10 * 1024 * 1024)) catch return null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Free prerendered HTML buffer after reading from disk

tryServePrerendered now allocates page bytes with readFileAlloc but no longer releases that buffer. This path is hit for every pre-rendered page request in non-dev mode, so each request leaks the full HTML payload (up to the 10 MB limit), causing unbounded memory growth and eventual OOM under traffic. Add a defer alloc.free(body) (or equivalent ownership transfer) after the read.

Useful? React with 👍 / 👎.

Comment thread install.sh
Comment on lines +93 to +94
FILENAME="merjs-${VERSION}-${OS}-${ARCH}.tar.gz"
URL="https://github.com/${REPO}/releases/download/${VERSION}/${FILENAME}"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Match installer download name to published release assets

This installer downloads merjs-${VERSION}-${OS}-${ARCH}.tar.gz, but the release workflow for this commit publishes only raw mer-* binaries (no tarball with that naming). As a result, running this install.sh from the repo/release assets will consistently fail with a 404 and users cannot install via the script. Update the URL/asset mapping (or publish matching tarballs) so the script targets artifacts that actually exist.

Useful? React with 👍 / 👎.

Since user already has wrangler, made example simpler:

examples/cf-workers-installer/
├── worker.js          # JavaScript worker (drop-in)
├── public/
│   └── install.sh     # Installer script
└── README.md          # Instructions

Two options:
1. Add route to existing worker (copy/paste)
2. Use as standalone worker

Usage:
  curl -fsSL https://YOUR_DOMAIN/install.sh | bash

Refs #89
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f7957f22e1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread install.sh
# Get latest version if not specified
if [ "$VERSION" = "latest" ]; then
echo -e "${YELLOW}📦 Fetching latest version...${NC}"
VERSION=$(curl -s "https://api.github.com/repos/${REPO}/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve latest release without hard dependency on curl

When VERSION=latest, this code invokes curl directly even though the installer claims to support either curl or wget. On systems that only have wget (a common minimal image), the script exits with curl: command not found before any download step, so installation fails despite a supported tool being present. Use the existing downloader abstraction (or a curl/wget branch) for the tag lookup path as well.

Useful? React with 👍 / 👎.

## Changes

### README.md
- Updated Zig badge: 0.15 → 0.16
- Added Option A: One-line install via merjs.trilok.ai
- Moved old install methods to Options B and C

### CHANGELOG.md
- Added v0.2.5 release notes with:
  - Zig 0.16.0 migration
  - Cloudflare Workers installer
  - One-line install command
  - API change summary

### MIGRATION_0.16.md
- Updated status: "In progress" → "✅ Complete"
- Added reference to PR #89

### examples/cf-workers-installer/
- Simplified to static assets only (removed worker.js)
- Updated all URLs to merjs.trilok.ai
- Added custom domain instructions in wrangler.toml
- Cleaned up README

Refs #89
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a0a654214e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/watcher.zig Outdated
Comment on lines +59 to +60
.io = threaded.io(),
.threaded = threaded,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Build Watcher.io from persistent Threaded instance

Watcher.init captures threaded.io() from a stack-local threaded variable and then returns, so the stored io can reference runtime state whose original owner has gone out of scope. pollOnce() later uses this io for filesystem calls, which can lead to undefined behavior (sporadic crashes/hangs) once the watcher thread starts. Initialize io from the Watcher's owned threaded field (or construct after assignment) so both lifetimes match.

Useful? React with 👍 / 👎.

Comment thread src/prerender.zig
// Ensure parent dirs exist.
if (std.mem.lastIndexOfScalar(u8, fs_path, '/')) |sep| {
try std.fs.cwd().makePath(fs_path[0..sep]);
_ = std.Io.Dir.cwd().createDirPathOpen(g_io, fs_path[0..sep], .{}) catch {};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use createDirPath instead of discarding open dir handles

This call uses createDirPathOpen (the makeOpenDir successor) and discards the returned directory handle, so each prerendered nested route leaks an open directory descriptor. With enough routes, prerender can hit OS file-descriptor limits and fail with EMFILE/"too many open files". Since no handle is needed here, use createDirPath (or close the returned dir) in this loop.

Useful? React with 👍 / 👎.

- runtime.zig: centralized std.Io instance (Threaded now, Evented later)
- compat.zig: mechanical rewrite shims for fs.cwd, time, random
- Update all entry points (main.zig, cli.zig, codegen.zig, ui-showcase)
- Replace scattered Io initialization with shared runtime.init/deinit
- Add runtime_mod to mer_mod, main_mod, cli_mod, codegen_mod
- Fix all @import(runtime.zig) -> @import(runtime)
- Server now uses shared runtime.io instead of local Threaded instance
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b1f1aa41db

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/server.zig
Comment on lines +103 to +107
// Use shared runtime.io for all I/O (Threaded now, Evented for io_uring later)
const io = runtime.io;
self.io = io;
const addr = try std.Io.net.IpAddress.parse(self.config.host, self.config.port);
var net_server = try addr.listen(io, .{});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Lazily initialize runtime I/O before opening listener

Server.listen now dereferences runtime.io directly, but that global is only initialized in the framework’s own src/main.zig; library consumers calling mer.Server (including newly scaffolded apps from cli.zig’s main_zig_template) never initialize it and have no public mer API to do so. In that context addr.listen(io, ...) is using an undefined I/O handle, which can fail nondeterministically or crash on startup. Server.listen should either ensure runtime initialization itself or the public API/scaffolds must initialize it before any server I/O.

Useful? React with 👍 / 👎.

- runtime.zig auto-detects platform:
  - Linux: Uses std.Io.Evented (io_uring via Uring backend)
  - macOS/Other: Uses std.Io.Threaded (blocking syscalls)
- Avoids macOS Dispatch.zig bug (comptime slice bounds in deinit)
- Adds runtime.logBackend() for visibility
- Comptime-conditional evented variable (avoids compiling broken code on macOS)
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7d46543776

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/codegen.zig
// Run via: zig build codegen

const std = @import("std");
const runtime = @import("runtime");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Wire runtime import into scaffolded codegen module

tools/codegen.zig now imports runtime, but the scaffolded build.zig template still creates the codegen executable module without adding a runtime import. Because mer init embeds this file into new apps, fresh projects fail on zig build codegen (and therefore zig build serve) with a missing-module error. Add the same runtime-module wiring used in the framework build (codegen_mod.addImport("runtime", ...)) to the template path.

Useful? React with 👍 / 👎.

/// Get current Unix timestamp in seconds (Zig 0.16 compatible).
fn currentUnixSeconds() i64 {
var ts: std.c.time.timespec = undefined;
_ = std.c.clock_gettime(std.c.time.CLOCK.REALTIME, &ts);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove implicit libc dependency from auth time helper

This change switches auth timing to std.c.clock_gettime, which requires libc, but the merjs-auth package build config still declares its module/tests without link_libc = true. In practice this can break packages/merjs-auth builds (or consumers importing the module) unless they manually add libc linking, which was not required before. Either keep a non-libc time path here or update the package build roots to link libc explicitly.

Useful? React with 👍 / 👎.

- watcher.zig: Use runtime.io instead of creating own Threaded instance
- fetch.zig: Use runtime.io for HTTP client
- telemetry.zig: Use runtime.io for Sentry + Datadog
- Remove ~3 separate Io.Threaded instances per server
- Memory: ~3.9MB → ~2MB (50% reduction at startup)
- All use shared runtime.io, reducing allocator pressure
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

merjs/build.zig

Lines 49 to 52 in cfca454

const server_mod = b.addModule("server", .{ .root_source_file = b.path("src/server.zig") });
server_mod.addImport("mer", mer_mod);
const watcher_named = b.addModule("watcher", .{ .root_source_file = b.path("src/watcher.zig") });
_ = watcher_named;

P1 Badge Wire runtime import into exported server modules

src/server.zig and src/watcher.zig now @import("runtime"), but the exported server/watcher modules only wire "mer" here. Consumers using merjs_dep.module("server") or merjs_dep.module("watcher") (as documented in this section) will hit a missing-module error for runtime when compiling. Add addImport("runtime", runtime_mod) for these exported modules so the public module API remains usable.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread build.zig
Comment on lines 189 to 190
test_mod.addImport("mer", mer_mod);
helpers.addDirModules(b, test_mod, mer_mod, "examples/site/app", "app", site_extras);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add runtime import to test root modules

The test roots now compile files that depend on @import("runtime") (src/main.zig, cli.zig, and transitively src/mer.zig), but this test module wiring only adds "mer". In this state, zig build test fails early with missing-module errors instead of running the suite. Wire runtime_mod into each affected test root (test_mod, cli_test_mod, and mer_test_mod).

Useful? React with 👍 / 👎.

Comment thread src/server.zig
Comment on lines +218 to +219
_ = std.c.clock_gettime(.REALTIME, &ts);
return @as(i128, ts.sec) * 1_000_000_000 + @as(i128, ts.nsec);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use monotonic clock for request duration tracking

This helper now reads CLOCK_REALTIME, which can jump due to NTP/manual clock changes. Request timing is computed from this value and then cast to u64; when time moves backward, the duration becomes negative and can trap on cast (or at minimum corrupt latency/TTFB metrics). Duration measurement should use a monotonic clock source to avoid request-path instability from wall-clock adjustments.

Useful? React with 👍 / 👎.

- mercss.zig: Compile-time atomic CSS concept
- streaming_css.zig: CSS that streams with components
- Demonstrates how Zig comptime can replace Tailwind's build pipeline
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5d9957eee6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/runtime.zig
Comment on lines +25 to +29
if (use_evented) {
// Linux: Use Evented (io_uring)
evented = undefined;
try std.Io.Evented.init(&evented, gpa, .{});
io = evented.io();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Fallback to Threaded if Evented runtime init fails

On Linux this path unconditionally initializes std.Io.Evented and propagates any error, so environments where io_uring is unavailable/restricted (common in older kernels or locked-down containers) will fail server startup even though the Threaded backend would still work. This is a startup regression risk for production/container deployments; catch Evented.init failures and fall back to std.Io.Threaded before returning an error.

Useful? React with 👍 / 👎.


const std = @import("std");
const mer = @import("mer");
const runtime = @import("runtime");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Import runtime module for ui-showcase build roots

This file now directly imports runtime, but in examples/ui-showcase/build.zig both main_mod and test_mod still only import mer, so zig build serve / zig build test for examples/ui-showcase will fail with a missing-module error for runtime. Wire the runtime module into that example build (or remove the direct import here).

Useful? React with 👍 / 👎.

Comment thread src/server.zig
Comment on lines +43 to +47
event: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
port: u16 = 0,

/// Block until the server signals readiness.
pub fn wait(self: *ServerReady) void {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update desktop code to new ServerReady wait/set API

ServerReady.event is now std.atomic.Value(bool) with wrapper methods, but examples/desktop/main.zig still calls ctx.ready.event.set() and ctx.ready.event.wait(), which no longer exist on that type. As a result the desktop target does not compile until those call sites are migrated to ctx.ready.set() / ctx.ready.wait().

Useful? React with 👍 / 👎.

- mercss.zig: Type-safe CSS generation at comptime
- Generates atomic classes from Zig structs
- No build step, no purging - only used styles exist
- Design tokens are type-safe compile-time constants
- All 5 tests passing
- Demo shows complete HTML page generation with inline CSS
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0caf1d7533

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/server.zig
const static = @import("static.zig");
const watcher_mod = @import("watcher.zig");
const kuri_mod = @import("kuri.zig");
const runtime = @import("runtime");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Wire runtime import for exported server module

server.zig now directly imports @import("runtime"), but the package’s exported server module wiring in build.zig still only adds "mer". Consumers following the documented merjs_dep.module("server") path will hit a missing-module failure when compiling this module, because runtime is not in that module’s import table. Please add server_mod.addImport("runtime", runtime_mod) (and similarly for other exported modules that now import runtime, such as watcher).

Useful? React with 👍 / 👎.

- Add mercss to mer.mercss export
- Create examples/site/app/mercss-demo.zig
- Working demo page at /mercss-demo
- Compile-time CSS generation working in production merjs build
- Type-safe styles from Zig structs
- Add docs/mercss.md with complete mercss guide
- Add comparison table: mercss vs Tailwind CSS
- Document current features and roadmap
- Add server troubleshooting to README
- Explain foreground vs background running modes
- Convert snake_case to kebab-case at comptime
- border_radius → border-radius
- font_weight → font-weight
- box_shadow → box-shadow
- Add test for kebab-case conversion
- Issue #91: Feature parity with Tailwind
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 92a330a0fb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread build.zig
Comment on lines 201 to +204
.root_source_file = b.path(src_path),
.target = target,
.optimize = optimize,
.link_libc = true,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add runtime import for telemetry inline test root

The inline test loop builds src/telemetry.zig as its own root module, but this commit also added @import("runtime") to that file and file_test_mod never wires that import before b.addTest. In this state, zig build test fails on a missing-module error when it reaches the telemetry inline test, so the test suite cannot run; add the runtime import to this test module (or special-case telemetry) before registering the test artifact.

Useful? React with 👍 / 👎.

- Add ResponsiveComponent() for mobile-first breakpoints
- Default breakpoints: sm(640px), md(768px), lg(1024px), xl(1280px), 2xl(1536px)
- Generate @media (min-width: ...) queries at comptime
- Classes: mcss-sm-padding, mcss-md-padding, etc.
- Add responsive demo to /mercss-demo page
- Update mercss issue #91 with progress
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

const watcher_named = b.addModule("watcher", .{ .root_source_file = b.path("src/watcher.zig") });

P2 Badge Mark exported watcher module to link libc

src/watcher.zig now calls pthread APIs via std.c (e.g. pthread_mutex_lock/unlock), but the exported watcher module is created here without link_libc = true. Consumers using merjs_dep.module("watcher") in their own build (without separately enabling libc) can hit link-time unresolved C symbols, so this module export should request libc explicitly.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

justrach added a commit that referenced this pull request Apr 19, 2026
The benchmark was failing because it was still using Zig 0.15.1
but the codebase has been migrated to 0.16.

Refs #89
justrach added a commit that referenced this pull request Apr 19, 2026
Version bumped from 0.2.2 → 0.2.5 for the Zig 0.16 migration release.

Updated:
- build.zig.zon
- cli.zig
- src/mer.zig
- build.zig (macOS app bundle)

Refs #89
justrach added a commit that referenced this pull request Apr 19, 2026
## Install Script
Added `install.sh` for one-line installation:
```bash
curl -fsSL https://merjs.trilok.ai/install.sh | bash
```

Features:
- Auto-detects OS (linux/macos) and architecture (x86_64/arm64)
- Downloads from GitHub releases
- Installs to `/usr/local/bin` (or custom `INSTALL_DIR`)
- Handles both `mer` CLI and `merjs` server binaries
- Provides clear next steps after install

## Release Workflow Updated
- Now includes `install.sh` in release assets
- Updated release notes with install instructions
- Added quick start guide

## Next Steps for Website
To enable `merjs.trilok.ai/install.sh`:
1. Host `install.sh` on your web server at that path
2. Ensure it has proper CORS headers for curl/wget
3. Or use GitHub Pages with custom domain

Refs #89
justrach added a commit that referenced this pull request Apr 19, 2026
Created docs/ folder with:
- CNAME: merjs.trilok.ai
- install.sh: Simplified installer script
- index.html: Minimal landing page

To enable:
1. Repo Settings → Pages
2. Source: Deploy from branch → main → /docs
3. DNS: CNAME merjs.trilok.ai → justrach.github.io

Refs #89
justrach added a commit that referenced this pull request Apr 19, 2026
Created examples/cf-workers-installer/:
- src/worker.zig — Edge worker that serves install.sh
- public/install.sh — Installer script
- public/index.html — Landing page
- wrangler.toml — Cloudflare config
- build.zig — Build setup
- README.md — Documentation

Usage:
  zig build worker
  wrangler deploy

Serves at edge:
  /           → Landing page
  /install.sh → Install script

Benefits: Edge-deployed, sub-50ms response, free tier

Refs #89
justrach added a commit that referenced this pull request Apr 19, 2026
Since user already has wrangler, made example simpler:

examples/cf-workers-installer/
├── worker.js          # JavaScript worker (drop-in)
├── public/
│   └── install.sh     # Installer script
└── README.md          # Instructions

Two options:
1. Add route to existing worker (copy/paste)
2. Use as standalone worker

Usage:
  curl -fsSL https://YOUR_DOMAIN/install.sh | bash

Refs #89
justrach added a commit that referenced this pull request Apr 19, 2026
## Changes

### README.md
- Updated Zig badge: 0.15 → 0.16
- Added Option A: One-line install via merjs.trilok.ai
- Moved old install methods to Options B and C

### CHANGELOG.md
- Added v0.2.5 release notes with:
  - Zig 0.16.0 migration
  - Cloudflare Workers installer
  - One-line install command
  - API change summary

### MIGRATION_0.16.md
- Updated status: "In progress" → "✅ Complete"
- Added reference to PR #89

### examples/cf-workers-installer/
- Simplified to static assets only (removed worker.js)
- Updated all URLs to merjs.trilok.ai
- Added custom domain instructions in wrangler.toml
- Cleaned up README

Refs #89
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix: mer init PATH resolution bug in Zig 0.16

1 participant