From c989653d89097f901adbead90921fcb82b907fad Mon Sep 17 00:00:00 2001 From: Thomas Ricouard Date: Wed, 8 Apr 2026 11:27:37 -0700 Subject: [PATCH] Add build macOS apps plugin (#130) ## Summary - add `build-macos-apps` as a new public Codex plugin for macOS app development workflows - include macOS-focused skills for build/run/debug loops, SwiftUI/AppKit guidance, Liquid Glass, window management, telemetry, signing, packaging, SwiftPM, and test triage - add command entrypoints, assets, plugin metadata, marketplace registration, and root README discovery - document maintainable new-app scaffolding guidance so non-trivial SwiftUI/macOS apps start with separate App, Views, Models, Stores, Services, and Support files ## Notes - keeps the plugin skills-first at the plugin level, matching the public `build-ios-apps` shape - uses public OpenAI metadata, policy URLs, MIT licensing, and Coding category conventions - preserves the `script/build_and_run.sh` and `.codex/environments/environment.toml` run-button guidance while keeping it separate from app source ## Validation - `git diff --check` and `git diff --cached --check` - JSON parsing for `plugins/build-macos-apps/.codex-plugin/plugin.json` and `.agents/plugins/marketplace.json` - alignment checks against `plugins/build-ios-apps/.codex-plugin/plugin.json` - skill frontmatter, asset path, marketplace ordering, and metadata scrub checks - grep/diff review for scaffold guidance consistency across `swiftui-patterns`, `view-refactor`, `build-run-debug`, and directly referenced component docs --- .agents/plugins/marketplace.json | 12 + README.md | 1 + .../.codex-plugin/plugin.json | 47 ++++ plugins/build-macos-apps/README.md | 77 ++++++ plugins/build-macos-apps/agents/openai.yaml | 6 + plugins/build-macos-apps/assets/app-icon.png | Bin 0 -> 4722 bytes .../assets/build-macos-apps-small.svg | 21 ++ .../commands/build-and-run-macos-app.md | 30 +++ .../commands/fix-codesign-error.md | 22 ++ .../commands/test-macos-app.md | 23 ++ .../skills/appkit-interop/SKILL.md | 68 ++++++ .../skills/appkit-interop/agents/openai.yaml | 4 + .../references/drag-drop-pasteboard.md | 23 ++ .../references/representables.md | 56 +++++ .../references/responder-menus.md | 22 ++ .../references/window-panels.md | 37 +++ .../skills/build-run-debug/SKILL.md | 129 ++++++++++ .../skills/build-run-debug/agents/openai.yaml | 4 + .../references/run-button-bootstrap.md | 183 ++++++++++++++ .../skills/liquid-glass/SKILL.md | 170 +++++++++++++ .../skills/liquid-glass/agents/openai.yaml | 4 + .../skills/packaging-notarization/SKILL.md | 47 ++++ .../packaging-notarization/agents/openai.yaml | 4 + .../skills/signing-entitlements/SKILL.md | 58 +++++ .../signing-entitlements/agents/openai.yaml | 4 + .../skills/swiftpm-macos/SKILL.md | 50 ++++ .../skills/swiftpm-macos/agents/openai.yaml | 4 + .../skills/swiftui-patterns/SKILL.md | 209 ++++++++++++++++ .../swiftui-patterns/agents/openai.yaml | 4 + .../references/commands-menus.md | 41 ++++ .../references/components-index.md | 16 ++ .../references/menu-bar-extra.md | 67 ++++++ .../swiftui-patterns/references/settings.md | 55 +++++ .../references/split-inspectors.md | 38 +++ .../swiftui-patterns/references/windowing.md | 50 ++++ .../skills/telemetry/SKILL.md | 86 +++++++ .../skills/telemetry/agents/openai.yaml | 4 + .../skills/test-triage/SKILL.md | 54 +++++ .../skills/test-triage/agents/openai.yaml | 4 + .../skills/view-refactor/SKILL.md | 109 +++++++++ .../skills/view-refactor/agents/openai.yaml | 4 + .../skills/window-management/SKILL.md | 227 ++++++++++++++++++ .../window-management/agents/openai.yaml | 4 + 43 files changed, 2078 insertions(+) create mode 100644 plugins/build-macos-apps/.codex-plugin/plugin.json create mode 100644 plugins/build-macos-apps/README.md create mode 100644 plugins/build-macos-apps/agents/openai.yaml create mode 100644 plugins/build-macos-apps/assets/app-icon.png create mode 100644 plugins/build-macos-apps/assets/build-macos-apps-small.svg create mode 100644 plugins/build-macos-apps/commands/build-and-run-macos-app.md create mode 100644 plugins/build-macos-apps/commands/fix-codesign-error.md create mode 100644 plugins/build-macos-apps/commands/test-macos-app.md create mode 100644 plugins/build-macos-apps/skills/appkit-interop/SKILL.md create mode 100644 plugins/build-macos-apps/skills/appkit-interop/agents/openai.yaml create mode 100644 plugins/build-macos-apps/skills/appkit-interop/references/drag-drop-pasteboard.md create mode 100644 plugins/build-macos-apps/skills/appkit-interop/references/representables.md create mode 100644 plugins/build-macos-apps/skills/appkit-interop/references/responder-menus.md create mode 100644 plugins/build-macos-apps/skills/appkit-interop/references/window-panels.md create mode 100644 plugins/build-macos-apps/skills/build-run-debug/SKILL.md create mode 100644 plugins/build-macos-apps/skills/build-run-debug/agents/openai.yaml create mode 100644 plugins/build-macos-apps/skills/build-run-debug/references/run-button-bootstrap.md create mode 100644 plugins/build-macos-apps/skills/liquid-glass/SKILL.md create mode 100644 plugins/build-macos-apps/skills/liquid-glass/agents/openai.yaml create mode 100644 plugins/build-macos-apps/skills/packaging-notarization/SKILL.md create mode 100644 plugins/build-macos-apps/skills/packaging-notarization/agents/openai.yaml create mode 100644 plugins/build-macos-apps/skills/signing-entitlements/SKILL.md create mode 100644 plugins/build-macos-apps/skills/signing-entitlements/agents/openai.yaml create mode 100644 plugins/build-macos-apps/skills/swiftpm-macos/SKILL.md create mode 100644 plugins/build-macos-apps/skills/swiftpm-macos/agents/openai.yaml create mode 100644 plugins/build-macos-apps/skills/swiftui-patterns/SKILL.md create mode 100644 plugins/build-macos-apps/skills/swiftui-patterns/agents/openai.yaml create mode 100644 plugins/build-macos-apps/skills/swiftui-patterns/references/commands-menus.md create mode 100644 plugins/build-macos-apps/skills/swiftui-patterns/references/components-index.md create mode 100644 plugins/build-macos-apps/skills/swiftui-patterns/references/menu-bar-extra.md create mode 100644 plugins/build-macos-apps/skills/swiftui-patterns/references/settings.md create mode 100644 plugins/build-macos-apps/skills/swiftui-patterns/references/split-inspectors.md create mode 100644 plugins/build-macos-apps/skills/swiftui-patterns/references/windowing.md create mode 100644 plugins/build-macos-apps/skills/telemetry/SKILL.md create mode 100644 plugins/build-macos-apps/skills/telemetry/agents/openai.yaml create mode 100644 plugins/build-macos-apps/skills/test-triage/SKILL.md create mode 100644 plugins/build-macos-apps/skills/test-triage/agents/openai.yaml create mode 100644 plugins/build-macos-apps/skills/view-refactor/SKILL.md create mode 100644 plugins/build-macos-apps/skills/view-refactor/agents/openai.yaml create mode 100644 plugins/build-macos-apps/skills/window-management/SKILL.md create mode 100644 plugins/build-macos-apps/skills/window-management/agents/openai.yaml diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json index 70ac03e7..a60b713a 100644 --- a/.agents/plugins/marketplace.json +++ b/.agents/plugins/marketplace.json @@ -282,6 +282,18 @@ }, "category": "Coding" }, + { + "name": "build-macos-apps", + "source": { + "source": "local", + "path": "./plugins/build-macos-apps" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Coding" + }, { "name": "build-web-apps", "source": { diff --git a/README.md b/README.md index 4ad75509..2e68e165 100644 --- a/README.md +++ b/README.md @@ -12,5 +12,6 @@ Highlighted richer examples in this repo include: - `plugins/figma` for `use_figma`, Code to Canvas, Code Connect, and design system rules - `plugins/notion` for planning, research, meetings, and knowledge capture - `plugins/build-ios-apps` for SwiftUI implementation, refactors, performance, and debugging +- `plugins/build-macos-apps` for macOS SwiftUI/AppKit workflows, build/run/debug loops, and packaging guidance - `plugins/build-web-apps` for deployment, UI, payments, and database workflows - `plugins/netlify`, `plugins/render`, and `plugins/google-slides` for additional public skill- and MCP-backed plugin bundles diff --git a/plugins/build-macos-apps/.codex-plugin/plugin.json b/plugins/build-macos-apps/.codex-plugin/plugin.json new file mode 100644 index 00000000..73269150 --- /dev/null +++ b/plugins/build-macos-apps/.codex-plugin/plugin.json @@ -0,0 +1,47 @@ +{ + "name": "build-macos-apps", + "version": "0.1.2", + "description": "Build, run, test, debug, instrument, and implement local macOS apps using Xcode, SwiftUI, AppKit interop, unified logging, and shell-first desktop workflows.", + "author": { + "name": "OpenAI", + "email": "support@openai.com", + "url": "https://openai.com/" + }, + "homepage": "https://openai.com/", + "repository": "https://github.com/openai/plugins", + "license": "MIT", + "keywords": [ + "macos", + "swift", + "swiftui", + "appkit", + "xcode", + "swiftpm", + "debugging", + "codesign", + "logging", + "telemetry", + "oslog" + ], + "skills": "./skills/", + "interface": { + "displayName": "Build macOS Apps", + "shortDescription": "Build, debug, instrument, and implement macOS apps with SwiftUI and AppKit guidance", + "longDescription": "macOS development workflows for discovering local projects, building and running desktop apps, implementing native SwiftUI scenes, bridging into AppKit when necessary, adding lightweight Logger telemetry, triaging test failures, inspecting signing and entitlements, and debugging desktop-specific build or runtime errors.", + "developerName": "OpenAI", + "category": "Coding", + "capabilities": [ + "Interactive", + "Read", + "Write" + ], + "websiteURL": "https://openai.com/", + "privacyPolicyURL": "https://openai.com/policies/privacy-policy/", + "termsOfServiceURL": "https://openai.com/policies/terms-of-use/", + "defaultPrompt": "Build/refactor native macOS UI, inspect AppKit interop, add telemetry, or debug a macOS app", + "screenshots": [], + "brandColor": "#1F6FEB", + "composerIcon": "./assets/build-macos-apps-small.svg", + "logo": "./assets/app-icon.png" + } +} diff --git a/plugins/build-macos-apps/README.md b/plugins/build-macos-apps/README.md new file mode 100644 index 00000000..4b7be879 --- /dev/null +++ b/plugins/build-macos-apps/README.md @@ -0,0 +1,77 @@ +# Build macOS Apps Plugin + +This plugin packages macOS-first development workflows in `plugins/build-macos-apps`. + +It currently includes these skills: + +- `build-run-debug` +- `test-triage` +- `signing-entitlements` +- `swiftpm-macos` +- `packaging-notarization` +- `swiftui-patterns` +- `liquid-glass` +- `window-management` +- `appkit-interop` +- `view-refactor` +- `telemetry` + +## What It Covers + +- discovering local Xcode workspaces, projects, and Swift packages +- building and running macOS apps with shell-first desktop workflows +- creating one project-local `script/build_and_run.sh` entrypoint and wiring `.codex/environments/environment.toml` so the Codex app Run button works +- implementing native macOS SwiftUI scenes, menus, settings, toolbars, and multiwindow flows +- adopting modern macOS Liquid Glass and design-system guidance with standard SwiftUI structures, toolbars, search, controls, and custom glass surfaces +- tailoring SwiftUI windows with title/toolbar styling, material-backed container backgrounds, minimize/restoration behavior, default and ideal placement, borderless window style, and launch behavior +- bridging into AppKit for representables, responder-chain behavior, panels, and other desktop-only needs +- refactoring large macOS view files toward stable scene, selection, and command structure +- adding lightweight `Logger` / `os.Logger` instrumentation for windows, sidebars, menu commands, and menu bar actions +- reading and verifying runtime events with Console, `log stream`, and process logs +- triaging failing unit, integration, and UI-hosted macOS tests +- debugging launch failures, crashes, linker problems, and runtime regressions +- inspecting signing identities, entitlements, hardened runtime, and Gatekeeper issues +- preparing packaging and notarization workflows for distribution + +## What It Does Not Cover + +- iOS, watchOS, or tvOS simulator control +- desktop UI automation +- App Store Connect release management +- pixel-perfect visual design or design-system generation + +## Plugin Structure + +The plugin lives at: + +- `plugins/build-macos-apps/` + +with this shape: + +- `.codex-plugin/plugin.json` + - required plugin manifest + - defines plugin metadata and points Codex at the plugin contents + +- `agents/` + - plugin-level agent metadata + - currently includes `agents/openai.yaml` for the OpenAI surface + +- `commands/` + - reusable workflow entrypoints for common macOS development tasks + +- `skills/` + - the actual skill payload + - each skill keeps the normal skill structure (`SKILL.md`, optional + `agents/`, `references/`, `assets/`, `scripts/`) + +## Notes + +This plugin is currently skills-first at the plugin level. It does not ship a +plugin-local `.mcp.json`, matching the public `plugins/build-ios-apps` shape. + +The default posture is shell-first. Unlike the iOS build plugin, this plugin +does not assume simulator tooling or touch-driven UI inspection for its main +workflows. The core execution model leans on `xcodebuild`, `swift`, `open`, +`lldb`, `codesign`, `spctl`, `plutil`, and `log stream`, with a compact desktop +UI layer for native SwiftUI scene design, AppKit interop, and macOS-specific +refactoring. diff --git a/plugins/build-macos-apps/agents/openai.yaml b/plugins/build-macos-apps/agents/openai.yaml new file mode 100644 index 00000000..68ac2d62 --- /dev/null +++ b/plugins/build-macos-apps/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "Build macOS Apps" + short_description: "Build, debug, instrument, and implement macOS apps with SwiftUI and AppKit guidance" + icon_small: "./assets/build-macos-apps-small.svg" + icon_large: "./assets/app-icon.png" + default_prompt: "Use Build macOS Apps to build or refactor native macOS UI, inspect windowing and AppKit interop issues, add telemetry, or debug a macOS app." diff --git a/plugins/build-macos-apps/assets/app-icon.png b/plugins/build-macos-apps/assets/app-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d1167805cd9c63a08ec95bd9487a3a27bcb058f6 GIT binary patch literal 4722 zcmd5=`8$;D7k{3a$(C&>TQU<$mdLI!M#P9@$x@af#!|u?AsI%tDKcXV$In8&@Ty9CW=5!`i0PkaP)KO=|DBxX4A7@%V~C-}VOqEL_jcz3 z0s>4Im}QViG1t3qcyC7>*%ytS{kCj#b96s5H+TB;)YKlWNkdpjh%r4`7qm7$nOBP_ zFutyE;pC&9!s%1=x%~9g165+wBl%;f>-HBe`y!gr=(r+8ez}5qda(1feCdPXV?q_e zSsRqRFpGOtmiMYe#ks)D%7*Tfl_6yc!|6S!bhs9EvoSE=XRQT)?bbyz4^ym%%Zs6q z<}lWEGMm}3(Vb>Lg31w?%K78sJ;%k>#prQsvKcSsk4vv}bZv8V9!hw{{s#rT#wKrx zhwRk0?v~YeO7*hitrw5d(@>AkDhTs=O(a!u?bJyK-lHoUM&Ne!Wq;+`!M@}&{E5}v zWBZ4~&SNFe5^aix^T#~UQnTRK^Pi@|2hHYD2Ga9M!YCGaK7NUf-gl&~mK3;*(kVX<2;kkmMsaiX@uD1eV-qBX?ODX1qsIeVK? zcKRo9Vl|NHY@znN_>_W!UE3k`+O!WNT<(FRhuhRf%4oHJAA3*3c5iMnH=pio)kLm{ zm7UYb2Tzq@ABTDZ;)##g7#9)lFIe_mq!ov9zJhqR7gexI7U1zb?Fx=XvL^y@dG~E_ z2j%%o`CJyS+Q$^|l0UHflZWW%s_0nfgCv7n>r>4mc7=t_yl-umuzUqAXLZ-O+;7}i z4Fkl>LvOQIlP-^weEj@c#eg`awaSwOtaulIdX`1!LiI%o#pxW)gknwFT0*QCqu|z-gG3j$- zu%9fzQhI-6QVo=yurmb*0y6uA4)c>ly8~(6YKC|&QjR>hV=J9>0!<+0EM~#xU%q_F ze2v=hYRCh8*0Ht5{aM&2k%-g9ErJ$Pb)F{vYTS z?#Ij#>r3yTYFX3zYt~R)JIH6Lzh7so*KYrJU$(Y3{gU3U8iuu;Uw3EA14wv(UH}5} z#H*YU3tD;kwSCUKPd?dcN!S|PVlqDb z^ZgAU{9~&ESQ+zy+@OAY@Apo~OpNes9;0>jbA5pJe)-R-yGnaMYFiHsNEmFhjllQb z#S6;_P}s9|?x-l@Tg3Y@zkGl-Bz3-hBg9_|vl6l2xZWZ!Ie-RDXnkj%i&B)4ks6%1 zYkI6f%5#Z7)(;Mx6}@S*yD#C|BSXZvB-$if_pSzS6d9|Hkbc_0%J?w)A)n0gLLV!@ z+WXmxin}SE6y;dKv7JSdw&wes(lV7VkBt0`kj@MZc#A3C*Oi2w;l!yBuMl-`hG-ek zWbt&|QX>-CX^c(2!&)iKryC+}A^ANn7^3;0pYcj9r#?1Ww7dF@AtLq?Ho2bo zD~LOW+*SbvGT_1;mpttVOYVK2rOE!2d<7ZlDnN}M9UZ;q1+6y53Y37N<8lQ8ND(2> zq^RAETuQxQ=%R&?E*7KyxtCa+p^FG)HE`93Y|Nr-tK^W8dxF%r-#*t13M)GDBVFVW z()A4wk4v_xap2&FhzvH++c1{gw6mB6ZpV?-+?SDOg1*{6(GFH5h4WL}xpgIFF6A6T zE^~t3R5Fl)Mn4ole#t`=xJtsrqe*J!hNN-?0trIc4RR-UW~zF^GLC{X@&*(f53bo3 zk--Db;GrOTzi0U1e~cTk;z^-BDS|TQh|@&aYbl;Hl2G3V&2mZK?F}B{vZFF$+O|14 z8pW;~`nbJ_)gV+9l?LEepm|q^%K7o%#1>+P%?NxCbb1} za(ELr-U);Mx|rvhgQtm_IiZZ=<;K?i-SvOno(2z$2?t=q$I)NA24*ci)oT5wd0j#g z(K2FJb>uckD}ww%>Tg3Xqr(4j1)x__G8 zkP-h85>PFLE+BU-^rXWxPJ!M}LI5XD+?%&>Ee_B-H3VRPDWYbq^Sm?%=7cJAXl*gj zJ<76V9NuHvpyV`C3$0X~U_&As8HsKkiSOw|+OVuE8|zV@?Np%2{XhjCJG-KrR)mw7rEjO#DT z_sp`jgMB>@{D{Z@ew_`7Nuqi9DP1M_4$bMXfTh@!-`0~YrTCPC!ir>^)RUOBCn08| z|E$VNP;6T<>55L9Oz&ut^a+U+^=Ll$0o0=<{QO{@v-ZM=zu2AG4uw%*rv_pxv_wvuv1$|syjoq+P?yl#-|3IqE!B&%McskAp^c_tsL#(zMxUJ5ly+-!NeHc?DAl~AA2Kf4z*vm74MWQdW$-d zIu_+cl$m1aZ}%v*ey(rx7r^%@FtSqddzWi4u#e3o-ngYyo`lX4@@_7284rkE& zbER8HP>!9`w8%Mx1D`?GbH#`rYH&KBB-A zfe?A104f`@=Lv$~|AnTgftguZjYojp^hNu$6X=PF2^fq!k^~IYn1P?TNqZ9s(kchu z3JJAw6U6@BR|i6U!^6W>lPh8loHl?K=bjhH?0Y@VP0HyBq^ZEcjHfX%F%WUNi0#%~ zFaoymkKH~vN>?>Buv!UsLZkuK$~P8^r73maM_9m@4IA-nT((Pys&@e5PyeM;;sN}5 zwTp|38j?v6yNcR=2rL(ydAisX$1bPopB*qJFe~e8YJNCMCx7#8s;MMlx;6HTRHpiN zqk!ey8%bsjq>Lzln7Z*X)tAkxj7Iych5pjnmAxKT7?!*A2<}o9{)cBFdAvQydwD7r`*jNH z6!tcEHC3$$3u^~^x2>%$zd8*>Ms&mN4?|ym>tDR-`Y^4GwlC{&W#107O98Yp7Hi7e zA9LewL-n+3Opzd#hh3LHxC|veCF@{!k*5zeGA}f0ehgO=fdzXSkb6TSY?Ew(QbsI@O;C0`b@9FmyM~D23z~QK^&cMM&_0B%j^>Q z?Q<8t9|rH>?svwL0X`(fn4fA~gj5y;jXb^f3PW@rS62jC_VK7b;3;cqMR)!3{j6H)Uo6t_V(0Y(3?Iud@E~O?XF+xt^k!-_3XgT$gisp7msGW%HhNr+e%Rl?x_CL zM~~QDt&JiIQsG6VNmcW^SF|RDW4+v5U1xYo6TczcVbFWnnO=&VxH=HQV zMm3D!n-3xG=FY~b*qv&=y95dTrCj8SsGs#IMMY<2@mvfv4S>=(m5$nMc*oRLj=W#w za2b_K8KY@HlK|eqWy^-1g<(5Wi^O#NJt^>ONx4e*`i16KGwg)&${CNiIAd(6`=L~^ zL9;QAjf%9t()d;MnF?wr3WwmX+iSy)fx;4x6S zdOo3_Jgz`*BpBK6f2-ac-wC+?jwgWoe>ZZ0B6+>qhf6E*6xq9ZC;bU*a^Bpq{G7|Z F{{Z})KK=jz literal 0 HcmV?d00001 diff --git a/plugins/build-macos-apps/assets/build-macos-apps-small.svg b/plugins/build-macos-apps/assets/build-macos-apps-small.svg new file mode 100644 index 00000000..2cc64d12 --- /dev/null +++ b/plugins/build-macos-apps/assets/build-macos-apps-small.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/build-macos-apps/commands/build-and-run-macos-app.md b/plugins/build-macos-apps/commands/build-and-run-macos-app.md new file mode 100644 index 00000000..dc493ff3 --- /dev/null +++ b/plugins/build-macos-apps/commands/build-and-run-macos-app.md @@ -0,0 +1,30 @@ +# /build-and-run-macos-app + +Create or update the project-local macOS `build_and_run.sh` script, wire the +Codex app Run button, then use that script as the default build/run entrypoint. + +## Arguments + +- `scheme`: Xcode scheme name (optional) +- `workspace`: path to `.xcworkspace` (optional) +- `project`: path to `.xcodeproj` (optional) +- `product`: SwiftPM executable product name (optional) +- `mode`: `run`, `debug`, `logs`, `telemetry`, or `verify` (optional, default: `run`) +- `app_name`: process/app name to stop before relaunching (optional) + +## Workflow + +1. Detect whether the repo uses an Xcode workspace, Xcode project, or SwiftPM package. +2. If the workspace is not inside git yet, run `git init` at the project root so Codex app git-backed features unlock. +3. Create or update `script/build_and_run.sh` so it always stops the current app, builds the macOS target, and launches the fresh result. +4. For SwiftPM, keep raw executable launch only for true CLI tools; for AppKit/SwiftUI GUI apps, create a project-local `.app` bundle and launch it with `/usr/bin/open -n`. +5. Support optional script flags for `--debug`, `--logs`, `--telemetry`, and `--verify`. +6. Follow the canonical bootstrap contract in `../skills/build-run-debug/references/run-button-bootstrap.md` for the exact script shape and `.codex/environments/environment.toml` format. +7. Run the script in the requested mode and summarize any build, script, or launch failure. + +## Guardrails + +- Do not initialize a nested git repo inside an existing parent checkout. +- Do not leave stale `Run` actions pointing at old script paths. +- Keep the no-flag script path simple: kill, build, run. +- Use `--debug`, `--logs`, `--telemetry`, or `--verify` only when the user asks for those modes. diff --git a/plugins/build-macos-apps/commands/fix-codesign-error.md b/plugins/build-macos-apps/commands/fix-codesign-error.md new file mode 100644 index 00000000..5414e483 --- /dev/null +++ b/plugins/build-macos-apps/commands/fix-codesign-error.md @@ -0,0 +1,22 @@ +# /fix-codesign-error + +Inspect a macOS signing or entitlement failure and explain the minimum fix path. + +## Arguments + +- `app`: path to `.app` bundle or binary (optional) +- `identity`: signing identity hint (optional) +- `mode`: `inspect` or `repair-plan` (optional, default: `inspect`) + +## Workflow + +1. Inspect the app bundle, executable, signing info, and entitlements. +2. Determine whether the problem is identity, provisioning, hardened runtime, sandboxing, or trust policy. +3. Summarize the exact failure class in plain language. +4. Provide the minimal repair sequence or validation command. + +## Guardrails + +- Never invent entitlements; read them from the binary or source files. +- Distinguish local development signing problems from distribution or notarization failures. +- Prefer verifiable commands like `codesign -d`, `spctl`, and `plutil` over guesswork. diff --git a/plugins/build-macos-apps/commands/test-macos-app.md b/plugins/build-macos-apps/commands/test-macos-app.md new file mode 100644 index 00000000..595b34bf --- /dev/null +++ b/plugins/build-macos-apps/commands/test-macos-app.md @@ -0,0 +1,23 @@ +# /test-macos-app + +Run the smallest meaningful macOS test scope first and explain failures by category. + +## Arguments + +- `scheme`: Xcode scheme name (optional) +- `target`: test target or product name (optional) +- `filter`: test filter expression (optional) +- `configuration`: `Debug` or `Release` (optional, default: `Debug`) + +## Workflow + +1. Detect whether the repo uses `xcodebuild test` or `swift test`. +2. Prefer focused test execution when a target or filter is provided. +3. Classify failures as compile, assertion, crash, env/setup, or flake. +4. Summarize the top blocker and the narrowest sensible next step. + +## Guardrails + +- Avoid rerunning the full suite if a focused rerun is possible. +- Distinguish build failures from actual failing tests. +- Note when host app setup or simulator-only test assumptions leak into a macOS run. diff --git a/plugins/build-macos-apps/skills/appkit-interop/SKILL.md b/plugins/build-macos-apps/skills/appkit-interop/SKILL.md new file mode 100644 index 00000000..68e8cb4f --- /dev/null +++ b/plugins/build-macos-apps/skills/appkit-interop/SKILL.md @@ -0,0 +1,68 @@ +--- +name: appkit-interop +description: Decide when and how to bridge a macOS app from SwiftUI into AppKit. Use when implementing NSViewRepresentable or NSViewControllerRepresentable, accessing NSWindow or the responder chain, presenting panels, customizing menus, or handling desktop behaviors that SwiftUI does not model cleanly. +--- + +# AppKit Interop + +## Quick Start + +Use this skill when SwiftUI is close but not quite enough for native macOS behavior. +Keep the bridge as small and explicit as possible. SwiftUI should usually remain +the source of truth, while AppKit handles the imperative edge. + +## Choose The Smallest Bridge + +- Use pure SwiftUI when the required behavior already exists in scenes, toolbars, commands, inspectors, or standard controls. +- Use `NSViewRepresentable` when you need a specific AppKit view with lightweight lifecycle needs. +- Use `NSViewControllerRepresentable` when you need controller lifecycle, delegation, or presentation coordination. +- Use direct AppKit window or app hooks when you need `NSWindow`, responder-chain, menu validation, panels, or app-level behavior. + +## Workflow + +1. Name the capability gap precisely. + - Window behavior + - Text system behavior + - Menu validation + - Drag and drop + - File open/save panels + - First responder control + +2. Pick the smallest boundary that solves it. + - Avoid porting a whole screen to AppKit when one wrapped control or coordinator would do. + +3. Keep ownership explicit. + - SwiftUI owns value state, selection, and observable models. + - AppKit objects stay inside the representable, coordinator, or bridge object. + +4. Expose a narrow interface back to SwiftUI. + - Bindings for editable state + - Small callbacks for events + - Focused bridge services only when necessary + +5. Validate lifecycle assumptions. + - SwiftUI may recreate representables. + - Coordinators exist to hold delegate and target-action glue, not as a second app architecture. + +## References + +- `references/representables.md`: choosing between view and view-controller wrappers, plus coordinator patterns. +- `references/window-panels.md`: window access, utility windows, and open/save panels. +- `references/responder-menus.md`: first responder, command routing, and menu validation. +- `references/drag-drop-pasteboard.md`: pasteboard, file URLs, and desktop drag/drop edges. + +## Guardrails + +- Do not duplicate the source of truth between SwiftUI and AppKit. +- Do not let `Coordinator` become an unstructured dumping ground. +- Do not store long-lived `NSView` or `NSWindow` instances globally without a strong ownership reason. +- Prefer a tiny tested bridge over rewriting the feature in raw AppKit. +- If a pattern can remain entirely in `swiftui-patterns`, keep it there. + +## Output Expectations + +Provide: +- the exact SwiftUI limitation being crossed +- the smallest recommended bridge type +- the data-flow boundary between SwiftUI and AppKit +- the lifecycle or validation risks to watch diff --git a/plugins/build-macos-apps/skills/appkit-interop/agents/openai.yaml b/plugins/build-macos-apps/skills/appkit-interop/agents/openai.yaml new file mode 100644 index 00000000..8f5e2415 --- /dev/null +++ b/plugins/build-macos-apps/skills/appkit-interop/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "AppKit Interop" + short_description: "Bridge SwiftUI into AppKit for native macOS behavior" + default_prompt: "Use $appkit-interop to choose the smallest AppKit bridge, keep SwiftUI as the source of truth, and implement macOS-specific behavior that SwiftUI does not model cleanly." diff --git a/plugins/build-macos-apps/skills/appkit-interop/references/drag-drop-pasteboard.md b/plugins/build-macos-apps/skills/appkit-interop/references/drag-drop-pasteboard.md new file mode 100644 index 00000000..d1f5e73c --- /dev/null +++ b/plugins/build-macos-apps/skills/appkit-interop/references/drag-drop-pasteboard.md @@ -0,0 +1,23 @@ +# Drag, Drop, and Pasteboard + +## Intent + +Use this when desktop drag/drop or pasteboard behavior exceeds what plain SwiftUI modifiers cover comfortably. + +## Good fits + +- File URL dragging +- Pasteboard interoperability with other macOS apps +- Rich drag previews or AppKit-specific drop validation +- Legacy AppKit views with custom drag types + +## Core patterns + +- Start with SwiftUI drag/drop APIs when they already cover the use case. +- Drop to AppKit when you need `NSPasteboard`, custom pasteboard types, or older AppKit delegate flows. +- Keep data conversion at the boundary instead of leaking AppKit types through the whole feature. + +## Pitfalls + +- Do not move your whole list or canvas into AppKit just for one drop target. +- Keep file and pasteboard types explicit and validated. diff --git a/plugins/build-macos-apps/skills/appkit-interop/references/representables.md b/plugins/build-macos-apps/skills/appkit-interop/references/representables.md new file mode 100644 index 00000000..104eea35 --- /dev/null +++ b/plugins/build-macos-apps/skills/appkit-interop/references/representables.md @@ -0,0 +1,56 @@ +# Representables + +## Intent + +Use this when wrapping an AppKit control or controller for a SwiftUI macOS app. + +## Choose the wrapper type + +- Use `NSViewRepresentable` for a view-level bridge such as `NSTextView`, `NSScrollView`, or a custom AppKit control. +- Use `NSViewControllerRepresentable` when you need controller lifecycle, delegate coordination, or AppKit presentation logic. + +## Skeleton + +```swift +struct LegacyTextView: NSViewRepresentable { + @Binding var text: String + + func makeCoordinator() -> Coordinator { + Coordinator(text: $text) + } + + func makeNSView(context: Context) -> NSScrollView { + let scrollView = NSScrollView() + let textView = NSTextView() + textView.delegate = context.coordinator + scrollView.documentView = textView + return scrollView + } + + func updateNSView(_ nsView: NSScrollView, context: Context) { + guard let textView = nsView.documentView as? NSTextView else { return } + if textView.string != text { + textView.string = text + } + } + + final class Coordinator: NSObject, NSTextViewDelegate { + @Binding var text: String + + init(text: Binding) { + _text = text + } + + func textDidChange(_ notification: Notification) { + guard let textView = notification.object as? NSTextView else { return } + text = textView.string + } + } +} +``` + +## Pitfalls + +- Avoid infinite update loops by only pushing state into AppKit when values actually changed. +- Keep delegates and target-action wiring in the coordinator. +- If the wrapper grows into a full screen, re-evaluate the boundary. diff --git a/plugins/build-macos-apps/skills/appkit-interop/references/responder-menus.md b/plugins/build-macos-apps/skills/appkit-interop/references/responder-menus.md new file mode 100644 index 00000000..9c312d72 --- /dev/null +++ b/plugins/build-macos-apps/skills/appkit-interop/references/responder-menus.md @@ -0,0 +1,22 @@ +# Responder Chain and Menus + +## Intent + +Use this when command handling depends on the active window, first responder, or AppKit menu validation. + +## Core patterns + +- Start with SwiftUI `commands`, `FocusedValue`, and focused scene state. +- Use AppKit responder-chain hooks only when command routing or validation truly depends on the underlying responder system. +- Keep menu enablement rules close to the state they depend on. + +## Good fits for AppKit + +- Validating whether a menu item should be enabled +- Routing actions through the current first responder +- Integrating with existing AppKit document or text behaviors + +## Pitfalls + +- Do not recreate AppKit-style global command handling when SwiftUI focused values would work. +- Avoid scattering command logic between SwiftUI closures and AppKit selectors without a clear boundary. diff --git a/plugins/build-macos-apps/skills/appkit-interop/references/window-panels.md b/plugins/build-macos-apps/skills/appkit-interop/references/window-panels.md new file mode 100644 index 00000000..f89e0f39 --- /dev/null +++ b/plugins/build-macos-apps/skills/appkit-interop/references/window-panels.md @@ -0,0 +1,37 @@ +# Windows and Panels + +## Intent + +Use this when SwiftUI scenes are not enough for the required macOS window or panel behavior. + +## Common cases + +- Accessing the backing `NSWindow` +- Configuring titlebar or toolbar behavior +- Presenting `NSOpenPanel` or `NSSavePanel` +- Managing utility panels or floating windows + +## Core patterns + +- Prefer SwiftUI `Window`, `WindowGroup`, and `openWindow` first. +- Use AppKit only for window features SwiftUI does not expose cleanly. +- Keep file open/save panels behind a small service or helper instead of scattering panel setup throughout the view tree. + +## Example: open panel + +```swift +@MainActor +func chooseFile() -> URL? { + let panel = NSOpenPanel() + panel.canChooseFiles = true + panel.canChooseDirectories = false + panel.allowsMultipleSelection = false + return panel.runModal() == .OK ? panel.url : nil +} +``` + +## Pitfalls + +- Do not let random views own long-lived `NSWindow` references. +- Keep floating panels and utility windows consistent with the scene model. +- If the behavior is really just settings or a secondary scene, go back to `swiftui-patterns`. diff --git a/plugins/build-macos-apps/skills/build-run-debug/SKILL.md b/plugins/build-macos-apps/skills/build-run-debug/SKILL.md new file mode 100644 index 00000000..dc03ddd9 --- /dev/null +++ b/plugins/build-macos-apps/skills/build-run-debug/SKILL.md @@ -0,0 +1,129 @@ +--- +name: build-run-debug +description: Build, run, and debug local macOS apps and desktop executables using shell-first Xcode and Swift workflows. Use when asked to build a Mac app, launch it, diagnose compiler or linker failures, inspect startup problems, or debug desktop-only runtime issues. +--- + +# Build / Run / Debug + +## Quick Start + +Use this skill to set up one project-local `script/build_and_run.sh` entrypoint, +wire `.codex/environments/environment.toml` so the Codex app shows a Run button, +then use that script as the default build/run path. + +Prefer shell-first workflows: + +- `./script/build_and_run.sh` as the single kill + build + run entrypoint once it exists +- `xcodebuild` for Xcode workspaces or projects +- `swift build` plus raw executable launch inside that script for true SwiftPM command-line tools +- `swift build` plus project-local `.app` bundle staging and `/usr/bin/open -n` launch for SwiftPM AppKit/SwiftUI GUI apps +- optional script flags for `lldb`, `log stream`, telemetry verification, or post-launch process checks + +Do not assume simulators, touch interaction, or mobile-specific tooling. + +If an Xcode-aware MCP surface is already available and the user explicitly wants +it, use it only where it fits. Keep that usage narrow and honest: prefer it for +Xcode-oriented discovery, logging, or debugging support, and do not force +simulator-specific workflows onto pure macOS tasks. + +## Workflow + +1. Discover the project shape. + - Check whether the workspace is already inside a git repo with `git rev-parse --is-inside-work-tree`. + - If no git repo is present, run `git init` at the project/workspace root before building so Codex app git-backed features are available. Never run `git init` inside a nested subdirectory when the current workspace already belongs to a parent repo. + - Look for `.xcworkspace`, `.xcodeproj`, and `Package.swift`. + - If more than one candidate exists, explain the default choice and the ambiguity. + +2. Resolve the runnable target and process name. + - For Xcode, list schemes and prefer the app-producing scheme unless the user names another one. + - For SwiftPM, identify executable products when possible. + - Split SwiftPM launch handling by product type: + - use raw executable launch only for true command-line tools, + - use a generated project-local `.app` bundle for AppKit/SwiftUI GUI apps. + - Determine the app/process name to kill before relaunching. + +3. Create or update `script/build_and_run.sh`. + - Make the script project-specific and executable. + - It should always: + 1. stop the existing running app/process if present, + 2. build the macOS target, + 3. launch the freshly built app or executable. + - Add optional flags for debugging/log inspection: + - `--debug` to launch under `lldb` or attach the debugger + - `--logs` to stream process logs after launch + - `--telemetry` to stream unified logs filtered to the app subsystem/category + - `--verify` to launch the app and confirm the process exists with `pgrep -x ` + - Keep the default no-flag path simple: kill, build, run. + - Prefer writing one script that owns this workflow instead of repeatedly asking the agent to manually run `swift build`, locate the artifact, then invoke an ad hoc run command. + - For SwiftPM GUI apps, make the script build the product, create `dist/.app`, copy the binary to `Contents/MacOS/`, generate a minimal `Contents/Info.plist` with `CFBundlePackageType=APPL`, `CFBundleExecutable`, `CFBundleIdentifier`, `CFBundleName`, `LSMinimumSystemVersion`, and `NSPrincipalClass=NSApplication`, then launch with `/usr/bin/open -n `. + - For SwiftPM GUI `--logs` and `--telemetry`, launch the bundle with `/usr/bin/open -n` first, then stream unified logs with `/usr/bin/log stream --info ...`. + - Do not recommend direct SwiftPM executable launch for AppKit/SwiftUI GUI apps. + - Use `references/run-button-bootstrap.md` as the canonical source for the + script shape and exact environment file format. Do not fork a second + authoritative snippet in another skill or command. + - Keep the run script outside app source. It belongs in `script/build_and_run.sh`, not in `App/`, `Views/`, `Models/`, `Stores/`, `Services/`, or `Support/`. + +4. Write `.codex/environments/environment.toml` at the project root once the script exists. + - Use this exact placement: `.codex/environments/environment.toml`. + - Use the exact action shape in `references/run-button-bootstrap.md`. + - This file is what gives the user a Codex app Run button wired to the script. + - If the project already has this file, update the `Run` action command to point at `./script/build_and_run.sh` instead of creating a duplicate action. + - Keep this Codex environment config separate from Swift app source files. + +5. Build and run through the script. + - Default to `./script/build_and_run.sh`. + - Use `./script/build_and_run.sh --debug`, `--logs`, `--telemetry`, or `--verify` when the user asks for debugger/log/telemetry/process verification support. + +6. Summarize failures correctly. + - Classify the blocker as compiler, linker, signing, build settings, missing SDK/toolchain, script bug, or runtime launch. + - Quote the smallest useful error snippet and explain what it means. + +7. Debug the right way. + - Use the script's `--logs` or `--telemetry` mode for config, entitlement, sandbox, and action-event verification. + - For SwiftPM GUI apps, if the app bundle launches but its window still does not come forward, check whether the entrypoint needs `NSApp.setActivationPolicy(.regular)` and `NSApp.activate(ignoringOtherApps: true)`. + - Use the script's `--debug` mode or direct `lldb` if symbolized crash debugging is needed. + - If the user needs to instrument and verify specific window, sidebar, menu, or menu bar actions, switch to `telemetry`. + - Keep evidence tight and user-facing. + +8. Use Xcode-aware MCP tooling only when it helps. + - If the user explicitly asks for XcodeBuildMCP and it is already available, prefer it over ad hoc setup. + - Use the MCP for Xcode-aware discovery or debug/logging workflows when the available tool surface clearly matches the task. + - Fall back to shell commands immediately when the MCP does not provide a clean macOS path. + +## Preferred Commands + +- Project discovery: + - `find . -name '*.xcworkspace' -o -name '*.xcodeproj' -o -name 'Package.swift'` +- Scheme discovery: + - `xcodebuild -list -workspace ` + - `xcodebuild -list -project ` +- Build/run: + - `./script/build_and_run.sh` + - `./script/build_and_run.sh --debug` + - `./script/build_and_run.sh --logs` + - `./script/build_and_run.sh --telemetry` + - `./script/build_and_run.sh --verify` + +## References + +- `references/run-button-bootstrap.md`: canonical `build_and_run.sh` and `.codex/environments/environment.toml` contract. + +## Guardrails + +- Prefer the narrowest command that proves or disproves the current theory. +- Do not leave the user with a one-off manual command chain once a stable `build_and_run.sh` script can own the workflow. +- Do not write `.codex/environments/environment.toml` before the run script exists, and do not point the Run action at a stale script path. +- Do not launch a SwiftUI/AppKit SwiftPM GUI app as a raw executable unless the user explicitly wants to diagnose that failure mode: it can produce no Dock icon, no foreground activation, and missing bundle identifier warnings. Keep raw executable launch only for true command-line tools. +- Do not claim UI state you cannot inspect directly. +- Do not describe mobile or simulator workflows as if they apply to macOS. +- If build output is huge, summarize the first real blocker and point to follow-up commands. + +## Output Expectations + +Provide: +- the detected project type +- the script path and Codex environment action you configured, if applicable +- the command you ran +- whether build and launch succeeded +- the top blocker if they failed +- the smallest sensible next action diff --git a/plugins/build-macos-apps/skills/build-run-debug/agents/openai.yaml b/plugins/build-macos-apps/skills/build-run-debug/agents/openai.yaml new file mode 100644 index 00000000..6ae822b4 --- /dev/null +++ b/plugins/build-macos-apps/skills/build-run-debug/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Build / Run / Debug" + short_description: "Build and debug macOS apps with shell-first workflows" + default_prompt: "Use $build-run-debug to discover the local project, build it for macOS, launch the app or executable, and summarize the real blocker when something fails." diff --git a/plugins/build-macos-apps/skills/build-run-debug/references/run-button-bootstrap.md b/plugins/build-macos-apps/skills/build-run-debug/references/run-button-bootstrap.md new file mode 100644 index 00000000..450fce69 --- /dev/null +++ b/plugins/build-macos-apps/skills/build-run-debug/references/run-button-bootstrap.md @@ -0,0 +1,183 @@ +# Run Button Bootstrap + +This is the canonical bootstrap contract for the macOS Build plugin's local run +loop. + +When a project does not already have an established macOS run entrypoint: + +1. Create one project-local `script/build_and_run.sh`. +2. Make it executable. +3. Use it as the single kill + build + run entrypoint. +4. Support optional `--debug`, `--logs`, `--telemetry`, and `--verify` flags. +5. Write `.codex/environments/environment.toml` so the Codex app exposes a + `Run` action wired to that script. + +## `script/build_and_run.sh` + +Use one project-specific script with a tiny mode switch and a default no-flag +path that just kills, builds, and launches. Keep raw executable launch only for +true command-line tools. For SwiftPM AppKit/SwiftUI GUI apps, stage a +project-local `.app` bundle and launch that bundle with `/usr/bin/open -n`. + +### SwiftPM CLI executable + +Use this shape for true command-line tools: + +```bash +#!/usr/bin/env bash +set -euo pipefail + +MODE="${1:-run}" +APP_NAME="MyTool" + +pkill -x "$APP_NAME" >/dev/null 2>&1 || true + +swift build +APP_BINARY="$(swift build --show-bin-path)/$APP_NAME" + +case "$MODE" in + run) + "$APP_BINARY" + ;; + --debug|debug) + lldb -- "$APP_BINARY" + ;; + --logs|logs) + "$APP_BINARY" & + /usr/bin/log stream --info --style compact --predicate "process == \"$APP_NAME\"" + ;; + --telemetry|telemetry) + "$APP_BINARY" & + /usr/bin/log stream --info --style compact --predicate "subsystem == \"com.example.MyTool\"" + ;; + --verify|verify) + "$APP_BINARY" & + sleep 1 + pgrep -x "$APP_NAME" >/dev/null + ;; + *) + echo "usage: $0 [run|--debug|--logs|--telemetry|--verify]" >&2 + exit 2 + ;; +esac +``` + +### SwiftPM AppKit/SwiftUI GUI app + +Use this shape for SwiftPM GUI apps so they launch as a real foreground app +bundle with Dock activation and bundle metadata: + +```bash +#!/usr/bin/env bash +set -euo pipefail + +MODE="${1:-run}" +APP_NAME="MyApp" +BUNDLE_ID="com.example.MyApp" +MIN_SYSTEM_VERSION="14.0" + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +DIST_DIR="$ROOT_DIR/dist" +APP_BUNDLE="$DIST_DIR/$APP_NAME.app" +APP_CONTENTS="$APP_BUNDLE/Contents" +APP_MACOS="$APP_CONTENTS/MacOS" +APP_BINARY="$APP_MACOS/$APP_NAME" +INFO_PLIST="$APP_CONTENTS/Info.plist" + +pkill -x "$APP_NAME" >/dev/null 2>&1 || true + +swift build +BUILD_BINARY="$(swift build --show-bin-path)/$APP_NAME" + +rm -rf "$APP_BUNDLE" +mkdir -p "$APP_MACOS" +cp "$BUILD_BINARY" "$APP_BINARY" +chmod +x "$APP_BINARY" + +cat >"$INFO_PLIST" < + + + + CFBundleExecutable + $APP_NAME + CFBundleIdentifier + $BUNDLE_ID + CFBundleName + $APP_NAME + CFBundlePackageType + APPL + LSMinimumSystemVersion + $MIN_SYSTEM_VERSION + NSPrincipalClass + NSApplication + + +PLIST + +open_app() { + /usr/bin/open -n "$APP_BUNDLE" +} + +case "$MODE" in + run) + open_app + ;; + --debug|debug) + lldb -- "$APP_BINARY" + ;; + --logs|logs) + open_app + /usr/bin/log stream --info --style compact --predicate "process == \"$APP_NAME\"" + ;; + --telemetry|telemetry) + open_app + /usr/bin/log stream --info --style compact --predicate "subsystem == \"$BUNDLE_ID\"" + ;; + --verify|verify) + open_app + sleep 1 + pgrep -x "$APP_NAME" >/dev/null + ;; + *) + echo "usage: $0 [run|--debug|--logs|--telemetry|--verify]" >&2 + exit 2 + ;; +esac +``` + +Launching a SwiftPM GUI binary directly can produce no Dock icon, no foreground +activation, and missing bundle identifier warnings. If the `.app` bundle opens +but the main window still does not come forward, the app entrypoint may need +`NSApp.setActivationPolicy(.regular)` and +`NSApp.activate(ignoringOtherApps: true)`. + +Adapt the build step for Xcode projects by replacing `swift build` with +`xcodebuild -project ...` or `xcodebuild -workspace ...`, then launch the built +`.app` binary from DerivedData or a deterministic project-local build path. Keep +the one-script interface and mode flags the same. + +## `.codex/environments/environment.toml` + +Write the environment file at this exact path: + +`.codex/environments/environment.toml` + +with this action shape: + +```toml +# THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY +version = 1 +name = "" + +[setup] +script = "" + +[[actions]] +name = "Run" +icon = "run" +command = "./script/build_and_run.sh" +``` + +If the project already has an environment file, update the existing `Run` +action to point at `./script/build_and_run.sh` instead of adding a duplicate. diff --git a/plugins/build-macos-apps/skills/liquid-glass/SKILL.md b/plugins/build-macos-apps/skills/liquid-glass/SKILL.md new file mode 100644 index 00000000..8f33d888 --- /dev/null +++ b/plugins/build-macos-apps/skills/liquid-glass/SKILL.md @@ -0,0 +1,170 @@ +--- +name: liquid-glass +description: Implement, refactor, or review modern macOS SwiftUI UI for the new design system and Liquid Glass. Use when adopting Liquid Glass, updating NavigationSplitView, toolbars, search, sheets, and controls, removing custom backgrounds that fight system materials, or building custom glass surfaces with glassEffect, GlassEffectContainer, and glassEffectID. +--- + +# Liquid Glass + +## Overview + +Use this skill to bring a macOS SwiftUI app into the modern macOS design system +with the least custom chrome possible. Start with standard app structure, +toolbars, search placement, sheets, and controls, then add custom Liquid Glass +only where the app needs a distinctive surface. + +Prefer system-provided glass and adaptive materials over bespoke blur, opaque +backgrounds, or custom toolbar/sidebar skins. Audit existing UI for extra fills, +scrims, and clipping before adding more effects. + +## Workflow + +1. Read the relevant scene or root view and identify the structural pattern: + `NavigationSplitView`, `TabView`, sheet presentation, detail/inspector + layout, toolbar, or custom floating controls. +2. Remove custom backgrounds or darkening layers behind system sheets, + sidebars, and toolbars unless the product explicitly needs them. These can + obscure Liquid Glass and interfere with the automatic scroll-edge effect. +3. Update standard SwiftUI structure and controls first. +4. Add custom `glassEffect` surfaces only for app-specific UI that standard + controls do not cover. +5. Validate that glass grouping, transitions, icon treatment, and foreground + activation are visually coherent and still usable with pointer and keyboard. +6. If the UI change also affects launch behavior for a SwiftPM GUI app, use + `build-run-debug` so the app runs as a foreground `.app` bundle rather + than as a raw executable. + +## App Structure + +- Prefer `NavigationSplitView` for hierarchy-driven macOS layouts. Let the + sidebar use the system Liquid Glass material instead of painting over it. +- For hero artwork or large media adjacent to a floating sidebar, use + `backgroundExtensionEffect` so the visual can extend beyond the safe area + without clipping the subject. +- Keep inspectors visually associated with the current selection and avoid + giving them a heavier custom background than the content they inspect. +- If the app uses tabs, keep `TabView` for persistent top-level sections and + preserve each tab's local navigation state. +- Do not force iPhone-only tab bar minimize/accessory behavior onto a Mac app. + On macOS, prefer a conventional top toolbar and native tab/search placement. +- If a sheet already uses `presentationBackground` purely to imitate frosted + material, consider removing it and letting the system's new material render. +- For sheet transitions that should visually originate from a toolbar button, + make the presenting item the source of a navigation zoom transition and mark + the sheet content as the destination. + +## Toolbars + +- Assume toolbar items are rendered on a floating Liquid Glass surface and are + grouped automatically. +- Use `ToolbarSpacer` to communicate grouping: + - fixed spacing to split related actions into a distinct group, + - flexible spacing to push a leading action away from a trailing group. +- Use `sharedBackgroundVisibility` when an item should stand alone without the + shared glass background, for example a profile/avatar item. +- Add `badge` to toolbar item content for notification or status indicators. +- Expect monochrome icon rendering in more toolbar contexts. Use `tint` only to + convey semantic meaning such as a primary action or alert state, not as pure + decoration. +- If content underneath a toolbar has extra darkening, blur, or custom + background layers, remove them before judging the new automatic scroll-edge + effect. +- For dense windows with many floating elements, tune the content's scroll-edge + treatment with `scrollEdgeEffectStyle` instead of building a custom bar + background. + +## Search + +- For a search field that applies across a whole split-view hierarchy, attach + `searchable` to the `NavigationSplitView`, not to just one column. +- When search is secondary and a compact affordance is better, use + `searchToolbarBehavior` instead of hand-rolling a toolbar button and a + separate field. +- For a dedicated search page in a multi-tab app, assign the search role to one + tab and place `searchable` on the `TabView`. +- Make most of the app's content discoverable from search when the field lives + in the top-trailing toolbar location. +- On iPad and Mac, expect the dedicated search tab to show a centered field + above browsing suggestions rather than a bottom search bar. + +## Controls + +- Prefer standard SwiftUI controls before creating custom glass components. +- Expect bordered buttons to default to a capsule shape at larger sizes. On + macOS, mini/small/medium controls preserve a rounded-rectangle shape for + denser layouts. +- Use `buttonBorderShape` when a button shape needs to be explicit. +- Use `controlSize` to preserve density in inspectors and popovers, and reserve + extra-large sizing for truly prominent actions. +- Use the system glass and glass-prominent button styles for primary actions + instead of recreating a translucent button background by hand. +- For sliders with discrete values, pass `step` to get automatic tick marks or + provide specific ticks in a `ticks` closure. +- For sliders that should expand left and right around a baseline, set + `neutralValue`. +- Use `Label` or standard control initializers for menu items so icons are + consistently placed on the leading edge across platforms. +- For custom shapes that must align concentrically with a sheet, card, or + window corner, use a concentric rectangle shape with the + `containerConcentric` corner configuration instead of guessing a radius. + +## Custom Liquid Glass + +- Use `glassEffect` for custom glass surfaces. The default shape is capsule-like + and text foregrounds are automatically made vibrant and legible against + changing content underneath. +- Pass an explicit shape to `glassEffect` when a capsule is not the right fit. +- Add `tint` only when color carries meaning, such as a status or call to + action. +- Use `glassEffect(... .interactive())` for custom controls or containers with + interactive elements so they scale, bounce, and shimmer like system glass. +- Wrap nearby custom glass elements in one `GlassEffectContainer`. This is a + visual correctness rule, not just organization: separate containers cannot + sample each other's glass and can produce inconsistent refraction. +- Use `glassEffectID` with a local `@Namespace` when matching glass elements + should morph between collapsed and expanded states. + +## Review Checklist + +- Standard structures and controls were updated first before adding custom + glass. +- Opaque backgrounds, dark scrims, and custom toolbar/sheet fills that fight the + system material were removed unless intentionally required. +- `searchable` is attached at the correct container level for the intended + search scope. +- Toolbar grouping uses `ToolbarSpacer`, `sharedBackgroundVisibility`, and + `badge` instead of one-off hand-built chrome. +- Icon tint is semantic, not decorative. +- Custom glass elements that sit near each other share a + `GlassEffectContainer`. +- Morphing glass transitions use `glassEffectID` with a namespace and stable + identity. +- Any SwiftPM GUI app used to test the result is launched as a `.app` bundle, + not as a raw executable. + +## Guardrails + +- Do not rebuild system sidebars, toolbars, sheets, or controls from scratch if + standard SwiftUI APIs already provide the modern macOS behavior. +- Do not apply custom opaque backgrounds behind a `NavigationSplitView` + sidebar, system toolbar, or sheet just because an older version needed + one. +- Do not scatter related glass elements across multiple + `GlassEffectContainer`s. +- Do not tint every icon or glass surface for visual variety alone. +- Do not assume an iPhone tab/search behavior is the right answer on macOS. + Prefer desktop-native toolbar, split-view, and inspector placement. +- Do not leave a GUI SwiftPM app launching as a bare executable when reviewing + Liquid Glass behavior; missing foreground activation can make a design bug + look like a rendering bug. + +## When To Use Other Skills + +- Use `swiftui-patterns` when the main question is scene architecture, + sidebar/detail layout, commands, or settings rather than Liquid Glass-specific + treatment. +- Use `view-refactor` when the main issue is file structure, state + ownership, and extracting large views before design changes. +- Use `appkit-interop` when the design requires window, panel, responder-chain, + or AppKit-only control behavior. +- Use `build-run-debug` when you need to launch, verify, or inspect logs + for the app after the visual update. diff --git a/plugins/build-macos-apps/skills/liquid-glass/agents/openai.yaml b/plugins/build-macos-apps/skills/liquid-glass/agents/openai.yaml new file mode 100644 index 00000000..299a312e --- /dev/null +++ b/plugins/build-macos-apps/skills/liquid-glass/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Liquid Glass" + short_description: "Adopt modern macOS SwiftUI design and Liquid Glass" + default_prompt: "Use $liquid-glass to modernize this macOS SwiftUI UI, remove custom chrome that fights system glass, and adopt Liquid Glass in app structure, toolbars, search, controls, and custom surfaces." diff --git a/plugins/build-macos-apps/skills/packaging-notarization/SKILL.md b/plugins/build-macos-apps/skills/packaging-notarization/SKILL.md new file mode 100644 index 00000000..47fc1431 --- /dev/null +++ b/plugins/build-macos-apps/skills/packaging-notarization/SKILL.md @@ -0,0 +1,47 @@ +--- +name: packaging-notarization +description: Prepare and troubleshoot packaging, signing, and notarization workflows for macOS distribution. Use when asked to archive a Mac app, validate bundle structure, reason about notarization readiness, or explain distribution-only failures. +--- + +# Packaging & Notarization + +## Quick Start + +Use this skill when the work is about shipping the app rather than merely +running it locally: archives, exported app bundles, notarization readiness, +hardened runtime, or distribution validation. + +## Workflow + +1. Confirm the distribution goal. + - Local archive validation + - Signed distributable app + - Notarization troubleshooting + +2. Inspect the artifact. + - Validate app bundle structure. + - Check nested frameworks, helper tools, and entitlements. + +3. Inspect signing and runtime prerequisites. + - Hardened runtime + - Signing identity + - Nested code signatures + - Required entitlements + +4. Explain notarization readiness or failure. + - Separate packaging issues from trust-policy symptoms. + - Point to the minimum follow-up validation commands. + +## Guardrails + +- Do not present notarization as required for ordinary local debug runs. +- Call out when you lack the actual exported artifact and are inferring from project settings. +- Keep advice concrete and verifiable. + +## Output Expectations + +Provide: +- what artifact or settings were inspected +- whether the app looks distribution-ready +- the top missing prerequisite or failure mode +- the next validation or repair step diff --git a/plugins/build-macos-apps/skills/packaging-notarization/agents/openai.yaml b/plugins/build-macos-apps/skills/packaging-notarization/agents/openai.yaml new file mode 100644 index 00000000..25fdb502 --- /dev/null +++ b/plugins/build-macos-apps/skills/packaging-notarization/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Packaging & Notarization" + short_description: "Inspect packaging, signing, and notarization readiness" + default_prompt: "Use $packaging-notarization to inspect a macOS app archive or bundle, explain distribution readiness, and summarize the next validation or repair step." diff --git a/plugins/build-macos-apps/skills/signing-entitlements/SKILL.md b/plugins/build-macos-apps/skills/signing-entitlements/SKILL.md new file mode 100644 index 00000000..1997e5f2 --- /dev/null +++ b/plugins/build-macos-apps/skills/signing-entitlements/SKILL.md @@ -0,0 +1,58 @@ +--- +name: signing-entitlements +description: Inspect signing, entitlements, hardened runtime, and Gatekeeper issues for macOS apps. Use when asked to diagnose code signing failures, missing entitlements, sandbox problems, notarization prerequisites, or trust-policy launch errors. +--- + +# Signing & Entitlements + +## Quick Start + +Use this skill when the failure smells like codesigning rather than compilation: +launch refusal, missing entitlement, invalid signature, sandbox mismatch, +hardened runtime confusion, or trust-policy rejection. + +## Workflow + +1. Inspect the bundle or binary. + - Locate the `.app` or executable. + - Identify the main binary inside `Contents/MacOS/`. + +2. Read signing details. + - Use `codesign -dvvv --entitlements :- `. + - Use `spctl -a -vv ` when Gatekeeper behavior matters. + - Use `plutil -p` for entitlements or Info.plist inspection. + +3. Classify the failure. + - Unsigned or ad hoc signed + - Wrong identity + - Entitlement mismatch + - Hardened runtime issue + - App Sandbox issue + - Nested code signing issue + - Distribution/notarization prerequisite issue + +4. Explain the minimum fix path. + - Say exactly what is wrong. + - Show the shortest set of validation or repair commands. + - Distinguish local development problems from distribution problems. + +## Useful Commands + +- `codesign -dvvv --entitlements :- ` +- `spctl -a -vv ` +- `security find-identity -p codesigning -v` +- `plutil -p ` + +## Guardrails + +- Never invent missing entitlements. +- Do not conflate notarization with local debug signing. +- If the real issue is a build setting or provisioning profile, say so directly. + +## Output Expectations + +Provide: +- what artifact was inspected +- what signing state it is in +- the exact failure class +- the minimum fix or validation sequence diff --git a/plugins/build-macos-apps/skills/signing-entitlements/agents/openai.yaml b/plugins/build-macos-apps/skills/signing-entitlements/agents/openai.yaml new file mode 100644 index 00000000..1c8155e4 --- /dev/null +++ b/plugins/build-macos-apps/skills/signing-entitlements/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Signing & Entitlements" + short_description: "Inspect codesign, entitlements, and Gatekeeper failures" + default_prompt: "Use $signing-entitlements to inspect a macOS app or binary, classify the signing or entitlement problem, and explain the minimum fix path." diff --git a/plugins/build-macos-apps/skills/swiftpm-macos/SKILL.md b/plugins/build-macos-apps/skills/swiftpm-macos/SKILL.md new file mode 100644 index 00000000..32e3e632 --- /dev/null +++ b/plugins/build-macos-apps/skills/swiftpm-macos/SKILL.md @@ -0,0 +1,50 @@ +--- +name: swiftpm-macos +description: Build, run, and test pure SwiftPM-based macOS packages and executables. Use when the repo is package-first, when there is no Xcode project, or when Swift package workflows are the fastest path to diagnosis. +--- + +# SwiftPM for macOS + +## Quick Start + +Use this skill when `Package.swift` is the primary entrypoint or when SwiftPM is +the fastest path to a reproducible result. + +## Workflow + +1. Inspect the package. + - Read `Package.swift`. + - Identify executable, library, and test products. + +2. Build with SwiftPM. + - Use `swift build` by default. + - Use release mode only when the user explicitly needs it. + +3. Run the right product. + - Use `swift run ` when an executable exists. + - If multiple executables exist, explain the default choice. + +4. Test narrowly. + - Use `swift test`. + - Apply filters when a specific test target or case is known. + +5. Summarize failures. + - Module/import resolution + - Package graph or dependency issue + - Linker failure + - Runtime failure + - Test regression + +## Guardrails + +- Prefer SwiftPM over Xcode when both exist and the package path is clearly simpler. +- Do not assume an app bundle exists in a pure package workflow. +- Explain when the package is library-only and therefore not directly runnable. + +## Output Expectations + +Provide: +- the package products you found +- the command you ran +- whether build, run, or test succeeded +- the top blocker if not diff --git a/plugins/build-macos-apps/skills/swiftpm-macos/agents/openai.yaml b/plugins/build-macos-apps/skills/swiftpm-macos/agents/openai.yaml new file mode 100644 index 00000000..26ee99a5 --- /dev/null +++ b/plugins/build-macos-apps/skills/swiftpm-macos/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "SwiftPM macOS" + short_description: "Build, run, and test macOS Swift packages" + default_prompt: "Use $swiftpm-macos to inspect the package graph, build the package, run executable products, and explain SwiftPM-specific failures clearly." diff --git a/plugins/build-macos-apps/skills/swiftui-patterns/SKILL.md b/plugins/build-macos-apps/skills/swiftui-patterns/SKILL.md new file mode 100644 index 00000000..7b535530 --- /dev/null +++ b/plugins/build-macos-apps/skills/swiftui-patterns/SKILL.md @@ -0,0 +1,209 @@ +--- +name: swiftui-patterns +description: Best practices and example-driven guidance for building native macOS SwiftUI scenes and components, including windows, commands, toolbars, settings, split views, inspectors, menu bar extras, and keyboard-driven workflows. Use when creating or refactoring macOS SwiftUI UI, choosing scene types, wiring menus or settings, or needing desktop-specific component patterns and examples. +--- + +# SwiftUI Patterns + +## Quick Start + +Choose a track based on your goal: + +### Existing project + +- Identify the feature or scene and the primary interaction model: document, editor, sidebar-detail, utility window, settings, or menu bar extra. +- Read the nearest existing scene or root view before inventing a new desktop structure. +- Choose the relevant reference from `references/components-index.md`. +- If SwiftUI cannot express the required platform behavior cleanly, use the `appkit-interop` skill rather than forcing a shaky workaround. + +### New app scaffolding + +- Choose the scene model first: `WindowGroup`, `Window`, `Settings`, `MenuBarExtra`, or `DocumentGroup`. +- If the app combines a normal main window and a `MenuBarExtra`, use `WindowGroup(..., id:)` for the primary window when it should appear at launch. Treat `Window(...)` as a better fit for auxiliary/on-demand singleton windows; in menu-bar-heavy apps, a `Window(...)` scene may not present the main window automatically at launch. +- Before creating the scaffold, check whether the workspace is already inside a git repo with `git rev-parse --is-inside-work-tree`. If not, run `git init` at the project root so Codex app git-backed features are available from the start. Do not initialize a nested repo inside an existing parent checkout. +- For a new app scaffold, also create one project-local `script/build_and_run.sh` and `.codex/environments/environment.toml` so the Codex app Run button works immediately. Use the exact bootstrap contract from `build-run-debug` and its `references/run-button-bootstrap.md` file rather than inventing a second variant here. +- Decide which state is app-wide, scene-scoped, or window-scoped before writing views. +- Sketch file and module boundaries before writing the full UI. For any non-trivial app, create the folder structure first and split files by responsibility from the start. +- Use a single Swift file only for tiny throwaway examples or snippets: roughly under 50 lines, one screen, no persistence, no networking/process client, and no reusable models. Anything beyond that should be multi-file immediately. +- Use system-adaptive colors and materials by default (`Color.primary`, `Color.secondary`, semantic foreground styles, `.regularMaterial`, etc.) so the app follows Light/Dark mode automatically. Do not hardcode white or light backgrounds unless the user explicitly asks for a fixed theme, and do not reach for opaque `windowBackgroundColor` fills for root panes by default. +- Pick the references for the first feature surface you need: windowing, commands, split layouts, or settings. + +## New App File Structure + +For any non-trivial macOS app, start with this shape instead of putting the app, +all views, models, stores, services, and helpers in one Swift file: + +- `App/App.swift`: the `@main` app type and `AppDelegate` only. +- `Views/ContentView.swift`: root layout and high-level composition only. +- `Views/SidebarView.swift`, `Views/DetailView.swift`, `Views/ComposerView.swift`, etc.: feature views named after their primary type. +- `Models/*.swift`: value models, identifiers, and selection enums. +- `Stores/*.swift`: persistence and state stores. +- `Services/*.swift`: app-server, network, process, or platform clients. +- `Support/*.swift`: small formatters, resolvers, extensions, and glue helpers. + +Keep files small and named after the primary type they contain. If a file starts +collecting unrelated views, models, stores, networking clients, and helper +extensions, split it before adding more behavior. + +## Pre-Edit Checklist For New App Scaffolds + +Before writing the full UI: + +1. Choose the scene model. +2. Choose state ownership: app-wide, scene-scoped, window-scoped, or view-local. +3. Sketch file and module boundaries. +4. Create the folder structure before filling in the UI. +5. Keep `script/build_and_run.sh` and `.codex/environments/environment.toml` separate from app source. + +## General Rules To Follow + +- Design for pointer, keyboard, menus, and multiple windows. +- Keep scenes explicit. A separate settings window, utility window, or menu bar extra should be modeled as its own scene, not hidden inside one monolithic `ContentView`. +- Prefer system desktop affordances: `commands`, toolbars, sidebars, inspectors, contextual menus, and `searchable`. +- For menu bar apps, keep `MenuBarExtra` item titles and action labels short and scannable. Cap visible menu item text at 30 characters; if source content is longer, truncate or summarize it before rendering and open the full content in a dedicated window or detail surface. +- If a `MenuBarExtra` app should still behave like a regular Dock app with a visible main window/process, install an `NSApplicationDelegate` via `@NSApplicationDelegateAdaptor`, call `NSApp.setActivationPolicy(.regular)` during launch, and activate the app with `NSApp.activate(ignoringOtherApps: true)`. If the app is intentionally menu-bar-only, document that `.accessory` / no-Dock behavior is a deliberate product choice. +- Prefer system-adaptive colors, materials, and semantic foreground styles. Avoid fixed white/light backgrounds in scaffolding and examples unless the requested design explicitly calls for a custom non-adaptive theme. +- Do not paint `NavigationSplitView` sidebars or root window panes with opaque custom `Color(...)` or `Color(nsColor: .windowBackgroundColor)` fills by default. Prefer native macOS sidebar/window materials and system-provided backgrounds unless the user explicitly asks for a custom opaque surface. In sidebar-detail-inspector layouts, let the sidebar keep the standard source-list/material appearance and reserve custom backgrounds for detail or inspector content cards where needed. +- Use `@SceneStorage` for per-window ephemeral state and `@AppStorage` for durable user preferences. +- Keep selection state explicit and stable. macOS layouts often pivot around sidebar selection rather than push navigation. +- Prefer `NavigationSplitView` or a deliberate manual split layout over iOS-style stacked flows when the app benefits from always-visible structure. +- For `List(...).listStyle(.sidebar)` and `NavigationSplitView` sidebars, prefer flat native rows with standard system selection/highlight behavior. Keep rows visually lightweight and Mail-like: at most one leading icon, one strong title line, and one optional secondary detail line in `.secondary`. Avoid stacked metadata rows, repeated inline utility icons, or dense multi-column status text in the sidebar. Reserve card-style and metadata-heavy surfaces for detail or inspector panes unless the user explicitly asks for a highly custom sidebar treatment. +- Keep primary actions discoverable from both UI chrome and keyboard shortcuts when appropriate. +- Use SwiftUI-native scenes and views first. If you need low-level window, responder-chain, text system, or panel control, switch to `appkit-interop`. + +## Recommended Sidebar Row Pattern + +Prefer a native source-list row shape: + +```swift +List(selection: $selection) { + ForEach(items) { item in + HStack(spacing: 10) { + Image(systemName: item.systemImage) + .foregroundStyle(.secondary) + .frame(width: 16) + + VStack(alignment: .leading, spacing: 2) { + Text(item.title) + .lineLimit(1) + + if let detail = item.detail { + Text(detail) + .font(.caption) + .foregroundStyle(.secondary) + .lineLimit(1) + } + } + } + .tag(item.id) + } +} +.listStyle(.sidebar) +``` + +This keeps selection, highlight, spacing, and scanability aligned with standard +macOS sidebars. Keep each row to one icon maximum and one or two text lines +maximum, with the second line reserved for a short detail label. Use richer card +treatments and denser metadata in the detail or inspector content, not in every +sidebar row. + +## Recommended Split-View Background Pattern + +Prefer letting the sidebar and split container use system backgrounds, while +applying custom surfaces only to detail cards or inspector sections: + +```swift +NavigationSplitView { + List(selection: $selection) { + ForEach(items) { item in + Label(item.title, systemImage: item.systemImage) + .tag(item.id) + } + } + .listStyle(.sidebar) +} detail: { + ScrollView { + VStack(alignment: .leading, spacing: 16) { + DetailSummaryCard(item: selectedItem) + DetailMetricsCard(item: selectedItem) + } + .padding() + } +} +``` + +Avoid painting the sidebar and root split panes with opaque custom fills by +default: + +```swift +NavigationSplitView { + List(items) { item in + SidebarCardRow(item: item) + } + .listStyle(.sidebar) + .background(Color(nsColor: .windowBackgroundColor)) +} detail: { + DetailView(item: selectedItem) + .background(Color(.white)) +} +``` + +## State Ownership Summary + +Use the narrowest state tool that matches the ownership model: + +| Scenario | Preferred pattern | +| --- | --- | +| Local view or control state | `@State` | +| Child mutates parent-owned value state | `@Binding` | +| Root-owned reference model on macOS 14+ | `@State` with an `@Observable` type | +| Child reads or mutates an injected `@Observable` model | Pass it explicitly as a stored property | +| Window-scoped ephemeral selection or expansion state | `@SceneStorage` when practical, otherwise scene-owned `@State` | +| Shared user preference | `@AppStorage` | +| Shared app service or configuration | `@Environment(Type.self)` | +| Legacy reference model on older targets | `@StateObject` at the owner and `@ObservedObject` when injected | + +Choose the ownership location first, then the wrapper. Do not turn simple desktop state into a view model by reflex. + +## Cross-Cutting References + +- `references/components-index.md`: entry point for scene and component guidance. +- `references/windowing.md`: choosing between `WindowGroup`, `Window`, `DocumentGroup`, and window-opening patterns. +- `references/settings.md`: dedicated settings scenes, `SettingsLink`, and preference layouts. +- `references/commands-menus.md`: command menus, keyboard shortcuts, focused values, and desktop action routing. +- `references/split-inspectors.md`: sidebars, split views, selection-driven layout, and inspectors. +- `references/menu-bar-extra.md`: menu bar extra structure and when it fits. + +## Anti-Patterns + +- One huge `ContentView` pretending the whole app is a single screen. +- A single Swift file containing the `@main` app, all views, models, stores, networking/process clients, formatters, and extensions. This is acceptable only for tiny throwaway snippets under the new-app threshold above. +- Touch-first interaction models ported directly from iOS without desktop affordances. +- Hiding core actions behind gestures with no menu, toolbar, or keyboard path. +- Building a menu-bar-plus-window app around only a `Window(...)` scene and then expecting the main window to appear at launch. Use `WindowGroup(..., id:)` for the primary launch window and reserve `Window(...)` for auxiliary/on-demand windows. +- Rendering full unbounded document titles, prompts, or message text directly inside a menu bar extra. Menu item labels should stay at or below 30 characters, with longer content moved into a dedicated window or detail view. +- Treating settings as another navigation destination in the main content window. +- Hardcoding `.background(.white)`, `Color.white`, or a fixed light palette in a brand-new scaffold without an explicit design requirement. +- Wrapping each sidebar item in large rounded custom cards inside a `.sidebar` list, which fights native source-list density, alignment, and selection behavior unless the user explicitly asked for a bespoke visual sidebar. +- Building sidebar rows with multiple repeated icons, three or more text lines, or a dense strip of inline metadata counters/timestamps/models. Keep the sidebar row to one icon and one or two text lines, then move richer metadata into the detail pane. +- Painting `NavigationSplitView` sidebars or root window panes with opaque custom color fills by default, instead of letting the sidebar use native source-list/material appearance and reserving custom backgrounds for actual content cards. +- Using push navigation for layouts that want stable sidebar selection and detail panes. +- Reaching for AppKit before the SwiftUI scene and command APIs have been used properly. + +## Workflow For A New macOS Scene Or View + +1. Define the scene type and ownership model before writing child views. +2. Decide which actions live in content, toolbars, commands, inspectors, or settings. +3. Sketch the selection model and layout: sidebar-detail, editor-inspector, document window, or utility window. +4. Create the file/folder structure for app entrypoint, root layout, feature views, models, stores, services, and support helpers. +5. Build with small, focused subviews and explicit inputs rather than giant computed fragments. +6. Add keyboard shortcuts and menu or toolbar exposure for actions that matter on desktop. +7. Validate the flow with a build and a quick usability pass: multiwindow assumptions, settings entry points, and selection stability. + +## Component References + +Use `references/components-index.md` as the entry point. Each component reference should include: +- intent and best-fit scenarios +- minimal usage pattern with desktop conventions +- pitfalls and discoverability notes +- when to fall back to `appkit-interop` diff --git a/plugins/build-macos-apps/skills/swiftui-patterns/agents/openai.yaml b/plugins/build-macos-apps/skills/swiftui-patterns/agents/openai.yaml new file mode 100644 index 00000000..74ef83c2 --- /dev/null +++ b/plugins/build-macos-apps/skills/swiftui-patterns/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "SwiftUI Patterns" + short_description: "Build native macOS SwiftUI scenes, menus, settings, and windows" + default_prompt: "Use $swiftui-patterns to choose the right macOS SwiftUI scene structure, implement desktop-native layouts and commands, and avoid iOS-centric UI patterns." diff --git a/plugins/build-macos-apps/skills/swiftui-patterns/references/commands-menus.md b/plugins/build-macos-apps/skills/swiftui-patterns/references/commands-menus.md new file mode 100644 index 00000000..47fb3b75 --- /dev/null +++ b/plugins/build-macos-apps/skills/swiftui-patterns/references/commands-menus.md @@ -0,0 +1,41 @@ +# Commands and Menus + +## Intent + +Use this when mapping desktop actions into menu items, keyboard shortcuts, and focused scene behavior. + +## Core patterns + +- Add `commands` at the scene level. +- Use `CommandMenu` for app-specific actions. +- Use `CommandGroup` to insert, replace, or remove menu sections. +- Use `FocusedValue` or scene state to make commands context-sensitive. +- Pair important commands with keyboard shortcuts and visible toolbar or content affordances when appropriate. + +## Example + +```swift +@main +struct SampleApp: App { + var body: some Scene { + WindowGroup { + EditorRootView() + } + .commands { + CommandMenu("Document") { + Button("New Note") { /* create */ } + .keyboardShortcut("n") + + Button("Toggle Inspector") { /* toggle */ } + .keyboardShortcut("i", modifiers: [.command, .option]) + } + } + } +} +``` + +## Pitfalls + +- Do not register the same shortcut in multiple places. +- Do not make commands the only discoverable path for a critical action. +- If you need responder-chain validation, custom menu item state, or AppKit-specific command behavior, use `appkit-interop`. diff --git a/plugins/build-macos-apps/skills/swiftui-patterns/references/components-index.md b/plugins/build-macos-apps/skills/swiftui-patterns/references/components-index.md new file mode 100644 index 00000000..0fe24327 --- /dev/null +++ b/plugins/build-macos-apps/skills/swiftui-patterns/references/components-index.md @@ -0,0 +1,16 @@ +# Components Index + +Use this file to find scene and component guidance. Each entry lists when to use it. + +## Available references + +- Windowing: `references/windowing.md` — Use when choosing between `WindowGroup`, `Window`, `DocumentGroup`, or window-opening APIs. +- Settings: `references/settings.md` — Use for dedicated settings scenes, preference storage, and settings entry points. +- Commands and menus: `references/commands-menus.md` — Use for menu items, keyboard shortcuts, focused actions, and command routing. +- Split views and inspectors: `references/split-inspectors.md` — Use for sidebar-detail apps, inspectors, and selection-driven desktop layouts. +- Menu bar extras: `references/menu-bar-extra.md` — Use when the app belongs primarily in the menu bar. + +## Adding entries + +- Add a new reference file when a macOS-specific pattern comes up repeatedly. +- Keep each reference short, actionable, and explicit about when SwiftUI is enough versus when AppKit interop is warranted. diff --git a/plugins/build-macos-apps/skills/swiftui-patterns/references/menu-bar-extra.md b/plugins/build-macos-apps/skills/swiftui-patterns/references/menu-bar-extra.md new file mode 100644 index 00000000..f0d243d8 --- /dev/null +++ b/plugins/build-macos-apps/skills/swiftui-patterns/references/menu-bar-extra.md @@ -0,0 +1,67 @@ +# Menu Bar Extra + +## Intent + +Use this when the app primarily lives in the macOS menu bar instead of a traditional always-open window. + +## Core patterns + +- Use `MenuBarExtra` for lightweight utilities, status indicators, and quick actions. +- If the app also has a primary main window that should appear at launch, define that scene with `WindowGroup(..., id:)` and use `Window(...)` only for auxiliary/on-demand windows. +- If the menu bar app should still show in the Dock and activate like a normal app, install an app delegate with `@NSApplicationDelegateAdaptor`, call `NSApp.setActivationPolicy(.regular)` during launch, and then `NSApp.activate(ignoringOtherApps: true)`. +- If the app is intentionally menu-bar-only, explicitly document that `.accessory` / no-Dock behavior is expected product behavior rather than a launch bug. +- Keep the menu content concise and action-oriented. +- Keep each visible menu item label to 30 characters or fewer. If the backing content can be longer than that, derive a short display title and open the full text in a separate window or detail pane. +- If the app has deeper workflows, open a dedicated window from the menu bar extra rather than cramming everything into the menu. + +## Example + +This snippet shows scene wiring only. In a real non-trivial app, keep the +`@main` app and `AppDelegate` in `App/App.swift`, and put the menu bar, +root content, and supporting models/services in separate files named after their +primary types. + +```swift +import AppKit + +private func shortMenuTitle(_ title: String) -> String { + if title.count <= 30 { + return title + } + return String(title.prefix(27)) + "..." +} + +final class AppDelegate: NSObject, NSApplicationDelegate { + func applicationDidFinishLaunching(_ notification: Notification) { + NSApp.setActivationPolicy(.regular) + NSApp.activate(ignoringOtherApps: true) + } +} + +@main +struct SampleApp: App { + @NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate + + var body: some Scene { + WindowGroup("Sample", id: "main") { + ContentView() + } + + MenuBarExtra("Sample", systemImage: "bolt.circle") { + Button(shortMenuTitle("Open Dashboard")) { /* open window */ } + Divider() + Button("Quit") { + NSApplication.shared.terminate(nil) + } + } + } +} +``` + +## Pitfalls + +- Do not rely on a `Window(...)` scene alone for the main launch window in a menu-bar-plus-window app when the product expects a regular window at startup. +- Do not silently ship a no-Dock menu-bar-only app if the user expects a normal app process. Either install the app delegate and switch to `.regular`, or clearly document that `.accessory` behavior is intentional. +- Do not turn the menu bar extra into a tiny, overloaded substitute for a full app window. +- Do not render raw unbounded titles or message bodies as menu items. Long labels quickly blow out the menu width and should be capped to 30 characters with a short display title. +- If the extra needs advanced status item customization or AppKit menu control, use `appkit-interop`. diff --git a/plugins/build-macos-apps/skills/swiftui-patterns/references/settings.md b/plugins/build-macos-apps/skills/swiftui-patterns/references/settings.md new file mode 100644 index 00000000..e974b2fa --- /dev/null +++ b/plugins/build-macos-apps/skills/swiftui-patterns/references/settings.md @@ -0,0 +1,55 @@ +# Settings + +## Intent + +Use this when building a native macOS settings window with SwiftUI. + +## Core patterns + +- Declare a dedicated `Settings` scene in the app. +- Keep settings content in a separate root view. +- Use `@AppStorage` for user preferences that should persist. +- Prefer tabs, sections, or a split settings layout over deep push navigation. +- Use `SettingsLink` or `OpenSettingsAction` for in-app entry points. + +## Example + +This snippet shows scene wiring only. In a real non-trivial app, keep the +`@main` app in `App/App.swift` and put settings content in a dedicated +view file such as `Views/SettingsView.swift`. + +```swift +@main +struct SampleApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + + Settings { + SettingsView() + } + } +} + +struct SettingsView: View { + @AppStorage("showSidebarIcons") private var showSidebarIcons = true + + var body: some View { + TabView { + Form { + Toggle("Show Sidebar Icons", isOn: $showSidebarIcons) + } + .tabItem { Label("General", systemImage: "gearshape") } + } + .frame(width: 460, height: 260) + .scenePadding() + } +} +``` + +## Pitfalls + +- Do not reuse an iOS full-screen settings screen unless the app really is a direct Catalyst-style port. +- Keep settings rows simple and accessible. +- If settings require custom panels, responders, or first-responder integration, use `appkit-interop`. diff --git a/plugins/build-macos-apps/skills/swiftui-patterns/references/split-inspectors.md b/plugins/build-macos-apps/skills/swiftui-patterns/references/split-inspectors.md new file mode 100644 index 00000000..8fef045c --- /dev/null +++ b/plugins/build-macos-apps/skills/swiftui-patterns/references/split-inspectors.md @@ -0,0 +1,38 @@ +# Split Views and Inspectors + +## Intent + +Use this when the app benefits from a stable sidebar-detail layout, optional supplementary content, or an inspector panel. + +## Core patterns + +- Prefer explicit selection state over push-only navigation. +- Start with `NavigationSplitView` when the layout matches the system mental model. +- Use a manual split only when you need unusual sizing or an always-visible custom column. +- Use `inspector(isPresented:)` for lightweight detail controls that complement the main content. + +## Example: sidebar + detail + +```swift +struct LibraryRootView: View { + @State private var selection: Item.ID? + @State private var showInspector = false + + var body: some View { + NavigationSplitView { + SidebarList(selection: $selection) + } detail: { + DetailView(selection: selection) + .inspector(isPresented: $showInspector) { + InspectorView(selection: selection) + } + } + } +} +``` + +## Pitfalls + +- Avoid swapping the whole root layout with top-level conditionals when selection changes. +- Avoid hiding too much detail behind modal sheets when an inspector or secondary column would fit better. +- If the layout requires AppKit split view delegation or advanced window coordination, use `appkit-interop`. diff --git a/plugins/build-macos-apps/skills/swiftui-patterns/references/windowing.md b/plugins/build-macos-apps/skills/swiftui-patterns/references/windowing.md new file mode 100644 index 00000000..a2764e64 --- /dev/null +++ b/plugins/build-macos-apps/skills/swiftui-patterns/references/windowing.md @@ -0,0 +1,50 @@ +# Windowing + +## Intent + +Use this when choosing the top-level scene model for a native macOS app. + +## Choose the scene type deliberately + +- Use `WindowGroup(..., id:)` for the primary app window when it should appear at launch, especially in apps that also have a `MenuBarExtra`. +- Use `WindowGroup` for any scene that can have multiple independent instances. +- Use `Window` for singleton utility windows or focused secondary surfaces. In menu-bar-heavy apps, `Window(...)` is better for auxiliary/on-demand windows and may not present the main window automatically at launch. +- Use `Settings` for preferences. Do not bury settings inside the main content flow. +- Use `DocumentGroup` when the app is fundamentally document-driven. + +## Example: main app plus utility window + +This snippet shows scene wiring only. In a real non-trivial app, keep the +`@main` app in `App/App.swift` and put `LibraryRootView`, +`InspectorRootView`, and `SettingsView` in dedicated `Views/` files. + +```swift +@main +struct SampleApp: App { + var body: some Scene { + WindowGroup("Library", id: "library") { + LibraryRootView() + } + + Window("Inspector", id: "inspector") { + InspectorRootView() + } + + Settings { + SettingsView() + } + } +} +``` + +## Opening windows + +- Use `openWindow(id:)` when a command, toolbar item, or button should open another scene. +- Keep per-window state in the scene or `@SceneStorage`, not in a single global pile. + +## Pitfalls + +- Avoid modeling every feature as a pushed destination inside one window. +- Do not use only `Window(...)` for the main launch window in a menu-bar-plus-window app unless you have verified the launch behavior and intentionally want an on-demand auxiliary window. +- Avoid singleton state for window-specific selections or drafts. +- If you need lower-level titlebar, tabbing, or window lifecycle control, switch to `appkit-interop`. diff --git a/plugins/build-macos-apps/skills/telemetry/SKILL.md b/plugins/build-macos-apps/skills/telemetry/SKILL.md new file mode 100644 index 00000000..c0ebb364 --- /dev/null +++ b/plugins/build-macos-apps/skills/telemetry/SKILL.md @@ -0,0 +1,86 @@ +--- +name: telemetry +description: Add lightweight runtime telemetry and debug instrumentation to macOS apps, then verify those events after building and running. Use when wiring `Logger` / `os.Logger`, adding log points for window/sidebar/menu-bar actions, reading runtime logs from Console or `log stream`, or confirming that expected events fire after a local run. +--- + +# Telemetry + +## Quick Start + +Use this skill to add lightweight app instrumentation that helps debug behavior +without turning the codebase into a logging landfill. Prefer Apple's unified +logging APIs and verify the events after a build/run loop. + +## Core Guidelines + +- Prefer `Logger` from the `OSLog` framework for structured app logs. +- Give each feature a clear subsystem/category pair so runtime filtering stays easy. +- Log meaningful user and app lifecycle events: window opening, sidebar selection changes, menu commands, menu bar extra actions, sync/load milestones, and unexpected fallback paths. +- Keep info logs concise and stable. Use debug logs for noisy state details. +- Do not log secrets, auth tokens, personal data, or raw document contents. +- Add signposts only when measuring timing or performance spans; do not overinstrument by default. + +## Minimal Logger Pattern + +```swift +import OSLog + +private let logger = Logger( + subsystem: Bundle.main.bundleIdentifier ?? "SampleApp", + category: "Sidebar" +) + +@MainActor +func selectItem(_ item: SidebarItem) { + logger.info("Selected sidebar item: \(item.id, privacy: .public)") + selection = item.id +} +``` + +Use feature-specific categories like `Windowing`, `Commands`, `MenuBar`, `Sidebar`, +`Sync`, or `Import` so logs can be filtered quickly. + +## Workflow + +1. Identify the behavior that needs observability. + - Window open/close + - Sidebar or inspector selection changes + - Menu or keyboard command actions + - Menu bar extra actions + - Background load/sync/import events + - Error and recovery paths + +2. Add the smallest useful instrumentation. + - Create one `Logger` per feature area or type. + - Log action boundaries and key state transitions. + - Prefer one high-signal line per user action over noisy value dumps. + +3. Build and run the app. + - Use `build-run-debug` for the build/run loop. + - If `script/build_and_run.sh` exists, prefer `./script/build_and_run.sh --telemetry` for live telemetry checks or `./script/build_and_run.sh --logs` for broader process logs. + - Exercise the UI or command path that should emit telemetry. + +4. Read runtime logs and verify the event fired. + - Use Console.app with a process/subsystem filter when that is the fastest manual check. + - Use `log stream --style compact --predicate 'process == "AppName"'` for live terminal verification. + - Prefer tighter predicates when you know the subsystem/category: + `log stream --style compact --predicate 'subsystem == "com.example.app" && category == "Sidebar"'` + +5. Tighten or remove instrumentation. + - If the event fires, keep only the logs that remain useful for future debugging. + - If it does not fire, move the log closer to the suspected control path and rerun. + +## Verification Checklist + +- The app builds after telemetry changes. +- The relevant action emits exactly one clear log line or a small bounded sequence. +- The log can be filtered by process, subsystem, or category. +- No sensitive payloads are written to unified logs. +- Noisy temporary debug logs are removed or demoted before finishing. + +## Guardrails + +- Do not use `print` as the primary app telemetry mechanism for macOS app code. +- Do not leave a dense trail of permanent debug logs around every state mutation. +- Do not claim an event is wired correctly until you have a concrete verification path through Console, `log stream`, or captured process output. +- If the debugging task is mostly about crash/backtrace analysis rather than action telemetry, switch to `build-run-debug`. diff --git a/plugins/build-macos-apps/skills/telemetry/agents/openai.yaml b/plugins/build-macos-apps/skills/telemetry/agents/openai.yaml new file mode 100644 index 00000000..de120dc3 --- /dev/null +++ b/plugins/build-macos-apps/skills/telemetry/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Telemetry" + short_description: "Add lightweight Logger instrumentation and verify macOS runtime events" + default_prompt: "Use $telemetry to add focused Logger instrumentation for macOS windows, sidebar, menu, and menu bar actions, then verify the emitted events with Console or log stream after running the app." diff --git a/plugins/build-macos-apps/skills/test-triage/SKILL.md b/plugins/build-macos-apps/skills/test-triage/SKILL.md new file mode 100644 index 00000000..48c3d5f7 --- /dev/null +++ b/plugins/build-macos-apps/skills/test-triage/SKILL.md @@ -0,0 +1,54 @@ +--- +name: test-triage +description: Triage failing macOS tests across Xcode and SwiftPM workflows. Use when asked to run macOS tests, narrow failing scopes, explain assertion or crash failures, or separate real test regressions from setup and environment problems. +--- + +# Test Triage + +## Quick Start + +Use this skill to run the smallest meaningful test scope first, classify +failures precisely, and avoid treating every test failure like a product bug. + +## Workflow + +1. Detect the test harness. + - Use `xcodebuild test` for Xcode-based projects. + - Use `swift test` for SwiftPM packages. + +2. Narrow the scope. + - If the user gave a target, product, or test filter, use it. + - If not, prefer the smallest likely failing target before a full suite. + +3. Classify the result. + - Build failure + - Assertion failure + - Crash or signal + - Async timing or flake + - Environment or fixture setup issue + - Missing entitlement or host app issue + +4. Rerun intelligently. + - Use focused reruns when a specific case fails. + - Avoid burning time on full-suite reruns without new information. + +5. Summarize clearly. + - What command ran + - Which tests failed + - What kind of failure it was + - The best next proof step or fix path + +## Guardrails + +- Distinguish compilation failures from test execution failures. +- Call out when a test appears to assume iOS-only or simulator-only behavior. +- Mark likely flakes as such instead of overstating confidence. + +## Output Expectations + +Provide: +- the command used +- the smallest failing scope +- the top failure category +- a concise explanation of the likely cause +- the next rerun or fix step diff --git a/plugins/build-macos-apps/skills/test-triage/agents/openai.yaml b/plugins/build-macos-apps/skills/test-triage/agents/openai.yaml new file mode 100644 index 00000000..62d2b90d --- /dev/null +++ b/plugins/build-macos-apps/skills/test-triage/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Test Triage" + short_description: "Run and explain macOS test failures with focused reruns" + default_prompt: "Use $test-triage to run the smallest meaningful macOS test scope first, classify failures correctly, and summarize the next best rerun or fix." diff --git a/plugins/build-macos-apps/skills/view-refactor/SKILL.md b/plugins/build-macos-apps/skills/view-refactor/SKILL.md new file mode 100644 index 00000000..1e0484ec --- /dev/null +++ b/plugins/build-macos-apps/skills/view-refactor/SKILL.md @@ -0,0 +1,109 @@ +--- +name: view-refactor +description: Refactor macOS SwiftUI views and scenes with strong defaults for small dedicated subviews, stable sidebar and selection structure, explicit command and toolbar ownership, scene-aware state, and narrow AppKit escape hatches. Use when cleaning up a macOS view file, splitting oversized scene roots, removing iOS-centric patterns, or tightening mixed SwiftUI/AppKit architecture. +--- + +# View Refactor + +## Overview + +Refactor macOS views toward small, explicit, stable scene and view types. Default +to native SwiftUI for layout, selection, commands, and settings. Reach for AppKit +only at the narrow edges where desktop behavior truly requires it. + +## Core Guidelines + +### 1) Model scenes explicitly + +- Break the app into meaningful scene roots: main window, settings, utility windows, inspectors, or menu bar extras. +- Do not let one giant root view silently own every desktop surface. + +### 2) Keep a predictable file shape + +- Follow this ordering unless the file already has a stronger local convention: +- Environment +- `private`/`public` `let` +- `@State` / other stored properties +- computed `var` (non-view) +- `init` +- `body` +- computed view builders / other view helpers +- helper / async functions + +### 2b) Split files by responsibility + +- For non-trivial apps, do not keep the full app, all views, models, stores, networking clients, process clients, and helpers in one Swift file. +- Accept a single Swift file only for tiny throwaway examples or snippets: roughly under 50 lines, one screen, no persistence, no networking/process client, and no reusable models. +- Use `App/App.swift` for the `@main` app and `AppDelegate` only. +- Keep `Views/ContentView.swift` focused on root layout and composition; move feature UI into files such as `Views/SidebarView.swift`, `Views/DetailView.swift`, and `Views/ComposerView.swift`. +- Move value types and selection enums into `Models/*.swift`, stores into `Stores/*.swift`, app-server/network/process clients into `Services/*.swift`, and small formatters/resolvers/extensions into `Support/*.swift`. +- Keep files small and named after the primary type they contain. + +### 3) Prefer dedicated subview types over many computed `some View` fragments + +- Extract meaningful desktop sections like sidebar rows, detail panels, inspectors, or toolbar content into focused subviews. +- Keep computed `some View` helpers small and rare. +- Pass explicit data, bindings, and actions into subviews instead of handing down the whole scene model. + +### 4) Keep selection and layout stable + +- Prefer one stable split or window layout with local conditionals inside it. +- Avoid top-level branch swapping between radically different roots when selection changes. +- Let the layout be constant; let state drive the content inside it. + +### 5) Extract commands, toolbars, and actions out of `body` + +- Do not bury non-trivial button logic inline. +- Do not mix command routing, menu state, and layout in the same block if they can be named clearly. +- Keep `body` readable as UI, not as a desktop view controller. + +### 6) Use scene and app storage intentionally + +- Use `@SceneStorage` for per-window ephemeral state when it truly helps restore the scene. +- Use `@AppStorage` for durable preferences, not transient UI toggles that only matter in one window. +- Keep scene-owned state close to the scene root. + +### 7) Keep AppKit escape hatches narrow + +- If a representable or `NSWindow` bridge exists, isolate it behind a small wrapper or helper. +- Do not let AppKit references spread through unrelated SwiftUI views. +- If the bridge starts owning the feature, re-evaluate the architecture. + +### 8) Observation usage + +- For `@Observable` reference types on modern macOS targets, store them as `@State` in the owning view. +- Pass observables explicitly to children. +- On older deployment targets, fall back to `@StateObject` and `@ObservedObject` where needed. + +## Workflow + +1. Identify the current scene boundary and whether the file is trying to do too much. +2. Reorder the file into a predictable top-to-bottom structure. +3. Extract desktop-specific sections into dedicated subview types. +4. Stabilize the root layout around selection, scenes, and commands rather than top-level branching. +5. Move action logic, command routing, and toolbar behavior into named helpers or separate types. +6. Tighten any AppKit bridge so the imperative edge is small and explicit. +7. Keep behavior intact unless the request explicitly asks for structural and behavioral changes together. + +## Refactor Checklist + +- Split oversized view files before adding more UI. +- Move pure models, identifiers, and selection enums out of view files. +- Move `Process`, `URLSession`, app-server, and platform client code out of SwiftUI views into `Services/`. +- Keep `AppDelegate` and the `@main` app entrypoint minimal. +- Build after each major split so compile errors stay local. + +## Common Smells + +- A root view that mixes window scaffolding, settings, toolbar code, command handling, and detail layout. +- A single app file that mixes app entrypoint, root layout, feature views, models, stores, service clients, and support extensions. +- iOS-style push navigation forced into a Mac sidebar-detail problem. +- Several booleans for mutually exclusive inspectors, sheets, or utility windows. +- AppKit objects passed through many SwiftUI layers without a clear ownership reason. +- Large computed view fragments standing in for real subviews. + +## Notes + +- A good macOS refactor should make scene structure, selection flow, and command ownership obvious. +- When the problem is fundamentally a missing desktop pattern, use `swiftui-patterns`. +- When the problem is fundamentally a boundary with AppKit, use `appkit-interop`. diff --git a/plugins/build-macos-apps/skills/view-refactor/agents/openai.yaml b/plugins/build-macos-apps/skills/view-refactor/agents/openai.yaml new file mode 100644 index 00000000..46d315c4 --- /dev/null +++ b/plugins/build-macos-apps/skills/view-refactor/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "View Refactor" + short_description: "Refactor macOS SwiftUI views and scenes toward stable desktop structure" + default_prompt: "Use $view-refactor to split oversized macOS scenes, stabilize sidebar and selection structure, extract commands and toolbar logic, and keep AppKit escape hatches narrow." diff --git a/plugins/build-macos-apps/skills/window-management/SKILL.md b/plugins/build-macos-apps/skills/window-management/SKILL.md new file mode 100644 index 00000000..77575bba --- /dev/null +++ b/plugins/build-macos-apps/skills/window-management/SKILL.md @@ -0,0 +1,227 @@ +--- +name: window-management +description: Customize macOS 15+ SwiftUI windows and scene behavior using Window, WindowGroup, and macOS window modifiers. Use when styling or hiding window toolbars and titles, extending drag regions with WindowDragGesture, replacing window backgrounds with materials, disabling minimize or restoration for utility windows, setting default or ideal window placement from content/display size, creating borderless windows, or tuning default launch behavior. +--- + +# Window Management + +## Overview + +Use this skill to tailor each SwiftUI window to its job. Start by identifying +which scene owns the window (`Window`, `WindowGroup`, or a dedicated utility +scene), then customize the toolbar/title area, background material, resize and +restoration behavior, and initial or zoomed placement. + +Prefer scene and window modifiers over ad hoc AppKit bridges when SwiftUI offers +the behavior directly. Keep each window purpose-built: a main browser window, an +About window, and a media player window usually want different chrome, +resizability, restoration, and placement rules. + +These APIs are macOS 15+ SwiftUI window/scene customizations. For older +deployment targets, expect to use more AppKit bridging or availability guards. + +## Workflow + +1. Inspect the relevant scene declaration and classify the window role: + main app navigation, inspector/detail utility, About/support window, media + playback window, welcome window, or a borderless custom surface. +2. Adjust toolbar and title presentation to match the content. +3. If the toolbar background or entire toolbar is hidden, make sure the window + still has a usable drag region. +4. Refine window behavior for that role: minimize availability, restoration, + resize expectations, and whether the window should appear at launch. +5. Set default placement for newly opened windows and ideal placement for zoom + behavior when content and display size matter. +6. Build and launch the app with `build-run-debug` to verify the result in + a real foreground `.app` bundle. +7. If SwiftUI scene/window modifiers are not enough, switch to `appkit-interop` + for a narrow `NSWindow` bridge rather than spreading AppKit through the view + tree. + +## Toolbar And Title + +- Use `.toolbar(removing: .title)` when the window title should stay associated + with the window for accessibility and menus, but not be visibly drawn in the + title bar. +- Use `.toolbarBackgroundVisibility(.hidden, for: .windowToolbar)` when large + media or hero content should visually extend to the top edge of the window. +- If the window still needs close/minimize/full-screen controls, remove only the + title and toolbar background. If the toolbar should disappear entirely, use + `.toolbarVisibility(.hidden, for: .windowToolbar)` instead. +- Remove custom toolbar backgrounds and manually painted titlebar fills before + layering new SwiftUI toolbar APIs on top. +- Keep the window's logical title meaningful even if hidden; the system can + still use it for accessibility and menu items. These are visual changes only. + +## Drag Regions + +- If a toolbar background is hidden or the toolbar is removed entirely, use + `WindowDragGesture()` to extend the draggable area into your content. +- Attach the gesture to a transparent overlay or non-interactive header region + that does not steal gestures from real controls. +- For a media player with custom playback controls, insert the drag overlay + between the video content and the controls so AVKit or transport controls keep + receiving input. +- Pair the drag gesture with `.allowsWindowActivationEvents(true)` so clicking + and immediately dragging a background window still activates and moves it. + +## Background And Materials + +- Use `.containerBackground(.thickMaterial, for: .window)` when a utility window + or About window should replace the default window background with a subtle + frosted material. +- Prefer system materials for stylized windows instead of hardcoded translucent + colors. +- Use this especially for fixed-content utility windows where a softer backdrop + is part of the design. + +## Window Behavior + +- Use `.windowMinimizeBehavior(.disabled)` for always-reachable utility windows + such as a custom About window where minimizing adds little value. +- Disable the green zoom control through fixed sizing or window constraints when + the window's content has one intended size. +- Use `.restorationBehavior(.disabled)` for windows that should not reopen on + next launch, such as About panels, transient support/info windows, or + first-run welcome surfaces. +- Keep state restoration enabled for primary document or navigation windows when + reopening prior size and position is desirable. +- By default, SwiftUI respects the user's system-wide macOS state restoration + setting. Use `restorationBehavior(...)` only when a specific window should + intentionally opt into or out of that system behavior. +- Use `.defaultLaunchBehavior(.presented)` for windows that should appear first + on launch, such as a welcome window, and choose that behavior intentionally + rather than relying on side effects from scene creation order. + +## Window Placement + +- Use `.defaultWindowPlacement { content, context in ... }` to control the + initial size and optional position of newly opened windows. +- Inside the placement closure, call `content.sizeThatFits(.unspecified)` to get + the content's ideal size. +- Read `context.defaultDisplay.visibleRect` to get the display's usable region + after accounting for the menu bar and Dock. +- Return `WindowPlacement(size: size)` with a size clamped to the visible rect + when media or document content may be larger than the display. If no position + is provided, the window is centered by default. +- Use `.windowIdealPlacement { content, context in ... }` to control what + happens when the user chooses Zoom from the Window menu or Option-clicks the + green toolbar button. For media windows, preserve aspect ratio and grow to the + largest size that fits the display. +- Treat default placement and ideal placement as separate policies: + - default placement controls where a new window first appears, + - ideal placement controls how large a zoomed window should become. +- Always consider external displays and rotated/narrow screens when sizing + player windows or document windows from content dimensions. + +## Borderless And Specialized Windows + +- Use `.windowStyle(.plain)` for borderless or highly custom chrome windows, but + make sure the content still provides a clear drag/move affordance and visible + context. +- For a borderless player, HUD, or welcome window, decide upfront whether losing + standard titlebar affordances is worth the custom presentation. +- Keep one clear path back to regular window management if the plain style makes + the window feel invisible or hard to move. + +## API Snippets + +```swift +WindowGroup("Destination Video") { + CatalogView() + .toolbar(removing: .title) + .toolbarBackgroundVisibility(.hidden, for: .windowToolbar) +} +``` + +```swift +Window("About", id: "about") { + AboutView() + .toolbar(removing: .title) + .toolbarBackgroundVisibility(.hidden, for: .windowToolbar) + .containerBackground(.thickMaterial, for: .window) +} +.windowMinimizeBehavior(.disabled) +.restorationBehavior(.disabled) +``` + +```swift +WindowGroup("Player", for: Video.self) { $video in + PlayerView(video: video) +} +.defaultWindowPlacement { content, context in + let idealSize = content.sizeThatFits(.unspecified) + let displayBounds = context.defaultDisplay.visibleRect + let fittedSize = clampToDisplay(idealSize, displayBounds: displayBounds) + return WindowPlacement(size: fittedSize) +} +.windowIdealPlacement { content, context in + let idealSize = content.sizeThatFits(.unspecified) + let displayBounds = context.defaultDisplay.visibleRect + let zoomedSize = zoomToFit(idealSize, displayBounds: displayBounds) + let position = centeredPosition(for: zoomedSize, in: displayBounds) + return WindowPlacement(position, size: zoomedSize) +} +``` + +```swift +PlayerView(video: video) + .overlay(alignment: .top) { + Color.clear + .frame(height: 48) + .contentShape(Rectangle()) + .gesture(WindowDragGesture()) + .allowsWindowActivationEvents(true) + } +``` + +```swift +Window("Welcome", id: "welcome") { + WelcomeView() +} +.windowStyle(.plain) +.defaultLaunchBehavior(.presented) +``` + +## Review Checklist + +- The scene type matches the window's role and lifecycle. +- Hidden titles still leave a meaningful logical title for accessibility and + menus. +- Toolbar background removal is intentional and does not hurt titlebar legibility + or window control placement. +- Windows with hidden or removed toolbars still have a reliable drag region and + support click-then-drag activation from the background. +- Utility windows have restoration/minimize behavior that matches their purpose. +- Restoration overrides are used only when a scene should intentionally differ + from the user's system-wide setting. +- Default and ideal placement use `content.sizeThatFits(.unspecified)` and + `context.defaultDisplay.visibleRect` when content/display size matters. +- Media windows preserve aspect ratio and fit on small or rotated displays. +- Borderless windows still have a usable move/drag affordance. + +## Guardrails + +- Do not use `.toolbar(removing: .title)` just to hide a title you forgot to set. + Keep the underlying window title meaningful. +- Do not hide the toolbar background or the whole toolbar without replacing the + lost drag affordance. +- Do not disable restoration on the main document/navigation window unless the + user explicitly wants a fresh-start app every launch. +- Do not hardcode one monitor size or assume a single-display setup when sizing + player windows. +- Do not reach for `NSWindow` mutation before checking whether + `.windowMinimizeBehavior`, `.restorationBehavior`, `.defaultWindowPlacement`, + `.windowIdealPlacement`, `.windowStyle`, or `.defaultLaunchBehavior` already + solve the problem. +- Do not leave a plain borderless window without any obvious drag or close path. + +## When To Use Other Skills + +- Use `swiftui-patterns` for broader scene, commands, settings, sidebar, + and inspector architecture. +- Use `liquid-glass` when the main question is modern macOS visual treatment, + Liquid Glass, or system material adoption. +- Use `appkit-interop` if a custom window behavior truly requires `NSWindow`, + `NSPanel`, or responder-chain control. +- Use `build-run-debug` to launch and verify the resulting windows. diff --git a/plugins/build-macos-apps/skills/window-management/agents/openai.yaml b/plugins/build-macos-apps/skills/window-management/agents/openai.yaml new file mode 100644 index 00000000..620da539 --- /dev/null +++ b/plugins/build-macos-apps/skills/window-management/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Window Management" + short_description: "Customize SwiftUI window chrome, drag regions, behavior, and placement" + default_prompt: "Use $window-management to tailor SwiftUI macOS windows with scene/window modifiers, including titlebar and toolbar styling, WindowDragGesture drag regions, minimize/restoration behavior, default and ideal placement, plain window style, and launch behavior."