NOASSERTIONupdated 1mo ago
Let be the absolute plugin directory two levels above this SKILL.md. Resolve that path before running commands or opening shared references. When these instructions say swiftui-ctx, invoke /scripts/swiftui-ctx; do not assume the command is on PATH.
What can you do with Audit Swiftui Appkit Interop?
name: audit-swiftui-appkit-interop description: Audit macOS SwiftUI appkit interop for correctness, current APIs, and production conventions. Use for that domain or as part of a full audit.
Bundled resource root
Let <swiftui-plugin-root> be the absolute plugin directory two levels above this SKILL.md. Resolve that path before running commands or opening shared references. When these instructions say swiftui-ctx, invoke <swiftui-plugin-root>/scripts/swiftui-ctx; do not assume the command is on PATH.
Audit SwiftUI AppKit Interop
AUDIT-ONLY · macOS-only · SwiftUI-only. Run this on a finished or in-progress macOS SwiftUI
project to detect — and where certain, flag with the canonical ✅ — every way a justified AppKit
bridge is wired wrong: a representable that compiles but never reflects state, a dead delegate
round-trip, a misunderstood first-responder chain, the wrong bridge protocol, a missing reverse bridge,
a Swift-6 race at the Coordinator boundary, in-window vibrancy faked as a native sidebar, and scene
chrome that silently does not bridge under NSHostingView. Findings are written to disk in the
toolkit's unified schema. This is never a from-scratch bridge generator.
This is the BRIDGE-CORRECTNESS safety net: the HOW of a justified escape hatch. The macOS responder
chain is not iOS's becomeFirstResponder()/@FocusState-covers-everything model — none of those
rules transfer. Default posture stays in SwiftUI; bridge only the one control that needs it.
Boundary / seam note (stay in lane)
- WHETHER a bridge should exist at all is
audit-swiftui-appkit-overuse's call, not this skill's. The two are a bidirectional handshake: overuse = whether, interop = how. Every finding here emits across_ref: appkit-overuseso the consolidated pass can confirm the bridge was justified before we grade its wiring (cross-ref-graph.md). - The
@Sendable/main-actor race (interop-06) is owned here for the bridge boundary butcross_ref: concurrency-safety— that skill owns the isolation model in depth (useswift_era+isolation_kind). - The
NSVisualEffectView-vs-material decision (interop-07) is shared withaudit-swiftui-appearance-color(vibrancy/material craft). Flag the in-window-flat sidebar here withcross_ref: appearance-color; defer broader color/material craft there. NSGlassEffectView(AppKit Liquid Glass) is out of scope — note in one line, do not audit here.
The bridge rules (non-negotiable)
- A representable has two halves.
makeNSView/makeNSViewControllerruns once;updateNSView/updateNSViewControllerruns on every state change. Omit the update half → the AppKit view silently goes stale. Guard the write (if nsView.stringValue != text) or you reset the cursor. - AppKit → SwiftUI flows through the Coordinator. A
@Bindingwith nomakeCoordinator()+ no.delegate = context.coordinatoris a dead direction — edits never reach the bound value. - macOS first-responder is window-scoped and explicit. A custom
NSViewis unfocusable unless it returnstruefromacceptsFirstResponder; you activate it viawindow.makeFirstResponder(_:).@FocusStatecovers SwiftUI-native controls only. - Match the bridge surface to the shape. Controller-shaped AppKit (
NSSplitViewController, editor, scroll/rulerNSTextView) →NSViewControllerRepresentable; a bareNSView→NSViewRepresentable; SwiftUI inside AppKit →NSHostingController/NSHostingView.
Defect index (interop-01 … interop-10)
id · tell · severity · fix · open reference. hard-fail = build break / never-correct;
warning = compiles but broken/non-native; advisory = judgment / craft. Every fix in this domain
needs human context → fix_mode: flag-only across the board (show the ✅; the dev applies it).
| id | One-line tell | Sev | Fix | Reference |
|---|---|---|---|---|
| interop-01 | : NSViewRepresentable/…ControllerRepresentable with no updateNSView/updateNSViewController (state staleness) |
warning | flag | representable-lifecycle.md |
| interop-02 | a representable with a @Binding but no makeCoordinator() / no .delegate = context.coordinator (dead AppKit→SwiftUI) |
warning | flag | representable-lifecycle.md |
| interop-03 | becomeFirstResponder() on a SwiftUI value, or a custom NSView expected to focus with no override var acceptsFirstResponder |
warning | flag | first-responder-focus.md |
| interop-04 | controller-shaped AppKit (…Controller/…VC) flattened to a bare NSView via vc.view (wrong protocol) |
warning | flag | representable-lifecycle.md |
| interop-05 | SwiftUI hand-instantiated in AppKit / addSubview of a View with no NSHostingController/NSHostingView |
warning | flag | reverse-bridge-hosting.md |
| interop-06 | a @Sendable closure (or DispatchQueue.main.async/Task.detached) at the Coordinator boundary reading self.parent.…/main-actor state |
warning | flag | bridge-concurrency.md |
| interop-07 | .ultraThinMaterial as a "native" sidebar/panel — composites in-window, never behind-window |
advisory | flag | vibrancy-material.md |
| interop-08 | .searchable/.toolbar/.navigationTitle on a view installed via NSHostingView (no/incomplete sceneBridgingOptions) |
warning | flag | reverse-bridge-hosting.md |
| interop-09 | observers / KVO / Timer added in makeNSView with no static func dismantleNSView (leak across identity) |
warning | flag | representable-lifecycle.md |
| interop-10 | a newer bridge surface (NSHostingMenu, NSAnimationContext.animate, sceneBridgingOptions, NSHostingSizingOptions) ungated under its floor |
warning | flag | newer-bridge-surfaces.md |
Two claims are UNVERIFIED on a fresh Xcode 26 target — carry as the noted severity but cite source: verify against Xcode 26 SDK, never assert as fact: that Default Actor Isolation = Main Actor
(-default-isolation MainActor) is off (interop-06 fires only when it is off — it is opt-in, not the
language default); that @concurrent/-default-isolation are Swift-6.2+ toolchain-gated.
The real API, at a glance
Real (exist on macOS): NSViewRepresentable / NSViewControllerRepresentable / NSHostingController
/ NSHostingView (makeNSView/updateNSView/makeCoordinator/dismantleNSView), acceptsFirstResponder,
window.makeFirstResponder(_:), NSVisualEffectView (.behindWindow blending), sceneBridgingOptions
(NSHostingSceneBridgingOptions), NSHostingSizingOptions, sizeThatFits(_:nsView:context:),
NSHostingMenu, NSAnimationContext.animate(_:changes:completion:). @FocusState covers SwiftUI-native
controls only — it does NOT drive arbitrary AppKit first-responder behaviour; never claim it does.
Does NOT exist / wrong direction: a public "make this arbitrary SwiftUI view first responder" call;
becomeFirstResponder() on a SwiftUI value; embedding MySwiftUIView() as an NSView subview directly.
.searchable has NO scene bridge — it never renders under a bare NSWindow + NSHostingView on any macOS.
Floors (13/14/14.4/15) and signatures are the reconciled truth in
<swiftui-plugin-root>/references/_shared/floors-master.md and references/newer-bridge-surfaces.md —
read, never restate. The canonical invented-name list is
<swiftui-plugin-root>/references/_shared/hallucination-blacklist.md.
Grounded ✅ — the canonical Correct (real code, not a placeholder)
The reverse bridge (interop-05) is the most-flagged shape, so its ✅ is anchored to live corpus evidence
rather than a hand-written snippet. bash <swiftui-plugin-root>/scripts/swiftui-ctx lookup NSHostingController --json returns consensus: [{ shape: "(rootView)", pct: 100 }],
introduced_macos: "10.15", and a recommended example with author_authority: 28561:
// ✅ the 100%-consensus reverse-bridge shape — SwiftUI hosted through the AppKit bridge type
self.contentViewController = NSHostingController(rootView: contentView) // macOS 10.15+
Source (real permalink + Apple spec, both verified 2026-06-07):
- example: jordanbaird/Ice (28.5k★) — https://github.com/jordanbaird/Ice/blob/11edd39115f3f43a83ae114b5348df6a0e1741cf/Ice/MenuBar/Appearance/MenuBarAppearanceEditor/MenuBarAppearanceEditorPanel.swift#L105 (fetch the enclosing body with
swiftui-ctx file ex_ff382027c2 --smart) - Apple doc (via Sosumi):
doc: https://sosumi.ai/documentation/swiftui/nshostingcontroller
Every other domain finding follows the same FIX discipline (step 7): the ✅ is the swiftui-ctx
consensus shape backed by a permalinked example, never an invented snippet.
The 8-step audit workflow (execute verbatim)
- ORIENT.
tree/findthe SwiftUI sources. Read the deployment target (project.pbxprojMACOSX_DEPLOYMENT_TARGET, orPackage.swiftplatforms:) and the Swift language mode /SWIFT_STRICT_CONCURRENCY/-default-isolationbuild setting. Both are load-bearing: interop-10 fires only when a surface's floor is above the target; interop-06 fires only under the Swift-6 language mode with Main-Actor isolation off. Record both. - LOCATE. Run the shared hybrid lint runner:
bash <swiftui-plugin-root>/scripts/swiftui-lint.sh --skill audit-swiftui-appkit-interop --dir <sources> --json /tmp/interop.json --sarif /tmp/interop.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 missing-updateNSView, missing-makeCoordinator, and controller-as-bare-view rules grep cannot express), plus a per-file parse probe, emitting 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:<swiftui-plugin-root>/references/_shared/lint-architecture.md. - READ. Open every located file in full — never pattern-match-and-patch blind. A representable's
two halves, the Coordinator's delegate wiring, gate scope, and
dismantleNSViewpresence are spread across the type and invisible to grep. Build a per-file inventory: each representable + itsmake/update/makeCoordinator/dismantlemembers + delegate wiring + focus opt-in. - DETECT. Apply the index. Assign each candidate a confidence; report a finding only at 100%
certainty (a representable that genuinely lacks
updateNSView; a@Bindingwith no Coordinator anywhere; abecomeFirstResponder()on a SwiftUI value). - VERIFY. For anything ≤ ~70% confidence (a member you're unsure is required, a floor you can't
place, a behaviour claim), run both evidence sources. (a) Practice —
bash <swiftui-plugin-root>/scripts/swiftui-ctx lookup <api> --json(andswiftui-ctx deprecated <api>for a currency rule): read itsconsensus(the canonical shape),recommendedpermalink,introduced_macos, andco_occurs_with; annsview-bridgerecipe redirect hands the real template; alookupexit 3 corroborates a hallucination. (b) Spec — confirm via Sosumi:curl -sSL https://sosumi.ai/<apple-path>usingreferences/source-directory.mdfor the path and<swiftui-plugin-root>/references/_shared/sosumi-reference.mdfor the protocol (neverWebFetchdeveloper.apple.com). Cross-checkintroduced_macosagainstfloors-master.md. The CLI contract is<swiftui-plugin-root>/references/_shared/swiftui-ctx-reference.md. Promote with the citation or discard. Deeper corpus evidence (this domain): when you doubt a bridge shape is real or want the make/update/ Coordinator/dismantle layout from a shipping app, run the bridges command —bash <swiftui-plugin-root>/scripts/swiftui-ctx bridges NSViewControllerRepresentable(stable envelope +next_actions) yields real bridges + permalinks (corpus: 4,698 bridges / 957 repos — 2,999NSViewRepresentable+ 143NSViewControllerRepresentable, e.g. AuroraEditorFindNavigatorResultList);swiftui-ctx recipe nsview-bridgehands the canonical template (interop-01/02/04/09). - REPORT. Write each confirmed finding (output contract below). One finding per file, zero-padded,
ordered. Emit
cross_refper the seam note. Write the run's_index.md. - FIX. This domain is
fix_mode: flag-onlyend-to-end (every fix needs human bridge context) — leave findingsopenwith the ✅ in## Correct, under the fix-safety protocol (<swiftui-plugin-root>/references/_shared/fix-safety-protocol.md: clean-tree gate, never weaken a check). The ✅ "Correct" is not a hand-written snippet — it is the swiftui-ctx consensus shape backed by a real macOS example fetched withbash <swiftui-plugin-root>/scripts/swiftui-ctx file <recommended.id> --smartwhose GitHub permalink (plus the Sosumidoc:) goes in## Source. (e.g. the reverse bridge:lookup NSHostingControllergivesconsensus (rootView) 100%+ a permalinkedNSHostingController(rootView:)from a 28k-star app.) - DOUBLE-CHECK. Re-read each flagged type to confirm the missing member is still missing (no false positive from a member declared far away) and the ✅ still compiles against the recorded floor. Re-confirm every citation still resolves at its recorded floor.
Confidence gating (load-bearing)
Report a finding only at 100% certainty. Anything ≤ ~70% goes to VERIFY (step 5) first — never emit a
speculative finding (e.g. don't flag a "missing updateNSView" until you've read the whole type). All
findings are fix_mode: flag-only; show the ✅, the dev applies it.
Output contract
Inherits the toolkit's unified contract (full schema + body sections + frontmatter keys:
<swiftui-plugin-root>/references/_shared/finding-schema.md — do not restate it). Specialized:
- Findings:
swiftui-audits/appkit-interop/<context>/NN-slug.md(one finding per file, zero-padded, ordered). Per-run index:swiftui-audits/appkit-interop/_index.md. domain: appkit-interop.fix_mode: flag-onlyfor all.availabilityreads fromfloors-master.md.sourceis an Apple URL + access date (fetched via Sosumi) orverify against Xcode 26 SDK. Emitcross_refper the seam note. interop-06 may addswift_era+isolation_kind(concurrency-safety's catalogued fields).
Starter <context> folders (file here when…):
<context> |
File a finding here when… |
|---|---|
representable-lifecycle/ |
a missing updateNSView, dead Coordinator, controller-as-bare-view, or missing dismantleNSView (interop-01/02/04/09) |
first-responder/ |
first-responder misuse — becomeFirstResponder() on a value or a missing acceptsFirstResponder (interop-03) |
reverse-bridge/ |
SwiftUI-in-AppKit hosting gaps or scene-chrome that doesn't bridge under NSHostingView (interop-05/08) |
bridge-concurrency/ |
a Swift-6 @Sendable/main-actor race at the Coordinator boundary (interop-06) |
vibrancy-material/ |
in-window .ultraThinMaterial faking a behind-window native sidebar (interop-07) |
availability-gating/ |
a newer bridge surface ungated under its macOS floor (interop-10) |
New-folder rule: if a finding does not fit any existing context folder, create a new one under
swiftui-audits/appkit-interop/ with a lowercase-hyphen slug, 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.
Reference routing
| File | Open when |
|---|---|
references/representable-lifecycle.md |
the two halves, Coordinator/delegate round-trip, controller-vs-view protocol choice, dismantleNSView cleanup (interop-01/02/04/09) |
references/first-responder-focus.md |
the macOS responder chain, acceptsFirstResponder, window.makeFirstResponder, @FocusState limits (interop-03) |
references/reverse-bridge-hosting.md |
NSHostingController/NSHostingView, sceneBridgingOptions, why .searchable never bridges (interop-05/08) |
references/bridge-concurrency.md |
the Swift-6 @Sendable/main-actor error at the Coordinator boundary + the isolation fixes (interop-06) |
references/vibrancy-material.md |
behind-window vibrancy vs in-window material, the NSVisualEffectView bridge (interop-07) |
references/newer-bridge-surfaces.md |
the macOS 13/14/14.4/15 bridge surfaces + their floors and gating (interop-10) |
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 |
|---|---|
<swiftui-plugin-root>/references/_shared/floors-master.md |
every floor/availability value (the reconciled truth) |
<swiftui-plugin-root>/references/_shared/hallucination-blacklist.md |
the canonical invented-name list |
<swiftui-plugin-root>/references/_shared/macos-arm-gating.md |
the macOS-arm gating rule + wrong-arm failure (interop-10 gates) |
<swiftui-plugin-root>/references/_shared/finding-schema.md |
the unified finding schema + frontmatter keys (incl. swift_era/isolation_kind) |
<swiftui-plugin-root>/references/_shared/fix-safety-protocol.md |
the fix-safety protocol (step 7) |
<swiftui-plugin-root>/references/_shared/sosumi-reference.md |
the Apple-doc spec fetch protocol (step 5 VERIFY) |
<swiftui-plugin-root>/references/_shared/swiftui-ctx-reference.md |
the practice-corpus CLI contract — lookup/recipe/file --smart for the consensus shape + permalinked example (steps 5 VERIFY · 7 FIX) |
<swiftui-plugin-root>/references/_shared/cross-ref-graph.md |
seam ownership + cross_ref targets (appkit-overuse · concurrency-safety · appearance-color) |
Detection accelerator
bash <swiftui-plugin-root>/scripts/swiftui-lint.sh --skill audit-swiftui-appkit-interop --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,
interop-03/05/06/07/08/09/10) + tier-2 ast-grep structural rules (lint/ast-grep/*.yml — interop-01
representable-missing-updateNSView, interop-02 representable-missing-makeCoordinator, interop-04
controller-flattened-to-view) that grep cannot express. It runs a per-file parse probe, emits unified
JSON + SARIF, emits warnings/advisories only (no hard-fail tells — nothing blocks the 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/interop-lint.sh
forwards to this runner. Engine + rule-file format + JSON/SARIF shape + safety rails:
<swiftui-plugin-root>/references/_shared/lint-architecture.md.
Install
Add Audit Swiftui Appkit Interop 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 plugins/swiftui/skills/audit-swiftui-appkit-interop ~/.claude/skills/A skill is a plain directory. Copy it into `.claude/skills/` in a project or in your home directory.
Score
80 / 100
Excellent