205ad6f646
Implements Phase 5 of CARD_PLAN.md. Phase 3 (asset sources) and
Phase 7 (zip importer) both depend on this so it goes first.
solitaire_engine/src/assets/user_dir.rs
user_theme_dir() -> PathBuf
Desktop (Linux/macOS/Windows): joins dirs::data_dir() with
"solitaire_quest/themes" — same parent as the rest of the
project's per-user files (settings.json, stats.json, etc.)
Mobile (Android/iOS): reads a process-wide OnceLock populated
by set_user_theme_dir() at entry-point bootstrap. Panics with a
targeted message if the override is missing — there is no
platform default we can guess that won't be wrong inside iOS
sandboxing or the Android storage model.
set_user_theme_dir(PathBuf) -> Result<(), PathBuf>
First-write-wins. Mobile entry points call this before App::run().
The plan suggested the `directories` crate; reused the existing `dirs`
workspace dep instead to keep the dependency surface minimal — both
crates share an author and the platform behaviour we need is identical.
3 new tests covering pure path composition (desktop nesting + empty
root) and a desktop-target-gated check that the detected data dir is
absolute. The OnceLock override is intentionally not unit-tested
because asserting its semantics would pollute global state for any
sibling test that calls `user_theme_dir()`.