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 Pointer Gestures?
name: audit-swiftui-pointer-gestures description: Audit macOS SwiftUI pointer gestures 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 Pointer & Gestures
AUDIT-ONLY Β· macOS-only Β· SwiftUI-only. Run this on a finished or in-progress macOS SwiftUI
project to detect β and where certain, fix β every way the pointer-affordance and gesture layer goes
wrong on a Mac: views that give no hover feedback, missing cursor shapes, binary .onHover where the
live pointer position is needed, missing right-click menus, deprecated pinch/rotate gestures, gestures
with no live @GestureState, mis-composed gestures, and ungated pointer modifiers. Findings are written
to disk in the toolkit's unified schema; certain mechanical defects are fixed under the fix-safety
protocol. This is never a from-scratch gesture generator.
The Mac is pointer-driven, not touch β it has a cursor, a right mouse button, and a hover state.
iOS-trained corpora have almost no .onHover, pointerStyle, or right-click .contextMenu code, so AI
ships views that compile and look plausible but read as "an iPad app in a window." Be suspicious wherever
a custom interactive view has no pointer affordance.
Boundary / seam note (stay in lane)
.helptooltips,.focusable()/@FocusStatekeyboard focus,Form/.formStyle, and control density (.controlSize/.buttonStyle/.pickerStyle) belong toaudit-swiftui-controls-forms. They are pointer-adjacent but are that skill's. Note them in one line andcross_refβ do not own them here.Transferabledrag payloads,dropDestination,fileImporter, security-scoped consent belong toaudit-swiftui-sandbox-files. This skill owns the gesture mechanics of aDragGesture; the dropped/transferred file's correctness is sandbox-files'.- Gesture-driven animation timing (
withAnimationcoupled to a gesture,.repeatForever) belongs toaudit-swiftui-animation-motion. This skill owns the gesture wiring; the motion is theirs. .contextMenuaction semantics +keyboardShortcutlean onaudit-swiftui-menus-commands; this skill flags a missing right-click menu where one belongs, thencross_refs menus-commands.- The deprecation flag on
MagnificationGesture/RotationGestureis owned byaudit-swiftui-api-currency(the blanket currency sweep); this skill owns the replacement mechanics (theMagnifyGesture/RotateGesturerewrite + its@GestureState). Emitcross_ref: api-currencyon pg-08/pg-09. - The blanket "is every OS-floored API gated" sweep belongs to
audit-swiftui-availability-gating; this skill owns pointer-modifier gating in depth (pointerStyle=15, onContinuousHover=14) and defers the rest there.
The three non-negotiable pointer rules
- A custom interactive view must answer the pointer. A row/card/handle with no
.onHover(and, for a draggable/resizable affordance, nopointerStyle) is dead on the Mac β it has a cursor to respond to. - Right-click is a primary Mac interaction. Actions on a row/item belong in a
.contextMenu, not only as on-screen buttons or a touch swipe. The same modifier fires via long-press on iOS β the right-click idiom is the Mac's. - A continuous gesture needs live state. A pinch/drag/rotate must surface its in-flight value through
@GestureState(auto-resets when the gesture ends) or a committed@State; reading nothing mid-gesture makes the interaction feel frozen.
The affordance test: remove the pointer modifier β did the view lose its cursor signal or hover
feedback? Then it was load-bearing (pointer affordance required). Full reasoning + the affordance-map
artifact: references/pointer-affordances.md.
Defect index (pg-01 β¦ pg-12)
id Β· tell Β· severity Β· fix Β· open reference. Severities: hard-fail (build break / never-correct),
warning (compiles but non-native), advisory (judgment / perf). auto = mechanical single-answer
fix; flag = show the β
, dev applies.
| id | One-line tell | Sev | Fix | Reference |
|---|---|---|---|---|
| pg-01 | PointerStyle.grabbing / .pointerStyle(.grabbing) β invented case |
hard-fail | auto | pointer-affordances.md |
| pg-02 | custom interactive row/card/handle with no .onHover (pointer feedback dead) |
warning | flag | pointer-affordances.md |
| pg-03 | draggable/resizable affordance with no pointerStyle (no cursor shape) |
warning | flag | pointer-affordances.md |
| pg-04 | .onHover { β¦ } reading only enter/exit where the live CGPoint is needed β onContinuousHover |
advisory | flag | pointer-affordances.md |
| pg-05 | row/item view with action buttons but no right-click .contextMenu |
warning | flag | pointer-affordances.md |
| pg-06 | .swipeActions as the only way to act on a row (touch idiom) β add .contextMenu |
advisory | flag | pointer-affordances.md |
| pg-07 | pointerStyle(macOS 15) / onContinuousHover(macOS 14) ungated under a lower floor |
warning | flag | gesture-availability.md |
| pg-08 | MagnificationGesture (deprecated 26.5) β MagnifyGesture (macOS 14) |
warning | flag | gestures-and-state.md |
| pg-09 | RotationGesture (deprecated 26.5) β RotateGesture (macOS 14) |
warning | flag | gestures-and-state.md |
| pg-10 | continuous DragGesture/MagnifyGesture/RotateGesture with no @GestureState (no live value) |
warning | flag | gestures-and-state.md |
| pg-11 | .gesture( on a control that also has a built-in gesture β use .simultaneousGesture / .highPriorityGesture |
advisory | flag | gestures-and-state.md |
| pg-12 | #available(iOS β¦) gating a pointer modifier in a macOS target (wrong arm) |
hard-fail | auto | gesture-availability.md |
Two claims are UNVERIFIED β carry as advisory with the flag, never assert as fact (each is flagged
in its reference + becomes source: verify against Xcode 26 SDK): pg-04 (whether a given site truly needs
the continuous coordinate vs. a binary hover β judgment, not mechanics); the macOS gesture-vs-built-in
priority resolution for pg-11 (verify the specific control's built-in gesture against the Xcode 26 SDK).
The real API, at a glance
Real (exist on macOS): onHover(perform:) (macOS 10.15+), onContinuousHover(coordinateSpace:perform:)
(macOS 14.0+; phases .active(CGPoint) / .ended), pointerStyle(_:) + PointerStyle (macOS 15.0+,
no iOS arm; cases .grabActive / .grabIdle / .link / .zoomIn / .zoomOut / .columnResize / .rowResize /
.frameResize(position:directions:)), DragGesture (macOS 10.15+), MagnifyGesture / RotateGesture
(macOS 14.0+), SpatialTapGesture (macOS 13.0+), @GestureState, .gesture / .simultaneousGesture /
.highPriorityGesture, .contextMenu(menuItems:) (macOS 10.15+, deprecated β prefer contextMenu(menuItems:preview:) macOS 13.0+). pointerStyle is macOS + visionOS only (no iOS arm); onContinuousHover is cross-platform (iOS 17.0+, macOS 14.0+) β never flag either as invented.
Stale / invented (never use): PointerStyle.grabbing (no such case β .grabActive / .grabIdle).
Real-but-deprecated (26.5): MagnificationGesture β MagnifyGesture; RotationGesture β
RotateGesture.
Grounded β
β the pointer-feedback shape, from real shipping code (not a placeholder). The pg-02
consensus is .onHover { β¦ } (96% of 5,694 real uses across 732 macOS repos, swiftui-ctx lookup onHover).
The canonical example is sindresorhus/Gifski (8.4kβ
) β a hover-driven background highlight, the exact
shape this skill recommends:
// β
pg-02 consensus, verified in the corpus β commit-pinned, runs on the Mac
.background(Capsule().fill(.white.opacity(isHovered ? 0.2 : 0.05)))
.onHover { isHovered = $0 }
// Source: https://github.com/sindresorhus/Gifski/blob/7f873856e2acd8b52e6681dee3aec31e6cab23e4/Gifski/Components/TrimmingAVPlayer.swift#L729
// Spec: https://sosumi.ai/documentation/swiftui/view/onhover (onHover β macOS 10.15+)
Every finding's ## Source carries the live recommended permalink for its API, fetched fresh via
swiftui-ctx lookup <api> + file <recommended.id> --smart (step 7) β the block above is the worked
template, not the only citation.
Signatures, floors, and the full βββ
rewrites: references/pointer-affordances.md +
references/gestures-and-state.md. Floor values are the reconciled truth in
<swiftui-plugin-root>/references/_shared/floors-master.md and the canonical invented-name list in
<swiftui-plugin-root>/references/_shared/hallucination-blacklist.md β read, never restate them.
The 8-step audit workflow (execute verbatim)
- ORIENT.
tree/findthe SwiftUI sources. Read the deployment target (project.pbxprojMACOSX_DEPLOYMENT_TARGET, orPackage.swiftplatforms:). It is load-bearing: pg-07 fires only when the floor is below the modifier's floor (pointerStyle=15, onContinuousHover=14). Record it. - LOCATE. Run the shared hybrid lint runner:
bash <swiftui-plugin-root>/scripts/swiftui-lint.sh --skill audit-swiftui-pointer-gestures --dir <sources> --json /tmp/pg.json --sarif /tmp/pg.sarif. It runs this skill's tier-1 grep tells (lint/grep-tells.tsv) + tier-2 structural ast-grep rules (lint/ast-grep/*.ymlβ wrong-arm gate-scope and gesture-composition co-occurrence 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 β never treat a hit as a finding. Engine + rule-file format + degradation:<swiftui-plugin-root>/references/_shared/lint-architecture.md. - READ. Open every located file in full β never pattern-match-and-patch blind. Whether a custom view is interactive (so pg-02/03/05 apply), whether a gesture is continuous (so pg-10 applies), gate scope, and gesture composition are invisible to grep. Build a per-file inventory: each interactive view + its pointer affordances (hover / cursor / right-click) + each gesture + its state + its gate.
- DETECT. Apply the index. Assign each candidate a confidence; report a finding only at 100%
certainty (e.g. a
.grabbingcase, aMagnificationGesture, an ungatedpointerStyleunder a <15 floor, aniOSgate arm). - VERIFY. For anything β€ ~70% confidence (a symbol you're unsure exists, a floor you can't place, a
behavior claim), run both evidence sources. (a) Practice β
bash <swiftui-plugin-root>/scripts/swiftui-ctx lookup <api> --json(andswiftui-ctx deprecated <api>for a currency/deprecation rule β pg-08/pg-09): read itsconsensus(the canonical shape),deprecated+replacement/migrate_to,recommendedpermalink,introduced_macos, andco_occurs_with; alookupexit 3 (not-found, with a did-you-meansuggestion) corroborates a hallucination finding (pg-01) β no shipping Mac app uses the symbol. (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.mdand the Sosumidoc:floor. The CLI contract is<swiftui-plugin-root>/references/_shared/swiftui-ctx-reference.md. Promote with the citation or discard. Carry the two UNVERIFIED items asadvisorywithsource: 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 (pg-08/pg-09 βapi-currency; a missing-.contextMenuwhose actions need shortcuts βmenus-commands; aDragGesturecarrying aTransferablepayload βsandbox-files). Write the run's_index.md. - FIX. Apply corrections under the fix-safety protocol
(
<swiftui-plugin-root>/references/_shared/fix-safety-protocol.md): clean-tree gate, findings-first, onlyfix_mode: auto(pg-01, pg-12), one conventional commit per finding citing itsrule_id, never weaken a check. 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 <swiftui-plugin-root>/scripts/swiftui-ctx file <recommended.id> --smartwhose GitHub permalink (plus the Sosumidoc:) goes in## Sourceas the canonical example. Leaveflag-onlyfindingsopenwith that β in## Correct. - DOUBLE-CHECK. Re-grep each fixed file to confirm the tell no longer matches; record the evidence in
## Fix applied?. Re-confirm every citation still resolves and still says the expected floor. If a fix introduced a new tell (e.g. apointerStyleyou added now needs a#available(macOS 15, *)gate), loop that file back to DETECT.
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. pg-02/03/05 hinge on the view being interactive: a static
label is not a defect for lacking hover. Auto-fix only the mechanical set (pg-01 stale-case rename, pg-12
wrong-arm rewrite); everything else is fix_mode: flag-only.
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 for this
domain:
- Findings:
swiftui-audits/pointer-gestures/<context>/NN-slug.md(one finding per file, zero-padded, ordered). Per-run index:swiftui-audits/pointer-gestures/_index.md. domain: pointer-gestures. Frontmatter is the canonical schema;fix_modeisautofor pg-01/pg-12, elseflag-only.availabilityreads fromfloors-master.md.sourceis an Apple URL + access date (fetched via Sosumi) orverify against Xcode 26 SDK. Emitcross_refper the seam note (step 6).
Starter <context> folders (file here whenβ¦):
<context> |
File a finding here when⦠|
|---|---|
stale-api/ |
an invented/stale case name on a Mac target (pg-01) |
hover-affordance/ |
a custom interactive view gives no hover/cursor feedback, or binary hover where the live coordinate is needed (pg-02, pg-03, pg-04) |
context-menu/ |
a row/item has actions but no right-click menu, or only a touch swipe to act (pg-05, pg-06) |
gesture-currency/ |
a deprecated pinch/rotate gesture needs its MagnifyGesture/RotateGesture rewrite (pg-08, pg-09) |
gesture-state/ |
a continuous gesture has no live @GestureState, or composition is wrong (pg-10, pg-11) |
availability-gating/ |
a pointer modifier is ungated under its floor, or gated on the iOS arm (pg-07, pg-12) |
New-folder rule: if a finding does not fit any existing context folder, create a new one under
swiftui-audits/pointer-gestures/ 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/pointer-gestures/_affordance-map.mdclassifying every custom interactive view ashas-hover/has-cursor/has-right-clickwith an affordance-coverage score β seereferences/pointer-affordances.md.
Reference routing
| File | Open when |
|---|---|
references/pointer-affordances.md |
hover/cursor/right-click affordances β the .onHover/pointerStyle/onContinuousHover/.contextMenu rules, the affordance test + map (pg-01/02/03/04/05/06) |
references/gestures-and-state.md |
gesture currency, live @GestureState, and gesture composition β the deprecated-rename mechanics + .gesture vs .simultaneousGesture/.highPriorityGesture (pg-08/09/10/11) |
references/gesture-availability.md |
pointer-modifier gating depth, the wrong-arm trap, the pre-floor fallback choice (pg-07/12) |
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/stale-name list (.grabbing; deprecated gesture renames) |
<swiftui-plugin-root>/references/_shared/macos-arm-gating.md |
the macOS-arm gating rule + wrong-arm failure (pg-12) |
<swiftui-plugin-root>/references/_shared/finding-schema.md |
the unified finding schema + frontmatter keys |
<swiftui-plugin-root>/references/_shared/fix-safety-protocol.md |
the 8-point 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/deprecated/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 |
Detection accelerator
bash <swiftui-plugin-root>/scripts/swiftui-lint.sh --skill audit-swiftui-pointer-gestures --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, pg-01/02/03/04/05/06/07/08/09/10/11) +
tier-2 ast-grep structural rules (lint/ast-grep/*.yml β pg-12 wrong-arm gate-scope and pg-11
gesture-composition co-occurrence) that grep cannot express. It 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 (pg-01/pg-12) 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/pg-lint.sh is a pointer to this
runner. Engine + rule-file format + JSON/SARIF shape + safety rails:
<swiftui-plugin-root>/references/_shared/lint-architecture.md.
Install
Add Audit Swiftui Pointer Gestures 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-pointer-gestures ~/.claude/skills/A skill is a plain directory. Copy it into `.claude/skills/` in a project or in your home directory.
Score
75 / 100
Good