Skip to content

feat(ios): complete accessibility and motion semantics - #443

Merged
BunsDev merged 1 commit into
mainfrom
feat/ios-accessibility-motion-semantics
Sep 11, 2026
Merged

feat(ios): complete accessibility and motion semantics#443
BunsDev merged 1 commit into
mainfrom
feat/ios-accessibility-motion-semantics

Conversation

@BunsDev

@BunsDev BunsDev commented Sep 11, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds explicit textual/accessibility status semantics for pane indicators and visible pane switcher status text.
  • Adds combined project/branch/host summaries with Dynamic Type fallbacks so identity survives accessibility text sizes.
  • Adds stable identifiers for pane actions and an accessibility-size Reduce Motion UI path test from Now to pane to remote action sheet.
  • Honors Reduce Motion by disabling nonessential SwiftUI transaction animations when system Reduce Motion is enabled or -uiReduceMotion is supplied by UI tests.

Acceptance

Criterion Result
Status is never color-only Pane indicators now expose Status + state value; rows/switcher also include textual state.
Essential identity survives accessibility text sizes Now/project/pane rows use ViewThatFits vertical fallbacks with project, branch, and host identity.
Focused pane announces selected state Existing focused terminal selected trait remains covered by UI tests.
Reduce Motion removes nonessential transitions Cockpit disables transaction animations for system Reduce Motion and -uiReduceMotion UI fixtures.
Accessibility UI test completes Now → pane → action sheet Added testAccessibleTextSizeAndReduceMotionCompleteNowPaneActionSheetPath using identifiers/labels with accessibility3.

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

  • Simulator-only evidence; VoiceOver was not exercised on a physical device.
  • Local Xcode is 26.6 with iOS 26.5 simulators available; CI pins Xcode 26.2.
  • The requested iPhone 17 simulator was not installed locally, so validation used the available iPhone 16 Pro simulator. Regular-width UI tests were skipped by existing guards on that device class.
  • One combined unit invocation showed a transient PsycheCore readiness failure; retrying the PsycheCore target passed.

Rollback

Revert commit e107d317acadf263fe744c2b9b844de4291a3625 to remove the iOS accessibility/motion semantics changes and added tests.

References

Bead psyche-i7c.10.3 (mirror #212); outcome #200

Copilot AI lite review requested due to automatic review settings September 11, 2026 17:09
@vercel

vercel Bot commented Sep 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
psyche-build-docs Ready Ready Preview Sep 11, 2026 5:10pm UTC

Request Review

Bead psyche-i7c.10.3 (mirror #212)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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. forStatus intentionally maps exited, unknown, and hibernating to .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 WorkspaceStatusDot is inside a row with accessibilityElement(children: .combine) or a button with an explicit accessibility label, and those labels already speak the pane state. Removing accessibilityHidden makes the dot's Status/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. ViewThatFits therefore 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.agent is 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 render item.status as its own line after the optional agent instead of using else.
                        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.map turns "" 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, WorkspacePaneRow rendered both pane.agent and pane.status; agent ?? status now 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. ViewThatFits therefore 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. ViewThatFits therefore 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(...).firstMatch to 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 call openPane) 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.

@BunsDev
BunsDev merged commit 870f6c1 into main Sep 11, 2026
10 checks passed
@BunsDev
BunsDev deleted the feat/ios-accessibility-motion-semantics branch September 11, 2026 17:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants