Add WCAG 2.2 AAA compliance design document

Comprehensive accessibility design covering all four WCAG principles
(Perceivable, Operable, Understandable, Robust) for the Driftwood
GTK4/libadwaita AppImage manager.
This commit is contained in:
lashman
2026-02-27 09:35:35 +02:00
parent fa28955919
commit dc97a6cbbc

View File

@@ -0,0 +1,246 @@
# WCAG 2.2 AAA Compliance Design for Driftwood
## Context
Driftwood is a GTK4/libadwaita AppImage manager. The app already uses semantic libadwaita widgets (ActionRow, PreferencesGroup, SwitchRow, NavigationView) and has partial accessibility support: ~11 accessible labels, status badges with text+color, keyboard shortcuts, and reduced-motion CSS. However, it falls short of full WCAG 2.2 AAA compliance.
This design covers every change needed to achieve AAA compliance across all four WCAG principles: Perceivable, Operable, Understandable, and Robust.
## Approach: Hybrid Helpers + Direct Properties
Create centralized accessibility helper functions in `widgets.rs` for patterns that repeat (labeled buttons, described badges, live announcements), then add direct accessible properties for unique cases in each UI file. This keeps the code DRY while ensuring nothing is missed.
## Scope
**In scope:** All UI code in `src/ui/`, `src/window.rs`, and `data/resources/style.css`.
**Out of scope:** CLI (`src/cli.rs`), core backend modules, build system.
---
## 1. Perceivable (WCAG 1.x)
### 1.1 Non-text Content (1.1.1 - Level A)
**Current state:** 8 icon-only buttons, some with accessible labels, some without.
**Changes needed:**
- Add `update_property(&[AccessibleProperty::Label(...)])` to every icon-only button:
- `library_view.rs`: menu button, search button, grid button, list button
- `duplicate_dialog.rs`: delete button per row
- `widgets.rs`: copy button
- `preferences.rs`: remove directory button
- Add accessible descriptions to integration emblem overlay in `app_card.rs`
- Add accessible labels to all `gtk::Image` icons used as prefixes in rows (check icons in integration_dialog, category icons in cleanup_wizard)
### 1.2 Time-based Media - N/A (no audio/video)
### 1.3 Adaptable
**1.3.1 Info and Relationships (A):**
- Add `AccessibleRole::Group` to badge boxes in detail_view, library_view list rows, and app_card
- Add `AccessibleProperty::Label` to ListBox containers in library_view (list view), preferences (directory list, cleanup wizard lists)
- Add `AccessibleRelation::LabelledBy` connecting PreferencesGroup titles to their child row containers where applicable
**1.3.6 Identify Purpose (AAA):**
- Set `AccessibleRole::Banner` on the detail view banner
- Set `AccessibleRole::Navigation` on the NavigationView wrapper
- Set `AccessibleRole::Search` on the search bar
- Set `AccessibleRole::Status` on status badges
- Set `AccessibleRole::Main` on the main content area
### 1.4 Distinguishable
**1.4.6 Enhanced Contrast (AAA - 7:1 ratio):**
- Add a `prefers-contrast: more` media query in `style.css` for high-contrast mode
- In high-contrast mode: solid opaque borders on app cards (no alpha), bolder status badge colors, thicker focus rings (3px)
- Verify that libadwaita theme variables meet 7:1 in both light and dark mode (they do - libadwaita's named colors are designed for WCAG AA, and the `prefers-contrast: more` variant handles AAA)
**1.4.8 Visual Presentation (AAA):**
- Text is already relative-sized (em units)
- libadwaita handles line height and spacing according to system preferences
- No changes needed beyond ensuring we do not override user-configured text spacing
**1.4.11 Non-text Contrast (AA):**
- Increase focus ring width to 3px (currently 2px) in high-contrast mode
- Ensure status badge borders are visible at 3:1 ratio against their background
---
## 2. Operable (WCAG 2.x)
### 2.1 Keyboard Accessible
**2.1.3 Keyboard No Exception (AAA):**
- Verify all dashboard actionable rows are keyboard-activatable (they use `activatable: true` + `action_name`)
- Verify cleanup wizard checkboxes are keyboard-toggleable via `activatable_widget`
- Add keyboard shortcut Ctrl+Q for quit (already exists in GNOME via app.quit)
- Ensure delete button in duplicate_dialog can be reached via Tab
### 2.2 Enough Time
**2.2.4 Interruptions (AAA):**
- Toast notifications already have timeouts and can be dismissed
- No other auto-updating content exists
### 2.3 Seizures and Physical Reactions
**2.3.3 Animation from Interactions (AAA):**
- Expand `prefers-reduced-motion` CSS to cover ALL transitions:
- `navigation view` slide transitions (currently only covers `stack`)
- `adw::Dialog` presentation animations
- `flowboxchild` hover/active transitions
- Search bar reveal animation
- Spinner animations (already respected by adw::Spinner)
### 2.4 Navigable
**2.4.7 Focus Visible (AA) + 2.4.13 Focus Appearance (AAA):**
- Add focus-visible styles for ALL focusable elements, not just app cards:
- `button:focus-visible` - 3px solid outline with accent color
- `row:focus-visible` - highlight with outline
- `switch:focus-visible`
- `checkbutton:focus-visible`
- `searchentry:focus-visible`
- `comborow:focus-visible`
- `spinrow:focus-visible`
- `expander:focus-visible`
- Focus indicator must be at least 2px thick and have 3:1 contrast (AAA requires this)
**2.4.8 Location (AAA):**
- Update the window title dynamically to reflect current page: "Driftwood - Dashboard", "Driftwood - Security Report", "Driftwood - {App Name}"
- This is already partially done via NavigationPage titles; ensure the window's actual title property updates
**2.5.8 Target Size (AA):**
- Audit all clickable targets for minimum 24x24px
- The copy button, delete button (duplicate dialog), and remove directory button need `min-width: 24px; min-height: 24px` in CSS
- Status badges in actionable rows are not click targets (the row is), so this is fine
---
## 3. Understandable (WCAG 3.x)
### 3.1 Readable
**3.1.1 Language of Page (A):**
- The GTK accessible layer reads the locale from the system. No explicit action needed for a native desktop app.
**3.1.3 Unusual Words (AAA):**
- Add `tooltip_text` explanations for technical terms displayed in the UI:
- "FUSE" -> tooltip: "Filesystem in Userspace - required for mounting AppImages"
- "XWayland" -> tooltip: "X11 compatibility layer for Wayland desktops"
- "AppImage Type 1/2" -> tooltip: "Type 1 uses ISO9660, Type 2 uses SquashFS"
- "CVE" -> tooltip: "Common Vulnerabilities and Exposures - security vulnerability identifier"
- "SHA256" -> tooltip: "Cryptographic hash for verifying file integrity"
- "Firejail" -> tooltip: "Linux application sandboxing tool"
- "zsync" -> tooltip: "Efficient delta-update download protocol"
- "fusermount" -> tooltip: "User-space filesystem mount utility"
**3.1.4 Abbreviations (AAA):**
- Expand "CVE" to "CVE (vulnerability)" on first use in security report
- Expand "SHA256" to "SHA256 checksum" in detail view
**3.1.5 Reading Level (AAA):**
- Review all user-facing strings for plain language (most are already simple)
- Replace "No update information embedded" with "This app cannot check for updates automatically"
- Replace "AppImage does not contain update information" with "No automatic update support"
### 3.2 Predictable - Already compliant (no unexpected changes)
### 3.3 Input Assistance
**3.3.5 Help (AAA):**
- Add contextual descriptions to all PreferencesGroup widgets (already mostly done)
- Add `description` text to any PreferencesGroup missing it
**3.3.6 Error Prevention - All (AAA):**
- Destructive actions already have confirmation (confirm-before-delete setting, alert dialogs)
- Add confirmation to bulk "Remove All Suggested" in duplicate dialog (currently executes immediately)
- Add confirmation to "Clean Selected" in cleanup wizard (currently executes immediately)
---
## 4. Robust (WCAG 4.x)
### 4.1.2 Name, Role, Value (A)
**Accessible Names (all interactive elements):**
Every button, toggle, switch, row, and input must have an accessible name. The full list of items needing labels:
| Widget | File | Label to add |
|--------|------|-------------|
| Menu button | library_view.rs | "Main menu" |
| Search toggle | library_view.rs | "Toggle search" |
| Grid view toggle | library_view.rs | "Switch to grid view" |
| List view toggle | library_view.rs | "Switch to list view" |
| Copy button | widgets.rs | "Copy to clipboard" |
| Delete button | duplicate_dialog.rs | "Delete this AppImage" |
| Remove directory | preferences.rs | "Remove scan directory" |
| Close button | cleanup_wizard.rs | "Close dialog" |
| Clean Selected | cleanup_wizard.rs | "Clean selected items" |
| Remove All Suggested | duplicate_dialog.rs | "Remove all suggested duplicates" |
| Add Location | preferences.rs | "Add scan directory" |
| Scan Now | library_view.rs | "Scan for AppImages" |
| Preferences | library_view.rs | "Open preferences" |
**Accessible Roles:**
- `FlowBox` -> already has label
- `ListBox` (list view) -> add `AccessibleProperty::Label("AppImage library list")`
- `ListBox` (preferences directory list) -> add label "Scan directories"
- `ListBox` (cleanup items) -> add label per category
- `ListBox` (integration dialog identity) -> add label "Application details"
- `ListBox` (integration dialog actions) -> add label "Integration actions"
- Status badges -> add `AccessibleRole::Status` (or use `update_property` with `AccessibleProperty::Label`)
**Accessible States:**
- Switch rows -> GTK handles this automatically via `SwitchRow`
- Scan button -> add `AccessibleState::Busy` while scanning is in progress
- Security scan row -> add `AccessibleState::Busy` during scan
- Analyze toolkit row -> add `AccessibleState::Busy` during analysis
### 4.1.3 Status Messages (AA)
**Live region announcements for async operations:**
Create a helper function `announce(text: &str)` in widgets.rs that uses a hidden GTK Label with `AccessibleRole::Alert` to broadcast status changes to screen readers.
Operations needing announcements:
- Scan start ("Scanning for AppImages...")
- Scan complete ("{n} AppImages found, {m} new")
- Update check start/complete
- Security scan start/complete
- Cleanup analysis start/complete
- Search results count change ("{n} results" or "No results")
---
## Files Modified
| File | Changes |
|------|---------|
| `data/resources/style.css` | Focus indicators for all widgets, high-contrast media query, reduced-motion expansion, target size minimums |
| `src/ui/widgets.rs` | New `announce()` live region helper, accessible label on copy_button, accessible role on status badges |
| `src/ui/library_view.rs` | Accessible labels on all header buttons, list box label, search results announcement, accessible roles |
| `src/ui/app_card.rs` | Accessible description for emblem overlay |
| `src/ui/detail_view.rs` | Accessible roles on banner, busy states on async rows, tooltips for technical terms, plain-language rewrites |
| `src/ui/dashboard.rs` | Tooltips for technical terms, accessible labels on any unlabeled elements |
| `src/ui/duplicate_dialog.rs` | Accessible label on delete buttons, confirmation before bulk remove, list box labels |
| `src/ui/cleanup_wizard.rs` | Accessible labels on buttons, confirmation before cleanup, list box labels, busy announcement |
| `src/ui/preferences.rs` | Accessible label on remove button and add button, list box labels |
| `src/ui/security_report.rs` | Accessible labels, tooltips for CVE terms |
| `src/ui/integration_dialog.rs` | List box labels, accessible descriptions |
| `src/ui/update_dialog.rs` | Plain-language rewrites |
| `src/window.rs` | Window title updates per page, live announcements for scan/update/clean operations |
## Verification
After implementation:
1. `cargo build` - zero errors, zero warnings
2. `cargo test` - all tests pass
3. Run with Orca screen reader - verify every element is announced correctly
4. Tab through entire app - verify all elements have visible focus indicators
5. Set `GTK_THEME=Adwaita:dark` - verify dark mode focus/contrast
6. Set high-contrast theme - verify enhanced contrast mode
7. Set `GTK_DEBUG=interactive` - inspect accessible tree
8. Keyboard-only navigation test through every screen
9. Verify all targets meet 24x24px minimum
10. Verify reduced-motion disables all animations