C14.15 r1: failed recovery and session writes show in the window, once each, with the last kept time

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-12 11:30:49 -04:00
1 parent e1b889cb7f
commit 906d64cf52
6 files changed
+155 -12

No files matched your search

+5
View File
@@ -51,6 +51,9 @@ pub struct Extra {
/// The last recovery point's mark, and one in flight.
pub pointed: Option<String>,
pub pointing: bool,
/// When a point last landed, and the failure last shown.
pub pointed_at: Option<std::time::SystemTime>,
pub point_failed: Option<String>,
/// A kept tab's mode, place and text came back once.
pub restored: bool,
/// Disk reads started; only the newest lands.
@@ -79,6 +82,8 @@ impl Default for Extra {
slot: String::new(),
pointed: None,
pointing: false,
pointed_at: None,
point_failed: None,
restored: false,
reads: 0,
looked: false,
+50 -11
View File
@@ -46,6 +46,8 @@ pub struct Keeping {
crash: Option<Crash>,
waiting: Vec<Waiting>,
written: Option<String>,
/// The session write failure last shown.
unwritten: Option<String>,
/// A quit began: the kept list stays as then.
pub leaving: bool,
/// The list as kept when the quit began.
@@ -129,6 +131,7 @@ impl Keeping {
crash: Some(crash),
waiting,
written: None,
unwritten: None,
leaving: false,
frozen: Vec::new(),
};
@@ -284,7 +287,7 @@ impl Desk {
recovery.write_once(current.as_bytes(), None, now)
})
.await;
desk.update(cx, |desk, _| desk.pointed(id, point, wrote, &slot))
desk.update(cx, |desk, cx| desk.pointed(id, point, wrote, &slot, cx))
.ok();
})
.detach();
@@ -304,11 +307,18 @@ impl Desk {
}
if !self.keeping.leaving {
self.keeping.frozen = kept.clone();
self.write_session(Saved { tabs: kept });
self.write_session(Saved { tabs: kept }, cx);
}
}
fn pointed(&mut self, id: u64, point: String, wrote: io::Result<Option<PathBuf>>, slot: &Path) {
fn pointed(
&mut self,
id: u64,
point: String,
wrote: io::Result<Option<PathBuf>>,
slot: &Path,
cx: &mut Context<Self>,
) {
// The tab closed while it wrote: the point goes too.
let Some(tab) = self.tab_mut(id) else {
if let Err(error) = clear(slot) {
@@ -317,21 +327,50 @@ impl Desk {
return;
};
tab.extra.pointing = false;
match wrote {
Ok(_) => tab.extra.pointed = Some(point),
Err(error) => log::error!("mark: a recovery point for tab {id} failed: {error}"),
let error = match wrote {
Ok(_) => {
tab.extra.pointed = Some(point);
tab.extra.pointed_at = Some(SystemTime::now());
tab.extra.point_failed = None;
return;
}
Err(error) => error.to_string(),
};
log::error!("mark: a recovery point for tab {id} failed: {error}");
// Each failure shows once; the next tick retries.
if tab.extra.point_failed.as_ref() == Some(&error) {
return;
}
tab.extra.point_failed = Some(error.clone());
let at = match tab.extra.pointed_at {
Some(at) => humantime(at),
None => text(cx, "office.stopped.never", &[]).to_string(),
};
let name = tab.name.to_string();
let said = text(cx, "mark.recovery.failed", &[("name", &name), ("error", &error), ("at", &at)]);
self.toast_unlogged(said, ely_gpui_component::primitives::Severity::Danger, cx);
}
fn write_session(&mut self, saved: Saved) {
fn write_session(&mut self, saved: Saved, cx: &mut Context<Self>) {
let json = serde_json::to_string_pretty(&saved).expect("the session serializes");
if self.keeping.written.as_ref() == Some(&json) {
return;
}
let file = self.keeping.dir.join("session.json");
match ely_files::save_atomic(&file, json.as_bytes(), 0) {
Ok(_) => self.keeping.written = Some(json),
Err(error) => log::error!("mark: the session did not save: {error}"),
let error = match ely_files::save_atomic(&file, json.as_bytes(), 0) {
Ok(_) => {
self.keeping.written = Some(json);
self.keeping.unwritten = None;
return;
}
Err(error) => error.to_string(),
};
log::error!("mark: the session did not save: {error}");
// Each failure shows once; the next tick retries.
if self.keeping.unwritten.as_ref() != Some(&error) {
self.keeping.unwritten = Some(error.clone());
let said = text(cx, "mark.session.failed", &[("error", &error)]);
self.toast(said, ely_gpui_component::primitives::Severity::Danger, cx);
}
}
@@ -379,7 +418,7 @@ impl Desk {
}
}
let tabs = self.keeping.frozen.clone();
self.write_session(Saved { tabs });
self.write_session(Saved { tabs }, cx);
}
/// A quit or close begins: the tabs now stay kept.
+90
View File
@@ -219,4 +219,94 @@ mod window {
);
assert!(read.is_err(), "an unknown mode is no default");
}
/// The window's toasts that hold `words`.
fn toasted(desk: &Entity<Desk>, words: &str, cx: &mut VisualTestContext) -> usize {
let root = desk.read_with(cx, |desk, _| desk.core.root.upgrade().expect("the window"));
let toasts = root.read_with(cx, |root, cx| root.toasts(cx));
toasts
.iter()
.filter(|(said, _)| said.contains(words))
.count()
}
#[gpui::test]
fn a_failed_recovery_write_shows_once_with_the_last_kept(cx: &mut TestAppContext) {
let (data, files) = (
tempfile::tempdir().expect("data"),
tempfile::tempdir().expect("files"),
);
let a = files.path().join("a.md");
std::fs::write(
&a, "Text.
",
)
.expect("a");
let (desk, cx) = start(cx, data.path());
open(&desk, &a, cx);
let view = show(&desk, "a.md", cx);
for (typed, broken) in [
(
"One.
", false,
),
(
"Two.
", true,
),
(
"Three.
", true,
),
] {
if broken && data.path().join("recovery").is_dir() {
std::fs::remove_dir_all(data.path().join("recovery")).expect("removes");
std::fs::write(data.path().join("recovery"), "not a folder").expect("blocks");
}
view.update(cx, |view, cx| view.adopt(typed.into(), cx));
cx.executor()
.advance_clock(std::time::Duration::from_secs(1));
cx.run_until_parked();
desk.update(cx, |desk, cx| desk.keep(cx));
cx.run_until_parked();
}
assert_eq!(toasted(&desk, "was not kept for recovery", cx), 1, "once");
let never = toasted(&desk, "none yet", cx);
assert_eq!(never, 0, "the first point landed, so its time shows");
}
#[gpui::test]
fn a_failed_session_write_shows_once(cx: &mut TestAppContext) {
let (data, files) = (
tempfile::tempdir().expect("data"),
tempfile::tempdir().expect("files"),
);
let a = files.path().join("a.md");
std::fs::write(
&a, "Text.
",
)
.expect("a");
let (desk, cx) = start(cx, data.path());
open(&desk, &a, cx);
let session = data.path().join("session.json");
if session.exists() {
std::fs::remove_file(&session).expect("removes");
}
std::fs::create_dir(&session).expect("blocks");
for line in [1, 2] {
let view = show(&desk, "a.md", cx);
view.update(cx, |view, cx| {
view.document()
.update(cx, |document, cx| document.set_caret(line, 0, cx))
});
desk.update(cx, |desk, cx| desk.keep(cx));
cx.run_until_parked();
}
assert_eq!(
toasted(&desk, "were not saved for the next launch", cx),
1,
"once"
);
}
}