From 757c35e4a0d42c6bf53286981fb3191513d4242d Mon Sep 17 00:00:00 2001 From: funman300 Date: Tue, 7 Jul 2026 11:00:20 -0700 Subject: [PATCH] fix(engine): degrade gracefully when the platform data dir is missing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit user_theme_dir() panicked on desktop when dirs::data_dir() returned None (broken $HOME / $XDG_*). Return an empty path instead — exactly what the wasm32 branch already did — so theme discovery reports 'no user themes' and the bundled default keeps working. Warns once with the set_user_theme_dir() workaround. CLAUDE.md §2.3: no panic! in runtime logic. Co-Authored-By: Claude Fable 5 --- solitaire_engine/src/assets/user_dir.rs | 51 +++++++++++++------------ 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/solitaire_engine/src/assets/user_dir.rs b/solitaire_engine/src/assets/user_dir.rs index 020116a..4db9637 100644 --- a/solitaire_engine/src/assets/user_dir.rs +++ b/solitaire_engine/src/assets/user_dir.rs @@ -53,12 +53,12 @@ pub fn set_user_theme_dir(path: PathBuf) -> Result<(), PathBuf> { /// Returns the absolute path of the user-themes directory on the /// current platform. /// -/// # Panics -/// -/// Panics if [`solitaire_data::data_dir`] returns `None`, which on -/// desktop indicates a broken `$HOME` / `$XDG_*` configuration. -/// Android always returns `Some`. The panic message names the -/// supported workaround ([`set_user_theme_dir`]). +/// When [`solitaire_data::data_dir`] returns `None` (broken `$HOME` / +/// `$XDG_*` on desktop; always on wasm32, which has no filesystem) this +/// returns an empty path — callers treat that as "no user themes" and +/// the bundled default theme still works. A warning naming the +/// [`set_user_theme_dir`] workaround is logged once. Android always +/// resolves. pub fn user_theme_dir() -> PathBuf { if let Some(p) = USER_THEME_DIR_OVERRIDE.get() { return p.clone(); @@ -76,29 +76,32 @@ fn user_theme_dir_for(data_dir: PathBuf) -> PathBuf { /// Per-target-os resolution of the platform's data dir. Delegates /// to [`solitaire_data::data_dir`] which encapsulates the /// per-target shape (desktop: `dirs::data_dir()`; android: the -/// hardcoded `/data/data//files` sandbox path). Panics -/// only when the underlying resolver returns `None`, which on -/// desktop indicates a broken `$HOME` / `$XDG_*` configuration — -/// the panic message names the supported workaround. +/// hardcoded `/data/data//files` sandbox path). +/// +/// When the resolver returns `None` — always on wasm32 (no +/// filesystem), or a broken `$HOME` / `$XDG_*` configuration on +/// desktop — this degrades to an empty path, which downstream theme +/// scanning treats as "no user themes"; the bundled default theme is +/// unaffected. CLAUDE.md §2.3 forbids panicking here: losing custom +/// themes must not take the whole game down with it. fn detected_platform_data_dir() -> PathBuf { solitaire_data::data_dir().unwrap_or_else(|| { - // On wasm32, data_dir() always returns None — there is no filesystem. - // User themes are not supported in the browser build; return an empty - // path so callers produce a benign empty dir rather than panicking. - #[cfg(target_arch = "wasm32")] - { - PathBuf::new() - } #[cfg(not(target_arch = "wasm32"))] { - panic!( - "user_theme_dir(): platform data directory is unavailable. \ - On Linux check $XDG_DATA_HOME or $HOME; on macOS / Windows \ - the OS reported no Application Support / AppData path. \ - As a workaround call solitaire_engine::assets::user_dir::\ - set_user_theme_dir() before App::run()." - ) + use std::sync::Once; + static WARN_ONCE: Once = Once::new(); + WARN_ONCE.call_once(|| { + bevy::log::warn!( + "user_theme_dir(): platform data directory is unavailable; \ + user themes are disabled. On Linux check $XDG_DATA_HOME or \ + $HOME; on macOS / Windows the OS reported no Application \ + Support / AppData path. As a workaround call \ + solitaire_engine::assets::user_dir::set_user_theme_dir() \ + before App::run()." + ); + }); } + PathBuf::new() }) }