@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.
- package/CHANGELOG.md +24 -0
- package/docs/facts.json +6 -6
- package/install/_mcp-register.mjs +1 -1
- package/install/_xcode-skills.mjs +263 -0
- package/install/catalog-history.json +1 -1
- package/install/claude.mjs +23 -0
- package/install/codex.mjs +11 -0
- package/install/copilot.mjs +9 -0
- package/manifest.json +85 -45
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/stack/SKILL.md +2 -0
- package/pipeline/lib/xcode-plugin.mjs +131 -0
- package/pipeline/multi-agent-refs/features/doctor.md +10 -0
- package/pipeline/scripts/doctor.mjs +38 -0
- package/pipeline/scripts/uninstall.mjs +57 -1
- package/pipeline/skills/.skill-manifest.json +33 -13
- package/pipeline/skills/.skills-index.json +66 -11
- package/pipeline/skills/shared/README.md +18 -13
- package/pipeline/skills/shared/external/NOTICE-avdlee-swiftui-agent-skill.md +3 -1
- package/pipeline/skills/shared/external/NOTICE-dimillian-skills.md +2 -1
- package/pipeline/skills/shared/external/hig-app-icons/SKILL.md +159 -0
- package/pipeline/skills/shared/external/hig-app-icons/evals/evals.json +48 -0
- package/pipeline/skills/shared/external/hig-app-icons/evals/trigger-eval.json +22 -0
- package/pipeline/skills/shared/external/hig-app-icons/references/icon-workflow.md +115 -0
- package/pipeline/skills/shared/external/hig-components-layout/SKILL.md +32 -4
- package/pipeline/skills/shared/external/hig-components-layout/evals/evals.json +47 -0
- package/pipeline/skills/shared/external/hig-components-layout/evals/trigger-eval.json +22 -0
- package/pipeline/skills/shared/external/hig-components-layout/references/bars-and-tabs.md +231 -0
- package/pipeline/skills/shared/external/hig-components-layout/references/containers.md +5 -30
- package/pipeline/skills/shared/external/hig-components-status/SKILL.md +1 -1
- package/pipeline/skills/shared/external/hig-foundations/SKILL.md +23 -4
- package/pipeline/skills/shared/external/hig-foundations/evals/evals.json +47 -0
- package/pipeline/skills/shared/external/hig-foundations/evals/trigger-eval.json +21 -0
- package/pipeline/skills/shared/external/hig-foundations/references/foundations-detail.md +34 -26
- package/pipeline/skills/shared/external/hig-foundations/references/type-color-spacing.md +219 -0
- package/pipeline/skills/shared/external/hig-inputs/SKILL.md +5 -4
- package/pipeline/skills/shared/external/hig-patterns/SKILL.md +19 -3
- package/pipeline/skills/shared/external/hig-patterns/references/onboarding-and-states.md +188 -0
- package/pipeline/skills/shared/external/hig-patterns/references/patterns.md +5 -11
- package/pipeline/skills/shared/external/hig-platforms/SKILL.md +17 -5
- package/pipeline/skills/shared/external/hig-platforms/references/ipad-layout.md +218 -0
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +4 -1
- package/pipeline/skills/shared/external/ios-accessibility/references/large-content-and-appearance.md +135 -0
- package/pipeline/skills/shared/external/ios-design-review/SKILL.md +146 -0
- package/pipeline/skills/shared/external/ios-design-review/evals/evals.json +48 -0
- package/pipeline/skills/shared/external/ios-design-review/evals/trigger-eval.json +22 -0
- package/pipeline/skills/shared/external/ios-design-review/references/appearance-matrix.md +135 -0
- package/pipeline/skills/shared/external/ios-design-review/references/checklist.md +193 -0
- package/pipeline/skills/shared/external/ios-haptics/SKILL.md +191 -0
- package/pipeline/skills/shared/external/ios-haptics/evals/evals.json +49 -0
- package/pipeline/skills/shared/external/ios-haptics/evals/trigger-eval.json +22 -0
- package/pipeline/skills/shared/external/ios-haptics/references/core-haptics.md +215 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/NOTICE.md +53 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/SKILL.md +179 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/evals/evals.json +65 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/evals/trigger-eval.json +22 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/references/api-map.md +212 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/references/fixed-assumptions.md +202 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/references/poses-and-panes.md +122 -0
- package/pipeline/skills/shared/external/ios-sf-symbols/SKILL.md +177 -0
- package/pipeline/skills/shared/external/ios-sf-symbols/evals/evals.json +49 -0
- package/pipeline/skills/shared/external/ios-sf-symbols/evals/trigger-eval.json +22 -0
- package/pipeline/skills/shared/external/ios-sf-symbols/references/effects-and-custom-symbols.md +135 -0
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +38 -50
- package/pipeline/skills/shared/external/swiftui-animation/evals/evals.json +47 -0
- package/pipeline/skills/shared/external/swiftui-animation/evals/trigger-eval.json +22 -0
- package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +7 -73
- package/pipeline/skills/shared/external/swiftui-animation/references/scroll-effects.md +185 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/SKILL.md +6 -6
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-advanced.md +3 -253
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-basics.md +4 -47
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-transitions.md +3 -111
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/liquid-glass.md +11 -381
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +184 -224
- package/pipeline/skills/shared/external/swiftui-liquid-glass/evals/evals.json +47 -0
- package/pipeline/skills/shared/external/swiftui-liquid-glass/evals/trigger-eval.json +22 -0
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +246 -33
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/uikit-glass.md +227 -0
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +15 -18
- package/pipeline/skills/shared/external/swiftui-ui-patterns/references/components-index.md +1 -1
- package/pipeline/skills/shared/external/swiftui-ui-patterns/references/haptics.md +6 -68
- package/pipeline/skills/skills-index.md +16 -11
- 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
|
+
]
|