diff --git a/crates/engine/src/cmd/browse.rs b/crates/engine/src/cmd/browse.rs
--- a/crates/engine/src/cmd/browse.rs
+++ b/crates/engine/src/cmd/browse.rs
@@ -513,7 +513,13 @@ pub(crate) fn move_folder(s: &mut Session, from: &str, to: &str) -> std::result:
     if !src.is_dir() {
         return Err(format!("{from} is not a folder"));
     }
-    if s.library.as_ref().is_some_and(|l| l.dir.starts_with(src) || src.starts_with(&l.dir)) {
+    // `a/../b` names a folder without spelling it, which the checks below and the relink can't see
+    if src.components().any(|c| matches!(c, std::path::Component::ParentDir)) {
+        return Err(format!("{from}: give the folder's path without '..'"));
+    }
+    // the library folder itself, and its own top-level folders (`Originals`, `thumbs`), stay where
+    // the library expects them; folders below those (an import's date folders) can be renamed
+    if s.library.as_ref().is_some_and(|l| l.dir.starts_with(src) || src.parent() == Some(l.dir.as_path())) {
         return Err("the library's own folders can't be moved here".into());
     }
     // Repairs retain their exact old paths, including those on surviving history entries.
diff --git a/crates/engine/src/tests_folders.rs b/crates/engine/src/tests_folders.rs
--- a/crates/engine/src/tests_folders.rs
+++ b/crates/engine/src/tests_folders.rs
@@ -16,6 +16,7 @@
 
 use lightcraft_catalog::{Op, Photo, Source};
 use serde_json::json;
+use std::path::Path;
 
 use crate::{LibrarySource, Session, filter_chips};
 
@@ -701,3 +702,29 @@ fn a_destination_without_write_permission_reports_an_error_and_keeps_everything(
     }
     std::fs::set_permissions(dir.0.join("ro"), std::fs::Permissions::from_mode(0o755)).unwrap();
 }
+
+#[test]
+fn a_date_folder_inside_the_library_can_be_renamed_but_the_librarys_own_folders_cannot() {
+    let dir = Scratch::new("in-library");
+    let lib = dir.0.join("lib");
+    let mut s = Session::new();
+    s.open_library(&lib, false).unwrap();
+    let day = lib.join("Originals").join("2026-10-16");
+    std::fs::create_dir_all(&day).unwrap();
+    add(&mut s, &day.join("a.jpg").to_string_lossy());
+    let day = day.to_string_lossy().to_string();
+    s.execute("folder.rename", &json!({"path": day, "name": "Beach"})).unwrap();
+    assert!(lib.join("Originals/Beach").is_dir(), "the imported date folder is renamed");
+    assert!(!Path::new(&day).exists());
+    s.execute("edit.undo", &json!({})).unwrap();
+    assert!(Path::new(&day).is_dir(), "undo names it back");
+    for own in ["Originals", "thumbs"] {
+        std::fs::create_dir_all(lib.join(own)).unwrap();
+        let path = lib.join(own).to_string_lossy().to_string();
+        assert!(s.execute("folder.rename", &json!({"path": path, "name": "x"})).is_err(), "{own} stays put");
+    }
+    assert!(s.execute("folder.rename", &json!({"path": lib.to_string_lossy(), "name": "x"})).is_err(), "and so does the library");
+    let alias = lib.join("Originals").join("..").join("Originals").to_string_lossy().to_string();
+    assert!(s.execute("folder.rename", &json!({"path": alias, "name": "x"})).is_err(), "even named through '..'");
+    assert!(lib.join("Originals").is_dir());
+}
diff --git a/crates/ui-egui/src/panels/dialogs.rs b/crates/ui-egui/src/panels/dialogs.rs
--- a/crates/ui-egui/src/panels/dialogs.rs
+++ b/crates/ui-egui/src/panels/dialogs.rs
@@ -1581,7 +1581,8 @@ pub fn show(app: &mut LightcraftApp, ctx: &egui::Context, blocked: bool) {
                 }
                 close = true;
             }
-            Err(e) if matches!(dlg, Dialog::MovePhotos { .. }) => {
+            // a prompt (Rename Folder, …) that is refused closes and says why
+            Err(e) if matches!(dlg, Dialog::MovePhotos { .. } | Dialog::TextPrompt { .. }) => {
                 app.toast(ctx, e);
                 close = true;
             }
