@mmerterden/multi-agent-pipeline 20.17.1 → 20.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/docs/facts.json +6 -6
  3. package/install/_mcp-register.mjs +1 -1
  4. package/install/_xcode-skills.mjs +263 -0
  5. package/install/catalog-history.json +1 -1
  6. package/install/claude.mjs +23 -0
  7. package/install/codex.mjs +11 -0
  8. package/install/copilot.mjs +9 -0
  9. package/manifest.json +85 -45
  10. package/package.json +1 -1
  11. package/pipeline/commands/multi-agent/stack/SKILL.md +2 -0
  12. package/pipeline/lib/xcode-plugin.mjs +131 -0
  13. package/pipeline/multi-agent-refs/features/doctor.md +10 -0
  14. package/pipeline/scripts/doctor.mjs +38 -0
  15. package/pipeline/scripts/uninstall.mjs +57 -1
  16. package/pipeline/skills/.skill-manifest.json +33 -13
  17. package/pipeline/skills/.skills-index.json +66 -11
  18. package/pipeline/skills/shared/README.md +18 -13
  19. package/pipeline/skills/shared/external/NOTICE-avdlee-swiftui-agent-skill.md +3 -1
  20. package/pipeline/skills/shared/external/NOTICE-dimillian-skills.md +2 -1
  21. package/pipeline/skills/shared/external/hig-app-icons/SKILL.md +159 -0
  22. package/pipeline/skills/shared/external/hig-app-icons/evals/evals.json +48 -0
  23. package/pipeline/skills/shared/external/hig-app-icons/evals/trigger-eval.json +22 -0
  24. package/pipeline/skills/shared/external/hig-app-icons/references/icon-workflow.md +115 -0
  25. package/pipeline/skills/shared/external/hig-components-layout/SKILL.md +32 -4
  26. package/pipeline/skills/shared/external/hig-components-layout/evals/evals.json +47 -0
  27. package/pipeline/skills/shared/external/hig-components-layout/evals/trigger-eval.json +22 -0
  28. package/pipeline/skills/shared/external/hig-components-layout/references/bars-and-tabs.md +231 -0
  29. package/pipeline/skills/shared/external/hig-components-layout/references/containers.md +5 -30
  30. package/pipeline/skills/shared/external/hig-components-status/SKILL.md +1 -1
  31. package/pipeline/skills/shared/external/hig-foundations/SKILL.md +23 -4
  32. package/pipeline/skills/shared/external/hig-foundations/evals/evals.json +47 -0
  33. package/pipeline/skills/shared/external/hig-foundations/evals/trigger-eval.json +21 -0
  34. package/pipeline/skills/shared/external/hig-foundations/references/foundations-detail.md +34 -26
  35. package/pipeline/skills/shared/external/hig-foundations/references/type-color-spacing.md +219 -0
  36. package/pipeline/skills/shared/external/hig-inputs/SKILL.md +5 -4
  37. package/pipeline/skills/shared/external/hig-patterns/SKILL.md +19 -3
  38. package/pipeline/skills/shared/external/hig-patterns/references/onboarding-and-states.md +188 -0
  39. package/pipeline/skills/shared/external/hig-patterns/references/patterns.md +5 -11
  40. package/pipeline/skills/shared/external/hig-platforms/SKILL.md +17 -5
  41. package/pipeline/skills/shared/external/hig-platforms/references/ipad-layout.md +218 -0
  42. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +4 -1
  43. package/pipeline/skills/shared/external/ios-accessibility/references/large-content-and-appearance.md +135 -0
  44. package/pipeline/skills/shared/external/ios-design-review/SKILL.md +146 -0
  45. package/pipeline/skills/shared/external/ios-design-review/evals/evals.json +48 -0
  46. package/pipeline/skills/shared/external/ios-design-review/evals/trigger-eval.json +22 -0
  47. package/pipeline/skills/shared/external/ios-design-review/references/appearance-matrix.md +135 -0
  48. package/pipeline/skills/shared/external/ios-design-review/references/checklist.md +193 -0
  49. package/pipeline/skills/shared/external/ios-haptics/SKILL.md +191 -0
  50. package/pipeline/skills/shared/external/ios-haptics/evals/evals.json +49 -0
  51. package/pipeline/skills/shared/external/ios-haptics/evals/trigger-eval.json +22 -0
  52. package/pipeline/skills/shared/external/ios-haptics/references/core-haptics.md +215 -0
  53. package/pipeline/skills/shared/external/ios-iphone-duo/NOTICE.md +53 -0
  54. package/pipeline/skills/shared/external/ios-iphone-duo/SKILL.md +179 -0
  55. package/pipeline/skills/shared/external/ios-iphone-duo/evals/evals.json +65 -0
  56. package/pipeline/skills/shared/external/ios-iphone-duo/evals/trigger-eval.json +22 -0
  57. package/pipeline/skills/shared/external/ios-iphone-duo/references/api-map.md +212 -0
  58. package/pipeline/skills/shared/external/ios-iphone-duo/references/fixed-assumptions.md +202 -0
  59. package/pipeline/skills/shared/external/ios-iphone-duo/references/poses-and-panes.md +122 -0
  60. package/pipeline/skills/shared/external/ios-sf-symbols/SKILL.md +177 -0
  61. package/pipeline/skills/shared/external/ios-sf-symbols/evals/evals.json +49 -0
  62. package/pipeline/skills/shared/external/ios-sf-symbols/evals/trigger-eval.json +22 -0
  63. package/pipeline/skills/shared/external/ios-sf-symbols/references/effects-and-custom-symbols.md +135 -0
  64. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +38 -50
  65. package/pipeline/skills/shared/external/swiftui-animation/evals/evals.json +47 -0
  66. package/pipeline/skills/shared/external/swiftui-animation/evals/trigger-eval.json +22 -0
  67. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +7 -73
  68. package/pipeline/skills/shared/external/swiftui-animation/references/scroll-effects.md +185 -0
  69. package/pipeline/skills/shared/external/swiftui-expert-skill/SKILL.md +6 -6
  70. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-advanced.md +3 -253
  71. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-basics.md +4 -47
  72. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-transitions.md +3 -111
  73. package/pipeline/skills/shared/external/swiftui-expert-skill/references/liquid-glass.md +11 -381
  74. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +184 -224
  75. package/pipeline/skills/shared/external/swiftui-liquid-glass/evals/evals.json +47 -0
  76. package/pipeline/skills/shared/external/swiftui-liquid-glass/evals/trigger-eval.json +22 -0
  77. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +246 -33
  78. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/uikit-glass.md +227 -0
  79. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +15 -18
  80. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/components-index.md +1 -1
  81. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/haptics.md +6 -68
  82. package/pipeline/skills/skills-index.md +16 -11
  83. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +0 -786
@@ -0,0 +1,135 @@
1
+ # Appearance matrix and simulator procedure
2
+
3
+ How to put a simulator into each appearance, what to look for in it, and a
4
+ toolkit procedure that sweeps the matrix for one screen. Tool names are from
5
+ the multi-agent-toolkit MCP server (3.19.0 or later).
6
+
7
+ ## Contents
8
+
9
+ 1. Setting each appearance
10
+ 2. What breaks in each appearance
11
+ 3. Sweep procedure
12
+ 4. Example batch
13
+ 5. Limits of a simulator review
14
+
15
+ ## 1. Setting each appearance
16
+
17
+ | Appearance | Toolkit call | Notes |
18
+ |---|---|---|
19
+ | Light | `ios_set_appearance` `mode: "light"` | |
20
+ | Dark | `ios_set_appearance` `mode: "dark"` | |
21
+ | Dynamic Type | `ios_set_content_size` `size: ...` | standard steps `extra-small` to `extra-extra-extra-large`; accessibility steps `accessibility-medium` to `accessibility-extra-extra-extra-large` (AX1 to AX5) |
22
+ | Increase Contrast | `ios_set_increase_contrast` `enabled: true` | |
23
+ | Right-to-left | `ios_set_locale` `language: "ar"` or `"he"`, `bundle_id` | relaunches the app; copy must exist in that language or the screen shows the development language left-to-right |
24
+ | Reduce Transparency | none | Settings > Accessibility > Display & Text Size, set by hand |
25
+ | Reduce Motion | none | Settings > Accessibility > Motion, set by hand |
26
+ | Liquid Glass look | none | Settings > Display & Brightness > Liquid Glass, Clear or Tinted; check the current Settings wording, set by hand where the OS offers it |
27
+ | Clean status bar | `ios_status_bar` `preset: "clean"` | revert with `preset: "clear"` |
28
+
29
+ Apple notes that the Liquid Glass look cannot be changed while Reduce
30
+ Transparency or Increase Contrast is on, because those settings already take
31
+ precedence. Review the look setting with both of them off, and review each
32
+ accessibility setting on its own.
33
+
34
+ A cell set by hand goes into the report as "set by hand", so a reader knows it
35
+ was not scripted and may not be reproduced by the next run.
36
+
37
+ ## 2. What breaks in each appearance
38
+
39
+ **Dark.** Fixed colours (a grey that vanishes on near-black), separators and
40
+ borders that disappear, shadows that read as dirt, images and illustrations
41
+ with no dark variant, white logo boxes. Check elevated surfaces: sheets and
42
+ popovers use slightly lighter backgrounds in dark mode.
43
+
44
+ **Dynamic Type.** Check at least the default (`large`), the largest standard
45
+ step and AX5. Look for clipped labels in fixed-height rows and buttons,
46
+ truncation that hides meaning, overlapping text, horizontal stacks that never
47
+ switch to vertical, icons that stay tiny next to huge text, and controls pushed
48
+ off screen with no scroll.
49
+
50
+ **Right-to-left.** Back chevrons and disclosure indicators must point the
51
+ other way; progress bars and sliders run right to left; text aligns to the
52
+ trailing edge. Photos, artwork, media playback controls and numerals are not
53
+ mirrored. Hard-coded left and right paddings show up as lopsided insets.
54
+
55
+ **Increase Contrast.** Custom colours need a high-contrast variant; system
56
+ colours already have one. Thin borders and subtle fills that carried meaning
57
+ should become more visible, not stay the same.
58
+
59
+ **Reduce Transparency.** Blur and glass should resolve to opaque or nearly
60
+ opaque surfaces. Custom translucent overlays that keep their transparency are
61
+ the usual finding.
62
+
63
+ **Reduce Motion.** Parallax, zooming transitions, spinning and bouncing
64
+ decorations and auto-playing repeating symbol effects should be replaced by
65
+ fades or stop. Motion that conveys essential state may stay, toned down.
66
+
67
+ **Liquid Glass look.** On Clear, check legibility of text and symbols on
68
+ glass over the busiest content behind it. On Tinted, custom glass should gain
69
+ opacity like system glass does.
70
+
71
+ ## 3. Sweep procedure
72
+
73
+ 1. Confirm a booted simulator and the app's bundle id. Note device model and
74
+ iOS version for the report.
75
+ 2. `ios_status_bar` `preset: "clean"`.
76
+ 3. Navigate to the screen. Build each locator with `ui_locate` until it
77
+ resolves to exactly one element (`code: OK`), then act with
78
+ `ios_tap_element`. Prefer an accessibility identifier (`id`) over a label,
79
+ because labels change with locale.
80
+ 4. Baseline: `ios_screenshot` with a `path` (full-resolution PNG on disk),
81
+ `ios_get_ui_tree` with a `path`, `design_ui_geometry` `platform: "ios"`.
82
+ 5. For each chosen cell: set it, wait for the screen to settle, assert you are
83
+ still on the right screen (`expect_element` on a stable identifier), then
84
+ capture as in step 4 with the cell in the file name.
85
+ 6. Measure:
86
+ - Targets and gaps from `design_ui_geometry` boxes (points).
87
+ - Contrast by sampling text and background colours in the PNG.
88
+ - Against a design render, when one exists on disk:
89
+ `design_visual_compare` with `figma_png`, `live_png`, `out_dir`, plus
90
+ `figma_frame`, `live_screen` and `live_geometry` for inset and gap
91
+ findings in points.
92
+ 7. Reset: `ios_set_appearance` light, `ios_set_content_size` `large`,
93
+ `ios_set_increase_contrast` false, the original locale, `ios_status_bar`
94
+ `preset: "clear"`, and any setting changed by hand.
95
+
96
+ ## 4. Example batch
97
+
98
+ `agent_run_steps` runs a sweep as one call and stops on the first failed
99
+ assertion, so a missed tap cannot produce a set of captures of the wrong
100
+ screen. Paths and identifiers below are placeholders.
101
+
102
+ ```json
103
+ {
104
+ "steps": [
105
+ { "tool": "ios_status_bar", "args": { "preset": "clean" } },
106
+ { "tool": "ios_tap_element", "args": { "locator": { "id": "tab.profile" } } },
107
+ { "tool": "expect_element", "args": { "platform": "ios", "locator": { "id": "profile.header" } } },
108
+ { "tool": "ios_screenshot", "args": { "path": "/abs/captures/profile-light-large.png" } },
109
+ { "tool": "ios_set_appearance", "args": { "mode": "dark" }, "wait_ms": 800 },
110
+ { "tool": "ios_screenshot", "args": { "path": "/abs/captures/profile-dark-large.png" } },
111
+ { "tool": "ios_set_content_size", "args": { "size": "accessibility-extra-extra-extra-large" }, "wait_ms": 800 },
112
+ { "tool": "expect_element", "args": { "platform": "ios", "locator": { "id": "profile.header" } } },
113
+ { "tool": "ios_screenshot", "args": { "path": "/abs/captures/profile-dark-ax5.png" } },
114
+ { "tool": "design_ui_geometry", "args": { "platform": "ios" } },
115
+ { "tool": "ios_set_increase_contrast", "args": { "enabled": true }, "wait_ms": 800 },
116
+ { "tool": "ios_screenshot", "args": { "path": "/abs/captures/profile-dark-ax5-contrast.png" } },
117
+ { "tool": "ios_set_increase_contrast", "args": { "enabled": false } },
118
+ { "tool": "ios_set_content_size", "args": { "size": "large" } },
119
+ { "tool": "ios_set_appearance", "args": { "mode": "light" } },
120
+ { "tool": "ios_status_bar", "args": { "preset": "clear" } }
121
+ ]
122
+ }
123
+ ```
124
+
125
+ ## 5. Limits of a simulator review
126
+
127
+ Record these under Not proven rather than skipping them silently:
128
+
129
+ - Haptics do not play on a simulator.
130
+ - Display brightness, outdoor legibility and true colour rendering need a
131
+ device.
132
+ - Touch comfort and reach (one-handed use) need a device in the hand.
133
+ - The accessibility tree reports frames, not always the real hit area of a
134
+ custom control.
135
+ - States the review could not reach without seed data or a forced failure.
@@ -0,0 +1,193 @@
1
+ # Design review checklist
2
+
3
+ Questions per layer, with the threshold or HIG page behind each. Ask them of
4
+ the rendered screen; when a question can only be answered from code or a
5
+ device, note that under Not proven instead of guessing.
6
+
7
+ ## Contents
8
+
9
+ 1. Layout and safe areas
10
+ 2. Typography
11
+ 3. Colour and contrast
12
+ 4. Materials and Liquid Glass
13
+ 5. Iconography
14
+ 6. Navigation and bars
15
+ 7. Controls and touch targets
16
+ 8. Feedback and motion
17
+ 9. States
18
+ 10. Content, copy and localization
19
+ 11. Report template
20
+
21
+ ## 1. Layout and safe areas
22
+
23
+ - Is interactive and legible content inside the safe area, while backgrounds
24
+ and artwork are allowed to run past it? Content under the status bar, home
25
+ indicator or a bar is a defect; a background that stops short of the edge
26
+ usually is too.
27
+ - Are margins consistent across the screen and with neighbouring screens? Two
28
+ different leading insets on one screen read as a bug.
29
+ - Do related items group visually (proximity, shared container) and unrelated
30
+ ones separate?
31
+ - Does anything align to an invisible line it nearly hits? Near misses of a
32
+ few points look worse than an obvious offset.
33
+ - Does the layout adapt to the available width rather than a device size: no
34
+ clipped trailing content on a narrow phone, no stretched single column on a
35
+ wide one?
36
+ - Are safe area insets treated per edge? On devices with controls on one side
37
+ the insets are asymmetric; see `ios-iphone-duo`.
38
+
39
+ HIG: https://developer.apple.com/design/human-interface-guidelines/layout
40
+
41
+ ## 2. Typography
42
+
43
+ - Is text set with text styles (Large Title through Caption) so it scales with
44
+ Dynamic Type? Text that does not grow at larger settings is the most common
45
+ finding of the whole review.
46
+ - Body text: iOS default 17 pt, minimum 11 pt. Thin custom weights need larger
47
+ sizes to stay legible.
48
+ - Is hierarchy carried by size and weight rather than by colour alone?
49
+ - At the largest accessibility size, is as much useful text visible as at the
50
+ largest standard size? Truncation in a scrolling region hides content; wrap
51
+ instead.
52
+ - Do horizontal rows switch to a vertical arrangement when text gets large,
53
+ instead of squeezing labels into ellipses?
54
+
55
+ HIG: https://developer.apple.com/design/human-interface-guidelines/typography
56
+
57
+ ## 3. Colour and contrast
58
+
59
+ - Minimum contrast: 4.5:1 for text up to 17 pt, 3:1 for 18 pt and larger,
60
+ 3:1 for bold text at any size. Check in light, dark and Increase Contrast.
61
+ - Are colours semantic (label, secondary label, system background, tint) so
62
+ they adapt to dark mode and Increase Contrast? A colour that looks right in
63
+ light mode and disappears in dark mode is almost always a fixed value.
64
+ - Is meaning ever carried by colour alone (red for error, green for success)?
65
+ Add a symbol, text or shape so it survives colour blindness and grayscale.
66
+ - Is the tint used consistently for interactive elements and not for static
67
+ text?
68
+
69
+ HIG: https://developer.apple.com/design/human-interface-guidelines/color,
70
+ https://developer.apple.com/design/human-interface-guidelines/dark-mode,
71
+ https://developer.apple.com/design/human-interface-guidelines/accessibility
72
+
73
+ ## 4. Materials and Liquid Glass
74
+
75
+ - Is Liquid Glass limited to the functional layer (bars, tab bars, sidebars,
76
+ floating controls)? Glass used for content cards or app backgrounds muddles
77
+ the hierarchy; the content layer uses standard materials.
78
+ - Is the clear variant used only over visually rich media, and is there a
79
+ dimming layer (about 35% dark) when that media is bright?
80
+ - Does text on glass stay legible over the busiest content that can scroll
81
+ beneath it?
82
+ - With Reduce Transparency, and with the Liquid Glass look set to Tinted, do
83
+ custom glass surfaces become more opaque the way system ones do?
84
+ - Are nested rounded shapes concentric with their container? Mismatched corner
85
+ radii stand out next to system glass.
86
+
87
+ Implementation fixes belong to `swiftui-liquid-glass`.
88
+ HIG: https://developer.apple.com/design/human-interface-guidelines/materials
89
+
90
+ ## 5. Iconography
91
+
92
+ - Are interface icons SF Symbols or custom symbols built from the SF Symbols
93
+ template, at a weight that matches adjacent text?
94
+ - Are variants consistent: outline in toolbars and lists, fill where the
95
+ container prefers it (tab bars), one variant per meaning?
96
+ - Does every icon-only control have a text label for VoiceOver and for the
97
+ overflow menu?
98
+ - Do directional symbols flip in right-to-left?
99
+
100
+ Details: `ios-sf-symbols`. HIG:
101
+ https://developer.apple.com/design/human-interface-guidelines/sf-symbols
102
+
103
+ ## 6. Navigation and bars
104
+
105
+ - Are system navigation bars, toolbars and tab bars used, so they adopt
106
+ platform behaviour (glass, minimization, vertical placement on foldable
107
+ hardware)? A hand-built bar misses all of that.
108
+ - Is Back or Close where people look first, and the primary action (Done,
109
+ Save) in its standard position?
110
+ - Do tabs name destinations, not actions? Is the selected tab obvious in every
111
+ appearance?
112
+ - Does the title fit at large text sizes, or collapse gracefully?
113
+
114
+ HIG: https://developer.apple.com/design/human-interface-guidelines/toolbars,
115
+ https://developer.apple.com/design/human-interface-guidelines/tab-bars
116
+
117
+ ## 7. Controls and touch targets
118
+
119
+ - iOS control size: 44x44 pt by default, 28x28 pt at minimum. Measure from
120
+ `design_ui_geometry`; the visible glyph may be smaller than the hit area,
121
+ which only the code or the tree can confirm.
122
+ - Is there enough space between adjacent targets that a miss does not trigger
123
+ a neighbour?
124
+ - Are standard controls used where they exist (toggle, stepper, picker,
125
+ segmented control) instead of look-alikes that miss system behaviour?
126
+ - Are disabled controls visibly disabled, and is it clear why?
127
+
128
+ HIG: https://developer.apple.com/design/human-interface-guidelines/accessibility
129
+
130
+ ## 8. Feedback and motion
131
+
132
+ - Does every action get a visible response within a moment: highlight,
133
+ progress, result?
134
+ - Does motion explain a change (where something came from, where it went), or
135
+ only decorate?
136
+ - Under Reduce Motion, are large movements, parallax, zooms and repeating
137
+ effects replaced by fades or removed?
138
+ - Are haptics used for meaning rather than on every tap? Haptics need a real
139
+ device to judge; mark them Not proven on a simulator. Details: `ios-haptics`.
140
+
141
+ HIG: https://developer.apple.com/design/human-interface-guidelines/motion
142
+
143
+ ## 9. States
144
+
145
+ For each screen, ask whether these states exist and look intentional:
146
+
147
+ - Empty (first run and after deleting everything), with a next step.
148
+ - Loading, without layout jumps when content arrives.
149
+ - Error, in plain language, with a way to recover.
150
+ - Offline or partial data.
151
+ - Permission denied, with a path to Settings.
152
+ - Long content: long names, long translations, many items.
153
+
154
+ A state the review could not reach (no seed data, no way to force an error) is
155
+ listed under Not proven, not assumed fine.
156
+
157
+ ## 10. Content, copy and localization
158
+
159
+ - Is copy short, specific and consistent in terminology across screens?
160
+ - Are buttons labelled with verbs that say what happens?
161
+ - Is all user-facing text localized, including accessibility labels?
162
+ - In right-to-left: text aligned to the trailing edge, back and progress
163
+ flipped, numerals not mirrored, photos and artwork not mirrored.
164
+ - Do longer languages (German, Finnish) fit without truncation?
165
+
166
+ HIG: https://developer.apple.com/design/human-interface-guidelines/writing,
167
+ https://developer.apple.com/design/human-interface-guidelines/right-to-left
168
+
169
+ ## 11. Report template
170
+
171
+ ```markdown
172
+ ## Design review: <screen or flow>
173
+
174
+ Inputs: <screenshots | simulator <device, iOS version> | source | design render>
175
+ Matrix covered: <cells>; skipped: <cells and reason>; set by hand: <cells>
176
+
177
+ ### Findings
178
+
179
+ 1. [blocking] Colour and contrast, Settings > Profile, dark mode
180
+ Where: ProfileHeader.swift:42 (or: Profile screen, subtitle label, dark)
181
+ Evidence: captures/profile-dark.png, subtitle #6E6E73 on #1C1C1E, 3.1:1,
182
+ needs 4.5:1 at 15 pt
183
+ Why: secondary text is unreadable for many people in dark mode
184
+ Fix: use the secondary label colour instead of a fixed grey
185
+
186
+ ### Not proven
187
+
188
+ - Hit area of the avatar button: glyph is 30 pt, hit area unknown without the
189
+ tree or source
190
+ - Haptic on save: simulator does not play haptics
191
+ ```
192
+
193
+ The angle-bracket placeholders in the template are filled in by the reviewer.
@@ -0,0 +1,191 @@
1
+ ---
2
+ name: ios-haptics
3
+ description: "Haptic feedback on iPhone: when a haptic adds meaning, sensoryFeedback, the UIFeedbackGenerator family, Core Haptics and AHAP, settings and on-device testing. Use when adding, choosing, reviewing or debugging haptics."
4
+ metadata:
5
+ source: multi-agent-pipeline
6
+ date_added: "2026-10-08"
7
+ ---
8
+
9
+ # Haptics
10
+
11
+ A haptic is a physical confirmation of something the person did or something
12
+ that happened. Used with restraint it makes an interface feel solid; used on
13
+ every tap it becomes noise people turn off. This skill owns haptics across
14
+ SwiftUI, UIKit and Core Haptics.
15
+
16
+ ## When a haptic earns its place
17
+
18
+ Apple's guidance reduces to four questions. Ask them before adding one.
19
+
20
+ 1. **Does it confirm a cause?** The person should be able to say what made the
21
+ phone tap: a toggle flipped, a value snapped, a payment succeeded.
22
+ 2. **Does it keep its meaning?** Use system patterns for their documented
23
+ purpose (success means success). Reusing the error pattern for "level
24
+ complete" teaches people the wrong thing.
25
+ 3. **Does it match what they see and hear?** Intensity and sharpness should
26
+ match the animation; a heavy thud under a tiny fade feels broken.
27
+ 4. **Would they miss it if it were gone, without noticing it while it is on?**
28
+ That is the target. Frequent haptics become tiring.
29
+
30
+ Do not add haptics:
31
+
32
+ - to standard controls that already play them (toggles, sliders, pickers),
33
+ because the feedback doubles;
34
+ - on scroll, on every keystroke, or on passive updates nobody triggered;
35
+ - as the only signal of an outcome; pair it with a visual (and VoiceOver
36
+ announcement where relevant), because some people cannot feel it and some
37
+ devices cannot play it;
38
+ - while the camera, microphone or gyroscope is capturing, because the
39
+ vibration can disturb the capture.
40
+
41
+ Prefer short haptics for discrete events. Long continuous patterns suit games
42
+ and rarely help in apps.
43
+
44
+ ## Choosing the API
45
+
46
+ | Situation | Use |
47
+ |---|---|
48
+ | SwiftUI view reacting to a state change | `sensoryFeedback` |
49
+ | UIKit, or feedback tied to a gesture's progress | `UIFeedbackGenerator` subclasses |
50
+ | A shaped pattern, audio sync, or parameters changing while it plays | Core Haptics |
51
+
52
+ Start at the top of the table and move down only when the simpler API cannot
53
+ express the feeling.
54
+
55
+ ## SwiftUI: sensoryFeedback (iOS 17)
56
+
57
+ The modifier plays feedback when a trigger value changes, so the haptic stays
58
+ attached to the state that causes it.
59
+
60
+ ```swift
61
+ @State private var savedCount = 0
62
+
63
+ Button("Save") {
64
+ save()
65
+ savedCount += 1
66
+ }
67
+ .sensoryFeedback(.success, trigger: savedCount)
68
+ ```
69
+
70
+ Three forms:
71
+
72
+ - `sensoryFeedback(_:trigger:)`: play on every change.
73
+ - `sensoryFeedback(_:trigger:condition:)`: play when the closure, given old
74
+ and new values, returns true.
75
+ - `sensoryFeedback(trigger:_:)`: return the feedback to play, or nil, from
76
+ old and new values. This is how one trigger maps to success or error.
77
+
78
+ ```swift
79
+ .sensoryFeedback(trigger: uploadState) { _, new in
80
+ switch new {
81
+ case .finished: .success
82
+ case .failed: .error
83
+ default: nil
84
+ }
85
+ }
86
+ ```
87
+
88
+ | Feedback | Meaning |
89
+ |---|---|
90
+ | `.success`, `.warning`, `.error` | Outcome of a task |
91
+ | `.selection` | A value is changing during selection |
92
+ | `.increase`, `.decrease` | A value went up or down a step |
93
+ | `.start`, `.stop` | Something began or ended |
94
+ | `.alignment` | A dragged item lined up with a guide |
95
+ | `.levelChange` | Moving between discrete pressure levels |
96
+ | `.impact`, `.impact(weight:intensity:)`, `.impact(flexibility:intensity:)` | A physical collision or snap |
97
+ | `.pathComplete` (17.5) | A drawn path finished or was recognised |
98
+ | `.press(_:)`, `.release(_:)`, `.selection(_:)` (26) | Touch down, touch up or value change on a kind of control (`.button`, `.buttonIconOnly`, `.slider`, `.tab`, `.toggle` for press) |
99
+
100
+ ## UIKit: feedback generators
101
+
102
+ Generators are `@MainActor` and come in four kinds: `UIImpactFeedbackGenerator`,
103
+ `UISelectionFeedbackGenerator`, `UINotificationFeedbackGenerator` and, from
104
+ iOS 17.5, `UICanvasFeedbackGenerator` for drawing events (`alignmentOccurred(at:)`,
105
+ `pathCompleted(at:)`).
106
+
107
+ - Call `prepare()` shortly before a predictable haptic (a drag begins) to cut
108
+ latency. Preparing has no effect when the haptic follows immediately, and
109
+ the engine returns to idle after a few seconds, after feedback plays, or
110
+ when the generator is released.
111
+ - Keep the generator alive for the interaction instead of creating one per
112
+ event.
113
+ - From iOS 17.5, create generators attached to a view (`init(view:)`,
114
+ `init(style:view:)`) and pass the touch location (`impactOccurred(intensity:at:)`)
115
+ so the system can place the feedback for the interaction.
116
+
117
+ ```swift
118
+ @MainActor
119
+ final class SnapFeedback {
120
+ private let impact: UIImpactFeedbackGenerator
121
+
122
+ init(view: UIView) {
123
+ impact = UIImpactFeedbackGenerator(style: .medium, view: view)
124
+ }
125
+
126
+ func dragBegan() { impact.prepare() }
127
+
128
+ func snapped(at point: CGPoint) {
129
+ impact.impactOccurred(intensity: 0.8, at: point)
130
+ }
131
+ }
132
+ ```
133
+
134
+ Fire generators from the UI layer where the action happens, never from the
135
+ data layer: a background sync should not make the phone tap.
136
+
137
+ ## Core Haptics
138
+
139
+ Core Haptics (iOS 13) builds patterns from transient events (taps) and
140
+ continuous events (sustained vibration), each with intensity and sharpness,
141
+ optional audio, parameter curves over time, and dynamic parameters changed
142
+ during playback. Patterns can live in code or in AHAP files, which are JSON
143
+ and can be tuned without recompiling.
144
+
145
+ Essentials:
146
+
147
+ - Check `CHHapticEngine.capabilitiesForHardware().supportsHaptics` first;
148
+ iPad, iPod touch and Apple Vision Pro do not support haptics.
149
+ - Create the engine, set `stoppedHandler` and `resetHandler` before starting
150
+ it, `start()` it, make a player, play. After a reset, restart the engine and
151
+ recreate players.
152
+ - Stop the engine when the screen no longer plays patterns, or set
153
+ `isAutoShutdownEnabled`, to save power.
154
+ - `playsHapticsOnly` ignores audio events when the pattern should be silent.
155
+ - Load AHAP with `playPattern(from:)` or `CHHapticPattern(contentsOf:)` (16).
156
+
157
+ Full engine lifecycle, event and parameter tables, curves, dynamic parameters,
158
+ advanced players and AHAP structure are in `references/core-haptics.md`.
159
+
160
+ ## Settings, accessibility, power
161
+
162
+ - Offer an in-app switch for non-essential haptics and read it before playing.
163
+ The app must work fully with haptics off.
164
+ - The system plays haptics only where the hardware and the person's settings
165
+ allow; a missing haptic is not an error to surface.
166
+ - Haptics complement, never replace, visible and spoken feedback.
167
+ - Every haptic costs power. Avoid long continuous patterns, do not prepare
168
+ generators early, and stop Core Haptics engines when idle.
169
+
170
+ ## Testing
171
+
172
+ Haptics have to be felt. The simulator plays none, so:
173
+
174
+ - Test on a physical iPhone, in a quiet hand and on a table.
175
+ - Check each haptic fires once per cause (no doubles from a control that
176
+ already plays one plus your own call).
177
+ - Turn the in-app switch off and confirm silence; check behaviour with the
178
+ system haptics setting off.
179
+ - Run through a whole flow and count haptics. If people would notice the
180
+ count, there are too many.
181
+ - In unit tests, inject a feedback protocol and assert the calls; the engine
182
+ cannot be observed from a test.
183
+
184
+ ## References
185
+
186
+ - `references/core-haptics.md`: engine lifecycle, events, parameters, curves, AHAP, pitfalls.
187
+ - HIG Playing haptics: https://developer.apple.com/design/human-interface-guidelines/playing-haptics
188
+ - sensoryFeedback: https://developer.apple.com/documentation/swiftui/view/sensoryfeedback(_:trigger:)
189
+ - SensoryFeedback: https://developer.apple.com/documentation/swiftui/sensoryfeedback
190
+ - UIFeedbackGenerator: https://developer.apple.com/documentation/uikit/uifeedbackgenerator
191
+ - Core Haptics: https://developer.apple.com/documentation/corehaptics
@@ -0,0 +1,49 @@
1
+ {
2
+ "skill_name": "ios-haptics",
3
+ "evals": [
4
+ {
5
+ "id": 0,
6
+ "name": "too-many-haptics-review",
7
+ "prompt": "Our SwiftUI app fires UIImpactFeedbackGenerator(style: .heavy).impactOccurred() on every button tap, on every toggle change, on every scroll page, and when background sync finishes. Users complain it feels buzzy. Review this and rewrite the approach.",
8
+ "expected_output": "Cuts haptics to meaningful causes: removes them from scrolling, background sync and toggles (toggles already play system haptics), keeps a few outcome haptics. Moves SwiftUI usage to sensoryFeedback tied to a trigger value (success, error, selection), avoids creating a generator per tap, keeps feedback in the UI layer, offers an in-app switch, pairs haptics with visual feedback, and recommends testing on a device because the simulator plays no haptics.",
9
+ "files": [],
10
+ "expectations": [
11
+ "Removes haptics from scrolling and background sync",
12
+ "Notes standard toggles already play haptics, avoiding doubles",
13
+ "Uses sensoryFeedback with a trigger value in SwiftUI",
14
+ "Uses notification-style feedback (success or error) for outcomes",
15
+ "Mentions a user setting to turn haptics off or pairing with visual feedback",
16
+ "Says haptics must be tested on a physical device"
17
+ ]
18
+ },
19
+ {
20
+ "id": 1,
21
+ "name": "drag-snap-uikit",
22
+ "prompt": "In a UIKit canvas editor, objects snap to guides while dragging. We want a crisp tick at each snap with minimal latency, iOS 17.5+. What should we use?",
23
+ "expected_output": "Uses a feedback generator kept alive for the drag: UICanvasFeedbackGenerator alignmentOccurred(at:) for snapping to guides, or UIImpactFeedbackGenerator created with init(style:view:) and impactOccurred(intensity:at:) with the touch location. Calls prepare() when the drag begins, not right before firing, and fires once per snap from the UI layer.",
24
+ "files": [],
25
+ "expectations": [
26
+ "Names UICanvasFeedbackGenerator or a view-attached UIImpactFeedbackGenerator",
27
+ "Passes the touch location (alignmentOccurred(at:) or impactOccurred(intensity:at:))",
28
+ "Calls prepare() at drag start to reduce latency",
29
+ "Keeps one generator for the interaction instead of creating one per snap",
30
+ "Fires once per snap rather than continuously"
31
+ ]
32
+ },
33
+ {
34
+ "id": 2,
35
+ "name": "custom-pattern-core-haptics",
36
+ "prompt": "Design wants a checkout success haptic: a sharp tap followed by a short soft swell, tunable by designers without app releases. How do we build this properly?",
37
+ "expected_output": "Uses Core Haptics: checks supportsHaptics, creates an engine, sets stopped and reset handlers before start, restarts and recreates players after reset, stops the engine or enables auto shutdown for power. Builds a transient event plus a continuous event with a parameter curve, and stores the pattern in a bundled AHAP file loaded with playPattern(from:) or CHHapticPattern(contentsOf:). Pairs the haptic with visual confirmation and notes devices without haptics.",
38
+ "files": [],
39
+ "expectations": [
40
+ "Checks hardware support with capabilitiesForHardware",
41
+ "Sets stoppedHandler and resetHandler and restarts after reset",
42
+ "Combines a transient event with a continuous event or curve",
43
+ "Uses an AHAP file for designer tuning",
44
+ "Stops the engine when idle or uses isAutoShutdownEnabled",
45
+ "Keeps a visual confirmation alongside the haptic"
46
+ ]
47
+ }
48
+ ]
49
+ }
@@ -0,0 +1,22 @@
1
+ [
2
+ { "query": "Add a success haptic when the user completes a purchase in SwiftUI", "should_trigger": true },
3
+ { "query": "sensoryFeedback vs UIImpactFeedbackGenerator, which should I use?", "should_trigger": true },
4
+ { "query": "Our haptics feel laggy, should I call prepare()?", "should_trigger": true },
5
+ { "query": "Create a custom haptic pattern with Core Haptics and an AHAP file", "should_trigger": true },
6
+ { "query": "Too many vibrations in our app, review where haptics should go", "should_trigger": true },
7
+ { "query": "CHHapticEngine stops after a phone call and never plays again", "should_trigger": true },
8
+ { "query": "Play a selection tick while the picker value changes", "should_trigger": true },
9
+ { "query": "Should haptics respect a user setting and how do we test them?", "should_trigger": true },
10
+ { "query": "Haptic feedback when a dragged item snaps to a grid in UIKit", "should_trigger": true },
11
+ { "query": "Use the new press and release sensory feedback for buttons on iOS 26", "should_trigger": true },
12
+ { "query": "Play a notification sound when a message arrives", "should_trigger": false },
13
+ { "query": "Add a spring animation when the card snaps into place", "should_trigger": false },
14
+ { "query": "Schedule a local push notification for tomorrow", "should_trigger": false },
15
+ { "query": "Detect shake gesture to undo", "should_trigger": false },
16
+ { "query": "Read accelerometer data with Core Motion", "should_trigger": false },
17
+ { "query": "Make the bell SF Symbol bounce when a notification arrives", "should_trigger": false },
18
+ { "query": "Rumble a game controller during gameplay on Apple TV", "should_trigger": false },
19
+ { "query": "Add VoiceOver announcements for upload completion", "should_trigger": false },
20
+ { "query": "Play background audio while the app is locked", "should_trigger": false },
21
+ { "query": "Design review of the checkout screen", "should_trigger": false }
22
+ ]