collective

Browse / github.com/storytold/lightcraft / patch

MENU-APP-UPDATES: --no-update-check skips the startup update check

MENU-APP-UPDATES: --no-update-check skips the startup update check Off `main`. ## Problem Every start of the desktop app asks GitHub for a newer release (#2933, the startup prompt of craft-libs#319's rollout). A throwaway copy of LightCraft (a test or scripted run started with a scratch library and settings folder) has no use for that: it makes a network request at start, and when a newer release exists the prompt opens and takes the keyboard (`updates_ui::blocks_input`), which can break a control-channel smoke run. There was no way to turn the check off. ## Change - `--no-update-check` on the desktop app skips the startup update check. Without it nothing

Status
open
Score
0.00
Adopters
0 other operators
Author
nledford (imported from pull request #3171, open)
Base
3a3e49af53aeff55248419234a05d1a262094092
Size
+101 −21 in 6 files
Review
pending
Shared
2026-10-28
Id
5d2ae90a1ae97b6e3110aa7a

How it went

No other operator has reported using it yet.

Diff

raw · README.md apps/lightcraft/src/launch.rs apps/lightcraft/src/main.rs apps/lightcraft/src/tests.rs docs/control-protocol.md docs/whats-new.md

diff --git a/README.md b/README.md--- a/README.md+++ b/README.md@@ -402,6 +402,7 @@ cargo run --release -p lightcraft                       # opens your library (~/ cargo run --release -p lightcraft -- ~/Pictures/trip    # import your photos (folders are scanned, duplicates skipped) cargo run --release -p lightcraft -- --memory           # a throwaway in-memory demo session (writes nothing) cargo run --release -p lightcraft -- --control 7980     # with the automation channel+cargo run --release -p lightcraft -- --no-update-check  # don't look for a newer release at start (Help ▸ Check for Updates… still works) cargo xtask web --serve                                 # the same app in the browser: http://127.0.0.1:8080/ cargo run --release -p lightcraft-cli -- render photo.jpg -o out.jpg --set light.exposure=0.5 cargo xtask ci                                          # fmt, clippy, HEIF, tests, parity, layers, assets, WASMdiff --git a/apps/lightcraft/src/launch.rs b/apps/lightcraft/src/launch.rs--- a/apps/lightcraft/src/launch.rs+++ b/apps/lightcraft/src/launch.rs@@ -13,6 +13,8 @@ OPTIONS:   --memory         throwaway in-memory session (alias --demo); nothing is saved   --control PORT   serve the JSON-lines control channel on 127.0.0.1:PORT (env LIGHTCRAFT_CONTROL_PORT;                    see docs/control-protocol.md)+  --no-update-check+                   don't look for a newer release at start (Help > Check for Updates… still works)   --version, --help  ENVIRONMENT:@@ -28,6 +30,50 @@ ENVIRONMENT:                    <settings folder>/logs/lightcraft.log (previous runs: lightcraft.1.log, lightcraft.2.log) "; +/// What the command line asks for (see [`parse_args`]).+#[derive(Debug, PartialEq)]+pub(crate) struct LaunchArgs {+    pub control_port: Option<u16>,+    /// Files and folders to import, as written (see [`launch_imports`]).+    pub files: Vec<String>,+    pub seed_demo: bool,+    pub in_memory: bool,+    pub library_dir: Option<std::path::PathBuf>,+    /// Whether the window looks for a newer release once it is up; `--no-update-check` turns it off.+    pub startup_update_check: bool,+}++/// How the command line was read.+#[derive(Debug, PartialEq)]+pub(crate) enum Parsed {+    Run(LaunchArgs),+    Version,+    Help,+    /// An option that doesn't exist.+    Unknown(String),+}++/// Reads the command line (without the program name); `control_port` is the one from+/// `LIGHTCRAFT_CONTROL_PORT`, which `--control` replaces.+pub(crate) fn parse_args(args: impl IntoIterator<Item = String>, control_port: Option<u16>) -> Parsed {+    let mut parsed = LaunchArgs { control_port, files: Vec::new(), seed_demo: true, in_memory: false, library_dir: None, startup_update_check: true };+    let mut args = args.into_iter();+    while let Some(a) = args.next() {+        match a.as_str() {+            "--control" => parsed.control_port = control_port_from("--control", Some(args.next().unwrap_or_default())),+            "--library" => parsed.library_dir = args.next().map(std::path::PathBuf::from),+            "--no-demo" => parsed.seed_demo = false,+            "--memory" | "--demo" => parsed.in_memory = true,+            "--no-update-check" => parsed.startup_update_check = false,+            "--version" => return Parsed::Version,+            "-h" | "--help" => return Parsed::Help,+            o if o.starts_with("--") => return Parsed::Unknown(a),+            _ => parsed.files.push(a),+        }+    }+    Parsed::Run(parsed)+}+ /// The launch arguments' files and folders to import (absolute), and a notice for each one that /// is not imported (see [`lightcraft_engine::import::launch_path`]; issue #374). pub(crate) fn launch_imports(args: &[String]) -> (Vec<String>, Vec<String>) {diff --git a/apps/lightcraft/src/main.rs b/apps/lightcraft/src/main.rs--- a/apps/lightcraft/src/main.rs+++ b/apps/lightcraft/src/main.rs@@ -1,6 +1,6 @@ //! LightCraft desktop app. //!-//! Usage: `lightcraft [--library DIR | --memory] [--no-demo] [--control <port>] [files or folders…]`+//! Usage: `lightcraft [--library DIR | --memory] [--no-demo] [--no-update-check] [--control <port>] [files or folders…]` //! //! The library (catalog, presets, thumbnail cache) lives in `--library DIR`, else //! `$LIGHTCRAFT_LIBRARY`, else — with Settings ▸ General ▸ At launch set to *Ask me*, or Alt/Option@@ -55,7 +55,10 @@ use gpu_crash::{gpu_crash_check, gpu_lost_check_at, gpu_lost_marker_path, gpu_lo use gpu_crash::{gpu_crash_check_at, gpu_crash_notice, window_wgpu_options}; #[cfg(test)] use launch::launch_imports_in;-use launch::{HELP, control_port_from, launch_imports, log_dir, panic_log_path_with, startup_failed, stopped_text, stopped_while_running};+use launch::{+    HELP, LaunchArgs, Parsed, control_port_from, launch_imports, log_dir, panic_log_path_with, parse_args, startup_failed, stopped_text,+    stopped_while_running,+}; #[cfg(test)] use platform::{explorer_select_arg, explorer_select_command, windows_open_url_command}; use prefs::{PrefsWriter, as_remembered, load_prefs, prefs_path};@@ -146,33 +149,23 @@ fn main() -> eframe::Result {         logging::record_panics(logger);     }     alloc_release::install();-    let mut control_port = control_port_from("LIGHTCRAFT_CONTROL_PORT", std::env::var("LIGHTCRAFT_CONTROL_PORT").ok().filter(|v| !v.is_empty()));-    let mut files = Vec::new();-    let mut seed_demo = true;-    let mut in_memory = false;-    let mut library_dir: Option<std::path::PathBuf> = None;-    let mut args = std::env::args().skip(1);-    while let Some(a) = args.next() {-        match a.as_str() {-            "--control" => control_port = control_port_from("--control", Some(args.next().unwrap_or_default())),-            "--library" => library_dir = args.next().map(std::path::PathBuf::from),-            "--no-demo" => seed_demo = false,-            "--memory" | "--demo" => in_memory = true,-            "--version" => {+    let env_port = control_port_from("LIGHTCRAFT_CONTROL_PORT", std::env::var("LIGHTCRAFT_CONTROL_PORT").ok().filter(|v| !v.is_empty()));+    let LaunchArgs { control_port, files, seed_demo, in_memory, library_dir, startup_update_check } =+        match parse_args(std::env::args().skip(1), env_port) {+            Parsed::Run(args) => args,+            Parsed::Version => {                 println!("lightcraft {}", env!("CARGO_PKG_VERSION"));                 return Ok(());             }-            "-h" | "--help" => {+            Parsed::Help => {                 print!("{HELP}");                 return Ok(());             }-            o if o.starts_with("--") => {+            Parsed::Unknown(o) => {                 eprintln!("lightcraft: unknown option `{o}`\n\n{HELP}");                 std::process::exit(2);             }-            _ => files.push(a),-        }-    }+        };     // The log file: opened after the arguments, so `--version`, `--help` and a usage error leave     // no file behind. Records logged until now are written to it first.     let mut log_file = None;@@ -267,7 +260,7 @@ fn main() -> eframe::Result {                 // HDR display: SDR unless the surface, platform and display all allow HDR                 hdr_display::report(cc.wgpu_render_state.as_ref().map(|r| r.target_format));                 let mut app = LightcraftApp::new(session, services(cc, app_log_file.as_deref()));-                app.set_startup_update_check(true);+                app.set_startup_update_check(startup_update_check);                 if let Some(ui) = prefs {                     app.ui = ui;                 }diff --git a/apps/lightcraft/src/tests.rs b/apps/lightcraft/src/tests.rs--- a/apps/lightcraft/src/tests.rs+++ b/apps/lightcraft/src/tests.rs@@ -462,3 +462,33 @@ fn on_exit_ends_the_process_on_macos() {     assert!(stdout.contains("calling on_exit"), "the child didn't run the test: {stdout}");     assert_eq!(stdout.contains("on_exit returned"), !cfg!(target_os = "macos"), "{stdout}"); }++fn parsed(args: &[&str]) -> LaunchArgs {+    match parse_args(args.iter().map(|a| a.to_string()), None) {+        Parsed::Run(a) => a,+        other => panic!("expected a launch, got {other:?}"),+    }+}++/// `--no-update-check` (throwaway test copies must not call GitHub at start) turns the startup+/// update check off and nothing else; without it the check stays on.+#[test]+fn no_update_check_turns_the_startup_update_check_off() {+    assert!(parsed(&[]).startup_update_check, "the default is to check");+    assert!(parsed(&["--memory", "--control", "7981", "Trip"]).startup_update_check, "other options leave it on");+    let off = parsed(&["--memory", "--no-update-check", "Trip"]);+    assert!(!off.startup_update_check);+    // the flag is only that: the other options and the files still read the same+    assert_eq!(LaunchArgs { startup_update_check: true, ..off }, parsed(&["--memory", "Trip"]));+    // a value of --library is that folder, never the flag+    let lib = parsed(&["--library", "--no-update-check"]);+    assert!(lib.startup_update_check);+    assert_eq!(lib.library_dir, Some(std::path::PathBuf::from("--no-update-check")));+}++/// The flag is a known option (an unknown one exits with a usage error) and `--help` lists it.+#[test]+fn no_update_check_is_a_known_option_and_listed_in_the_help() {+    assert_eq!(parse_args(["--no-update-chek".to_string()], None), Parsed::Unknown("--no-update-chek".into()));+    assert!(HELP.contains("--no-update-check"));+}diff --git a/docs/control-protocol.md b/docs/control-protocol.md--- a/docs/control-protocol.md+++ b/docs/control-protocol.md@@ -51,6 +51,9 @@ empty value disables the settings directory, while the emergency panic log retai Explicit model-directory environment variables still take precedence over their defaults. Also pass `--library DIR` with a separate scratch library: the configuration override does not change the default photo-library location. Do not use `--memory` or `LIGHTCRAFT_NO_PREFS` when testing persistence.+Pass `--no-update-check` as well: the startup check for a newer release (a request to GitHub) is skipped, so a+throwaway run makes no network request at start, and a newer release can't open the update prompt, which takes the+keyboard. Help ▸ Check for Updates… (`app.checkForUpdates`) still checks when asked.  A native acceptance run must reject the stderr message `rendering headlessly`: `ui.screenshot` automatically uses the CPU fallback after two seconds without a presented frame. That fallback is usefuldiff --git a/docs/whats-new.md b/docs/whats-new.md--- a/docs/whats-new.md+++ b/docs/whats-new.md@@ -1,5 +1,12 @@ # What's new in LightCraft +## 2026-10-27++### `--no-update-check` for throwaway copies+- Starting LightCraft with `--no-update-check` skips the look for a newer release at start, so a throwaway copy (a test+  or scripted run) makes no request to GitHub and never opens the update prompt. Help ▸ Check for Updates… still+  checks when you ask; without the option nothing changes.+ ## 2026-10-24  ### Relative-date filters and smart albums follow the clock