mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-03 16:41:26 +01:00
The Sync page gives each folder one card with its state, a Synced / Online only switch and a menu for the rest; switching either way asks first and the download prompt shows the free space on the disk. Enabling, disabling and restarting the Proton Drive service ran systemctl on GTK's main thread and froze the window. They now run in the background, and every Retry button goes through one shared restart helper. Ten icons are bundled under resources/icons for glyphs no stock theme has or that read ambiguously (sync, share, people, link, menu, map and so on). A new test fails when the app or search prompt asks for a symbolic icon that is neither bundled nor on a list checked against Adwaita 50, libadwaita and GTK, or when a bundled icon is missing from the resource manifest. The tray's sync icon, which Adwaita 50 no longer ships, is now view-refresh. Wording follows one glossary, recorded in docs/TRANSLATING.md: "Proton Drive service" instead of "mount service" or "daemon", "Online only" instead of "On-demand", "Proton Drive folder" instead of "mountpoint". Dialog headings and buttons use title case and page titles match the sidebar. Dates go through one formatter (app/dates.rs) and sizes through i18n::human_bytes, and the cache label is translated. The status tick stops while the window is hidden or minimized and runs every fifth tick while it is unfocused, refreshing as soon as it gains focus. "Sync complete" is announced only for large batches and only when the window isn't focused. Smaller fixes: Empty Trash hides when there is nothing to empty, copy buttons confirm with a check mark, the sidebar status spins until the first report, and Photos moves Import and Find Duplicates into a menu beside a labelled Select toggle. All catalogs are updated and complete.
355 lines
23 KiB
Markdown
355 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 / Online only | — |
|
||
| Where My files appears on disk | Proton Drive folder — never "mountpoint" in UI | — |
|
||
| 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).
|