<feed xmlns='http://www.w3.org/2005/Atom'>
<title>pardes.git/src/macos/icon.swift, branch main</title>
<subtitle>Pardes</subtitle>
<id>https://git.0x4200.cafe/pardes.git/atom?h=main</id>
<link rel='self' href='https://git.0x4200.cafe/pardes.git/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/'/>
<updated>2026-09-02T19:35:21Z</updated>
<entry>
<title>hosts: the effects three shells kept a copy of become one, and the mac's own bugs go with them</title>
<updated>2026-09-02T19:35:21Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-09-01T17:34:19Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=3f2d6f43199d0e230490396deb50f8dc49c7b8b0'/>
<id>urn:sha1:3f2d6f43199d0e230490396deb50f8dc49c7b8b0</id>
<content type='text'>
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 -&gt; 0.130 ms,
frame 3.37 ms -&gt; 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.
</content>
</entry>
<entry>
<title>docs: the tutor taught three keystrokes wrong, and the rest had drifted</title>
<updated>2026-08-12T19:07:33Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-08-12T16:32:43Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=bc89f57cb576e23a58572ec35f96db068367f1b4'/>
<id>urn:sha1:bc89f57cb576e23a58572ec35f96db068367f1b4</id>
<content type='text'>
The documentation had gone stale in the ordinary way -- claims that were true
when they were written and that nothing since had been obliged to re-read.
Some of them were load-bearing.

THE TUTOR. It still said there is no multi-cursor, that NextColor cycles three
themes, and that its practice blocks "are also run as unit tests (generated
from this file by tutor_gen)" -- a tool that appears nowhere in the tree, and
nothing anywhere parses a `# keys:` block. Left alone, that claim is what
makes the next wrong block survive.

Three of those blocks WERE wrong, and all three for one reason: since the
helix motion model landed, w/e/f/t SELECT the range they cross, so `i` after
one inserts at the SELECTION'S START. `w i Z esc` on "foo bar" gives
"Zfoo bar", not the "foo Zbar" the file promised. They were written against a
vim reading of the same keys. Every block in the file has now been run through
`zig build hxdiff` against the real core and matches byte for byte, and the
trap itself is written down in 3.3 rather than left to be rediscovered.

The tutor gains a PART 4 for everything added since it was written -- PDF
panes, the in-process ZLS backend, themes and fonts, the startup file -- and
PART 3 gains counts (and which keys ignore one), f/F/t/T, the whole g table
(bare `G` is a no-op; `ge` is the START of the last line), multiple cursors
and the s/S regex pair, `m`, `]`/`[`, `|`, insert mode, and all fifty leader
paths.

THE REST. design.typ's line table claimed 7,626 lines against a real 38,048,
and its rows did not sum to its own total; its Event/Effect boundary contract
-- the part a shell author writes against -- named four variants that do not
exist and omitted fourteen that do. lsp.md's probe count. config.md's
theme-name rules, which as written could not reach a zed theme at all.
helix-keys.md's Skipped section, holding five families that have since landed.
macos.md's menu bar, undocumented, along with sixteen other claims. web.md on
what the browser build can actually do.

SOURCE COMMENTS that had rotted alongside them: `tag_normal` is a space, not
the `•` its own comment describes; Wrap is ON by default, not off; a FontSel
row is SELECTED by n and RUN by Tab, not run by n; the SPC paths in lsp.zig
lost their `l` group prefix when the language group moved; and the
differential suites are 481 and 561 cases, not 360 and 440.

TWO THINGS FOUND BY DOCUMENTING THEM, both left standing and written down
rather than papered over. Typing `[^\n]` at an s/S prompt panics: the live
preview compiles every prefix, and `[^\` indexes an empty slice in mvzr's
parseCharSet. Both the tutor and a waiver recommended that pattern as the
workaround for `.` matching a newline; they now say what it costs and what
would make it sayable. And `Exec` is a builtin, so an `Exec` line in the
startup config types that command into a shell before the first frame -- the
tutor said nothing in that file is ever sent to one.

Nine adversarial reviews over two rounds, each with the hxdiff harness to
execute what it doubted. The second round exists because the first round's
fixes needed checking too, and it caught three regressions of my own -- one of
them a probe count I had "corrected" away from the truth.

Verified: unit-test, snap 87/87, hxdiff 481/0, hxparity 561/0, mupdf-check.
docs/design.pdf regenerated. The tutor's first seventeen lines are byte-
identical, which is what tutor.golden pins.
</content>
</entry>
<entry>
<title>macos: pixel attachments, live theming, and a signed app</title>
<updated>2026-08-11T12:58:59Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-08-10T20:23:18Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=a797a1ab2f648e773f7a1b28d12bf9441b9f13f4'/>
<id>urn:sha1:a797a1ab2f648e773f7a1b28d12bf9441b9f13f4</id>
<content type='text'>
The AppKit shell now draws what the core renders, follows the theme without a
relaunch, and builds into something you can hand to someone.

- Pixel attachments. Surface.images was dropped on the floor here, so a PDF
  pane showed nothing at all: native_images is now set, pardes_image_s carries
  the geometry the core already clipped, and PardesView keeps one CGImage per
  (serial, page, revision) so scrolling costs a draw and not a decode. Image
  panes get real pixels instead of the petscii fallback.

- Themes take hold live. pardes_tick never advanced the chrome animation, so
  every tagline kept the previous theme's colours until the next launch and
  the 16 ms re-pump spun for the rest of the session. pardes_theme_bg retires
  the hand-agreed #121212 and drives the window background and the titlebar
  appearance; a theme with no background of its own now gets a transparent
  window over an NSVisualEffectView.

- The cell snaps to whole DEVICE pixels rather than whole points. Monaco
  advances 8.4014pt at 14, so ceiling to 9 spaced every column 7.1% wider than
  the face was drawn for.

- The dial is one notch per 10 degrees instead of 20, and a release keeps
  turning in proportion to how hard it was thrown -- ramping up from zero at
  the floor, so a slow twist coasts not a little but not at all.

- A file dropped on the grid is a click plus Look, so it opens beside the pane
  it was dropped on. No drop concept was added to the core.

- The titlebar follows the focused pane: proxy icon, filename, and the dirty
  dot. File.saved_revision is the watermark that last one needed.

- Config (SPC f c) prints the resolved startup config path.

- build.zig assembles, signs and packages the bundle itself; build-app.sh is
  gone. -Dmacos-identity= takes a Developer ID, macos-dmg makes the image, and
  the icon is Glenda.
</content>
</entry>
<entry>
<title>macos: the AppKit shell, its icon, and the offscreen e2e harness</title>
<updated>2026-08-11T12:58:59Z</updated>
<author>
<name>Gabriel Schneider</name>
<email>gbrls@0x4200.cafe</email>
</author>
<published>2026-08-10T12:58:31Z</published>
<link rel='alternate' type='text/html' href='https://git.0x4200.cafe/pardes.git/commit/?id=eb11ab331b4e13e2b9e5a673a4c012d22fdd1d9c'/>
<id>urn:sha1:eb11ab331b4e13e2b9e5a673a4c012d22fdd1d9c</id>
<content type='text'>
</content>
</entry>
</feed>
