@mmerterden/multi-agent-pipeline 20.35.0 → 20.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/README.md +6 -5
- package/README.tr.md +6 -5
- package/docs/architecture.md +3 -3
- package/docs/ecosystem.md +5 -5
- package/docs/facts.json +4 -4
- package/install/catalog-history.json +1 -1
- package/manifest.json +210 -136
- package/package.json +5 -1
- package/pipeline/commands/multi-agent/SKILL.md +33 -154
- package/pipeline/commands/multi-agent/analysis/SKILL.md +14 -68
- package/pipeline/commands/multi-agent/analysis-jira/SKILL.md +5 -8
- package/pipeline/commands/multi-agent/autopilot-on/SKILL.md +38 -109
- package/pipeline/commands/multi-agent/channels/SKILL.md +38 -167
- package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +37 -144
- package/pipeline/commands/multi-agent/design-check/SKILL.md +27 -131
- package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +22 -48
- package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +37 -180
- package/pipeline/commands/multi-agent/jira/SKILL.md +16 -46
- package/pipeline/commands/multi-agent/office-pair/SKILL.md +66 -0
- package/pipeline/commands/multi-agent/office-status/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/refactor/SKILL.md +23 -184
- package/pipeline/commands/multi-agent/resume/SKILL.md +30 -136
- package/pipeline/commands/multi-agent/review/SKILL.md +24 -199
- package/pipeline/commands/multi-agent/setup/SKILL.md +25 -181
- package/pipeline/commands/multi-agent/stack/SKILL.md +9 -53
- package/pipeline/commands/multi-agent/status/SKILL.md +13 -2
- package/pipeline/commands/multi-agent/store-ready/SKILL.md +27 -226
- package/pipeline/commands/multi-agent/sync/SKILL.md +38 -167
- package/pipeline/commands/multi-agent/test-gen/SKILL.md +2 -4
- package/pipeline/commands/multi-agent/update/SKILL.md +18 -60
- package/pipeline/contract/CHANGELOG.md +9 -0
- package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
- package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
- package/pipeline/contract/fixtures/error-unsigned.json +1 -1
- package/pipeline/contract/fixtures/runs-awaiting-question.json +1 -1
- package/pipeline/contract/fixtures/runs-empty.json +1 -1
- package/pipeline/contract/fixtures/runs-failed.json +1 -1
- package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +1 -1
- package/pipeline/contract/fixtures/runs-pr-opened.json +1 -1
- package/pipeline/contract/fixtures/runs-running.json +1 -1
- package/pipeline/contract/manifest.json +5 -5
- package/pipeline/contract/types/index.d.ts +3 -2
- package/pipeline/lib/qr-code.mjs +26 -0
- package/pipeline/multi-agent-refs/autopilot-on/details.md +148 -0
- package/pipeline/multi-agent-refs/channels/generator.md +14 -0
- package/pipeline/multi-agent-refs/channels/preview.md +13 -0
- package/pipeline/multi-agent-refs/channels/reference.md +46 -0
- package/pipeline/multi-agent-refs/complaint-analysis/phases.md +159 -0
- package/pipeline/multi-agent-refs/cross-cli-contract.md +4 -4
- package/pipeline/multi-agent-refs/design-check/export.md +1 -0
- package/pipeline/multi-agent-refs/design-check/init-inventory.md +1 -1
- package/pipeline/multi-agent-refs/design-check/scope-and-ledger.md +64 -0
- package/pipeline/multi-agent-refs/features/analysis-command-notes.md +53 -0
- package/pipeline/multi-agent-refs/garbage-collect/phases.md +60 -0
- package/pipeline/multi-agent-refs/help/en.md +1 -0
- package/pipeline/multi-agent-refs/help/tr.md +1 -0
- package/pipeline/multi-agent-refs/ios-coding-standard/plan.md +71 -0
- package/pipeline/multi-agent-refs/ios-coding-standard/resolve-and-scan.md +135 -0
- package/pipeline/multi-agent-refs/jira/picker-detail.md +71 -0
- package/pipeline/multi-agent-refs/orchestrator/copilot-pipeline.md +88 -0
- package/pipeline/multi-agent-refs/orchestrator/dispatcher.md +96 -1
- package/pipeline/multi-agent-refs/refactor/sweep.md +190 -0
- package/pipeline/multi-agent-refs/resume/details.md +115 -2
- package/pipeline/multi-agent-refs/review/inputs.md +33 -1
- package/pipeline/multi-agent-refs/review/module-guides.md +9 -1
- package/pipeline/multi-agent-refs/review/panel.md +75 -0
- package/pipeline/multi-agent-refs/review/valven.md +5 -1
- package/pipeline/multi-agent-refs/setup/hooks-and-plugins.md +4 -0
- package/pipeline/multi-agent-refs/setup/token-save-flow.md +13 -0
- package/pipeline/multi-agent-refs/stack/background.md +72 -0
- package/pipeline/multi-agent-refs/store-ready/gates.md +139 -0
- package/pipeline/multi-agent-refs/store-ready/report.md +40 -2
- package/pipeline/multi-agent-refs/sync/office.md +10 -0
- package/pipeline/multi-agent-refs/sync/overview.md +61 -0
- package/pipeline/multi-agent-refs/sync/repo.md +91 -0
- package/pipeline/multi-agent-refs/update/details.md +83 -0
- package/pipeline/schemas/runs-index.schema.json +2 -1
- package/pipeline/scripts/contract-server.mjs +1 -1
- package/pipeline/scripts/office-pair.mjs +40 -9
- package/pipeline/scripts/runs-index.mjs +34 -7
- package/pipeline/scripts/status-overview.mjs +401 -0
- package/pipeline/skills/.skill-manifest.json +69 -65
- package/pipeline/skills/.skills-index.json +15 -4
- package/pipeline/skills/shared/README.md +4 -3
- package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +8 -71
- package/pipeline/skills/shared/core/apple-archive-compliance/references/examples.md +67 -0
- package/pipeline/skills/shared/core/apple-archive-compliance/references/version-history.md +8 -0
- package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +11 -73
- package/pipeline/skills/shared/core/google-play-compliance/references/examples.md +69 -0
- package/pipeline/skills/shared/core/google-play-compliance/references/notes.md +7 -0
- package/pipeline/skills/shared/core/google-play-compliance/references/version-history.md +5 -0
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +39 -262
- package/pipeline/skills/shared/core/multi-agent-autopilot-on/SKILL.md +35 -73
- package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +37 -337
- package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +27 -122
- package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +8 -340
- package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +37 -180
- package/pipeline/skills/shared/core/multi-agent-office-pair/SKILL.md +19 -0
- package/pipeline/skills/shared/core/multi-agent-office-status/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +23 -184
- package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +14 -82
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +25 -277
- package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +13 -2
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +75 -219
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +11 -162
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-code.md +257 -0
- package/pipeline/skills/shared/external/android-architecture/SKILL.md +7 -58
- package/pipeline/skills/shared/external/android-architecture/references/code-examples.md +68 -0
- package/pipeline/skills/shared/external/app-intents/SKILL.md +3 -45
- package/pipeline/skills/shared/external/app-intents/references/code-examples.md +54 -0
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +5 -81
- package/pipeline/skills/shared/external/app-store-optimization/references/engagement-and-localization.md +96 -0
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +6 -112
- package/pipeline/skills/shared/external/app-store-review/references/monetization-and-submission.md +138 -0
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +10 -117
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models-code.md +186 -0
- package/pipeline/skills/shared/external/authentication/SKILL.md +3 -50
- package/pipeline/skills/shared/external/authentication/references/code-examples.md +59 -0
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +3 -84
- package/pipeline/skills/shared/external/callkit-voip/references/code-examples.md +93 -0
- package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +7 -263
- package/pipeline/skills/shared/external/ci-cd-pipelines/references/pipeline-examples.md +281 -0
- package/pipeline/skills/shared/external/clean-code/SKILL.md +8 -41
- package/pipeline/skills/shared/external/clean-code/references/code-examples.md +56 -0
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +8 -146
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-code.md +214 -0
- package/pipeline/skills/shared/external/compose-components/SKILL.md +6 -80
- package/pipeline/skills/shared/external/compose-components/references/code-examples.md +89 -0
- package/pipeline/skills/shared/external/compose-navigation/SKILL.md +5 -64
- package/pipeline/skills/shared/external/compose-navigation/references/code-examples.md +70 -0
- package/pipeline/skills/shared/external/core-data/SKILL.md +9 -157
- package/pipeline/skills/shared/external/core-data/references/core-data-code.md +221 -0
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +3 -89
- package/pipeline/skills/shared/external/core-nfc/references/code-examples.md +98 -0
- package/pipeline/skills/shared/external/coreml/SKILL.md +17 -133
- package/pipeline/skills/shared/external/coreml/references/coreml-code.md +208 -0
- package/pipeline/skills/shared/external/css-modern/SKILL.md +8 -131
- package/pipeline/skills/shared/external/css-modern/references/code-examples.md +142 -0
- package/pipeline/skills/shared/external/database-patterns/SKILL.md +6 -103
- package/pipeline/skills/shared/external/database-patterns/references/orm-examples.md +112 -0
- package/pipeline/skills/shared/external/html-semantic/SKILL.md +7 -48
- package/pipeline/skills/shared/external/html-semantic/references/code-examples.md +54 -0
- package/pipeline/skills/shared/external/humanizer/SKILL.md +6 -19
- package/pipeline/skills/shared/external/humanizer/references/voice.md +24 -0
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +13 -170
- package/pipeline/skills/shared/external/ios-accessibility/references/assistive-tech-review.md +224 -0
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +2 -24
- package/pipeline/skills/shared/external/ios-localization/references/code-examples.md +30 -0
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +3 -58
- package/pipeline/skills/shared/external/ios-networking/references/code-examples.md +67 -0
- package/pipeline/skills/shared/external/ios-security/SKILL.md +4 -64
- package/pipeline/skills/shared/external/ios-security/references/code-examples.md +74 -0
- package/pipeline/skills/shared/external/live-activities/SKILL.md +4 -90
- package/pipeline/skills/shared/external/live-activities/references/code-examples.md +108 -0
- package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +9 -23
- package/pipeline/skills/shared/external/localization-reuse-map/references/project-settings.md +22 -0
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +5 -95
- package/pipeline/skills/shared/external/mapkit-location/references/code-examples.md +117 -0
- package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +7 -106
- package/pipeline/skills/shared/external/nextjs-app-router/references/app-router-examples.md +124 -0
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +2 -31
- package/pipeline/skills/shared/external/permissionkit/references/code-examples.md +37 -0
- package/pipeline/skills/shared/external/play-store-review/SKILL.md +6 -74
- package/pipeline/skills/shared/external/play-store-review/references/signing-and-policies.md +83 -0
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +8 -125
- package/pipeline/skills/shared/external/push-notifications/references/notification-code.md +176 -0
- package/pipeline/skills/shared/external/python-patterns/SKILL.md +7 -158
- package/pipeline/skills/shared/external/python-patterns/references/python-examples.md +176 -0
- package/pipeline/skills/shared/external/rest-api-design/SKILL.md +7 -157
- package/pipeline/skills/shared/external/rest-api-design/references/api-examples.md +175 -0
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +11 -230
- package/pipeline/skills/shared/external/swift-architecture/references/pattern-examples.md +295 -0
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +17 -149
- package/pipeline/skills/shared/external/swift-codable/references/codable-recipes.md +207 -0
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +12 -99
- package/pipeline/skills/shared/external/swift-concurrency/references/code-examples.md +100 -0
- package/pipeline/skills/shared/external/swift-concurrency/references/swift-6-2-additions.md +18 -0
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +9 -78
- package/pipeline/skills/shared/external/swift-formatstyle/references/formatstyle-examples.md +113 -0
- package/pipeline/skills/shared/external/swift-language/SKILL.md +3 -56
- package/pipeline/skills/shared/external/swift-language/references/code-examples.md +65 -0
- package/pipeline/skills/shared/external/swift-testing-pro/SKILL.md +1 -0
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +6 -122
- package/pipeline/skills/shared/external/swiftdata/references/code-examples.md +140 -0
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +12 -139
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/interop-code.md +198 -0
- package/pipeline/skills/shared/external/tailwind-css/SKILL.md +9 -81
- package/pipeline/skills/shared/external/tailwind-css/references/code-examples.md +91 -0
- package/pipeline/skills/shared/external/testing-backend/SKILL.md +7 -207
- package/pipeline/skills/shared/external/testing-backend/references/testing-examples.md +225 -0
- package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +8 -89
- package/pipeline/skills/shared/external/typescript-patterns/references/code-examples.md +100 -0
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +2 -20
- package/pipeline/skills/shared/external/vision-framework/references/code-examples.md +26 -0
- package/pipeline/skills/shared/external/vue-composition/SKILL.md +7 -169
- package/pipeline/skills/shared/external/vue-composition/references/vue-examples.md +187 -0
- package/pipeline/skills/shared/external/web-accessibility/SKILL.md +7 -174
- package/pipeline/skills/shared/external/web-accessibility/references/a11y-examples.md +192 -0
- package/pipeline/skills/shared/external/web-performance/SKILL.md +8 -66
- package/pipeline/skills/shared/external/web-performance/references/code-examples.md +77 -0
- package/pipeline/skills/shared/external/web-testing/SKILL.md +11 -186
- package/pipeline/skills/shared/external/web-testing/references/testing-examples.md +213 -0
- package/pipeline/skills/skills-index.md +2 -1
- /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/format-and-output.md +0 -0
- /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/publish-and-snapshot.md +0 -0
- /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/sources-and-recipes.md +0 -0
|
@@ -15,6 +15,7 @@ UIKit and AppKit.
|
|
|
15
15
|
|
|
16
16
|
References:
|
|
17
17
|
|
|
18
|
+
- [Assistive technology review](references/assistive-tech-review.md): Reduce Motion and appearance code, the per-technology review rules (Voice Control, Switch Control, Full Keyboard Access, Assistive Access), UIKit and AppKit specifics, custom content, and the manual and XCTest test steps.
|
|
18
19
|
- [Code patterns](references/a11y-patterns.md): worked examples of the
|
|
19
20
|
modifiers in both UI frameworks, grouping, custom and adjustable controls, custom actions, rotors,
|
|
20
21
|
Dynamic Type, AppKit, Voice Control, Switch Control, Full Keyboard Access,
|
|
@@ -184,46 +185,16 @@ unread threads or headings. Add them to screens dense with content; see
|
|
|
184
185
|
|
|
185
186
|
With Reduce Motion on, replace movement with a crossfade or with no animation.
|
|
186
187
|
|
|
187
|
-
```swift
|
|
188
|
-
struct FilterDrawer: View {
|
|
189
|
-
@Environment(\.accessibilityReduceMotion) private var reduceMotion
|
|
190
|
-
@State private var isExpanded = false
|
|
191
|
-
|
|
192
|
-
var body: some View {
|
|
193
|
-
VStack {
|
|
194
|
-
Button("Filters") {
|
|
195
|
-
withAnimation(reduceMotion ? nil : .spring()) { isExpanded.toggle() }
|
|
196
|
-
}
|
|
197
|
-
if isExpanded {
|
|
198
|
-
PanelView()
|
|
199
|
-
.transition(reduceMotion ? .opacity : .move(edge: .bottom))
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
```
|
|
205
|
-
|
|
206
188
|
Go through every moving transition, not just the obvious one: row removal,
|
|
207
189
|
quantity changes, presenting a sheet or checkout, dismissing a modal. Slides,
|
|
208
190
|
bounces, parallax, springs and big spatial moves become fades, an immediate
|
|
209
191
|
jump to the end state, or no animation at all.
|
|
210
192
|
|
|
211
|
-
|
|
193
|
+
A `FilterDrawer` example that swaps the spring and slide for no animation and a fade: [Assistive technology review](references/assistive-tech-review.md).
|
|
212
194
|
|
|
213
|
-
|
|
214
|
-
@Environment(\.accessibilityReduceTransparency) private var reduceTransparency
|
|
215
|
-
@Environment(\.colorSchemeContrast) private var contrast
|
|
216
|
-
@Environment(\.legibilityWeight) private var legibilityWeight
|
|
217
|
-
|
|
218
|
-
private let base = Color(uiColor: .systemBackground)
|
|
195
|
+
### Transparency, contrast, bold text
|
|
219
196
|
|
|
220
|
-
|
|
221
|
-
Text(caption)
|
|
222
|
-
.foregroundStyle(contrast == .increased ? .primary : .secondary)
|
|
223
|
-
.fontWeight(legibilityWeight == .bold ? .bold : .regular)
|
|
224
|
-
.background(reduceTransparency ? base : base.opacity(0.8))
|
|
225
|
-
}
|
|
226
|
-
```
|
|
197
|
+
Code that adapts foreground style, font weight and background opacity to these three settings: [Assistive technology review](references/assistive-tech-review.md).
|
|
227
198
|
|
|
228
199
|
## Decorative versus meaningful images
|
|
229
200
|
|
|
@@ -244,62 +215,15 @@ instead of hiding it.
|
|
|
244
215
|
|
|
245
216
|
## Voice Control
|
|
246
217
|
|
|
247
|
-
|
|
248
|
-
is the name a person says to tap the element. An element with no label, or a label nobody can say, cannot be
|
|
249
|
-
targeted by voice.
|
|
250
|
-
|
|
251
|
-
- Every interactive element needs a label a person can say aloud. No labels
|
|
252
|
-
made only of emoji or symbols.
|
|
253
|
-
- Labels must differ among the elements on screen at the same time;
|
|
254
|
-
duplicates push users onto overlay numbers.
|
|
255
|
-
- Add `accessibilityInputLabels` before feature freeze, not as late polish,
|
|
256
|
-
wherever a label is long, clumsy, localized, full of acronyms, or usually
|
|
257
|
-
shortened when spoken. Both Full Keyboard Access and Voice Control consume
|
|
258
|
-
them. List the entries most important first.
|
|
259
|
-
- Good candidates: media controls, settings and account links, per-row
|
|
260
|
-
actions that repeat down a list, quantity steppers, localized labels that
|
|
261
|
-
contain product names or acronyms.
|
|
262
|
-
- Turn Voice Control on and say "Show Names", then "Show Numbers". The names
|
|
263
|
-
overlay proves labels can be spoken; the numbers overlay proves every
|
|
264
|
-
visible target stays reachable even if its name is missing, duplicated or
|
|
265
|
-
awkward. A review
|
|
266
|
-
checks both overlays.
|
|
267
|
-
|
|
268
|
-
Examples and speakable-label guidance: [code patterns](references/a11y-patterns.md).
|
|
218
|
+
Every interactive element needs a unique label a person can say aloud; add `accessibilityInputLabels` early for long, localized or acronym-heavy labels, and check both the Show Names and Show Numbers overlays. Full rules: [Assistive technology review](references/assistive-tech-review.md).
|
|
269
219
|
|
|
270
220
|
## Switch Control
|
|
271
221
|
|
|
272
|
-
|
|
273
|
-
order, so grouping and custom actions matter more here than anywhere else.
|
|
274
|
-
|
|
275
|
-
- `.accessibilityElement(children: .combine)` merges related pieces and
|
|
276
|
-
removes scan stops. Each remaining stop should mean something and do
|
|
277
|
-
something.
|
|
278
|
-
- Anything hidden from VoiceOver as decorative is skipped by Switch Control
|
|
279
|
-
too.
|
|
280
|
-
- Multi-finger gestures, long presses and swipe-to-delete are out of reach
|
|
281
|
-
for switch users. Offer those operations as `.accessibilityAction(named:)`; Switch
|
|
282
|
-
Control lists them in a menu.
|
|
283
|
-
- In point scanning mode, a custom control whose tappable area is unusual
|
|
284
|
-
must report an `accessibilityFrame` that matches that area.
|
|
222
|
+
Scanning follows reading order: combine related pieces to cut scan stops, and offer gestures (multi-finger, long press, swipe-to-delete) as named custom actions. Full rules: [Assistive technology review](references/assistive-tech-review.md).
|
|
285
223
|
|
|
286
224
|
## Full Keyboard Access
|
|
287
225
|
|
|
288
|
-
|
|
289
|
-
app from a hardware keyboard. This skill owns the review: can everything be
|
|
290
|
-
reached, is it clearly labelled, is focus visible, does it work without touch.
|
|
291
|
-
`UIFocusGuide`, tvOS focus, scene-focused values, directional movement,
|
|
292
|
-
`focusSection()`, `@FocusState`, `.focusable()`, custom cards that Tab jumps
|
|
293
|
-
over, and Tab traversal bugs in general go to `hig-inputs` first; only the accessibility finding
|
|
294
|
-
stays here.
|
|
295
|
-
|
|
296
|
-
Audit:
|
|
297
|
-
|
|
298
|
-
- the keyboard alone can reach and trigger every interactive element;
|
|
299
|
-
- the traversal order makes sense and never traps focus;
|
|
300
|
-
- focus indicators stay visible at every contrast and text-size setting;
|
|
301
|
-
- anything that only works through a gesture has a keyboard route;
|
|
302
|
-
- the app's own shortcuts leave system ones (Cmd+C, Cmd+V, Cmd+Tab) alone.
|
|
226
|
+
Audit that the keyboard alone reaches and triggers everything, order never traps focus, focus stays visible, gestures have a keyboard route, and system shortcuts stay free; focus-system mechanics go to `hig-inputs`. Full audit list: [Assistive technology review](references/assistive-tech-review.md).
|
|
303
227
|
|
|
304
228
|
## Traversal order
|
|
305
229
|
|
|
@@ -314,70 +238,19 @@ the ordering audit and its accessibility impact are reported here.
|
|
|
314
238
|
|
|
315
239
|
## Assistive Access (iOS 18+)
|
|
316
240
|
|
|
317
|
-
|
|
318
|
-
apps should behave well inside it.
|
|
319
|
-
|
|
320
|
-
```swift
|
|
321
|
-
struct LibraryScreen: View {
|
|
322
|
-
@Environment(\.accessibilityAssistiveAccessEnabled) private var assistiveAccess
|
|
323
|
-
|
|
324
|
-
var body: some View {
|
|
325
|
-
if assistiveAccess { SimpleLibraryView() } else { FullLibraryView() }
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
- Less on screen: fewer controls, bigger targets, flatter navigation.
|
|
331
|
-
- Literal, plain wording in labels and instructions.
|
|
332
|
-
- Only a few choices at a time.
|
|
333
|
-
- Try it under Settings > Accessibility > Assistive Access.
|
|
334
|
-
|
|
335
|
-
From iOS 26 an app can also declare `UISupportsAssistiveAccess` in its
|
|
336
|
-
Info.plist and provide a dedicated `AssistiveAccess` scene.
|
|
241
|
+
Read `accessibilityAssistiveAccessEnabled` and offer a simpler screen: fewer controls, bigger targets, literal wording, few choices. Example and the iOS 26 scene option: [Assistive technology review](references/assistive-tech-review.md).
|
|
337
242
|
|
|
338
243
|
## UIKit
|
|
339
244
|
|
|
340
|
-
|
|
341
|
-
- `accessibilityLabel` on every interactive element without visible text.
|
|
342
|
-
- Change traits with `.insert()` and `.remove()` instead of assigning:
|
|
343
|
-
`badgeView.accessibilityTraits.insert(.button)`,
|
|
344
|
-
`badgeView.accessibilityTraits.remove(.staticText)`.
|
|
345
|
-
- `accessibilityViewIsModal = true` on custom overlays to keep focus inside.
|
|
346
|
-
- Post `.announcement` for passing status, `.layoutChanged` with a target for
|
|
347
|
-
partial updates, `.screenChanged` for full transitions.
|
|
245
|
+
Custom views that carry meaning set `isAccessibilityElement`, labels and traits (insert and remove, never assign), overlays set `accessibilityViewIsModal`, and changes post the matching notification. Details: [Assistive technology review](references/assistive-tech-review.md).
|
|
348
246
|
|
|
349
247
|
## AppKit
|
|
350
248
|
|
|
351
|
-
|
|
352
|
-
protocols. Standard controls already publish most of what assistive apps need,
|
|
353
|
-
including notifications, so prefer them and customise only where the defaults
|
|
354
|
-
fall short.
|
|
355
|
-
|
|
356
|
-
- A custom `NSView` adopts the right role behaviour and reports its role,
|
|
357
|
-
label, value and supported actions accurately.
|
|
358
|
-
- Items with no view behind them use `NSAccessibilityElement`.
|
|
359
|
-
- Post `NSAccessibility` notifications when a change must reach assistive
|
|
360
|
-
apps.
|
|
361
|
-
|
|
362
|
-
The full custom-view example (a toggle-like `NSView` returning role, label,
|
|
363
|
-
"On"/"Off" value and a press action, posting `.valueChanged` when its state
|
|
364
|
-
changes) and `NSAccessibilityElement` usage are in
|
|
365
|
-
[code patterns](references/a11y-patterns.md#appkit).
|
|
249
|
+
Prefer standard controls; custom `NSView`s report role, label, value and actions, viewless items use `NSAccessibilityElement`, and changes post `NSAccessibility` notifications. Details: [Assistive technology review](references/assistive-tech-review.md).
|
|
366
250
|
|
|
367
251
|
## Custom content
|
|
368
252
|
|
|
369
|
-
`.accessibilityCustomContent`
|
|
370
|
-
By default VoiceOver offers them only on request (More Content rotor); with
|
|
371
|
-
`importance: .high` they are read along with the element.
|
|
372
|
-
|
|
373
|
-
```swift
|
|
374
|
-
HotelCard(hotel: hotel)
|
|
375
|
-
.accessibilityCustomContent("Nightly rate", Text(hotel.rateText), importance: .high)
|
|
376
|
-
.accessibilityCustomContent("Guest score", Text(hotel.scoreText))
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
More UIKit, AppKit and custom-content examples:
|
|
380
|
-
[code patterns](references/a11y-patterns.md).
|
|
253
|
+
`.accessibilityCustomContent` adds labelled facts, offered on request unless `importance: .high`. Example: [Assistive technology review](references/assistive-tech-review.md).
|
|
381
254
|
|
|
382
255
|
## App Store Accessibility Nutrition Labels
|
|
383
256
|
|
|
@@ -396,41 +269,11 @@ only recommended after you:
|
|
|
396
269
|
|
|
397
270
|
### By hand
|
|
398
271
|
|
|
399
|
-
|
|
400
|
-
|---|---|---|
|
|
401
|
-
| Accessibility Inspector | Run an audit against the Simulator or a device; it reports contrast issues, absent labels and wrong traits | Xcode > Open Developer Tool |
|
|
402
|
-
| VoiceOver | Swipe from the first element to the last on each screen | Settings > Accessibility > VoiceOver |
|
|
403
|
-
| Voice Control | Say "Show Names", then "Show Numbers" | Settings > Accessibility > Voice Control |
|
|
404
|
-
| Full Keyboard Access | Press Tab across each screen and confirm each control receives focus | Settings > Accessibility > Keyboards > Full Keyboard Access |
|
|
405
|
-
| Switch Control | Watch the scan order; gesture features need custom actions | Settings > Accessibility > Switch Control |
|
|
406
|
-
| Dynamic Type | Step through the whole size range | Settings > Accessibility > Display & Text Size > Larger Text |
|
|
272
|
+
Accessibility Inspector, VoiceOver, Voice Control, Full Keyboard Access, Switch Control and the Dynamic Type range, each checked per screen. Tool table: [Assistive technology review](references/assistive-tech-review.md).
|
|
407
273
|
|
|
408
274
|
### With XCTest
|
|
409
275
|
|
|
410
|
-
|
|
411
|
-
ones in `XCUIElementAttributes` are `identifier`, `label`, `title`, `value`,
|
|
412
|
-
`placeholderValue`, `isSelected`, `isEnabled` and `hasFocus`.
|
|
413
|
-
|
|
414
|
-
```swift
|
|
415
|
-
@MainActor
|
|
416
|
-
func testFocusReturnsAfterSheet() {
|
|
417
|
-
let app = XCUIApplication()
|
|
418
|
-
app.launch()
|
|
419
|
-
|
|
420
|
-
let opener = app.buttons["Seat map"]
|
|
421
|
-
opener.tap()
|
|
422
|
-
app.buttons["Done"].tap()
|
|
423
|
-
XCTAssertTrue(opener.hasFocus)
|
|
424
|
-
}
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
A row test (querying `app.cells["id"]`, asserting `exists`, `isEnabled`, a
|
|
428
|
-
non-empty `label` and a nested `buttons[...]` query) is in
|
|
429
|
-
[code patterns](references/a11y-patterns.md#automated-checks-with-xctest).
|
|
430
|
-
|
|
431
|
-
`hasFocus` reports keyboard focus (Full Keyboard Access, hardware keyboard),
|
|
432
|
-
not the VoiceOver cursor, which XCTest cannot observe. Confirm VoiceOver focus
|
|
433
|
-
restoration by hand as well.
|
|
276
|
+
`XCUIElement` attributes assert identifiers, labels, values and keyboard focus; `hasFocus` does not see the VoiceOver cursor, so confirm that by hand. Example: [Assistive technology review](references/assistive-tech-review.md).
|
|
434
277
|
|
|
435
278
|
## Mistakes seen most often
|
|
436
279
|
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Assistive technology review
|
|
2
|
+
|
|
3
|
+
<!-- toc -->
|
|
4
|
+
- [Reduce Motion](#reduce-motion)
|
|
5
|
+
- [Transparency, contrast, bold text](#transparency-contrast-bold-text)
|
|
6
|
+
- [Voice Control](#voice-control)
|
|
7
|
+
- [Switch Control](#switch-control)
|
|
8
|
+
- [Full Keyboard Access](#full-keyboard-access)
|
|
9
|
+
- [Assistive Access (iOS 18+)](#assistive-access-ios-18)
|
|
10
|
+
- [UIKit](#uikit)
|
|
11
|
+
- [AppKit](#appkit)
|
|
12
|
+
- [Custom content](#custom-content)
|
|
13
|
+
<!-- /toc -->
|
|
14
|
+
|
|
15
|
+
Reduce Motion and appearance code, the review rules for Voice Control, Switch Control, Full Keyboard Access and Assistive Access, the UIKit and AppKit specifics, custom content, and the manual and XCTest testing steps. SKILL.md keeps the ground rules, focus, traversal order and the review checklist.
|
|
16
|
+
|
|
17
|
+
## Reduce Motion
|
|
18
|
+
|
|
19
|
+
With Reduce Motion on, replace movement with a crossfade or with no animation.
|
|
20
|
+
|
|
21
|
+
```swift
|
|
22
|
+
struct FilterDrawer: View {
|
|
23
|
+
@Environment(\.accessibilityReduceMotion) private var reduceMotion
|
|
24
|
+
@State private var isExpanded = false
|
|
25
|
+
|
|
26
|
+
var body: some View {
|
|
27
|
+
VStack {
|
|
28
|
+
Button("Filters") {
|
|
29
|
+
withAnimation(reduceMotion ? nil : .spring()) { isExpanded.toggle() }
|
|
30
|
+
}
|
|
31
|
+
if isExpanded {
|
|
32
|
+
PanelView()
|
|
33
|
+
.transition(reduceMotion ? .opacity : .move(edge: .bottom))
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Go through every moving transition, not just the obvious one: row removal,
|
|
41
|
+
quantity changes, presenting a sheet or checkout, dismissing a modal. Slides,
|
|
42
|
+
bounces, parallax, springs and big spatial moves become fades, an immediate
|
|
43
|
+
jump to the end state, or no animation at all.
|
|
44
|
+
|
|
45
|
+
## Transparency, contrast, bold text
|
|
46
|
+
|
|
47
|
+
```swift
|
|
48
|
+
@Environment(\.accessibilityReduceTransparency) private var reduceTransparency
|
|
49
|
+
@Environment(\.colorSchemeContrast) private var contrast
|
|
50
|
+
@Environment(\.legibilityWeight) private var legibilityWeight
|
|
51
|
+
|
|
52
|
+
private let base = Color(uiColor: .systemBackground)
|
|
53
|
+
|
|
54
|
+
var body: some View {
|
|
55
|
+
Text(caption)
|
|
56
|
+
.foregroundStyle(contrast == .increased ? .primary : .secondary)
|
|
57
|
+
.fontWeight(legibilityWeight == .bold ? .bold : .regular)
|
|
58
|
+
.background(reduceTransparency ? base : base.opacity(0.8))
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Voice Control
|
|
63
|
+
|
|
64
|
+
Spoken commands find their target through its accessibility label: the label
|
|
65
|
+
is the name a person says to tap the element. An element with no label, or a label nobody can say, cannot be
|
|
66
|
+
targeted by voice.
|
|
67
|
+
|
|
68
|
+
- Every interactive element needs a label a person can say aloud. No labels
|
|
69
|
+
made only of emoji or symbols.
|
|
70
|
+
- Labels must differ among the elements on screen at the same time;
|
|
71
|
+
duplicates push users onto overlay numbers.
|
|
72
|
+
- Add `accessibilityInputLabels` before feature freeze, not as late polish,
|
|
73
|
+
wherever a label is long, clumsy, localized, full of acronyms, or usually
|
|
74
|
+
shortened when spoken. Both Full Keyboard Access and Voice Control consume
|
|
75
|
+
them. List the entries most important first.
|
|
76
|
+
- Good candidates: media controls, settings and account links, per-row
|
|
77
|
+
actions that repeat down a list, quantity steppers, localized labels that
|
|
78
|
+
contain product names or acronyms.
|
|
79
|
+
- Turn Voice Control on and say "Show Names", then "Show Numbers". The names
|
|
80
|
+
overlay proves labels can be spoken; the numbers overlay proves every
|
|
81
|
+
visible target stays reachable even if its name is missing, duplicated or
|
|
82
|
+
awkward. A review
|
|
83
|
+
checks both overlays.
|
|
84
|
+
|
|
85
|
+
Examples and speakable-label guidance: [code patterns](a11y-patterns.md).
|
|
86
|
+
|
|
87
|
+
## Switch Control
|
|
88
|
+
|
|
89
|
+
Switch Control steps through accessibility elements one at a time in reading
|
|
90
|
+
order, so grouping and custom actions matter more here than anywhere else.
|
|
91
|
+
|
|
92
|
+
- `.accessibilityElement(children: .combine)` merges related pieces and
|
|
93
|
+
removes scan stops. Each remaining stop should mean something and do
|
|
94
|
+
something.
|
|
95
|
+
- Anything hidden from VoiceOver as decorative is skipped by Switch Control
|
|
96
|
+
too.
|
|
97
|
+
- Multi-finger gestures, long presses and swipe-to-delete are out of reach
|
|
98
|
+
for switch users. Offer those operations as `.accessibilityAction(named:)`; Switch
|
|
99
|
+
Control lists them in a menu.
|
|
100
|
+
- In point scanning mode, a custom control whose tappable area is unusual
|
|
101
|
+
must report an `accessibilityFrame` that matches that area.
|
|
102
|
+
|
|
103
|
+
## Full Keyboard Access
|
|
104
|
+
|
|
105
|
+
Available since iOS and iPadOS 13.4, Full Keyboard Access lets people run the
|
|
106
|
+
app from a hardware keyboard. This skill owns the review: can everything be
|
|
107
|
+
reached, is it clearly labelled, is focus visible, does it work without touch.
|
|
108
|
+
`UIFocusGuide`, tvOS focus, scene-focused values, directional movement,
|
|
109
|
+
`focusSection()`, `@FocusState`, `.focusable()`, custom cards that Tab jumps
|
|
110
|
+
over, and Tab traversal bugs in general go to `hig-inputs` first; only the accessibility finding
|
|
111
|
+
stays here.
|
|
112
|
+
|
|
113
|
+
Audit:
|
|
114
|
+
|
|
115
|
+
- the keyboard alone can reach and trigger every interactive element;
|
|
116
|
+
- the traversal order makes sense and never traps focus;
|
|
117
|
+
- focus indicators stay visible at every contrast and text-size setting;
|
|
118
|
+
- anything that only works through a gesture has a keyboard route;
|
|
119
|
+
- the app's own shortcuts leave system ones (Cmd+C, Cmd+V, Cmd+Tab) alone.
|
|
120
|
+
|
|
121
|
+
## Assistive Access (iOS 18+)
|
|
122
|
+
|
|
123
|
+
This pared-down system mode serves people with cognitive disabilities, and
|
|
124
|
+
apps should behave well inside it.
|
|
125
|
+
|
|
126
|
+
```swift
|
|
127
|
+
struct LibraryScreen: View {
|
|
128
|
+
@Environment(\.accessibilityAssistiveAccessEnabled) private var assistiveAccess
|
|
129
|
+
|
|
130
|
+
var body: some View {
|
|
131
|
+
if assistiveAccess { SimpleLibraryView() } else { FullLibraryView() }
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
- Less on screen: fewer controls, bigger targets, flatter navigation.
|
|
137
|
+
- Literal, plain wording in labels and instructions.
|
|
138
|
+
- Only a few choices at a time.
|
|
139
|
+
- Try it under Settings > Accessibility > Assistive Access.
|
|
140
|
+
|
|
141
|
+
From iOS 26 an app can also declare `UISupportsAssistiveAccess` in its
|
|
142
|
+
Info.plist and provide a dedicated `AssistiveAccess` scene.
|
|
143
|
+
|
|
144
|
+
## UIKit
|
|
145
|
+
|
|
146
|
+
- `isAccessibilityElement = true` on custom views that carry meaning.
|
|
147
|
+
- `accessibilityLabel` on every interactive element without visible text.
|
|
148
|
+
- Change traits with `.insert()` and `.remove()` instead of assigning:
|
|
149
|
+
`badgeView.accessibilityTraits.insert(.button)`,
|
|
150
|
+
`badgeView.accessibilityTraits.remove(.staticText)`.
|
|
151
|
+
- `accessibilityViewIsModal = true` on custom overlays to keep focus inside.
|
|
152
|
+
- Post `.announcement` for passing status, `.layoutChanged` with a target for
|
|
153
|
+
partial updates, `.screenChanged` for full transitions.
|
|
154
|
+
|
|
155
|
+
## AppKit
|
|
156
|
+
|
|
157
|
+
AppKit accessibility rests on `NSAccessibilityProtocol` plus role-specific
|
|
158
|
+
protocols. Standard controls already publish most of what assistive apps need,
|
|
159
|
+
including notifications, so prefer them and customise only where the defaults
|
|
160
|
+
fall short.
|
|
161
|
+
|
|
162
|
+
- A custom `NSView` adopts the right role behaviour and reports its role,
|
|
163
|
+
label, value and supported actions accurately.
|
|
164
|
+
- Items with no view behind them use `NSAccessibilityElement`.
|
|
165
|
+
- Post `NSAccessibility` notifications when a change must reach assistive
|
|
166
|
+
apps.
|
|
167
|
+
|
|
168
|
+
The full custom-view example (a toggle-like `NSView` returning role, label,
|
|
169
|
+
"On"/"Off" value and a press action, posting `.valueChanged` when its state
|
|
170
|
+
changes) and `NSAccessibilityElement` usage are in
|
|
171
|
+
[code patterns](a11y-patterns.md#appkit).
|
|
172
|
+
|
|
173
|
+
## Custom content
|
|
174
|
+
|
|
175
|
+
`.accessibilityCustomContent` attaches extra labelled facts to an element.
|
|
176
|
+
By default VoiceOver offers them only on request (More Content rotor); with
|
|
177
|
+
`importance: .high` they are read along with the element.
|
|
178
|
+
|
|
179
|
+
```swift
|
|
180
|
+
HotelCard(hotel: hotel)
|
|
181
|
+
.accessibilityCustomContent("Nightly rate", Text(hotel.rateText), importance: .high)
|
|
182
|
+
.accessibilityCustomContent("Guest score", Text(hotel.scoreText))
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
More UIKit, AppKit and custom-content examples:
|
|
186
|
+
[code patterns](a11y-patterns.md).
|
|
187
|
+
|
|
188
|
+
### By hand
|
|
189
|
+
|
|
190
|
+
| Tool | What to do | Where |
|
|
191
|
+
|---|---|---|
|
|
192
|
+
| Accessibility Inspector | Run an audit against the Simulator or a device; it reports contrast issues, absent labels and wrong traits | Xcode > Open Developer Tool |
|
|
193
|
+
| VoiceOver | Swipe from the first element to the last on each screen | Settings > Accessibility > VoiceOver |
|
|
194
|
+
| Voice Control | Say "Show Names", then "Show Numbers" | Settings > Accessibility > Voice Control |
|
|
195
|
+
| Full Keyboard Access | Press Tab across each screen and confirm each control receives focus | Settings > Accessibility > Keyboards > Full Keyboard Access |
|
|
196
|
+
| Switch Control | Watch the scan order; gesture features need custom actions | Settings > Accessibility > Switch Control |
|
|
197
|
+
| Dynamic Type | Step through the whole size range | Settings > Accessibility > Display & Text Size > Larger Text |
|
|
198
|
+
|
|
199
|
+
### With XCTest
|
|
200
|
+
|
|
201
|
+
UI tests can assert accessibility through `XCUIElement` attributes. The useful
|
|
202
|
+
ones in `XCUIElementAttributes` are `identifier`, `label`, `title`, `value`,
|
|
203
|
+
`placeholderValue`, `isSelected`, `isEnabled` and `hasFocus`.
|
|
204
|
+
|
|
205
|
+
```swift
|
|
206
|
+
@MainActor
|
|
207
|
+
func testFocusReturnsAfterSheet() {
|
|
208
|
+
let app = XCUIApplication()
|
|
209
|
+
app.launch()
|
|
210
|
+
|
|
211
|
+
let opener = app.buttons["Seat map"]
|
|
212
|
+
opener.tap()
|
|
213
|
+
app.buttons["Done"].tap()
|
|
214
|
+
XCTAssertTrue(opener.hasFocus)
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
A row test (querying `app.cells["id"]`, asserting `exists`, `isEnabled`, a
|
|
219
|
+
non-empty `label` and a nested `buttons[...]` query) is in
|
|
220
|
+
[code patterns](a11y-patterns.md#automated-checks-with-xctest).
|
|
221
|
+
|
|
222
|
+
`hasFocus` reports keyboard focus (Full Keyboard Access, hardware keyboard),
|
|
223
|
+
not the VoiceOver cursor, which XCTest cannot observe. Confirm VoiceOver focus
|
|
224
|
+
restoration by hand as well.
|
|
@@ -251,30 +251,7 @@ parsing, `Date.IntervalFormatStyle`, `URL.FormatStyle` and reusable formatter
|
|
|
251
251
|
API design belong to `swift-formatstyle`. Here, only point out locale risks and
|
|
252
252
|
testing for those, unless the user explicitly asks for an implementation.
|
|
253
253
|
|
|
254
|
-
|
|
255
|
-
let flight = Date.now
|
|
256
|
-
flight.formatted(date: .long, time: .shortened)
|
|
257
|
-
// en_US "June 3, 2026 at 4:05 PM", de_DE "3. Juni 2026 um 16:05"; ja_JP uses year-month-day order
|
|
258
|
-
flight.formatted(.dateTime.month(.wide).day().year())
|
|
259
|
-
Text(flight, format: .dateTime.weekday().hour().minute())
|
|
260
|
-
|
|
261
|
-
let visitors = 1_284_503
|
|
262
|
-
visitors.formatted() // "1,284,503" / "1.284.503"
|
|
263
|
-
3.14159.formatted(.number.precision(.fractionLength(2)))
|
|
264
|
-
0.18.formatted(.percent) // "18%"
|
|
265
|
-
Decimal(59.9).formatted(.currency(code: "USD"))
|
|
266
|
-
Decimal(59.9).formatted(.currency(code: "EUR")) // symbol position follows locale
|
|
267
|
-
|
|
268
|
-
Measurement(value: 42, unit: UnitLength.kilometers)
|
|
269
|
-
.formatted(.measurement(width: .wide)) // miles in en_US
|
|
270
|
-
Measurement(value: 21, unit: UnitTemperature.celsius)
|
|
271
|
-
.formatted(.measurement(width: .abbreviated)) // Fahrenheit in en_US
|
|
272
|
-
|
|
273
|
-
Duration.seconds(5_025).formatted(.time(pattern: .hourMinuteSecond)) // "1:23:45"
|
|
274
|
-
var author = PersonNameComponents(); author.givenName = "Aiko"; author.familyName = "Tanaka"
|
|
275
|
-
author.formatted(.name(style: .long)) // order differs en_US vs ja_JP
|
|
276
|
-
["tea", "coffee", "juice"].formatted(.list(type: .and)) // "tea, coffee, and juice"; French joins with "et" and no serial comma
|
|
277
|
-
```
|
|
254
|
+
Code: [Locale-Aware Formatting with FormatStyle](references/code-examples.md#locale-aware-formatting-with-formatstyle).
|
|
278
255
|
|
|
279
256
|
Full coverage of every style is in
|
|
280
257
|
[references/formatstyle-locale.md](references/formatstyle-locale.md).
|
|
@@ -363,3 +340,4 @@ Rules: prefer `.leading` and `.trailing` (they flip) over `.left` and `.right`
|
|
|
363
340
|
- [references/formatstyle-locale.md](references/formatstyle-locale.md):
|
|
364
341
|
FormatStyle per value type, forcing a locale, RTL, `@ScaledMetric` and
|
|
365
342
|
Accessibility Inspector.
|
|
343
|
+
- [Code examples](references/code-examples.md): open for the full code behind Locale-Aware Formatting with FormatStyle.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# iOS Localization Code Examples
|
|
2
|
+
|
|
3
|
+
Full code samples for the SKILL.md sections that link here. The rules and the decision guidance stay in SKILL.md; each heading below matches the SKILL.md section the code belongs to.
|
|
4
|
+
|
|
5
|
+
## Locale-Aware Formatting with FormatStyle
|
|
6
|
+
|
|
7
|
+
```swift
|
|
8
|
+
let flight = Date.now
|
|
9
|
+
flight.formatted(date: .long, time: .shortened)
|
|
10
|
+
// en_US "June 3, 2026 at 4:05 PM", de_DE "3. Juni 2026 um 16:05"; ja_JP uses year-month-day order
|
|
11
|
+
flight.formatted(.dateTime.month(.wide).day().year())
|
|
12
|
+
Text(flight, format: .dateTime.weekday().hour().minute())
|
|
13
|
+
|
|
14
|
+
let visitors = 1_284_503
|
|
15
|
+
visitors.formatted() // "1,284,503" / "1.284.503"
|
|
16
|
+
3.14159.formatted(.number.precision(.fractionLength(2)))
|
|
17
|
+
0.18.formatted(.percent) // "18%"
|
|
18
|
+
Decimal(59.9).formatted(.currency(code: "USD"))
|
|
19
|
+
Decimal(59.9).formatted(.currency(code: "EUR")) // symbol position follows locale
|
|
20
|
+
|
|
21
|
+
Measurement(value: 42, unit: UnitLength.kilometers)
|
|
22
|
+
.formatted(.measurement(width: .wide)) // miles in en_US
|
|
23
|
+
Measurement(value: 21, unit: UnitTemperature.celsius)
|
|
24
|
+
.formatted(.measurement(width: .abbreviated)) // Fahrenheit in en_US
|
|
25
|
+
|
|
26
|
+
Duration.seconds(5_025).formatted(.time(pattern: .hourMinuteSecond)) // "1:23:45"
|
|
27
|
+
var author = PersonNameComponents(); author.givenName = "Aiko"; author.familyName = "Tanaka"
|
|
28
|
+
author.formatted(.name(style: .long)) // order differs en_US vs ja_JP
|
|
29
|
+
["tea", "coffee", "juice"].formatted(.list(type: .and)) // "tea, coffee, and juice"; French joins with "et" and no serial comma
|
|
30
|
+
```
|
|
@@ -101,42 +101,7 @@ for try await line in stream.lines {
|
|
|
101
101
|
Put the client behind a protocol. Tests then substitute a double instead of
|
|
102
102
|
mocking URLSession.
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
protocol APIClientProtocol: Sendable {
|
|
106
|
-
func fetch<Reply: Decodable & Sendable>(_ endpoint: Endpoint, as kind: Reply.Type) async throws -> Reply
|
|
107
|
-
func send<Reply: Decodable & Sendable>(_ endpoint: Endpoint, json payload: some Encodable & Sendable,
|
|
108
|
-
as kind: Reply.Type) async throws -> Reply
|
|
109
|
-
func perform(_ endpoint: Endpoint) async throws
|
|
110
|
-
func upload<Reply: Decodable & Sendable>(_ endpoint: Endpoint, bytes: Data,
|
|
111
|
-
as kind: Reply.Type) async throws -> Reply
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
struct Endpoint: Sendable {
|
|
115
|
-
enum HTTPMethod: String, Sendable { case get, post, put, patch, delete }
|
|
116
|
-
|
|
117
|
-
var path: String
|
|
118
|
-
var method: HTTPMethod = .get
|
|
119
|
-
var queryItems: [URLQueryItem] = []
|
|
120
|
-
var headers: [String: String] = [:]
|
|
121
|
-
var body: Data? = nil
|
|
122
|
-
var cachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy
|
|
123
|
-
var timeoutInterval: TimeInterval = 30
|
|
124
|
-
|
|
125
|
-
func urlRequest(relativeTo baseURL: URL) throws -> URLRequest {
|
|
126
|
-
guard var parts = URLComponents(url: baseURL.appending(path: path),
|
|
127
|
-
resolvingAgainstBaseURL: true) else {
|
|
128
|
-
throw NetworkError.invalidURL
|
|
129
|
-
}
|
|
130
|
-
if !queryItems.isEmpty { parts.queryItems = queryItems }
|
|
131
|
-
guard let url = parts.url else { throw NetworkError.invalidURL }
|
|
132
|
-
var request = URLRequest(url: url, cachePolicy: cachePolicy, timeoutInterval: timeoutInterval)
|
|
133
|
-
request.httpMethod = method.rawValue.uppercased()
|
|
134
|
-
request.httpBody = body
|
|
135
|
-
request.allHTTPHeaderFields = headers
|
|
136
|
-
return request
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
```
|
|
104
|
+
Code: [Protocol-based client](references/code-examples.md#protocol-based-client).
|
|
140
105
|
|
|
141
106
|
A bad URL is a thrown `NetworkError.invalidURL`, never a crash; nothing in
|
|
142
107
|
this skill force-unwraps.
|
|
@@ -269,28 +234,7 @@ Retry inside structured concurrency so cancellation stops the loop. Never
|
|
|
269
234
|
retry cancellation or a 4xx, with 429 as the exception; 5xx and transient
|
|
270
235
|
transport errors are fair game.
|
|
271
236
|
|
|
272
|
-
|
|
273
|
-
func withRetry<T: Sendable>(
|
|
274
|
-
maxAttempts: Int = 3,
|
|
275
|
-
initialDelay: Duration = .seconds(1),
|
|
276
|
-
maxDelay: Duration = .seconds(30),
|
|
277
|
-
shouldRetry: @Sendable (any Error) -> Bool = isTransient,
|
|
278
|
-
operation: @Sendable () async throws -> T
|
|
279
|
-
) async throws -> T {
|
|
280
|
-
precondition(maxAttempts > 0, "maxAttempts must be at least 1")
|
|
281
|
-
for attempt in 0..<maxAttempts {
|
|
282
|
-
try Task.checkCancellation()
|
|
283
|
-
do {
|
|
284
|
-
return try await operation()
|
|
285
|
-
} catch {
|
|
286
|
-
guard attempt < maxAttempts - 1, shouldRetry(error) else { throw error }
|
|
287
|
-
let capped = min(initialDelay * (1 << attempt), maxDelay)
|
|
288
|
-
try await Task.sleep(for: capped + capped * Double.random(in: 0...0.1))
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
preconditionFailure("the last attempt always returns or throws")
|
|
292
|
-
}
|
|
293
|
-
```
|
|
237
|
+
Code: [Retry with exponential backoff](references/code-examples.md#retry-with-exponential-backoff).
|
|
294
238
|
|
|
295
239
|
There is no sleep after the last attempt: its error is rethrown at once. The
|
|
296
240
|
`isTransient` predicate is in
|
|
@@ -412,3 +356,4 @@ path instead of failing immediately. Implement
|
|
|
412
356
|
- [lightweight clients](references/lightweight-clients.md): struct-of-closures client injected through `init` for tests and previews.
|
|
413
357
|
- [Network framework guide](references/network-framework.md): `NWConnection`, `NWListener`, `NWBrowser`, `NWPathMonitor`, raw TCP, UDP and WebSocket, iOS 26 `NetworkConnection`.
|
|
414
358
|
- [file storage patterns](references/file-storage-patterns.md): directory choice, `FileProtectionType`, backup exclusion, low-storage handling.
|
|
359
|
+
- [Code examples](references/code-examples.md): open for the full code behind Protocol-based client, Retry with exponential backoff.
|