Background
Follow-up tracking issue for Scope B of #223, intentionally deferred when #227 landed Scope A (the doc/resolution-consistency fix for all-smi record's default output path). #227 deliberately left the cache-path code untouched so this platform-aware work could be decided and shipped on its own — it relocates files on macOS and Windows and needs a migration/changelog note.
Decision (from review on this issue): adopt B1 — resolve every cache path (record output, energy WAL, users CSV) through a single platform-aware helper built on dirs::cache_dir(), and retire the hard-coded ~/.cache/... strings. Rationale below.
Why B1 (it's more than "platform correctness")
A code audit shows the cache base is currently resolved three different ways, so the locations already diverge per platform today:
| Consumer |
Source |
Linux ($XDG_CACHE_HOME set) |
macOS |
Windows |
| users CSV export |
cache_dir_for_all_smi() — src/view/event_handler.rs:671 |
$XDG_CACHE_HOME/all-smi |
~/.cache/all-smi |
%LOCALAPPDATA%\all-smi\cache |
| record output (default) |
record.output_dir = "~/.cache/all-smi/records" — src/common/config_file.rs:256 |
~/.cache/all-smi/records (ignores $XDG_CACHE_HOME) |
~/.cache/all-smi/records |
…\.cache\all-smi\records (Unix-style dotdir in the profile) |
| energy WAL |
wal_path = "~/.cache/all-smi/energy-wal.bin" — src/common/config.rs:197 |
~/.cache/all-smi/energy-wal.bin (ignores $XDG_CACHE_HOME) |
~/.cache/all-smi/energy-wal.bin |
…\.cache\all-smi\energy-wal.bin (dotdir) |
- Windows is already split: the users CSV lands under
%LOCALAPPDATA%\all-smi\cache, but record output and the energy WAL land in a Unix-style .cache dotdir inside the user profile.
- Linux is split when
$XDG_CACHE_HOME is set: the users CSV honors it; record output and the WAL ignore it.
- macOS puts all three under
~/.cache/... (none use the Apple-correct ~/Library/Caches/...).
cache_dir_for_all_smi() is a hand-rolled reimplementation of dirs::cache_dir(), with a comment that it avoids pulling in the dirs crate "for a single lookup." That rationale no longer holds: dirs is already a direct dependency (used by src/common/paths.rs for config_dir()/home_dir()), and there are now three lookups, not one.
- The config path is already platform-aware via
dirs::config_dir() (src/common/paths.rs:95), so the cache path being hard-coded is an internal inconsistency.
B1 collapses these three paths into one helper, deletes cache_dir_for_all_smi(), and aligns the cache layer with the config layer.
Target layout
Resolve a single cache root via dirs::cache_dir().join("all-smi"):
| Platform |
dirs::cache_dir() |
all-smi cache root |
| Linux |
$XDG_CACHE_HOME or ~/.cache |
…/all-smi |
| macOS |
~/Library/Caches |
~/Library/Caches/all-smi |
| Windows |
%LOCALAPPDATA% |
%LOCALAPPDATA%\all-smi |
Then: record output → <cache-root>/records/, energy WAL → <cache-root>/energy-wal.bin, users CSV → <cache-root>/users-<ts>.csv.
Recordings also live under cache_dir() (decided)
dirs::cache_dir() maps to OS-purgeable locations (macOS may reclaim ~/Library/Caches under storage pressure; cleanup tools target cache dirs). The energy WAL (crash-recovery scratch) and users CSV (on-demand export) are unambiguously cache-like.
record outputs are user-created artifacts, so dirs::data_local_dir() was considered for them specifically. Decision: keep all three — record output included — under dirs::cache_dir(). The default output path is a convenience fallback; operators who want durability already pass -o <path>, and a single cache root keeps the resolver and the docs simple and consistent. (data_dir() was explicitly rejected: on Windows it is Roaming %APPDATA%, which would sync potentially-large recordings across machines.)
Migration
- Linux without
$XDG_CACHE_HOME (the common case): dirs::cache_dir() == ~/.cache, so nothing moves — no migration needed.
- macOS, Windows, and Linux with
$XDG_CACHE_HOME set: the cache root relocates. The affected data is low-stakes — recordings remain replayable via an explicit view --replay <path>, the WAL is recoverable scratch, and the users CSV is an on-demand export whose path is printed to the user.
- Ship a release/changelog note documenting the new per-platform locations, plus an optional one-time best-effort migration that moves an existing
~/.cache/all-smi/{records,energy-wal.bin} to the new root only when the new root is empty (never overwrite).
Implementation notes
- Add a
cache_dir() helper in src/common/paths.rs mirroring the existing config_dir(): dirs::cache_dir().map(|d| d.join(APP_DIR_NAME)).
- Route all three consumers through it; delete the hand-rolled
cache_dir_for_all_smi() in src/view/event_handler.rs (≈ lines 667–695) and its bespoke XDG_CACHE_HOME/LOCALAPPDATA/HOME probing.
- Reconcile the Windows users-CSV subpath: today it is
%LOCALAPPDATA%\all-smi\cache; under the shared helper it becomes %LOCALAPPDATA%\all-smi.
- Keep
expand_tilde for user-supplied values only (explicit -o <path>, and config-file record.output_dir / energy wal_path overrides); the compiled default should come from the platform helper, not a ~/.cache/... string literal.
- Update the defaults in
src/common/config_file.rs (output_dir) and src/common/config.rs (wal_path) so they derive from the helper rather than hard-coded ~/.cache/... literals.
- Preserve the existing symlink /
O_NOFOLLOW / 0o600 defenses around the users-CSV and any new cache writes.
Affected files
src/common/paths.rs — new cache_dir() helper.
src/common/config_file.rs — record.output_dir default.
src/common/config.rs — energy wal_path default.
src/view/event_handler.rs — delete cache_dir_for_all_smi(), use the helper.
src/metrics/energy_wal.rs — consume the resolved WAL path (no hard-coded literal).
- Docs:
src/cli.rs (--output long_help, ALL_SMI_ENERGY_WAL_PATH text), README.md, docs/man/all-smi.1 if applicable.
Acceptance criteria
References
Background
Follow-up tracking issue for Scope B of #223, intentionally deferred when #227 landed Scope A (the doc/resolution-consistency fix for
all-smi record's default output path). #227 deliberately left the cache-path code untouched so this platform-aware work could be decided and shipped on its own — it relocates files on macOS and Windows and needs a migration/changelog note.Decision (from review on this issue): adopt B1 — resolve every cache path (record output, energy WAL, users CSV) through a single platform-aware helper built on
dirs::cache_dir(), and retire the hard-coded~/.cache/...strings. Rationale below.Why B1 (it's more than "platform correctness")
A code audit shows the cache base is currently resolved three different ways, so the locations already diverge per platform today:
$XDG_CACHE_HOMEset)cache_dir_for_all_smi()—src/view/event_handler.rs:671$XDG_CACHE_HOME/all-smi~/.cache/all-smi%LOCALAPPDATA%\all-smi\cacherecord.output_dir = "~/.cache/all-smi/records"—src/common/config_file.rs:256~/.cache/all-smi/records(ignores$XDG_CACHE_HOME)~/.cache/all-smi/records…\.cache\all-smi\records(Unix-style dotdir in the profile)wal_path = "~/.cache/all-smi/energy-wal.bin"—src/common/config.rs:197~/.cache/all-smi/energy-wal.bin(ignores$XDG_CACHE_HOME)~/.cache/all-smi/energy-wal.bin…\.cache\all-smi\energy-wal.bin(dotdir)%LOCALAPPDATA%\all-smi\cache, but record output and the energy WAL land in a Unix-style.cachedotdir inside the user profile.$XDG_CACHE_HOMEis set: the users CSV honors it; record output and the WAL ignore it.~/.cache/...(none use the Apple-correct~/Library/Caches/...).cache_dir_for_all_smi()is a hand-rolled reimplementation ofdirs::cache_dir(), with a comment that it avoids pulling in thedirscrate "for a single lookup." That rationale no longer holds:dirsis already a direct dependency (used bysrc/common/paths.rsforconfig_dir()/home_dir()), and there are now three lookups, not one.dirs::config_dir()(src/common/paths.rs:95), so the cache path being hard-coded is an internal inconsistency.B1 collapses these three paths into one helper, deletes
cache_dir_for_all_smi(), and aligns the cache layer with the config layer.Target layout
Resolve a single cache root via
dirs::cache_dir().join("all-smi"):dirs::cache_dir()$XDG_CACHE_HOMEor~/.cache…/all-smi~/Library/Caches~/Library/Caches/all-smi%LOCALAPPDATA%%LOCALAPPDATA%\all-smiThen: record output →
<cache-root>/records/, energy WAL →<cache-root>/energy-wal.bin, users CSV →<cache-root>/users-<ts>.csv.Recordings also live under
cache_dir()(decided)dirs::cache_dir()maps to OS-purgeable locations (macOS may reclaim~/Library/Cachesunder storage pressure; cleanup tools target cache dirs). The energy WAL (crash-recovery scratch) and users CSV (on-demand export) are unambiguously cache-like.recordoutputs are user-created artifacts, sodirs::data_local_dir()was considered for them specifically. Decision: keep all three — record output included — underdirs::cache_dir(). The default output path is a convenience fallback; operators who want durability already pass-o <path>, and a single cache root keeps the resolver and the docs simple and consistent. (data_dir()was explicitly rejected: on Windows it is Roaming%APPDATA%, which would sync potentially-large recordings across machines.)Migration
$XDG_CACHE_HOME(the common case):dirs::cache_dir()==~/.cache, so nothing moves — no migration needed.$XDG_CACHE_HOMEset: the cache root relocates. The affected data is low-stakes — recordings remain replayable via an explicitview --replay <path>, the WAL is recoverable scratch, and the users CSV is an on-demand export whose path is printed to the user.~/.cache/all-smi/{records,energy-wal.bin}to the new root only when the new root is empty (never overwrite).Implementation notes
cache_dir()helper insrc/common/paths.rsmirroring the existingconfig_dir():dirs::cache_dir().map(|d| d.join(APP_DIR_NAME)).cache_dir_for_all_smi()insrc/view/event_handler.rs(≈ lines 667–695) and its bespokeXDG_CACHE_HOME/LOCALAPPDATA/HOMEprobing.%LOCALAPPDATA%\all-smi\cache; under the shared helper it becomes%LOCALAPPDATA%\all-smi.expand_tildefor user-supplied values only (explicit-o <path>, and config-filerecord.output_dir/ energywal_pathoverrides); the compiled default should come from the platform helper, not a~/.cache/...string literal.src/common/config_file.rs(output_dir) andsrc/common/config.rs(wal_path) so they derive from the helper rather than hard-coded~/.cache/...literals.O_NOFOLLOW/0o600defenses around the users-CSV and any new cache writes.Affected files
src/common/paths.rs— newcache_dir()helper.src/common/config_file.rs—record.output_dirdefault.src/common/config.rs— energywal_pathdefault.src/view/event_handler.rs— deletecache_dir_for_all_smi(), use the helper.src/metrics/energy_wal.rs— consume the resolved WAL path (no hard-coded literal).src/cli.rs(--outputlong_help,ALL_SMI_ENERGY_WAL_PATHtext),README.md,docs/man/all-smi.1if applicable.Acceptance criteria
cache_dir()helper exists insrc/common/paths.rs(built ondirs::cache_dir()), mirroringconfig_dir().<cache_dir()>/all-smi/...through that one helper;cache_dir_for_all_smi()is removed and records are not split out todata_local_dir.~/.cache/all-smi/...default literals are gone fromconfig_file.rsandconfig.rs;expand_tildeis used only for user-supplied overrides.--help, source docs, README, and the manpage agree on the new defaults.O_NOFOLLOW/0o600write protections are preserved.References