@mmerterden/multi-agent-pipeline 20.17.1 → 20.17.2
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 +11 -0
- package/docs/facts.json +6 -6
- package/install/catalog-history.json +1 -1
- package/manifest.json +75 -37
- package/package.json +1 -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 +170 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/evals/evals.json +50 -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 +145 -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,215 @@
|
|
|
1
|
+
# Core Haptics
|
|
2
|
+
|
|
3
|
+
For patterns the feedback generators cannot express: shaped intensity and
|
|
4
|
+
sharpness, several events in sequence, haptics synchronised with audio, or a
|
|
5
|
+
pattern that changes while it plays (a drag that strengthens with distance).
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
|
|
9
|
+
1. Engine lifecycle
|
|
10
|
+
2. Events and parameters
|
|
11
|
+
3. Playing a pattern
|
|
12
|
+
4. Parameter curves and dynamic parameters
|
|
13
|
+
5. Advanced players
|
|
14
|
+
6. AHAP files
|
|
15
|
+
7. Pitfalls
|
|
16
|
+
|
|
17
|
+
## 1. Engine lifecycle
|
|
18
|
+
|
|
19
|
+
Check support, create the engine once for the screen or feature, set both
|
|
20
|
+
handlers before starting, and stop it when it is no longer needed.
|
|
21
|
+
|
|
22
|
+
```swift
|
|
23
|
+
import CoreHaptics
|
|
24
|
+
import OSLog
|
|
25
|
+
|
|
26
|
+
@MainActor
|
|
27
|
+
final class PatternPlayer {
|
|
28
|
+
private var engine: CHHapticEngine?
|
|
29
|
+
private let logger = Logger(subsystem: "app.haptics", category: "engine")
|
|
30
|
+
|
|
31
|
+
var supportsHaptics: Bool {
|
|
32
|
+
CHHapticEngine.capabilitiesForHardware().supportsHaptics
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func start() throws {
|
|
36
|
+
guard supportsHaptics, engine == nil else { return }
|
|
37
|
+
let engine = try CHHapticEngine()
|
|
38
|
+
engine.stoppedHandler = { @Sendable [logger] reason in
|
|
39
|
+
logger.info("Haptic engine stopped: \(reason.rawValue)")
|
|
40
|
+
}
|
|
41
|
+
engine.resetHandler = { @Sendable [weak self] in
|
|
42
|
+
Task { @MainActor in
|
|
43
|
+
try? self?.engine?.start()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
try engine.start()
|
|
47
|
+
self.engine = engine
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
func stop() {
|
|
51
|
+
engine?.stop()
|
|
52
|
+
engine = nil
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
- **Stopped handler**: runs when something outside the app stops the engine
|
|
58
|
+
(an audio interruption such as a call, or the app moving to the
|
|
59
|
+
background). It is not called for your own `stop`. Restart before the next
|
|
60
|
+
pattern.
|
|
61
|
+
- **Reset handler**: runs after the haptic server recovers from a failure.
|
|
62
|
+
Restart the engine, re-register custom audio resources, and recreate any
|
|
63
|
+
players you held; old players are invalid.
|
|
64
|
+
- Do not assume the handlers run on the main thread; hop to the main actor
|
|
65
|
+
before touching main-actor state.
|
|
66
|
+
- **Power**: stop the engine when the screen stops playing patterns, or set
|
|
67
|
+
`isAutoShutdownEnabled = true` to let the engine stop itself after about two
|
|
68
|
+
minutes idle and start on demand.
|
|
69
|
+
- `playsHapticsOnly` makes the engine ignore audio events; `isMutedForHaptics`
|
|
70
|
+
and `isMutedForAudio` mute one channel.
|
|
71
|
+
- The engine is not a singleton. Several can exist; each behaves independently.
|
|
72
|
+
|
|
73
|
+
## 2. Events and parameters
|
|
74
|
+
|
|
75
|
+
| Event type | Feels like |
|
|
76
|
+
|---|---|
|
|
77
|
+
| `.hapticTransient` | A short tap or impulse |
|
|
78
|
+
| `.hapticContinuous` | A sustained vibration for a `duration` |
|
|
79
|
+
| `.audioContinuous` | A sustained generated tone |
|
|
80
|
+
| `.audioCustom` | An audio file registered with `registerAudioResource(_:options:)` |
|
|
81
|
+
|
|
82
|
+
Event parameters (`CHHapticEvent.ParameterID`), normalised 0 to 1, where 0
|
|
83
|
+
and 1 map to the minimum and maximum the hardware supports:
|
|
84
|
+
|
|
85
|
+
- `.hapticIntensity`: strength.
|
|
86
|
+
- `.hapticSharpness`: soft and round (low) to crisp and mechanical (high).
|
|
87
|
+
- `.attackTime`, `.decayTime`, `.releaseTime`, `.sustained`: envelope of a
|
|
88
|
+
continuous event.
|
|
89
|
+
- Audio: `.audioVolume`, `.audioPitch`, `.audioPan`, `.audioBrightness`.
|
|
90
|
+
|
|
91
|
+
Match intensity and sharpness to the visual: a crisp snap is short and sharp,
|
|
92
|
+
a soft landing is lower in both.
|
|
93
|
+
|
|
94
|
+
## 3. Playing a pattern
|
|
95
|
+
|
|
96
|
+
```swift
|
|
97
|
+
func playTap(on engine: CHHapticEngine) throws {
|
|
98
|
+
let tap = CHHapticEvent(
|
|
99
|
+
eventType: .hapticTransient,
|
|
100
|
+
parameters: [
|
|
101
|
+
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.9),
|
|
102
|
+
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.7)
|
|
103
|
+
],
|
|
104
|
+
relativeTime: 0
|
|
105
|
+
)
|
|
106
|
+
let pattern = try CHHapticPattern(events: [tap], parameters: [])
|
|
107
|
+
let player = try engine.makePlayer(with: pattern)
|
|
108
|
+
try player.start(atTime: CHHapticTimeImmediate)
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
A player (`CHHapticPatternPlayer`) can `start(atTime:)`, `stop(atTime:)`,
|
|
113
|
+
`cancel()`, `sendParameters(_:atTime:)` and `scheduleParameterCurve(_:atTime:)`.
|
|
114
|
+
|
|
115
|
+
## 4. Parameter curves and dynamic parameters
|
|
116
|
+
|
|
117
|
+
**Curves** schedule a change over time inside the pattern; values move
|
|
118
|
+
linearly between control points.
|
|
119
|
+
|
|
120
|
+
```swift
|
|
121
|
+
let rumble = CHHapticEvent(
|
|
122
|
+
eventType: .hapticContinuous,
|
|
123
|
+
parameters: [
|
|
124
|
+
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.3),
|
|
125
|
+
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.2)
|
|
126
|
+
],
|
|
127
|
+
relativeTime: 0,
|
|
128
|
+
duration: 1.0
|
|
129
|
+
)
|
|
130
|
+
let swell = CHHapticParameterCurve(
|
|
131
|
+
parameterID: .hapticIntensityControl,
|
|
132
|
+
controlPoints: [
|
|
133
|
+
.init(relativeTime: 0, value: 0.3),
|
|
134
|
+
.init(relativeTime: 0.5, value: 0.7),
|
|
135
|
+
.init(relativeTime: 1.0, value: 1.0)
|
|
136
|
+
],
|
|
137
|
+
relativeTime: 0
|
|
138
|
+
)
|
|
139
|
+
let pattern = try CHHapticPattern(events: [rumble], parameterCurves: [swell])
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Dynamic parameters** (`CHHapticDynamicParameter`) change the whole pattern
|
|
143
|
+
right now, from live input: send them with `sendParameters(_:atTime:)` as a
|
|
144
|
+
drag moves. They use the `...Control` IDs (`.hapticIntensityControl`,
|
|
145
|
+
`.hapticSharpnessControl`, envelope and audio controls), which scale the
|
|
146
|
+
event values rather than replace them.
|
|
147
|
+
|
|
148
|
+
## 5. Advanced players
|
|
149
|
+
|
|
150
|
+
`makeAdvancedPlayer(with:)` returns a `CHHapticAdvancedPatternPlayer`, which
|
|
151
|
+
adds `loopEnabled` and `loopEnd`, `pause(atTime:)`, `resume(atTime:)`,
|
|
152
|
+
`seek(toOffset:)`, `playbackRate` and a `completionHandler`. Use it for looping
|
|
153
|
+
or scrubbing; a plain player is enough for one-shot patterns.
|
|
154
|
+
|
|
155
|
+
## 6. AHAP files
|
|
156
|
+
|
|
157
|
+
AHAP (Apple Haptic and Audio Pattern) is a JSON file holding one pattern.
|
|
158
|
+
Ship it as a bundle resource and load it at run time, so designers can tune
|
|
159
|
+
the feel without a rebuild.
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"Version": 1.0,
|
|
164
|
+
"Pattern": [
|
|
165
|
+
{
|
|
166
|
+
"Event": {
|
|
167
|
+
"Time": 0.0,
|
|
168
|
+
"EventType": "HapticTransient",
|
|
169
|
+
"EventParameters": [
|
|
170
|
+
{ "ParameterID": "HapticIntensity", "ParameterValue": 0.9 },
|
|
171
|
+
{ "ParameterID": "HapticSharpness", "ParameterValue": 0.6 }
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"Event": {
|
|
177
|
+
"Time": 0.12,
|
|
178
|
+
"EventType": "HapticContinuous",
|
|
179
|
+
"EventDuration": 0.3,
|
|
180
|
+
"EventParameters": [
|
|
181
|
+
{ "ParameterID": "HapticIntensity", "ParameterValue": 0.5 }
|
|
182
|
+
]
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
]
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
- Top-level keys are only `Version` and `Pattern`; `Pattern` is an array of
|
|
190
|
+
entries, each an `Event`, a `Parameter` (dynamic parameter) or a
|
|
191
|
+
`ParameterCurve`.
|
|
192
|
+
- Missing keys take defaults, out-of-range values are clamped, unknown keys
|
|
193
|
+
are ignored.
|
|
194
|
+
- Play directly with `engine.playPattern(from: url)`, or build a pattern with
|
|
195
|
+
`CHHapticPattern(contentsOf:)` (iOS 16) to make a player you can control.
|
|
196
|
+
|
|
197
|
+
```swift
|
|
198
|
+
func playCheckout(on engine: CHHapticEngine) throws {
|
|
199
|
+
guard let url = Bundle.main.url(forResource: "checkout", withExtension: "ahap") else { return }
|
|
200
|
+
try engine.playPattern(from: url)
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## 7. Pitfalls
|
|
205
|
+
|
|
206
|
+
- Starting an engine on a device without haptics: check capabilities first and
|
|
207
|
+
fall back to visual or audio feedback.
|
|
208
|
+
- Playing after an interruption without restarting: handle the stopped
|
|
209
|
+
handler, restart lazily before the next pattern.
|
|
210
|
+
- Keeping players across a reset: recreate them in the reset handler.
|
|
211
|
+
- Leaving the engine running on a screen that no longer plays haptics: it
|
|
212
|
+
costs power.
|
|
213
|
+
- Long continuous patterns in an app: they dilute meaning and tire the hand.
|
|
214
|
+
- Haptics during camera, microphone or motion capture: they can disturb the
|
|
215
|
+
capture.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Notice: ideas from iphone-duo-skills
|
|
2
|
+
|
|
3
|
+
The `ios-iphone-duo` skill is original writing for this repository. Its
|
|
4
|
+
structure and several ideas were informed by a third-party skill set; no text
|
|
5
|
+
was copied from it. Technical facts are taken from Apple's developer
|
|
6
|
+
documentation and Human Interface Guidelines, which the skill links.
|
|
7
|
+
|
|
8
|
+
## Upstream
|
|
9
|
+
|
|
10
|
+
- Repository: https://github.com/mirzaaghazadeh/iphone-duo-skills
|
|
11
|
+
- License: MIT (text below)
|
|
12
|
+
|
|
13
|
+
## Ideas used
|
|
14
|
+
|
|
15
|
+
- Organising readiness work as a hunt for fixed assumptions (orientation,
|
|
16
|
+
screen, cached sizes, idiom, device sizes, symmetric insets, hand-built
|
|
17
|
+
bars), each paired with its replacement.
|
|
18
|
+
- A pose-to-pattern view of layout and a design review checklist for
|
|
19
|
+
foldable layouts.
|
|
20
|
+
- Treating hinge data as input for effects and reserved regions or
|
|
21
|
+
arrangements as the tools for layout.
|
|
22
|
+
|
|
23
|
+
## Trademarks
|
|
24
|
+
|
|
25
|
+
Apple, iPhone, iPhone Duo, iOS, Xcode, SwiftUI and UIKit are trademarks of
|
|
26
|
+
Apple Inc. This skill is not affiliated with, endorsed by or sponsored by
|
|
27
|
+
Apple Inc.
|
|
28
|
+
|
|
29
|
+
## License (MIT)
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
MIT License
|
|
33
|
+
|
|
34
|
+
Copyright (c) 2026 Navid Mirzaaghazadeh
|
|
35
|
+
|
|
36
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
37
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
38
|
+
in the Software without restriction, including without limitation the rights
|
|
39
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
40
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
41
|
+
furnished to do so, subject to the following conditions:
|
|
42
|
+
|
|
43
|
+
The above copyright notice and this permission notice shall be included in all
|
|
44
|
+
copies or substantial portions of the Software.
|
|
45
|
+
|
|
46
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
47
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
48
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
49
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
50
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
51
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
52
|
+
SOFTWARE.
|
|
53
|
+
```
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ios-iphone-duo
|
|
3
|
+
description: "Readiness for iPhone Duo, the foldable two-display iPhone on iOS 27.1: fixed-assumption hunting, asymmetric safe areas, reserved regions, ArrangementView, hinge changes, vertical bars and dual-pane patterns. Use when preparing, auditing or designing an app for iPhone Duo, the fold or vertical toolbars."
|
|
4
|
+
metadata:
|
|
5
|
+
source: multi-agent-pipeline
|
|
6
|
+
date_added: "2026-10-08"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# iPhone Duo readiness
|
|
10
|
+
|
|
11
|
+
iPhone Duo has a compact outer display and a large inner display, each with a
|
|
12
|
+
front camera, joined by a hinge that can rest at any angle. Apple's framing is
|
|
13
|
+
the one to keep: it is still an iPhone, and an app that already resizes well
|
|
14
|
+
(iPad, Split View, iPhone Mirroring) is most of the way there. The work is
|
|
15
|
+
removing every place the app assumes a fixed shape, then adopting a few new
|
|
16
|
+
APIs where the fold or the vertical bars need them.
|
|
17
|
+
|
|
18
|
+
**Beta status.** Everything marked *(27.1 beta)* below is documented as beta
|
|
19
|
+
in the iOS 27.1 SDK and may change before release. Check availability before
|
|
20
|
+
shipping code that depends on it, and gate it with `if #available`.
|
|
21
|
+
|
|
22
|
+
## One rule behind every fix
|
|
23
|
+
|
|
24
|
+
Do not ask "is this iPhone Duo?". Ask "how much space do I have, and what is
|
|
25
|
+
reserved in it?". Branches on size class and available space are right on
|
|
26
|
+
iPad, in Split View and on the next device too; a device check is right on one
|
|
27
|
+
device and wrong everywhere else.
|
|
28
|
+
|
|
29
|
+
## Step 1: build and run
|
|
30
|
+
|
|
31
|
+
- Build with the latest Xcode. Apps built with Xcode 26 or earlier do not
|
|
32
|
+
extend under the status bar and camera on iPhone Duo.
|
|
33
|
+
- Test in the iPhone Duo simulator from Device Hub: closed, open, partially
|
|
34
|
+
folded, rotated in each pose, and in Split View on both sides.
|
|
35
|
+
- Walk every screen, sheet and popover. Note views that resize badly,
|
|
36
|
+
controls that land in the fold, sheets that sit awkwardly when folding, and
|
|
37
|
+
how bars move to the side.
|
|
38
|
+
|
|
39
|
+
Layout bugs here depend on the pose. Reading code finds candidates; only
|
|
40
|
+
running finds the bugs.
|
|
41
|
+
|
|
42
|
+
## Step 2: hunt fixed assumptions
|
|
43
|
+
|
|
44
|
+
Search the codebase with the patterns in `references/fixed-assumptions.md`.
|
|
45
|
+
Each hit is a candidate defect with a known replacement:
|
|
46
|
+
|
|
47
|
+
| Assumption | Why it breaks | Replace with |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| Orientation decides layout (`UIDevice.current.orientation`, `interfaceOrientation`) | Orientation no longer tells you the shape of your space | Compare the width and height you actually have |
|
|
50
|
+
| `UIScreen.main` (deprecated in iOS 26) | Two displays make "the screen" ambiguous | View, window or scene bounds; `displayScale` from traits or environment |
|
|
51
|
+
| Size read once and cached | Folding resizes the app mid-session | Lay out in `layoutSubviews` / `viewDidLayoutSubviews`, `GeometryReader`, `onGeometryChange` |
|
|
52
|
+
| Regular width means iPad (`userInterfaceIdiom`, `horizontalSizeClass == .regular`) | The inner display is regular width on a phone | Size class and available space, never idiom |
|
|
53
|
+
| Device size tables, width thresholds | No table contains this device | Size classes, container-relative sizing |
|
|
54
|
+
| Symmetric insets (`insets.left * 2`) | Side-mounted bars make insets asymmetric | Each edge's inset on its own |
|
|
55
|
+
| Hand-built `UIToolbar`, `UINavigationBar`, `UITabBar` | Only container-provided bars move to the side | Toolbar items on navigation containers |
|
|
56
|
+
| Full-bleed fill media | Wider displays crop the subject | Choose fill or fit from size, set a focal point |
|
|
57
|
+
|
|
58
|
+
## Step 3: let containers adapt
|
|
59
|
+
|
|
60
|
+
Standard containers already handle sizes, the fold and the cameras:
|
|
61
|
+
`NavigationSplitView` / `UISplitViewController` (collapses on the outer
|
|
62
|
+
display, adapts column widths and margins to the fold), `TabView`,
|
|
63
|
+
`NavigationStack`, `List`, `ScrollView`. Alerts, context menus and sheets move
|
|
64
|
+
away from the fold by themselves. The more of the app sits in these, the less
|
|
65
|
+
custom fold handling it needs.
|
|
66
|
+
|
|
67
|
+
## Step 4: safe areas and reserved regions
|
|
68
|
+
|
|
69
|
+
- Keep interactive and legible content inside the safe area; let backgrounds
|
|
70
|
+
run past it.
|
|
71
|
+
- Treat every edge separately and test Split View on both sides, where bars
|
|
72
|
+
sit on the outer edge of each app.
|
|
73
|
+
- Reserved regions describe space the hardware takes:
|
|
74
|
+
- **division**: the fold, active only while partially folded;
|
|
75
|
+
- **occlusion**: the outer camera (always) and the inner camera (only
|
|
76
|
+
while active).
|
|
77
|
+
- Custom views read them with `GeometryProxy.reservedRegions(kind:options:layoutDirectionBehavior:)`
|
|
78
|
+
*(27.1 beta)* or `UIView.reservedRegions(kind:options:)` *(27.1 beta)*. The
|
|
79
|
+
query returns regions intersecting the view whether or not they are active,
|
|
80
|
+
so filter on `isActive` for current layout and use `.includeInactive` only
|
|
81
|
+
for stable decisions (for example an even column count on hardware that has
|
|
82
|
+
a fold). A region's `frame` already includes its `margins`.
|
|
83
|
+
- Reserved region frames are physical; SwiftUI mirrors them for right-to-left
|
|
84
|
+
by default (`.mirrors`), which suits a custom `Layout`. Pass `.fixed` for
|
|
85
|
+
raw frames.
|
|
86
|
+
|
|
87
|
+
Use the hinge for effects, and reserved regions or arrangements for layout.
|
|
88
|
+
|
|
89
|
+
## Step 5: arrangements for two-view layouts
|
|
90
|
+
|
|
91
|
+
`ArrangementView(primary:secondary:)` *(27.1 beta)*, and
|
|
92
|
+
`UIArrangementViewController` in UIKit, lay out exactly two views and adapt
|
|
93
|
+
to size, size class and the fold:
|
|
94
|
+
|
|
95
|
+
- `.arrangementViewStyle(.split)` (the automatic default): side by side when
|
|
96
|
+
wider than tall, stacked when taller than wide, adjusted around the fold.
|
|
97
|
+
Natural replacement for an `HStack` or `VStack` of two panes.
|
|
98
|
+
- `.arrangementViewStyle(.overlay)`: primary over secondary when flat or
|
|
99
|
+
closed, one on each side of the fold when partially open. Natural
|
|
100
|
+
replacement for a `ZStack` such as controls over video.
|
|
101
|
+
- Constrain axes with `.split.axes(...)` or `.overlay.axes(...)`.
|
|
102
|
+
|
|
103
|
+
Keep navigation containers outside an arrangement, and do not put an
|
|
104
|
+
arrangement inside `NavigationSplitView`, `List` or `ScrollView`, where part
|
|
105
|
+
of it can become unreachable.
|
|
106
|
+
|
|
107
|
+
## Step 6: vertical bars
|
|
108
|
+
|
|
109
|
+
On the outer display, and on the inner display in landscape, toolbars, tab
|
|
110
|
+
bars and navigation controls move to the side. The inner display in portrait
|
|
111
|
+
keeps horizontal bars.
|
|
112
|
+
|
|
113
|
+
- Only container-provided bars participate: `.toolbar` on `NavigationStack` or
|
|
114
|
+
`NavigationSplitView`, items on a view controller in a navigation
|
|
115
|
+
controller.
|
|
116
|
+
- Order: Back or Close at the top (`cancellationAction`, or `leadingItemGroups`
|
|
117
|
+
in UIKit), then prominent actions (`topBarPinnedTrailing`, iOS 27, or
|
|
118
|
+
`pinnedTrailingGroup`), then the rest in their groups (`ToolbarItemGroup`).
|
|
119
|
+
- Give items both a title and a symbol (`Label`). Vertical placement uses the
|
|
120
|
+
icon; items with only a title or a custom view stay horizontal.
|
|
121
|
+
- Overflow fills bottom to top; reorder with `visibilityPriority(_:)` (iOS 27),
|
|
122
|
+
add items straight to the menu with `ToolbarOverflowMenu` (iOS 27) or
|
|
123
|
+
`additionalOverflowItems`.
|
|
124
|
+
- `axisBehavior(_:)` *(27.1 beta)* keeps an item horizontal-only.
|
|
125
|
+
- `toolbarVerticalCompressionBehavior(_:)` *(27.1 beta)*: keep the tab bar
|
|
126
|
+
for navigation-heavy screens, prefer toolbar items for task screens.
|
|
127
|
+
- `toolbarVerticalBehavior(.disabled)` *(27.1 beta)* or
|
|
128
|
+
`preferredVerticalBarBehavior` opts a screen out, for example a full-screen
|
|
129
|
+
player or a non-scrolling calculator. Make it a stable per-screen choice.
|
|
130
|
+
- `toolbarVerticalEdge` *(27.1 beta)* environment value, `verticalBarEdge`
|
|
131
|
+
trait in UIKit: the edge the system prefers for a vertical bar, or nil
|
|
132
|
+
(`.unspecified` in UIKit) where none is used. Align custom floating controls with it.
|
|
133
|
+
- Extend hero images under a vertical bar with `backgroundExtensionEffect()`
|
|
134
|
+
or `UIBackgroundExtensionView`.
|
|
135
|
+
|
|
136
|
+
## Step 7: the hinge
|
|
137
|
+
|
|
138
|
+
`onHingeChange(isEnabled:_:)` *(27.1 beta)* passes the old and new
|
|
139
|
+
`DeviceHingeContext`; its `hinge` is nil on hardware without one.
|
|
140
|
+
`DeviceHinge.status` is `.closed`, `.partiallyOpen` or `.fullyOpen` (a struct,
|
|
141
|
+
so a `switch` needs `default`). UIKit's `UIHingeInteraction` reports a
|
|
142
|
+
`UIHinge` whose `angle` is in radians and whose status adds `.unknown`. Prefer
|
|
143
|
+
the status over angle thresholds; update rate and precision are system policy.
|
|
144
|
+
|
|
145
|
+
API details, code and the UIKit side are in `references/api-map.md`.
|
|
146
|
+
|
|
147
|
+
## Design: poses and panes
|
|
148
|
+
|
|
149
|
+
Design to two size classes, compact (outer) and regular (inner), not to poses.
|
|
150
|
+
Keep the same functions and state on both displays; the inner display may show
|
|
151
|
+
one more level of the same hierarchy (list and detail side by side), not a
|
|
152
|
+
different app. `references/poses-and-panes.md` has the pose-to-pattern table,
|
|
153
|
+
dual-pane rules and the review checklist; `ios-design-review` runs the
|
|
154
|
+
general visual review.
|
|
155
|
+
|
|
156
|
+
## Reporting
|
|
157
|
+
|
|
158
|
+
Report the Xcode and SDK the app builds with, each defect with `file:line`
|
|
159
|
+
and the replacement, which screens were run in which poses, and what is still
|
|
160
|
+
unverified (pose behaviour seen only in code, beta APIs not yet run).
|
|
161
|
+
|
|
162
|
+
## References
|
|
163
|
+
|
|
164
|
+
- `references/fixed-assumptions.md`: grep patterns and replacements.
|
|
165
|
+
- `references/api-map.md`: every API by job, availability, beta marks, code.
|
|
166
|
+
- `references/poses-and-panes.md`: poses, dual-pane rules, review checklist.
|
|
167
|
+
- Preparing your app for iPhone Duo: https://developer.apple.com/documentation/technologyoverviews/preparing-your-app-for-iphone-duo
|
|
168
|
+
- Designing for iPhone Duo (HIG): https://developer.apple.com/design/human-interface-guidelines/designing-for-iphone-duo
|
|
169
|
+
- Get ready for iPhone Duo: https://developer.apple.com/iphone-duo/
|
|
170
|
+
- `NOTICE.md`: attribution for ideas drawn from a third-party skill set.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skill_name": "ios-iphone-duo",
|
|
3
|
+
"evals": [
|
|
4
|
+
{
|
|
5
|
+
"id": 0,
|
|
6
|
+
"name": "readiness-audit-plan",
|
|
7
|
+
"prompt": "We have a UIKit and SwiftUI mixed iPhone app. Leadership wants it ready for iPhone Duo. Give me an audit plan: what to search for in the code, what to replace it with, and how to verify. We currently use UIScreen.main.bounds in a dozen places, show our iPad layout when horizontalSizeClass is regular, and have a custom UIToolbar at the bottom of the editor.",
|
|
8
|
+
"expected_output": "Starts with building on the latest Xcode and running in the iPhone Duo simulator from Device Hub across closed, open, folded, rotated and Split View. Provides a grep list (UIScreen.main, orientation, userInterfaceIdiom, regular size class, device widths, symmetric insets, hand-built bars, fill media) with replacements: view or scene bounds and displayScale instead of UIScreen.main (deprecated in iOS 26), size class and available space instead of treating regular as iPad, container-provided toolbar items instead of the custom UIToolbar so bars can move vertical. Covers asymmetric safe areas and reserved regions, marks 27.1 APIs as beta, and reports file:line findings separating simulator-confirmed from code-only.",
|
|
9
|
+
"files": [],
|
|
10
|
+
"expectations": [
|
|
11
|
+
"Gives grep patterns including UIScreen.main and userInterfaceIdiom or orientation",
|
|
12
|
+
"Replaces UIScreen.main with view, window or scene bounds and notes its iOS 26 deprecation",
|
|
13
|
+
"Explains that regular width no longer means iPad because the inner display is regular",
|
|
14
|
+
"Replaces the custom UIToolbar with container-provided toolbar items for vertical bars",
|
|
15
|
+
"Mentions asymmetric safe area insets handled per edge",
|
|
16
|
+
"Requires verification in the iPhone Duo simulator across poses and Split View",
|
|
17
|
+
"Marks iOS 27.1 APIs such as ReservedRegion or ArrangementView as beta"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": 1,
|
|
22
|
+
"name": "fold-aware-media-layout",
|
|
23
|
+
"prompt": "Our video app shows playback controls over the video in a ZStack. On the foldable iPhone, when it is half folded, we want video on top half and controls on the bottom half, and the controls must never sit in the fold. Which APIs should we use and how?",
|
|
24
|
+
"expected_output": "Recommends ArrangementView with the overlay style (iOS 27.1 beta), controls as primary and video as secondary, which layers them when flat or closed and splits them around the fold when partially open; optionally constrains axes. Keeps navigation containers outside and does not place the arrangement in a List or ScrollView. For any custom positioning, reads reserved regions of kind division from GeometryProxy, filters isActive, notes frame includes margins and SwiftUI mirrors frames for RTL by default. Uses onHingeChange only for effects, not layout.",
|
|
25
|
+
"files": [],
|
|
26
|
+
"expectations": [
|
|
27
|
+
"Uses ArrangementView with the overlay style",
|
|
28
|
+
"Explains overlay behaviour: layered when flat, split around the fold when partially open",
|
|
29
|
+
"Marks ArrangementView or reserved regions as iOS 27.1 beta",
|
|
30
|
+
"Mentions reserved regions of kind division with an isActive check for custom layout",
|
|
31
|
+
"Keeps navigation containers outside the arrangement or avoids List and ScrollView",
|
|
32
|
+
"Says the hinge API is for effects, not layout"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": 2,
|
|
37
|
+
"name": "vertical-toolbar-design",
|
|
38
|
+
"prompt": "On the outer display our editor's toolbar moved to the side and half the buttons vanished, and our 'Save changes' text button stays at the top. How should we organise the toolbar for iPhone Duo?",
|
|
39
|
+
"expected_output": "Explains vertical bars on the outer display and inner landscape, with items needing a symbol and title (text-only items stay horizontal). Orders Back or Close at top via cancellationAction, prominent Done via topBarPinnedTrailing, then grouped items with ToolbarItemGroup; sets visibilityPriority so frequent and badged items stay visible; uses ToolbarOverflowMenu for extra actions instead of a custom menu; mentions axisBehavior and toolbarVerticalCompressionBehavior as 27.1 beta, and toolbarVerticalBehavior(.disabled) only for screens better served by horizontal bars.",
|
|
40
|
+
"files": [],
|
|
41
|
+
"expectations": [
|
|
42
|
+
"Says items need both a symbol and a title and that text-only items stay horizontal",
|
|
43
|
+
"Places Back or Close first with cancellationAction and a prominent action with topBarPinnedTrailing",
|
|
44
|
+
"Uses visibilityPriority to control what overflows",
|
|
45
|
+
"Uses ToolbarOverflowMenu or the system overflow instead of a custom menu",
|
|
46
|
+
"Treats disabling vertical bars as a rare, stable per-screen choice"
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[
|
|
2
|
+
{ "query": "Get our app ready for iPhone Duo", "should_trigger": true },
|
|
3
|
+
{ "query": "How do we avoid putting buttons in the fold on the foldable iPhone?", "should_trigger": true },
|
|
4
|
+
{ "query": "Our toolbar moves to the side on the outer display, how do we organise items?", "should_trigger": true },
|
|
5
|
+
{ "query": "Replace UIScreen.main across the codebase for the two-display iPhone", "should_trigger": true },
|
|
6
|
+
{ "query": "Use ArrangementView for a split between player and lyrics", "should_trigger": true },
|
|
7
|
+
{ "query": "Read the hinge angle with onHingeChange", "should_trigger": true },
|
|
8
|
+
{ "query": "Reserved regions for the inner front camera occlusion", "should_trigger": true },
|
|
9
|
+
{ "query": "Design review of our app on the inner display of iPhone Duo in book pose", "should_trigger": true },
|
|
10
|
+
{ "query": "We show the iPad layout when size class is regular, is that a problem on the foldable?", "should_trigger": true },
|
|
11
|
+
{ "query": "What does toolbarVerticalEdge return and when should I use it?", "should_trigger": true },
|
|
12
|
+
{ "query": "Make our iPad app support Stage Manager window resizing", "should_trigger": false },
|
|
13
|
+
{ "query": "Build a NavigationSplitView for iPad with a sidebar", "should_trigger": false },
|
|
14
|
+
{ "query": "Adapt our Android app to foldables with Jetpack WindowManager", "should_trigger": false },
|
|
15
|
+
{ "query": "Support landscape orientation on iPhone 17", "should_trigger": false },
|
|
16
|
+
{ "query": "Add Liquid Glass to the tab bar", "should_trigger": false },
|
|
17
|
+
{ "query": "Design review of the checkout screen in dark mode", "should_trigger": false },
|
|
18
|
+
{ "query": "Use the camera to capture photos with AVFoundation", "should_trigger": false },
|
|
19
|
+
{ "query": "Show content on an external display with AirPlay", "should_trigger": false },
|
|
20
|
+
{ "query": "Fix Auto Layout constraint warnings on rotation", "should_trigger": false },
|
|
21
|
+
{ "query": "Make our tab bar minimize on scroll", "should_trigger": false }
|
|
22
|
+
]
|