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). 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/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 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..7734b3d7 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); @@ -104,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 (