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
This commit is contained in:
lashman
2026-02-27 23:24:34 +02:00
parent 830c3cad9d
commit 5ef3789c7d

View File

@@ -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<i64>` 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<PathBuf>
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<PathBuf>`
- 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<String>,
pub version_id: String,
}
pub fn detect_distro() -> Option<DistroInfo> // parses /etc/os-release
pub fn get_fuse_install_command(distro: &DistroInfo) -> Option<String>
```
- 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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policyconfig PUBLIC
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
"https://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
<policyconfig>
<action id="app.driftwood.Driftwood.install-fuse">
<description>Install FUSE library for AppImage support</description>
<message>Authentication is required to install the FUSE library</message>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin</allow_active>
</defaults>
</action>
</policyconfig>
```
- 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<String>` - 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 <url>` - 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 <path> --enable/--disable` - Toggle autostart for an AppImage
- `driftwood purge` - Remove ALL system modifications for all managed apps (for Driftwood removal)
- `driftwood verify <path>` - 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<MountInfo>
pub fn scan_mount_for_appimages(mount: &MountInfo) -> Vec<DiscoveredAppImage>
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<CatalogApp>`
- 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