summaryrefslogtreecommitdiff
path: root/build.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-01 14:34:19 -0300
committerGabriel Schneider <[email protected]>2026-09-02 16:35:21 -0300
commit3f2d6f43199d0e230490396deb50f8dc49c7b8b0 (patch)
tree309d464fe96487545082920b4080639576644bdf /build.zig
parentcce6b18a49870086982f9a0e1fda90ed170b9fba (diff)
downloadpardes-3f2d6f43199d0e230490396deb50f8dc49c7b8b0.tar.gz
pardes-3f2d6f43199d0e230490396deb50f8dc49c7b8b0.zip
hosts: the effects three shells kept a copy of become one, and the mac's own bugs go with them
Nine read-only scouts compared every host-side concern across `src/macos.zig`, `src/tty/tty.zig`, `src/gui/gui.zig` and `src/detached/server.zig`. What they found was not a style problem: each duplicated body had drifted, and in every case the drift WAS a bug the users of that shell could see. So the fixes and the deduplication are the same change. **One PATH, adopted before the first fork.** LaunchServices hands a bundle launchd's environment, whose `PATH` is `/usr/bin:/bin:/usr/sbin:/sbin`. Every pty shell, `|` filter and language server the app forked inherited it, so `yazi` in `/opt/homebrew/bin` was absent from a Dock launch and present in the identical binary run from a terminal — the "it worked briefly" window was simply the sessions started from a shell. `shell_bin.adoptSystemPath` composes `/etc/paths` then `/etc/paths.d/*` in the order `path_helper` reads them, deduplicating on first occurrence, and runs once at startup in all four native hosts. It APPENDS: an entry already present keeps its position, so running it over a real session cannot demote a mise shim behind `/usr/bin` and silently change which `node` runs. A `PATH` that was configured is left byte-for-byte alone; only one nobody configured is repaired. `prepareForFork` folds that adoption together with the prompt-rc staging and the `BASH_SILENCE_DEPRECATION_WARNING` setenv the five hand-copied prefork sites had between them — `server.zig` had none of it, which is why every detached pane opened with Apple's zsh banner. **The LSP protocol client never worked on macOS.** It opened its control socket with `libc.SOCK.CLOEXEC`; Zig defines that constant for Linux and Darwin answers `socketpair` with `EPROTONOSUPPORT`, so the call failed before any fork, `ensure` returned `error.NoServer`, and every row in the spec table — rust-analyzer, clangd, gopls — was unreachable in every macOS build. The in-process ZLS backend kept answering, which is what made it read as "only Zig is supported". It is a plain socket plus `fcntl(FD_CLOEXEC)` now, the route `fuse.zig:943` and `nested.zig:95` already took for the same reason. The snapshot suite that covered this path had never run natively on a Mac: the harness targets defaulted to x86_64-linux. **One LSP host worker.** `src/lsp_host.zig` is the snapshot, the worker body and the job lifetime that `tty.zig` and `gui.zig` carried verbatim — `gui.zig` said so in a comment — and that `macos.zig` did not carry at all: `lsp` and `pipe` were absent from its `Host.VTable`, so the core answered its own empty answer, `SPC l i` rendered a blank panel and a `|` filter silently did nothing. All three shells share the module, and the AppKit host implements both effects. Its status sink is now REGISTERED as well as defined, so unsolicited server news reaches the message row instead of nowhere. **The animation clock measures time.** `pardes_animation_tick` advanced one scene frame per callback and published `frame_count / 60`, so scene time was a count of callbacks rather than elapsed seconds — and `AppDelegate` re-armed `asyncAfter(.now() + 0.016)` only after the previous frame's work had finished, making the true period 16 ms plus all of it. Motion ran at about three quarters of wall clock and unevenly. The tick now spends measured monotonic time in whole `frame_ns` steps and banks the remainder, so a late callback advances two frames instead of stretching one; `spendTickTime` is that arithmetic as a pure function with its own tests and no display attached. On macOS 14+ the animating run is one `CADisplayLink` phase-locked to vsync rather than a chain rebuilt after every frame; macOS 13 keeps the old chain. **Three more single definitions.** `panel_animation.paintOrder` is the moving-then-opening-then-closing composite order as a rule the core applies once in `Pardes.render` — `macos.zig` was re-sorting an already-sorted list. `selection_pipe.Tasks` is the bounded in-flight pipe table `tty.zig` and `gui.zig` each declared. `boxContains` was a fourth copy of the half-open cell test and is now an alias of `Box.contains`. **A filtered terminal stops asking libm per cell.** `Filter`'s legibility stage called `RGB.contrast` for every painted cell, and that ends in `std.math.pow` up to six times, re-deriving a ratio against a background that had not moved; the existing memo cache covered the palette reduction beside it and never this. The indexed path's input is a `u8`, so all 256 answers are enumerated once per pass — after the default roles are fixed, before the first cell is read — and what a cell names becomes an array index. Only truecolour still reduces. ReleaseFast, 190x56, Tracy: recolour 3.09 ms -> 0.130 ms, frame 3.37 ms -> 0.299 ms. The comptime luminance table is pinned to `RGB.luminance` and `RGB.contrast` by exact-equality test over every channel value and all 65 536 palette pairs, because the decision is a threshold comparison where one ULP is a different colour. A `filterInit` Tracy zone records the part that is still per-pass: 2.9 us warm against a 117 us pass, which is the measurement that says not to cache it across frames. Released as 0.0.2. `build.zig.zon` carries the version into `pardes --version` and into the `Changelog` pane through `@embedFile`, so the entries above open a `## 0.0.2` section and `## 0.0.1` closes with the tagline work of the parent commit. Two bugs here were mine, caught by review rather than by me: a double free in the macOS pipe drain arm (`Msg.free` already owns the response) that segfaulted the app on the first `|`, and a proposed `getRowAndCell` optimisation that targeted 2 of 43 draw samples while the contrast math beside it took 12 — and would not have compiled. The profile that justified it was a Debug build, which `build.zig:1160` already documents as ~5x slower than release. Native and -Dplatform=macos suites: 0 failures. All targets build with Tracy on and off; the shipped release binary contains no `___tracy_emit_zone_begin`. App reinstalled, signature verified, dmg regenerated, launched with 0 crash reports; installed binaries verified byte-identical to a fresh build.
Diffstat (limited to 'build.zig')
-rw-r--r--build.zig72
1 files changed, 68 insertions, 4 deletions
diff --git a/build.zig b/build.zig
index dd2d64bf..f94a4bcf 100644
--- a/build.zig
+++ b/build.zig
@@ -1200,16 +1200,18 @@ pub fn build(b: *std.Build) void {
"Metal",
});
- // The icon is generated, not committed: the mark is drawn out of
- // the same palette PardesView.swift renders cells with, so a colour
- // that moves there moves here on the next build instead of a binary
- // blob quietly disagreeing with the app it ships in. Compiled alone
+ // The ICNS is generated, not committed: it is a ten-size container
+ // macOS reads, and a binary blob in the tree is one nobody can
+ // review. What IS committed is src/macos/icon.png, the drawing it
+ // is cut from — passed as a file arg so a new picture invalidates
+ // the cached icon instead of shipping the old one. Compiled alone
// because the file is top-level code — one file, one module.
const icon_build = b.addSystemCommand(&.{ "swiftc", "-O", "-target", triple, "-o" });
const icon_bin = icon_build.addOutputFileArg("pardes-icon");
icon_build.addFileArg(b.path("src/macos/icon.swift"));
const icon_run = std.Build.Step.Run.create(b, "pardes-icon");
icon_run.addFileArg(icon_bin);
+ icon_run.addFileArg(b.path("src/macos/icon.png"));
const icon_dir = icon_run.addOutputDirectoryArg("Resources");
// One version, two consumers: LSMinimumSystemVersion is stamped
@@ -1601,6 +1603,68 @@ pub fn build(b: *std.Build) void {
.link_libc = true,
}) });
unit_step.dependOn(&b.addRunArtifact(nested_test).step);
+ // The LSP protocol client. Its own module for the reason its
+ // neighbours have, and it earned the comment above the hard way: the
+ // core DOES import src/lsp/lsp.zig, which imports this file, and that
+ // is not enough — zig collects tests from a module's ROOT SOURCE FILE
+ // only, so every test in src/lsp/ compiled and none of them ran. What
+ // went unnoticed behind that is the whole reason this entry exists: the
+ // transport asked for SOCK_CLOEXEC, which darwin's socketpair(2)
+ // rejects, so no language server had ever spawned on macOS. The
+ // end-to-end cover (test/snapshots/lsp-client.snap) could not catch it
+ // either — `snap` runs the x86_64-linux target, where the flag is real.
+ //
+ // `pardes_config` and `zls` are attached because lsp.zig's `backends`
+ // names the in-process analyser; this test module compiles the same
+ // graph the shells do rather than a second, luckier one.
+ const lsp_client_test = b.addTest(.{ .root_module = blk: {
+ const m = b.createModule(.{
+ .target = target,
+ .optimize = optimize,
+ .root_source_file = b.path("src/lsp/lsp_client.zig"),
+ .link_libc = true,
+ });
+ m.addOptions("pardes_config", opts);
+ if (zls_mod) |x| m.addImport("zls", x);
+ break :blk m;
+ } });
+ unit_step.dependOn(&b.addRunArtifact(lsp_client_test).step);
+ // The shared LSP host worker. Rooted here for the reason every module
+ // above it is: zig collects tests from a module's ROOT FILE only, and
+ // this one is reached through src/pardes.zig by three shells that would
+ // each have compiled its tests and run none of them. It needs the whole
+ // core graph because it imports the core — the same wiring hx_core_mod
+ // gets, in one call, so a dependency added there is not missed here.
+ const lsp_host_test = b.addTest(.{ .root_module = blk: {
+ const m = b.createModule(.{
+ .target = target,
+ .optimize = optimize,
+ .root_source_file = b.path("src/lsp_host.zig"),
+ .link_libc = true,
+ });
+ m.addOptions("pardes_config", opts);
+ m.addOptions("pardes_isolation", iso_off);
+ wireCore(m, .{
+ .themes = themes_mod,
+ .zls = zls_mod,
+ .tree_sitter = ts_mod,
+ .ts_libs = ts_libs.items,
+ .ts_queries = ts_queries_opts,
+ .zstbi = zstbi_mod,
+ .mvzr = mvzr_mod,
+ .mupdf = mupdf_core_mod,
+ .ghostty_vt = ghostty_core_vt,
+ .vaxis = vaxis_mod,
+ .uucode = uucode_mod,
+ });
+ // src/source_manifest.zig embeds these two by name; every core
+ // compilation gets them beside `pardes_config` (see the core_mods
+ // loop), and a module rooted inside the core needs them too.
+ m.addAnonymousImport("root-build.zig", .{ .root_source_file = b.path("build.zig") });
+ m.addAnonymousImport("root-build.zig.zon", .{ .root_source_file = b.path("build.zig.zon") });
+ break :blk m;
+ } });
+ unit_step.dependOn(&b.addRunArtifact(lsp_host_test).step);
// The 9P2000 codec and its sans-io server. Its own module for the
// reason spelled out above rather than as a convention, and a stronger
// one than its neighbours have: 9p.zig is FREESTANDING — it imports