Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@
## 2026-06-23 - [SwiftUI Button Accessibility]
**Learning:** Found a pattern where SwiftUI icon-only buttons or minimal UI elements were given `.help()` modifiers for hover tooltips but lacked `.accessibilityLabel()` modifiers for screen readers.
**Action:** When adding `.help()` to buttons, always pair it with a corresponding `.accessibilityLabel()` to ensure full accessibility.
## 2026-06-30 - Dynamic Context for Accessibility in Repeated Lists
**Learning:** When using repeated lists (like `ForEach` loops in SwiftUI), icon-only buttons for actions like Edit and Delete become ambiguous for screen reader users and those relying on pointer tooltips. Using a dynamic context, derived from the item's properties (like `name` or `actionDisplayString`), provides essential context and disambiguates the actions.
**Action:** Always include item-specific context in `.help()` and `.accessibilityLabel()` modifiers for icon-only buttons within repeated lists.
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ struct SharedMacroRow: View {
.foregroundColor(.accentColor)
}
.buttonStyle(.borderless)
.help("Edit in shared library")
.accessibilityLabel("Edit in shared library")
.help("Edit \(macro.name.isEmpty ? "Untitled Macro" : macro.name) in shared library")
.accessibilityLabel("Edit \(macro.name.isEmpty ? "Untitled Macro" : macro.name) in shared library")
}
.padding(.horizontal, 12)
.padding(.vertical, 8)
Expand Down Expand Up @@ -206,16 +206,16 @@ struct MacroRow: View {
.foregroundColor(.accentColor)
}
.buttonStyle(.borderless)
.help("Edit")
.accessibilityLabel("Edit")
.help("Edit \(macro.name.isEmpty ? "Untitled Macro" : macro.name)")
.accessibilityLabel("Edit \(macro.name.isEmpty ? "Untitled Macro" : macro.name)")

Button(action: onDelete) {
Image(systemName: "trash")
.foregroundColor(.red.opacity(0.8))
}
.buttonStyle(.borderless)
.help("Delete")
.accessibilityLabel("Delete")
.help("Delete \(macro.name.isEmpty ? "Untitled Macro" : macro.name)")
.accessibilityLabel("Delete \(macro.name.isEmpty ? "Untitled Macro" : macro.name)")
}
}
.padding(.horizontal, 12)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,16 +115,16 @@ struct ChordRow: View {
.foregroundColor(.accentColor)
}
.buttonStyle(.borderless)
.help("Edit")
.accessibilityLabel("Edit")
.help("Edit chord \(chord.actionDisplayString)")
.accessibilityLabel("Edit chord \(chord.actionDisplayString)")

Button(action: onDelete) {
Image(systemName: "trash")
.foregroundColor(.red.opacity(0.8))
}
.buttonStyle(.borderless)
.help("Delete")
.accessibilityLabel("Delete")
.help("Delete chord \(chord.actionDisplayString)")
.accessibilityLabel("Delete chord \(chord.actionDisplayString)")
Comment on lines +118 to +127

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use the resolved action name here, not actionDisplayString.

Lines 118-127 and 264-273 still become generic for macro/script mappings because actionDisplayString returns "Macro" / "Script" in those cases. ChordRow already resolves the actual macro/script name for the visible label, so the new tooltip/VoiceOver text remains ambiguous in repeated lists.

♿ Suggested direction
- .help("Edit chord \(chord.actionDisplayString)")
- .accessibilityLabel("Edit chord \(chord.actionDisplayString)")
+ .help("Edit chord \(resolvedChordActionName)")
+ .accessibilityLabel("Edit chord \(resolvedChordActionName)")

- .help("Delete sequence \(sequence.actionDisplayString)")
- .accessibilityLabel("Delete sequence \(sequence.actionDisplayString)")
+ .help("Delete sequence \(resolvedSequenceActionName)")
+ .accessibilityLabel("Delete sequence \(resolvedSequenceActionName)")
private var resolvedChordActionName: String {
    if let systemCommand = chord.systemCommand { return chord.hint ?? systemCommand.displayName }
    if let macroId = chord.macroId,
       let profile = profileManager.activeProfile,
       let macroName = profile.macroDisplayName(for: macroId) {
        return chord.hint ?? macroName
    }
    if let scriptId = chord.scriptId,
       let profile = profileManager.activeProfile,
       let script = profile.scripts.first(where: { $0.id == scriptId }) {
        return chord.hint ?? script.name
    }
    return chord.hint ?? chord.actionDisplayString
}

Also applies to: 264-273

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@XboxControllerMapper/XboxControllerMapper/Views/MainWindow/ChordSequenceListViews.swift`
around lines 118 - 127, The tooltip and accessibility text for chord actions
still use actionDisplayString, which collapses macro/script entries to generic
labels. Update ChordRow so its .help and .accessibilityLabel use the
already-resolved chord action name (for example via the existing resolved label
logic) instead of actionDisplayString, and apply the same change to both button
groups referenced in ChordSequenceListViews.

}
}
.padding(.horizontal, 12)
Expand Down Expand Up @@ -261,16 +261,16 @@ struct SequenceRow: View {
.foregroundColor(.accentColor)
}
.buttonStyle(.borderless)
.help("Edit")
.accessibilityLabel("Edit")
.help("Edit sequence \(sequence.actionDisplayString)")
.accessibilityLabel("Edit sequence \(sequence.actionDisplayString)")

Button(action: onDelete) {
Image(systemName: "trash")
.foregroundColor(.red.opacity(0.8))
}
.buttonStyle(.borderless)
.help("Delete")
.accessibilityLabel("Delete")
.help("Delete sequence \(sequence.actionDisplayString)")
.accessibilityLabel("Delete sequence \(sequence.actionDisplayString)")
}
}
.padding(.horizontal, 12)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ struct ScriptListView: View {
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.help("Use \(example.name) example")
.accessibilityLabel("Use \(example.name) example")
Comment on lines +132 to +133

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Don’t replace the example card’s synthesized label.

Line 133 likely strips out the visible description from the accessibility tree and reduces the button to just “Use … example”. These cards already expose example.name and example.description; keeping that content and moving the action wording to a hint preserves more context for screen-reader users.

♿ Suggested fix
 .help("Use \(example.name) example")
-.accessibilityLabel("Use \(example.name) example")
+.accessibilityHint("Use \(example.name) example")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.help("Use \(example.name) example")
.accessibilityLabel("Use \(example.name) example")
.help("Use \(example.name) example")
.accessibilityHint("Use \(example.name) example")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@XboxControllerMapper/XboxControllerMapper/Views/Scripts/ScriptListView.swift`
around lines 132 - 133, The example card action is replacing the synthesized
accessibility label with only “Use … example,” which removes the visible
title/description context. In ScriptListView’s example card accessibility
modifier, keep the existing content from example.name and example.description
instead of setting a custom accessibilityLabel, and move the action wording into
the accessibility hint so the card remains fully descriptive to screen readers.

}

HStack(spacing: 12) {
Expand Down Expand Up @@ -222,16 +224,16 @@ struct ScriptRow: View {
.foregroundColor(.accentColor)
}
.buttonStyle(.borderless)
.help("Edit")
.accessibilityLabel("Edit")
.help("Edit \(script.name.isEmpty ? "Untitled Script" : script.name)")
.accessibilityLabel("Edit \(script.name.isEmpty ? "Untitled Script" : script.name)")

Button(action: onDelete) {
Image(systemName: "trash")
.foregroundColor(.red.opacity(0.8))
}
.buttonStyle(.borderless)
.help("Delete")
.accessibilityLabel("Delete")
.help("Delete \(script.name.isEmpty ? "Untitled Script" : script.name)")
.accessibilityLabel("Delete \(script.name.isEmpty ? "Untitled Script" : script.name)")
}
}
.padding(.horizontal, 12)
Expand Down
Loading