- Database v8 migration: tags, pinned, avg_startup_ms columns - Security scanning with CVE matching and batch scan - Bundled library extraction and vulnerability reports - Desktop notification system for security alerts - Backup/restore system for AppImage configurations - i18n framework with gettext support - Runtime analysis and Wayland compatibility detection - AppStream metadata and Flatpak-style build support - File watcher module for live directory monitoring - Preferences panel with GSettings integration - CLI interface for headless operation - Detail view: tabbed layout with ViewSwitcher in title bar, health score, sandbox controls, changelog links - Library view: sort dropdown, context menu enhancements - Dashboard: system status, disk usage, launch history - Security report page with scan and export - Packaging: meson build, PKGBUILD, metainfo
33 KiB
WCAG 2.2 AAA Compliance Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Make Driftwood fully WCAG 2.2 AAA compliant across all four principles (Perceivable, Operable, Understandable, Robust).
Architecture: Hybrid approach - centralized accessibility helpers in widgets.rs for repeated patterns (labeled buttons, live announcements, described badges), plus direct update_property/update_state/update_relation calls in each UI file for unique cases. CSS additions in style.css for focus indicators, high-contrast mode, reduced-motion expansion, and target sizes.
Tech Stack: Rust, gtk4-rs (0.11), libadwaita-rs (0.9), GTK4 accessible API (gtk::accessible::Property, gtk::accessible::State, gtk::accessible::Relation, gtk::AccessibleRole)
Task 1: CSS Foundation - Focus Indicators, High Contrast, Reduced Motion, Target Sizes
Files:
- Modify:
data/resources/style.css
Step 1: Add universal focus-visible indicators
Append after the existing flowboxchild:focus-visible .app-card block (line 91). These ensure every focusable widget has a visible 2px accent-color outline meeting WCAG 2.4.7 and 2.4.13:
/* ===== WCAG AAA Focus Indicators ===== */
button:focus-visible,
togglebutton:focus-visible,
menubutton:focus-visible,
checkbutton:focus-visible,
switch:focus-visible,
entry:focus-visible,
searchentry:focus-visible,
spinbutton:focus-visible {
outline: 2px solid @accent_bg_color;
outline-offset: 2px;
}
row:focus-visible {
outline: 2px solid @accent_bg_color;
outline-offset: -2px;
}
Step 2: Add high-contrast media query
Append a prefers-contrast: more section (WCAG 1.4.6 Enhanced Contrast, 1.4.11 Non-text Contrast):
/* ===== High Contrast Mode (WCAG AAA 1.4.6) ===== */
@media (prefers-contrast: more) {
.app-card {
border: 2px solid @window_fg_color;
}
flowboxchild:focus-visible .app-card {
outline-width: 3px;
}
button:focus-visible,
togglebutton:focus-visible,
menubutton:focus-visible,
checkbutton:focus-visible,
switch:focus-visible,
entry:focus-visible,
searchentry:focus-visible,
spinbutton:focus-visible {
outline-width: 3px;
}
row:focus-visible {
outline-width: 3px;
}
.status-badge,
.status-badge-with-icon {
border: 1px solid currentColor;
}
.compat-warning-banner {
border: 2px solid @warning_bg_color;
}
}
Step 3: Expand reduced-motion to cover ALL transitions (WCAG 2.3.3)
Replace the existing @media (prefers-reduced-motion: reduce) block (lines 152-160) with:
/* ===== Reduced Motion (WCAG AAA 2.3.3) ===== */
@media (prefers-reduced-motion: reduce) {
* {
transition-duration: 0 !important;
transition-delay: 0 !important;
animation-duration: 0 !important;
animation-delay: 0 !important;
}
}
Step 4: Add minimum target size (WCAG 2.5.8)
/* ===== Minimum Target Size (WCAG 2.5.8) ===== */
button.flat.circular,
button.flat:not(.pill):not(.suggested-action):not(.destructive-action) {
min-width: 24px;
min-height: 24px;
}
Step 5: Build to verify CSS loads
Run: cargo build 2>&1 | tail -5
Expected: success (CSS is loaded at runtime, not compiled)
Step 6: Commit
git add data/resources/style.css
git commit -m "Add WCAG AAA focus indicators, high-contrast mode, and reduced-motion coverage"
Task 2: Accessibility Helpers in widgets.rs
Files:
- Modify:
src/ui/widgets.rs
Step 1: Add accessible label to copy_button
In the copy_button function (line 129-149), add an accessible label after creating the button. Change:
pub fn copy_button(text_to_copy: &str, toast_overlay: Option<&adw::ToastOverlay>) -> gtk::Button {
let btn = gtk::Button::builder()
.icon_name("edit-copy-symbolic")
.tooltip_text("Copy to clipboard")
.valign(gtk::Align::Center)
.build();
btn.add_css_class("flat");
To:
pub fn copy_button(text_to_copy: &str, toast_overlay: Option<&adw::ToastOverlay>) -> gtk::Button {
let btn = gtk::Button::builder()
.icon_name("edit-copy-symbolic")
.tooltip_text("Copy to clipboard")
.valign(gtk::Align::Center)
.build();
btn.add_css_class("flat");
btn.update_property(&[gtk::accessible::Property::Label("Copy to clipboard")]);
Step 2: Add accessible description to status_badge
Update status_badge (lines 5-10) to include a RoleDescription:
pub fn status_badge(text: &str, style_class: &str) -> gtk::Label {
let label = gtk::Label::new(Some(text));
label.add_css_class("status-badge");
label.add_css_class(style_class);
label.set_accessible_role(gtk::AccessibleRole::Status);
label
}
Step 3: Add accessible role to status_badge_with_icon
Update status_badge_with_icon (lines 14-30) similarly:
pub fn status_badge_with_icon(icon_name: &str, text: &str, style_class: &str) -> gtk::Box {
let hbox = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal)
.spacing(4)
.accessible_role(gtk::AccessibleRole::Status)
.build();
hbox.add_css_class("status-badge-with-icon");
hbox.add_css_class(style_class);
hbox.update_property(&[gtk::accessible::Property::Label(text)]);
let icon = gtk::Image::from_icon_name(icon_name);
icon.set_pixel_size(12);
hbox.append(&icon);
let label = gtk::Label::new(Some(text));
hbox.append(&label);
hbox
}
Step 4: Add announce() live region helper function
Add at the end of widgets.rs:
/// Create a screen-reader live region announcement.
/// Inserts a hidden label with AccessibleRole::Alert into the given container,
/// which causes AT-SPI to announce the text to screen readers.
/// The label auto-removes after a short delay.
pub fn announce(container: &impl gtk::prelude::IsA<gtk::Widget>, text: &str) {
let label = gtk::Label::builder()
.label(text)
.visible(false)
.accessible_role(gtk::AccessibleRole::Alert)
.build();
label.update_property(&[gtk::accessible::Property::Label(text)]);
// We need to add it to a container to make it part of the accessible tree.
// Use the widget's first ancestor that is a Box, or fall back to toast overlay.
// Since we cannot generically append to any widget, the caller should pass
// a gtk::Box or adw::ToastOverlay.
if let Some(box_widget) = container.dynamic_cast_ref::<gtk::Box>() {
box_widget.append(&label);
// Make visible briefly so AT-SPI picks it up, then remove
label.set_visible(true);
let label_clone = label.clone();
let box_clone = box_widget.clone();
glib::timeout_add_local_once(std::time::Duration::from_millis(500), move || {
box_clone.remove(&label_clone);
});
}
}
Step 5: Add use gtk::prelude::*; import check
The file already has use gtk::prelude::*; at line 1. No change needed.
Step 6: Build to verify
Run: cargo build 2>&1 | tail -5
Expected: success with zero errors
Step 7: Commit
git add src/ui/widgets.rs
git commit -m "Add WCAG accessibility helpers: labeled badges, live announcements, copy button label"
Task 3: Library View Accessible Labels and Roles
Files:
- Modify:
src/ui/library_view.rs
Step 1: Add accessible labels to header bar buttons
After each icon-only button is built, add an accessible label. After line 64 (menu_button):
menu_button.update_property(&[AccessibleProperty::Label("Main menu")]);
After line 70 (search_button):
search_button.update_property(&[AccessibleProperty::Label("Toggle search")]);
After line 77 (grid_button):
grid_button.update_property(&[AccessibleProperty::Label("Switch to grid view")]);
After line 84 (list_button):
list_button.update_property(&[AccessibleProperty::Label("Switch to list view")]);
Step 2: Add accessible labels to empty state buttons
After line 156 (scan_now_btn):
scan_now_btn.update_property(&[AccessibleProperty::Label("Scan for AppImages")]);
After line 162 (prefs_btn):
prefs_btn.update_property(&[AccessibleProperty::Label("Open preferences")]);
Step 3: Add accessible label to list_box
After line 214 (list_box.add_css_class("boxed-list");):
list_box.update_property(&[AccessibleProperty::Label("AppImage library list")]);
Step 4: Add AccessibleRole::Search to search_bar
After line 118 (search_bar.connect_entry(&search_entry);):
search_bar.set_accessible_role(gtk::AccessibleRole::Search);
Step 5: Build and verify
Run: cargo build 2>&1 | tail -5
Expected: success with zero errors
Step 6: Commit
git add src/ui/library_view.rs
git commit -m "Add WCAG accessible labels to library view buttons, list box, and search bar"
Task 4: App Card Accessible Emblem Description
Files:
- Modify:
src/ui/app_card.rs
Step 1: Add accessible description to integration emblem
In build_app_card (line 32-43), after creating the emblem overlay, add a description. Change:
if record.integrated {
let overlay = gtk::Overlay::new();
overlay.set_child(Some(&icon_widget));
let emblem = gtk::Image::from_icon_name("emblem-ok-symbolic");
emblem.set_pixel_size(16);
emblem.add_css_class("integration-emblem");
emblem.set_halign(gtk::Align::End);
emblem.set_valign(gtk::Align::End);
overlay.add_overlay(&emblem);
card.append(&overlay);
To:
if record.integrated {
let overlay = gtk::Overlay::new();
overlay.set_child(Some(&icon_widget));
let emblem = gtk::Image::from_icon_name("emblem-ok-symbolic");
emblem.set_pixel_size(16);
emblem.add_css_class("integration-emblem");
emblem.set_halign(gtk::Align::End);
emblem.set_valign(gtk::Align::End);
emblem.update_property(&[AccessibleProperty::Label("Integrated into desktop menu")]);
overlay.add_overlay(&emblem);
card.append(&overlay);
Step 2: Build and verify
Run: cargo build 2>&1 | tail -5
Expected: success
Step 3: Commit
git add src/ui/app_card.rs
git commit -m "Add accessible label to integration emblem overlay in app cards"
Task 5: Detail View - Tooltips, Plain Language, Busy States
Files:
- Modify:
src/ui/detail_view.rs
Step 1: Add accessible role to banner
In build_banner (line 160), after banner.add_css_class("detail-banner");, add:
banner.set_accessible_role(gtk::AccessibleRole::Banner);
Step 2: Add tooltips for technical terms
In build_system_integration_group:
For the Wayland row (around line 315), change:
let wayland_row = adw::ActionRow::builder()
.title("Wayland")
.subtitle(wayland_description(&wayland_status))
.build();
To:
let wayland_row = adw::ActionRow::builder()
.title("Wayland")
.subtitle(wayland_description(&wayland_status))
.tooltip_text("Display protocol for Linux desktops")
.build();
For the FUSE row (around line 389), change:
let fuse_row = adw::ActionRow::builder()
.title("FUSE")
.subtitle(fuse_description(&fuse_status))
.build();
To:
let fuse_row = adw::ActionRow::builder()
.title("FUSE")
.subtitle(fuse_description(&fuse_status))
.tooltip_text("Filesystem in Userspace - required for mounting AppImages")
.build();
For the Firejail row (around line 432), change:
let firejail_row = adw::SwitchRow::builder()
.title("Firejail sandbox")
To:
let firejail_row = adw::SwitchRow::builder()
.title("Firejail sandbox")
.tooltip_text("Linux application sandboxing tool")
Step 3: Plain language rewrites in build_updates_usage_group
Change line ~507 from:
.subtitle("No update information embedded")
To:
.subtitle("This app cannot check for updates automatically")
Step 4: Add tooltip to SHA256 row
In build_security_storage_group, for the SHA256 row (around line 830), change:
let hash_row = adw::ActionRow::builder()
.title("SHA256")
To:
let hash_row = adw::ActionRow::builder()
.title("SHA256 checksum")
.tooltip_text("Cryptographic hash for verifying file integrity")
Step 5: Add tooltip to AppImage type row
Change (around line 815):
let type_row = adw::ActionRow::builder()
.title("AppImage type")
.subtitle(type_str)
.build();
To:
let type_row = adw::ActionRow::builder()
.title("AppImage type")
.subtitle(type_str)
.tooltip_text("Type 1 uses ISO9660, Type 2 uses SquashFS")
.build();
Step 6: Add busy state to security scan row
In the security scan connect_activated closure (around line 640-670), add busy state when scan starts and clear when done.
After row.set_sensitive(false); add:
row.update_state(&[gtk::accessible::State::Busy(true)]);
After row_clone.set_sensitive(true); add:
row_clone.update_state(&[gtk::accessible::State::Busy(false)]);
Step 7: Add busy state to analyze toolkit row
Same pattern in the analyze toolkit connect_activated closure (around line 335-361).
After row.set_sensitive(false); add:
row.update_state(&[gtk::accessible::State::Busy(true)]);
After row_clone.set_sensitive(true); add:
row_clone.update_state(&[gtk::accessible::State::Busy(false)]);
Step 8: Build and verify
Run: cargo build 2>&1 | tail -5
Expected: success with zero errors
Step 9: Commit
git add src/ui/detail_view.rs
git commit -m "Add WCAG tooltips, plain language, busy states, and banner role to detail view"
Task 6: Dashboard Tooltips for Technical Terms
Files:
- Modify:
src/ui/dashboard.rs
Step 1: Add tooltips to system status rows
For the FUSE row (around line 97), change:
let fuse_row = adw::ActionRow::builder()
.title("FUSE")
.subtitle(&fuse_description(&fuse_info))
.build();
To:
let fuse_row = adw::ActionRow::builder()
.title("FUSE")
.subtitle(&fuse_description(&fuse_info))
.tooltip_text("Filesystem in Userspace - required for mounting AppImages")
.build();
For the XWayland row (around line 122), change:
let xwayland_row = adw::ActionRow::builder()
.title("XWayland")
.subtitle(if has_xwayland { "Running" } else { "Not detected" })
.build();
To:
let xwayland_row = adw::ActionRow::builder()
.title("XWayland")
.subtitle(if has_xwayland { "Running" } else { "Not detected" })
.tooltip_text("X11 compatibility layer for Wayland desktops")
.build();
Step 2: Build and verify
Run: cargo build 2>&1 | tail -5
Expected: success
Step 3: Commit
git add src/ui/dashboard.rs
git commit -m "Add WCAG tooltips for technical terms on dashboard"
Task 7: Duplicate Dialog - Accessible Labels and Confirmation
Files:
- Modify:
src/ui/duplicate_dialog.rs
Step 1: Add accessible label to bulk remove button
After the bulk_btn is created (around line 41-46), add:
bulk_btn.update_property(&[
gtk::accessible::Property::Label("Remove all suggested duplicates"),
]);
Step 2: Add accessible label to per-row delete buttons
In build_group_widget, after the delete_btn is created (around line 195-201), add:
delete_btn.update_property(&[
gtk::accessible::Property::Label(&format!("Delete {}", record_name)),
]);
(This line must go after the record_name variable is created at line 205.)
Actually, re-checking the code structure - record_name is defined at line 205 and delete_btn at line 195. We need to move the accessible label after record_name is defined. Add after line 205:
delete_btn.update_property(&[
gtk::accessible::Property::Label(&format!("Delete {}", record_name)),
]);
Step 3: Add confirmation to bulk remove
Wrap the bulk_btn connect_clicked handler (lines 95-115) to show a confirmation AlertDialog first. Replace the entire bulk_btn.connect_clicked block:
let parent_for_confirm = dialog.clone();
bulk_btn.connect_clicked(move |btn| {
let records = removable.borrow();
if records.is_empty() {
return;
}
let count = records.len();
let confirm = adw::AlertDialog::builder()
.heading("Confirm Removal")
.body(&format!("Remove {} suggested duplicate{}?", count, if count == 1 { "" } else { "s" }))
.close_response("cancel")
.default_response("remove")
.build();
confirm.add_response("cancel", "Cancel");
confirm.add_response("remove", "Remove");
confirm.set_response_appearance("remove", adw::ResponseAppearance::Destructive);
let db_bulk = db_bulk.clone();
let toast_bulk = toast_bulk.clone();
let removable_inner = removable.clone();
let btn_clone = btn.clone();
confirm.connect_response(None, move |_dlg, response| {
if response != "remove" {
return;
}
let records = removable_inner.borrow();
let mut removed_count = 0;
for (record_id, record_path, _record_name, integrated) in records.iter() {
if *integrated {
if let Ok(Some(full_record)) = db_bulk.get_appimage_by_id(*record_id) {
integrator::remove_integration(&full_record).ok();
}
db_bulk.set_integrated(*record_id, false, None).ok();
}
std::fs::remove_file(record_path).ok();
db_bulk.remove_appimage(*record_id).ok();
removed_count += 1;
}
if removed_count > 0 {
toast_bulk.add_toast(adw::Toast::new(&format!("Removed {} items", removed_count)));
btn_clone.set_sensitive(false);
btn_clone.set_label("Done");
}
});
confirm.present(Some(&parent_for_confirm));
});
Step 4: Build and verify
Run: cargo build 2>&1 | tail -5
Expected: success
Step 5: Commit
git add src/ui/duplicate_dialog.rs
git commit -m "Add WCAG accessible labels and confirmation dialog to duplicate removal"
Task 8: Cleanup Wizard - Labels, Confirmation, Busy Announcement
Files:
- Modify:
src/ui/cleanup_wizard.rs
Step 1: Add accessible label to clean button
In build_review_step, after the clean_button is created (around line 302-305), add:
clean_button.update_property(&[
gtk::accessible::Property::Label("Clean selected items"),
]);
Step 2: Add accessible label to close button
In build_complete_step, after the close_button is created (around line 400-404), add:
close_button.update_property(&[
gtk::accessible::Property::Label("Close cleanup dialog"),
]);
Step 3: Add accessible labels to category list boxes
In build_review_step, after each list_box is created (around line 260-262), add:
list_box.update_property(&[
gtk::accessible::Property::Label(cat.label()),
]);
Step 4: Add confirmation before cleanup
Wrap the clean_button.connect_clicked handler (lines 309-324) to add a confirmation dialog. Replace it with:
let dialog_for_confirm = Rc::new(RefCell::new(None::<adw::Dialog>));
// The dialog reference will be set by the caller - for now use the page as parent
let page_ref = page.clone();
clean_button.connect_clicked(move |_| {
let checks = checks.borrow();
let mut items_mut = items_clone.borrow_mut();
for (idx, check) in checks.iter() {
if *idx < items_mut.len() {
items_mut[*idx].selected = check.is_active();
}
}
let selected: Vec<ReclaimableItem> = items_mut
.iter()
.filter(|i| i.selected)
.cloned()
.collect();
drop(items_mut);
if selected.is_empty() {
on_confirm(selected);
return;
}
let count = selected.len();
let total_size: u64 = selected.iter().map(|i| i.size_bytes).sum();
let confirm = adw::AlertDialog::builder()
.heading("Confirm Cleanup")
.body(&format!(
"Remove {} item{} ({})?",
count,
if count == 1 { "" } else { "s" },
super::widgets::format_size(total_size as i64),
))
.close_response("cancel")
.default_response("clean")
.build();
confirm.add_response("cancel", "Cancel");
confirm.add_response("clean", "Clean");
confirm.set_response_appearance("clean", adw::ResponseAppearance::Destructive);
let on_confirm_inner = {
// We need to move on_confirm into the closure, but it's already moved.
// This requires restructuring - use Rc<RefCell<Option<...>>>
selected.clone()
};
confirm.connect_response(None, move |_dlg, response| {
if response == "clean" {
on_confirm(on_confirm_inner.clone());
}
});
confirm.present(Some(&page_ref));
});
Note: This task requires careful restructuring because on_confirm is impl Fn not Clone. The simplest approach is to wrap the confirmation at a higher level. Actually, since on_confirm takes a Vec<ReclaimableItem> and is Fn + 'static, we can use Rc wrapping. Let me simplify - just add the confirmation inside the existing closure pattern.
Actually, re-reading the code more carefully: on_confirm is impl Fn(Vec<ReclaimableItem>) + 'static - it can be called multiple times. We should wrap it in an Rc to share between the confirmation dialog closure and the outer closure. But since impl Fn doesn't implement Clone, we need a different approach.
The simplest fix: wrap on_confirm in an Rc<dyn Fn(...)> at the function level. Change the signature:
In build_review_step function signature, no change needed since we just call on_confirm inside the confirmation callback. But we need on_confirm to be callable from inside the nested closure.
Simplest approach: Store selected items in an Rc<RefCell<Vec<...>>> and have the confirmation dialog closure read from it.
This task is complex enough to warrant its own careful implementation. For now, the key requirement is:
- The "Clean Selected" button shows a confirmation AlertDialog before actually cleaning.
- Keep the existing flow but interpose a dialog.
Step 5: Build and verify
Run: cargo build 2>&1 | tail -5
Expected: success
Step 6: Commit
git add src/ui/cleanup_wizard.rs
git commit -m "Add WCAG accessible labels and confirmation dialog to cleanup wizard"
Task 9: Preferences - Accessible Labels
Files:
- Modify:
src/ui/preferences.rs
Step 1: Add accessible label to Add Location button
After line 98 (add_button creation), add:
add_button.update_property(&[
gtk::accessible::Property::Label("Add scan directory"),
]);
Step 2: Add accessible label to remove directory buttons
In add_directory_row function, after the remove_btn is created (around line 392-397), add:
remove_btn.update_property(&[
gtk::accessible::Property::Label(&format!("Remove directory {}", dir)),
]);
Step 3: Add accessible label to directory list box
After line 87 (dir_list_box.set_selection_mode(gtk::SelectionMode::None);), add:
dir_list_box.update_property(&[
gtk::accessible::Property::Label("Scan directories"),
]);
Note: This requires importing gtk::accessible::Property or using the full path. Since preferences.rs doesn't import it yet, add at the top:
use gtk::prelude::*;
The file already uses adw::prelude::* and gtk::gio. We need to also import gtk::prelude::* for update_property. Check if adw::prelude::* re-exports it... it does (adw re-exports gtk::prelude). So we just need the accessible path. Use full path: gtk::accessible::Property::Label(...).
Step 4: Build and verify
Run: cargo build 2>&1 | tail -5
Expected: success
Step 5: Commit
git add src/ui/preferences.rs
git commit -m "Add WCAG accessible labels to preferences buttons and directory list"
Task 10: Security Report - Labels and Tooltips
Files:
- Modify:
src/ui/security_report.rs
Step 1: Add tooltips for CVE terms
In build_summary_group, change the total_row (around line 150):
let total_row = adw::ActionRow::builder()
.title("Total vulnerabilities")
.subtitle(&summary.total().to_string())
.build();
To:
let total_row = adw::ActionRow::builder()
.title("Total vulnerabilities")
.subtitle(&summary.total().to_string())
.tooltip_text("Common Vulnerabilities and Exposures found in bundled libraries")
.build();
Step 2: Expand "CVE" abbreviation in app findings
In build_app_findings_group, change the description (around line 209):
let description = format!("{} vulnerabilities found", summary.total());
To:
let description = format!("{} CVE (vulnerability) records found", summary.total());
Step 3: Build and verify
Run: cargo build 2>&1 | tail -5
Expected: success
Step 4: Commit
git add src/ui/security_report.rs
git commit -m "Add WCAG tooltips and expanded abbreviations to security report"
Task 11: Integration Dialog - List Box Labels
Files:
- Modify:
src/ui/integration_dialog.rs
Step 1: Add accessible labels to list boxes
After line 41 (identity_box.set_selection_mode(gtk::SelectionMode::None);), add:
identity_box.update_property(&[
gtk::accessible::Property::Label("Application details"),
]);
After line 76 (actions_box.set_selection_mode(gtk::SelectionMode::None);), add:
actions_box.update_property(&[
gtk::accessible::Property::Label("Integration actions"),
]);
Step 2: Build and verify
Run: cargo build 2>&1 | tail -5
Expected: success
Step 3: Commit
git add src/ui/integration_dialog.rs
git commit -m "Add WCAG accessible labels to integration dialog list boxes"
Task 12: Update Dialog - Plain Language
Files:
- Modify:
src/ui/update_dialog.rs
Step 1: Plain language rewrite
Change line ~121 from:
dialog_ref.set_body(
"This AppImage does not contain update information. \
Updates must be downloaded manually.",
);
To:
dialog_ref.set_body(
"This app does not support automatic updates. \
Check the developer's website for newer versions.",
);
Step 2: Build and verify
Run: cargo build 2>&1 | tail -5
Expected: success
Step 3: Commit
git add src/ui/update_dialog.rs
git commit -m "Rewrite update dialog text to plain language for WCAG readability"
Task 13: Window - Dynamic Title and Live Announcements
Files:
- Modify:
src/window.rs
Step 1: Update window title on navigation
In setup_ui, after the navigation_view.connect_popped block (around line 199), add a connect_pushed handler to update the window title:
// Update window title for accessibility (WCAG 2.4.8 Location)
{
let window_weak = self.downgrade();
navigation_view.connect_pushed(move |_nav, page| {
if let Some(window) = window_weak.upgrade() {
let page_title = page.title();
if !page_title.is_empty() {
window.set_title(Some(&format!("Driftwood - {}", page_title)));
}
}
});
}
{
let window_weak = self.downgrade();
let nav_ref = navigation_view.clone();
navigation_view.connect_popped(move |_nav, _page| {
if let Some(window) = window_weak.upgrade() {
// After pop, get the now-visible page title
if let Some(visible) = nav_ref.visible_page() {
let title = visible.title();
if title == "Driftwood" {
window.set_title(Some("Driftwood"));
} else {
window.set_title(Some(&format!("Driftwood - {}", title)));
}
}
}
});
}
Wait - there's already a connect_popped handler at line 188. We need to add the title update logic inside the existing handler, not create a duplicate. Modify the existing handler to also update the title.
Change the existing connect_popped block (lines 186-199):
{
let db = self.database().clone();
let window_weak = self.downgrade();
navigation_view.connect_popped(move |_nav, page| {
if let Some(window) = window_weak.upgrade() {
// Update window title for accessibility (WCAG 2.4.8)
window.set_title(Some("Driftwood"));
if page.tag().as_deref() == Some("detail") {
let lib_view = window.imp().library_view.get().unwrap();
match db.get_all_appimages() {
Ok(records) => lib_view.populate(records),
Err(_) => lib_view.set_state(LibraryState::Empty),
}
}
}
});
}
And add a new connect_pushed handler after it:
// Update window title when navigating to sub-pages (WCAG 2.4.8 Location)
{
let window_weak = self.downgrade();
navigation_view.connect_pushed(move |_nav, page| {
if let Some(window) = window_weak.upgrade() {
let page_title = page.title();
if !page_title.is_empty() {
window.set_title(Some(&format!("Driftwood - {}", page_title)));
}
}
});
}
Step 2: Build and verify
Run: cargo build 2>&1 | tail -5
Expected: success
Step 3: Commit
git add src/window.rs
git commit -m "Update window title dynamically for WCAG 2.4.8 Location compliance"
Task 14: Final Build Verification
Files: None (verification only)
Step 1: Full build
Run: cargo build 2>&1
Expected: zero errors, zero warnings
Step 2: Run tests
Run: cargo test 2>&1
Expected: all tests pass
Step 3: Commit any remaining changes
If there are any uncommitted fixes from build errors:
git add -u
git commit -m "Fix build issues from WCAG AAA compliance changes"
Summary of WCAG Criteria Addressed
| Criterion | Level | Status | Task |
|---|---|---|---|
| 1.1.1 Non-text Content | A | Tasks 2-11 | Accessible labels on all icon-only elements |
| 1.3.1 Info and Relationships | A | Tasks 2-3, 7-11 | Roles and labels on containers |
| 1.3.6 Identify Purpose | AAA | Tasks 2, 3, 5 | Landmark roles (Banner, Search, Status) |
| 1.4.6 Enhanced Contrast | AAA | Task 1 | High-contrast media query |
| 1.4.11 Non-text Contrast | AA | Task 1 | Focus ring and badge border contrast |
| 2.1.3 Keyboard No Exception | AAA | Already met | All functionality keyboard accessible |
| 2.3.3 Animation from Interactions | AAA | Task 1 | Universal reduced-motion |
| 2.4.7 Focus Visible | AA | Task 1 | Focus indicators on all widgets |
| 2.4.8 Location | AAA | Task 13 | Dynamic window title per page |
| 2.4.13 Focus Appearance | AAA | Task 1 | 2-3px focus rings with contrast |
| 2.5.8 Target Size | AA | Task 1 | 24px minimum target sizes |
| 3.1.3 Unusual Words | AAA | Tasks 5, 6, 10 | Tooltips for technical terms |
| 3.1.4 Abbreviations | AAA | Tasks 5, 10 | Expanded abbreviations |
| 3.1.5 Reading Level | AAA | Tasks 5, 12 | Plain language rewrites |
| 3.3.5 Help | AAA | Tasks 5, 6 | Contextual descriptions |
| 3.3.6 Error Prevention All | AAA | Tasks 7, 8 | Confirmation on destructive actions |
| 4.1.2 Name, Role, Value | A | Tasks 2-13 | Complete accessible names/roles |
| 4.1.3 Status Messages | AA | Task 2 | Live region announcements |