From 79519c500a628dd19b835bbd360e823dd6f22f29 Mon Sep 17 00:00:00 2001 From: lashman Date: Sun, 1 Mar 2026 00:47:14 +0200 Subject: [PATCH] Add design doc for tags, export/import, and changelog improvements --- ...2026-03-01-final-ux-improvements-design.md | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 docs/plans/2026-03-01-final-ux-improvements-design.md diff --git a/docs/plans/2026-03-01-final-ux-improvements-design.md b/docs/plans/2026-03-01-final-ux-improvements-design.md new file mode 100644 index 0000000..e6b985c --- /dev/null +++ b/docs/plans/2026-03-01-final-ux-improvements-design.md @@ -0,0 +1,166 @@ +# Final UX Improvements Design: Tags, Export/Import, Changelog + +## Overview + +Three remaining UX improvements from the 30-item enhancement list: + +- **#12** App grouping with tags + grouped library sections +- **#13** Unified export/import (shared core for CLI + GUI) with extended fields +- **#14** Bridge changelog gap (GitHub release notes -> release_history) + updates view preview + +## #12: Tags + Grouped Library Sections + +### Current State + +- `tags: Option` field exists in `AppImageRecord` (database.rs:50) +- `update_tags(id, tags)` DB method exists (database.rs:2069) +- No UI for viewing, editing, or filtering by tags +- Library view is flat (grid or list mode, no sections) + +### Design + +**Detail view - tag editor:** +- Add a "Tags" row in the detail view info section +- Display existing tags as pill-shaped chips with "x" remove buttons +- A "+" button at the end opens an inline text entry to add a new tag +- Tags stored as comma-separated string in the existing `tags` column +- On add/remove, call `db.update_tags()` immediately (no save button) + +**Library view - tag filter chips:** +- Add a horizontal scrollable chip bar at the top of the library view (same pattern as catalog category chips) +- "All" chip selected by default, plus one chip per unique tag found across all apps +- Selecting a tag filters the view to only apps with that tag +- Chip bar is only visible when at least one app has tags + +**Library view - grouped sections:** +- When no search is active and no tag filter is selected, group apps by their first tag under collapsible section headers +- Apps with no tags go under an "Uncategorized" section at the bottom +- Sort dropdown still works within each group +- In search mode or tag-filter mode, grouping is disabled (flat list) + +**Data model:** No schema changes. Comma-separated tags in existing column. + +### Files Modified + +| File | Changes | +|------|---------| +| `src/ui/detail_view.rs` | Add tag editor chips row in info section | +| `src/ui/library_view.rs` | Add tag filter chip bar, grouped section headers | +| `src/core/database.rs` | Add `get_all_tags()` query to collect distinct tags | + +## #13: Unified Export/Import (CLI + GUI) + +### Current State + +- CLI-only export/import in `cli.rs` (lines 887-1000+) +- JSON format v1 with limited fields: path, app_name, app_version, integrated, notes, categories +- No GUI file picker dialogs for export/import +- No shared module - logic is inline in CLI handlers + +### Design + +**Shared core module: `src/core/backup.rs`** + +New module with two public functions: + +``` +pub fn export_app_list(db: &Database, path: &Path) -> Result +pub fn import_app_list(db: &Database, path: &Path) -> Result +``` + +Where `ImportResult` contains: +- `matched: usize` - apps found and metadata merged +- `missing: Vec` - app names/paths not found on disk + +**JSON format v2:** + +```json +{ + "version": 2, + "exported_at": "2026-03-01T10:00:00Z", + "appimages": [ + { + "path": "/home/user/Applications/MyApp.AppImage", + "app_name": "MyApp", + "app_version": "1.0.0", + "integrated": true, + "notes": "user notes", + "categories": "Graphics;Utility;", + "tags": "work,design", + "pinned": false, + "launch_args": "--no-sandbox", + "sandbox_mode": "permissive", + "autostart": false + } + ] +} +``` + +Graceful v1 handling: missing fields default to None (not overwritten on import). + +**Import merge semantics:** +- Match apps by path (exact match) +- For matched apps, merge each field: only overwrite if the import value is non-empty/non-null AND the existing value is empty/null (preserve user's current data) +- Tags: merge (union of existing + imported tags, no duplicates) +- For unmatched paths: collect into `missing` list + +**CLI (cli.rs):** +- `cmd_export()` and `cmd_import()` become thin wrappers calling `backup::export_app_list()` / `backup::import_app_list()` +- Same CLI flags and output messages + +**GUI (window.rs):** +- Add "Export app list" and "Import app list" items to the hamburger menu +- Export: `gtk::FileDialog` save picker, default filename `driftwood-apps.json`, calls `backup::export_app_list()`, toast on success +- Import: `gtk::FileDialog` open picker with `.json` filter, calls `backup::import_app_list()`, toast showing "Imported N apps" + dialog listing missing apps if any +- Refresh library view after import + +### Files Modified + +| File | Changes | +|------|---------| +| `src/core/backup.rs` | New module: export/import logic, JSON v2 format | +| `src/core/mod.rs` | Add `pub mod backup;` | +| `src/cli.rs` | Refactor cmd_export/cmd_import to call backup module | +| `src/window.rs` | Add menu items + file picker dialogs for export/import | + +## #14: Bridge Changelog Gap + Updates View Preview + +### Current State + +- `release_history: Option` field exists in AppImageRecord (JSON array) +- Detail view already renders release history when data exists (detail_view.rs:760-820) +- AppStream parsing populates release_history for local metainfo (rare) +- GitHub enrichment fetches release info but does NOT write to release_history +- Updates view shows update-available cards but no changelog preview + +### Design + +**Enrichment bridge (github_enrichment.rs):** +- After `enrich_app_release_info()` fetches the latest release, also fetch up to 10 recent releases from the GitHub releases API +- Convert each release to `ReleaseInfo { version, date, description }` format +- Serialize as JSON array and write to `release_history` column +- Only update if the field is currently empty (don't overwrite AppStream data which may be more authoritative) +- The existing detail view Release History section will then automatically display this data for all GitHub-enriched apps + +**New DB method:** +- `update_release_history(id: i64, history_json: &str)` - simple UPDATE on the release_history column + +**Updates view changelog preview (updates_view.rs):** +- Each update card gets an `adw::ExpanderRow` "What's new" section below the existing content +- If `release_history` is populated, find the entry matching `latest_version` and show its description +- If no matching entry or no release_history data: show "Release notes not available" in dim text +- Collapsed by default to keep the view compact + +### Files Modified + +| File | Changes | +|------|---------| +| `src/core/github_enrichment.rs` | Fetch recent releases, write to release_history | +| `src/core/database.rs` | Add `update_release_history()` method | +| `src/ui/updates_view.rs` | Add expandable "What's new" section to update cards | + +## Implementation Order + +1. **#14 first** - Smallest scope, self-contained enrichment + UI change +2. **#12 second** - Tags are self-contained, no dependency on other features +3. **#13 last** - Export should include tags (depends on #12 being functional)