mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-08 20:12:31 +00:00
The desktop app asks for a newer release on its first frame, and nothing could stop it, though the README says PdfCraft works offline. A preference, on by default so nothing changes for anyone who leaves it, keeps the app off the network until Help ▸ Check for updates is chosen. It has its own settings key: check_updates_at_start was an opt-in, off unless chosen, so settings written then would turn this off unasked.
This commit is contained in:
10 files changed
+100
-4
No files matched your search
@@ -231,6 +231,8 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md
|
||||
|
||||
- **2026-11-02 (M10, #5743):** macOS print jobs name their title and owner (`com.apple.print.JobInfo.PMJobName`, `PMJobOwner`), as the system print dialog does, because a Kyocera filter crashes on a job without them. Checked by a test and through `doc_print` against a stand-in `lp`; not yet on a physical printer.
|
||||
|
||||
- **2026-11-02 (M14, #5737, #5460):** Preferences can turn off the update check at start. It stays on by default, and Help ▸ Check for updates still asks.
|
||||
|
||||
- **2026-11-01 (M7, #5657):** Header/footer, watermark and background additions and replacements stage the complete call before committing. Synthetic later-page failures preserve exact incremental bytes, object count, dirty state and allocation cursor; success controls retain other marks and shared/inherited resources. Estimates unchanged.
|
||||
|
||||
- **2026-11-01 (M14, #5572):** Preferences ▸ Documents and view sets how many recent files to keep, from 0 to 50 with 12 by default. At 0 Home has no Recent section and File ▸ Open Recent is disabled. The list is cut when Preferences closes, so the number can pass through 0 on its way to another.
|
||||
|
||||
@@ -128,6 +128,7 @@ impl PdfCraftApp {
|
||||
last_session: Default::default(),
|
||||
quit_session: None,
|
||||
reload_changed_files: true,
|
||||
update_check_at_start: true,
|
||||
show_secured_notice: true,
|
||||
always_default_view: false,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
||||
@@ -47,6 +47,9 @@ impl PdfCraftApp {
|
||||
("show_page_edge_shadows", _) => {
|
||||
self.show_page_edge_shadows = value.parse::<bool>().map_err(|_| "show_page_edge_shadows must be true or false")?;
|
||||
}
|
||||
("update_check_at_start", _) => {
|
||||
self.update_check_at_start = value.parse::<bool>().map_err(|_| "update_check_at_start must be true or false")?;
|
||||
}
|
||||
("scroll_speed", _) => {
|
||||
self.scroll_speed = scroll_speed::ScrollSpeed::parse(value).ok_or("scroll_speed must be slow, normal, or fast")?;
|
||||
}
|
||||
|
||||
@@ -147,6 +147,9 @@ impl PdfCraftApp {
|
||||
// Set outside json! above, which is at serde_json's macro recursion limit.
|
||||
v["hide_empty_highlight_hover"] = serde_json::Value::Bool(self.comment_prefs.hide_empty_highlight_hover);
|
||||
v["show_page_edge_shadows"] = self.show_page_edge_shadows.into();
|
||||
// Not `check_updates_at_start`: that key was an opt-in of its own, off unless chosen, and
|
||||
// settings written then would turn this off unasked.
|
||||
v["update_check_at_start"] = self.update_check_at_start.into();
|
||||
v["hotbar"] = serde_json::to_value(&self.hotbar).unwrap_or_default();
|
||||
v["comment_tool_pinned"] = serde_json::Value::Bool(self.comment_prefs.pinned);
|
||||
v["show_secured_notice"] = serde_json::Value::Bool(self.show_secured_notice);
|
||||
@@ -219,6 +222,9 @@ impl PdfCraftApp {
|
||||
if let Some(on) = v["reload_changed_files"].as_bool() {
|
||||
self.reload_changed_files = on;
|
||||
}
|
||||
if let Some(on) = v["update_check_at_start"].as_bool() {
|
||||
self.update_check_at_start = on;
|
||||
}
|
||||
if let Some(on) = v["show_secured_notice"].as_bool() {
|
||||
self.show_secured_notice = on;
|
||||
}
|
||||
|
||||
@@ -659,6 +659,8 @@ fn card_general(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens, ctx: &mut
|
||||
ctx.checkbox(ui, &mut app.restore_last_view, tl!("Restore the last view (page, zoom and page display) when a document is opened again"), t);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
ctx.checkbox(ui, &mut app.reload_changed_files, tl!("Reload documents when their files change on disk"), t);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
ctx.checkbox(ui, &mut app.update_check_at_start, tl!("Check for updates when PdfCraft starts"), t);
|
||||
ctx.checkbox(ui, &mut app.show_secured_notice, tl!("Show a notice when a document is secured"), t);
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
||||
@@ -370,6 +370,9 @@ pub struct PdfCraftApp {
|
||||
quit_session: Option<last_session::LastSession>,
|
||||
/// Preferences: reload a document when its file changes on disk (#431).
|
||||
pub reload_changed_files: bool,
|
||||
/// Preferences: ask for a newer release when the desktop app starts (#5737). Off, the app
|
||||
/// asks only from Help ▸ Check for updates.
|
||||
pub update_check_at_start: bool,
|
||||
/// Preferences: tell the user, above the pages, that a document is secured (its security
|
||||
/// settings restrict changes). Off only hides that notice (#4035); kept in the settings.
|
||||
pub show_secured_notice: bool,
|
||||
|
||||
@@ -21,7 +21,8 @@ pub(crate) struct Updates {
|
||||
updater: Updater,
|
||||
/// The Updates dialog is showing.
|
||||
pub(crate) open: bool,
|
||||
/// Ask for a newer release at start; the desktop app turns it on, so tests never reach the network.
|
||||
/// Ask for a newer release at start, unless Preferences says not to; the desktop app turns
|
||||
/// it on, so tests never reach the network.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
startup_check: bool,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -73,10 +74,11 @@ impl PdfCraftApp {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let Some(ctx) = self.ctx.clone() else { return };
|
||||
let at_start = self.update_check_at_start;
|
||||
let updates = &mut self.updates;
|
||||
if std::mem::take(&mut updates.check_requested) {
|
||||
updates.updater.check_now(&ctx);
|
||||
} else if updates.startup_check {
|
||||
} else if updates.startup_check && at_start {
|
||||
updates.updater.check_at_start(&ctx);
|
||||
}
|
||||
updates.updater.tick(&ctx);
|
||||
|
||||
@@ -100,6 +100,82 @@ fn nothing_is_asked_until_the_user_checks() {
|
||||
h.get_by_label_contains("PdfCraft 99.0.0 is available");
|
||||
}
|
||||
|
||||
/// The desktop app's start, with `settings` restored, and the count of what the source was asked.
|
||||
fn started_with(settings: &'static str) -> (Harness<'static, PdfCraftApp>, Arc<std::sync::atomic::AtomicUsize>) {
|
||||
let calls = Arc::new(std::sync::atomic::AtomicUsize::new(0));
|
||||
let counted = calls.clone();
|
||||
let h = Harness::builder().with_size(egui::vec2(1200.0, 800.0)).build_eframe(move |_cc| {
|
||||
let mut app = PdfCraftApp::new();
|
||||
app.set_option("language", "en").unwrap();
|
||||
app.set_startup_update_check(true);
|
||||
app.restore(settings);
|
||||
let counted = counted.clone();
|
||||
app.set_update_source(Arc::new(move || {
|
||||
counted.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||
Ok(Some(release("v99.0.0")))
|
||||
}));
|
||||
app
|
||||
});
|
||||
(h, calls)
|
||||
}
|
||||
|
||||
/// Run frames for `time`, or until the source has been asked. The check at start shows nothing
|
||||
/// while it runs, so `settle` can't wait for it.
|
||||
fn run_until_asked(h: &mut Harness<'static, PdfCraftApp>, calls: &std::sync::atomic::AtomicUsize, time: std::time::Duration) {
|
||||
let deadline = std::time::Instant::now() + time;
|
||||
while calls.load(std::sync::atomic::Ordering::SeqCst) == 0 && std::time::Instant::now() < deadline {
|
||||
h.run_steps(2);
|
||||
std::thread::sleep(std::time::Duration::from_millis(5));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_preference_keeps_the_check_at_start_from_reaching_the_network() {
|
||||
use std::sync::atomic::Ordering::SeqCst;
|
||||
assert!(PdfCraftApp::new().update_check_at_start, "on by default");
|
||||
|
||||
// The same start with the preference left on does ask, so a count of none below means
|
||||
// the preference held the check back and not that it hadn't run yet.
|
||||
let (mut h, calls) = started_with("{}");
|
||||
run_until_asked(&mut h, &calls, std::time::Duration::from_secs(30));
|
||||
assert_eq!(calls.load(SeqCst), 1, "on, the app asks at start");
|
||||
|
||||
let (mut h, calls) = started_with(r#"{"update_check_at_start": false}"#);
|
||||
run_until_asked(&mut h, &calls, std::time::Duration::from_millis(500));
|
||||
assert_eq!(calls.load(SeqCst), 0, "nothing is asked at start");
|
||||
|
||||
let mut restarted = PdfCraftApp::new();
|
||||
restarted.restore(&h.state().persist());
|
||||
assert!(!restarted.update_check_at_start, "the choice is kept");
|
||||
|
||||
// Help ▸ Check for updates still asks.
|
||||
h.state_mut().execute("help.check_updates");
|
||||
settle(&mut h);
|
||||
assert_eq!(calls.load(SeqCst), 1);
|
||||
h.get_by_label_contains("PdfCraft 99.0.0 is available");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preferences_turn_the_check_at_start_off() {
|
||||
let (mut h, _calls) = started_with(r#"{"update_check_at_start": true}"#);
|
||||
h.state_mut().execute("app.preferences");
|
||||
h.run_steps(3);
|
||||
h.get_by_label("Check for updates when PdfCraft starts").click();
|
||||
h.run_steps(3);
|
||||
assert!(!h.state().update_check_at_start);
|
||||
// The control channel's option, as an agent sets it.
|
||||
h.state_mut().set_option("update_check_at_start", "true").unwrap();
|
||||
assert!(h.state().update_check_at_start);
|
||||
assert!(h.state_mut().set_option("update_check_at_start", "maybe").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settings_from_the_old_opt_in_leave_the_check_at_start_on() {
|
||||
let mut app = PdfCraftApp::new();
|
||||
app.restore(r#"{"check_updates_at_start": false}"#);
|
||||
assert!(app.update_check_at_start, "a key of the removed option decides nothing");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_check_at_start_offers_the_prompt_and_later_dismisses_it() {
|
||||
let mut h = Harness::builder().with_size(egui::vec2(1200.0, 800.0)).build_eframe(|_cc| {
|
||||
|
||||
@@ -307,6 +307,7 @@ Estimates unchanged.
|
||||
| 2026-11-03 | patch | M5 #5850 review: indirect typewriter intent and composer edits preserve outer whitespace; movement regression commits by outside click; estimates unchanged. |
|
||||
| 2026-11-03 | patch | M5 #5850: Enter adds Fill & Sign line breaks; outside clicks commit, Escape cancels, and saved multiline appearances retain their text and height; estimates unchanged. |
|
||||
| 2026-11-02 | patch | M10 #5743: macOS print jobs carry the job name and owner some vendors' filters need; not yet checked on a physical printer |
|
||||
| 2026-11-02 | patch | M14 #5737, #5460: Preferences turns off the update check at start, on by default |
|
||||
| 2026-11-01 | patch | M7 #5657: header/footer, watermark and background operations commit all selected pages together; later-page errors preserve marks, content, shared/inherited resources and pending edits; estimates unchanged. |
|
||||
| 2026-10-31 | patch | #1577: reconcile optional page shadows with current viewer/organizer and settings modules; CI validation pending. |
|
||||
| 2026-10-31 | patch | M3 #1354: reconcile the launch override with split app/file modules and the structured open result; preserve headless help/version exits. CI validation pending. |
|
||||
|
||||
@@ -1778,8 +1778,8 @@ milestone = "M3"
|
||||
status = "partial"
|
||||
commands = ["help.check_updates"]
|
||||
tools = []
|
||||
evidence = ["crates/ui-egui/tests/it/updates.rs::a_newer_release_is_offered_for_download", "crates/ui-egui/tests/it/updates.rs::an_up_to_date_or_failed_check_says_so", "crates/ui-egui/tests/it/updates.rs::the_check_at_start_offers_the_prompt_and_later_dismisses_it", "crates/ui-egui/tests/it/updates.rs::nothing_is_asked_until_the_user_checks"]
|
||||
notes = "asks GitHub Releases through craft-update at start (desktop app only) and on request, offers the release page, and updates and restarts in place where the install type allows it; an install waits while documents are unsaved"
|
||||
evidence = ["crates/ui-egui/tests/it/updates.rs::a_newer_release_is_offered_for_download", "crates/ui-egui/tests/it/updates.rs::an_up_to_date_or_failed_check_says_so", "crates/ui-egui/tests/it/updates.rs::the_check_at_start_offers_the_prompt_and_later_dismisses_it", "crates/ui-egui/tests/it/updates.rs::nothing_is_asked_until_the_user_checks", "crates/ui-egui/tests/it/updates.rs::the_preference_keeps_the_check_at_start_from_reaching_the_network", "crates/ui-egui/tests/it/updates.rs::settings_from_the_old_opt_in_leave_the_check_at_start_on"]
|
||||
notes = "asks GitHub Releases through craft-update at start (desktop app only) and on request, offers the release page, and updates and restarts in place where the install type allows it; an install waits while documents are unsaved; Preferences ▸ Check for updates when PdfCraft starts (update_check_at_start, on by default) turns the check at start off, leaving only the request from the Help menu"
|
||||
|
||||
[[feature]]
|
||||
id = "view.single-key-accelerators"
|
||||
|
||||
Reference in new issue
Block a user