From a797a1ab2f648e773f7a1b28d12bf9441b9f13f4 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 10 Aug 2026 17:23:18 -0300 Subject: macos: pixel attachments, live theming, and a signed app 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. --- src/pardes.zig | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) (limited to 'src/pardes.zig') diff --git a/src/pardes.zig b/src/pardes.zig index ec938a6d..d07fc1f1 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -1748,6 +1748,43 @@ test "startup config runs builtin lines in order and isolates bad lines" { }; } +test "Config prints the resolved startup config path and refreshes one buffer" { + const path = "/home/pardes-test/.config/pardes"; + // `startup_config` stays null: the file is MISSING and the path still + // resolves, which is the case this builtin exists to answer. + const p = try Pardes.init(std.testing.allocator, .{ .startup_config_path = path }); + defer p.deinit(); + + try std.testing.expect(p.executeBuiltinLine(0, "Config")); + const opened = p.active; + const out = p.panes[opened].?.file.?; + try std.testing.expectEqualStrings(path ++ "\n", out.content); + try std.testing.expectEqualStrings(config.config_buffer, std.fs.path.basename(out.path)); + try std.testing.expectEqual(output_pane.Origin{ .cmd = .Config }, out.output.?.from); + + // Asking again refreshes the buffer already open rather than stacking a + // byte-identical twin beside it — Help's rule, and for the same reason. + try std.testing.expect(p.executeBuiltinLine(0, "Config")); + try std.testing.expectEqual(opened, p.active); + var buffers: usize = 0; + for (p.panes) |slot| { + const sp = slot orelse continue; + const f = sp.file orelse continue; + const o = f.output orelse continue; + if (std.meta.eql(o.from, output_pane.Origin{ .cmd = .Config })) buffers += 1; + } + try std.testing.expectEqual(@as(usize, 1), buffers); +} + +test "Config says so when there is no per-user config path" { + const p = try Pardes.init(std.testing.allocator, .{}); + defer p.deinit(); + + try std.testing.expect(p.executeBuiltinLine(0, "Config")); + const out = p.panes[p.active].?.file.?; + try std.testing.expect(std.mem.indexOf(u8, out.content, "no per-user config path") != null); +} + test "runtime theme changes animate chrome and retarget without a jump" { const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true }); defer p.deinit(); @@ -3215,6 +3252,22 @@ pub const File = struct { /// goes through file_pane.setContent, which bumps this; a pipe completion /// accepted against another revision would overwrite intervening work. revision: u32 = 0, + /// The `revision` this buffer was last WRITTEN at. Equal means what is on + /// screen is what is on disk; anything else is unsaved work. + /// + /// Bookkeeping only — nothing in the core renders it, and no shell has to + /// read it. It exists because a windowed host has somewhere to PUT the + /// answer (macOS puts a dot in the close button, and the proxy icon it sits + /// beside is the same pane's path), and a shell cannot derive it: revision + /// counts edits, and only the save knows which edit was the last one + /// committed. Zero for a fresh buffer, which is why every construction site + /// gets clean-on-open from the default and none of them mention it. + /// + /// Marked at the moment Save is ASKED, not when the write lands: the + /// save_file effect carries no completion back, so this is as honest as the + /// rest of that path. A failed write reads as saved, exactly as the tagline + /// already does. + saved_revision: u32 = 0, /// set = this is an OUTPUT buffer (acme's +Errors): a file pane with no /// file behind it, showing text the core produced itself. It records the /// COMMAND that opened it, and output_pane.zig's one table turns that into @@ -4162,6 +4215,11 @@ pub const Options = struct { /// deterministic; when present, each line is dispatched as a builtin /// before init returns and therefore before any frontend can render. startup_config: ?[]const u8 = null, + /// ...and WHERE that came from, which is a separate fact: the path + /// resolves even when the file does not exist, and that is precisely the + /// case the Config builtin is asked about. Null on the web and in every + /// core test, where there is no per-user config to name. + startup_config_path: ?[]const u8 = null, image_allocator: ?std.mem.Allocator = null, pdf_allocator: ?std.mem.Allocator = null, tree_sitter_allocator: ?std.mem.Allocator = null, -- cgit v1.3