From cce6b18a49870086982f9a0e1fda90ed170b9fba Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 1 Sep 2026 13:46:44 -0300 Subject: macos: one tagline rule for both hosts, a kqueue beside the inotify, and effects that compile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three things this shell had its own copy of, and in each case the fix is that it stops having one. **The tagline band.** A pane tag draws at `gui_tagline_font_percent` of the body face and the band it sits on shrinks with it, while the grid row stays body-sized — so something has to decide where the shorter band sits in the taller row. This shell decided by centring, always, which is precisely the case `config.gui_topbar_pane_border_px` exists to prevent: the topbar's unused half-band meets the first pane tag's unused half-band and the window background shows through the seam. The strip is as wide as the bands are short — on a 20-pixel cell, 4 physical pixels at the default 82%, 10 at 50%, 14 at 30% — so it grew as the tagline face shrank and read as "the tagline is wrong on the mac" rather than as one missing rule. The rule is `pardes.taglineBandOffset` in the core now and both pixel hosts call it: row zero bottom-aligned, the first pane-tag row top-aligned, the two joined by `gui_topbar_pane_border_px` in the theme's scrollbar-track colour, every row between centred, and a `Tagbottom` band on the final row flush with the window edge — with the sub-cell strip beneath it painted in that band's own colour, because the core grid holds only whole cells and a window is any height it likes. `pardes_tagline_band_offset`, `pardes_topbar_pane_border_px` and `pardes_topbar_pane_border_rgb` carry it over the C ABI as PHYSICAL pixels: the host multiplies its points by the backing scale going in and divides coming out, which is the snapping `Metrics` already does for the cell, and is what keeps a one-pixel rule one pixel instead of a two-pixel smear. **The watch.** `file_watch.zig` was one mark/reconcile transaction over `inotify`, so the tty shell, the SDL window and the detached daemon all watched nothing off Linux: an edit made outside pardes never reached the pane, and a PDF replaced on disk kept rendering the old inode. It is the same transaction over two kernels now — `init`, `wait`, `stop`, `drain`, `markDir` and `unmarkDir` are still the whole of it, and the hosts wait on a kqueue and poll it exactly as they did the old descriptor. A macOS mark is TWO filters, because a kqueue directory filter reports its entries changing and never a write to a file already inside it: the parent mark follows rename-over saves, `markFile` catches in-place writes, and `remarkFile` re-arms the file filter once a rename has moved the inode. That is the same pair the AppKit host's DispatchSources already used for the same reason. Directory marks are deduplicated here by device and inode, because each `EVFILT_VNODE` filter needs a descriptor of its own and inotify did that deduplication itself; `stop` and `drain` wake through the one `EVFILT_USER` filter, since a kqueue cannot simply be read the way an inotify descriptor can. **The effects.** The three `crt.ci.metal` entry points are `extern "C" [[stitchable]]`. `CIKernel.kernels(withMetalString:)` compiles that source at runtime, looks for stitchable functions, and rejects the WHOLE source with "cannot find a valid stitchable Metal function in the source" when it finds none — so `ScenePostprocessor.init?` returned nil and every scene effect and panel transition silently degraded to the plain CoreText draw. The `effect_sources.zig` test pins the exact spelling of all three, and `draw-effect` in the e2e suite catches the degradation rather than the spelling. Beside them, the offscreen harness owes the core a PRESENTATION. Its window is borderless and never ordered front, so AppKit runs no display cycle and `pardes_frame_presented` — whose only caller is `draw(_:)` — never fired. The core holds pointer gestures inert while a layout mutation has not reached a backend, which for an unpresenting harness is the rest of the script: the first pane a script opened silently killed every later click, drag and Look. So `readFrame` presents what it just rendered, into a bitmap nobody reads. `PARDES_CHROME` also looks under `/Applications`, where a browser's executable lives inside an application bundle and never on `PATH`. The macOS goldens are regenerated; docs/macos.md, config.md, detached.md, web.md and the design PDF follow. --- docs/macos.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) (limited to 'docs/macos.md') diff --git a/docs/macos.md b/docs/macos.md index 2b50e6bc..0ce29bf9 100644 --- a/docs/macos.md +++ b/docs/macos.md @@ -529,6 +529,34 @@ different kinds of assertion because a snapshot is the core's cell buffer and the core has no font: `font Menlo-Regular` asks the view what it is actually wearing, and the snapshots catch the grid moving when the cell changes size. +### The tagline band follows the core's rule, not this shell's + +A pane tag is drawn at `gui_tagline_font_percent` of the body face +(`TaglineSize ` changes it live) and the band it sits on shrinks with +it, while the grid row stays body-sized. Where that shorter band sits inside its +row is no longer this shell's arithmetic: `pardes_tagline_band_offset` and +`pardes_topbar_pane_border_px` answer out of `src/pardes.zig`, and that is the +same rule `src/gui/gui.zig` draws with. + +It is shared because it drifted. This shell centred every band in its own row, +and centring two reduced-height bands is precisely the case +`config.gui_topbar_pane_border_px` exists to prevent: the topbar's unused +half-band meets the first pane tag's unused half-band and the window background +shows through the seam. The strip is as wide as the bands are short — on a +20-pixel cell, 4 physical pixels at the default 82%, 10 at 50%, 14 at 30% — so it +read as "the tagline is wrong on the mac" rather than as one missing rule. Row +zero is bottom-aligned now, the first pane-tag row top-aligned, the two joined by +`gui_topbar_pane_border_px` in the theme's scrollbar-track colour +(`pardes_topbar_pane_border_rgb`, or a compiled override), every row between +centred, and a `Tagbottom` band on the final row bottom-aligned against the +window edge — with the sub-cell strip below it painted in that band's own colour, +because the core grid holds only whole cells and a window is any height it likes. + +The offsets cross as PHYSICAL PIXELS. The host multiplies its points by the +backing scale going in and divides coming out, which is the snapping `Metrics` +already does for the cell, and is what keeps a one-pixel rule one pixel instead +of a two-pixel smear. + ### The cell is snapped to device pixels, not to points The grid has to land on whole *device* pixels: the background pass runs with @@ -843,6 +871,11 @@ already do (`install_app_bin`, `install_plist`, `install_scene_kernel`, module import named `effect-source-crt.ci.metal`, added by `build.zig`'s per-shell module wiring under `if (shell == .macos)`, which is what lets `EffectCode` print the exact source the app executes. + Its three entry points are `extern "C" [[stitchable]]`: the runtime compiler + looks for stitchable functions and rejects the WHOLE source with "cannot find + a valid stitchable Metal function in the source" when there are none, which + costs the view its postprocessor and turns every effect silently off. The + `draw-effect` command in the e2e suite exists to catch exactly that. - **The link.** One swiftc invocation, with the optimize mode following `-Doptimize` — `-Onone` for Debug, `-Osize` for ReleaseSmall, `-O` otherwise — so both halves of the app are built the same way: @@ -991,6 +1024,18 @@ every pixel comes out identical, which is what keeps `draw(_:)` honest — `snap reads the core's cell buffer and would be perfectly happy with a `draw` that returned on its first line. +The harness also owes the core a PRESENTATION, and that is not cosmetic. The +window is borderless and never ordered front, so AppKit runs no display cycle +for it and `draw(_:)` — the only caller of `pardes_frame_presented` — would +never run outside the `draw` command. The core holds pointer gestures inert +while a layout mutation has not reached a backend (`panel_presentation_pending`, +read in `presentedPointer`), which for the app is one frame and for an +unpresenting harness is the rest of the script: the first pane a script opens +would silently kill every later click, drag and Look. So `readFrame` presents +what it just rendered, into a bitmap nobody reads — the app's +`AppDelegate.pump` marks the view and AppKit draws it, and this is the same +debt paid the same way. + The Linux loop proves the new C layout, flag encoding, clock wrap, embedded kernel source, header syntax, and static library. Compiling Swift, runtime Metal kernel compilation, and comparing processed pixels remain `macos-e2e` work on @@ -998,8 +1043,10 @@ a Darwin host; Linux has neither AppKit nor Apple's Metal runtime. Goldens are hermetic: a fake `$HOME` with a pinned `PS1`, `Shell bash` in the config (fish's prompt carries a hostname), `LC_ALL=C`, `PARDES_NOTIME=1`, and -`TMPDIR` inside the per-script world so that `New`'s document has a reproducible -directory — its six mkstemp characters are masked on capture. +`TMPDIR` inside the per-script world, so that any temporary document a script +opens has a reproducible directory — its six mkstemp characters are masked on +capture. `New` itself no longer makes one: since `c3d0b84` it opens the +in-memory `+New` scratch buffer and `Save` asks for a path. **The app itself.** `zig build -Dplatform=macos && open zig-out/pardes.app`. Some things only a hand can test: which System Settings checkbox is on, what a -- cgit v1.3