From 192524265ca461ceb4c6ffd0ae083c7d98d9c56b Mon Sep 17 00:00:00 2001
From: Renzo F <45401804+rennf93@users.noreply.github.com>
Date: Mon, 13 Jul 2026 06:38:48 +0200
Subject: [PATCH] [f309463f] Systematic tooltip and aria-label pass across the
entire panel (#484)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* [001c9a7a] Author tooltip/aria-label spec for the panel (#469) (#473)
* [001c9a7a] docs(ux_ui): add tooltip/aria-label classification spec for panel controls
* [001c9a7a] docs(ux_ui): commit missing tooltip/aria-label spec content
Prior commit's message claimed to add the spec but only touched
unrelated generated lifecycle prompt files — the actual spec file was
never git-added. This commits the real content.
---------
Co-authored-by: UX/UI Developer 1
* [dbe222aa] Implement tooltip and aria-label sweep across all panel surfaces (#478)
* [6f991331] Add aria-label + matching tooltip per tooltip-aria-label-spec.md (#476)
* [6f991331] feat(panel): add aria-label + matching tooltip to 8 icon-only controls per tooltip-aria-label-spec.md §1a/§1b, wrap assignee-avatar initials in a full-name tooltip
* [6f991331] docs(accessibility): add icon-only controls pattern guide for aria-label + matching tooltip
Documented the implemented pattern for accessible icon-only controls across 8 components (bell, back-arrow, menu, toggle, drag-handle, move-forward, settings, review-link) plus the assignee-avatar tooltip. Covers when to apply the pattern, naming conventions, state-dependent labels, testing approach, and rationale for local TooltipProvider scope.
---------
Co-authored-by: Frontend Developer 1
Co-authored-by: Frontend Documenter
* [e34da833] Fix notification-bell.tsx and assignee-avatar.tsx, re-verify all 9 claimed tooltip/aria-label retrofits (#480)
* [e34da833] test(notifications): add regression coverage confirming the bell button's aria-label/title/Tooltip and re-verify the other 8 tooltip-aria-label-spec controls by direct file read
* [e34da833] docs(ux_ui): update tooltip-aria-label-spec.md status to "implemented" with test coverage summary
---------
Co-authored-by: Frontend Developer 1
Co-authored-by: Frontend Documenter
* [09414273] fix(header): wrap refresh button in Tooltip; correct spec.md and accessible-icon-buttons.md doc-accuracy issues (#483)
Co-authored-by: Frontend Developer 1
---------
Co-authored-by: Frontend Developer 1
Co-authored-by: Frontend Documenter
* [f309463f] fix: missing tooltip/Link/ArrowLeft imports + dedupe command-center tooltip import, drop redundant native title on refresh button, reflow doc prose
- kanban-card.tsx, header.tsx: import TooltipProvider (used but undefined -> eslint react/jsx-no-undef, blocked Panel lint + QA image panel build)
- task-header.tsx: import Link (next/link) and ArrowLeft (lucide-react) for the back button tooltip
- command-center.tsx: remove the duplicate tooltip primitive import block (kept the one with TooltipProvider; tsc duplicate-identifier)
- header.tsx: drop native title= on the refresh button now that a Radix Tooltip carries the hint (header test expects no native title)
- docs/frontend/components/accessible-icon-buttons.md: reflow hard-wrapped prose (python gate make reflow-docs)
* [f309463f] chore: regenerate lifecycle artifacts + verb tables (reconcile after master merge)
The branch's generated intro prose in agents/prompts/_generated/lifecycle-*.md
and verbs.md had drifted to unwrapped lines (master is wrapped). The foundation-
check gate (make lifecycle + regenerate_verb_tables + git diff --exit-code) caught
the drift. Re-rendered via the canonical generators; no hand-edits.
* [f309463f] Close remaining a11y gaps: aria-labels on task-table row-expand + pagination, titles on work-session truncated task-id/branch, secretary Start loading label
---------
Co-authored-by: UX/UI Developer 1
Co-authored-by: Frontend Developer 1
Co-authored-by: Frontend Documenter
Co-authored-by: Renn F
---
.../components/accessible-icon-buttons.md | 158 ++++++++++++++++++
docs/ux_ui/design/tooltip-aria-label-spec.md | 121 ++++++++++++++
.../src/components/business/secretary-tab.tsx | 1 +
.../components/dashboard/command-center.tsx | 34 ++--
.../components/dashboard/pr-review-queue.tsx | 32 +++-
.../core/__tests__/kanban-card-aria.test.tsx | 75 +++++++++
.../components/kanban/core/kanban-card.tsx | 80 +++++----
.../shared/__tests__/assignee-avatar.test.tsx | 26 +++
.../kanban/shared/assignee-avatar.tsx | 28 ++--
.../layout/__tests__/sidebar.test.tsx | 95 +++--------
panel/src/components/layout/header.tsx | 37 ++--
panel/src/components/layout/sidebar.tsx | 47 +++---
.../__tests__/notification-bell.test.tsx | 38 +++++
.../notifications/notification-bell.tsx | 50 +++---
panel/src/components/tasks/task-actions.tsx | 30 +++-
.../tasks/task-detail/task-header.tsx | 23 +++
panel/src/components/tasks/task-table.tsx | 3 +
.../work-sessions/work-session-table.tsx | 3 +
18 files changed, 668 insertions(+), 213 deletions(-)
create mode 100644 docs/frontend/components/accessible-icon-buttons.md
create mode 100644 docs/ux_ui/design/tooltip-aria-label-spec.md
create mode 100644 panel/src/components/kanban/core/__tests__/kanban-card-aria.test.tsx
create mode 100644 panel/src/components/kanban/shared/__tests__/assignee-avatar.test.tsx
create mode 100644 panel/src/components/notifications/__tests__/notification-bell.test.tsx
diff --git a/docs/frontend/components/accessible-icon-buttons.md b/docs/frontend/components/accessible-icon-buttons.md
new file mode 100644
index 00000000..96563d2a
--- /dev/null
+++ b/docs/frontend/components/accessible-icon-buttons.md
@@ -0,0 +1,158 @@
+# Accessible Icon-Only Controls (aria-label + Tooltip)
+
+## Overview
+
+Icon-only controls—buttons without visible text—require two layers of accessibility to be usable by all:
+
+1. **aria-label** attribute for screen reader users
+2. **Visible Tooltip** (matching text) for mouse, keyboard, and screen reader users
+
+Both layers must use identical text that describes the action or result.
+
+## Pattern
+
+All icon-only controls in the panel follow this structure:
+
+```typescript
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
+
+const LABEL = "Open settings";
+
+
+
+
+
+
+ {LABEL}
+
+
+```
+
+### Why three attributes?
+
+- **aria-label**: The accessible name for screen readers
+- **title**: Browser native tooltip (fallback, appears on hover/focus)
+- **TooltipContent**: Radix UI tooltip for consistent visual feedback
+
+### Naming convention
+
+Label text uses **active verbs** describing what happens when the control is clicked:
+
+| Control | Label |
+|---------|-------|
+| Settings gear | "Open settings" |
+| Notification bell | "View notifications" |
+| Back arrow | "Go back to tasks list" |
+| Collapse toggle | "Collapse sidebar" / "Expand sidebar" |
+| Drag handle | "Drag to move task between columns" |
+| Menu trigger | "Open task actions menu" |
+
+Avoid passive voice ("Settings opened") or generic labels ("Button").
+
+## State-dependent labels
+
+When a control's action varies by state, compute the label dynamically:
+
+```typescript
+const toggleLabel = sidebarCollapsed ? "Expand sidebar" : "Collapse sidebar";
+
+
+```
+
+The label updates whenever state changes, keeping screen reader users informed.
+
+## Truncated content (avatars, badges)
+
+When an icon-only control displays shortened content (e.g., "FD1" for "Frontend Dev 1"), wrap in a tooltip showing the full value:
+
+```typescript
+
+
+
+
+ {initials}
+
+
+ {fullName}
+
+
+```
+
+## When NOT to use this pattern
+
+**Do not** apply aria-label + tooltip to:
+
+- **Text-labeled buttons** — the text is the label
+- **Decorative icons** — non-interactive graphics (use `aria-hidden="true"` instead)
+- **Self-labeling badges** — content + styling conveys meaning
+- **Chart tooltips** — handled by the charting library (recharts, etc.)
+
+## Implemented controls
+
+The following 8 icon-only controls have been retrofitted:
+
+1. **notification-bell.tsx** — "View notifications"
+2. **task-header.tsx** (back button) — "Go back to tasks list"
+3. **task-actions.tsx** (menu) — "Open task actions menu"
+4. **sidebar.tsx** (collapse toggle) — "Collapse sidebar" / "Expand sidebar"
+5. **kanban-card.tsx** (drag handle) — "Drag to move task between columns"
+6. **kanban-card.tsx** (move-forward) — "Move forward" / "PM must activate this task first"
+7. **command-center.tsx** (settings) — "Open settings"
+8. **pr-review-queue.tsx** (details link) — "Review details"
+
+Plus one avatar tooltip:
+
+9. **assignee-avatar.tsx** — Shows full agent display name
+
+## Testing
+
+### Screen reader
+1. Tab to the icon-only control
+2. Verify the aria-label is announced
+3. Focus should be visible and clear
+
+### Mouse
+1. Hover over the control
+2. Tooltip appears with the same text as aria-label
+3. Click triggers the expected action
+
+### Keyboard
+1. Tab to focus the control
+2. title attribute provides a browser tooltip
+3. Verify the label is consistent
+
+### State changes
+1. For conditional labels, verify the label updates when state changes
+2. Tab away and back to re-announce the new label
+
+## TooltipProvider scope
+
+Each component wraps its controls in a local `` (not a single app-root provider). This pattern:
+
+- Keeps tooltip state scoped to the component
+- Matches existing codebase patterns
+- Simplifies DOM structure and reduces global state
+
+If a future refactoring uses a root-level provider, the structure remains valid—only the wrapping changes, not the aria-label/title pattern.
+
+## Resources
+
+- [WCAG 2.1: Text Alternatives for Images](https://www.w3.org/WAI/WCAG21/Understanding/text-alternatives)
+- [ARIA Authoring Practices: Buttons](https://www.w3.org/WAI/ARIA/apg/patterns/button/)
+- [Radix UI Tooltip](https://www.radix-ui.com/docs/primitives/components/tooltip)
+- Local test files: See `kanban-card-aria.test.tsx`, `sidebar.test.tsx`, `assignee-avatar.test.tsx`
diff --git a/docs/ux_ui/design/tooltip-aria-label-spec.md b/docs/ux_ui/design/tooltip-aria-label-spec.md
new file mode 100644
index 00000000..5cf3ea3e
--- /dev/null
+++ b/docs/ux_ui/design/tooltip-aria-label-spec.md
@@ -0,0 +1,121 @@
+# Tooltip / aria-label spec: which panel controls need which
+
+Status: implemented (§1a–§1c complete, test coverage added)
+
+Owner: ux-dev-1
+
+Implementation status: All icon-only controls listed in §1a have been retrofitted with aria-label + title + matching Radix Tooltip (via PR #476). Test coverage is provided by per-control regression test files in `panel/src/components/**/\__tests__/`.
+
+Last updated: 2026-07-11
+
+Surface: every icon-bearing interactive control in `panel/src/components` — surveyed against the sidebar (`layout/sidebar.tsx`), header (`layout/header.tsx`), task detail (`tasks/task-detail/`, `tasks/task-actions.tsx`), kanban (`kanban/core/`, `kanban/shared/`), the dashboard queues (`dashboard/*-queue.tsx`, `dashboard/command-center.tsx`), and metrics (`metrics/*.tsx`).
+
+## Dial read
+
+Per the team design bar, the panel is dense product UI, not a marketing surface:
+
+- **DESIGN_VARIANCE:** 1 — this spec changes no layout or grid; it is a copy/markup rule for existing controls.
+- **MOTION_INTENSITY:** 2 — tooltips use the existing Radix primitive (`ui/tooltip.tsx`), whose default fade/zoom-on-open is the motion budget; nothing in this spec adds a new transition.
+- **VISUAL_DENSITY:** 8 — the panel packs many small icon-only affordances per row (kanban cards, table action cells, queue rows); the density rule most relevant here is the "noisy vs. informative" line in §3 — every tooltip added to a dense row is one more thing competing for attention, so it must earn its place.
+
+## Problem (Resolved)
+
+This spec resolved the panel's mixed accessible-naming strategies for icon-only controls by establishing a single rule: all icon-only controls must carry a mandatory `aria-label` (§1a), optionally with a matching visible Tooltip (§1b).
+
+All controls listed below have been retrofitted to the correct pattern (verified 2026-07-11):
+
+| Component | Pattern | Test Coverage |
+|---|---|---|
+| `layout/header.tsx:59-68` (refresh button) | ✅ `aria-label` + `title` + Tooltip | ✅ `header.test.tsx` |
+| `ui/copy-button.tsx:64-74` | ✅ `aria-label` + `title` + Tooltip | ✅ Implicit (component pattern) |
+| `kanban/core/kanban-card.tsx:237-253` (move-forward button) | ✅ `aria-label` + `title` + Tooltip | ✅ `kanban-card-aria.test.tsx` |
+| `notifications/notification-bell.tsx:24-31` (bell button) | ✅ `aria-label` + `title` + Tooltip | ✅ `notification-bell.test.tsx` (NEW) |
+| `tasks/task-detail/task-header.tsx:476-478` (back-arrow button) | ✅ `aria-label` + `title` + Tooltip | ❌ Not covered — `header.test.tsx` only renders `layout/header.tsx`, not this component |
+| `tasks/task-actions.tsx:145-147` (overflow-menu trigger) | ✅ `aria-label` + `title` + Tooltip | ❌ Not covered — `header.test.tsx` only renders `layout/header.tsx`, not this component |
+| `layout/sidebar.tsx:170-182` (collapse-rail toggle) | ✅ `aria-label` + `title` + Tooltip | ✅ `sidebar.test.tsx` |
+| `kanban/core/kanban-card.tsx:122-128` (drag handle) | ✅ `aria-label` + `title` + Tooltip | ✅ `kanban-card-aria.test.tsx` |
+| `kanban/shared/assignee-avatar.tsx` (initials badge) | ✅ `aria-label` + Tooltip with full name | ✅ `assignee-avatar.test.tsx` |
+
+Separately, some controls that DO have a visible label still lack a tooltip where one would help (`kanban/shared/assignee-avatar.tsx` shows only two-letter initials, with nothing disambiguating which agent that is), while others already use tooltips correctly for genuinely supplementary info (`kanban-card.tsx:137-151`'s sequence-number badge, `header.tsx:35-50`'s "Coming Soon" search tooltip).
+
+This spec gives every future control a three-way answer — mandatory aria-label, recommended tooltip, or neither — plus the copy rule for whichever applies.
+
+## 1. Classification
+
+### 1a. `aria-label` is mandatory
+
+**Any interactive control (`button`, a `Link`/`` wrapping a `Button`, a dropdown/popover trigger) whose only visible content is an icon.** No exceptions — this is a WCAG requirement, not a style preference (see §4).
+
+Examples already in the codebase that need this fixed:
+
+- `notifications/notification-bell.tsx:24` — `