NOASSERTIONupdated 1mo ago
AUDIT-ONLY Β· macOS-only Β· SwiftUI-only. Run this on a finished or in-progress macOS SwiftUI project to detect β and where certain, flag β every way the scene graph at App.body level goes wrong on the Mac: the iOS one-window mental model, Preferences crammed into the main window instead of the Settings {} scene, auxiliary windows faked with a sheet or a @State bool, a menu-bar app faked with AppKit NSStatusItem, the headline MenuBarExtra β openWindow/openSettings activation trap, openWindow(id:) typos that no-op silently, and a WindowGroup-only App with no lifecycle bridge.
What can you do with Audit Swiftui Scenes Windows?
name: audit-swiftui-scenes-windows description: Audits a finished or in-progress macOS SwiftUI codebase for scene-composition and window defects at App.body level and writes per-finding Markdown to swiftui-audits/. Use when a menu-bar Settings or window opens behind everything or not at all, when Preferences live in the main window instead of the Settings scene, when a second window is faked with a sheet or a State boolean, when a menu-bar app uses NSStatusItem instead of MenuBarExtra, when openWindow(id:) silently does nothing, when an app never quits after the last window closes, or when asked to verify MenuBarExtra, Settings, WindowGroup, Window, UtilityWindow, openWindow, openSettings, dismissWindow, SettingsLink, windowResizability, or NSApplicationDelegateAdaptor on a Mac target. AUDIT-ONLY, macOS-only, SwiftUI-only. Not for menu/command CONTENTS inside a MenuBarExtra closure (audit-swiftui-menus-commands), not for content-frame window sizing (audit-swiftui-layout-and-tables), not for DocumentGroup modeling, not for writing new scenes from scratch.
Audit SwiftUI Scenes & Windows
AUDIT-ONLY Β· macOS-only Β· SwiftUI-only. Run this on a finished or in-progress macOS SwiftUI
project to detect β and where certain, flag β every way the scene graph at App.body level goes
wrong on the Mac: the iOS one-window mental model, Preferences crammed into the main window instead of
the Settings {} scene, auxiliary windows faked with a sheet or a @State bool, a menu-bar app faked
with AppKit NSStatusItem, the headline MenuBarExtra β openWindow/openSettings activation trap,
openWindow(id:) typos that no-op silently, and a WindowGroup-only App with no lifecycle bridge.
Findings are written to disk in the toolkit's unified schema. This is never a from-scratch scene generator.
This domain is almost entirely macOS-divergent β MenuBarExtra and Settings {} have no iOS
analog; the Window (single, unique) vs WindowGroup (user-duplicable, βN) split barely matters on
iOS but is fundamental on the Mac. The scene-composition APIs live at App.body level, a spot iOS
tutorials rarely exercise, so the model has thin priors there. Be suspicious wherever AI wrote scenes.
Boundary / seam note (stay in lane)
- Menu/command CONTENTS inside a
MenuBarExtra { β¦ }closure (the buttons,Dividers, item shortcuts) belong toaudit-swiftui-menus-commands. This skill owns the scene + the activation trap; an item-level issue inside the closure iscross_ref: audit-swiftui-menus-commands. (Tiebreaker:${CLAUDE_PLUGIN_ROOT}/references/_shared/cross-ref-graph.mdΒ§2.) - Window sizing is a two-layer split. The scene-modifier layer (
.defaultSize,.windowResizability,.windowIdealSize,.windowManagerRole) is ours (sw-08). The content frame (.frame(min/ideal/maxβ¦)on the root view) isaudit-swiftui-layout-and-tables; emit a companioncross_refwhen both apply. navigationTitleinside aWindowscene replacing the titlebar (sw-13) is owned here as the scene-side gotcha; the structuralnavigationTitle/toolbar migration isaudit-swiftui-navigation-toolbars.DocumentGroupdocument modeling (FileDocument,ReferenceFileDocument, conflict handling) is the futureaudit-swiftui-document-model. This skill audits document scenes only, not the model.- Whether an AppKit bridge should exist at all is
audit-swiftui-appkit-overuse; this skill flagsNSStatusItem-instead-of-MenuBarExtra(sw-05) as the scene-shaped symptom and cross_refs there.
The non-negotiable Mac scene rules
- Preferences β
Settings {}, never an in-window link. Only theSettings {}scene wires the "Settingsβ¦" menu item, the β, shortcut, and a floating modeless window. ANavigationLink/.sheetgives none of those. - A real second window is a registered scene +
openWindow, never a sheet or a@Statebool. The scene system owns window lifetime; a boolean cannot create, own, or close a real window. - Menu-bar UI is
MenuBarExtra, neverNSStatusItem+NSMenu. The AppKit blob doesn't compose with the SwiftUI scene graph. - Opening anything from a
MenuBarExtrarequires explicit activation β and evenNSApp.activate()+openSettings()fails for.accessoryapps on macOS 26 (the headline trap, sw-06). - Set
.defaultSize+.windowResizabilityon every Mac scene, and bridge an@NSApplicationDelegateAdaptorwhen the app needs quit-on-last-window / launch / terminate hooks.
The five scene types + the activation trap in full: references/scene-types-and-settings.md and
references/menu-bar-activation-trap.md.
Defect index (sw-01 β¦ sw-13)
id Β· tell Β· severity Β· fix Β· open reference. Severities: hard-fail (silent runtime failure /
never-correct), warning (compiles but non-native), advisory (judgment / polish). auto =
mechanical single-answer; flag = show the β
, dev applies. Every defect here is flag-only β each
fix depends on the app's type (a menu-bar-only app's quit/dismiss/style answers differ from a
document app's), so none is mechanically auto-fixable.
| id | One-line tell | Sev | Fix | Reference |
|---|---|---|---|---|
| sw-01 | stale/invented scene API β Preferences {}, showSettingsWindow:/showPreferencesWindow: selector, @FocusedDocument, DocumentGroupLaunchScene on a Mac arm |
hard-fail | flag | scene-types-and-settings.md |
| sw-02 | single WindowGroup + a *Settings*/*Preferences* view reached via NavigationLink/.sheet β missing Settings {} scene |
warning | flag | scene-types-and-settings.md |
| sw-03 | a Settings/Preferences Form with Save/Cancel/Apply buttons (modal, Windows-style) β HIG violation |
warning | flag | scene-types-and-settings.md |
| sw-04 | a separate window (inspector / second doc) faked with .sheet(isPresented:) or a @State bool |
warning | flag | windows-sizing-lifecycle.md |
| sw-05 | NSStatusItem / NSStatusBar.system.statusItem / NSMenu in a SwiftUI-first app β should be MenuBarExtra |
warning | flag | menu-bar-activation-trap.md |
| sw-06 | openWindow(/openSettings( inside a MenuBarExtra { } with no NSApp.activate β or a lone NSApp.activate()+openSettings() for an .accessory app on macOS 26 |
hard-fail | flag | menu-bar-activation-trap.md |
| sw-07 | SettingsLink placed directly inside a MenuBarExtra { } (fails to surface Settings on macOS 26) |
warning | flag | menu-bar-activation-trap.md |
| sw-08 | a WindowGroup/Window/UtilityWindow scene with no .defaultSize and no .windowResizability |
advisory | flag | windows-sizing-lifecycle.md |
| sw-09 | openWindow(id: "β¦") / dismissWindow(id: "β¦") whose literal string matches no registered scene id β silent no-op |
warning | flag | windows-sizing-lifecycle.md |
| sw-10 | @Environment(\.openWindow) present but no @Environment(\.dismissWindow) anywhere despite an auxiliary window |
advisory | flag | windows-sizing-lifecycle.md |
| sw-11 | an App with scenes but no @NSApplicationDelegateAdaptor, needing quit-on-last-window / launch / terminate |
warning | flag | windows-sizing-lifecycle.md |
| sw-12 | a content-forward window with the default titled chrome where .windowStyle(.hiddenTitleBar)/.plain is wanted |
advisory | flag | windows-sizing-lifecycle.md |
| sw-13 | navigationTitle(_:) placed inside a Window scene β replaces the window titlebar (cross_ref nav-toolbars) |
advisory | flag | windows-sizing-lifecycle.md |
UNVERIFIED β carry as the flagged status, never assert as fact (each is source: verify against Xcode 26 SDK): the menuBarExtraStyle case names (.menu/.window/.automatic); the exact
windowStyle case strings; dismissWindow's verbatim description; and the macOS 26 activation
regression itself is an open, unresolved platform gap (sw-06) β flag it, do not promise the workaround works.
The real API, at a glance
Real scene types (macOS): WindowGroup (11.0+, βN-duplicable; prefer the value-based
WindowGroup(id:for:content:) β string-title-label inits are deprecated), Window (13.0+, single
unique), UtilityWindow (15.0+, macOS-only floating inspector panel), Settings {} (11.0+,
macOS-only), MenuBarExtra (13.0+, macOS-only). Actions: openWindow (13.0+),
dismissWindow (14.0+ β NOT 13), openSettings (14.0+, macOS-only), SettingsLink (14.0+, macOS-only).
Scene modifiers: defaultSize, windowResizability, windowStyle, defaultLaunchBehavior (15.0+),
windowIdealPlacement (15.0+). Lifecycle bridge: @NSApplicationDelegateAdaptor (11.0+) β
applicationShouldTerminateAfterLastWindowClosed(_:) / applicationWillTerminate(_:).
Stale / invented (sw-01): Preferences {} and the showSettingsWindow: / showPreferencesWindow:
selectors are stale pre-Settings-scene patterns; @FocusedDocument is not a real Apple symbol
(use a custom FocusedValues key β see the shared blacklist). Floor-uncertain β carry verify against Xcode 26 SDK: pushWindow (Apple pages show visionOS 2.0+ only; macOS unconfirmed β do not
assert macOS 15) and DocumentGroupLaunchScene (macOS ABSENT β iOS/iPadOS/Mac Catalyst/visionOS only). NSApp.activate(ignoringOtherApps:)
is deprecated β use plain NSApp.activate() on macOS 14+.
Floor values are the reconciled truth in ${CLAUDE_PLUGIN_ROOT}/references/_shared/floors-master.md
and the canonical invented-name list in ${CLAUDE_PLUGIN_ROOT}/references/_shared/hallucination-blacklist.md
β read, never restate them. Signatures + full βββ
rewrites: references/scene-types-and-settings.md.
Grounded β
shape (the consensus, from real code β not invented). swiftui-ctx consensus for
MenuBarExtra is the trailing-closure form MenuBarExtra { β¦ } label: { β¦ } (50% of corpus call sites,
introduced_macos: 13.0); the canonical menu-bar + auxiliary-Window scene graph it anchors:
var body: some Scene {
MenuBarExtra {
MenuBarPopoverView(manager: manager, openLibrary: { showLibraryWindow() })
} label: {
Image(systemName: "play.rectangle.fill")
}
.menuBarExtraStyle(.window)
Window("Phosphene", id: "library") { // a real registered scene β what openWindow(id:) targets
LibraryWindow(manager: manager)
}
.defaultSize(width: 900, height: 600) // sw-08: scene-modifier sizing layer
}
Source (the FIX must cite a real permalink like this one, never a placeholder): kageroumado/phosphene
Phosphene/PhospheneApp.swift#L11 β permalink
https://github.com/kageroumado/phosphene/blob/757cae705aaf36ac13ba973919a181ea89fb2e3c/Phosphene/PhospheneApp.swift#L11
Β· Apple doc (via Sosumi) doc: https://sosumi.ai/documentation/swiftui/menubarextra (access 2026-06-07).
Re-fetch the current consensus + permalink per audit with swiftui-ctx lookup MenuBarExtra --json β
swiftui-ctx file <recommended.id> --smart (steps 5 VERIFY Β· 7 FIX); the shape above is the live result, not a fixture.
The 8-step audit workflow (execute verbatim)
- ORIENT.
tree/findthe SwiftUI sources. Find the@mainAppstructand read itsbodyβ this domain lives there. Read the deployment target (project.pbxprojMACOSX_DEPLOYMENT_TARGET, orPackage.swiftplatforms:) and the activation policy (LSUIElementinInfo.plist, orNSApp.setActivationPolicy(.accessory)) β both are load-bearing for sw-06 (the trap is worst for an.accessorymenu-bar-only app). Record the app type (document / single-window / menu-bar-only) β it decides every flag's β . - LOCATE. Run the shared hybrid lint runner:
bash ${CLAUDE_PLUGIN_ROOT}/scripts/swiftui-lint.sh --skill audit-swiftui-scenes-windows --dir <sources> --json /tmp/sw.json --sarif /tmp/sw.sarif. It runs this skill's tier-1 grep tells (lint/grep-tells.tsv) + tier-2 structural ast-grep rules (lint/ast-grep/*.ymlβ the activation-trap-in-MenuBarExtraandSettingsLink-in-MenuBarExtracontainment rules grep can't express), plus a per-file parse probe, and emits unified JSON + SARIF. Read itsparse_warningsβ a flagged file did not fully parse, so a structural miss can't masquerade as clean; READ those by hand. The runner only LOCATES. Engine + rule-file format + degradation:${CLAUDE_PLUGIN_ROOT}/references/_shared/lint-architecture.md. - READ. Open every located file in full β never pattern-match-and-patch blind. The two
load-bearing cross-line facts grep can't see: (a) does an
openWindow(id:)/dismissWindow(id:)string match a registeredWindow(id:)/WindowGroup(id:)scene somewhere else in the project (sw-09) β build the scene-id β open-call table; (b) is anopenWindow/openSettingscall inside aMenuBarExtraclosure and is there an adjacentNSApp.activate(sw-06). Also inventory: every scene + its sizing modifiers, every*Settings*view + how it's reached, the@NSApplicationDelegateAdaptor. - DETECT. Apply the index. Assign each candidate a confidence; report a finding only at 100%
certainty (e.g. an
openWindow(id:)with no matching scene; a stalePreferences {}; anNSStatusItemin a SwiftUI app; anopenSettings()insideMenuBarExtrawith noNSApp.activate). - VERIFY. For anything β€ ~70% confidence (a symbol you're unsure exists, a floor you can't place, a
behavior claim, the activation regression), run both evidence sources. (a) Practice β
bash ${CLAUDE_PLUGIN_ROOT}/scripts/swiftui-ctx lookup <api> --json(andswiftui-ctx deprecated <api>for a currency/deprecation rule): read itsconsensus(the canonical shape),deprecated+replacement,recommendedpermalink,introduced_macos, andco_occurs_with; alookupexit 3 (not-found, with asuggestion) corroborates a hallucination finding (sw-01).swiftui-ctx recipe menubar-appandrecipe window-sceneare the multi-API patterns for this domain. Deeper corpus evidence (sw-02/sw-03): for any Settings-scene finding,bash ${CLAUDE_PLUGIN_ROOT}/scripts/swiftui-ctx settings(+swiftui-ctx recipe settings-screen) gives the real Settings-Formvocab β across 1,157 repos the 8,579 catalogued screens lead with Toggle (2,207)/Section (1,998)/Picker (1,678) and no Save/Cancel/Apply button in the vocab, which grounds the sw-03 β . (b) Spec β confirm via Sosumi:curl -sSL https://sosumi.ai/<apple-path>usingreferences/source-directory.mdfor the path and${CLAUDE_PLUGIN_ROOT}/references/_shared/sosumi-reference.mdfor the protocol (neverWebFetchdeveloper.apple.com). Cross-checkintroduced_macosagainstfloors-master.md. The CLI contract is${CLAUDE_PLUGIN_ROOT}/references/_shared/swiftui-ctx-reference.md. Promote with the citation or discard. Carry the UNVERIFIED items as their status withsource: verify against Xcode 26 SDK. - REPORT. Write each confirmed finding (output contract below). One finding per file, zero-padded,
ordered. Emit
cross_refon shared-seam findings (menu-item contents β menus-commands; content-frame sizing β layout-and-tables;navigationTitlemigration β navigation-toolbars). Write the run's_index.md. - FIX. Apply corrections under the fix-safety protocol
(
${CLAUDE_PLUGIN_ROOT}/references/_shared/fix-safety-protocol.md): clean-tree gate, findings-first, one conventional commit per finding citing itsrule_id, never weaken a check. Every defect here isfix_mode: flag-onlyβ leave findingsopenwith the β in## Correct. The β "Correct" is not a hand-written snippet β it is the swiftui-ctx consensus shape put in## Correct, backed by a real macOS example fetched withbash ${CLAUDE_PLUGIN_ROOT}/scripts/swiftui-ctx file <recommended.id> --smartwhose GitHub permalink (plus the Sosumidoc:) goes in## Source. For sw-06 the β is the hidden-Window+.regular-policy workaround with the runtime-test caveat, never "this is fixed." - DOUBLE-CHECK. Re-grep each touched file to confirm the tell no longer matches; record the
evidence in
## Fix applied?. Re-confirm every citation still resolves. For sw-06/sw-07 note that the fix is runtime-verified on the target OS only β reading the diff is not enough.
Confidence gating (load-bearing)
Report a finding only at 100% certainty. Anything β€ ~70% goes to VERIFY (step 5) before it can
become a finding β never emit a speculative finding. sw-09 (the id no-op) is 100% only once you have
confirmed no scene registers that string anywhere in the project. Every defect is fix_mode: flag-only β there is no auto-fix set in this domain.
Output contract
Inherits the toolkit's unified contract (full schema + body sections + frontmatter keys:
${CLAUDE_PLUGIN_ROOT}/references/_shared/finding-schema.md β do not restate it). Specialized for this
domain:
- Findings:
swiftui-audits/scenes-windows/<context>/NN-slug.md(one finding per file, zero-padded, ordered). Per-run index:swiftui-audits/scenes-windows/_index.md. domain: scenes-windows. Frontmatter is the canonical schema;fix_modeisflag-onlyfor every rule here.availabilityreads fromfloors-master.md.sourceis an Apple URL + access date (fetched via Sosumi) orverify against Xcode 26 SDK. Usecross_refper the seam note.
Starter <context> folders (file here whenβ¦):
<context> |
File a finding here when⦠|
|---|---|
stale-scene-api/ |
a stale/invented scene symbol β Preferences {}, showSettingsWindow:, @FocusedDocument, DocumentGroupLaunchScene on Mac (sw-01) |
settings-scene/ |
Preferences are in the main window, or a Settings Form has Save/Cancel buttons (sw-02, sw-03) |
menu-bar-activation/ |
a faked menu-bar app, the activation trap, or SettingsLink-in-MenuBarExtra (sw-05, sw-06, sw-07) |
auxiliary-windows/ |
a window faked with a sheet/bool, an openWindow(id:) no-op, or a missing dismiss path (sw-04, sw-09, sw-10) |
window-sizing-style/ |
a scene with no size/resizability, wrong/absent chrome style, or navigationTitle-in-Window (sw-08, sw-12, sw-13) |
app-lifecycle/ |
a WindowGroup-only App missing the @NSApplicationDelegateAdaptor lifecycle bridge (sw-11) |
New-folder rule: if a finding does not fit any existing context folder, create a new one under
swiftui-audits/scenes-windows/ with a lowercase-hyphen slug naming the sub-category, and note it in the
run's _index.md. Prefer an existing folder when the fit is reasonable; consistency across runs is a
hard requirement. Two runs over the same code produce structurally identical trees.
Go-beyond artifact (optional):
swiftui-audits/scenes-windows/_scene-graph.mdβ a table of every scene theAppdeclares (type Β·idΒ· sizing Β· style Β· gate) plus everyopenWindow/dismissWindowcall mapped to its scene (red where unmatched). Seereferences/windows-sizing-lifecycle.md.
Reference routing
| File | Open when |
|---|---|
references/scene-types-and-settings.md |
the five scene types, the Settings {} scene + its HIG hard rules, stale/invented scene names (sw-01/02/03) |
references/menu-bar-activation-trap.md |
MenuBarExtra vs NSStatusItem, the openWindow/openSettings activation trap + macOS 26 regression + workaround, SettingsLink-in-MenuBarExtra (sw-05/06/07) |
references/windows-sizing-lifecycle.md |
auxiliary windows + openWindow/dismissWindow, scene sizing/resizability/style, the id no-op, the @NSApplicationDelegateAdaptor lifecycle bridge, the scene-graph artifact (sw-04/08/09/10/11/12/13) |
references/source-directory.md |
step VERIFY β the Apple/WWDC/practitioner source map fetched via Sosumi |
lint/grep-tells.tsv + lint/ast-grep/*.yml |
step LOCATE β this skill's declarative lint rule set fed to the shared runner (tier-1 grep tells + tier-2 structural ast-grep); edit here to tune detection |
Shared toolkit references (point in, never restate):
| Shared file | For |
|---|---|
${CLAUDE_PLUGIN_ROOT}/references/_shared/floors-master.md |
every floor/availability value (the reconciled truth β SettingsLink=14, dismissWindow=14, pushWindow/DocumentGroupLaunchScene=verify-SDK) |
${CLAUDE_PLUGIN_ROOT}/references/_shared/hallucination-blacklist.md |
the canonical invented-name list (incl. @FocusedDocument β custom FocusedValues key) |
${CLAUDE_PLUGIN_ROOT}/references/_shared/macos-arm-gating.md |
the macOS-arm gating rule for any floored scene API under a <floor target |
${CLAUDE_PLUGIN_ROOT}/references/_shared/finding-schema.md |
the unified finding schema + frontmatter keys |
${CLAUDE_PLUGIN_ROOT}/references/_shared/fix-safety-protocol.md |
the 8-point fix-safety protocol (step 7) |
${CLAUDE_PLUGIN_ROOT}/references/_shared/sosumi-reference.md |
the Apple-doc spec fetch protocol (step 5 VERIFY) |
${CLAUDE_PLUGIN_ROOT}/references/_shared/swiftui-ctx-reference.md |
the practice-corpus CLI contract β lookup/deprecated/recipe/file --smart for the consensus shape + permalinked example (steps 5 VERIFY Β· 7 FIX) |
${CLAUDE_PLUGIN_ROOT}/references/_shared/cross-ref-graph.md |
seam ownership + cross_ref targets (the MenuBarExtra-contents and window-sizing tiebreakers) |
Detection accelerator
bash ${CLAUDE_PLUGIN_ROOT}/scripts/swiftui-lint.sh --skill audit-swiftui-scenes-windows --dir <files-or-dir> [--json out.json] [--sarif out.sarif] β the toolkit's one shared hybrid lint engine, fed this
skill's declarative rules: tier-1 grep tells (lint/grep-tells.tsv,
sw-01/02/03/04/05/08/10/11/12/13) + tier-2 ast-grep structural rules (lint/ast-grep/*.yml β sw-06
activation-trap-inside-MenuBarExtra and sw-07 SettingsLink-inside-MenuBarExtra, both kind-anchored
containment rules grep cannot express). sw-09 (the id no-op) is deliberately not a lint rule β it
needs the project-wide scene-id β open-call cross-reference only the agent can build in READ (step 3);
the grep tell only surfaces the openWindow(id: call sites to cross-check. The runner runs a per-file
parse probe (surfaces "did not fully parse" so a structural miss can't look clean), emits unified
JSON + SARIF, exits 2 on any hard-fail (sw-01/06) for a CI gate, and degrades to grep-only with
a notice if ast-grep is unreachable (npx --package @ast-grep/cli ast-grep; faster: brew install ast-grep). It only LOCATES β always READ each hit in full before reporting (step 3). The thin
scripts/scenes-lint.sh is a pointer to this runner. Engine + rule-file format + JSON/SARIF shape +
safety rails: ${CLAUDE_PLUGIN_ROOT}/references/_shared/lint-architecture.md.
Install
Add Audit Swiftui Scenes Windows to your client. Pick the one you use.
npx skills add yigitkonur/plugin-swiftuiInstalls every skill in the repository, then prompts for which to keep.
/plugin marketplace add yigitkonur/plugin-swiftuiAdds the repository as a plugin marketplace; install individual plugins with `/plugin install`.
git clone https://github.com/yigitkonur/plugin-swiftui
cp -r skills/audit-swiftui-scenes-windows ~/.claude/skills/A skill is a plain directory. Copy it into `.claude/skills/` in a project or in your home directory.
Score
76 / 100
Good