Skip to content

feat: Move generated app state to XDG state/cache dirs - #407

Merged
LargeModGames merged 39 commits into
LargeModGames:mainfrom
DinoLeung:xdg-config-path
Aug 6, 2026
Merged

LargeModGames merged 39 commits into
LargeModGames:mainfrom
DinoLeung:xdg-config-path

Conversation

@DinoLeung

@DinoLeung DinoLeung commented Jul 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Move generated runtime/app state out of the config directory and into XDG state/cache locations.

This separates user-authored configuration from app-generated state. config.yml remains suitable for hand editing or declarative management, while runtime changes such as volume, layout, active source, radio favorites, history, and token/cache files move to the appropriate XDG state/cache locations.

  • Keeps user-authored config in $XDG_CONFIG_HOME/spotatui.
  • Stores state.yml, listening history, and Spotify OAuth token cache under $XDG_STATE_HOME/spotatui.
  • Stores native streaming credentials/cache under $XDG_CACHE_HOME/spotatui/streaming_cache.
  • Persists volume changes through runtime state and flushes pending state saves before CLI command exit.
  • Documents the updated config/state/cache ownership.

Testing

  • cargo fmt --all
  • cargo test --no-default-features --features telemetry
    • 504 passed
  • cargo clippy --no-default-features --features telemetry -- -D warnings
  • cargo test
    • 764 passed
  • cargo test --features all-sources
    • 837 passed, 15 ignored
  • cargo clippy --features all-sources -- -D warnings
  • cargo check

Additional notes

Existing config-dir app data is migrated on first use when the new target path does not already exist. This includes legacy runtime fields/radio favorites from config.yml, listening history, last_session.yml, Spotify OAuth token caches, and native streaming credentials/audio cache.

If a new state/cache target already exists, the legacy file or directory is left in place instead of being merged or overwritten; users may need to move or remove legacy files manually only in that conflict case.

Summary by CodeRabbit

New Features

  • Added XDG-compliant locations for configuration, runtime state, history, cache, credentials, plugins, and streaming data.
  • Runtime settings now persist separately, including volume, shuffle, layout, active source, announcements, and saved radio stations.
  • Added migration support for existing configuration and state files.

Bug Fixes

  • Improved radio-station merging, deduplication, sanitization, and protection of configured stations.
  • Invalid configuration values now fall back to defaults with a warning.
  • Improved secure persistence for sensitive files and streaming data.

Documentation

  • Updated configuration, plugin, keybinding, theme, streaming, scripting, setup, playlist, and migration guidance, including local playlist locations and persisted runtime settings.

DinoLeung added 12 commits July 24, 2026 20:44
chore: use xdg config path for user config

chore: use xdg state path for last session

chore: use xdg state path for histories

chore: use xdg cache path for streaming cache

chore: use xdg config path for runtime client auth config

chore: update comments to not explicitly reference `~/.config/spotatui/`

chore: show actual config file path in first run prompt
introduce a core state module for machine-managed app state that will
move out of config.yml, including volume, shuffle, active source,
announcement state, layout sizes, radio stations, and sync token.

resolve the state file through the XDG state directory, save it
atomically with private file permissions, and add focused round-trip and
sanitization coverage.
Separate app-managed runtime state into state.yml while keeping
config.yml for user-authored preferences and startup overrides.
Move Spotify OAuth token cache out of the config directory and into the
XDG state directory while keeping client.yml as user configuration.

Gate cache-dir path helpers behind the streaming feature and document
the state/cache locations for history, token cache, and native streaming
cache.

Persist volume changes through runtime state and flush pending state
saves before CLI command exit.
@coderabbitai

coderabbitai Bot commented Jul 28, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5f54d265-8771-49ed-86b7-c7b1f5c04543

📥 Commits

Reviewing files that changed from the base of the PR and between 0016e5d and 372043a.

📒 Files selected for processing (1)
  • src/core/auth.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/core/auth.rs

📝 Walkthrough

Walkthrough

Spotatui separates startup configuration from persisted runtime state. It adds XDG-aware config, cache, and state paths, migrates legacy data, and updates playback, layout, radio, UI, plugin, and documentation code.

Changes

Runtime state migration

Layer / File(s) Summary
Runtime-state and storage foundation
src/core/state.rs, src/core/paths.rs, src/core/user_config.rs, src/core/auth.rs
Adds runtime-state schemas, sparse persistence, sanitization, atomic private writes, optional configuration overrides, and XDG-aware directory resolution.
Migration and startup integration
src/core/migrations.rs, src/core/config.rs, src/runtime.rs, src/core/app.rs, src/core/first_run.rs
Migrates legacy files, caches, and configuration fields without overwriting existing targets. Startup loads runtime state, and App schedules and flushes state patches.
Playback, layout, radio, and UI integration
src/core/layout.rs, src/infra/*, src/tui/*
Playback uses runtime volume and shuffle values. Layout, radio stations, source selection, announcements, resizing, and rendering use runtime state.
Plugin API and storage paths
src/core/plugin_api.rs, src/infra/scripting/*, src/infra/history.rs, src/infra/player/streaming.rs, src/core/persisted_playback.rs
Plugin snapshots expose runtime-backed values. History, playback sessions, streaming caches, credentials, and device IDs use state or cache paths.
Documentation and examples
README.md, PLUGINS.md, CHANGELOG.md, docs/*, examples/plugins/*, src/cli/plugin.rs
Documentation, CLI help, and plugin examples describe XDG-aware configuration, state, cache, and plugin locations.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title uses the permitted feat: prefix and concisely describes the PR's main change.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (2)
src/core/plugin_api.rs (1)

423-429: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add regression coverage for the runtime-backed snapshot.

Test that changing RuntimeState updates all five migrated fields while UserConfig.behavior remains unchanged. The new values are exposed to plugins through src/infra/scripting/engine.rs.

As per coding guidelines, behavior changes require adding or adjusting Rust tests.

Also applies to: 474-479

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/plugin_api.rs` around lines 423 - 429, Add Rust regression coverage
for config_snapshot, verifying that changing RuntimeState updates all five
migrated snapshot fields while UserConfig.behavior remains unchanged. Exercise
the plugin-facing values exposed through the scripting engine path, and preserve
existing behavior for the unchanged user configuration.

Source: Coding guidelines

src/core/user_config.rs (1)

47-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Same-named helper with different semantics than state.rs's sanitized_radio_stations.

This one dedupes by URL; crate::core::state's module-level sanitized_radio_stations (used for RuntimeState/state.yml) doesn't. Two functions with the same name and near-identical purpose diverging in behavior is easy to lose track of during future edits.

Consider extracting one shared helper (e.g. in crate::core::state, taking a slice or iterator) and reusing it from both user_config.rs and state.rs, so both call sites get the same trim/empty-filter/dedupe guarantees for free.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/user_config.rs` around lines 47 - 64, Consolidate the duplicated
sanitized_radio_stations helpers by defining one shared implementation in
crate::core::state and reusing it from both user_config.rs and state.rs. Ensure
the shared helper consistently trims names and URLs, filters empty values, and
deduplicates stations by URL, while preserving both callers’ existing behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/plugins/README.md`:
- Around line 24-35: Make XDG path handling consistent across all listed sites:
in examples/plugins/README.md lines 24-35 and the install commands in
examples/plugins/accent-cycler.lua lines 6-10,
examples/plugins/now-playing-webhook.lua lines 7-9, and
examples/plugins/queue-browser.lua lines 6-10, normalize relative
XDG_CONFIG_HOME values to the application fallback before creating or copying
files; document in docs/native-streaming.md lines 29-37 that both
XDG_CONFIG_HOME and XDG_CACHE_HOME must be absolute, and in docs/scripting.md
lines 9-10 and docs/themes.md lines 24-26 that XDG_CONFIG_HOME is honored only
when absolute.

In `@README.md`:
- Around line 222-230: Update the XDG documentation and installation examples to
state that XDG_CONFIG_HOME, XDG_STATE_HOME, and XDG_CACHE_HOME are used only
when set to absolute paths; unset or relative values must use the HOME-based
fallback. In README.md (222-230 and 329-336), PLUGINS.md (20-23),
docs/configuration.md (14-14), and docs/keybindings.md (40-41), revise the prose
and config_home migration calculation accordingly. In
examples/plugins/session-stats/main.lua (7-9),
examples/plugins/track-info-popup.lua (4-6), and
examples/plugins/track-notifier.lua (4-6), validate XDG_CONFIG_HOME as absolute
before using it for copying, otherwise use the fallback path.

In `@src/infra/queue/dispatch.rs`:
- Around line 95-98: Persist runtime state after each ChangeVolume handler
updates RuntimeState: add the established save operation in
src/infra/queue/dispatch.rs lines 95-98, src/infra/subsonic/dispatch.rs lines
120-125, and src/infra/youtube/dispatch.rs lines 122-126, while preserving the
existing volume updates and return behavior.

In `@src/infra/radio/dispatch.rs`:
- Around line 89-93: Update the IoEvent::ChangeVolume branch to call
schedule_state_save() after assigning runtime_state.volume_percent, ensuring the
changed volume is queued for persistence before returning true.

In `@src/runtime.rs`:
- Around line 1111-1129: Gate the compatibility assignments for volume_percent,
sidebar_width_percent, playbar_height_rows, and library_height_percent behind
should_save_initial_state so they run only during initial state creation.
Preserve values loaded from state.yml on subsequent launches, and keep saving
the resulting migrated runtime state through the existing state_path persistence
block.

In `@src/tui/handlers/resize.rs`:
- Around line 67-86: Update reset_layout to clamp the config-derived
playbar_height_rows value to MAX_PLAYBAR_ROWS, matching the limit enforced by
increase_playbar_height. Preserve the existing default fallback and state-save
behavior.

---

Nitpick comments:
In `@src/core/plugin_api.rs`:
- Around line 423-429: Add Rust regression coverage for config_snapshot,
verifying that changing RuntimeState updates all five migrated snapshot fields
while UserConfig.behavior remains unchanged. Exercise the plugin-facing values
exposed through the scripting engine path, and preserve existing behavior for
the unchanged user configuration.

In `@src/core/user_config.rs`:
- Around line 47-64: Consolidate the duplicated sanitized_radio_stations helpers
by defining one shared implementation in crate::core::state and reusing it from
both user_config.rs and state.rs. Ensure the shared helper consistently trims
names and URLs, filters empty values, and deduplicates stations by URL, while
preserving both callers’ existing behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9b4de3df-1b0b-4452-bc35-d12b5db9aaf5

📥 Commits

Reviewing files that changed from the base of the PR and between 7b87065 and bf31e42.

📒 Files selected for processing (53)
  • PLUGINS.md
  • README.md
  • docs/configuration.md
  • docs/keybindings.md
  • docs/native-streaming.md
  • docs/scripting.md
  • docs/themes.md
  • examples/plugins/README.md
  • examples/plugins/accent-cycler.lua
  • examples/plugins/now-playing-webhook.lua
  • examples/plugins/queue-browser.lua
  • examples/plugins/session-stats/main.lua
  • examples/plugins/track-info-popup.lua
  • examples/plugins/track-notifier.lua
  • src/cli/plugin.rs
  • src/core/app.rs
  • src/core/config.rs
  • src/core/first_run.rs
  • src/core/layout.rs
  • src/core/mod.rs
  • src/core/paths.rs
  • src/core/persisted_playback.rs
  • src/core/plugin_api.rs
  • src/core/state.rs
  • src/core/user_config.rs
  • src/infra/history.rs
  • src/infra/local/dispatch.rs
  • src/infra/media_metadata.rs
  • src/infra/network/native_shuffle.rs
  • src/infra/network/playback.rs
  • src/infra/network/utils.rs
  • src/infra/player/events.rs
  • src/infra/player/streaming.rs
  • src/infra/queue/dispatch.rs
  • src/infra/radio/dispatch.rs
  • src/infra/radio/mod.rs
  • src/infra/scripting/engine.rs
  • src/infra/subsonic/dispatch.rs
  • src/infra/youtube/dispatch.rs
  • src/infra/youtube/playlists.rs
  • src/runtime.rs
  • src/tui/handlers/announcement_prompt.rs
  • src/tui/handlers/library.rs
  • src/tui/handlers/mod.rs
  • src/tui/handlers/mouse.rs
  • src/tui/handlers/playlist.rs
  • src/tui/handlers/resize.rs
  • src/tui/handlers/search_results.rs
  • src/tui/handlers/select_device.rs
  • src/tui/runner.rs
  • src/tui/ui/library.rs
  • src/tui/ui/lyrics.rs
  • src/tui/ui/player.rs

Comment thread examples/plugins/README.md Outdated
Comment thread README.md Outdated
Comment thread src/infra/queue/dispatch.rs
Comment thread src/infra/radio/dispatch.rs
Comment thread src/runtime.rs Outdated
Comment thread src/tui/handlers/resize.rs Outdated
@DinoLeung DinoLeung changed the title Move generated app state to XDG state/cache dirs feat: Move generated app state to XDG state/cache dirs Jul 28, 2026
Document that XDG directory variables are honored only when set to
absolute paths, with HOME-based fallbacks for unset or relative values.
Update plugin install and migration snippets to mirror runtime path
resolution.
Persist decoded-backend volume changes through scheduled runtime state
saves.

Apply configured volume and layout defaults only when the corresponding
runtime state fields are missing, so saved state.yml values remain
authoritative after startup.

Clamp config-derived playbar height during layout reset and document the
initial-default behavior.
Add regression coverage for config_snapshot and the Lua-facing
spotatui.config() path to ensure migrated behavior fields come from
RuntimeState without mutating UserConfig.

Share radio station sanitization between runtime state and user config.

@LargeModGames LargeModGames left a comment •

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Free-source users get pushed into a forced Spotify OAuth flow on upgrade (inline on src/runtime.rs).
  2. Listening history is silently orphaned, along with last_session.yml (inline on src/infra/history.rs).
  3. The window for doing this migration later closes on the first config save (inline on src/core/user_config.rs).

All three are covered by one startup shim, run before state::load:

  • If <state>/state.yml is absent, deserialize the legacy keys off the raw config.yml into a small LegacyRuntimeKeys struct and seed PersistedRuntimeState (active_source, shuffle_enabled, seen_announcement_ids, plus the app-saved radio_stations).
  • For .spotify_token_cache.json, last_session.yml, history/, and streaming_cache/: if the new path is absent and the old one exists, fs::rename it, falling back to copy across filesystems.

Finding 3 is why this belongs in this PR rather than a follow-up: once save_config() runs once, the legacy keys are gone for good.

Non-blocking

Four more inline: an existing-user regression on in-app saved radio stations, blind-overwrite vs the read-modify-write that save_config does, state-dir permissions, and a Windows path separator nit. Plus:

  • Two dedupe implementations for radio stations (state::sanitized_radio_stations uses a HashSet, radio::dispatch::merged_radio_stations re-implements it with a Vec scan). Tiny lists, so this is drift risk rather than perf.
  • dismissed_announcements has no reader on main either. Good moment to drop dead state rather than carry it into a new file format.
  • Volume now persists from three layers (App::{increase,decrease,set}_volume, each decoded dispatcher, and now Network::change_volume). Correct, just redundant.
  • No CHANGELOG entry, against a repo that keeps detailed prose entries for user-visible changes.

Thanks for taking this on, the config/state separation is overdue.

Comment thread src/runtime.rs
Comment thread src/infra/history.rs Outdated
Comment thread src/core/user_config.rs
app.set_status_message("Radio station has no stream URL".to_string(), 4);
return;
};
if app.is_configured_radio_station_url(url) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Existing-user regression, worth handling in the same shim.

Before this PR, F (favorite) wrote into behavior.radio_stations in config.yml. After it, everything in config.yml counts as configured, so this guard fires and refuses removal with "Radio station is configured in config.yml". Every station a user saved in-app now needs a manual file edit to remove.

The ownership model itself is good, and I like that the guard exists rather than failing silently. The issue is only the day-one transition: moving pre-existing config.yml stations into state.yml during migration would avoid it entirely.

Comment thread src/core/state.rs
Comment thread src/core/config.rs Outdated
Comment thread src/core/paths.rs Outdated
Comment thread src/infra/radio/dispatch.rs Outdated
@DinoLeung

DinoLeung commented Jul 29, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review, and apologies for an extra long PR. I did want to keep it short and focus on the state and cache decoupling, it quickly gets out of hand.

Will be addressing them later in the week.

@LargeModGames

Copy link
Copy Markdown
Owner

Sorry this keeps growing, i know you've already done more rounds than you signed up for. If you'd rather hand any of it off, i'll push the fixes myself.

@DinoLeung

Copy link
Copy Markdown
Contributor Author

Thanks man. I’m happy to take another pass at the changes, but if there’s another round after that, I’d be happy to hand the remaining fixes over to you.

@DinoLeung

Copy link
Copy Markdown
Contributor Author

I've made another round of changes.

I'll leave #407 (comment) and #407 (comment) to you, since I'm not confident I can make a set of changes there that would be up to the project's standard.

Also, I found it funny that you read my intent here as making the project compatible with Home Manager configuration.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/infra/player/streaming.rs (1)

1524-1574: 🩺 Stability & Availability | 🔵 Trivial

Migration now preserves the Connect device identity; consider surfacing a failed migration to the user.

get_default_cache_path now calls migrate_legacy_streaming_cache_if_unclaimed before returning the new cache path, and legacy_streaming_cache_migration_preserves_connect_device_id confirms device_id and credentials.json survive the move. This resolves the earlier concern that upgrading would silently register spotatui as a brand-new Spotify Connect device.

One gap remains: when migration fails, the code only logs a warning and continues with the new, empty cache path. In that case the device still re-registers as new, the same user-visible symptom as before, just now triggered by a filesystem error instead of "always". Consider surfacing this failure through a status message at startup (once the runtime plumbing for it exists) instead of relying on a log line most users never see.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/infra/player/streaming.rs` around lines 1524 - 1574, Update
get_default_cache_path to surface migration failures through the existing
startup status-message mechanism once runtime plumbing is available, rather than
only emitting log::warn. Preserve returning the new cache path, and include
enough context in the user-visible message to indicate that legacy streaming
cache migration failed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/plugins/accent-cycler.lua`:
- Line 6: Update the plugin installation commands to resolve XDG_CONFIG_HOME
only when it is absolute, otherwise falling back to $HOME/.config before
appending the Spotatui plugins directory. Apply this consistently in
examples/plugins/accent-cycler.lua lines 6-6,
examples/plugins/now-playing-webhook.lua lines 7-7,
examples/plugins/now-playing.lua lines 6-8,
examples/plugins/track-info-popup.lua lines 4-6, and
examples/plugins/track-notifier.lua lines 4-4.

---

Nitpick comments:
In `@src/infra/player/streaming.rs`:
- Around line 1524-1574: Update get_default_cache_path to surface migration
failures through the existing startup status-message mechanism once runtime
plumbing is available, rather than only emitting log::warn. Preserve returning
the new cache path, and include enough context in the user-visible message to
indicate that legacy streaming cache migration failed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fa2856cb-9ff8-433d-8782-befa86572e93

📥 Commits

Reviewing files that changed from the base of the PR and between d3bc833 and e8dcef5.

📒 Files selected for processing (20)
  • CHANGELOG.md
  • PLUGINS.md
  • docs/keybindings.md
  • examples/plugins/README.md
  • examples/plugins/accent-cycler.lua
  • examples/plugins/now-playing-webhook.lua
  • examples/plugins/now-playing.lua
  • examples/plugins/queue-browser.lua
  • examples/plugins/session-stats/main.lua
  • examples/plugins/track-info-popup.lua
  • examples/plugins/track-notifier.lua
  • src/core/app.rs
  • src/core/config.rs
  • src/core/migrations.rs
  • src/core/persisted_playback.rs
  • src/infra/history.rs
  • src/infra/player/streaming.rs
  • src/runtime.rs
  • src/tui/handlers/playlist.rs
  • src/tui/runner.rs
🚧 Files skipped from review as they are similar to previous changes (9)
  • examples/plugins/session-stats/main.lua
  • src/infra/history.rs
  • CHANGELOG.md
  • PLUGINS.md
  • src/tui/runner.rs
  • examples/plugins/README.md
  • docs/keybindings.md
  • examples/plugins/queue-browser.lua
  • src/core/app.rs

Comment thread examples/plugins/accent-cycler.lua
@DinoLeung
DinoLeung requested a review from LargeModGames August 3, 2026 05:08
LargeModGames and others added 4 commits August 3, 2026 22:40
A malformed state.yml wedged every future save: `save` re-reads the file
before merging, so a single bad write surfaced a UI error on every volume
nudge and never recovered. The save path now backs the unreadable file up
to state.yml.bak, warns, and continues from defaults, mirroring the
config.yml fallback. Genuine read failures still propagate so a transient
error cannot overwrite good data with defaults.

State and config writes shared a fixed .tmp name, so two processes could
write the same temp file and rename each other's half-written bytes into
place. Route both through one atomic helper that writes to a process-unique
temp (pid plus a per-process counter) and removes it if the rename fails.
Address Codex review of the state-recovery change. A write truncated mid
multibyte character leaves invalid UTF-8, which read_to_string reported as
an I/O error and the save path propagated instead of healing. Read raw
bytes and treat non-UTF-8 contents as a corrupt file, so it backs up and
recovers like any other malformed state.

The atomic write helper only removed its temp file when the rename failed;
a failure during the write itself leaked the temp. Clean up on either
failure since each attempt uses a fresh unique name.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/core/user_config.rs (1)

832-837: 🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift

Fix config precedence for the optional startup overrides.

RuntimeState::default() and RuntimeState::apply_persisted() supply current/fallback runtime values, not config seeds. If config seeds these values, apply the config defaults to runtime_state before persisting on startup; otherwise runtime state cannot distinguish saved runtime values from config-started values.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/user_config.rs` around lines 832 - 837, The startup initialization
flow for the optional overrides sidebar_width_percent, playbar_height_rows, and
library_height_percent must apply configured seed values to runtime_state before
persistence, rather than relying on RuntimeState::default() or
RuntimeState::apply_persisted(). Update the relevant config-to-runtime
initialization logic while preserving existing persisted runtime values as the
higher-priority source.
🧹 Nitpick comments (1)
src/core/auth.rs (1)

118-118: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use anyhow::Result<()> for this helper, or document the exception.

write_private_file_atomic is new code under src/**/*.rs, but it returns std::io::Result<()>. The path rule requires anyhow::Result<T> and ? for error propagation. Change the helper to the project error type, or confirm that low-level filesystem helpers are exempt.

As per path instructions, src/**/*.rs must use anyhow::Result<T> and the ? operator for error propagation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/auth.rs` at line 118, Update write_private_file_atomic to return
anyhow::Result<()> and preserve ?-based propagation for its filesystem
operations, converting underlying I/O errors through anyhow as needed; only
retain std::io::Result<()> if the project explicitly documents this low-level
helper as exempt from the src/**/*.rs error-type rule.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/core/user_config.rs`:
- Around line 832-837: The startup initialization flow for the optional
overrides sidebar_width_percent, playbar_height_rows, and library_height_percent
must apply configured seed values to runtime_state before persistence, rather
than relying on RuntimeState::default() or RuntimeState::apply_persisted().
Update the relevant config-to-runtime initialization logic while preserving
existing persisted runtime values as the higher-priority source.

---

Nitpick comments:
In `@src/core/auth.rs`:
- Line 118: Update write_private_file_atomic to return anyhow::Result<()> and
preserve ?-based propagation for its filesystem operations, converting
underlying I/O errors through anyhow as needed; only retain std::io::Result<()>
if the project explicitly documents this low-level helper as exempt from the
src/**/*.rs error-type rule.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b4d2648c-273f-41d6-9727-491f51356ac3

📥 Commits

Reviewing files that changed from the base of the PR and between 4003ccf and 0016e5d.

📒 Files selected for processing (3)
  • src/core/auth.rs
  • src/core/state.rs
  • src/core/user_config.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/core/state.rs

New code under src/ uses anyhow::Result and ?-based propagation; convert
the atomic write helper's error type to match. Callers already add their own
context or propagate through anyhow, so this only drops the io::Result outlier.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants