From 91a5f21340263cb81aba6ab79a28807916cd6c13 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Wed, 23 Sep 2026 03:01:27 +0200 Subject: [PATCH] docs(core,fuse,gui,cli): US spelling and correct schema lines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- crates/pdfs-cli/src/main.rs | 6 ++-- crates/pdfs-core/src/control.rs | 10 +++--- crates/pdfs-core/src/db/albums.rs | 4 +-- crates/pdfs-core/src/db/migrations.rs | 4 +-- crates/pdfs-core/src/db/photos.rs | 4 +-- crates/pdfs-core/src/db/tests.rs | 2 +- crates/pdfs-core/src/takeout.rs | 2 +- crates/pdfs-fuse/src/background.rs | 8 ++--- crates/pdfs-fuse/src/control.rs | 4 +-- crates/pdfs-fuse/src/photos.rs | 6 ++-- crates/pdfs-fuse/src/redate.rs | 6 ++-- crates/pdfs-fuse/src/takeout.rs | 6 ++-- crates/pdfs-gui/src/app/pages/photo_viewer.rs | 14 ++++---- crates/pdfs-gui/src/app/pages/photos.rs | 32 +++++++++---------- crates/pdfs-gui/src/app/pages/status.rs | 8 ++--- docs/CHANGELOG.md | 13 ++++++-- docs/DEVELOPMENT.md | 8 ++--- docs/UI_UX_PLAN.md | 15 +++++---- 18 files changed, 78 insertions(+), 74 deletions(-) diff --git a/crates/pdfs-cli/src/main.rs b/crates/pdfs-cli/src/main.rs index 048da24..04b1ccc 100644 --- a/crates/pdfs-cli/src/main.rs +++ b/crates/pdfs-cli/src/main.rs @@ -146,11 +146,11 @@ enum Command { /// Skip this many photos from the start of the timeline. #[arg(long, default_value_t = 0)] offset: usize, - /// List only favourited photos. + /// List only favorited photos. #[arg(long)] favorites: bool, }, - /// Add or remove a photo's favourite mark. + /// Add or remove a photo's favorite mark. Favorite { /// Photo node uid in `volume~link` form (from `pdfs photos`). uid: String, @@ -188,7 +188,7 @@ enum Command { /// and rewrites them at the right time. /// /// Proton cannot edit a sealed capture time, so a rewrite means re-uploading - /// the photo and trashing the original — favourites and albums are carried + /// the photo and trashing the original — favorites and albums are carried /// across, and the original goes to Proton's trash, not away. Run it with /// `--dry-run` first. RedatePhotos { diff --git a/crates/pdfs-core/src/control.rs b/crates/pdfs-core/src/control.rs index 99ee3c8..900f069 100644 --- a/crates/pdfs-core/src/control.rs +++ b/crates/pdfs-core/src/control.rs @@ -63,7 +63,7 @@ pub enum Request { /// timeline. Like `kind`, the offset is relative to the filtered set. #[serde(default)] range: Option<(i64, i64)>, - /// Restrict the page to favourited photos. Older front-ends omit it and + /// Restrict the page to favorited photos. Older front-ends omit it and /// get the whole timeline, as before. #[serde(default)] favorites: bool, @@ -181,7 +181,7 @@ pub enum Request { /// time by more than a day: `IMG-20230219-WA0001.jpg` filed under 2026 is /// the signature of an import whose metadata sidecars did not match. /// - /// Favourites and album memberships are carried across; the original is + /// Favorites and album memberships are carried across; the original is /// **trashed**, not deleted, so a bad run is recoverable from Proton's /// trash. With `dry_run` nothing is uploaded or trashed and the report says /// what would have been done. @@ -1395,8 +1395,8 @@ pub struct PhotoItem { /// the split omit it; a front-end then treats everything as a still photo. #[serde(default = "default_photo_kind")] pub kind: PhotoKind, - /// Whether the photo is favourited. Older daemons omit it, which a front-end - /// reads as "not favourited" — the same thing it would show for a photo whose + /// Whether the photo is favorited. Older daemons omit it, which a front-end + /// reads as "not favorited" — the same thing it would show for a photo whose /// tags it has not learned yet. #[serde(default)] pub favorite: bool, @@ -2703,7 +2703,7 @@ mod tests { } } - /// A front-end that predates the favourites filter sends no `favorites` + /// A front-end that predates the favorites filter sends no `favorites` /// field, and must keep getting the whole timeline rather than an empty page. #[test] fn timeline_requests_without_the_favorites_filter_still_decode() { diff --git a/crates/pdfs-core/src/db/albums.rs b/crates/pdfs-core/src/db/albums.rs index 6589369..1ff0469 100644 --- a/crates/pdfs-core/src/db/albums.rs +++ b/crates/pdfs-core/src/db/albums.rs @@ -178,7 +178,7 @@ impl Db { ratio: r.get(3)?, thumb_state: r.get(4)?, kind: crate::control::PhotoKind::from_i64(r.get(5)?), - // Album rows have no favourite state of their own: an album + // Album rows have no favorite state of their own: an album // photo on someone else's volume is not in our timeline, and // favouriting one is not supported yet anyway. favorite: false, @@ -239,7 +239,7 @@ impl Db { ratio: r.get(3)?, thumb_state: r.get(4)?, kind: crate::control::PhotoKind::from_i64(r.get(5)?), - // Album rows have no favourite state of their own: an album + // Album rows have no favorite state of their own: an album // photo on someone else's volume is not in our timeline, and // favouriting one is not supported yet anyway. favorite: false, diff --git a/crates/pdfs-core/src/db/migrations.rs b/crates/pdfs-core/src/db/migrations.rs index b649ec4..e762c79 100644 --- a/crates/pdfs-core/src/db/migrations.rs +++ b/crates/pdfs-core/src/db/migrations.rs @@ -684,7 +684,7 @@ CREATE INDEX idx_album_photos_seq ON album_photos(album_uid, seq); CREATE INDEX idx_album_photos_uid ON album_photos(uid); "; -/// V20: whether a photo is a favourite. +/// V20: whether a photo is a favorite. /// /// A column rather than a tag table: `Favorite` is the only Proton photo tag a /// user sets by hand (the rest — Video, Screenshot, Selfie, Raw… — are the @@ -938,7 +938,7 @@ CREATE INDEX IF NOT EXISTS idx_photos_group ON photos(group_key); /// Schema v31: which photos a refresh still has to read from the server. /// /// A refresh used to resolve every photo's node to learn its name, media type, -/// favourite tag, content hash and photo relation — minutes of round-trips for a +/// favorite tag, content hash and photo relation — minutes of round-trips for a /// library of any size, repeated every time the timeline went stale. `resolved_at` /// records that the work was already done, so a refresh only reads the photos it /// has never read and the ones a remote event marked stale (`resolved_at = NULL`). diff --git a/crates/pdfs-core/src/db/photos.rs b/crates/pdfs-core/src/db/photos.rs index 956f8b9..063cb3d 100644 --- a/crates/pdfs-core/src/db/photos.rs +++ b/crates/pdfs-core/src/db/photos.rs @@ -455,7 +455,7 @@ impl Db { /// restricts the page to one tab (Photos / Videos / Raw); `range`, when set, /// restricts it to a `[from, to)` capture-time window (epoch seconds) — the /// date scrubber's jump. `offset` is relative to whatever the filters leave. - /// `favorites` restricts the page to favourited photos. + /// `favorites` restricts the page to favorited photos. /// /// The page holds one row per *group*: the RAW and the JPEG of one shot are /// one tile. The Raw tab is the exception and lists every raw file, because @@ -620,7 +620,7 @@ impl Db { Ok(photos) } - /// Record a photo's favourite flag locally, after the server accepted the + /// Record a photo's favorite flag locally, after the server accepted the /// change. A uid the timeline does not hold is a no-op — an album photo on /// someone else's volume is never in our own `photos` table. pub fn photos_set_favorite(&self, uid: &str, favorite: bool) -> Result<()> { diff --git a/crates/pdfs-core/src/db/tests.rs b/crates/pdfs-core/src/db/tests.rs index a42b204..1ffc8c0 100644 --- a/crates/pdfs-core/src/db/tests.rs +++ b/crates/pdfs-core/src/db/tests.rs @@ -222,7 +222,7 @@ fn favorites_are_remembered_across_refreshes_and_filter_a_page() { assert_eq!( favorites.iter().map(|p| p.uid.as_str()).collect::>(), ["p1"], - "only the favourited photo is in a favourites page" + "only the favorited photo is in a favorites page" ); // A local toggle survives a refresh that could not resolve the photo's node diff --git a/crates/pdfs-core/src/takeout.rs b/crates/pdfs-core/src/takeout.rs index f7e0786..8df5db3 100644 --- a/crates/pdfs-core/src/takeout.rs +++ b/crates/pdfs-core/src/takeout.rs @@ -812,7 +812,7 @@ mod tests { } #[test] - fn sidecar_supplies_capture_time_and_favourite() { + fn sidecar_supplies_capture_time_and_favorite() { let mut sidecars = HashMap::new(); sidecar( &mut sidecars, diff --git a/crates/pdfs-fuse/src/background.rs b/crates/pdfs-fuse/src/background.rs index 1509076..d6463f5 100644 --- a/crates/pdfs-fuse/src/background.rs +++ b/crates/pdfs-fuse/src/background.rs @@ -511,7 +511,7 @@ impl PhotoEventPlan { /// /// `is_echo` claims an event as this daemon's own write. The feed replays our /// writes back at us and a `NodeUpdated` carries no revision id, so without this -/// every favourite the user toggles here would mark its own photo stale and buy +/// every favorite the user toggles here would mark its own photo stale and buy /// a resolve of a node we just wrote. fn classify_photo_events( events: &[DriveEvent], @@ -529,7 +529,7 @@ fn classify_photo_events( if *is_trashed { plan.gone.push(node_uid.to_string()); } else if !is_echo(node_uid) { - // An untrash, a rename, a favourite toggled on the phone: the + // An untrash, a rename, a favorite toggled on the phone: the // event says which node changed but never what about it, so // the uid is marked stale and the next refresh reads it. plan.touched.push(node_uid.to_string()); @@ -974,7 +974,7 @@ mod tests { }, DriveEvent::NodeUpdated { id: DriveEventId::from("e3"), - node_uid: uid("favourited"), + node_uid: uid("favorited"), parent_node_uid: None, is_trashed: false, is_shared: false, @@ -986,7 +986,7 @@ mod tests { plan, PhotoEventPlan { gone: vec![uid("deleted").to_string(), uid("trashed").to_string()], - touched: vec![uid("favourited").to_string()], + touched: vec![uid("favorited").to_string()], full: false, } ); diff --git a/crates/pdfs-fuse/src/control.rs b/crates/pdfs-fuse/src/control.rs index f7b8a23..1529426 100644 --- a/crates/pdfs-fuse/src/control.rs +++ b/crates/pdfs-fuse/src/control.rs @@ -387,9 +387,9 @@ fn handle_control_conn(core: &Core, username: &str, mountpoint: &Path, stream: U Some(node_uid) => match core.set_photo_favorite(&node_uid, favorite) { Ok(()) => CtlResponse::Ok { message: if favorite { - "added to favourites".to_string() + "added to favorites".to_string() } else { - "removed from favourites".to_string() + "removed from favorites".to_string() }, }, Err(e) => CtlResponse::error(e), diff --git a/crates/pdfs-fuse/src/photos.rs b/crates/pdfs-fuse/src/photos.rs index a43ef59..f40550d 100644 --- a/crates/pdfs-fuse/src/photos.rs +++ b/crates/pdfs-fuse/src/photos.rs @@ -501,7 +501,7 @@ impl Core { .map_err(|e| CoreError::from_api(&e, "update photo tags"))?; // The photo event feed replays this write back at us as a bare // `NodeUpdated`, which is indistinguishable from the same photo being - // favourited on a phone. Claim the echo, or the gallery pays for a + // favorited on a phone. Claim the echo, or the gallery pays for a // resolve of a node whose one changed field is already stored below. self.note_self_change(uid); self.db @@ -1469,7 +1469,7 @@ impl Core { NodeKind::File { media_type, .. } => Some(media_type.clone()), NodeKind::Folder => None, }; - // The favourite state is a tag on the photo node, so it + // The favorite state is a tag on the photo node, so it // rides along with the metadata resolve rather than // costing a listing of its own. let favorite = node @@ -1507,7 +1507,7 @@ impl Core { let key = it.uid.to_string(); let main_uid = main_of.get(&key).cloned(); // An unresolved photo keeps whatever was learned before, for the - // favourite flag as much as for the name and media type. + // favorite flag as much as for the name and media type. match meta.get(&key).cloned() { Some(m) => db::TimelineRow { name: m.name, diff --git a/crates/pdfs-fuse/src/redate.rs b/crates/pdfs-fuse/src/redate.rs index b102b46..89f9488 100644 --- a/crates/pdfs-fuse/src/redate.rs +++ b/crates/pdfs-fuse/src/redate.rs @@ -8,7 +8,7 @@ //! //! Proton has no API for editing a sealed capture time, so the repair is a //! rewrite: download the photo, upload it again with the capture time its file -//! name implies, put the new copy back into the albums and favourites the old +//! name implies, put the new copy back into the albums and favorites the old //! one was in, then **trash** (not delete) the original. Trash rather than //! delete so a bad run is recoverable from Proton's own trash for 30 days. //! @@ -213,7 +213,7 @@ impl Core { drop(guard); let new_uid = uploaded.map_err(|e| CoreError::from_api(&e, "re-upload photo"))?; - // Favourite and albums before the trash: a failure here leaves both + // Favorite and albums before the trash: a failure here leaves both // copies on the account, which is recoverable by hand. Losing the // original first would not be. if candidate.favorite { @@ -227,7 +227,7 @@ impl Core { // just set does not come back as a remote change. Ok(_) => self.note_self_change(&new_uid), Err(e) => { - warn!(name = candidate.name, error = %e, "cannot re-favourite re-dated photo") + warn!(name = candidate.name, error = %e, "cannot re-favorite re-dated photo") } } } diff --git a/crates/pdfs-fuse/src/takeout.rs b/crates/pdfs-fuse/src/takeout.rs index 2d99c9f..f94f1a4 100644 --- a/crates/pdfs-fuse/src/takeout.rs +++ b/crates/pdfs-fuse/src/takeout.rs @@ -357,9 +357,9 @@ impl Core { } } - // Favourites are a separate call: the upload seal takes classification + // Favorites are a separate call: the upload seal takes classification // tags, but `Favorite` is not one of them — it goes through the photos - // volume's dedicated favourite endpoint. + // volume's dedicated favorite endpoint. if !favorites.is_empty() { let updates: Vec = favorites .into_iter() @@ -370,7 +370,7 @@ impl Core { }) .collect(); if let Err(e) = self.rt.block_on(photos.update_photos(&updates)) { - warn!(error = %e, "marking imported favourites failed"); + warn!(error = %e, "marking imported favorites failed"); } } Ok(()) diff --git a/crates/pdfs-gui/src/app/pages/photo_viewer.rs b/crates/pdfs-gui/src/app/pages/photo_viewer.rs index cefe42b..e329c4f 100644 --- a/crates/pdfs-gui/src/app/pages/photo_viewer.rs +++ b/crates/pdfs-gui/src/app/pages/photo_viewer.rs @@ -19,7 +19,7 @@ pub(crate) struct Viewer { pub(crate) info_map: gtk4::Button, /// Coordinates behind `info_map`, once a photo with GPS tags is shown. pub(crate) coords: RefCell>, - /// The favourite toggle in the top bar, and whether it is being set from + /// The favorite toggle in the top bar, and whether it is being set from /// the model rather than by the user — the same suppression the details pane /// uses for its pin switch, so painting a photo doesn't fire a round-trip. pub(crate) favorite: gtk4::ToggleButton, @@ -51,7 +51,7 @@ pub(crate) struct ExifInfo { pub(crate) coords: Option<(f64, f64)>, } -/// Paint the favourite toggle without firing its handler. +/// Paint the favorite toggle without firing its handler. pub(crate) fn show_favorite(viewer: &Rc, favorite: bool) { viewer.favorite_suppress.set(true); viewer.favorite.set_active(favorite); @@ -59,7 +59,7 @@ pub(crate) fn show_favorite(viewer: &Rc, favorite: bool) { viewer.favorite_suppress.set(false); } -/// Filled star for a favourite, outline for the rest. +/// Filled star for a favorite, outline for the rest. fn set_favorite_icon(button: >k4::ToggleButton, favorite: bool) { button.set_icon_name(if favorite { "starred-symbolic" @@ -68,7 +68,7 @@ fn set_favorite_icon(button: >k4::ToggleButton, favorite: bool) { }); } -/// Write a favourite change back into the loaded gallery page, so returning to +/// Write a favorite change back into the loaded gallery page, so returning to /// the grid (or reopening the photo) shows what was just set without a reload. pub(crate) fn set_gallery_favorite(ui: &Rc, uid: &str, favorite: bool) { let Some(idx) = find_photo_index(&ui.gallery.model, uid) else { @@ -147,7 +147,7 @@ pub(crate) fn load_photo(ui: &Rc, viewer: &Rc, uid: String) { *viewer.path.borrow_mut() = None; clear_info(viewer); - // The favourite state comes from the timeline page the gallery already has, + // The favorite state comes from the timeline page the gallery already has, // so the button is right on the first frame instead of after a round-trip. let favorite = find_photo_index(&ui.gallery.model, &uid) .and_then(|idx| ui.gallery.model.item(idx)) @@ -556,7 +556,7 @@ pub(crate) fn open_photo_viewer(ui: &Rc, initial_uid: String) { let favorite_btn = gtk4::ToggleButton::builder() .icon_name("non-starred-symbolic") - .tooltip_text("Favourite") + .tooltip_text("Favorite") .valign(gtk4::Align::Center) .build(); favorite_btn.add_css_class("flat"); @@ -758,7 +758,7 @@ pub(crate) fn open_photo_viewer(ui: &Rc, initial_uid: String) { _ => Some("The mount service didn't respond.".to_string()), }; if let Some(detail) = failed { - toast_error(&ui_result, "Couldn't change the favourite", &detail); + toast_error(&ui_result, "Couldn't change the favorite", &detail); // The server refused, so the button must go back to describing // what is actually stored — without firing this handler again. if *viewer_result.uid.borrow() == uid { diff --git a/crates/pdfs-gui/src/app/pages/photos.rs b/crates/pdfs-gui/src/app/pages/photos.rs index c30646e..21a4b31 100644 --- a/crates/pdfs-gui/src/app/pages/photos.rs +++ b/crates/pdfs-gui/src/app/pages/photos.rs @@ -84,7 +84,7 @@ pub(crate) struct GalleryState { /// The capture-time window the timeline is currently filtered to, or `None` /// for the whole span. Read by [`load_gallery`], set by the date dropdown. pub(crate) range: Cell>, - /// The favourites toggle, and whether it is on. When on, the timeline is + /// The favorites toggle, and whether it is on. When on, the timeline is /// restricted to photos carrying Proton's `Favorite` tag. pub(crate) favorites_btn: gtk4::ToggleButton, pub(crate) favorites: Cell, @@ -495,12 +495,12 @@ pub(crate) fn build_gallery_page() -> (gtk4::Widget, GalleryWidgets) { tab_group.append(btn); } - // Favourites: a filter, not a tab — it cuts across Photos / Videos / Raw, so + // Favorites: a filter, not a tab — it cuts across Photos / Videos / Raw, so // it stays outside the segmented control rather than becoming a fifth option // that would silently drop the kind the user picked. let favorites_btn = gtk4::ToggleButton::builder() .icon_name("starred-symbolic") - .tooltip_text("Show only favourites") + .tooltip_text("Show only favorites") .build(); favorites_btn.add_css_class("pill"); @@ -962,7 +962,7 @@ pub(crate) fn wire_gallery( }); } - // Favourites: reload the timeline restricted to favourites (or back to all). + // Favorites: reload the timeline restricted to favorites (or back to all). // Independent of the kind tabs and the date jump, both of which keep their // current value across the toggle. let ui_fav = ui.clone(); @@ -2118,7 +2118,7 @@ pub(crate) fn short_capture_time(secs: i64) -> String { /// Fetch a timeline page from the daemon. When `append` is false the model is /// cleared first (fresh load); otherwise the next page is tacked on. -/// The empty state for a timeline filtered to `kind`, favourites and/or a +/// The empty state for a timeline filtered to `kind`, favorites and/or a /// `month`: what is missing, in the filter's own words. pub(crate) fn empty_timeline_text( kind: Option, @@ -2138,12 +2138,12 @@ pub(crate) fn empty_timeline_text( "Photos you upload to Proton Drive appear here.".to_string(), ), (true, None, None) => ( - "No favourites yet".to_string(), + "No favorites yet".to_string(), "Star a photo in the viewer or from its menu to find it here.".to_string(), ), (true, _, _) => ( - format!("No favourite {what}{when}"), - "Turn off the favourites filter to see everything.".to_string(), + format!("No favorite {what}{when}"), + "Turn off the favorites filter to see everything.".to_string(), ), (false, _, _) => ( format!("No {what}{when}"), @@ -2222,9 +2222,9 @@ fn show_photo_menu(ui: &Rc, photo: &PhotoItem, anchor: >k4::Button, x: f64 let (ui_c, uid, favorite) = (ui.clone(), photo.uid.clone(), photo.favorite); item( if favorite { - "Remove from Favourites" + "Remove from Favorites" } else { - "Add to Favourites" + "Add to Favorites" }, if favorite { "non-starred-symbolic" @@ -2269,18 +2269,18 @@ fn set_photo_favorite(ui: &Rc, uid: String, favorite: bool) { toast( &ui, if favorite { - "Added to Favourites" + "Added to Favorites" } else { - "Removed from Favourites" + "Removed from Favorites" }, ); } Ok(Ok(Response::Error { message, .. })) => { - toast_error(&ui, "Couldn't change the favourite", &message) + toast_error(&ui, "Couldn't change the favorite", &message) } _ => toast_error( &ui, - "Couldn't change the favourite", + "Couldn't change the favorite", "The mount service didn't respond.", ), } @@ -2540,14 +2540,14 @@ mod tests { #[test] fn an_empty_filter_names_what_it_filtered() { assert_eq!(empty_timeline_text(None, false, None).0, "No photos yet"); - assert_eq!(empty_timeline_text(None, true, None).0, "No favourites yet"); + assert_eq!(empty_timeline_text(None, true, None).0, "No favorites yet"); assert_eq!( empty_timeline_text(Some(PhotoKind::Video), false, Some("June 2024")).0, "No videos in June 2024" ); assert_eq!( empty_timeline_text(Some(PhotoKind::Raw), true, None).0, - "No favourite raw files" + "No favorite raw files" ); } } diff --git a/crates/pdfs-gui/src/app/pages/status.rs b/crates/pdfs-gui/src/app/pages/status.rs index 5a13edd..8d10563 100644 --- a/crates/pdfs-gui/src/app/pages/status.rs +++ b/crates/pdfs-gui/src/app/pages/status.rs @@ -190,7 +190,7 @@ pub(crate) fn build_main_page() -> MainWidgets { let appearance_group = adw::PreferencesGroup::builder().title("Appearance").build(); let accent_row = adw::SwitchRow::builder() .title("Proton purple accent") - .subtitle("Use the Proton brand colour instead of the system accent colour") + .subtitle("Use the Proton brand color instead of the system accent color") .build(); appearance_group.add(&accent_row); @@ -531,11 +531,7 @@ pub(crate) fn wire_settings( let mut config = ui_accent.dirs.load_config(); config.proton_accent = Some(on); if let Err(e) = ui_accent.dirs.save_config(&config) { - toast_error( - &ui_accent, - "Couldn't save the accent colour", - &e.to_string(), - ); + toast_error(&ui_accent, "Couldn't save the accent color", &e.to_string()); } }); } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index fceae59..4866fee 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -11,7 +11,7 @@ scratch (user data in `staging/` and `recovery/` is never touched by this). ## [Unreleased] -Schema: **30** (unchanged). +Schema: **31** (unchanged). ### Added - **Pause and resume sync.** The Sync page, the tray and `pdfs sync pause [--for 1h]` / @@ -66,10 +66,12 @@ Schema: **30** (unchanged). keyring, a skipped two-factor or verification step — instead of the raw error. The button spins while signing in, and the page links to account creation and password reset. The two-factor dialog mentions recovery codes. +- **US spelling throughout.** The app, the CLI and the docs say "Favorite" and "color", matching the + `pdfs favorite` command. ## [1.11.1] — 2026-09-19 -Schema: **30** (unchanged). +Schema: **31** (unchanged). ### Fixed - **An upload parked behind a temp-file rename no longer stays parked forever.** A create for a @@ -96,7 +98,7 @@ Schema: **30** (unchanged). ## [1.11.0] — 2026-09-19 -Schema: **30** (`photos.content_hash` / `main_uid` / `group_key`); SDK bumped to `proton-sdk` / +Schema: **31** (V30 `photos.content_hash` / `main_uid` / `group_key`, V31 `photos.resolved_at`); SDK bumped to `proton-sdk` / `proton-drive-rs` **0.6.5**. ### Changed @@ -882,6 +884,11 @@ First stable release: FUSE files-on-demand mount, sync daemon under `proton-driv - The outstanding FUSE defects tracked in `docs/BUGS.md`, plus a truncate defect, validated by a new POSIX compliance suite for the filesystem. +[1.11.1]: https://github.com/narrrl/proton-drive-linux/releases/tag/v1.11.1 +[1.11.0]: https://github.com/narrrl/proton-drive-linux/releases/tag/v1.11.0 +[1.10.2]: https://github.com/narrrl/proton-drive-linux/releases/tag/v1.10.2 +[1.10.1]: https://github.com/narrrl/proton-drive-linux/releases/tag/v1.10.1 +[1.10.0]: https://github.com/narrrl/proton-drive-linux/releases/tag/v1.10.0 [1.9.3]: https://github.com/narrrl/proton-drive-linux/releases/tag/v1.9.3 [1.9.2]: https://github.com/narrrl/proton-drive-linux/releases/tag/v1.9.2 [1.9.1]: https://github.com/narrrl/proton-drive-linux/releases/tag/v1.9.1 diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 0748249..4c6082e 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -14,7 +14,7 @@ Consolidated from the historical feature roadmap; current bug status lives in [` * **Custom FUSE Mount Options (Horizon 1)**: Expose custom mounting parameters in configuration (e.g. `ro` for read-only, `direct_io` to bypass kernel caching, `allow_other`). Auto-generate systemd user mount units upon sync folder configuration. * **Symbolic Link & Hard Link Virtualization (Horizon 2)**: Add symbolic link virtualization inside the metadata store (`NodeType::Symlink`) by intercepting `readlink(2)` and `symlink(2)`. Targets should sync remotely as small encrypted metadata placeholder files on Drive (`proton-vfs-symlink: `). * **Desktop File Manager Integration (Horizon 2)**: Develop shell extension plugins for popular Linux file managers (Nautilus Python plugin, Dolphin Service Menus, Thunar custom actions) to offer context-menu shortcuts ("Pin", "Unpin", "Copy Share Link", "View Version History") and file overlay sync badges. -* **Integrated Photo Gallery Enhancements (Horizon 2)**: Album *editing* — rename, cover photo, delete, and removing photos from an album — is still unported in the SDK (TS `updateAlbum` / `deleteAlbum` / `removePhotos`), as is adding a photo that lives on someone else's volume (TS `copyPhoto`). Album **creation** and adding own-volume photos landed with the Google Photos import. Still open: an auto-upload Pictures folder pipeline, and a GUI for creating albums by hand. Exif display, the date-based timeline scrubber and favourites are implemented. +* **Integrated Photo Gallery Enhancements (Horizon 2)**: Album *editing* — rename, cover photo, delete, and removing photos from an album — is still unported in the SDK (TS `updateAlbum` / `deleteAlbum` / `removePhotos`), as is adding a photo that lives on someone else's volume (TS `copyPhoto`). Album **creation** and adding own-volume photos landed with the Google Photos import. Still open: an auto-upload Pictures folder pipeline, and a GUI for creating albums by hand. Exif display, the date-based timeline scrubber and favorites are implemented. * **Google Photos import follow-ups**: the importer uploads photos one at a time — a large export would finish sooner with a bounded concurrent upload pool. Trash folders are recognized by a hardcoded list of localized names ([`takeout.rs`](../crates/pdfs-core/src/takeout.rs) `TRASH_FOLDERS`); a locale not on it imports its trash as ordinary photos. Motion-photo `.MP`/`.MV` sidecar videos are ignored rather than uploaded as related photos. Live validation against a real export is pending. * **Random-Access Media Streaming seek support (Horizon 2)**: Intercept out-of-order sparse reads (player seeks) in `pdfs-fuse` and prioritize downloading blocks around the seek offset, aborting/postponing sequential pre-fetches. * **Dynamic Sync Dashboard & Queue Visualization (Horizon 2)**: GUI real-time transfer list (progress bars, speed, ETA), sync history feed, storage usage breakdown, and global pause/resume sync controls. @@ -135,10 +135,10 @@ Three properties that shape the code: - **The active revision cannot be deleted.** `Core::delete_revision_for_uid` checks that locally so the user gets a sentence rather than an API code, and the dialog gives the current row no delete button. - **Saving a version never overwrites.** `SaveRevisionAs` refuses an existing destination and removes a partial file if the download fails — a half-written export looks identical to a good one to every tool that opens it afterwards. -### Photo Favourites (Implemented) +### Photo Favorites (Implemented) **Files**: [`photos.rs`](../crates/pdfs-fuse/src/photos.rs), [`photo_viewer.rs`](../crates/pdfs-gui/src/app/pages/photo_viewer.rs) -The gallery can mark and filter favourites, through the SDK's photo-tag API (`update_photos`). Schema **v20** adds `photos.favorite`; the flag is learned in the timeline enrichment pass that already resolves each photo's name and media type, and is kept across a refresh that could not resolve a photo — the same learned-and-kept rule as `media_type`. The lightbox carries a star toggle, the Photos header a favourites filter, and the CLI has `pdfs favorite [--remove]` plus `pdfs photos --favorites`. +The gallery can mark and filter favorites, through the SDK's photo-tag API (`update_photos`). Schema **v20** adds `photos.favorite`; the flag is learned in the timeline enrichment pass that already resolves each photo's name and media type, and is kept across a refresh that could not resolve a photo — the same learned-and-kept rule as `media_type`. The lightbox carries a star toggle, the Photos header a favorites filter, and the CLI has `pdfs favorite [--remove]` plus `pdfs photos --favorites`. Favouriting a photo that is not on this account's own photos volume (shared with us, or album-only) needs it re-encrypted for our timeline root, which the SDK does not implement; the daemon surfaces that as an error rather than silently doing nothing. @@ -155,7 +155,7 @@ The gallery lays each day out in justified rows (`justify_rows` / `plan_rows` / ### RAW + JPEG Grouping (Implemented) **Files**: [`photos.rs`](../crates/pdfs-core/src/db/photos.rs), [`migrations.rs`](../crates/pdfs-core/src/db/migrations.rs), [`photos.rs`](../crates/pdfs-fuse/src/photos.rs), [`photo_viewer.rs`](../crates/pdfs-gui/src/app/pages/photo_viewer.rs) -Schema **v30** adds `content_hash`, `main_uid` and an indexed `group_key` to `photos`. The first two come from the server's `PhotoProperties` and are filled by the enrichment pass in `refresh_timeline` that already resolves each photo's name, media type and favourite tag; `group_key` is computed in `photos_replace`, where the whole timeline is in hand. +Schema **v30** adds `content_hash`, `main_uid` and an indexed `group_key` to `photos`. The first two come from the server's `PhotoProperties` and are filled by the enrichment pass in `refresh_timeline` that already resolves each photo's name, media type and favorite tag; `group_key` is computed in `photos_replace`, where the whole timeline is in hand. - **Precedence.** The server relation first (`main_photo_uid` and `related_photo_uids`, read from whichever end resolves — the two can land in different enrichment chunks); then same capture day, same case-insensitive name stem, and one member raw while the other is not; otherwise the photo is its own group. The union-find pass is over the timeline in memory, so it costs one pass per refresh rather than a query per photo. - **Representative.** The non-raw member when there is one — a JPEG decodes in milliseconds and is what the person expects to see. `group_key` holds that member's uid, so "is this the tile" is `uid = group_key`, an index scan. diff --git a/docs/UI_UX_PLAN.md b/docs/UI_UX_PLAN.md index 13f09d9..3fc98cd 100644 --- a/docs/UI_UX_PLAN.md +++ b/docs/UI_UX_PLAN.md @@ -21,7 +21,7 @@ The client is functionally rich but the UI is built page by page, and it shows: `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 "favourite"), trash icons mean "stop syncing", "remove device" and "remove + 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 @@ -212,7 +212,7 @@ Header: `[‹ ›] Path bar (breadcrumbs, scroll to end, Ctrl+L to edit) … [ - 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, Favourites) instead of +- 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. @@ -221,9 +221,9 @@ Header: `[‹ ›] Path bar (breadcrumbs, scroll to end, Ctrl+L to edit) … [ - 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, Favourite, Add to album, Download/Save copy, Show in My files, Move to - Trash. Selection: Shift range, Ctrl+A, bulk Favourite, Add to album, Download. -- Empty states per filter ("No favourites yet", "No videos in June 2024") instead of the global +- 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. @@ -233,7 +233,7 @@ Header: `[‹ ›] Path bar (breadcrumbs, scroll to end, Ctrl+L to edit) … [ (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, favourite tooltip reflects state, readable dates. + success toast, "Open with…" app chooser, favorite tooltip reflects state, readable dates. ### Shared with me / Shared by me @@ -292,7 +292,8 @@ Header: `[‹ ›] Path bar (breadcrumbs, scroll to end, Ctrl+L to edit) … [ | Photos section | Photos | `image-x-generic-symbolic` | | Sharing | Shared by me / Shared with me | distinct icons (`emblem-shared` / `folder-publicshare`) | -Spelling: pick one (US "Favorite" or UK "Favourite") and apply it everywhere. Title Case for +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)