From f5c60ba21c0b23f7ead4e19ef3ed43dde7b313f3 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 08:53:54 +0000 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20dynamic=20c?= =?UTF-8?q?ontext=20to=20list=20action=20accessibility=20labels=20and=20to?= =?UTF-8?q?oltips?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: NSEvent <44446865+NSEvent@users.noreply.github.com> --- .Jules/palette.md | 4 ++++ .../Views/Macros/MacroListView.swift | 12 ++++++------ .../MainWindow/ChordSequenceListViews.swift | 16 ++++++++-------- .../Views/MainWindow/GestureListViews.swift | 8 ++++---- .../Views/Scripts/ScriptListView.swift | 8 ++++---- 5 files changed, 26 insertions(+), 22 deletions(-) diff --git a/.Jules/palette.md b/.Jules/palette.md index f61603c8..d8759344 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -25,3 +25,7 @@ ## 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. + +## 2024-07-06 - [Contextual Accessibility on Repeated List Actions] +**Learning:** Icon-only buttons in repeated lists (like "Edit" or "Delete" in lists of Macros, Scripts, or Gestures) often lack contextual disambiguation in their VoiceOver labels and tooltips. When all list items just say "Edit" and "Delete", screen reader users lose context on which item they are operating on. +**Action:** Always append dynamic context from the current list item (e.g. `macro.name.isEmpty ? "Unnamed Macro" : macro.name`) to `.help()` and `.accessibilityLabel()` strings for list actions. diff --git a/XboxControllerMapper/XboxControllerMapper/Views/Macros/MacroListView.swift b/XboxControllerMapper/XboxControllerMapper/Views/Macros/MacroListView.swift index 211ecb1e..8e249337 100644 --- a/XboxControllerMapper/XboxControllerMapper/Views/Macros/MacroListView.swift +++ b/XboxControllerMapper/XboxControllerMapper/Views/Macros/MacroListView.swift @@ -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 ? "Unnamed Macro" : macro.name) in shared library") + .accessibilityLabel("Edit \(macro.name.isEmpty ? "Unnamed Macro" : macro.name) in shared library") } .padding(.horizontal, 12) .padding(.vertical, 8) @@ -206,16 +206,16 @@ struct MacroRow: View { .foregroundColor(.accentColor) } .buttonStyle(.borderless) - .help("Edit") - .accessibilityLabel("Edit") + .help("Edit \(macro.name.isEmpty ? "Unnamed Macro" : macro.name)") + .accessibilityLabel("Edit \(macro.name.isEmpty ? "Unnamed 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 ? "Unnamed Macro" : macro.name)") + .accessibilityLabel("Delete \(macro.name.isEmpty ? "Unnamed Macro" : macro.name)") } } .padding(.horizontal, 12) diff --git a/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/ChordSequenceListViews.swift b/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/ChordSequenceListViews.swift index 169053d5..d9e133d9 100644 --- a/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/ChordSequenceListViews.swift +++ b/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/ChordSequenceListViews.swift @@ -115,16 +115,16 @@ struct ChordRow: View { .foregroundColor(.accentColor) } .buttonStyle(.borderless) - .help("Edit") - .accessibilityLabel("Edit") + .help("Edit \(chord.hasAction ? "Chord" : "Empty Chord")") + .accessibilityLabel("Edit \(chord.hasAction ? "Chord" : "Empty Chord")") Button(action: onDelete) { Image(systemName: "trash") .foregroundColor(.red.opacity(0.8)) } .buttonStyle(.borderless) - .help("Delete") - .accessibilityLabel("Delete") + .help("Delete \(chord.hasAction ? "Chord" : "Empty Chord")") + .accessibilityLabel("Delete \(chord.hasAction ? "Chord" : "Empty Chord")") } } .padding(.horizontal, 12) @@ -261,16 +261,16 @@ struct SequenceRow: View { .foregroundColor(.accentColor) } .buttonStyle(.borderless) - .help("Edit") - .accessibilityLabel("Edit") + .help("Edit \(chord.hasAction ? "Chord" : "Empty Chord")") + .accessibilityLabel("Edit \(chord.hasAction ? "Chord" : "Empty Chord")") Button(action: onDelete) { Image(systemName: "trash") .foregroundColor(.red.opacity(0.8)) } .buttonStyle(.borderless) - .help("Delete") - .accessibilityLabel("Delete") + .help("Delete \(chord.hasAction ? "Chord" : "Empty Chord")") + .accessibilityLabel("Delete \(chord.hasAction ? "Chord" : "Empty Chord")") } } .padding(.horizontal, 12) diff --git a/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/GestureListViews.swift b/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/GestureListViews.swift index a77232ea..ed85a992 100644 --- a/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/GestureListViews.swift +++ b/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/GestureListViews.swift @@ -78,8 +78,8 @@ struct GestureRow: View { .foregroundColor(.accentColor) } .buttonStyle(.borderless) - .help("Edit") - .accessibilityLabel("Edit") + .help("Edit \(gestureType.displayName)") + .accessibilityLabel("Edit \(gestureType.displayName)") if mapping?.hasAction == true { Button(action: onClear) { @@ -87,8 +87,8 @@ struct GestureRow: View { .foregroundColor(.red.opacity(0.8)) } .buttonStyle(.borderless) - .help("Clear") - .accessibilityLabel("Clear") + .help("Clear \(gestureType.displayName)") + .accessibilityLabel("Clear \(gestureType.displayName)") } } } diff --git a/XboxControllerMapper/XboxControllerMapper/Views/Scripts/ScriptListView.swift b/XboxControllerMapper/XboxControllerMapper/Views/Scripts/ScriptListView.swift index 40c6f5ff..11c8dd28 100644 --- a/XboxControllerMapper/XboxControllerMapper/Views/Scripts/ScriptListView.swift +++ b/XboxControllerMapper/XboxControllerMapper/Views/Scripts/ScriptListView.swift @@ -222,16 +222,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) From d3dd35e8546ea06e72f79e32d321748512e54147 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 09:01:44 +0000 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Fix=20compiler=20?= =?UTF-8?q?error=20due=20to=20nested=20quotes=20in=20String=20interpolatio?= =?UTF-8?q?n,=20add=20dynamic=20context=20to=20list=20action=20accessibili?= =?UTF-8?q?ty=20labels=20and=20tooltips?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: NSEvent <44446865+NSEvent@users.noreply.github.com> --- .../MainWindow/ChordSequenceListViews.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/ChordSequenceListViews.swift b/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/ChordSequenceListViews.swift index d9e133d9..8b1fdb55 100644 --- a/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/ChordSequenceListViews.swift +++ b/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/ChordSequenceListViews.swift @@ -115,16 +115,16 @@ struct ChordRow: View { .foregroundColor(.accentColor) } .buttonStyle(.borderless) - .help("Edit \(chord.hasAction ? "Chord" : "Empty Chord")") - .accessibilityLabel("Edit \(chord.hasAction ? "Chord" : "Empty Chord")") + .help(chord.hasAction ? "Edit Chord" : "Edit Empty Chord") + .accessibilityLabel(chord.hasAction ? "Edit Chord" : "Edit Empty Chord") Button(action: onDelete) { Image(systemName: "trash") .foregroundColor(.red.opacity(0.8)) } .buttonStyle(.borderless) - .help("Delete \(chord.hasAction ? "Chord" : "Empty Chord")") - .accessibilityLabel("Delete \(chord.hasAction ? "Chord" : "Empty Chord")") + .help(chord.hasAction ? "Delete Chord" : "Delete Empty Chord") + .accessibilityLabel(chord.hasAction ? "Delete Chord" : "Delete Empty Chord") } } .padding(.horizontal, 12) @@ -261,16 +261,16 @@ struct SequenceRow: View { .foregroundColor(.accentColor) } .buttonStyle(.borderless) - .help("Edit \(chord.hasAction ? "Chord" : "Empty Chord")") - .accessibilityLabel("Edit \(chord.hasAction ? "Chord" : "Empty Chord")") + .help(sequence.hasAction ? "Edit Sequence" : "Edit Empty Sequence") + .accessibilityLabel(sequence.hasAction ? "Edit Sequence" : "Edit Empty Sequence") Button(action: onDelete) { Image(systemName: "trash") .foregroundColor(.red.opacity(0.8)) } .buttonStyle(.borderless) - .help("Delete \(chord.hasAction ? "Chord" : "Empty Chord")") - .accessibilityLabel("Delete \(chord.hasAction ? "Chord" : "Empty Chord")") + .help(sequence.hasAction ? "Delete Sequence" : "Delete Empty Sequence") + .accessibilityLabel(sequence.hasAction ? "Delete Sequence" : "Delete Empty Sequence") } } .padding(.horizontal, 12) From b32d6123279d6db13efd9294a7ff154a3c9b7750 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 09:24:08 +0000 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20contextual?= =?UTF-8?q?=20disambiguation=20to=20list=20action=20tooltips=20and=20acces?= =?UTF-8?q?sibility=20labels?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 💡 **What:** The UX enhancement added dynamic context from the current list item to `.help()` tooltips and `.accessibilityLabel()` attributes for icon-only list actions in Macros, Scripts, and Gestures. 🎯 **Why:** Previously, VoiceOver users and hover tooltips would simply read "Edit" or "Delete" multiple times in a row for repeated list items. ♿ **Accessibility:** Added significant disambiguation to screen reader navigation. Co-authored-by: NSEvent <44446865+NSEvent@users.noreply.github.com> --- .../MainWindow/ChordSequenceListViews.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/ChordSequenceListViews.swift b/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/ChordSequenceListViews.swift index 8b1fdb55..169053d5 100644 --- a/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/ChordSequenceListViews.swift +++ b/XboxControllerMapper/XboxControllerMapper/Views/MainWindow/ChordSequenceListViews.swift @@ -115,16 +115,16 @@ struct ChordRow: View { .foregroundColor(.accentColor) } .buttonStyle(.borderless) - .help(chord.hasAction ? "Edit Chord" : "Edit Empty Chord") - .accessibilityLabel(chord.hasAction ? "Edit Chord" : "Edit Empty Chord") + .help("Edit") + .accessibilityLabel("Edit") Button(action: onDelete) { Image(systemName: "trash") .foregroundColor(.red.opacity(0.8)) } .buttonStyle(.borderless) - .help(chord.hasAction ? "Delete Chord" : "Delete Empty Chord") - .accessibilityLabel(chord.hasAction ? "Delete Chord" : "Delete Empty Chord") + .help("Delete") + .accessibilityLabel("Delete") } } .padding(.horizontal, 12) @@ -261,16 +261,16 @@ struct SequenceRow: View { .foregroundColor(.accentColor) } .buttonStyle(.borderless) - .help(sequence.hasAction ? "Edit Sequence" : "Edit Empty Sequence") - .accessibilityLabel(sequence.hasAction ? "Edit Sequence" : "Edit Empty Sequence") + .help("Edit") + .accessibilityLabel("Edit") Button(action: onDelete) { Image(systemName: "trash") .foregroundColor(.red.opacity(0.8)) } .buttonStyle(.borderless) - .help(sequence.hasAction ? "Delete Sequence" : "Delete Empty Sequence") - .accessibilityLabel(sequence.hasAction ? "Delete Sequence" : "Delete Empty Sequence") + .help("Delete") + .accessibilityLabel("Delete") } } .padding(.horizontal, 12) From 648fe0f466a21a01f19a925fd5104f488c9168f3 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 09:57:51 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20contextual?= =?UTF-8?q?=20disambiguation=20to=20list=20action=20tooltips=20and=20acces?= =?UTF-8?q?sibility=20labels?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 💡 **What:** The UX enhancement added dynamic context from the current list item to `.help()` tooltips and `.accessibilityLabel()` attributes for icon-only list actions in Macros, Scripts, and Gestures. 🎯 **Why:** Previously, VoiceOver users and hover tooltips would simply read "Edit" or "Delete" multiple times in a row for repeated list items. ♿ **Accessibility:** Added significant disambiguation to screen reader navigation. Co-authored-by: NSEvent <44446865+NSEvent@users.noreply.github.com>