Phase A.3 finding: AppPaths::migration_sentinel was added with intent
during the rebrand-architecture phase but never wired to any caller.
Exhaustive grep across crates/ src-tauri/ src/ docs/ surfaces:
- 1 definition (paths.rs)
- 1 architecture-map description that ASSERTS the method is in use
- 0 production callers
- 0 test references
Both boot-time migrations (migrate_legacy_data_dir +
migrate_tauri_app_data_dir_with_paths) are idempotent by construction:
each re-probes the legacy path via Path::exists() on every boot and
short-circuits on the steady state. A sentinel file would optimise the
probe but is not required for correctness; one syscall per legacy
candidate at startup is negligible.
Per the atomiser principle of removing dead surface area rather than
keeping stale promises:
- Delete AppPaths::migration_sentinel entirely
- Update docs/architecture-map/.../core-paths.md to describe the actual
idempotency model (re-probing) rather than the sentinel pattern that
was never implemented
- Steer future migrations toward storage/src/migrations.rs schema_version
(transactional, survives backup/restore) rather than reintroducing
filesystem sentinels
Verification:
- cargo test -p lumotia-core paths::: 17/17 (no test relied on the method)
- cargo clippy -p lumotia-core --all-targets -- -D warnings: clean
Phase A.4 (stray-magnotia string scan): clean. Every magnotia reference
in the tree is legitimate — migration source paths, documentation, or
test fixtures. The rebrand cascade was thorough.
5.0 KiB
name, type, slice, last_verified
| name | type | slice | last_verified |
|---|---|---|---|
| Core app paths | architecture-map-page | 05-core-storage-hotkey-build | 2026/05/09 |
Core app paths
Where you are: Architecture map → Core, Storage, Hotkey, Build → Core app paths
Plain English summary. Where Lumotia stores its files on disk. One root, derived from the OS conventions (LOCALAPPDATA on Windows, ~/Library/Application Support/Lumotia on macOS, $XDG_DATA_HOME/lumotia on Linux), and named accessor methods for each subdirectory. Every other crate that needs to know "where does the database live?" reaches for lumotia_core::paths::AppPaths::current().
At a glance
- File:
crates/core/src/paths.rs(125 LOC). - External deps: standard library only.
- Public surface:
AppPaths(struct),app_paths,app_data_dir. - Consumers:
lumotia-storage::file_storage(re-exports the path helpers), the Tauri startup code (slice 2), the model manager (slices 3 + 4), the diagnostic-report bundler (slice 2).
What's in here
AppPaths — crates/core/src/paths.rs:6
pub struct AppPaths { app_data_dir: PathBuf }
impl AppPaths {
pub fn current() -> Self; // resolves at construction
pub fn app_data_dir(&self) -> PathBuf;
pub fn database_path(&self) -> PathBuf; // <root>/lumotia.db
pub fn recordings_dir(&self) -> PathBuf; // <root>/recordings
pub fn crashes_dir(&self) -> PathBuf; // <root>/crashes
pub fn logs_dir(&self) -> PathBuf; // <root>/logs
pub fn diagnostic_reports_dir(&self) -> PathBuf; // <root>/diagnostic-reports
pub fn models_dir(&self) -> PathBuf; // <root>/models
pub fn speech_model_dir(&self, id: &ModelId) -> PathBuf; // <root>/models/<id>
pub fn llm_models_dir(&self) -> PathBuf; // <root>/models/llm
}
pub fn app_paths() -> AppPaths;
pub fn app_data_dir() -> PathBuf;
Root resolution — crates/core/src/paths.rs:66
The resolve_app_data_dir function picks the root by cfg(target_os = ...):
| OS | Root |
|---|---|
| Windows | %LOCALAPPDATA%/lumotia |
| macOS | $HOME/Library/Application Support/Lumotia |
| Linux | $HOME/.lumotia if it already exists (legacy), else $XDG_DATA_HOME/lumotia if set, else $HOME/.local/share/lumotia |
| other | $HOME/.lumotia |
The Linux legacy-path branch keeps existing users on ~/.lumotia (early dogfooding default) without forcing a migration when the canonical XDG location is preferred.
Migration idempotency — no sentinel files
There is no sentinel-file pattern. The two boot-time migrations
(migrate_legacy_data_dir for the legacy magnotia data dir, and
migrate_tauri_app_data_dir_with_paths for the Tauri bundle-identifier
move) are both idempotent by construction: each cheaply re-probes for
the legacy path via Path::exists() on every boot and short-circuits
on the steady state. A sentinel file would optimise that probe but is
not required for correctness, and the cost is one syscall per legacy
candidate — negligible at startup.
If a future migration needs cross-restart "this ran already" state
(e.g. a destructive one-shot reorganisation that mutates the legacy
path in place), reach for crates/storage/src/migrations.rs
(the SQLite schema_version table) rather than reintroducing a
filesystem sentinel — schema_version is transactional and survives
backup/restore, sentinel files don't.
Data flow / contract
- All paths are derived from a single root. Changing
resolve_app_data_dironce moves every downstream file. Tested atpaths.rs:111. - Path resolution is pure: only reads env vars (
HOME,LOCALAPPDATA,XDG_DATA_HOME) plusPath::existsfor the Linux legacy probe. AppPaths::current()does not create directories. Callers that need a directory to exist callstd::fs::create_dir_allat write time. The storageinitfunction does this for the database parent (crates/storage/src/database.rs:10).
Tests
crates/core/src/paths.rs:104-125:
derives_all_paths_from_one_base— verifies thatdatabase_path,speech_model_dir(<id>), andllm_models_dirare all rooted at the sameapp_data_dir.
Watch-outs
std::env::var(...).unwrap_or_else(|_| "/tmp".to_string())is the fallback for missingHOME. Should never trigger in practice; defensive.AppPaths::current()reads env vars at every call. Cheap, but repeated calls are wasteful. Slice 2 caches aOnceLock<AppPaths>so the value is resolved once at startup.- No Windows fallback for missing
LOCALAPPDATA. Falls through to.(current working directory). On a misconfigured Windows host this could write the database next to the binary. Not great; the impact is limited to first-run scenarios where the env is broken.
See also
- Storage file paths — re-exports.
- Model registry — model IDs feed into
speech_model_dir. - Slice 2 Tauri startup — caller of
AppPaths::current()at boot.