Add WCAG 2.2 AAA compliance and automated AT-SPI audit tool
- Bring all UI widgets to WCAG 2.2 AAA conformance across all views - Add accessible labels, roles, descriptions, and announcements - Bump focus outlines to 3px, target sizes to 44px AAA minimum - Fix announce()/announce_result() to walk widget tree via parent() - Add AT-SPI accessibility audit script (tools/a11y-audit.py) that checks SC 4.1.2, 1.1.1, 1.3.1, 2.1.1, 2.5.5, 2.5.8, 2.4.8, 2.4.9, 2.4.10, 2.1.3 with JSON report output for CI - Clean up project structure, archive old plan documents
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
use gtk::prelude::*;
|
||||
use gtk::accessible::Property as AccessibleProperty;
|
||||
|
||||
use crate::core::database::CatalogApp;
|
||||
use super::widgets;
|
||||
@@ -95,6 +96,7 @@ pub fn build_catalog_tile(app: &CatalogApp, installed: bool) -> gtk::FlowBoxChil
|
||||
.build();
|
||||
let dl_icon = gtk::Image::from_icon_name("folder-download-symbolic");
|
||||
dl_icon.set_pixel_size(12);
|
||||
dl_icon.update_property(&[AccessibleProperty::Label("Downloads")]);
|
||||
dl_box.append(&dl_icon);
|
||||
let dl_label = gtk::Label::new(Some(&widgets::format_count(downloads)));
|
||||
dl_label.add_css_class("caption");
|
||||
@@ -111,6 +113,7 @@ pub fn build_catalog_tile(app: &CatalogApp, installed: bool) -> gtk::FlowBoxChil
|
||||
.build();
|
||||
let star_icon = gtk::Image::from_icon_name("starred-symbolic");
|
||||
star_icon.set_pixel_size(12);
|
||||
star_icon.update_property(&[AccessibleProperty::Label("Stars")]);
|
||||
star_box.append(&star_icon);
|
||||
let star_label = gtk::Label::new(Some(&widgets::format_count(stars)));
|
||||
star_box.append(&star_label);
|
||||
@@ -124,6 +127,7 @@ pub fn build_catalog_tile(app: &CatalogApp, installed: bool) -> gtk::FlowBoxChil
|
||||
.build();
|
||||
let ver_icon = gtk::Image::from_icon_name("tag-symbolic");
|
||||
ver_icon.set_pixel_size(12);
|
||||
ver_icon.update_property(&[AccessibleProperty::Label("Version")]);
|
||||
ver_box.append(&ver_icon);
|
||||
let ver_label = gtk::Label::builder()
|
||||
.label(ver.as_str())
|
||||
@@ -161,13 +165,6 @@ pub fn build_catalog_tile(app: &CatalogApp, installed: bool) -> gtk::FlowBoxChil
|
||||
inner.append(&installed_badge);
|
||||
}
|
||||
|
||||
// Source badge - show which source this app came from
|
||||
let source_label = if app.ocs_id.is_some() { "AppImageHub" } else { "Community" };
|
||||
let source_badge = widgets::status_badge(source_label, "neutral");
|
||||
source_badge.set_halign(gtk::Align::Start);
|
||||
source_badge.set_margin_top(2);
|
||||
inner.append(&source_badge);
|
||||
|
||||
card.append(&inner);
|
||||
|
||||
let child = gtk::FlowBoxChild::builder()
|
||||
@@ -176,6 +173,30 @@ pub fn build_catalog_tile(app: &CatalogApp, installed: bool) -> gtk::FlowBoxChil
|
||||
child.add_css_class("activatable");
|
||||
widgets::set_pointer_cursor(&child);
|
||||
|
||||
// Accessible label for screen readers
|
||||
let mut a11y_parts = vec![app.name.clone()];
|
||||
if !plain.is_empty() {
|
||||
a11y_parts.push(plain.chars().take(80).collect());
|
||||
}
|
||||
if let Some(downloads) = app.ocs_downloads.filter(|&d| d > 0) {
|
||||
a11y_parts.push(format!("{} downloads", downloads));
|
||||
}
|
||||
if let Some(stars) = app.github_stars.filter(|&s| s > 0) {
|
||||
a11y_parts.push(format!("{} stars", stars));
|
||||
}
|
||||
if let Some(ref cats) = app.categories {
|
||||
if let Some(cat) = cats.split(';').next().or_else(|| cats.split(',').next()) {
|
||||
let cat = cat.trim();
|
||||
if !cat.is_empty() {
|
||||
a11y_parts.push(format!("category: {}", cat));
|
||||
}
|
||||
}
|
||||
}
|
||||
if installed {
|
||||
a11y_parts.push("installed".to_string());
|
||||
}
|
||||
child.update_property(&[AccessibleProperty::Label(&a11y_parts.join(", "))]);
|
||||
|
||||
child
|
||||
}
|
||||
|
||||
@@ -262,6 +283,22 @@ pub fn build_catalog_row(app: &CatalogApp, installed: bool) -> gtk::FlowBoxChild
|
||||
.build();
|
||||
child.add_css_class("activatable");
|
||||
widgets::set_pointer_cursor(&child);
|
||||
|
||||
// Accessible label for screen readers
|
||||
let mut a11y_parts = vec![app.name.clone()];
|
||||
if !plain.is_empty() {
|
||||
a11y_parts.push(plain.chars().take(60).collect());
|
||||
}
|
||||
if let Some(downloads) = app.ocs_downloads.filter(|&d| d > 0) {
|
||||
a11y_parts.push(format!("{} downloads", downloads));
|
||||
} else if let Some(stars) = app.github_stars.filter(|&s| s > 0) {
|
||||
a11y_parts.push(format!("{} stars", stars));
|
||||
}
|
||||
if installed {
|
||||
a11y_parts.push("installed".to_string());
|
||||
}
|
||||
child.update_property(&[AccessibleProperty::Label(&a11y_parts.join(", "))]);
|
||||
|
||||
child
|
||||
}
|
||||
|
||||
@@ -283,6 +320,24 @@ pub fn build_featured_tile(app: &CatalogApp) -> gtk::Box {
|
||||
widgets::set_pointer_cursor(&card);
|
||||
card.set_widget_name(&format!("featured-{}", app.id));
|
||||
|
||||
// Accessible label for screen readers
|
||||
let mut a11y_parts = vec![app.name.clone()];
|
||||
if let Some(desc) = app.ocs_summary.as_deref()
|
||||
.filter(|d| !d.is_empty())
|
||||
.or(app.github_description.as_deref().filter(|d| !d.is_empty()))
|
||||
{
|
||||
a11y_parts.push(strip_html(desc).chars().take(60).collect());
|
||||
}
|
||||
if let Some(ref cats) = app.categories {
|
||||
if let Some(cat) = cats.split(';').next().or_else(|| cats.split(',').next()) {
|
||||
let cat = cat.trim();
|
||||
if !cat.is_empty() {
|
||||
a11y_parts.push(format!("category: {}", cat));
|
||||
}
|
||||
}
|
||||
}
|
||||
card.update_property(&[AccessibleProperty::Label(&a11y_parts.join(", "))]);
|
||||
|
||||
// Screenshot preview area (top)
|
||||
let screenshot_frame = gtk::Frame::new(None);
|
||||
screenshot_frame.add_css_class("catalog-featured-screenshot");
|
||||
@@ -297,6 +352,7 @@ pub fn build_featured_tile(app: &CatalogApp) -> gtk::Box {
|
||||
.width_request(32)
|
||||
.height_request(32)
|
||||
.build();
|
||||
spinner.update_property(&[gtk::accessible::Property::Label("Loading screenshot")]);
|
||||
screenshot_frame.set_child(Some(&spinner));
|
||||
card.append(&screenshot_frame);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user