From adb6aac130814b0f16416aca98755818092ff6f7 Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Mon, 30 Mar 2026 22:19:12 +0000
Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20aria-busy?=
=?UTF-8?q?=20to=20loading=20buttons?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Added aria-busy attributes to buttons that enter a loading state during async operations (exporting, syncing, refreshing) to correctly alert screen readers of the busy state.
Co-authored-by: aarjava <218419324+aarjava@users.noreply.github.com>
---
.Jules/palette.md | 4 ++++
src/components/ExportButton.tsx | 2 +-
src/components/dashboard/ExportRoiRail.tsx | 4 ++++
src/components/dashboard/IntegrationHealthRail.tsx | 2 ++
src/components/dashboard/dashboard-header.tsx | 1 +
5 files changed, 12 insertions(+), 1 deletion(-)
create mode 100644 .Jules/palette.md
diff --git a/.Jules/palette.md b/.Jules/palette.md
new file mode 100644
index 00000000..31990973
--- /dev/null
+++ b/.Jules/palette.md
@@ -0,0 +1,4 @@
+
+## 2025-03-05 - Missing aria-busy on loading buttons
+**Learning:** Found several buttons across the app (like refresh/sync/export buttons) that implement loading states with visual spinners or loading text, but fail to inform screen readers of their "busy" state via the \`aria-busy\` attribute.
+**Action:** Always add \`aria-busy={isLoading ? 'true' : undefined}\` to any interactive element that displays a loading state, ensuring the loading process is exposed to assistive technologies.
diff --git a/src/components/ExportButton.tsx b/src/components/ExportButton.tsx
index f368c658..e0ebff2f 100644
--- a/src/components/ExportButton.tsx
+++ b/src/components/ExportButton.tsx
@@ -121,6 +121,7 @@ function ExportButton({ variant = 'default', className = '' }: ExportButtonProps
? 'Export quota reached for this week.'
: 'Export dashboard data (CSV, JSON, PDF)'
}
+ aria-busy={isExporting ? 'true' : undefined}
>
{isExporting ? (
@@ -152,7 +153,6 @@ function ExportButton({ variant = 'default', className = '' }: ExportButtonProps
stiffness: 300,
damping: 20,
}}
-
onClick={(e) => e.stopPropagation()}
className="w-full max-w-md rounded-lg border border-[#E5E7EB] bg-white p-6 shadow-lg"
role="dialog"
diff --git a/src/components/dashboard/ExportRoiRail.tsx b/src/components/dashboard/ExportRoiRail.tsx
index 374612e0..1febaf85 100644
--- a/src/components/dashboard/ExportRoiRail.tsx
+++ b/src/components/dashboard/ExportRoiRail.tsx
@@ -166,6 +166,7 @@ export default function ExportRoiRail() {
onClick={() => exportData('csv')}
disabled={!!exporting || syncing || exportsDepleted}
className="inline-flex items-center gap-1.5 rounded-md border border-[#E5E7EB] bg-white px-3 py-1.5 text-[11px] font-semibold text-[#374151] transition hover:bg-[#F3F4F6] disabled:opacity-60"
+ aria-busy={exporting === 'csv' ? 'true' : undefined}
>
{exporting === 'csv' ? (
@@ -178,6 +179,7 @@ export default function ExportRoiRail() {
onClick={() => exportData('json')}
disabled={!!exporting || syncing || exportsDepleted}
className="inline-flex items-center gap-1.5 rounded-md border border-[#E5E7EB] bg-white px-3 py-1.5 text-[11px] font-semibold text-[#374151] transition hover:bg-[#F3F4F6] disabled:opacity-60"
+ aria-busy={exporting === 'json' ? 'true' : undefined}
>
{exporting === 'json' ? (
@@ -190,6 +192,7 @@ export default function ExportRoiRail() {
onClick={() => exportData('pdf')}
disabled={!!exporting || syncing || exportsDepleted}
className="inline-flex items-center gap-1.5 rounded-md border border-[#E5E7EB] bg-white px-3 py-1.5 text-[11px] font-semibold text-[#374151] transition hover:bg-[#F3F4F6] disabled:opacity-60"
+ aria-busy={exporting === 'pdf' ? 'true' : undefined}
>
{exporting === 'pdf' ? (
@@ -202,6 +205,7 @@ export default function ExportRoiRail() {
onClick={syncAll}
disabled={syncing || !!exporting}
className="inline-flex items-center gap-1.5 rounded-md border border-[#E5E7EB] bg-[#F8FAFC] px-3 py-1.5 text-[11px] font-semibold text-[#111827] transition hover:border-[#6C63FF] hover:bg-white disabled:opacity-60"
+ aria-busy={syncing ? 'true' : undefined}
>
{syncing ? (
diff --git a/src/components/dashboard/IntegrationHealthRail.tsx b/src/components/dashboard/IntegrationHealthRail.tsx
index 6edce6b0..bc976abd 100644
--- a/src/components/dashboard/IntegrationHealthRail.tsx
+++ b/src/components/dashboard/IntegrationHealthRail.tsx
@@ -234,6 +234,7 @@ export default function IntegrationHealthRail() {
onClick={loadStatuses}
className="inline-flex items-center gap-1 rounded-md border border-[#E5E7EB] bg-white px-3 py-1.5 text-xs font-semibold text-[#374151] hover:bg-[#F3F4F6] transition-colors disabled:opacity-60"
disabled={loading}
+ aria-busy={loading ? 'true' : undefined}
>
{loading ? (
@@ -344,6 +345,7 @@ export default function IntegrationHealthRail() {
onClick={() => handleSync(integration)}
disabled={disabled || syncing[integration.type]}
className="flex-1 inline-flex items-center justify-center gap-1.5 rounded-md border border-[#E5E7EB] bg-white px-3 py-1.5 text-[11px] font-semibold text-[#374151] transition-colors hover:bg-[#F3F4F6] disabled:cursor-not-allowed disabled:opacity-60"
+ aria-busy={syncing[integration.type] ? 'true' : undefined}
>
{syncing[integration.type] ? (
diff --git a/src/components/dashboard/dashboard-header.tsx b/src/components/dashboard/dashboard-header.tsx
index fe86c11d..e1e9b037 100644
--- a/src/components/dashboard/dashboard-header.tsx
+++ b/src/components/dashboard/dashboard-header.tsx
@@ -31,6 +31,7 @@ export function DashboardHeader({
disabled={isLoading}
className="p-1.5 text-[#9CA3AF] hover:text-[#333333] transition-colors rounded-md hover:bg-[#FAFAFA]"
aria-label="Refresh"
+ aria-busy={isLoading ? 'true' : undefined}
>
From 9ba1a78ca46c2105f2c147a24a50162754f5fc72 Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Mon, 30 Mar 2026 22:23:02 +0000
Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20aria-busy?=
=?UTF-8?q?=20to=20loading=20buttons?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Added aria-busy attributes to buttons that enter a loading state during async operations (exporting, syncing, refreshing) to correctly alert screen readers of the busy state.
Co-authored-by: aarjava <218419324+aarjava@users.noreply.github.com>
---
.Jules/palette.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.Jules/palette.md b/.Jules/palette.md
index 31990973..0b23f04c 100644
--- a/.Jules/palette.md
+++ b/.Jules/palette.md
@@ -1,4 +1,4 @@
-
## 2025-03-05 - Missing aria-busy on loading buttons
+
**Learning:** Found several buttons across the app (like refresh/sync/export buttons) that implement loading states with visual spinners or loading text, but fail to inform screen readers of their "busy" state via the \`aria-busy\` attribute.
**Action:** Always add \`aria-busy={isLoading ? 'true' : undefined}\` to any interactive element that displays a loading state, ensuring the loading process is exposed to assistive technologies.