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:
@@ -7,7 +7,7 @@ use gtk::gio;
|
||||
use crate::config::APP_ID;
|
||||
use crate::core::database::Database;
|
||||
use crate::core::updater;
|
||||
use crate::i18n::i18n;
|
||||
use crate::i18n::{i18n, ni18n_f};
|
||||
use crate::ui::update_dialog;
|
||||
use crate::ui::widgets;
|
||||
|
||||
@@ -23,10 +23,11 @@ pub fn build_updates_view(db: &Rc<Database>) -> adw::ToolbarView {
|
||||
header.set_title_widget(Some(&title));
|
||||
|
||||
// Check Now button
|
||||
let check_btn = gtk::Button::builder()
|
||||
.icon_name("view-refresh-symbolic")
|
||||
.tooltip_text(&i18n("Check for updates (Ctrl+U)"))
|
||||
.build();
|
||||
let check_btn = widgets::accessible_icon_button(
|
||||
"view-refresh-symbolic",
|
||||
"Check for updates",
|
||||
&i18n("Check for updates (Ctrl+U)"),
|
||||
);
|
||||
header.pack_end(&check_btn);
|
||||
|
||||
// Update All button (only visible when updates exist)
|
||||
@@ -60,6 +61,7 @@ pub fn build_updates_view(db: &Rc<Database>) -> adw::ToolbarView {
|
||||
.height_request(32)
|
||||
.halign(gtk::Align::Center)
|
||||
.build();
|
||||
spinner.update_property(&[gtk::accessible::Property::Label("Checking for updates")]);
|
||||
checking_page.set_child(Some(&spinner));
|
||||
stack.add_named(&checking_page, Some("checking"));
|
||||
|
||||
@@ -157,7 +159,7 @@ pub fn build_updates_view(db: &Rc<Database>) -> adw::ToolbarView {
|
||||
// Re-read records from the shared db so UI picks up changes from the bg thread
|
||||
drop(fresh_db);
|
||||
populate_update_list(&state_c);
|
||||
state_c.toast_overlay.add_toast(adw::Toast::new(&i18n("Update check complete")));
|
||||
state_c.toast_overlay.add_toast(widgets::info_toast(&i18n("Update check complete")));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -198,7 +200,7 @@ pub fn build_updates_view(db: &Rc<Database>) -> adw::ToolbarView {
|
||||
let count = result.unwrap_or(0);
|
||||
if count > 0 {
|
||||
state_c.toast_overlay.add_toast(
|
||||
adw::Toast::new(&format!("{} apps updated", count)),
|
||||
widgets::info_toast(&ni18n_f("{} app updated", "{} apps updated", count, &[("{}", &count.to_string())])),
|
||||
);
|
||||
}
|
||||
populate_update_list(&state_c);
|
||||
@@ -257,7 +259,9 @@ fn populate_update_list(state: &Rc<UpdatesState>) {
|
||||
|
||||
state.stack.set_visible_child_name("updates");
|
||||
state.update_all_btn.set_visible(true);
|
||||
state.title.set_subtitle(&format!("{} updates available", updatable.len()));
|
||||
let count = updatable.len();
|
||||
state.title.set_subtitle(&format!("{} updates available", count));
|
||||
widgets::announce(state.list_box.upcast_ref::<gtk::Widget>(), &format!("{} updates available", count));
|
||||
|
||||
for record in &updatable {
|
||||
let name = record.app_name.as_deref().unwrap_or(&record.filename);
|
||||
@@ -284,8 +288,9 @@ fn populate_update_list(state: &Rc<UpdatesState>) {
|
||||
.expanded(false)
|
||||
.build();
|
||||
|
||||
// App icon
|
||||
// App icon (decorative - row title already names the app)
|
||||
let icon = widgets::app_icon(record.icon_path.as_deref(), name, 32);
|
||||
icon.set_accessible_role(gtk::AccessibleRole::Presentation);
|
||||
row.add_prefix(&icon);
|
||||
|
||||
// "What's new" content inside the expander
|
||||
@@ -317,6 +322,7 @@ fn populate_update_list(state: &Rc<UpdatesState>) {
|
||||
.tooltip_text(&i18n("Update this app"))
|
||||
.css_classes(["flat"])
|
||||
.build();
|
||||
update_btn.update_property(&[gtk::accessible::Property::Label(&format!("Update {}", name))]);
|
||||
|
||||
let app_id = record.id;
|
||||
let update_url = record.update_url.clone();
|
||||
@@ -355,11 +361,11 @@ fn populate_update_list(state: &Rc<UpdatesState>) {
|
||||
btn_c.set_sensitive(true);
|
||||
if result.unwrap_or(false) {
|
||||
state_c.toast_overlay.add_toast(
|
||||
adw::Toast::new(&i18n("Update complete")),
|
||||
widgets::info_toast(&i18n("Update complete")),
|
||||
);
|
||||
} else {
|
||||
state_c.toast_overlay.add_toast(
|
||||
adw::Toast::new(&i18n("Update failed")),
|
||||
widgets::error_toast(&i18n("Update failed")),
|
||||
);
|
||||
}
|
||||
populate_update_list(&state_c);
|
||||
|
||||
Reference in New Issue
Block a user