diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-01 14:34:19 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-09-02 16:35:21 -0300 |
| commit | 3f2d6f43199d0e230490396deb50f8dc49c7b8b0 (patch) | |
| tree | 309d464fe96487545082920b4080639576644bdf /src/CHANGELOG.md | |
| parent | cce6b18a49870086982f9a0e1fda90ed170b9fba (diff) | |
| download | pardes-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 'src/CHANGELOG.md')
| -rw-r--r-- | src/CHANGELOG.md | 75 |
1 files changed, 75 insertions, 0 deletions
diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md index 71e47f82..714497b6 100644 --- a/src/CHANGELOG.md +++ b/src/CHANGELOG.md @@ -1,5 +1,80 @@ # Changelog +## 0.0.2 + +- A filtered terminal costs what an unfiltered one does. `Filter`'s second + stage asked `RGB.contrast` for every cell it painted, and that call ends in + `std.math.pow` six times over — a libm round trip per cell, per frame, to + re-derive a ratio against a background that had not moved. The indexed path + takes a `u8`, so all 256 of its answers are now enumerated once per pass, + after the two default roles are fixed and before the first cell is read; + what a cell names is an array index into them. Only truecolour, whose + 16.7M inputs cannot be tabulated, still reduces, and the direct-mapped RGB + cache is what keeps that cheap. Tracy over the AppKit shell, ReleaseFast, + 190x56: the recolour pass falls 3.09 ms to 0.130 ms and the whole frame + 3.37 ms to 0.299 ms. The luminance table is comptime-evaluated from + ghostty's own expression and a test pins it to `RGB.luminance` and + `RGB.contrast` exactly, across every channel value and all 65 536 palette + pairs, because the filter's decision is a threshold comparison where one + ULP is a different colour on screen. +- A pardes launched from the Dock finds the same programs a pardes launched + from a terminal does. LaunchServices hands a bundle launchd's own + environment, whose `PATH` is `/usr/bin:/bin:/usr/sbin:/sbin`, and every pty + shell, `|` filter and language server inherited it — so `yazi` in + `/opt/homebrew/bin` was missing in the app and present in the same build run + from a shell, which reads as "the Dock build is broken". `shell_bin` + composes `/etc/paths` then `/etc/paths.d/*` in the order `path_helper` + reads them and adopts the result before the first fork in all four native + hosts. It appends, so an inherited entry is never demoted and a configured + `PATH` is left byte-for-byte alone: the rule is that only a `PATH` nobody + configured gets repaired. +- Language servers other than ZLS start on macOS. The protocol client opened + its control socket with `SOCK.CLOEXEC`, which Zig defines for Linux and + which Darwin answers with `EPROTONOSUPPORT` — so `socketpair` failed before + any fork, every spec in the table reported "no server", and rust-analyzer, + clangd and gopls were unreachable in every macOS build. It is a plain + socket plus `fcntl(FD_CLOEXEC)` now, the route `fuse.zig` and `nested.zig` + already used. Verified end to end against a 200-crate Rust workspace: + rust-analyzer indexes, and `gd`, `document_symbols` and `hover` answer. +- `SPC l i` reports what the language backends are doing in the macOS app, + and `|` filters a selection there. Both effects were unimplemented host + methods — `lsp` and `pipe` were absent from the AppKit vtable, so the core + answered its own empty answer and the panel rendered blank while a sort + filter silently did nothing. The snapshot-plus-worker body each shell had + its own copy of is one module (`src/lsp_host.zig`), and the in-flight pipe + table is `selection_pipe.Tasks`; all three native shells share both. + Unsolicited server news ("rust-analyzer indexing 45%") reaches the message + row because the status sink is registered rather than merely defined. +- Animation runs at the speed it claims. `pardes_animation_tick` advanced one + scene frame per callback and reported time as `frame_count / 60`, so + animation time was a count of how often the callback happened rather than + how much time had passed — and the AppKit chain re-armed `asyncAfter(.now() + + 0.016)` only AFTER the previous frame's work, making the real period 16 ms + plus all of it. Motion ran at roughly three quarters of wall clock, + unevenly. The tick measures elapsed monotonic time and spends it in whole + 16 ms steps, banking the remainder, so a late callback advances two frames + instead of stretching one; the arithmetic is a pure function with its own + tests, no display attached. On macOS 14 and later the run is driven by one + `CADisplayLink` phase-locked to vsync instead of a chain rebuilt per frame. +- A filtered terminal's colours are mapped in two stages, and the second one + keeps text off the page. `Filter` reduces every colour to its nearest + canonical xterm key and reads that key out of ghostty-vt's theme-derived + 256-colour projection — a comparison of RGB triples, which knows about hue + and nothing about the background. The projection is generated FROM the + default foreground and background, so its cube corners are those two + anchors, and the nearest key to a truecolour extreme was therefore the + background itself: `\x1b[38;2;255;255;255m` on acme's `#ffffea` paper + resolved to `#ffffea`, a WCAG ratio of 1.000, text painted the colour of the + page under it. Every curated theme owned such a key — 231 on the light one, + ANSI black on both dark ones, which is a bare `\x1b[30m`. The two default + roles are now mapped first and named as the anchors they are, and every + other FOREGROUND has to clear `config.tty_filter_min_contrast` (1.5) against + the mapped background; one that cannot is not mapped at all, and takes + whichever anchor is still visible there. Backgrounds are exempt, because a + background is the page the floor is measured against. Measured across the + curated three the floor refuses 12, 16 and 7 of 256 keys, where a WCAG + body-text 4.5 would refuse 61, 154 and 91 and flatten the palette. + ## 0.0.1 - Pane taglines sit where they do in the SDL window. The macOS shell had its own |
