feat(ios): complete accessibility and motion semantics - #443
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Bead psyche-i7c.10.3 (mirror #212) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
e107d31 to
bcdb8ba
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
Unresolved moderate accessibility, Dynamic Type, and UI-test findings remain.
Pull request overview
This pull request improves iOS accessibility semantics, Dynamic Type identity, action discoverability, and Reduce Motion behavior.
Changes:
- Adds textual status and host/project/branch accessibility summaries with responsive fallbacks.
- Adds stable pane-action identifiers and accessibility test coverage.
- Disables nonessential animations when Reduce Motion is enabled.
File summaries
| File | Reviewed changes | Final review notes |
|---|---|---|
native/ios/PsycheApp/UnitTests/PaneIndicatorTests.swift |
Tests textual indicator status semantics. | No final findings. |
native/ios/PsycheApp/UnitTests/PaneAccessibilityTests.swift |
Tests host-aware accessibility labels and summaries. | No final findings. |
native/ios/PsycheApp/UnitTests/CreatePaneFormTests.swift |
Tests consequence-focused labels. | No final findings. |
native/ios/PsycheApp/Tests/PsycheAppUITests/PsycheAppUITests.swift |
Adds accessibility-size Now-to-pane action-sheet coverage. | Moderate, 1 vote: use the containing cell or openPane instead of tapping a potentially non-hittable generic match. |
native/ios/PsycheApp/Sources/PsycheApp/Views/ProjectsView.swift |
Adds host identity and Dynamic Type project/pane fallbacks. | Moderate, 1 vote: measure compact ViewThatFits candidates as single lines so long identity text selects the fallback. |
native/ios/PsycheApp/Sources/PsycheApp/Views/PaneSwitcher.swift |
Displays textual pane status. | No final findings. |
native/ios/PsycheApp/Sources/PsycheApp/Views/PaneControls.swift |
Adds stable identifiers and consequence-focused labels. | No final findings. |
native/ios/PsycheApp/Sources/PsycheApp/Views/PaneAccessibility.swift |
Builds host-aware accessibility summaries. | P2 / moderate, 1 vote: avoid producing a dangling host fragment for empty names.P2 / moderate, 1 vote: retain pane status in compact project-detail summaries when an agent is present. |
native/ios/PsycheApp/Sources/PsycheApp/Views/NowView.swift |
Adds responsive identity summaries for larger text sizes. | Moderate, 1 vote: ensure wrapped compact candidates select the vertical fallback. P2 / moderate, 1 vote: include status in the agent-present fallback. |
native/ios/PsycheApp/Sources/PsycheApp/PsycheApp.swift |
Supports Dynamic Type UI-test configuration. | No final findings. |
native/ios/PsycheApp/Sources/PsycheApp/Design/PsycheTheme.swift |
Adds indicator accessibility semantics. | P2 / moderate, 1 vote: preserve underlying quiet statuses instead of announcing all as “Idle”. P2 / moderate, 1 vote: prevent status dots from being announced twice in composite controls. |
native/ios/PsycheApp/Sources/PsycheApp/CockpitView.swift |
Applies Reduce Motion transaction handling. | No final findings. |
Review details
Suppressed comments (9)
native/ios/PsycheApp/Sources/PsycheApp/Design/PsycheTheme.swift:78
- [P2] Preserve the underlying quiet status in accessibility text.
forStatusintentionally mapsexited,unknown, andhibernatingto.quiet, so this value makes all of them announce as “Idle”, which is inaccurate and can mislead VoiceOver users about whether a pane is still active. Use the raw status for quiet cases (or retain it in the indicator) instead of always using the collapsed label.
.accessibilityValue(indicator.text)
native/ios/PsycheApp/Sources/PsycheApp/Design/PsycheTheme.swift:78
- [P2] Avoid exposing the dot twice in composite controls. Every current use of
WorkspaceStatusDotis inside a row withaccessibilityElement(children: .combine)or a button with an explicit accessibility label, and those labels already speak the pane state. RemovingaccessibilityHiddenmakes the dot'sStatus/value participate alongside the parent label, so VoiceOver can announce the same state twice; hide it in those composite call sites or expose it only when used standalone.
.accessibilityLabel("Status")
.accessibilityValue(indicator.text)
native/ios/PsycheApp/Sources/PsycheApp/Views/NowView.swift:71
- At accessibility sizes this first candidate can wrap into the proposed width, and the
.lineLimit(2)below is inherited by the candidate during measurement.ViewThatFitstherefore considers it a fit instead of selecting the vertical fallback; long project/host/status strings can be truncated to two lines and hide the identity this change is meant to preserve. Measure the compact candidate as a single horizontal line so the fallback is selected when it cannot fit.
Text(contextLine)
native/ios/PsycheApp/Sources/PsycheApp/Views/NowView.swift:78
- [P2] Preserve the status in the accessibility-size fallback. When
item.agentis present (as it is for the fixture's web-home pane), this branch renders project, agent, and host but omits the pane status entirely. The fallback is the layout intended to preserve visible semantics at large Dynamic Type sizes, so renderitem.statusas its own line after the optional agent instead of usingelse.
if let agent = item.agent {
Text(agent)
} else {
Text(item.status)
}
native/ios/PsycheApp/Sources/PsycheApp/Views/PaneAccessibility.swift:23
- [P2] Avoid creating a host fragment for an empty host name.
hostName.mapturns""into the non-empty string"host ", so the filter cannot remove it and rows can show/speak a dangling host label when a paired server has no name. Filter the optional name before formatting it, as the project subtitle and label helpers already do.
[projectTitle, agent ?? status, hostName.map { "host \($0)" }]
native/ios/PsycheApp/Sources/PsycheApp/Views/PaneAccessibility.swift:23
- [P2] Keep the pane status in the project-detail row's compact summary. Before this helper was introduced,
WorkspacePaneRowrendered bothpane.agentandpane.status;agent ?? statusnow drops the status whenever an agent is present, so the normal row shows only the agent while the status remains available only in the fallback/accessibility label. Use a summary that includes both for this row, or keep the agent-preferred helper separate for Now.
[projectTitle, agent ?? status, hostName.map { "host \($0)" }]
native/ios/PsycheApp/Sources/PsycheApp/Views/ProjectsView.swift:55
- At accessibility sizes this first candidate can wrap into the proposed width, and the
.lineLimit(2)below is inherited by the candidate during measurement.ViewThatFitstherefore considers it a fit instead of selecting the vertical fallback; a long branch/host/count subtitle can be truncated and lose essential project identity. Measure the compact candidate as a single horizontal line so the fallback is selected when it cannot fit.
Text(subtitle)
native/ios/PsycheApp/Sources/PsycheApp/Views/ProjectsView.swift:215
- At accessibility sizes this first candidate can wrap into the proposed width, and the
.lineLimit(2)below is inherited by the candidate during measurement.ViewThatFitstherefore considers it a fit instead of selecting the vertical fallback; a long project/agent/host context can be truncated to two lines and lose the identity this change is intended to preserve. Measure the compact candidate as a single horizontal line so the fallback is selected when it cannot fit.
Text(contextLine)
native/ios/PsycheApp/Tests/PsycheAppUITests/PsycheAppUITests.swift:129
- The row helper documents that a SwiftUI row identifier can resolve through a generic
element(...).firstMatchto a non-hittable image or text descendant, which is why existing navigation uses the containing cell. This new test bypasses that helper and taps the generic match, so it can fail before exercising the Now-to-pane path. Query the containing cell (or callopenPane) here.
let paneRow = element("now-pane-web-home", in: app)
XCTAssertTrue(paneRow.waitForExistence(timeout: 30))
XCTAssertTrue(paneRow.label.contains("open-coven.dev"), paneRow.label)
XCTAssertTrue(paneRow.label.contains("psyche-demo.local"), paneRow.label)
paneRow.tap()
- Files reviewed: 12/12 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Summary
-uiReduceMotionis supplied by UI tests.Acceptance
Status+ state value; rows/switcher also include textual state.ViewThatFitsvertical fallbacks with project, branch, and host identity.-uiReduceMotionUI fixtures.testAccessibleTextSizeAndReduceMotionCompleteNowPaneActionSheetPathusing identifiers/labels withaccessibility3.Commands and results
xcodebuild test -project native/ios/Psyche.xcodeproj -scheme PsycheApp -destination 'platform=iOS Simulator,name=iPhone 16 Pro,OS=26.5' -only-testing:PsycheCoreTests CODE_SIGNING_ALLOWED=NO— passed, 453 tests.xcodebuild test -project native/ios/Psyche.xcodeproj -scheme PsycheApp -destination 'platform=iOS Simulator,name=iPhone 16 Pro,OS=26.5' -only-testing:PsycheAppTests CODE_SIGNING_ALLOWED=NO— passed, 115 tests.xcodebuild test -project native/ios/Psyche.xcodeproj -scheme PsycheApp -destination 'platform=iOS Simulator,name=iPhone 16 Pro,OS=26.5' -only-testing:PsycheAppUITests CODE_SIGNING_ALLOWED=NO— passed, 48 tests, 8 skipped regular-width tests on iPhone simulator.pnpm ios:project:check— passed.git diff --check— passed.Gaps
Rollback
Revert commit
e107d317acadf263fe744c2b9b844de4291a3625to remove the iOS accessibility/motion semantics changes and added tests.References
Bead psyche-i7c.10.3 (mirror #212); outcome #200