mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-03 16:41:26 +01:00
User-visible strings, doc comments and docs now say "Favorite" and "color", matching the `pdfs favorite` command; UI_UX_PLAN.md §8 records the choice. The changelog's 1.11.x and Unreleased entries said schema 30, but V31 (`photos.resolved_at`) shipped in 1.11.0. Adds the missing release links for 1.10.0 to 1.11.1.
354 lines
23 KiB
Markdown
354 lines
23 KiB
Markdown
# UI/UX audit and improvement plan
|
||
|
||
Audit date: 2026-09-23. Scope: `pdfs-gui` (all pages, dialogs, photo viewer), `pdfs-tray`, and the
|
||
backend capabilities the UI would need. File references are to `crates/pdfs-gui/src/app/` unless
|
||
stated otherwise.
|
||
|
||
## 1. Summary
|
||
|
||
The client is functionally rich but the UI is built page by page, and it shows:
|
||
|
||
- **No sync control surface.** There is no way to pause or resume sync, no list of what is stuck or
|
||
failing, and no conflict overview. The backend has no pause at all, no per-op listing, and no
|
||
per-file conflict record (conflicts exist only as `(sync-conflict <ts>)` files plus activity rows).
|
||
The GUI reads `online`, `pending_uploads` and `pending_changes` from `Status` and ignores
|
||
`failing_ops`, `failing_error`, `parked_uploads` and `staged_*`.
|
||
- **Information architecture is scattered.** Live transfers are a group on *Settings* while the
|
||
*Activity* page is history only. *Locations* and *Computers* point at each other. Import lives under
|
||
Settings but is launched from Gallery and returns to Settings. Sign-out, quota, mount status,
|
||
pinned files, import and developer info all share the Settings page.
|
||
- **Chrome is non-standard.** Every page draws its own title and toolbar row under an almost empty
|
||
`AdwHeaderBar` whose title is always "Proton Drive". Two stacked bars, wasted height, no current
|
||
folder in the title.
|
||
- **Icons and wording drift.** Upload uses `document-send-symbolic` (reads as "send"), offline uses
|
||
stars (reads as "favorite"), trash icons mean "stop syncing", "remove device" and "remove
|
||
bookmark". "Keep offline" / "Unpin" / "Remove offline copy" / "Available offline" are one concept.
|
||
"Computers" vs "device", "Gallery" vs "Photos", "mount service" vs "daemon" vs "Connect".
|
||
- **Theming is hardcoded.** `PROTON_PURPLE` overrides the system accent; badge, viewer and tile
|
||
colours are hex/rgba literals and ignore light, dark and high-contrast modes.
|
||
- **Several visible bugs** (section 9).
|
||
|
||
## 2. Design principles
|
||
|
||
1. **Follow GNOME HIG and current libadwaita.** The system has libadwaita 1.9; the crate is pinned to
|
||
feature `v1_5`. Raise it to at least `v1_7` to get `adw::Spinner`, `ToggleGroup`,
|
||
`InlineViewSwitcher`, `ButtonRow`, `WrapBox`, `BottomSheet`, and (1.8) `ShortcutsDialog`.
|
||
2. **Sync state is first class.** The user must always see: up to date / syncing / paused / offline /
|
||
needs attention, and be one click from pausing or from the list of problems.
|
||
3. **One term, one icon, one place** per concept (glossary in section 8).
|
||
4. **Secondary and destructive actions go into menus.** Rows get at most one visible suffix action
|
||
plus a `view-more-symbolic` menu. Destructive actions are red inside the menu and in the confirm
|
||
dialog, not as filled buttons in headers.
|
||
5. **Never block on the network visibly.** Keep old content on screen while reloading, show a header
|
||
spinner, never flash the whole page to a "Loading…" status page.
|
||
6. **Respect the system.** Use the system accent by default (brand purple stays on login, app icon
|
||
and an opt-in toggle). Use named colours (`@success_color`, `@warning_color`, `@error_color`).
|
||
|
||
## 3. New information architecture
|
||
|
||
### Sidebar
|
||
|
||
```
|
||
Proton Drive (header: app name)
|
||
My files
|
||
Photos
|
||
Shared with me (badge: pending invitations)
|
||
Shared by me
|
||
Computers
|
||
Trash
|
||
─────────────
|
||
Sync (badge: issue count, icon reflects state)
|
||
───────────── footer
|
||
[●] Up to date · Pause ← sync status strip, click opens Sync
|
||
▓▓▓░░░ 269 GiB of 1.6 TiB ← quota
|
||
(avatar) user@proton.me ⋮ ← account menu: Preferences, Sign out…
|
||
```
|
||
|
||
- **Remove** sidebar rows: *Locations* (merged into Sync), *Activity* (becomes the Sync "History"
|
||
view), *Settings* (becomes an `AdwPreferencesDialog`, `Ctrl+,`).
|
||
- **Rename** *Gallery* to *Photos* and *Shared* to *Shared by me*.
|
||
- Primary menu: Preferences, Keyboard Shortcuts, About. The About dialog's `debug_info` gets app
|
||
version, user agent, daemon status and config path (replacing the "Developer" group).
|
||
|
||
### Sync page (new, replaces Locations + Activity + Settings→Activity)
|
||
|
||
Top: an `AdwBanner`-style status card with state icon, one-line summary ("Syncing 12 files · 3.4
|
||
MB/s", "Paused until you resume", "2 files need attention") and one primary button: **Pause** /
|
||
**Resume** (split button with "Pause for 1 hour / until tomorrow / until resumed").
|
||
|
||
Below, an `InlineViewSwitcher` with four views:
|
||
|
||
1. **Issues** (default when non-empty): conflicts, failing uploads (`last_error`, attempts, next
|
||
retry), folders in `error`/`conflict` state, parked uploads older than N minutes. Each row has a
|
||
primary fix action (Resolve…, Retry now) and a menu (Show in folder, Discard change…).
|
||
2. **Transfers**: in-flight uploads and downloads with progress, speed, cancel; background jobs
|
||
(thumbnail build, Takeout import, re-date) with their own cancel.
|
||
3. **Folders**: the old Locations list. Row: path, mode as a dropdown suffix (Synced / On-demand, with
|
||
confirm when going on-demand deletes local copies), state subtitle, `Sync now` and a menu (Open,
|
||
Change location…, Pause this folder, Stop syncing…). "Add folder…" as a `ButtonRow` at the end.
|
||
The primary mount appears first and is not removable.
|
||
4. **History**: the old Activity feed, with a kind filter, failures in `@error_color`, rows
|
||
activatable (open or reveal the target), and "Clear history".
|
||
|
||
### Preferences dialog (slimmed Settings)
|
||
|
||
| Page | Contents |
|
||
|---|---|
|
||
| General | Start on login; Mount location (path + Change…); Notifications (on sync issues, on import done) |
|
||
| Storage | Cache usage bar; Cache budget (spin row, in GB, "Unlimited" switch instead of magic 0); Clear cache…; Offline files (count + "Manage" → Sync › Folders filter or a sub-page) |
|
||
| Sync | Ignore patterns (editable list); Conflict clean-up: Off / Report / Remove identical copies (`conflict_sweep`); later: bandwidth limits |
|
||
| Advanced | Open-with rules, search prompt config (only if kept in GUI) |
|
||
|
||
Moved out: account row and Sign out (sidebar account menu, with confirm), quota (sidebar footer),
|
||
mount status (sidebar status strip), live transfers (Sync), Google Photos import (Photos page menu),
|
||
version/user agent (About → Debug info).
|
||
|
||
## 4. Backend work required
|
||
|
||
Everything below is new; nothing like it exists today.
|
||
|
||
### B-1 Pause and resume (global, then per folder)
|
||
|
||
- `Request::SetSyncPaused { paused: bool, until: Option<i64> }`, persisted (config key
|
||
`sync_paused_until` or a DB setting) so a pause survives restarts; `Response::Status` gains
|
||
`paused` and `paused_until`.
|
||
- Drain: `run_pending_drain` (`pdfs-fuse/src/drain.rs:265`) already releases claimed ops while
|
||
offline; gate on `paused` at the same point and reuse the wake path.
|
||
- Sync engine: add `SyncMsg::Pause/Resume` or gate `reconcile_all` / `reconcile_folder`; the 120 s
|
||
poll and watcher keep recording, they just do not apply.
|
||
- FUSE reads must keep working while paused (on-demand hydration is a read, not a sync); document that
|
||
pause means "no uploads, no mirror reconcile".
|
||
- Per folder later: `sync_folder.paused` column, skipped in `reconcile_all`.
|
||
- Bandwidth limits later: token bucket in `CountingReader` / `CountingWriter`
|
||
(`pdfs-fuse/src/transfers.rs`).
|
||
|
||
### B-2 Pending ops and transfers
|
||
|
||
- `Request::ListPendingOps` → id, kind, path/name, attempts, `last_error` (classified as
|
||
`ErrorKind`), `next_attempt_at`, parked flag, staged bytes.
|
||
- `Request::RetryOp { id }` (set `next_attempt_at = now`, wake drain) and `RetryAllFailing`.
|
||
- `Request::DiscardOp { id }` for a create/revision whose bytes should be dropped. Must save the
|
||
staged blob to a local recovery folder first; never silent data loss (see `docs/RECOVERY.md`).
|
||
- `TransferItem` gains a stable id and `Request::CancelTransfer { id }`.
|
||
- Per-folder last error message on `SyncFolderInfo` (today the text only exists in the activity feed).
|
||
|
||
### B-3 Conflicts
|
||
|
||
- `Request::ListConflicts`: walk nodes whose name parses with `sweep.rs` `conflict_base_name`, for
|
||
the mount and every mirror folder. Return copy path, original path (if it exists), both sizes and
|
||
mtimes, whether content is identical (size + `content_sha1`, as the sweep already proves), and
|
||
which side is local vs remote when known.
|
||
- Optionally a `conflict` table written by `keep_as_conflict_copy` / `preserve_conflict_copy` so the
|
||
origin (drain vs mirror, device) is recorded instead of inferred from names.
|
||
- `Request::ResolveConflict { copy, keep: Original | Copy | Both }`:
|
||
- Original: trash the copy (re-verify like the enforcing sweep does).
|
||
- Copy: upload the copy's content as a new revision of the original, then trash the copy (the old
|
||
content stays in version history, so this is reversible).
|
||
- Both: rename the copy to a user-chosen name, drop the `(sync-conflict …)` stamp.
|
||
- Activity rows for each resolution.
|
||
|
||
## 5. Shell and visual polish (all pages)
|
||
|
||
- **Per-page header bars.** Give each stack page its own `adw::ToolbarView` + `adw::HeaderBar` (or
|
||
inject page widgets into the shared bar on page switch). Title = page name, or the current folder
|
||
in My files. Page actions move into the header; the extra toolbar row disappears.
|
||
- **One page skeleton.** Same margins everywhere (today 12 vs 18), same `Clamp` rule (lists that are
|
||
settings-like clamp at 720, file/photo grids are full width), same status pages (`compact`,
|
||
`vexpand`, crossfade), same Retry semantics (Retry reloads; "Start Proton Drive" is a separate,
|
||
explicitly labelled button when the service is down).
|
||
- **Loading.** `adw::Spinner` in the header; keep the old model until the new one lands; status page
|
||
only on first load.
|
||
- **Theme.** Drop the global `accent_*` override; offer "Use Proton purple accent" in Preferences.
|
||
Replace hex colours with named colours; test light, dark and high contrast.
|
||
- **Move the 70-line CSS string** in `main.rs:232` to `resources/style.css` in the gresource
|
||
(`style-dark.css` if needed), so it can be edited and linted.
|
||
- **Shortcuts.** Replace the hand-rolled cheatsheet with `adw::ShortcutsDialog` (or
|
||
`GtkShortcutsWindow` before 1.8) listing all bindings, including F5/Ctrl+R and the viewer keys.
|
||
- **Toasts vs dialogs.** Dialogs report their own errors inline (versions, share, add bookmark); a
|
||
toast behind a modal is invisible. Pluralise properly (`ngettext`-ready helpers, no "item(s)").
|
||
- **i18n groundwork.** Wrap user strings in a `gettext!`-style macro now, even without translations,
|
||
so later localisation is mechanical.
|
||
|
||
## 6. Page-by-page plan
|
||
|
||
### My files
|
||
|
||
Header: `[‹ ›] Path bar (breadcrumbs, scroll to end, Ctrl+L to edit) … [search] [view ▾] [+ New ▾]`
|
||
|
||
- **New ▾** (`list-add-symbolic`, suggested): New folder (Ctrl+Shift+N), Upload files (Ctrl+U),
|
||
Upload folder. Replaces three separate icons, fixes the "send" icon.
|
||
- **View ▾** split button: Grid/List (Ctrl+1/2), Sort by Name/Size/Modified, ascending/descending,
|
||
Folders first, zoom slider. Persist choice (config or GSettings).
|
||
- **Folder ⋮ menu** (in header or background context menu): Refresh, Build thumbnails, Make folder
|
||
available offline, Open in file manager, Share folder…. Build thumbnails leaves the main toolbar.
|
||
- **Navigation.** Back/forward history (Alt+←/→), Up (Alt+↑); searching then pressing Back clears
|
||
the search consistently.
|
||
- **Context menus** as `gtk::PopoverMenu` from `gio::Menu` + actions: keyboard navigable, Menu /
|
||
Shift+F10, same action set as details pane. Add a background menu. Order: Open, Open with…, Play
|
||
(media) · Available offline (check item) · Share…, Copy link · Rename…, Move to…, Versions… ·
|
||
Move to Trash (destructive).
|
||
- **Offline state.** One concept "Available offline", toggle semantics. Badges: cloud-only = no
|
||
badge or subtle `cloud-outline`; downloaded = check; available offline = pin/`folder-download`
|
||
style filled icon. Use named colours. Folders can be made available offline too, or the toggle is
|
||
hidden for them (fix `details.rs:259`).
|
||
- **List view.** Sortable, resizable columns: Name, Status, Size, Modified (date + time), and
|
||
Location for search results. Rubberband selection. One selection model shared between views.
|
||
- **Details pane.** On wide windows a real side pane (breakpoint, not overlay); header bar with
|
||
close; real thumbnail; sections: Info, Offline, Sharing (people count, link), Versions (latest 3 +
|
||
"All versions…"). Same actions as the context menu.
|
||
- **Selection bar** (≥1 selected, not ≥2): count + size, Available offline, Move to…, Share (single),
|
||
Move to Trash; Select all (Ctrl+A). Drop the confirm dialog for trash since Undo exists (trash is
|
||
reversible), or keep confirm and drop Undo — not both.
|
||
- **Drag and drop.** Drop external files/folders to upload (reuse Takeout's `FileList` target), drag
|
||
the whole selection, highlight drop targets, breadcrumbs accept drops, Undo toast on move.
|
||
- **Move to…** a folder-picker dialog (tree/list of Drive folders) instead of a free-text path.
|
||
- **Rename** pre-selects the stem, not the extension; drop the redundant "Rename “x”." body.
|
||
- **Search.** Scope toggle (This folder / Everywhere), "Showing first 200 results" note, Location
|
||
column, correct cached badges (search hits hardcode `cached: false`).
|
||
- **Upload feedback.** Toast "Uploading 3 items" with "View" → Sync › Transfers.
|
||
|
||
### Photos (was Gallery)
|
||
|
||
- Header: title "Photos", `InlineViewSwitcher` Timeline | Albums in the header, Select toggle,
|
||
`+ Upload` (fix: use `adw::ButtonContent`, today `.icon_name()` overwrites `.label()` at
|
||
`photos.rs:426-432`), ⋮ menu: Import from Google Photos…, Refresh, Rebuild library.
|
||
- Filters: a single filter dropdown/`ToggleGroup` (All, Photos, Videos, RAW, Favorites) instead of
|
||
two segmented rows where "Photos" appears twice. Counts follow the active filter.
|
||
- **Month scrubber** on the right edge that *jumps* (not filters); the month dropdown becomes a real
|
||
"Filter by date" if kept.
|
||
- Infinite scroll with prefetch (no "Load more" pill; page size ≥ 200; also trigger when the first
|
||
page does not fill the viewport).
|
||
- Tiles: `ContentFit::Cover` (crop) in justified rows, known aspect ratio from metadata to prevent
|
||
reflow, placeholder as a tinted card of the final size, subtle hover (no 1.06 scale with heavy
|
||
shadow on a 2 px gap grid), last row justified up to a max stretch.
|
||
- Context menu on tiles: Open, Favorite, Add to album, Download/Save copy, Show in My files, Move to
|
||
Trash. Selection: Shift range, Ctrl+A, bulk Favorite, Add to album, Download.
|
||
- Empty states per filter ("No favorites yet", "No videos in June 2024") instead of the global
|
||
"No photos yet" with upload buttons.
|
||
- Import: runs show a banner on Photos ("Importing from Google Photos — 1,204 of 5,000 · View") and
|
||
Back returns to where the user came from.
|
||
- **Albums:** Create, rename, delete, remove from album (not trash). Fix state bugs: filter row
|
||
hidden on the album grid, subtitle count, scroll position kept when switching back.
|
||
- **Viewer:** `adw::Window` (or full-window overlay), controls auto-hide after 2 s idle, zoom/pan
|
||
(pinch, Ctrl+scroll, double-click), info panel beside the image instead of over the top bar,
|
||
pages beyond the loaded 60, counter against the real total, videos play inline with
|
||
`gtk::Video`, trash with the same confirm/undo rule as the grid, real filename in "Save a copy",
|
||
success toast, "Open with…" app chooser, favorite tooltip reflects state, readable dates.
|
||
|
||
### Shared with me / Shared by me
|
||
|
||
- Shared with me: header title shows the drill-down path, with back; invitations as a banner at
|
||
the top ("2 invitations · Review") opening a list, Reject confirmed; bookmarks become a separate
|
||
view or section via a switcher. Rows: Open, Save to My files, Available offline where supported.
|
||
- Shared by me: rows activatable (open), suffix "Manage" becomes a menu with Copy link, Manage
|
||
access…, Stop sharing….
|
||
- Share dialog: inline validation, loading spinner, confirm "Remove access" and "Delete link",
|
||
human role names, link expiry.
|
||
|
||
### Computers
|
||
|
||
- Show the real device name for "This computer" with rename inline.
|
||
- Other computers: one suffix menu (Rename, Use this identity…, Remove…). Remove uses a destructive
|
||
confirm that requires typing the device name since it deletes backups.
|
||
- "Restore folders" becomes a per-device action ("Restore to this computer…") with an inline-empty
|
||
state when nothing is restorable, and moves the "Add folder" responsibility fully to Sync.
|
||
- Terminology: "computer" everywhere in UI copy.
|
||
|
||
### Trash
|
||
|
||
- Same skeleton as other list pages (clamped `boxed-list`), sort by deleted date, multi-select with
|
||
Restore / Delete permanently, "Empty Trash…" moved into a header ⋮ menu or kept as a flat button
|
||
with a destructive confirm, proper plurals, Retry on error.
|
||
|
||
### Login
|
||
|
||
- Human error messages mapped from error kinds, spinner in the button, "Create account" and "Forgot
|
||
password" links, recovery-code hint in 2FA, cancelling CAPTCHA returns to the form with a message.
|
||
|
||
## 7. Tray
|
||
|
||
- Symbolic icon variants: `pdfs-synced`, `pdfs-syncing`, `pdfs-paused`, `pdfs-offline`,
|
||
`pdfs-attention`; tooltip with the one-line state; `NeedsAttention` status for issues.
|
||
- Menu: status line · "N issues — View" (opens Sync › Issues) · Pause sync ▸ (1 h, until tomorrow,
|
||
until resumed) / Resume · Open Proton Drive · Open folder · "Sign in…" when logged out (no
|
||
"Connect") · Stop Proton Drive (confirm) · "Hide tray icon" instead of "Quit" (daemon keeps
|
||
running and the label must not suggest otherwise).
|
||
|
||
## 8. Glossary (one term, one icon)
|
||
|
||
| Concept | UI term | Icon |
|
||
|---|---|---|
|
||
| Root of Drive | My files | `folder-symbolic` |
|
||
| Background service | Proton Drive (running / stopped) — never "daemon" or "mount service" in UI | — |
|
||
| Cloud-only file | Online only | none / `cloud-outline` |
|
||
| Pinned file | Available offline | pin/download-filled custom icon, not stars |
|
||
| Upload | Upload files / Upload folder | `document-upload`-style arrow up, custom pair |
|
||
| Folder sync mode | Synced / On-demand | — |
|
||
| Stop syncing a folder | Stop syncing… | `media-playback-stop-symbolic` in a menu, not trash |
|
||
| Remove a computer | Remove computer… | destructive, in menu |
|
||
| Conflict copy | Conflict | `dialog-warning-symbolic` + warning colour |
|
||
| Failure | Failed / Needs attention | `dialog-error-symbolic` + error colour |
|
||
| Pause | Pause sync / Resume sync | `media-playback-pause-symbolic` / `-start-` |
|
||
| Photos section | Photos | `image-x-generic-symbolic` |
|
||
| Sharing | Shared by me / Shared with me | distinct icons (`emblem-shared` / `folder-publicshare`) |
|
||
|
||
Spelling: US English throughout ("Favorite", "color"), matching the `pdfs favorite` command and
|
||
Proton's own apps. Title Case for
|
||
dialog headings and buttons in dialogs, Sentence case for menu items and rows (HIG).
|
||
|
||
## 9. Bugs found during the audit (quick wins)
|
||
|
||
1. Photos Upload pill loses its label (`photos.rs:426-432`); same for viewer "Show on map"
|
||
(`photo_viewer.rs:594-598`).
|
||
2. Takeout Back always goes to Settings, sidebar highlights Settings when opened from Photos
|
||
(`main.rs:779`, `takeout.rs:131-136`).
|
||
3. Thumbnail-build completion message computed then hidden (`browser.rs:1632-1667`).
|
||
4. Search results show cached files as cloud-only (`browser.rs:2344`).
|
||
5. Details "Available offline" switch shown for folders (`details.rs:259`).
|
||
6. Grid and list keep separate selections; details pane can describe a hidden-view item.
|
||
7. F2 silently does nothing with multiple items selected.
|
||
8. Filter changes on the Albums grid swap to the timeline while the Albums toggle stays active
|
||
(`photos.rs:958-997`).
|
||
9. Subtitle "60 photos" next to "All 1,416" (loaded count vs total, `photos.rs:2007-2015`); viewer
|
||
counter "12 of 60".
|
||
10. Viewer info panel covers Close/Trash/Save (`photo_viewer.rs:588` vs `648`).
|
||
11. Locations mode switch and removal reload Computers, not Locations; a rejected mode switch stays
|
||
flipped until the next tick (`devices.rs:612`, `devices.rs:809`, `locations.rs:374`).
|
||
12. Going on-demand deletes local copies without confirmation.
|
||
13. Retry on Not-connected silently restarts the systemd unit; Locations' Retry does not.
|
||
14. Toasts from Versions/Share dialogs land behind the modal.
|
||
15. "Emptied trash —" dangling dash, "item(s)", "archive(s)", raw revision id as version title,
|
||
raw lowercase link role.
|
||
16. Bulk context menu shows a separator directly under the header when a folder is selected.
|
||
17. Sign out, Reject invitation, Remove access, Delete link, tray Disconnect: no confirmation.
|
||
18. Stale doc comments (`devices.rs:46-48`, `locations.rs:329-331`, `albums.rs:4`,
|
||
`browser.rs:1155`).
|
||
|
||
## 10. Phased roadmap
|
||
|
||
Each phase is shippable on its own. Estimates are rough, for one developer.
|
||
|
||
| Phase | Content | Crates | Size |
|
||
|---|---|---|---|
|
||
| 0 Quick wins | Section 9 bugs, glossary wording, icon swaps (upload, offline, stop syncing), confirmations, plurals | gui, tray | S (2–3 days) |
|
||
| 1 Shell | libadwaita feature bump, per-page header bars, sidebar restructure + footer (status, quota, account), Preferences dialog, About debug info, theme/colour cleanup, CSS to gresource, shortcuts dialog | gui | M (1 week) |
|
||
| 2 Sync hub + pause | B-1 global pause, B-2 list/retry pending ops, Sync page (status card, Issues, Transfers, Folders, History), tray states and pause menu, GUI uses `failing_*` / `parked_*` | core, fuse, gui, tray, cli | L (1.5–2 weeks) |
|
||
| 3 Conflicts | B-3 list + resolve, Issues rows with a compare/resolve dialog (both versions side by side: size, date, device, preview for images/text), CLI `pdfs conflicts` | core, fuse, gui, cli | M–L |
|
||
| 4 File browser | Header layout, New menu, sort/persisted view, PopoverMenu context menus, details side pane, DnD upload, folder picker for Move, search scope | gui | L |
|
||
| 5 Photos | Rename, header switcher, filters, infinite scroll + scrubber, tile layout, context menu, album management, viewer rewrite (auto-hide, zoom, inline video) | gui (+ core for album mutations) | L |
|
||
| 6 Rest | Shared pages, Computers, Trash, Login, per-folder pause, bandwidth limits, i18n wrapping | all | M |
|
||
|
||
Phase 2 is where the user-reported gaps (pause, resume, stuck items) get closed; phase 3 closes the
|
||
conflict overview. Phases 0–1 fix most of the "unpolished" impression cheaply and should come first
|
||
because later phases build on the new header/page skeleton.
|
||
|
||
### Verification per phase
|
||
|
||
- `cargo fmt --all -- --check`, `cargo clippy --workspace --all-targets -- -D warnings`,
|
||
`cargo test --workspace`.
|
||
- Backend phases: DB migration tests in `crates/pdfs-core/src/db/tests.rs` (paused state, conflict
|
||
table if added), drain tests proving no op is claimed while paused, resolve-conflict tests proving
|
||
the losing side stays recoverable (trash or version history).
|
||
- GUI phases: before/after screenshots in `images/` (light and dark), keyboard-only walkthrough of
|
||
each page, narrow-window check (sidebar collapse breakpoint).
|