@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.
Files changed (73) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/docs/facts.json +6 -6
  3. package/install/catalog-history.json +1 -1
  4. package/manifest.json +75 -37
  5. package/package.json +1 -1
  6. package/pipeline/skills/.skill-manifest.json +33 -13
  7. package/pipeline/skills/.skills-index.json +66 -11
  8. package/pipeline/skills/shared/README.md +18 -13
  9. package/pipeline/skills/shared/external/NOTICE-avdlee-swiftui-agent-skill.md +3 -1
  10. package/pipeline/skills/shared/external/NOTICE-dimillian-skills.md +2 -1
  11. package/pipeline/skills/shared/external/hig-app-icons/SKILL.md +159 -0
  12. package/pipeline/skills/shared/external/hig-app-icons/evals/evals.json +48 -0
  13. package/pipeline/skills/shared/external/hig-app-icons/evals/trigger-eval.json +22 -0
  14. package/pipeline/skills/shared/external/hig-app-icons/references/icon-workflow.md +115 -0
  15. package/pipeline/skills/shared/external/hig-components-layout/SKILL.md +32 -4
  16. package/pipeline/skills/shared/external/hig-components-layout/evals/evals.json +47 -0
  17. package/pipeline/skills/shared/external/hig-components-layout/evals/trigger-eval.json +22 -0
  18. package/pipeline/skills/shared/external/hig-components-layout/references/bars-and-tabs.md +231 -0
  19. package/pipeline/skills/shared/external/hig-components-layout/references/containers.md +5 -30
  20. package/pipeline/skills/shared/external/hig-components-status/SKILL.md +1 -1
  21. package/pipeline/skills/shared/external/hig-foundations/SKILL.md +23 -4
  22. package/pipeline/skills/shared/external/hig-foundations/evals/evals.json +47 -0
  23. package/pipeline/skills/shared/external/hig-foundations/evals/trigger-eval.json +21 -0
  24. package/pipeline/skills/shared/external/hig-foundations/references/foundations-detail.md +34 -26
  25. package/pipeline/skills/shared/external/hig-foundations/references/type-color-spacing.md +219 -0
  26. package/pipeline/skills/shared/external/hig-inputs/SKILL.md +5 -4
  27. package/pipeline/skills/shared/external/hig-patterns/SKILL.md +19 -3
  28. package/pipeline/skills/shared/external/hig-patterns/references/onboarding-and-states.md +188 -0
  29. package/pipeline/skills/shared/external/hig-patterns/references/patterns.md +5 -11
  30. package/pipeline/skills/shared/external/hig-platforms/SKILL.md +17 -5
  31. package/pipeline/skills/shared/external/hig-platforms/references/ipad-layout.md +218 -0
  32. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +4 -1
  33. package/pipeline/skills/shared/external/ios-accessibility/references/large-content-and-appearance.md +135 -0
  34. package/pipeline/skills/shared/external/ios-design-review/SKILL.md +146 -0
  35. package/pipeline/skills/shared/external/ios-design-review/evals/evals.json +48 -0
  36. package/pipeline/skills/shared/external/ios-design-review/evals/trigger-eval.json +22 -0
  37. package/pipeline/skills/shared/external/ios-design-review/references/appearance-matrix.md +135 -0
  38. package/pipeline/skills/shared/external/ios-design-review/references/checklist.md +193 -0
  39. package/pipeline/skills/shared/external/ios-haptics/SKILL.md +191 -0
  40. package/pipeline/skills/shared/external/ios-haptics/evals/evals.json +49 -0
  41. package/pipeline/skills/shared/external/ios-haptics/evals/trigger-eval.json +22 -0
  42. package/pipeline/skills/shared/external/ios-haptics/references/core-haptics.md +215 -0
  43. package/pipeline/skills/shared/external/ios-iphone-duo/NOTICE.md +53 -0
  44. package/pipeline/skills/shared/external/ios-iphone-duo/SKILL.md +170 -0
  45. package/pipeline/skills/shared/external/ios-iphone-duo/evals/evals.json +50 -0
  46. package/pipeline/skills/shared/external/ios-iphone-duo/evals/trigger-eval.json +22 -0
  47. package/pipeline/skills/shared/external/ios-iphone-duo/references/api-map.md +212 -0
  48. package/pipeline/skills/shared/external/ios-iphone-duo/references/fixed-assumptions.md +145 -0
  49. package/pipeline/skills/shared/external/ios-iphone-duo/references/poses-and-panes.md +122 -0
  50. package/pipeline/skills/shared/external/ios-sf-symbols/SKILL.md +177 -0
  51. package/pipeline/skills/shared/external/ios-sf-symbols/evals/evals.json +49 -0
  52. package/pipeline/skills/shared/external/ios-sf-symbols/evals/trigger-eval.json +22 -0
  53. package/pipeline/skills/shared/external/ios-sf-symbols/references/effects-and-custom-symbols.md +135 -0
  54. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +38 -50
  55. package/pipeline/skills/shared/external/swiftui-animation/evals/evals.json +47 -0
  56. package/pipeline/skills/shared/external/swiftui-animation/evals/trigger-eval.json +22 -0
  57. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +7 -73
  58. package/pipeline/skills/shared/external/swiftui-animation/references/scroll-effects.md +185 -0
  59. package/pipeline/skills/shared/external/swiftui-expert-skill/SKILL.md +6 -6
  60. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-advanced.md +3 -253
  61. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-basics.md +4 -47
  62. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-transitions.md +3 -111
  63. package/pipeline/skills/shared/external/swiftui-expert-skill/references/liquid-glass.md +11 -381
  64. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +184 -224
  65. package/pipeline/skills/shared/external/swiftui-liquid-glass/evals/evals.json +47 -0
  66. package/pipeline/skills/shared/external/swiftui-liquid-glass/evals/trigger-eval.json +22 -0
  67. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +246 -33
  68. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/uikit-glass.md +227 -0
  69. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +15 -18
  70. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/components-index.md +1 -1
  71. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/haptics.md +6 -68
  72. package/pipeline/skills/skills-index.md +16 -11
  73. 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
+ ]