From 5ef3789c7def6876ae7d3a0740b365362c983f3d Mon Sep 17 00:00:00 2001 From: lashman Date: Fri, 27 Feb 2026 23:24:34 +0200 Subject: [PATCH] Add feature roadmap design for 25 new features Comprehensive design doc covering discovery-to-uninstall lifecycle: - System modification tracking for clean reversible uninstalls - Sorted by complexity from quick wins to major features - Covers FUSE wizard, autostart, MIME types, catalog browser, portable mode, batch operations, and more --- .../2026-02-27-feature-roadmap-design.md | 679 ++++++++++++++++++ 1 file changed, 679 insertions(+) create mode 100644 docs/plans/2026-02-27-feature-roadmap-design.md diff --git a/docs/plans/2026-02-27-feature-roadmap-design.md b/docs/plans/2026-02-27-feature-roadmap-design.md new file mode 100644 index 0000000..0534a7e --- /dev/null +++ b/docs/plans/2026-02-27-feature-roadmap-design.md @@ -0,0 +1,679 @@ +# Driftwood Feature Roadmap - Design Document + +**Goal:** Add 26 features to make Driftwood the definitive AppImage manager for Linux newcomers coming from Windows, covering the full lifecycle from discovery to clean uninstall. + +**Architecture:** All features build on the existing GTK4/libadwaita/Rust stack. Every system modification (desktop files, icons, MIME associations, autostart entries) is tracked in a central `system_modifications` table and fully reversed on app removal. Features are ordered from simplest to most complex. + +**Tech Stack:** Rust, gtk4-rs, libadwaita-rs, rusqlite, gio, notify crate, pkexec/polkit for privileged operations, XDG specs (Desktop Entry, Autostart, MIME Applications), AppImage feed.json catalog. + +--- + +## Core Architecture: System Modification Tracking + +Every feature that touches system files MUST use this tracking system. No exceptions. + +### New Database Table + +```sql +CREATE TABLE IF NOT EXISTS system_modifications ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + appimage_id INTEGER REFERENCES appimages(id) ON DELETE CASCADE, + mod_type TEXT NOT NULL, + file_path TEXT NOT NULL, + previous_value TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')) +); +CREATE INDEX IF NOT EXISTS idx_system_mods_appimage + ON system_modifications(appimage_id); +``` + +**mod_type values:** `desktop_file`, `icon`, `autostart`, `mime_default`, `mime_entry`, `system_desktop`, `system_icon`, `system_binary` + +### Core Functions (src/core/integrator.rs) + +```rust +pub fn register_modification(db: &Database, appimage_id: i64, mod_type: &str, file_path: &str, previous_value: Option<&str>) -> Result<()> +pub fn undo_modification(db: &Database, mod_id: i64) -> Result<()> +pub fn undo_all_modifications(db: &Database, appimage_id: i64) -> Result<()> +``` + +### Uninstall Flow + +1. Query `system_modifications` for the appimage_id +2. For each modification (in reverse order): + - `desktop_file` / `autostart` / `icon`: delete the file + - `mime_default`: restore previous_value via `xdg-mime default {previous_value} {mime_type}` + - `system_*`: delete via `pkexec rm` +3. Run `update-desktop-database ~/.local/share/applications/` +4. Run `gtk-update-icon-cache ~/.local/share/icons/hicolor/` (if icons were removed) +5. Optionally delete app data paths (with user confirmation) +6. Delete the AppImage file +7. Delete DB record (CASCADE handles system_modifications rows) + +### CLI Purge Command + +`driftwood purge` - removes ALL system modifications for ALL managed apps, for when Driftwood itself is being removed from the system. + +--- + +## Feature Specifications (Sorted: Easiest to Most Complex) + +--- + +### F1. Auto-Set Executable Permission on Existing Files + +**Complexity:** Trivial (15 min) +**Problem:** Files already in scan directories skip chmod during drag-and-drop. Non-executable AppImages found during scan aren't fixed. +**Files:** `src/ui/drop_dialog.rs`, `src/core/discovery.rs` + +**Changes:** +- `drop_dialog.rs`: In the `in_scan_dir` branch of `register_dropped_files()`, add permission check and fix: + ```rust + if !std::os::unix::fs::PermissionsExt::mode(&metadata.permissions()) & 0o111 != 0 { + std::fs::set_permissions(&final_path, std::fs::Permissions::from_mode(0o755))?; + } + ``` +- `discovery.rs`: After scan finds a non-executable AppImage, auto-fix permissions and log it + +--- + +### F2. Move-or-Copy Option for Drag-and-Drop (+ Keep in Place) + +**Complexity:** Easy (30 min) +**Problem:** Always copies file, wasting disk. No option to keep in place. +**Files:** `src/ui/drop_dialog.rs` + +**Changes:** +- Replace 3-button dialog with 4 response options: + - `"cancel"` - Cancel + - `"keep-in-place"` - Keep in place (register at current location, set executable, no copy) + - `"copy-only"` - Copy to Applications (current "Just add" behavior) + - `"copy-and-integrate"` - Copy & add to menu (current "Add to app menu", suggested/default) +- `register_dropped_files()` receives a new `copy_mode` enum: `KeepInPlace`, `CopyOnly`, `CopyAndIntegrate` +- `KeepInPlace`: set executable on original path, register in DB at original location, optionally integrate +- Dialog text updated: "Where should this AppImage live?" + +--- + +### F3. Version Rollback + +**Complexity:** Easy (1 hr) +**Problem:** No way to go back if an update breaks things. +**Files:** `src/core/updater.rs`, `src/ui/detail_view.rs`, `src/core/database.rs` + +**Changes:** +- New DB column: `previous_version_path TEXT` on appimages +- In `updater.rs` update flow: before replacing, rename old to `{path}.prev` and store path in `previous_version_path` +- Detail view system tab: "Rollback to previous version" button (visible only when `previous_version_path` is set) +- Rollback: swap current and .prev files, update DB fields, re-run analysis +- Cleanup: delete .prev file when user explicitly confirms or after configurable retention + +--- + +### F4. Source Tracking + +**Complexity:** Easy (1 hr) +**Problem:** Users can't tell where an AppImage was downloaded from. +**Files:** `src/ui/detail_view.rs`, `src/core/database.rs`, `src/ui/drop_dialog.rs` + +**Changes:** +- New DB column: `source_url TEXT` on appimages +- Auto-detect from `update_info` field (already stored): parse GitHub/GitLab URLs +- Display "Source: github.com/obsidianmd/obsidian" on detail view overview tab +- Drop dialog: optional "Where did you download this?" text field (pre-filled if detected) +- Catalog installs (F26): automatically set source_url from catalog entry + +--- + +### F5. Launch Statistics Dashboard + +**Complexity:** Easy (1-2 hrs) +**Problem:** Launch data is tracked but never shown. +**Files:** `src/ui/dashboard.rs`, `src/core/database.rs` + +**Changes:** +- New DB queries: + - `get_top_launched(limit: i32) -> Vec<(String, u64)>` - most launched apps + - `get_launch_count_since(since: &str) -> u64` - total launches since date + - `get_recent_launches(limit: i32) -> Vec<(String, String)>` - recent launch events with timestamps +- Dashboard: new "Activity" section showing: + - Top 5 most-launched apps with launch counts + - "X launches this week" summary stat + - "Last launched: AppName, 2 hours ago" + +--- + +### F6. Batch Operations + +**Complexity:** Medium (2-3 hrs) +**Problem:** Can't select multiple AppImages for bulk actions. +**Files:** `src/ui/library_view.rs`, `src/ui/app_card.rs`, `src/window.rs` + +**Changes:** +- Library view header: "Select" toggle button +- When active: app cards show checkboxes, bottom action bar slides up +- Action bar buttons: "Integrate" / "Remove Integration" / "Delete" / "Export" +- Each action confirms with count: "Integrate 5 AppImages?" +- Delete uses the full uninstall flow (F14/system_modifications cleanup) +- Selection state stored in a `HashSet` of record IDs on the LibraryView + +--- + +### F7. Automatic Desktop Integration on Scan + +**Complexity:** Easy (30 min) +**Problem:** Users forget to integrate after scanning. +**Files:** `src/window.rs`, `src/ui/preferences.rs` + +**Changes:** +- GSettings key `auto-integrate` already exists (default false) +- Wire it up: after scan completes in `window.rs`, if setting is true, iterate newly discovered apps and call `integrator::integrate()` for each +- Register all created files via `register_modification()` +- Preferences: add toggle in Behavior page (already may be there, verify) + +--- + +### F8. Autostart Manager + +**Complexity:** Medium (2 hrs) +**Problem:** No way to set AppImages to start at login. +**Spec:** XDG Autostart - `.desktop` file in `~/.config/autostart/` +**Files:** `src/core/integrator.rs`, `src/ui/detail_view.rs`, `src/core/database.rs` + +**Changes:** +- New DB column: `autostart INTEGER NOT NULL DEFAULT 0` +- New functions in `integrator.rs`: + ```rust + pub fn enable_autostart(db: &Database, record: &AppImageRecord) -> Result + pub fn disable_autostart(db: &Database, record_id: i64) -> Result<()> + ``` +- `enable_autostart`: creates `~/.config/autostart/driftwood-{id}.desktop` with: + ```ini + [Desktop Entry] + Type=Application + Name={app_name} + Exec={appimage_path} + Icon={icon_path} + X-GNOME-Autostart-enabled=true + X-Driftwood-AppImage-ID={id} + ``` +- Registers modification with mod_type `autostart` +- Detail view system tab: "Start at login" switch row +- `disable_autostart`: deletes the file, removes from system_modifications +- Uninstall flow: handled by `undo_all_modifications()` + +--- + +### F9. System Notification Integration + +**Complexity:** Medium (2 hrs) +**Problem:** Toasts vanish, important events get missed. +**Files:** `src/core/notification.rs`, `src/window.rs` + +**Changes:** +- Use `gio::Application::send_notification(id, notification)` for: + - App crash on launch (high priority) + - Updates available after background check (normal priority) + - Security vulnerabilities found (high priority if critical/high severity) +- Keep toasts for minor confirmations (copied, integrated, etc.) +- Notification click opens Driftwood and navigates to relevant view +- `notification.rs` already has the logic for CVE notifications - extend to use gio::Notification instead of/in addition to libnotify + +--- + +### F10. Storage Dashboard per App + +**Complexity:** Medium (2 hrs) +**Problem:** Users don't know total disk usage per app. +**Files:** `src/ui/detail_view.rs`, `src/core/footprint.rs`, `src/core/database.rs` + +**Changes:** +- `footprint.rs`: new function `get_total_footprint(db: &Database, record_id: i64) -> FootprintSummary` + ```rust + pub struct FootprintSummary { + pub binary_size: u64, + pub config_size: u64, + pub cache_size: u64, + pub data_size: u64, + pub state_size: u64, + pub total_size: u64, + } + ``` +- Detail view storage tab: visual breakdown with labeled size bars +- Each category shows path and size: "Config (~/.config/MyApp) - 12 MB" +- "Clean cache" button per category (deletes cache paths only) +- Library list view: optional "Total size" column + +--- + +### F11. Background Update Checks + +**Complexity:** Medium (2-3 hrs) +**Problem:** No automatic update awareness. +**Files:** `src/window.rs`, `src/ui/dashboard.rs`, `src/ui/library_view.rs`, GSettings schema + +**Changes:** +- New GSettings key: `update-check-interval-hours` type i, default 24, range 1-168 +- New GSettings key: `last-update-check` type s, default '' +- On startup: if `auto-check-updates` is true and enough time has passed, spawn background check +- Background check: iterate all apps with update_info, call `check_appimage_for_update()` per app +- Results: update `update_available` column in DB, send gio::Notification if updates found +- Dashboard: show "X updates available" with timestamp "Last checked: 2h ago" +- Library view: badge on apps with updates +- Preferences: toggle + interval dropdown + +--- + +### F12. One-Click Update All + +**Complexity:** Medium (3-4 hrs) +**Problem:** Can only update one app at a time. +**Files:** `src/ui/dashboard.rs`, new `src/ui/batch_update_dialog.rs`, `src/core/updater.rs` + +**Changes:** +- Dashboard: "Update All (N)" button when updates are available +- Opens batch update dialog showing list of apps to update with checkboxes +- Progress: per-app progress bar, overall progress bar +- Each update: download new version, save old as .prev (F3 rollback), replace, re-analyze +- On completion: summary toast "Updated 5 apps successfully, 1 failed" +- Failed updates: show error per app, keep old version +- Cancel: stops remaining updates, already-updated apps stay updated + +--- + +### F13. Full Uninstall with Data Cleanup + +**Complexity:** Medium (3 hrs) +**Problem:** Deleting AppImage leaves config/cache/data behind. +**Files:** `src/ui/detail_view.rs`, new confirmation dialog logic, `src/core/footprint.rs` + +**Changes:** +- Delete button in detail view triggers new uninstall flow: + 1. Show dialog with FootprintSummary (from F10): + - "Delete MyApp?" with breakdown: + - [x] AppImage file (245 MB) + - [x] Configuration (~/.config/MyApp) - 12 MB + - [x] Cache (~/.cache/MyApp) - 89 MB + - [x] Data (~/.local/share/MyApp) - 1.2 GB + - Total: 1.5 GB will be freed + 2. All checked by default, user can uncheck to keep data + 3. On confirm: + - Call `undo_all_modifications()` (removes .desktop, icons, autostart, MIME defaults) + - Delete selected data paths + - Delete the AppImage file + - Remove DB record +- Batch delete (F6) uses same flow with aggregated summary + +--- + +### F14. Theme/Icon Preview in Drop Dialog + +**Complexity:** Medium (2 hrs) +**Problem:** Users don't see what the app looks like before integrating. +**Files:** `src/ui/drop_dialog.rs`, `src/core/inspector.rs` + +**Changes:** +- New function in inspector: `extract_icon_fast(path: &Path) -> Option` + - Runs `unsquashfs -l` to find icon file, then extracts just that one file + - Much faster than full inspection +- Drop dialog: after registration, show preview card: + - App icon (from fast extraction) + - App name (from filename initially, updated after full analysis) + - "This is how it will appear in your app menu" +- If icon extraction fails, show default AppImage icon + +--- + +### F15. FUSE Fix Wizard + +**Complexity:** Significant (4-5 hrs) +**Problem:** #1 support issue - FUSE not installed, AppImages won't run. +**Files:** `src/core/fuse.rs`, new `src/ui/fuse_wizard.rs`, `src/ui/dashboard.rs`, new `data/app.driftwood.Driftwood.policy` + +**Changes:** +- New distro detection in `fuse.rs`: + ```rust + pub struct DistroInfo { + pub id: String, // "ubuntu", "fedora", "arch", etc. + pub id_like: Vec, + pub version_id: String, + } + pub fn detect_distro() -> Option // parses /etc/os-release + pub fn get_fuse_install_command(distro: &DistroInfo) -> Option + ``` +- Install commands by distro family: + - Debian/Ubuntu: `apt install -y libfuse2t64` (24.04+) or `apt install -y libfuse2` (older) + - Fedora/RHEL: `dnf install -y fuse-libs` + - Arch/Manjaro: `pacman -S --noconfirm fuse2` + - openSUSE: `zypper install -y libfuse2` +- Polkit policy file (`data/app.driftwood.Driftwood.policy`): + ```xml + + + + + Install FUSE library for AppImage support + Authentication is required to install the FUSE library + + auth_admin + auth_admin + auth_admin + + + + ``` +- Wizard dialog (NavigationPage-style multi-step): + 1. "FUSE is not installed" - explanation of what FUSE is and why it's needed + 2. "We detected {distro}. Install with: `{command}`" - shows exact command + 3. "Install now" button runs `pkexec sh -c "{command}"` and shows output + 4. Re-check: calls `detect_system_fuse()` and shows success/failure +- Dashboard: yellow banner when FUSE is missing with "Fix now" button + +--- + +### F16. File Type Association Manager + +**Complexity:** Significant (4 hrs) +**Problem:** AppImages don't register MIME types, so files don't open with them. +**Spec:** XDG MIME Applications - `~/.local/share/applications/mimeapps.list` +**Files:** `src/core/integrator.rs`, `src/ui/detail_view.rs` + +**Changes:** +- `inspector.rs` already extracts desktop entry content including MimeType= field +- New function: `parse_mime_types(desktop_entry: &str) -> Vec` - extracts MimeType= values +- When integrating: include `MimeType=` in generated .desktop file +- Detail view overview tab: "Supported file types" section listing MIME types +- Per-type toggle: "Set as default for .png files" +- Setting default: + 1. Query current default: `xdg-mime query default {mime_type}` + 2. Store in `system_modifications` with `previous_value` = current default + 3. Set new: `xdg-mime default driftwood-{id}.desktop {mime_type}` +- Removing: restore previous default from `previous_value` +- Uninstall: handled by `undo_all_modifications()` + +--- + +### F17. Taskbar/Panel Icon Fix (StartupWMClass) + +**Complexity:** Significant (3 hrs) +**Problem:** Running AppImages show wrong/generic icon in taskbar. +**Files:** `src/core/integrator.rs`, `src/core/wayland.rs`, `src/ui/detail_view.rs` + +**Changes:** +- During inspection: extract `StartupWMClass=` from embedded .desktop entry +- Store in new DB column: `startup_wm_class TEXT` +- When generating .desktop file for integration, include StartupWMClass if available +- After launch + Wayland analysis: read `/proc/{pid}/environ` for `GDK_BACKEND`, check window list via `xdotool` or `xprop` for WM_CLASS +- If WM_CLASS doesn't match StartupWMClass: log warning, offer to patch .desktop file +- Detail view system tab: show detected WM_CLASS, allow manual override + +--- + +### F18. Download Verification Helper + +**Complexity:** Significant (4 hrs) +**Problem:** No way to verify AppImage authenticity. +**Files:** New `src/core/verification.rs`, `src/ui/detail_view.rs`, `src/ui/drop_dialog.rs` + +**Changes:** +- AppImage signature spec: GPG signature embedded at offset stored in ELF section +- New module `verification.rs`: + ```rust + pub enum VerificationStatus { + SignedValid { signer: String }, + SignedInvalid { reason: String }, + Unsigned, + ChecksumMatch, + ChecksumMismatch, + NotChecked, + } + pub fn check_embedded_signature(path: &Path) -> VerificationStatus + pub fn verify_sha256(path: &Path, expected: &str) -> VerificationStatus + ``` +- `check_embedded_signature`: runs `unsquashfs -l` to check for `.sha256` or signature files, or uses the AppImage --appimage-signature flag +- Drop dialog: after adding, show verification badge +- Detail view: "Verification: Signed by ..." or "Unsigned - verify manually" +- Manual verification: paste SHA256 hash, compare with computed hash +- New DB column: `verification_status TEXT` on appimages + +--- + +### F19. First-Run Permission Summary + +**Complexity:** Medium (3 hrs) +**Problem:** Users don't know what an AppImage can access before running it. +**Files:** New `src/ui/permission_dialog.rs`, `src/ui/detail_view.rs` + +**Changes:** +- Before first launch of any AppImage (check `launch_events` count = 0): + 1. Show permission dialog: + - "MyApp will run with full access to your files and system" + - Icon + app name + - If `has_firejail()`: offer sandbox options + - "Run without restrictions" (default for now) + - "Run in Firejail sandbox (recommended)" + - "Don't show this again" checkbox (stored per-app in DB) + 2. If user chooses Firejail: set `sandbox_mode = 'firejail'` in DB +- New DB column: `first_run_prompted INTEGER NOT NULL DEFAULT 0` +- Skip dialog if `first_run_prompted = 1` +- Preferences: global toggle to disable first-run prompts + +--- + +### F20. Default App Selector + +**Complexity:** Medium (3 hrs) +**Problem:** Can't set AppImage as default browser, mail client, etc. +**Files:** `src/ui/detail_view.rs`, `src/core/integrator.rs` + +**Changes:** +- Detect app capabilities from categories: + - `WebBrowser` -> can be default web browser + - `Email` -> can be default email client + - `FileManager` -> can be default file manager + - `TerminalEmulator` -> can be default terminal +- Detail view overview tab: "Set as default" section (only shown for applicable categories) +- Setting defaults: + - Browser: `xdg-settings set default-web-browser driftwood-{id}.desktop` + - Email: `xdg-mime default driftwood-{id}.desktop x-scheme-handler/mailto` + - File manager: `xdg-mime default driftwood-{id}.desktop inode/directory` +- Before setting: query current default, store in `system_modifications` with `previous_value` +- Uninstall: restore previous defaults via `undo_all_modifications()` +- Requirements: app must be integrated first (needs .desktop file) + +--- + +### F21. Multi-User / System-Wide Install + +**Complexity:** Significant (4 hrs) +**Problem:** Can't install for all users on a shared computer. +**Files:** `src/core/integrator.rs`, `src/ui/detail_view.rs`, polkit policy + +**Changes:** +- Reuses polkit policy from F15 (add new action `app.driftwood.Driftwood.system-install`) +- "Install system-wide" option in detail view (requires app to be integrated first) +- Flow: + 1. `pkexec cp {appimage} /opt/driftwood-apps/{filename}` + 2. `pkexec chmod 755 /opt/driftwood-apps/{filename}` + 3. Generate system .desktop in `/usr/share/applications/driftwood-{id}.desktop` + 4. `pkexec cp {desktop_file} /usr/share/applications/` + 5. Copy icon to `/usr/share/icons/hicolor/` via pkexec + 6. `pkexec update-desktop-database /usr/share/applications/` +- Register all paths as `system_desktop`, `system_icon`, `system_binary` in system_modifications +- Uninstall system-wide: `pkexec rm` for each tracked path +- DB flag: `system_wide INTEGER NOT NULL DEFAULT 0` + +--- + +### F22. CLI Enhancements + +**Complexity:** Medium (3 hrs) +**Problem:** Missing install-from-URL and update-all commands. +**Files:** `src/cli.rs` + +**New commands:** +- `driftwood install ` - Download from URL, validate, move to ~/Applications, set executable, register, optionally integrate +- `driftwood update --all` - Check all apps, download and apply available updates +- `driftwood autostart --enable/--disable` - Toggle autostart for an AppImage +- `driftwood purge` - Remove ALL system modifications for all managed apps (for Driftwood removal) +- `driftwood verify ` - Check embedded signature or compare SHA256 + +--- + +### F23. Portable Mode / USB Drive Support + +**Complexity:** Major (6-8 hrs) +**Problem:** Can't manage AppImages on removable media. +**Files:** New `src/core/portable.rs`, `src/ui/library_view.rs`, `src/window.rs`, `src/core/database.rs` + +**Changes:** +- New GSettings key: `watch-removable-media` type b, default false +- `portable.rs`: + ```rust + pub fn detect_removable_mounts() -> Vec + pub fn scan_mount_for_appimages(mount: &MountInfo) -> Vec + pub fn is_path_on_removable(path: &Path) -> bool + ``` +- Detection: parse `/proc/mounts` for removable media (type vfat, exfat, ntfs on /media/ or /run/media/) +- Alternative: use `gio::VolumeMonitor` to watch for mount/unmount events +- DB changes: + - New column: `is_portable INTEGER DEFAULT 0` + - New column: `mount_point TEXT` +- Library view: "Portable" filter/section showing apps on removable media +- When drive unmounts: grey out those apps, mark as unavailable +- When drive mounts: re-scan and refresh +- Portable apps skip the "copy to ~/Applications" step - they stay on the drive +- Integration: .desktop files use the full path (may break when unmounted - show warning) + +--- + +### F24. "Similar to..." Recommendations + +**Complexity:** Medium (3 hrs, depends on F26) +**Problem:** No app discovery within the tool. +**Files:** `src/ui/detail_view.rs`, `src/core/database.rs` + +**Changes:** +- Requires catalog data from F26 (catalog_apps table populated) +- New DB query: `find_similar_apps(categories: &[String], exclude_id: i64, limit: i32) -> Vec` +- Matches on shared categories, weighted by specificity +- Detail view overview tab: "You might also like" section at the bottom +- Shows up to 5 catalog apps with icon, name, one-line description +- Click opens catalog detail page (F26) +- Falls back to nothing if catalog is empty + +--- + +### F25. AppImageHub In-App Catalog Browser + +**Complexity:** Major (8-12 hrs) +**Problem:** No way to discover and install new AppImages from within the app. +**Data source:** `https://appimage.github.io/feed.json` (~1500 apps) +**Files:** New `src/ui/catalog_view.rs`, new `src/ui/catalog_detail.rs`, `src/core/database.rs`, `src/window.rs` + +**Architecture:** + +#### Data Layer +- Fetch `feed.json` and parse into `catalog_apps` table (already exists in DB schema) +- Store in `catalog_sources` as source record +- Fields mapped from feed.json: + - `name` -> `name` + - `description` -> `description` + - `categories` -> `categories` (joined with ;) + - `authors[0].name` -> `author` + - `license` -> `license` + - `links` (type=GitHub) -> `repository_url` + - `links` (type=Download) -> `download_url` + - `icons[0]` -> `icon_url` (prefix with `https://appimage.github.io/database/`) + - `screenshots` -> `screenshots` (JSON array) +- Refresh: on first open, then daily if enabled +- New GSettings key: `catalog-last-refreshed` type s, default '' + +#### Catalog Browse View (NavigationPage) +- Header: search bar + category filter chips +- Categories from feed: AudioVideo, Development, Education, Game, Graphics, Network, Office, Science, System, Utility +- Grid of catalog app cards (reuse app_card pattern): + - Icon (fetched from URL, cached locally) + - App name + - Short description (first line) + - Category badge +- Pagination: load 50 at a time, "Load more" button +- Search: filters by name and description (client-side, data is local) + +#### Catalog App Detail (NavigationPage pushed on click) +- App icon (large) +- Name, author, license +- Full description +- Screenshots carousel (if available) +- "Install" button (suggested style) +- Source link (opens GitHub/website in browser) + +#### Install Flow +1. User clicks "Install" +2. Resolve download URL: + - If `download_url` points to GitHub releases page: fetch latest release via GitHub API (`https://api.github.com/repos/{owner}/{repo}/releases/latest`), find .AppImage asset + - If direct link: use as-is +3. Download with progress bar (reqwest or gio file download) +4. Validate: check AppImage magic bytes +5. Move to ~/Applications, set executable +6. Register in DB with `source_url` set +7. Run full analysis pipeline +8. Optionally integrate (based on `auto-integrate` setting) +9. Navigate to the app's detail view in library + +#### Navigation +- Main sidebar/navigation: add "Catalog" entry alongside Dashboard and Library +- Or: floating action button on library view "Browse catalog" + +--- + +## New GSettings Keys Summary + +| Key | Type | Default | Range/Choices | Feature | +|-----|------|---------|---------------|---------| +| `update-check-interval-hours` | i | 24 | 1-168 | F11 | +| `last-update-check` | s | '' | - | F11 | +| `catalog-last-refreshed` | s | '' | - | F25 | +| `watch-removable-media` | b | false | - | F23 | +| `show-first-run-prompt` | b | true | - | F19 | + +## New Database Columns Summary + +| Table | Column | Type | Default | Feature | +|-------|--------|------|---------|---------| +| appimages | previous_version_path | TEXT | NULL | F3 | +| appimages | source_url | TEXT | NULL | F4 | +| appimages | autostart | INTEGER | 0 | F8 | +| appimages | startup_wm_class | TEXT | NULL | F17 | +| appimages | verification_status | TEXT | NULL | F18 | +| appimages | first_run_prompted | INTEGER | 0 | F19 | +| appimages | system_wide | INTEGER | 0 | F21 | +| appimages | is_portable | INTEGER | 0 | F23 | +| appimages | mount_point | TEXT | NULL | F23 | + +## New Files Summary + +| File | Feature | Purpose | +|------|---------|---------| +| `src/ui/fuse_wizard.rs` | F15 | FUSE installation wizard dialog | +| `src/ui/batch_update_dialog.rs` | F12 | Batch update progress dialog | +| `src/ui/permission_dialog.rs` | F19 | First-run permission summary | +| `src/ui/catalog_view.rs` | F25 | Catalog browse/search page | +| `src/ui/catalog_detail.rs` | F25 | Catalog app detail page | +| `src/core/verification.rs` | F18 | Signature and checksum verification | +| `src/core/portable.rs` | F23 | Removable media detection and management | +| `data/app.driftwood.Driftwood.policy` | F15, F21 | Polkit policy for privileged operations | + +## Implementation Order + +The features are numbered F1-F25 in order of complexity. Implement sequentially - some later features depend on earlier ones: + +- F25 (Catalog) depends on nothing but is largest +- F24 (Similar to) depends on F25 +- F12 (Update All) depends on F11 (Background checks) +- F13 (Full Uninstall) depends on system_modifications table (core architecture) +- F20 (Default App) depends on F16 (MIME associations) + +**Critical path:** Core architecture (system_modifications) -> F1-F7 quick wins -> F8-F14 medium features -> F15-F22 significant features -> F23-F25 major features