Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
0026427
rebase zig gui on release 0.0.161
pranavp311 Jun 21, 2026
8a83708
cleanup(gui): remove old tauri backend
pranavp311 Jun 21, 2026
d0efc1b
fix(gui): share Codex CLI auth
pranavp311 Jun 21, 2026
8970083
fix(gui): wire model catalog and project picker
pranavp311 Jun 21, 2026
72f1813
fix(gui): restore sends and migrate theme settings
pranavp311 Jun 21, 2026
b4d021c
fix(gui): wire queue reasoning and terminal
pranavp311 Jun 21, 2026
9c7d2d6
fix(gui): keep chat navigation and queue input usable
pranavp311 Jun 21, 2026
9151f33
fix(gui): submit current draft and show queued prompts
pranavp311 Jun 21, 2026
d2a8218
fix(gui): persist chats via CLI sessions
pranavp311 Jun 21, 2026
b018c28
fix(gui): restore chat paste and file drop
pranavp311 Jun 22, 2026
fd1d96e
fix(gui): route native shell actions through mer
pranavp311 Jun 22, 2026
69add09
Improve GUI terminal sessions
pranavp311 Jun 22, 2026
3b24528
Implement native PTY terminal backend
pranavp311 Jun 22, 2026
d650efb
Fix PTY terminal sizing and raw output
pranavp311 Jun 22, 2026
b781ade
Update GUI graff JSON protocol invocation
pranavp311 Jun 22, 2026
be7d25d
fix(gui): stabilize chat submit and session titles
pranavp311 Jun 22, 2026
55d088b
Fix GUI chat submit and slash command wiring
pranavp311 Jun 22, 2026
e6d8498
gui: stream message deltas and request lifecycle events over SSE
pranavp311 Jun 22, 2026
ee8dae3
gui(chat): chronological tool/text interleaving + subtler thinking + …
pranavp311 Jun 22, 2026
fddcf6c
chore: gitignore local scratch artifacts (.graff/, plans/, shots/)
pranavp311 Jun 22, 2026
4efd772
chore: untrack local plan/handoff doc (now gitignored)
pranavp311 Jun 22, 2026
0854733
gui(chat): redesign final-answer card and follow-up composer
pranavp311 Jun 22, 2026
1f1a904
Fix GUI backend phase 1 safety issues
pranavp311 Jun 22, 2026
3ff68fa
Tighten GUI conversation selection validation
pranavp311 Jun 22, 2026
29737cd
Improve GUI backend event and metadata performance
pranavp311 Jun 22, 2026
34ca55d
fix gui backend terminal regressions
pranavp311 Jun 23, 2026
95da48d
preserve gui session transcript semantics
pranavp311 Jun 23, 2026
68634fa
preserve unknown gui protocol events
pranavp311 Jun 23, 2026
070046f
bound gui sse replay memory
pranavp311 Jun 23, 2026
608ad4e
cache gui workspace status refreshes
pranavp311 Jun 23, 2026
e486575
align gui providers with core schema
pranavp311 Jun 23, 2026
a013189
guard gui desktop route contract
pranavp311 Jun 23, 2026
737a4eb
avoid fake gui git mutation success
pranavp311 Jun 23, 2026
715a446
Harden GUI protocol decoding and benchmark transport
pranavp311 Jun 23, 2026
4e0e029
Keep persistent graff JSON sessions in GUI
pranavp311 Jun 23, 2026
e789169
Improve GUI bootstrap streaming responsiveness
pranavp311 Jun 23, 2026
5dafe4e
Cache GUI streaming transcript updates
pranavp311 Jun 23, 2026
757bb85
Reduce GUI backend turn setup latency
pranavp311 Jun 23, 2026
04625ab
Lazy load saved workspace panels
pranavp311 Jun 23, 2026
9c256a4
Add rendering thresholds for large markdown and diffs
pranavp311 Jun 23, 2026
0c93901
Avoid synthesizing huge generated diffs
pranavp311 Jun 23, 2026
4d35d62
Add localized render error boundaries
pranavp311 Jun 23, 2026
86a11fa
Mark GUI performance plan complete
pranavp311 Jun 23, 2026
af838a3
Polish premium GUI surfaces
pranavp311 Jun 24, 2026
d951809
Hide raw protocol compatibility events
pranavp311 Jun 24, 2026
ec37295
Polish expanded thinking messages
pranavp311 Jun 24, 2026
5c5c127
Flatten expanded thinking content
pranavp311 Jun 24, 2026
2fe89b3
Sync CLI sessions into GUI
pranavp311 Jun 24, 2026
08c5e9d
Avoid CLI session polling during prompt submit
pranavp311 Jun 24, 2026
49c3cc2
Queue prompts on active send conflicts
pranavp311 Jun 24, 2026
b79f6de
Guard duplicate native prompt submits
pranavp311 Jun 24, 2026
487282b
Reconcile native prompt send races
pranavp311 Jun 24, 2026
449d86e
Preserve in-chat optimistic submits across snapshots
pranavp311 Jun 24, 2026
86dd80e
Preserve native optimistic selection across stale snapshots
pranavp311 Jun 24, 2026
ecc5cee
Revert native Enter send fixes
pranavp311 Jun 24, 2026
30e3ae1
Revert "Avoid CLI session polling during prompt submit"
pranavp311 Jun 24, 2026
31dd364
Fix native prompt input interactions
pranavp311 Jun 24, 2026
ffb161c
Disable autocorrect for chat inputs
pranavp311 Jun 25, 2026
447d597
chore(gui): remove stale Tauri references
pranavp311 Jun 26, 2026
1af1215
feat(gui): add ultracode slash choice picker
pranavp311 Jun 26, 2026
6185a16
feat(gui): rank model picker by usage
pranavp311 Jun 26, 2026
32dd08c
feat(gui): restore history attachments
pranavp311 Jun 26, 2026
717f7b2
feat(gui): auto-copy terminal selection
pranavp311 Jun 26, 2026
d825856
fix(gui): preserve segmented button borders
pranavp311 Jun 26, 2026
04cc7ef
fix(gui): reconcile native shell after rebase
pranavp311 Jun 27, 2026
8e5a381
chore(gui): remove stale tauri release references
pranavp311 Jun 27, 2026
1727d91
fix(gui): prevent sidebar footer overlap
pranavp311 Jun 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ node_modules/
harness.trace.jsonl
harness.trajectory.jsonl
.harness/
.graff/worktrees/
.graff/
.claude/scheduled_tasks.lock
*.session.json
.mcp.json
Expand All @@ -29,5 +29,9 @@ __pycache__/
# local agent/IDE config (not project source)
.devin/

# local scratch artifacts (plans/handoffs, visual-QA screenshots)
plans/
shots/

# release artifacts
dist/
280 changes: 137 additions & 143 deletions bun.lock

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions gui/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ lerna-debug.log*
node_modules
dist
dist-ssr
src-tauri/target
src-tauri/gen
*.local

# Editor directories and files
Expand Down
9 changes: 9 additions & 0 deletions gui/api/stub.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const mer = @import("mer");
const backend = @import("backend");

pub fn render(req: mer.Request) mer.Response {
const rt = backend.instance orelse {
return mer.Response.init(.internal_server_error, .json, "{\"error\":\"backend runtime is not initialized\"}");
};
return rt.handleApi(req);
}
177 changes: 177 additions & 0 deletions gui/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// build.zig — codegraff-zig GUI native app (merjs consumer).
//
// Builds the native desktop shell: a merjs HTTP server (serving the Vite-built
// React SPA from dist/ + api/ backend routes + SSE /events) hosted in a system
// WKWebView. The React frontend (src/) is unchanged; only its runtime host
// changes from the old desktop backend to merjs.
//
// zig build native # dev: run the shell (serves dist/)
// zig build native-build # prod: build the binary
// zig build package # bundle as Codegraff.app
//
// Prereq: `bun run build` produces dist/ (the React SPA).
const std = @import("std");

pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});

const merjs = b.dependency("merjs", .{
.target = target,
.optimize = optimize,
});
const mer_mod = merjs.module("mer");
const runtime_mod = merjs.module("runtime");

// native/main.zig — shell entry point.
const native_mod = b.createModule(.{
.root_source_file = b.path("native/main.zig"),
.target = target,
.optimize = optimize,
});
native_mod.addImport("mer", mer_mod);
native_mod.addImport("runtime", runtime_mod);

// manifest — mer.app.zon (comptime config for the shell).
const manifest_mod = b.createModule(.{
.root_source_file = b.path("mer.app.zon"),
});
native_mod.addImport("manifest", manifest_mod);

// Local PTY module: vendored/adapted openpty + Codegraff-owned shell process lifecycle.
const pty_mod = b.createModule(.{
.root_source_file = b.path("src-backend/pty/root.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
});

// Backend runtime module (the port of runtime/simple.rs).
const backend_mod = b.createModule(.{
.root_source_file = b.path("src-backend/runtime.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
});
backend_mod.addImport("mer", mer_mod);
backend_mod.addImport("runtime", runtime_mod);
backend_mod.addImport("pty", pty_mod);
native_mod.addImport("backend", backend_mod);

// routes — the app's route table. We have api/ backend routes only (no SSR
// pages; the React SPA owns client rendering). Wire each api/*.zig as a
// named import on the routes module, then add routes.zig as "routes".
const routes_mod = b.createModule(.{
.root_source_file = b.path("routes.zig"),
});
routes_mod.addImport("mer", mer_mod);
routes_mod.addImport("backend", backend_mod);
// Scan api/ and add each .zig as "api/<name>".
addApiModules(b, routes_mod, mer_mod, runtime_mod, backend_mod, "api");
native_mod.addImport("routes", routes_mod);

// macOS frameworks for the WKWebView shell.
native_mod.linkFramework("AppKit", .{});
native_mod.linkFramework("WebKit", .{});
native_mod.linkFramework("Foundation", .{});
native_mod.link_libc = true;

const exe = b.addExecutable(.{ .name = "codegraff-gui", .root_module = native_mod });
b.installArtifact(exe);

// `native` — run (dev).
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| run_cmd.addArgs(args);
const run_step = b.step("native", "Run the codegraff GUI native shell");
run_step.dependOn(&run_cmd.step);

// `native-build` — install only (prod).
const build_step = b.step("native-build", "Build the native shell binary (prod)");
build_step.dependOn(b.getInstallStep());

const backend_tests = b.addTest(.{ .root_module = backend_mod });
const run_backend_tests = b.addRunArtifact(backend_tests);
const test_step = b.step("test", "Run GUI backend unit tests");
test_step.dependOn(&run_backend_tests.step);

// `package` — .app bundle with manifest-driven Info.plist.
const app_zon = @import("mer.app.zon");
const pkg_name = b.fmt("{s}.app", .{app_zon.display_name});
const plist_xml = b.fmt(
\\<?xml version="1.0" encoding="UTF-8"?>
\\<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
\\<plist version="1.0">
\\<dict>
\\ <key>CFBundleExecutable</key> <string>codegraff-gui</string>
\\ <key>CFBundleIdentifier</key> <string>{s}</string>
\\ <key>CFBundleName</key> <string>{s}</string>
\\ <key>CFBundleVersion</key> <string>{s}</string>
\\ <key>CFBundleIconFile</key> <string>icon.icns</string>
\\ <key>NSHighResolutionCapable</key><true/>
\\ <key>NSPrincipalClass</key> <string>NSApplication</string>
\\</dict>
\\</plist>
, .{ app_zon.id, app_zon.display_name, app_zon.version });
const plist = b.addWriteFile(b.fmt("{s}/Contents/Info.plist", .{pkg_name}), plist_xml);
const pkg_bin = b.addInstallFile(
exe.getEmittedBin(),
b.fmt("{s}/Contents/MacOS/codegraff-gui", .{pkg_name}),
);
pkg_bin.step.dependOn(b.getInstallStep());
const clean_pkg = b.addSystemCommand(&.{
"rm",
"-rf",
b.getInstallPath(.prefix, pkg_name),
});
const pkg_icon = b.addInstallFile(
b.path("native/icons/icon.icns"),
b.fmt("{s}/Contents/Resources/icon.icns", .{pkg_name}),
);
const pkg_dist = b.addInstallDirectory(.{
.source_dir = b.path("dist"),
.install_dir = .prefix,
.install_subdir = b.fmt("{s}/Contents/Resources/dist", .{pkg_name}),
});
const pkg_plist = b.addInstallDirectory(.{
.source_dir = plist.getDirectory(),
.install_dir = .prefix,
.install_subdir = "",
});
const package_step = b.step("package", "Package the GUI as a .app bundle (macOS)");
pkg_bin.step.dependOn(&clean_pkg.step);
pkg_icon.step.dependOn(&clean_pkg.step);
pkg_dist.step.dependOn(&clean_pkg.step);
pkg_plist.step.dependOn(&clean_pkg.step);
package_step.dependOn(&pkg_bin.step);
package_step.dependOn(&pkg_icon.step);
package_step.dependOn(&pkg_dist.step);
package_step.dependOn(&pkg_plist.step);
}

/// Scan `dir/` for *.zig files and add each as a named module "api/<name>"
/// on `mod`, with `mer` wired so route files can `@import("mer")`.
fn addApiModules(
b: *std.Build,
mod: *std.Build.Module,
mer_mod: *std.Build.Module,
runtime_mod: *std.Build.Module,
backend_mod: *std.Build.Module,
dir: []const u8,
) void {
var d = std.Io.Dir.cwd().openDir(b.graph.io, dir, .{ .iterate = true }) catch return;
defer d.close(b.graph.io);
var walker = d.walk(b.allocator) catch return;
defer walker.deinit();
while (walker.next(b.graph.io) catch null) |entry| {
if (entry.kind != .file) continue;
if (!std.mem.endsWith(u8, entry.path, ".zig")) continue;
const file_path = b.fmt("{s}/{s}", .{ dir, entry.path });
const import_name = b.fmt("api/{s}", .{entry.path[0 .. entry.path.len - 4]});
const route_mod = b.createModule(.{ .root_source_file = b.path(file_path) });
route_mod.addImport("mer", mer_mod);
route_mod.addImport("runtime", runtime_mod);
route_mod.addImport("backend", backend_mod);
mod.addImport(import_name, route_mod);
}
}
20 changes: 20 additions & 0 deletions gui/build.zig.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.{
.name = .codegraff_gui,
.fingerprint = 0xbaf1ec805de39b91,
.version = "0.0.13",
.minimum_zig_version = "0.16.0",
.dependencies = .{
.merjs = .{
.path = "../../../merjs",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"mer.app.zon",
"native",
"api",
"src-backend",
"routes.zig",
},
}
7 changes: 1 addition & 6 deletions gui/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@ import tseslint from "typescript-eslint";
import { defineConfig, globalIgnores } from "eslint/config";

export default defineConfig([
globalIgnores([
"dist",
"vendor/**",
"src-tauri/gen/**",
"src-tauri/target/**",
]),
globalIgnores(["dist", "vendor/**"]),
{
files: ["**/*.{ts,tsx}"],
extends: [
Expand Down
39 changes: 39 additions & 0 deletions gui/mer.app.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// mer.app.zon — codegraff-zig GUI native app manifest.
//
// The merjs native shell reads this comptime: window size/title, server mode,
// static dir, and bridge permissions. The React SPA (built to dist/ by Vite) is
// served by the embedded merjs server; the Zig backend exposes api/ routes +
// an SSE /events channel for session-updated pushes.
.{
.id = "dev.codegraff.gui",
.name = "codegraff-gui",
.display_name = "Codegraff",
.version = "0.0.13",
.web_engine = "system",
.server = .{
.mode = "embedded", // in-process loopback server (no hot reload in prod)
.host = "127.0.0.1",
.port = 0, // 0 = ephemeral; shell reads back via ServerReady
.static_dir = "dist", // serve the Vite-built React SPA
},
.capabilities = .{ "webview", "js_bridge" },
.permissions = .{ "window", "clipboard", "dialog", "open" },
.security = .{
.navigation = .{ .allowed_origins = .{ "http://127.0.0.1" } },
},
.windows = .{
.{ .label = "main", .title = "Codegraff", .width = 1280, .height = 832 },
},
.bridge = .{
.commands = .{
.{ .name = "dialog.openFile", .permissions = .{ "dialog" }, .origins = .{ "http://127.0.0.1" } },
.{ .name = "dialog.pickDirectory", .permissions = .{ "dialog" }, .origins = .{ "http://127.0.0.1" } },
.{ .name = "clipboard.read", .permissions = .{ "clipboard" }, .origins = .{ "http://127.0.0.1" } },
.{ .name = "clipboard.write", .permissions = .{ "clipboard" }, .origins = .{ "http://127.0.0.1" } },
.{ .name = "open.external", .permissions = .{ "open" }, .origins = .{ "http://127.0.0.1" } },
.{ .name = "open.path", .permissions = .{ "open" }, .origins = .{ "http://127.0.0.1" } },
.{ .name = "window.setTitle", .permissions = .{ "window" }, .origins = .{ "http://127.0.0.1" } },
.{ .name = "window.close", .permissions = .{ "window" }, .origins = .{ "http://127.0.0.1" } },
},
},
}
File renamed without changes.
76 changes: 76 additions & 0 deletions gui/native/main.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// native/main.zig — codegraff-gui native shell entry point.
//
// Boots the merjs HTTP server (serving the React SPA from dist/ + api/ routes
// + SSE /events) inside a system WKWebView. The React frontend talks to this
// Zig backend over HTTP.
//
// zig build native # run
// zig build native-build # prod binary
// zig build package # .app bundle
const std = @import("std");
const mer = @import("mer");
const runtime = @import("runtime");
const native = mer.native;
const backend = @import("backend");

const log = std.log.scoped(.native);

pub fn main(init: std.process.Init.Minimal) !void {
var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit();
const allocator = gpa.allocator();

var arena_state: std.heap.ArenaAllocator = .init(std.heap.page_allocator);
defer arena_state.deinit();
const args = try init.args.toSlice(arena_state.allocator());

var app_manifest = native.Manifest.fromZon(@import("manifest"));

// CLI overrides.
var i: usize = 1;
while (i < args.len) : (i += 1) {
if (std.mem.eql(u8, args[i], "--dev")) {
app_manifest.dev = true;
app_manifest.server_mode = "dev";
} else if (std.mem.eql(u8, args[i], "--no-dev")) {
app_manifest.dev = false;
app_manifest.server_mode = "embedded";
}
}

if (!app_manifest.dev) {
if (bundleResourcePath(allocator, args[0], "dist")) |bundle_dist| {
app_manifest.static_dir = bundle_dist;
} else |_| {}
}

// Initialize the backend runtime (Phase 1 stub; Phase 2 = real RuntimeManager).
var rt = try backend.Runtime.init(allocator);
defer rt.deinit();

// Build the router from our api/ routes.
var router = mer.Router.fromGenerated(allocator, @import("routes"));
defer router.deinit();

// Register the SSE /events raw handler (checked before routing).
const raw_handler = mer.RawHandler{
.ctx = @ptrCast(rt),
.callback = &backend.handleEvents,
};

log.info("codegraff-gui native — {s} v{s} ({s}, static={s})", .{
app_manifest.display_name,
app_manifest.version,
app_manifest.server_mode,
app_manifest.static_dir orelse "public",
});
try native.Shell.run(allocator, app_manifest, &router, .{
.raw_handler = &raw_handler,
});
}

fn bundleResourcePath(allocator: std.mem.Allocator, exe_path: []const u8, resource_name: []const u8) ![]const u8 {
const marker = "/Contents/MacOS/";
const idx = std.mem.lastIndexOf(u8, exe_path, marker) orelse return error.NotAppBundle;
return std.fmt.allocPrint(allocator, "{s}/Contents/Resources/{s}", .{ exe_path[0..idx], resource_name });
}
Loading
Loading