@mmerterden/multi-agent-pipeline 20.17.1 → 20.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/docs/facts.json +6 -6
  3. package/install/_mcp-register.mjs +1 -1
  4. package/install/_xcode-skills.mjs +263 -0
  5. package/install/catalog-history.json +1 -1
  6. package/install/claude.mjs +23 -0
  7. package/install/codex.mjs +11 -0
  8. package/install/copilot.mjs +9 -0
  9. package/manifest.json +85 -45
  10. package/package.json +1 -1
  11. package/pipeline/commands/multi-agent/stack/SKILL.md +2 -0
  12. package/pipeline/lib/xcode-plugin.mjs +131 -0
  13. package/pipeline/multi-agent-refs/features/doctor.md +10 -0
  14. package/pipeline/scripts/doctor.mjs +38 -0
  15. package/pipeline/scripts/uninstall.mjs +57 -1
  16. package/pipeline/skills/.skill-manifest.json +33 -13
  17. package/pipeline/skills/.skills-index.json +66 -11
  18. package/pipeline/skills/shared/README.md +18 -13
  19. package/pipeline/skills/shared/external/NOTICE-avdlee-swiftui-agent-skill.md +3 -1
  20. package/pipeline/skills/shared/external/NOTICE-dimillian-skills.md +2 -1
  21. package/pipeline/skills/shared/external/hig-app-icons/SKILL.md +159 -0
  22. package/pipeline/skills/shared/external/hig-app-icons/evals/evals.json +48 -0
  23. package/pipeline/skills/shared/external/hig-app-icons/evals/trigger-eval.json +22 -0
  24. package/pipeline/skills/shared/external/hig-app-icons/references/icon-workflow.md +115 -0
  25. package/pipeline/skills/shared/external/hig-components-layout/SKILL.md +32 -4
  26. package/pipeline/skills/shared/external/hig-components-layout/evals/evals.json +47 -0
  27. package/pipeline/skills/shared/external/hig-components-layout/evals/trigger-eval.json +22 -0
  28. package/pipeline/skills/shared/external/hig-components-layout/references/bars-and-tabs.md +231 -0
  29. package/pipeline/skills/shared/external/hig-components-layout/references/containers.md +5 -30
  30. package/pipeline/skills/shared/external/hig-components-status/SKILL.md +1 -1
  31. package/pipeline/skills/shared/external/hig-foundations/SKILL.md +23 -4
  32. package/pipeline/skills/shared/external/hig-foundations/evals/evals.json +47 -0
  33. package/pipeline/skills/shared/external/hig-foundations/evals/trigger-eval.json +21 -0
  34. package/pipeline/skills/shared/external/hig-foundations/references/foundations-detail.md +34 -26
  35. package/pipeline/skills/shared/external/hig-foundations/references/type-color-spacing.md +219 -0
  36. package/pipeline/skills/shared/external/hig-inputs/SKILL.md +5 -4
  37. package/pipeline/skills/shared/external/hig-patterns/SKILL.md +19 -3
  38. package/pipeline/skills/shared/external/hig-patterns/references/onboarding-and-states.md +188 -0
  39. package/pipeline/skills/shared/external/hig-patterns/references/patterns.md +5 -11
  40. package/pipeline/skills/shared/external/hig-platforms/SKILL.md +17 -5
  41. package/pipeline/skills/shared/external/hig-platforms/references/ipad-layout.md +218 -0
  42. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +4 -1
  43. package/pipeline/skills/shared/external/ios-accessibility/references/large-content-and-appearance.md +135 -0
  44. package/pipeline/skills/shared/external/ios-design-review/SKILL.md +146 -0
  45. package/pipeline/skills/shared/external/ios-design-review/evals/evals.json +48 -0
  46. package/pipeline/skills/shared/external/ios-design-review/evals/trigger-eval.json +22 -0
  47. package/pipeline/skills/shared/external/ios-design-review/references/appearance-matrix.md +135 -0
  48. package/pipeline/skills/shared/external/ios-design-review/references/checklist.md +193 -0
  49. package/pipeline/skills/shared/external/ios-haptics/SKILL.md +191 -0
  50. package/pipeline/skills/shared/external/ios-haptics/evals/evals.json +49 -0
  51. package/pipeline/skills/shared/external/ios-haptics/evals/trigger-eval.json +22 -0
  52. package/pipeline/skills/shared/external/ios-haptics/references/core-haptics.md +215 -0
  53. package/pipeline/skills/shared/external/ios-iphone-duo/NOTICE.md +53 -0
  54. package/pipeline/skills/shared/external/ios-iphone-duo/SKILL.md +179 -0
  55. package/pipeline/skills/shared/external/ios-iphone-duo/evals/evals.json +65 -0
  56. package/pipeline/skills/shared/external/ios-iphone-duo/evals/trigger-eval.json +22 -0
  57. package/pipeline/skills/shared/external/ios-iphone-duo/references/api-map.md +212 -0
  58. package/pipeline/skills/shared/external/ios-iphone-duo/references/fixed-assumptions.md +202 -0
  59. package/pipeline/skills/shared/external/ios-iphone-duo/references/poses-and-panes.md +122 -0
  60. package/pipeline/skills/shared/external/ios-sf-symbols/SKILL.md +177 -0
  61. package/pipeline/skills/shared/external/ios-sf-symbols/evals/evals.json +49 -0
  62. package/pipeline/skills/shared/external/ios-sf-symbols/evals/trigger-eval.json +22 -0
  63. package/pipeline/skills/shared/external/ios-sf-symbols/references/effects-and-custom-symbols.md +135 -0
  64. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +38 -50
  65. package/pipeline/skills/shared/external/swiftui-animation/evals/evals.json +47 -0
  66. package/pipeline/skills/shared/external/swiftui-animation/evals/trigger-eval.json +22 -0
  67. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +7 -73
  68. package/pipeline/skills/shared/external/swiftui-animation/references/scroll-effects.md +185 -0
  69. package/pipeline/skills/shared/external/swiftui-expert-skill/SKILL.md +6 -6
  70. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-advanced.md +3 -253
  71. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-basics.md +4 -47
  72. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-transitions.md +3 -111
  73. package/pipeline/skills/shared/external/swiftui-expert-skill/references/liquid-glass.md +11 -381
  74. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +184 -224
  75. package/pipeline/skills/shared/external/swiftui-liquid-glass/evals/evals.json +47 -0
  76. package/pipeline/skills/shared/external/swiftui-liquid-glass/evals/trigger-eval.json +22 -0
  77. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +246 -33
  78. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/uikit-glass.md +227 -0
  79. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +15 -18
  80. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/components-index.md +1 -1
  81. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/haptics.md +6 -68
  82. package/pipeline/skills/skills-index.md +16 -11
  83. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +0 -786
@@ -0,0 +1,212 @@
1
+ # iPhone Duo API map
2
+
3
+ Every API this skill relies on, grouped by job, with the iOS version that
4
+ introduced it. *(27.1 beta)* marks symbols documented as beta in the iOS 27.1
5
+ SDK; their names and behaviour may change before release.
6
+
7
+ ## Contents
8
+
9
+ 1. Size, screen and shape
10
+ 2. Reserved regions
11
+ 3. Arrangements
12
+ 4. Vertical bars
13
+ 5. Sheets
14
+ 6. Hinge
15
+ 7. Scenes and accessories
16
+
17
+ ## 1. Size, screen and shape
18
+
19
+ | Job | SwiftUI | UIKit | iOS |
20
+ |---|---|---|---|
21
+ | Size class | `horizontalSizeClass`, `verticalSizeClass` environment | trait collection size classes | 13 |
22
+ | Space actually available | `GeometryReader`, `onGeometryChange(for:of:action:)` | `view.bounds`, `viewDidLayoutSubviews`, `layoutSubviews` | 13 / 16 |
23
+ | Display scale | `displayScale` environment | `traitCollection.displayScale` | 13 / 8 |
24
+ | A window for a scene | | `UIWindow(windowScene:)` | 13 |
25
+ | The scene's screen, if truly needed | | `view.window?.windowScene?.screen` | 13 |
26
+ | Choose among layouts | `ViewThatFits`, `AnyLayout` | | 16 |
27
+ | Size relative to container | `containerRelativeFrame(_:alignment:)` | Auto Layout | 17 |
28
+ | Corners concentric with the device | `ConcentricRectangle` | `UICornerConfiguration`, `UIView.cornerConfiguration` | 26 |
29
+
30
+ `UIScreen.main` is deprecated as of iOS 26. Apple's guidance is to base layout
31
+ on the scene or containing view's bounds and on size classes, and not to use
32
+ `userInterfaceIdiom` or `UIInterfaceOrientation` for layout decisions.
33
+
34
+ ## 2. Reserved regions
35
+
36
+ | Job | SwiftUI | UIKit |
37
+ |---|---|---|
38
+ | Region type | `ReservedRegion` *(27.1 beta)* | `UIView.ReservedRegion` *(27.1 beta)* |
39
+ | Query | `GeometryProxy.reservedRegions(kind:options:layoutDirectionBehavior:)` *(27.1 beta)* | `UIView.reservedRegions(kind:options:)` *(27.1 beta)* |
40
+ | Kinds | `.division` (fold), `.occlusion` (cameras) | same |
41
+ | Options | `.includeInactive` | same |
42
+ | Properties | `frame` (includes margins), `margins`, `isActive`, `kind`, `id` | `frame`, `margins`, `isActive`, `kind` (no `id`) |
43
+
44
+ ```swift
45
+ struct FoldAwareGrid<Content: View>: View {
46
+ @ViewBuilder var content: (_ columns: Int) -> Content
47
+
48
+ var body: some View {
49
+ GeometryReader { proxy in
50
+ let hasFold = !proxy.reservedRegions(kind: .division, options: .includeInactive).isEmpty
51
+ let base = proxy.size.width > proxy.size.height ? 3 : 2
52
+ content(hasFold && base.isMultiple(of: 2) == false ? base + 1 : base)
53
+ }
54
+ }
55
+ }
56
+ ```
57
+
58
+ Notes:
59
+
60
+ - The query returns regions that intersect the view whether active or not.
61
+ Filter `isActive` when positioning for the current pose.
62
+ - `frame` already contains `margins`, the clearance kept for interactive
63
+ content. Avoiding `frame` is enough for controls; inset `frame` by
64
+ `margins` when full-bleed media may run up to the bare hardware area.
65
+ - SwiftUI mirrors region frames for right-to-left by default
66
+ (`layoutDirectionBehavior: .mirrors`), so a custom `Layout` can compare them
67
+ with subview frames directly. Pass `.fixed` for the physical frames.
68
+ - System containers (split views, lists, scroll views, sheets, alerts, menus)
69
+ already avoid reserved regions; custom views and custom layouts are the
70
+ ones that need this API.
71
+
72
+ ## 3. Arrangements
73
+
74
+ | Job | SwiftUI | UIKit |
75
+ |---|---|---|
76
+ | Container | `ArrangementView(primary:secondary:)` *(27.1 beta)* | `UIArrangementViewController` *(27.1 beta)* |
77
+ | Choose style | `.arrangementViewStyle(.split)` / `.overlay` / `.automatic` | `updateArrangement(_:animated:)` with `UISplitArrangement` / `UIOverlayArrangement` |
78
+ | Limit axes | `.split.axes(.vertical)`, `.overlay.axes(...)` | `axes(_:)` on the arrangement |
79
+ | Assign content | the two view builders | `setViewController(_:for:animated:)` with `.primary` / `.secondary` |
80
+ | Split sizing | `splitArrangementLayoutRatio(_:)`, `splitArrangementLayoutSize(...)`, `splitArrangementFixedLayoutSize(...)` | `UISplitArrangement.Dimension` |
81
+ | Overlay details | `overlayArrangementEdge(_:)`, `overlayArrangementZIndex` environment | `state(for:)` |
82
+
83
+ ```swift
84
+ ArrangementView {
85
+ NowPlayingView()
86
+ } secondary: {
87
+ LyricsView()
88
+ }
89
+ .arrangementViewStyle(.split)
90
+
91
+ ArrangementView {
92
+ PlayerControls()
93
+ } secondary: {
94
+ VideoSurface()
95
+ }
96
+ .arrangementViewStyle(.overlay)
97
+ ```
98
+
99
+ The automatic style resolves to split. Split goes side by side when wider than
100
+ tall and stacks when taller than wide; overlay layers primary over secondary
101
+ when closed or fully open and places them on either side of the fold when
102
+ partially open (primary trailing or bottom, secondary leading or top).
103
+
104
+ Placement rules: navigation containers go around an arrangement, never inside
105
+ it; an arrangement does not go inside `NavigationSplitView`, `List`,
106
+ `ScrollView` or another container that could make part of it unreachable.
107
+
108
+ ## 4. Vertical bars
109
+
110
+ | Job | SwiftUI | UIKit | iOS |
111
+ |---|---|---|---|
112
+ | Bars that can go vertical | `.toolbar` on `NavigationStack` / `NavigationSplitView` | items on a view controller in a navigation controller | |
113
+ | Custom Back or Close | `ToolbarItem(placement: .cancellationAction)` | `leadingItemGroups` | 14 / 16 |
114
+ | Prominent action | `topBarPinnedTrailing` | `pinnedTrailingGroup` | 27 / 16 |
115
+ | Group items | `ToolbarItemGroup` | `UIBarButtonItemGroup` | 14 |
116
+ | Title plus symbol | `Label` | `UIBarButtonItem` title and image | |
117
+ | Overflow order | `visibilityPriority(_:)`, `ToolbarItemVisibilityPriority` (`.automatic`, `.low`, `.high`, `init(higherThan:)`, `init(lowerThan:)`) | `visibilityPriority`, `UIBarButtonItemVisibilityPriority` | 27 |
118
+ | Put items in overflow | `ToolbarOverflowMenu` | `additionalOverflowItems` | 27 / 16 |
119
+ | Keep an item horizontal | `axisBehavior(_:)` with `ToolbarItemAxisBehavior` (`.automatic`, `.horizontalOnly`, `.verticalPreferred`) | `axisBehavior`, `UIBarButtonItem.AxisBehavior` | *(27.1 beta)* |
120
+ | Tab bar versus toolbar when tight | `toolbarVerticalCompressionBehavior(_:)` (`.automatic`, `.prefersTabBar`, `.prefersToolbarItems`) | `UIVerticalBarCompressionBehavior` | *(27.1 beta)* |
121
+ | Opt a screen out | `toolbarVerticalBehavior(.disabled)` | `preferredVerticalBarBehavior`, `setNeedsUpdateOfVerticalBarConfiguration()` | *(27.1 beta)* |
122
+ | Which edge the bar prefers | `toolbarVerticalEdge` environment (`HorizontalEdge?`) | `verticalBarEdge` trait (`UIVerticalBarEdge`) | *(27.1 beta)* |
123
+ | Image under the bar | `backgroundExtensionEffect()` | `UIBackgroundExtensionView` | 26 |
124
+
125
+ ```swift
126
+ NavigationStack {
127
+ EditorView()
128
+ .toolbar {
129
+ ToolbarItem(placement: .cancellationAction) {
130
+ Button("Close", systemImage: "xmark") { dismiss() }
131
+ }
132
+ ToolbarItem(placement: .topBarPinnedTrailing) {
133
+ Button("Done", systemImage: "checkmark") { save() }
134
+ }
135
+ ToolbarItemGroup {
136
+ Button("Bold", systemImage: "bold") { toggleBold() }
137
+ Button("Italic", systemImage: "italic") { toggleItalic() }
138
+ }
139
+ .visibilityPriority(.low)
140
+ }
141
+ }
142
+ ```
143
+
144
+ How items are shown: vertically with their icon; horizontally with icon or
145
+ title (icon preferred); in overflow with both. An item with only a title or a
146
+ custom view is never placed vertically.
147
+
148
+ Contexts that choose differently: inspectors keep horizontal bars; a split
149
+ view uses horizontal bars for sidebar and content columns and vertical bars
150
+ for the detail column; sheets on the outer display go vertical by default.
151
+
152
+ `toolbarVerticalBehavior` is resolved per container: the top view of a
153
+ `NavigationStack`, the selected tab of a `TabView`, the trailing column of a
154
+ `NavigationSplitView`. To hide bars rather than change their axis, use
155
+ `toolbarVisibility(_:for:)`.
156
+
157
+ `toolbarVerticalEdge` reports the preferred edge even when no vertical bar is
158
+ currently visible, and nil where the system never uses one:
159
+
160
+ ```swift
161
+ @Environment(\.toolbarVerticalEdge) private var barEdge
162
+
163
+ FloatingPalette()
164
+ .frame(maxWidth: .infinity, alignment: barEdge == .leading ? .leading : .trailing)
165
+ ```
166
+
167
+ ## 5. Sheets
168
+
169
+ On the inner display, sheets use horizontal toolbars when centred or leading
170
+ and vertical ones when trailing. Choose placement with
171
+ `presentationPlacement(_:)` (iOS 27) or `UISheetPresentationController.preferredPlacement`
172
+ (iOS 27). On the outer display, a sheet with a single action can opt out of the
173
+ vertical bar with `toolbarVerticalBehavior(.disabled)`.
174
+
175
+ ## 6. Hinge
176
+
177
+ | | SwiftUI *(27.1 beta)* | UIKit *(27.1 beta)* |
178
+ |---|---|---|
179
+ | Observe | `onHingeChange(isEnabled:_:)`, old and new `DeviceHingeContext` | `UIHingeInteraction(updateHandler:)`, `(interaction, update)` |
180
+ | Hinge value | `DeviceHingeContext.hinge: DeviceHinge?` | `Update.hinge: UIHinge?` |
181
+ | Angle | `angle: Angle` | `angle: CGFloat`, radians |
182
+ | Status | `DeviceHinge.Status` struct: `.closed`, `.partiallyOpen`, `.fullyOpen` | `UIHinge.Status` enum: same plus `.unknown` |
183
+
184
+ ```swift
185
+ @State private var hingeStatus: DeviceHinge.Status?
186
+
187
+ var body: some View {
188
+ Canvas3DView(tilted: hingeStatus == .partiallyOpen)
189
+ .onHingeChange { _, new in
190
+ hingeStatus = new.hinge?.status
191
+ }
192
+ }
193
+ ```
194
+
195
+ - `hinge` is nil where there is no hinge; treat that as the normal phone case.
196
+ - The UIKit handler runs with the initial state, on changes, and when the
197
+ interaction moves between hierarchies. It escapes, so capture weakly.
198
+ - Status comes from angle and orientation together; do not rebuild it from
199
+ angle thresholds. Update rate and precision are system policy.
200
+ - Use the hinge for interactions and effects. Layout belongs to reserved
201
+ regions, arrangements and size classes.
202
+
203
+ ## 7. Scenes and accessories
204
+
205
+ - `sceneAccessory(content:)` (iOS 27) and UIKit's scene accessory
206
+ registration (`registerSceneAccessory(_:)`) attach content to another
207
+ display, for example a camera capture accessory on the outer display while
208
+ the app runs on the inner one. See Apple's article on registering a camera
209
+ capture accessory on iPhone Duo.
210
+ - New windows depend on what the current display allows. Prefer
211
+ `UIWindowScene.ActivationAction` for "open in new window" menu items, and
212
+ handle a failed scene request instead of assuming success.
@@ -0,0 +1,202 @@
1
+ # Fixed assumptions: grep list and replacements
2
+
3
+ Run these from the repository root. Every hit is a candidate, not a verdict:
4
+ read the surrounding code, decide whether it affects layout, and confirm on
5
+ the iPhone Duo simulator. Exclude vendored and generated code from the count.
6
+
7
+ ## Contents
8
+
9
+ 1. The search
10
+ 2. Orientation
11
+ 3. The screen
12
+ 4. Cached sizes
13
+ 5. Idiom and size class
14
+ 6. Device sizes and thresholds
15
+ 7. Insets
16
+ 8. Bars
17
+ 9. Media
18
+ 10. Life cycle, legacy guides and the keyboard
19
+ 11. Recording the result
20
+
21
+ ## 1. The search
22
+
23
+ ```bash
24
+ rg -n --type swift \
25
+ -e 'UIScreen\.main' \
26
+ -e 'UIScreen\.screens' \
27
+ -e 'UIDevice\.current\.orientation' \
28
+ -e 'interfaceOrientation' \
29
+ -e 'statusBarOrientation' \
30
+ -e 'userInterfaceIdiom' \
31
+ -e 'horizontalSizeClass\s*==\s*\.regular' \
32
+ -e 'UIWindow\(frame:' \
33
+ -e 'safeAreaInsets\.(left|right|top|bottom)\s*\*\s*2' \
34
+ -e '(width|height)\s*(==|>=|<=|>|<)\s*(320|375|390|393|402|414|428|430|440)\b' \
35
+ -e 'UIToolbar\(|UINavigationBar\(|UITabBar\(' \
36
+ -e 'scaleAspectFill|contentMode:\s*\.fill' \
37
+ -e 'topLayoutGuide|bottomLayoutGuide' \
38
+ -e 'keyboardWillShowNotification|keyboardFrameEndUserInfoKey' \
39
+ -e 'applicationDidBecomeActive|applicationWillResignActive|applicationDidEnterBackground|applicationWillEnterForeground' \
40
+ .
41
+ ```
42
+
43
+ The scene manifest lives in Info.plist, which the Swift search does not read:
44
+
45
+ ```bash
46
+ rg -l 'UIApplicationSceneManifest' -g '*.plist' . || echo "no scene manifest"
47
+ ```
48
+
49
+ Add Objective-C (`--type objc`) and storyboards (`-g '*.storyboard'` for
50
+ fixed-size constraints) when the project has them.
51
+
52
+ ## 2. Orientation
53
+
54
+ Hits: `UIDevice.current.orientation`, `interfaceOrientation` (including
55
+ `windowScene.effectiveGeometry.interfaceOrientation`), `statusBarOrientation`.
56
+
57
+ Why: on a foldable device orientation no longer says whether the app is wide
58
+ or tall, and Apple's guidance is not to use `UIInterfaceOrientation` for
59
+ layout decisions.
60
+
61
+ Replace: compare the width and height of the space you have, `view.bounds` in
62
+ a view controller, `bounds` of the superview in a view, the size from
63
+ `GeometryReader` or `onGeometryChange` in SwiftUI. Keep orientation only for
64
+ things that are about the device's physical attitude (camera capture
65
+ rotation), not about layout.
66
+
67
+ ## 3. The screen
68
+
69
+ Hits: `UIScreen.main` (deprecated in iOS 26), `UIScreen.screens`,
70
+ `UIWindow(frame: UIScreen.main.bounds)`.
71
+
72
+ | Instead of | Use |
73
+ |---|---|
74
+ | `UIScreen.main.bounds` | `view.bounds`, `window.bounds`, `GeometryReader`, `onGeometryChange` |
75
+ | `UIScreen.main.scale` | `traitCollection.displayScale`, `@Environment(\.displayScale)` |
76
+ | `UIWindow(frame: UIScreen.main.bounds)` | `UIWindow(windowScene:)` |
77
+ | A stored `UIScreen` | `view.window?.windowScene?.screen`, read when needed |
78
+
79
+ ## 4. Cached sizes
80
+
81
+ Hard to grep; look for sizes stored in properties at `viewDidLoad`,
82
+ `viewIsAppearing`, `init`, `onAppear`, or a `GeometryReader` value copied into
83
+ `@State` once.
84
+
85
+ Why: folding and unfolding resizes the app during a session. A size read at
86
+ launch is wrong after the first fold.
87
+
88
+ Replace: do size-dependent layout in `viewDidLayoutSubviews` or
89
+ `layoutSubviews`; use `viewWillTransition(to:with:)` for work tied to the
90
+ change itself; in SwiftUI derive layout from the current geometry every time,
91
+ or keep it current with `onGeometryChange`.
92
+
93
+ ## 5. Idiom and size class
94
+
95
+ Hits: `userInterfaceIdiom == .pad`, `horizontalSizeClass == .regular` used to
96
+ mean "iPad", and code that assumes iPhone is always compact.
97
+
98
+ Why: the inner display is regular width on an iPhone. An iPad-only layout
99
+ branch now runs on a phone, and a "phone means compact" branch never expects
100
+ regular.
101
+
102
+ Replace: branch on size class and available space only. Check both
103
+ directions: code that fires on regular, and code that assumes compact.
104
+
105
+ ## 6. Device sizes and thresholds
106
+
107
+ Hits: comparisons with known iPhone widths or heights, device lookup tables,
108
+ "if width > 390" breakpoints.
109
+
110
+ Why: no table contains this device, and Split View produces sizes no device
111
+ has.
112
+
113
+ Replace: size classes (compact for the outer display, regular for the inner),
114
+ `ViewThatFits`, `containerRelativeFrame`, proportional and intrinsic sizing.
115
+
116
+ ## 7. Insets
117
+
118
+ Hits: `safeAreaInsets.left * 2`, `width - 2 * inset`, `insets.left` used for
119
+ both sides, padding that mirrors one edge onto the other.
120
+
121
+ Why: side-mounted bars make safe areas and layout margins asymmetric, and the
122
+ heavy side depends on pose and Split View position.
123
+
124
+ Replace: inset each edge by its own value, or constrain to the safe area
125
+ layout guide and layout margins and let the system resolve each edge.
126
+
127
+ ## 8. Bars
128
+
129
+ Hits: `UIToolbar(`, `UINavigationBar(`, `UITabBar(` created by hand, or a
130
+ SwiftUI `HStack` styled as a bar pinned to the top or bottom.
131
+
132
+ Why: only bars provided by navigation containers take part in vertical
133
+ placement. A custom bar stays where it is and collides with the system's
134
+ vertical controls.
135
+
136
+ Replace: toolbar items on `NavigationStack` / `NavigationSplitView` with
137
+ `.toolbar`, or on a view controller inside a navigation controller; `TabView`
138
+ or `UITabBarController` for tabs.
139
+
140
+ ## 9. Media
141
+
142
+ Hits: `scaleAspectFill`, `.aspectRatio(contentMode: .fill)` on hero images
143
+ and video.
144
+
145
+ Why: wider displays crop more of the image and can lose the subject.
146
+
147
+ Replace: choose fill or fit from the current size or aspect ratio, crop around
148
+ a focal point, and extend backgrounds under bars with
149
+ `backgroundExtensionEffect()` rather than stretching the image.
150
+
151
+ ## 10. Life cycle, legacy guides and the keyboard
152
+
153
+ **Scene life cycle.** Hits: the four `application...` foreground and background
154
+ callbacks doing real work in the app delegate, no `UIApplicationSceneManifest`
155
+ in Info.plist, no `UIWindowSceneDelegate`.
156
+
157
+ Why: from iOS 27, an app built with the latest SDK that has not adopted the
158
+ scene-based life cycle fails to launch. The system also delivers foreground and
159
+ background per scene, so app-level callbacks miss what happens to one window
160
+ of several.
161
+
162
+ Replace: a scene manifest and a scene delegate that creates the window with
163
+ `UIWindow(windowScene:)`, and the per-window work moved to the scene callbacks
164
+ (`sceneDidBecomeActive`, `sceneWillResignActive`, `sceneDidEnterBackground`,
165
+ `sceneWillEnterForeground`). Work that runs once per process stays in
166
+ `application(_:didFinishLaunchingWithOptions:)`. Supporting several windows is
167
+ a separate, optional step.
168
+
169
+ **Legacy layout guides.** Hits: `topLayoutGuide`, `bottomLayoutGuide`, and
170
+ constants standing in for the status bar or navigation bar height.
171
+
172
+ Why: those guides are deprecated and know nothing about side insets; a fixed
173
+ bar height is wrong as soon as a bar moves or the status bar changes.
174
+
175
+ Replace: `safeAreaLayoutGuide` and layout margins.
176
+
177
+ **The keyboard.** Hits: frames computed from `keyboardFrameEndUserInfoKey` in
178
+ a `keyboardWillShowNotification` handler.
179
+
180
+ Why: a screen-space keyboard frame needs converting into the window by hand,
181
+ and frame arithmetic misses the cases a resized window creates.
182
+
183
+ Replace: constraints to `view.keyboardLayoutGuide` (iOS 15); SwiftUI already
184
+ avoids the keyboard through the safe area.
185
+
186
+ **Stored insets.** Hits: a safe-area inset copied into a property at load
187
+ time.
188
+
189
+ Why: insets change with pose, bar placement and Split View position.
190
+
191
+ Replace: read them where layout happens, and react in
192
+ `viewSafeAreaInsetsDidChange()` / `safeAreaInsetsDidChange()`.
193
+
194
+ **Before replacing, classify.** An idiom or orientation check is a defect
195
+ only when it stands in for available space. A check for a genuine capability of
196
+ the device, or an orientation lock for a camera or game, stays.
197
+
198
+ ## 11. Recording the result
199
+
200
+ For each hit, record `file:line`, the category, keep or fix, and for fixes the
201
+ replacement. Separate "confirmed on the simulator" from "found in code only".
202
+ Pose-dependent behaviour is not proven until it has been seen running.
@@ -0,0 +1,122 @@
1
+ # Poses, panes and the design review
2
+
3
+ Design guidance for iPhone Duo. The rules marked HIG come from Apple's
4
+ Designing for iPhone Duo page; the pattern table is a recommendation built on
5
+ those rules and on size classes, and should be adapted to the app's content.
6
+
7
+ ## Contents
8
+
9
+ 1. Poses
10
+ 2. Pose to pattern
11
+ 3. Using the inner display
12
+ 4. Dual-pane rules
13
+ 5. Controls on the side
14
+ 6. The fold
15
+ 7. Review checklist
16
+
17
+ ## 1. Poses
18
+
19
+ People hold and set down iPhone Duo in several ways (HIG): closed, using the
20
+ outer display; open flat on the inner display; partially folded like a book;
21
+ placed on a surface; standing on its edges. Every pose can also be rotated,
22
+ and Split View can put two apps side by side on the inner display.
23
+
24
+ Supporting poses does not mean a custom layout per pose (HIG). A compact
25
+ width layout for the outer display and a regular width layout for the inner
26
+ display cover the fundamentals; the existing layout expands into the space.
27
+
28
+ ## 2. Pose to pattern
29
+
30
+ | Pose | Size class | What usually works |
31
+ |---|---|---|
32
+ | Closed, outer display | compact width | The phone layout. Bars on the side free vertical space; content offset from the bar side |
33
+ | Open, inner display, landscape | regular width | One more level of hierarchy: list and detail side by side, or content reflowed into columns. Bars stay on the side |
34
+ | Open, inner display, portrait | regular width | Same hierarchy as landscape; horizontal bars return because vertical space is plentiful |
35
+ | Partially folded (book) | regular width | A split that rebalances around the fold; even column counts in grids; nothing tappable in the curve |
36
+ | On a surface, partly folded | regular width | Viewing on the upper part, touch on the lower, stable part; suits media and hands-free use |
37
+ | Standing on edges | depends on orientation | Glanceable, hands-free content with few controls |
38
+ | Split View | either, per half | Each app keeps its controls on its outer edge; the app must work at half width |
39
+
40
+ The on-surface layout is optional and still has to offer every control the
41
+ other poses offer.
42
+
43
+ ## 3. Using the inner display
44
+
45
+ A stretched phone layout is the failure to avoid. Options, in rough order of
46
+ cost:
47
+
48
+ - **Split view**: `NavigationSplitView` shows list and detail together when
49
+ open and collapses to a stack when closed. The hierarchy is the same; the
50
+ inner display shows one more level of it (HIG). Mail shows the list or a
51
+ message when closed and both side by side when open.
52
+ - **Reflow**: a single column becomes two columns when width allows
53
+ (`ViewThatFits`, `AnyLayout`, adaptive grids).
54
+ - **Arrangement**: two related views (now playing and lyrics, controls and
55
+ video) in an `ArrangementView` that adapts to the fold.
56
+ - **Sidebar tabs**: a sidebar-adaptable tab view for information-dense apps.
57
+
58
+ ## 4. Dual-pane rules
59
+
60
+ - Same functions and state on both displays (HIG). Opening or closing the
61
+ device mid-task must not lose a selection, a scroll position or a draft.
62
+ - Never tie a function to a pose (HIG). Everything reachable in one pose is
63
+ reachable in all of them, even if it moves into overflow.
64
+ - The secondary pane depends on the primary: selecting in the list drives the
65
+ detail. When the device closes, the detail the person was reading should be
66
+ what they see, not the list.
67
+ - Keep controls with the content they act on (HIG). Actions for the list sit
68
+ above the list pane, not in the side bar beside the detail.
69
+ - Each pane keeps its own safe area and margins; do not let one pane's insets
70
+ bleed into the other.
71
+ - Prefer containers that rebalance themselves around the fold over manual
72
+ positioning (HIG), and in grids prefer an even number of columns so content
73
+ divides cleanly.
74
+
75
+ ## 5. Controls on the side
76
+
77
+ - Follow the system's vertical placement and do not override it without a
78
+ strong reason (HIG); it is one of the device's core patterns.
79
+ - Top of the vertical axis: Back or Close, then prominent actions such as
80
+ Done; remaining items keep their original groupings (HIG).
81
+ - Give every item a symbol and a title; keep text-only buttons to a minimum,
82
+ because text items stay in a horizontal bar (HIG).
83
+ - Assign visibility priority so frequent actions and items with badges stay
84
+ visible longest (HIG).
85
+ - Use the system overflow menu instead of a custom one; reserve the ellipsis
86
+ for overflow (HIG).
87
+ - Content next to side controls is asymmetric. Offset content by the safe
88
+ area by default; centre on the full display only for immersive,
89
+ non-scrolling screens with nothing interactive under the controls; or let a
90
+ background span the full width while the scrollable, interactive content
91
+ stays inset (HIG).
92
+
93
+ ## 6. The fold
94
+
95
+ - Keep interactive elements out of the folding region (HIG). System controls
96
+ are moved aside automatically; custom ones use reserved regions.
97
+ - Move as little as possible as the device folds (HIG). Small adjustments
98
+ that keep elements visible beat controls that vanish or jump.
99
+ - Scrolling content may pass through the fold; fixed text and controls
100
+ should not rest in it.
101
+
102
+ ## 7. Review checklist
103
+
104
+ Use with `ios-design-review` for the general visual pass.
105
+
106
+ 1. Is anything interactive in the fold or under the side controls?
107
+ 2. Is any function or piece of state available in one pose but not another?
108
+ 3. Does state survive opening, closing and folding mid-task?
109
+ 4. Are there fixed widths or device breakpoints instead of size classes?
110
+ 5. Do toolbar items have symbols and titles, sensible priorities, and does the
111
+ overflow hold the right things?
112
+ 6. Does the inner display do better than stretching (split, reflow,
113
+ arrangement)?
114
+ 7. Is content offset from the side controls, deliberately centred, or mixed
115
+ with every control inside the inset area?
116
+ 8. Does the screen hold up in Split View on both sides, at half width?
117
+ 9. Do custom components reimplement fold avoidance the system components
118
+ would have provided?
119
+ 10. Are controls next to the content they affect?
120
+ 11. Do grids divide evenly around the fold?
121
+ 12. Which of the above were seen on the simulator, and which only reasoned
122
+ from design files or code?