buildanything 1.7.0 → 1.8.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +55 -0
- package/README.md +71 -61
- package/agents/ios-app-review-guardian.md +49 -0
- package/agents/ios-foundation-models-specialist.md +46 -0
- package/agents/ios-storekit-specialist.md +52 -0
- package/agents/ios-swift-architect.md +102 -0
- package/agents/ios-swift-search.md +130 -0
- package/agents/ios-swift-ui-design.md +104 -0
- package/commands/build.md +80 -176
- package/commands/fix.md +65 -0
- package/commands/setup.md +73 -0
- package/commands/ux-review.md +63 -0
- package/commands/verify.md +72 -0
- package/hooks/session-start +18 -1
- package/package.json +5 -2
- package/protocols/brainstorm.md +99 -0
- package/protocols/build-fix.md +52 -0
- package/protocols/cleanup.md +54 -0
- package/protocols/design.md +269 -0
- package/protocols/eval-harness.md +61 -0
- package/protocols/fake-data-detector.md +64 -0
- package/protocols/ios-context.md +235 -0
- package/protocols/ios-frameworks-map.md +323 -0
- package/protocols/ios-phase-branches.md +162 -0
- package/protocols/ios-preflight.md +27 -0
- package/protocols/metric-loop.md +93 -0
- package/protocols/planning.md +87 -0
- package/protocols/smoke-test.md +110 -0
- package/protocols/verify.md +67 -0
- package/protocols/web-phase-branches.md +201 -0
- package/skills/ios/_VENDORED.md +60 -0
- package/skills/ios/activitykit/LICENSE +131 -0
- package/skills/ios/activitykit/SKILL.md +505 -0
- package/skills/ios/activitykit/references/activitykit-patterns.md +868 -0
- package/skills/ios/app-intents/LICENSE +131 -0
- package/skills/ios/app-intents/SKILL.md +494 -0
- package/skills/ios/app-intents/references/appintents-advanced.md +1076 -0
- package/skills/ios/apple-on-device-ai/LICENSE +131 -0
- package/skills/ios/apple-on-device-ai/SKILL.md +505 -0
- package/skills/ios/apple-on-device-ai/references/coreml-conversion.md +425 -0
- package/skills/ios/apple-on-device-ai/references/coreml-optimization.md +344 -0
- package/skills/ios/apple-on-device-ai/references/foundation-models.md +508 -0
- package/skills/ios/apple-on-device-ai/references/mlx-swift.md +285 -0
- package/skills/ios/ios-26-platform/SKILL.md +53 -0
- package/skills/ios/ios-26-platform/references/automatic-adoption.md +161 -0
- package/skills/ios/ios-26-platform/references/backward-compat.md +238 -0
- package/skills/ios/ios-26-platform/references/liquid-glass.md +255 -0
- package/skills/ios/ios-26-platform/references/swiftui-apis.md +277 -0
- package/skills/ios/ios-26-platform/references/toolbar-navigation.md +250 -0
- package/skills/ios/ios-bootstrap/SKILL.md +98 -0
- package/skills/ios/ios-bootstrap/references/apple-docs-mcp-config.md +28 -0
- package/skills/ios/ios-bootstrap/references/new-project-dialog.md +41 -0
- package/skills/ios/ios-bootstrap/references/xcode-mcp-config.md +29 -0
- package/skills/ios/ios-debugger-agent/LICENSE +21 -0
- package/skills/ios/ios-debugger-agent/SKILL.md +58 -0
- package/skills/ios/ios-debugger-agent/agents/openai.yaml +4 -0
- package/skills/ios/ios-entitlements-generator/SKILL.md +47 -0
- package/skills/ios/ios-hig/SKILL.md +41 -0
- package/skills/ios/ios-hig/references/accessibility.md +81 -0
- package/skills/ios/ios-hig/references/content.md +142 -0
- package/skills/ios/ios-hig/references/feedback.md +123 -0
- package/skills/ios/ios-hig/references/interaction.md +199 -0
- package/skills/ios/ios-hig/references/performance-platform.md +129 -0
- package/skills/ios/ios-hig/references/privacy-permissions.md +181 -0
- package/skills/ios/ios-hig/references/visual-design.md +84 -0
- package/skills/ios/ios-info-plist-hardening/SKILL.md +130 -0
- package/skills/ios/ios-maestro-flow-author/SKILL.md +68 -0
- package/skills/ios/ios-maestro-flow-author/references/input-and-scroll.yaml +17 -0
- package/skills/ios/ios-maestro-flow-author/references/modal-and-dismiss.yaml +14 -0
- package/skills/ios/ios-maestro-flow-author/references/onboarding-flow.yaml +16 -0
- package/skills/ios/ios-maestro-flow-author/references/tab-navigation.yaml +13 -0
- package/skills/ios/ios-maestro-flow-author/references/tap-and-assert.yaml +9 -0
- package/skills/ios/swift-accessibility/LICENSE +21 -0
- package/skills/ios/swift-accessibility/SKILL.md +371 -0
- package/skills/ios/swift-accessibility/examples/before-after-appkit.md +446 -0
- package/skills/ios/swift-accessibility/examples/before-after-swiftui.md +441 -0
- package/skills/ios/swift-accessibility/examples/before-after-uikit.md +464 -0
- package/skills/ios/swift-accessibility/references/assistive-access.md +441 -0
- package/skills/ios/swift-accessibility/references/display-settings.md +491 -0
- package/skills/ios/swift-accessibility/references/dynamic-type.md +420 -0
- package/skills/ios/swift-accessibility/references/media-accessibility.md +421 -0
- package/skills/ios/swift-accessibility/references/motor-input.md +393 -0
- package/skills/ios/swift-accessibility/references/nutrition-labels.md +362 -0
- package/skills/ios/swift-accessibility/references/platform-specifics.md +515 -0
- package/skills/ios/swift-accessibility/references/semantic-structure.md +585 -0
- package/skills/ios/swift-accessibility/references/testing-auditing.md +507 -0
- package/skills/ios/swift-accessibility/references/voice-control.md +317 -0
- package/skills/ios/swift-accessibility/references/voiceover-swiftui.md +584 -0
- package/skills/ios/swift-accessibility/references/voiceover-uikit.md +519 -0
- package/skills/ios/swift-accessibility/references/wcag-mapping.md +167 -0
- package/skills/ios/swift-accessibility/resources/audit-template.swift +128 -0
- package/skills/ios/swift-accessibility/resources/qa-checklist.md +258 -0
- package/skills/ios/swift-concurrency/LICENSE +21 -0
- package/skills/ios/swift-concurrency/SKILL.md +171 -0
- package/skills/ios/swift-concurrency/references/_index.md +50 -0
- package/skills/ios/swift-concurrency/references/actors.md +660 -0
- package/skills/ios/swift-concurrency/references/async-algorithms.md +847 -0
- package/skills/ios/swift-concurrency/references/async-await-basics.md +266 -0
- package/skills/ios/swift-concurrency/references/async-sequences.md +710 -0
- package/skills/ios/swift-concurrency/references/core-data.md +560 -0
- package/skills/ios/swift-concurrency/references/glossary.md +135 -0
- package/skills/ios/swift-concurrency/references/linting.md +155 -0
- package/skills/ios/swift-concurrency/references/memory-management.md +569 -0
- package/skills/ios/swift-concurrency/references/migration.md +1104 -0
- package/skills/ios/swift-concurrency/references/performance.md +593 -0
- package/skills/ios/swift-concurrency/references/sendable.md +598 -0
- package/skills/ios/swift-concurrency/references/tasks.md +636 -0
- package/skills/ios/swift-concurrency/references/testing.md +592 -0
- package/skills/ios/swift-concurrency/references/threading.md +495 -0
- package/skills/ios/swift-security-expert/LICENSE +21 -0
- package/skills/ios/swift-security-expert/SKILL.md +470 -0
- package/skills/ios/swift-security-expert/references/biometric-authentication.md +565 -0
- package/skills/ios/swift-security-expert/references/certificate-trust.md +592 -0
- package/skills/ios/swift-security-expert/references/common-anti-patterns.md +690 -0
- package/skills/ios/swift-security-expert/references/compliance-owasp-mapping.md +537 -0
- package/skills/ios/swift-security-expert/references/credential-storage-patterns.md +721 -0
- package/skills/ios/swift-security-expert/references/cryptokit-public-key.md +505 -0
- package/skills/ios/swift-security-expert/references/cryptokit-symmetric.md +497 -0
- package/skills/ios/swift-security-expert/references/keychain-access-control.md +508 -0
- package/skills/ios/swift-security-expert/references/keychain-fundamentals.md +596 -0
- package/skills/ios/swift-security-expert/references/keychain-item-classes.md +476 -0
- package/skills/ios/swift-security-expert/references/keychain-sharing.md +458 -0
- package/skills/ios/swift-security-expert/references/migration-legacy-stores.md +727 -0
- package/skills/ios/swift-security-expert/references/secure-enclave.md +539 -0
- package/skills/ios/swift-security-expert/references/testing-security-code.md +781 -0
- package/skills/ios/swift-testing-expert/LICENSE +21 -0
- package/skills/ios/swift-testing-expert/SKILL.md +79 -0
- package/skills/ios/swift-testing-expert/references/_index.md +12 -0
- package/skills/ios/swift-testing-expert/references/async-testing-and-waiting.md +127 -0
- package/skills/ios/swift-testing-expert/references/expectations.md +145 -0
- package/skills/ios/swift-testing-expert/references/fundamentals.md +141 -0
- package/skills/ios/swift-testing-expert/references/migration-from-xctest.md +127 -0
- package/skills/ios/swift-testing-expert/references/parallelization-and-isolation.md +95 -0
- package/skills/ios/swift-testing-expert/references/parameterized-testing.md +284 -0
- package/skills/ios/swift-testing-expert/references/performance-and-best-practices.md +187 -0
- package/skills/ios/swift-testing-expert/references/traits-and-tags.md +114 -0
- package/skills/ios/swift-testing-expert/references/xcode-workflows.md +70 -0
- package/skills/ios/swiftdata-pro/LICENSE +21 -0
- package/skills/ios/swiftdata-pro/SKILL.md +102 -0
- package/skills/ios/swiftdata-pro/agents/openai.yaml +10 -0
- package/skills/ios/swiftdata-pro/assets/swiftdata-pro-icon.png +0 -0
- package/skills/ios/swiftdata-pro/assets/swiftdata-pro-icon.svg +29 -0
- package/skills/ios/swiftdata-pro/references/class-inheritance.md +104 -0
- package/skills/ios/swiftdata-pro/references/cloudkit.md +10 -0
- package/skills/ios/swiftdata-pro/references/core-rules.md +20 -0
- package/skills/ios/swiftdata-pro/references/indexing.md +27 -0
- package/skills/ios/swiftdata-pro/references/predicates.md +73 -0
- package/skills/ios/swiftui-design-principles/AGENTS.md +21 -0
- package/skills/ios/swiftui-design-principles/LICENSE +21 -0
- package/skills/ios/swiftui-design-principles/README.md +41 -0
- package/skills/ios/swiftui-design-principles/SKILL.md +605 -0
- package/skills/ios/swiftui-design-principles/metadata.json +10 -0
- package/skills/ios/swiftui-liquid-glass/LICENSE +21 -0
- package/skills/ios/swiftui-liquid-glass/SKILL.md +95 -0
- package/skills/ios/swiftui-liquid-glass/agents/openai.yaml +4 -0
- package/skills/ios/swiftui-liquid-glass/references/liquid-glass.md +280 -0
- package/skills/ios/swiftui-performance-audit/LICENSE +21 -0
- package/skills/ios/swiftui-performance-audit/SKILL.md +111 -0
- package/skills/ios/swiftui-performance-audit/agents/openai.yaml +4 -0
- package/skills/ios/swiftui-performance-audit/references/code-smells.md +150 -0
- package/skills/ios/swiftui-performance-audit/references/demystify-swiftui-performance-wwdc23.md +46 -0
- package/skills/ios/swiftui-performance-audit/references/optimizing-swiftui-performance-instruments.md +29 -0
- package/skills/ios/swiftui-performance-audit/references/profiling-intake.md +44 -0
- package/skills/ios/swiftui-performance-audit/references/report-template.md +47 -0
- package/skills/ios/swiftui-performance-audit/references/understanding-hangs-in-your-app.md +33 -0
- package/skills/ios/swiftui-performance-audit/references/understanding-improving-swiftui-performance.md +52 -0
- package/skills/ios/swiftui-pro/LICENSE +21 -0
- package/skills/ios/swiftui-pro/SKILL.md +108 -0
- package/skills/ios/swiftui-pro/agents/openai.yaml +10 -0
- package/skills/ios/swiftui-pro/assets/swiftui-pro-icon.png +0 -0
- package/skills/ios/swiftui-pro/assets/swiftui-pro-icon.svg +29 -0
- package/skills/ios/swiftui-pro/references/accessibility.md +13 -0
- package/skills/ios/swiftui-pro/references/api.md +39 -0
- package/skills/ios/swiftui-pro/references/data.md +43 -0
- package/skills/ios/swiftui-pro/references/design.md +31 -0
- package/skills/ios/swiftui-pro/references/hygiene.md +9 -0
- package/skills/ios/swiftui-pro/references/navigation.md +14 -0
- package/skills/ios/swiftui-pro/references/performance.md +46 -0
- package/skills/ios/swiftui-pro/references/swift.md +56 -0
- package/skills/ios/swiftui-pro/references/views.md +35 -0
- package/skills/ios/swiftui-ui-patterns/LICENSE +21 -0
- package/skills/ios/swiftui-ui-patterns/SKILL.md +100 -0
- package/skills/ios/swiftui-ui-patterns/agents/openai.yaml +4 -0
- package/skills/ios/swiftui-ui-patterns/references/app-wiring.md +201 -0
- package/skills/ios/swiftui-ui-patterns/references/async-state.md +96 -0
- package/skills/ios/swiftui-ui-patterns/references/components-index.md +50 -0
- package/skills/ios/swiftui-ui-patterns/references/controls.md +57 -0
- package/skills/ios/swiftui-ui-patterns/references/deeplinks.md +66 -0
- package/skills/ios/swiftui-ui-patterns/references/focus.md +90 -0
- package/skills/ios/swiftui-ui-patterns/references/form.md +97 -0
- package/skills/ios/swiftui-ui-patterns/references/grids.md +71 -0
- package/skills/ios/swiftui-ui-patterns/references/haptics.md +71 -0
- package/skills/ios/swiftui-ui-patterns/references/input-toolbar.md +51 -0
- package/skills/ios/swiftui-ui-patterns/references/lightweight-clients.md +93 -0
- package/skills/ios/swiftui-ui-patterns/references/list.md +86 -0
- package/skills/ios/swiftui-ui-patterns/references/loading-placeholders.md +38 -0
- package/skills/ios/swiftui-ui-patterns/references/macos-settings.md +71 -0
- package/skills/ios/swiftui-ui-patterns/references/matched-transitions.md +59 -0
- package/skills/ios/swiftui-ui-patterns/references/media.md +73 -0
- package/skills/ios/swiftui-ui-patterns/references/menu-bar.md +101 -0
- package/skills/ios/swiftui-ui-patterns/references/navigationstack.md +159 -0
- package/skills/ios/swiftui-ui-patterns/references/overlay.md +45 -0
- package/skills/ios/swiftui-ui-patterns/references/performance.md +62 -0
- package/skills/ios/swiftui-ui-patterns/references/previews.md +48 -0
- package/skills/ios/swiftui-ui-patterns/references/scroll-reveal.md +133 -0
- package/skills/ios/swiftui-ui-patterns/references/scrollview.md +87 -0
- package/skills/ios/swiftui-ui-patterns/references/searchable.md +71 -0
- package/skills/ios/swiftui-ui-patterns/references/sheets.md +155 -0
- package/skills/ios/swiftui-ui-patterns/references/split-views.md +72 -0
- package/skills/ios/swiftui-ui-patterns/references/tabview.md +114 -0
- package/skills/ios/swiftui-ui-patterns/references/theming.md +71 -0
- package/skills/ios/swiftui-ui-patterns/references/title-menus.md +93 -0
- package/skills/ios/swiftui-ui-patterns/references/top-bar.md +49 -0
- package/skills/ios/swiftui-view-refactor/LICENSE +21 -0
- package/skills/ios/swiftui-view-refactor/SKILL.md +207 -0
- package/skills/ios/swiftui-view-refactor/agents/openai.yaml +4 -0
- package/skills/ios/swiftui-view-refactor/references/mv-patterns.md +161 -0
- package/skills/ios/widgetkit/LICENSE +131 -0
- package/skills/ios/widgetkit/SKILL.md +502 -0
- package/skills/ios/widgetkit/references/widgetkit-advanced.md +871 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
# Before/After: AppKit Accessibility
|
|
2
|
+
|
|
3
|
+
Concrete code transformations for macOS AppKit apps. Each example shows the inaccessible version, the corrected version, and a summary of every change.
|
|
4
|
+
|
|
5
|
+
Priority tiers:
|
|
6
|
+
- **Blocks Assistive Tech** — Element is completely unreachable or unusable
|
|
7
|
+
- **Degrades Experience** — Reachable with significant friction
|
|
8
|
+
- **Incomplete Support** — Gaps that prevent Nutrition Label claims
|
|
9
|
+
|
|
10
|
+
## Contents
|
|
11
|
+
|
|
12
|
+
### Blocks Assistive Tech
|
|
13
|
+
- Icon-only NSButton missing label
|
|
14
|
+
- Custom NSView not in accessibility tree
|
|
15
|
+
- NSTableView row without accessible summary
|
|
16
|
+
|
|
17
|
+
### Degrades Experience
|
|
18
|
+
- Keyboard focus missing on custom view
|
|
19
|
+
- Context menu without keyboard equivalent
|
|
20
|
+
- Wrong role on custom control
|
|
21
|
+
|
|
22
|
+
### Incomplete Support
|
|
23
|
+
- Hardcoded font sizes (no Dynamic Type)
|
|
24
|
+
- Color-only status in NSTableView cell
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## [Blocks Assistive Tech] Icon-only NSButton missing label
|
|
29
|
+
|
|
30
|
+
**Problem:** VoiceOver announces "button" with no description. The user cannot tell what the button does.
|
|
31
|
+
This commonly happens with icon-only controls backed by custom assets when you rely on visuals alone.
|
|
32
|
+
|
|
33
|
+
```swift
|
|
34
|
+
// ❌ Before
|
|
35
|
+
let shareButton = NSButton()
|
|
36
|
+
shareButton.image = NSImage(named: "share")
|
|
37
|
+
shareButton.imageScaling = .scaleProportionallyDown
|
|
38
|
+
shareButton.isBordered = false
|
|
39
|
+
shareButton.bezelStyle = .toolbar
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```swift
|
|
43
|
+
// ✅ After
|
|
44
|
+
let shareButton = NSButton()
|
|
45
|
+
shareButton.image = NSImage(named: "share")
|
|
46
|
+
shareButton.imageScaling = .scaleProportionallyDown
|
|
47
|
+
shareButton.isBordered = false
|
|
48
|
+
shareButton.bezelStyle = .toolbar
|
|
49
|
+
shareButton.setAccessibilityLabel("Share") // [VERIFY] confirm label matches intent
|
|
50
|
+
shareButton.toolTip = "Share"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Changes:**
|
|
54
|
+
| Change | Why |
|
|
55
|
+
|---|---|
|
|
56
|
+
| Kept the same icon-only visual in both versions | Isolates the accessibility delta to semantic labeling, not visuals |
|
|
57
|
+
| Added `setAccessibilityLabel("Share")` | Aligns with AppKit guidance: the control, not its decorative image, owns the semantic label |
|
|
58
|
+
| Kept custom image asset unchanged | Ensures the improvement comes from semantics, not image substitution |
|
|
59
|
+
| Added `toolTip` | Improves discoverability for sighted keyboard and pointer users |
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## [Blocks Assistive Tech] Custom NSView not in accessibility tree
|
|
64
|
+
|
|
65
|
+
**Problem:** A custom-drawn card view exists visually but VoiceOver cannot reach it at all.
|
|
66
|
+
|
|
67
|
+
```swift
|
|
68
|
+
// ❌ Before
|
|
69
|
+
class ProjectCardView: NSView {
|
|
70
|
+
var title: String = ""
|
|
71
|
+
var status: String = ""
|
|
72
|
+
|
|
73
|
+
override func draw(_ dirtyRect: NSRect) {
|
|
74
|
+
// Custom drawing...
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```swift
|
|
80
|
+
// ✅ After
|
|
81
|
+
class ProjectCardView: NSView {
|
|
82
|
+
var title: String = "" {
|
|
83
|
+
didSet { updateAccessibility() }
|
|
84
|
+
}
|
|
85
|
+
var status: String = "" {
|
|
86
|
+
didSet { updateAccessibility() }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
override init(frame frameRect: NSRect) {
|
|
90
|
+
super.init(frame: frameRect)
|
|
91
|
+
setAccessibilityElement(true)
|
|
92
|
+
setAccessibilityRole(.group)
|
|
93
|
+
updateAccessibility()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
required init?(coder: NSCoder) {
|
|
97
|
+
super.init(coder: coder)
|
|
98
|
+
setAccessibilityElement(true)
|
|
99
|
+
setAccessibilityRole(.group)
|
|
100
|
+
updateAccessibility()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private func updateAccessibility() {
|
|
104
|
+
setAccessibilityLabel("\(title), \(status)")
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
override func draw(_ dirtyRect: NSRect) {
|
|
108
|
+
// Custom drawing...
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Changes:**
|
|
114
|
+
| Change | Why |
|
|
115
|
+
|---|---|
|
|
116
|
+
| Added `setAccessibilityElement(true)` | Exposes the custom-drawn view to the accessibility tree |
|
|
117
|
+
| Added `setAccessibilityRole(.group)` | Announces semantic container role instead of generic unlabeled content |
|
|
118
|
+
| Added `updateAccessibility()` and property observers | Keeps label synchronized as title/status change |
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## [Blocks Assistive Tech] NSTableView row without accessible summary
|
|
123
|
+
|
|
124
|
+
**Problem:** VoiceOver reads individual cells but cannot summarize the row. Users hear fragmented information with no context.
|
|
125
|
+
|
|
126
|
+
```swift
|
|
127
|
+
// ❌ Before
|
|
128
|
+
class TaskRowView: NSTableRowView {
|
|
129
|
+
var taskName: String = ""
|
|
130
|
+
var assignee: String = ""
|
|
131
|
+
var dueDate: String = ""
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
```swift
|
|
136
|
+
// ✅ After
|
|
137
|
+
class TaskRowView: NSTableRowView {
|
|
138
|
+
var taskName: String = "" {
|
|
139
|
+
didSet { updateAccessibility() }
|
|
140
|
+
}
|
|
141
|
+
var assignee: String = "" {
|
|
142
|
+
didSet { updateAccessibility() }
|
|
143
|
+
}
|
|
144
|
+
var dueDate: String = "" {
|
|
145
|
+
didSet { updateAccessibility() }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
override init(frame frameRect: NSRect) {
|
|
149
|
+
super.init(frame: frameRect)
|
|
150
|
+
setAccessibilityElement(true)
|
|
151
|
+
updateAccessibility()
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
required init?(coder: NSCoder) {
|
|
155
|
+
super.init(coder: coder)
|
|
156
|
+
setAccessibilityElement(true)
|
|
157
|
+
updateAccessibility()
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private func updateAccessibility() {
|
|
161
|
+
setAccessibilityLabel(taskName)
|
|
162
|
+
setAccessibilityValue("Assigned to \(assignee), due \(dueDate)")
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Changes:**
|
|
168
|
+
| Change | Why |
|
|
169
|
+
|---|---|
|
|
170
|
+
| Added `setAccessibilityElement(true)` | Ensures the row itself can expose a concise summary |
|
|
171
|
+
| Added `setAccessibilityLabel` | Provides a primary row name (task title) |
|
|
172
|
+
| Added `setAccessibilityValue` | Adds secondary context (assignee and due date) |
|
|
173
|
+
| Added `updateAccessibility()` in observers and initializers | Prevents stale announcements when row data updates |
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## [Degrades Experience] Keyboard focus missing on custom view
|
|
178
|
+
|
|
179
|
+
**Problem:** A clickable card responds to mouse clicks but cannot be reached or activated via keyboard.
|
|
180
|
+
|
|
181
|
+
```swift
|
|
182
|
+
// ❌ Before
|
|
183
|
+
class ClickableCardView: NSView {
|
|
184
|
+
var onClick: (() -> Void)?
|
|
185
|
+
|
|
186
|
+
override func mouseDown(with event: NSEvent) {
|
|
187
|
+
onClick?()
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
```swift
|
|
193
|
+
// ✅ After
|
|
194
|
+
class ClickableCardView: NSView {
|
|
195
|
+
var onClick: (() -> Void)?
|
|
196
|
+
|
|
197
|
+
override var acceptsFirstResponder: Bool { true }
|
|
198
|
+
|
|
199
|
+
override init(frame frameRect: NSRect) {
|
|
200
|
+
super.init(frame: frameRect)
|
|
201
|
+
setAccessibilityElement(true)
|
|
202
|
+
setAccessibilityRole(.button)
|
|
203
|
+
setAccessibilityLabel("Project card") // [VERIFY]
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
required init?(coder: NSCoder) {
|
|
207
|
+
super.init(coder: coder)
|
|
208
|
+
setAccessibilityElement(true)
|
|
209
|
+
setAccessibilityRole(.button)
|
|
210
|
+
setAccessibilityLabel("Project card") // [VERIFY]
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
override func mouseDown(with event: NSEvent) {
|
|
214
|
+
onClick?()
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
override func keyDown(with event: NSEvent) {
|
|
218
|
+
if event.keyCode == 36 || event.keyCode == 49 { // Return or Space
|
|
219
|
+
onClick?()
|
|
220
|
+
} else {
|
|
221
|
+
super.keyDown(with: event)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
override func drawFocusRingMask() {
|
|
226
|
+
bounds.fill()
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
override var focusRingMaskBounds: NSRect { bounds }
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**Changes:**
|
|
234
|
+
| Change | Why |
|
|
235
|
+
|---|---|
|
|
236
|
+
| Added `acceptsFirstResponder` | Enables keyboard focus traversal |
|
|
237
|
+
| Added `keyDown` handling for Return/Space | Supports keyboard activation parity with mouse input |
|
|
238
|
+
| Added focus ring overrides | Makes keyboard focus visible |
|
|
239
|
+
| Added accessibility role and label | Announces the custom view as an actionable control |
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## [Degrades Experience] Context menu without keyboard equivalent
|
|
244
|
+
|
|
245
|
+
**Problem:** Right-click menu is the only way to access actions. Keyboard and VoiceOver users cannot reach them.
|
|
246
|
+
|
|
247
|
+
```swift
|
|
248
|
+
// ❌ Before
|
|
249
|
+
class DocumentView: NSView {
|
|
250
|
+
override func menu(for event: NSEvent) -> NSMenu? {
|
|
251
|
+
let menu = NSMenu()
|
|
252
|
+
menu.addItem(NSMenuItem(title: "Duplicate", action: #selector(duplicateDocument), keyEquivalent: ""))
|
|
253
|
+
menu.addItem(NSMenuItem(title: "Delete", action: #selector(deleteDocument), keyEquivalent: ""))
|
|
254
|
+
return menu
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
```swift
|
|
260
|
+
// ✅ After
|
|
261
|
+
class DocumentView: NSView {
|
|
262
|
+
override func menu(for event: NSEvent) -> NSMenu? {
|
|
263
|
+
let menu = NSMenu()
|
|
264
|
+
let duplicateItem = NSMenuItem(title: "Duplicate", action: #selector(duplicateDocument), keyEquivalent: "d")
|
|
265
|
+
duplicateItem.target = self
|
|
266
|
+
let deleteItem = NSMenuItem(title: "Delete", action: #selector(deleteDocument), keyEquivalent: "\u{8}") // Delete key
|
|
267
|
+
deleteItem.target = self
|
|
268
|
+
menu.addItem(duplicateItem)
|
|
269
|
+
menu.addItem(deleteItem)
|
|
270
|
+
return menu
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
override init(frame frameRect: NSRect) {
|
|
274
|
+
super.init(frame: frameRect)
|
|
275
|
+
configureAccessibilityActions()
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
required init?(coder: NSCoder) {
|
|
279
|
+
super.init(coder: coder)
|
|
280
|
+
configureAccessibilityActions()
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private func configureAccessibilityActions() {
|
|
284
|
+
setAccessibilityCustomActions([
|
|
285
|
+
NSAccessibilityCustomAction(
|
|
286
|
+
name: "Duplicate",
|
|
287
|
+
target: self,
|
|
288
|
+
selector: #selector(duplicateDocument)
|
|
289
|
+
),
|
|
290
|
+
NSAccessibilityCustomAction(
|
|
291
|
+
name: "Delete",
|
|
292
|
+
target: self,
|
|
293
|
+
selector: #selector(deleteDocument)
|
|
294
|
+
)
|
|
295
|
+
])
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
@objc private func duplicateDocument() {
|
|
299
|
+
// Duplicate document content.
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
@objc private func deleteDocument() {
|
|
303
|
+
// Delete document content.
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
**Changes:**
|
|
309
|
+
| Change | Why |
|
|
310
|
+
|---|---|
|
|
311
|
+
| Added `keyEquivalent` values | Exposes right-click actions to keyboard users |
|
|
312
|
+
| Added explicit menu item targets | Makes selector dispatch deterministic |
|
|
313
|
+
| Added `setAccessibilityCustomActions` | Exposes menu-only actions in the VoiceOver Actions rotor |
|
|
314
|
+
| Added selector method implementations | Keeps the after example self-contained and compilable |
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## [Degrades Experience] Wrong role on custom control
|
|
319
|
+
|
|
320
|
+
**Problem:** A custom toggle is exposed as a generic group. VoiceOver doesn't announce it as a toggle or report its state.
|
|
321
|
+
|
|
322
|
+
```swift
|
|
323
|
+
// ❌ Before
|
|
324
|
+
class CustomToggleView: NSView {
|
|
325
|
+
var isOn = false
|
|
326
|
+
|
|
327
|
+
override func mouseDown(with event: NSEvent) {
|
|
328
|
+
isOn.toggle()
|
|
329
|
+
needsDisplay = true
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
```swift
|
|
335
|
+
// ✅ After
|
|
336
|
+
class CustomToggleView: NSView {
|
|
337
|
+
var isOn = false {
|
|
338
|
+
didSet {
|
|
339
|
+
setAccessibilityValue(isOn ? "1" : "0")
|
|
340
|
+
needsDisplay = true
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
override init(frame frameRect: NSRect) {
|
|
345
|
+
super.init(frame: frameRect)
|
|
346
|
+
setAccessibilityElement(true)
|
|
347
|
+
setAccessibilityRole(.checkBox)
|
|
348
|
+
setAccessibilityLabel("Feature toggle") // [VERIFY]
|
|
349
|
+
setAccessibilityValue(isOn ? "1" : "0")
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
required init?(coder: NSCoder) {
|
|
353
|
+
super.init(coder: coder)
|
|
354
|
+
setAccessibilityElement(true)
|
|
355
|
+
setAccessibilityRole(.checkBox)
|
|
356
|
+
setAccessibilityLabel("Feature toggle") // [VERIFY]
|
|
357
|
+
setAccessibilityValue(isOn ? "1" : "0")
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
override func mouseDown(with event: NSEvent) {
|
|
361
|
+
isOn.toggle()
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
override func accessibilityPerformPress() -> Bool {
|
|
365
|
+
isOn.toggle()
|
|
366
|
+
return true
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
**Changes:**
|
|
372
|
+
| Change | Why |
|
|
373
|
+
|---|---|
|
|
374
|
+
| Added `setAccessibilityRole(.checkBox)` | Announces correct control type instead of generic group |
|
|
375
|
+
| Added `setAccessibilityValue` updates | Exposes on/off state for VoiceOver users |
|
|
376
|
+
| Added `accessibilityPerformPress()` | Enables activation from assistive technologies |
|
|
377
|
+
| Added `setAccessibilityLabel` | Provides a stable, human-readable control name |
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## [Incomplete Support] Hardcoded font sizes
|
|
382
|
+
|
|
383
|
+
**Problem:** Text doesn't scale with the system Dynamic Type setting. macOS users who increase text size in System Settings see no change.
|
|
384
|
+
|
|
385
|
+
```swift
|
|
386
|
+
// ❌ Before
|
|
387
|
+
let titleLabel = NSTextField(labelWithString: "Project Name")
|
|
388
|
+
titleLabel.font = NSFont.systemFont(ofSize: 18, weight: .bold)
|
|
389
|
+
|
|
390
|
+
let bodyLabel = NSTextField(labelWithString: "Description")
|
|
391
|
+
bodyLabel.font = NSFont.systemFont(ofSize: 14)
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
```swift
|
|
395
|
+
// ✅ After
|
|
396
|
+
let titleLabel = NSTextField(labelWithString: "Project Name")
|
|
397
|
+
titleLabel.font = NSFont.preferredFont(forTextStyle: .headline)
|
|
398
|
+
|
|
399
|
+
let bodyLabel = NSTextField(labelWithString: "Description")
|
|
400
|
+
bodyLabel.font = NSFont.preferredFont(forTextStyle: .body)
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
**Changes:**
|
|
404
|
+
| Change | Why |
|
|
405
|
+
|---|---|
|
|
406
|
+
| Replaced `systemFont(ofSize:)` with `preferredFont(forTextStyle:)` | Uses user-preferred text styles for scalable typography |
|
|
407
|
+
| Used semantic styles (`.headline`, `.body`) | Preserves content hierarchy while adapting to text size preferences |
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## [Incomplete Support] Color-only status in NSTableView cell
|
|
412
|
+
|
|
413
|
+
**Problem:** A green/red dot indicates task status. In grayscale or for color-blind users, the dots are indistinguishable.
|
|
414
|
+
|
|
415
|
+
```swift
|
|
416
|
+
// ❌ Before
|
|
417
|
+
let statusDot = NSView()
|
|
418
|
+
statusDot.wantsLayer = true
|
|
419
|
+
statusDot.layer?.backgroundColor = task.isComplete ? NSColor.green.cgColor : NSColor.red.cgColor
|
|
420
|
+
statusDot.layer?.cornerRadius = 5
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
```swift
|
|
424
|
+
// ✅ After
|
|
425
|
+
let statusDot = NSView()
|
|
426
|
+
statusDot.wantsLayer = true
|
|
427
|
+
statusDot.layer?.backgroundColor = task.isComplete ? NSColor.systemGreen.cgColor : NSColor.systemRed.cgColor
|
|
428
|
+
statusDot.layer?.cornerRadius = 5
|
|
429
|
+
statusDot.setAccessibilityElement(false)
|
|
430
|
+
|
|
431
|
+
let statusIcon = NSImageView()
|
|
432
|
+
statusIcon.image = NSImage(
|
|
433
|
+
systemSymbolName: task.isComplete ? "checkmark.circle.fill" : "xmark.circle.fill",
|
|
434
|
+
accessibilityDescription: nil
|
|
435
|
+
)
|
|
436
|
+
statusIcon.contentTintColor = task.isComplete ? .systemGreen : .systemRed
|
|
437
|
+
statusIcon.setAccessibilityLabel(task.isComplete ? "Complete" : "Incomplete")
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
**Changes:**
|
|
441
|
+
| Change | Why |
|
|
442
|
+
|---|---|
|
|
443
|
+
| Added icon alongside color | Differentiates status without relying on color alone |
|
|
444
|
+
| Switched to semantic colors (`systemGreen` / `systemRed`) | Adapts better across appearance and contrast settings |
|
|
445
|
+
| Hid decorative dot from accessibility | Avoids duplicate announcements |
|
|
446
|
+
| Added accessibility label on `NSImageView` | Announces semantic status (`Complete` / `Incomplete`) |
|