From becc5bf4c8e02ed4c98d081c560965cad4b286e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elberte=20Pl=C3=ADnio?= Date: Fri, 31 Jul 2026 14:43:57 -0300 Subject: [PATCH 1/4] fix(macos): use native window chrome so the window gets system rounded corners The main window was created frameless (decorations: false), which on macOS produces a square, shadowless NSWindow. A macOS platform config now decorates the window with an overlay titlebar (hidden title, traffic lights at 14,13): the system draws rounded corners, shadow, and traffic lights, while the app titlebar keeps its custom content and drag handling. The custom window controls and edge resize handles no longer render on macOS - the decorated window provides both natively. Windows 11 already rounds resizable frameless windows via DWM; Linux compositors have no equivalent standard, so both stay frameless. --- src-tauri/tauri.macos.conf.json | 22 ++++++++++++++++++++++ src/components/ResizeHandles.tsx | 7 ++++--- src/components/WindowControls.tsx | 5 +++-- 3 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 src-tauri/tauri.macos.conf.json diff --git a/src-tauri/tauri.macos.conf.json b/src-tauri/tauri.macos.conf.json new file mode 100644 index 00000000..86f50a98 --- /dev/null +++ b/src-tauri/tauri.macos.conf.json @@ -0,0 +1,22 @@ +{ + "app": { + "windows": [ + { + "label": "main", + "title": "PickForge", + "width": 1280, + "height": 820, + "minWidth": 880, + "minHeight": 600, + "decorations": true, + "titleBarStyle": "Overlay", + "hiddenTitle": true, + "trafficLightPosition": { "x": 14, "y": 13 }, + "transparent": false, + "backgroundColor": "#0a0a0b", + "dragDropEnabled": true, + "acceptFirstMouse": true + } + ] + } +} diff --git a/src/components/ResizeHandles.tsx b/src/components/ResizeHandles.tsx index 1d95b306..2585fb09 100644 --- a/src/components/ResizeHandles.tsx +++ b/src/components/ResizeHandles.tsx @@ -1,10 +1,11 @@ import { For } from "solid-js"; -import { isTauri } from "../lib/platform"; +import { hostPlatform, isTauri } from "../lib/platform"; // A frameless window (decorations: false) loses the OS resize border and its // resize cursors. These thin edge + corner zones restore the affordance: each // shows the matching resize cursor and starts a native resize-drag on press, -// so the window resizes exactly like a decorated one. Rendered only under Tauri. +// so the window resizes exactly like a decorated one. Rendered only under Tauri, +// and never on macOS — the window is decorated there and resizes natively. const HANDLES = [ { dir: "North", cls: "n" }, { dir: "South", cls: "s" }, @@ -17,7 +18,7 @@ const HANDLES = [ ] as const; export function ResizeHandles() { - if (!isTauri()) return null; + if (!isTauri() || hostPlatform() === "macos") return null; const start = (dir: string) => async (e: MouseEvent) => { if (e.button !== 0) return; diff --git a/src/components/WindowControls.tsx b/src/components/WindowControls.tsx index b2c4a58c..407df033 100644 --- a/src/components/WindowControls.tsx +++ b/src/components/WindowControls.tsx @@ -13,8 +13,9 @@ async function appWindow() { export function WindowControls() { // The web build (and Playwright VRT) has no window chrome — keep it identical - // to today by rendering nothing. - if (hostPlatform() === "web") return null; + // to today by rendering nothing. macOS uses the native traffic lights (the + // window is decorated with an overlay titlebar there), so no custom controls. + if (hostPlatform() === "web" || hostPlatform() === "macos") return null; const [maximized, setMaximized] = createSignal(false); From 6329bacca3a910ed374efc19f466d365c7302714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elberte=20Pl=C3=ADnio?= Date: Fri, 31 Jul 2026 14:44:36 -0300 Subject: [PATCH 2/4] docs(release): note native macOS window chrome in unreleased draft --- docs/releases/UNRELEASED.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/releases/UNRELEASED.md b/docs/releases/UNRELEASED.md index f94a2ad4..b56d7c77 100644 --- a/docs/releases/UNRELEASED.md +++ b/docs/releases/UNRELEASED.md @@ -6,6 +6,10 @@ reset this file. ## User-facing changes +- The macOS window now uses native chrome: system rounded corners, shadow, + and traffic lights via a titlebar overlay (the custom window-control + buttons and edge resize handles are macOS-native now). Windows/Linux keep + the existing frameless chrome. - The Codex agent-model picker now merges live-discovered models (`codex debug models --bundled`) with the curated static table, so new Codex releases can show up without a PickForge update. Curated entries keep From 985281b19ab0575073e36c4827845f23af8fde3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elberte=20Pl=C3=ADnio?= Date: Fri, 31 Jul 2026 14:53:30 -0300 Subject: [PATCH 3/4] fix(macos): clean up dead chrome paths and guard the platform window config Review follow-ups: WindowControls drops its unreachable macOS ordering branch (the component never renders on macOS now); the Settings window-controls row hides on macOS where it governed nothing; README documents the per-platform chrome; a unit test pins tauri.macos.conf.json to the base window entry so a base-config edit cannot silently skip macOS (the platform merge replaces the windows array wholesale). --- README.md | 7 +++-- src/components/WindowControls.tsx | 13 ++------- src/screens/Settings.tsx | 37 ++++++++++++------------- tests/unit/tauriMacosWindowConf.test.ts | 32 +++++++++++++++++++++ 4 files changed, 57 insertions(+), 32 deletions(-) create mode 100644 tests/unit/tauriMacosWindowConf.test.ts diff --git a/README.md b/README.md index 6b94394c..b79c1b2d 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,11 @@ Requires a [Rust toolchain](https://rustup.rs) and [Bun](https://bun.sh) 1.2+. P on [Tauri v2](https://tauri.app) — a Rust core (`crates/pickforge-core`) behind a Tauri shell (`src-tauri/`) with a SolidJS frontend (`src/`). -The window uses custom chrome (`decorations: false`) — one draggable title bar -with the brand, nav, status, and min/maximize/close controls. On Linux/Wayland, +The window uses custom chrome (`decorations: false`) on Windows/Linux — one +draggable title bar with the brand, nav, status, and min/maximize/close +controls. macOS is decorated instead (`src-tauri/tauri.macos.conf.json`: overlay +title bar, hidden title), so the system draws the rounded corners, shadow, and +traffic lights over the same title bar content. On Linux/Wayland, the window app_id is forced to the bundle identifier `dev.pickforge.app` at startup via `gtk::glib::set_prgname` in `src-tauri/src/lib.rs` (`enableGTKAppId` alone doesn't set the xdg_toplevel app_id under WebKitGTK — GTK derives it from diff --git a/src/components/WindowControls.tsx b/src/components/WindowControls.tsx index 407df033..7734b3d7 100644 --- a/src/components/WindowControls.tsx +++ b/src/components/WindowControls.tsx @@ -105,18 +105,11 @@ export function WindowControls() { ); - // macOS renders controls on the LEFT and follows the traffic-light action - // order close → minimize → maximize (left→right). Windows/Linux render on the - // right with minimize → maximize → close. - const isMac = hostPlatform() === "macos"; - return (
- }> - - - - + + +
); } diff --git a/src/screens/Settings.tsx b/src/screens/Settings.tsx index 200987e6..7e47e6f0 100644 --- a/src/screens/Settings.tsx +++ b/src/screens/Settings.tsx @@ -2157,27 +2157,24 @@ const AppearanceContent = () => ( -
- - Window controls - - macOS · always left - - -
- - {(s) => ( - - )} - + {/* macOS has native traffic lights — there are no app-drawn controls to place. */} + +
+ Window controls +
+ + {(s) => ( + + )} + +
-
+ ); diff --git a/tests/unit/tauriMacosWindowConf.test.ts b/tests/unit/tauriMacosWindowConf.test.ts new file mode 100644 index 00000000..39023a87 --- /dev/null +++ b/tests/unit/tauriMacosWindowConf.test.ts @@ -0,0 +1,32 @@ +// The Tauri v2 platform-config merge (RFC 7396) replaces the `windows` array +// wholesale, so tauri.macos.conf.json must restate the full main-window entry. +// This guards against drift: a field edited in the base config but forgotten in +// the macOS overlay would silently not apply on macOS. +import { readFileSync } from "node:fs"; +import { describe, expect, it } from "vitest"; + +const MACOS_ONLY_FIELDS = ["decorations", "titleBarStyle", "hiddenTitle", "trafficLightPosition"]; + +function mainWindow(path: string): Record { + const conf = JSON.parse(readFileSync(path, "utf8")) as { + app: { windows: Array> }; + }; + const win = conf.app.windows.find((w) => w.label === "main"); + if (!win) throw new Error(`no main window in ${path}`); + return win; +} + +describe("tauri.macos.conf.json window entry", () => { + it("matches the base window entry apart from the macOS chrome fields", () => { + const base = mainWindow("src-tauri/tauri.conf.json"); + const macos = mainWindow("src-tauri/tauri.macos.conf.json"); + + const strip = (win: Record) => + Object.fromEntries(Object.entries(win).filter(([k]) => !MACOS_ONLY_FIELDS.includes(k))); + + expect(strip(macos)).toEqual(strip(base)); + expect(macos.decorations).toBe(true); + expect(macos.titleBarStyle).toBe("Overlay"); + expect(macos.hiddenTitle).toBe(true); + }); +}); From 9ab65b5a7b598a9ed29f6e9adbe13434217e1b87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elberte=20Pl=C3=ADnio?= Date: Fri, 31 Jul 2026 14:54:04 -0300 Subject: [PATCH 4/4] docs(agents): note RFC 7396 wholesale-array merge for platform tauri configs --- AGENTS.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 870ce888..b9c7aa34 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -55,7 +55,10 @@ migrated away. See `plans/rust-migration/`.) That file grants Tauri core/plugin permissions; app-defined commands are reachable without ACL entries, and adding one would switch the app to default-deny and break every command not migrated at the same time. - `tauri.conf.json` is the app manifest. + `tauri.conf.json` is the app manifest. Platform overlays + (`tauri.macos.conf.json`) merge as RFC 7396 — arrays are replaced wholesale, + so an overlaid `windows` entry must restate every base field; a unit test + pins the macOS entry to the base one. - `src/` — SolidJS frontend. - `screens/` — `Onboarding`, `Settings`, `History`, `RunHistory`, and `workbench/` (Workbench, InspectorPanel, ProjectsChatsPanel, FileExplorer).