Skip to content

Unified settings.json#2286

Merged
yujonglee merged 4 commits intomainfrom
unified-settings-json
Dec 14, 2025
Merged

Unified settings.json#2286
yujonglee merged 4 commits intomainfrom
unified-settings-json

Conversation

@yujonglee
Copy link
Contributor

No description provided.

@netlify
Copy link

netlify bot commented Dec 14, 2025

Deploy Preview for hyprnote-storybook ready!

Name Link
🔨 Latest commit bbd12cc
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote-storybook/deploys/693e229579d4100008d7364d
😎 Deploy Preview https://deploy-preview-2286--hyprnote-storybook.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link

netlify bot commented Dec 14, 2025

Deploy Preview for hyprnote ready!

Name Link
🔨 Latest commit bbd12cc
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote/deploys/693e2295f36df40008baf641
😎 Deploy Preview https://deploy-preview-2286--hyprnote.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 14, 2025

📝 Walkthrough

Walkthrough

This PR introduces a new Tauri settings plugin (tauri-plugin-settings) and migrates the desktop application's configuration storage from a keys-based TinyBase store to a unified settings-based store. The plugin provides persistent load/save capabilities for application settings, and components are refactored to source configuration data from the new settings store module instead of legacy keys and main stores. The JSON persister is updated to support the new settings format and includes migration logic from the old keys.json file.

Changes

Cohort / File(s) Summary
Plugin Infrastructure
plugins/settings/Cargo.toml, plugins/settings/package.json, plugins/settings/tsconfig.json, plugins/settings/.gitignore
New Tauri plugin package configuration files defining dependencies (@tauri-apps/api, serde, specta) and build scripts for the settings plugin.
Plugin Implementation
plugins/settings/src/lib.rs, plugins/settings/src/commands.rs, plugins/settings/src/state.rs, plugins/settings/src/error.rs, plugins/settings/src/ext.rs
Core plugin logic implementing path/load/save commands, SettingsState for concurrent file I/O, error handling, and extension trait for plugin integration.
Plugin Build & Bindings
plugins/settings/build.rs, plugins/settings/js/index.ts
Build script registering plugin commands (path, load, save) and JavaScript module re-exporting generated TypeScript bindings.
Workspace & App Integration
Cargo.toml, apps/desktop/package.json, apps/desktop/src-tauri/Cargo.toml
Added tauri-plugin-settings to workspace dependencies and desktop app dependencies.
Tauri Configuration & Capabilities
apps/desktop/src-tauri/src/lib.rs, apps/desktop/src-tauri/capabilities/default.json
Initialized settings plugin in builder chain; added "settings:default" permission and "fs:allow-remove" capability for path operations.
Store Layer: New Settings Store
apps/desktop/src/store/tinybase/settings.ts
New TinyBase store module with SETTINGS_MAPPING for value/table schemas, persister/synchronizer setup, migration from main store, and query definitions for LLM and STT providers.
Store Layer: Removed Keys Store
apps/desktop/src/store/tinybase/keys.ts
Entire file removed; replaced by settings store module.
Store Layer: Updated App Root
apps/desktop/src/main.tsx
Replaced KeysStoreComponent with SettingsStoreComponent in AppWithTiny render.
Persistence Layer
apps/desktop/src/store/tinybase/jsonPersister.ts, apps/desktop/src/store/tinybase/jsonPersister.test.ts
Replaced simplified-format helpers with settingsToContent/storeToSettings converters; integrated plugin-settings load/save commands; added migrateKeysJsonToSettings migration function; updated test suite for round-trip data preservation.
Config Hooks
apps/desktop/src/config/use-config.ts
Updated useConfigValue/useConfigValues to source from settings store instead of main store.
LLM/STT Connection Hooks
apps/desktop/src/hooks/useLLMConnection.ts, apps/desktop/src/hooks/useSTTConnection.ts, apps/desktop/src/hooks/useCurrentModelModalitySupport.ts
Replaced main/keys store imports with settings store; updated store ID and UI hook references.
Sidebar Banner Component
apps/desktop/src/components/main/sidebar/banner/index.tsx
Replaced Tinybase main.UI.useValues with useConfigValues hook for LLM/STT provider data.
Settings: AI Components
apps/desktop/src/components/settings/ai/llm/select.tsx, apps/desktop/src/components/settings/ai/llm/health.tsx, apps/desktop/src/components/settings/ai/stt/select.tsx, apps/desktop/src/components/settings/ai/stt/configure.tsx, apps/desktop/src/components/settings/ai/shared/index.tsx
Updated imports and store references from keys/main to settings module; uses settings.UI hooks and STORE_ID for provider configuration state.
Settings: General Components
apps/desktop/src/components/settings/general/index.tsx, apps/desktop/src/components/settings/general/notification.tsx
Updated store imports from main to settings; refactored hook calls to use settings.STORE_ID and settings.UI methods.
Plugin Build Scripts
plugins/importer/build.rs, plugins/webhook/build.rs
Cleared COMMANDS arrays to register no commands for importer and webhook plugins.
Documentation
apps/web/content/docs/faq/7.settings.mdx
New MDX doc page describing settings.json file location for Hyprnote.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Plugin Security & State Management: Review SettingsState async I/O patterns, file operations (atomicity via temp file), and error handling in plugins/settings/src/state.rs and plugins/settings/src/commands.rs.
  • Data Migration Logic: Verify migrateKeysJsonToSettings() and migrateFromMainStore() preserve all settings fields and handle partial/missing data correctly. Test round-trip conversions in settingsToContent/storeToSettings.
  • Store Schema Compatibility: Confirm SETTINGS_MAPPING correctly maps all value fields and ai_providers table schema matches previous keys store structure.
  • Persistence Integration: Validate that plugin-settings load/save commands replace old file-based persistence without data loss. Check error handling when settings file is missing or corrupted.
  • Component Import Coverage: Verify all usages of legacy keys/main stores have been updated; check for missed references that could break at runtime.

Possibly related PRs

  • Tweak accelerators #993: Modifies Tauri plugin initialization chain in apps/desktop/src-tauri/src/lib.rs by adding tauri_plugin_task::init(), similar plugin registration pattern to this PR's settings plugin.
  • AI setting custom #1586: Updates AI settings and connection logic in components like apps/desktop/src/hooks/useLLMConnection.ts and settings components, overlapping with the configuration refactoring in this PR.
  • fixes before beta release #1692: Modifies apps/desktop/src-tauri/capabilities/default.json to update filesystem paths and permissions, related to capability configuration changes in this PR.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to assess whether the description relates to the changeset. Add a detailed description explaining the purpose, scope, and rationale for unifying the settings.json structure and migrating from the previous configuration approach.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Unified settings.json' clearly and concisely describes the main objective of the pull request: consolidating settings management into a unified settings.json file.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch unified-settings-json

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 and usage tips.

@yujonglee yujonglee merged commit f0aa0b6 into main Dec 14, 2025
15 of 16 checks passed
@yujonglee yujonglee deleted the unified-settings-json branch December 14, 2025 02:40
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (6)
apps/desktop/src/components/settings/ai/llm/health.tsx (1)

106-132: Prefer eliminating the result as ... type assertion

You can usually get the same union typing by returning as const objects from each branch (or by annotating result), avoiding the cast at Line 131.

apps/desktop/src/components/settings/ai/shared/index.tsx (1)

15-68: Store swap looks consistent; avoid the hard cast if the hook can be typed.
The migration to settings.UI.* + settings.STORE_ID is consistent. If useSetPartialRowCallback can be generically typed, prefer that over as (row: Partial<AIProvider>) => void to avoid masking signature mismatches.

plugins/settings/package.json (1)

1-10: Consider adding exports field for clarity in workspace consumption.

While the current "main": "./js/index.ts" works in this monorepo with Vite's native TypeScript support, adding an exports field would clarify module resolution:

 {
   "name": "@hypr/plugin-settings",
   "private": true,
   "main": "./js/index.ts",
+  "exports": {
+    ".": "./js/index.ts"
+  },
   "scripts": {
     "codegen": "cargo test -p tauri-plugin-settings"
   },
   "dependencies": {
     "@tauri-apps/api": "^2.9.1"
   }
 }

The @tauri-apps/api version ^2.9.1 is correctly aligned with the rest of the codebase.

plugins/settings/src/lib.rs (1)

30-34: Consider graceful error handling for data_dir() call.

The unwrap() on line 31 will panic if data_dir() returns None. While this is unlikely in normal operation, consider propagating the error for more graceful handling:

 .setup(|app, _api| {
-    let base = app.path().data_dir().unwrap().join("hyprnote");
+    let base = app.path().data_dir()
+        .ok_or_else(|| "Failed to resolve data directory".to_string())?
+        .join("hyprnote");
     let state = SettingsState::new(base);
     assert!(app.manage(state));
     Ok(())
 })
plugins/settings/src/commands.rs (1)

1-1: Unused import.

The tauri::Manager trait is not used in this file. The state() method comes from tauri::AppHandle directly.

-use tauri::Manager;
apps/desktop/src/store/tinybase/jsonPersister.test.ts (1)

133-135: Consider using a type guard or explicit interface.

The inline type assertion is functional but verbose. If this pattern repeats, consider defining a shared type for partial settings results.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9de8b9f and bbd12cc.

⛔ Files ignored due to path filters (24)
  • Cargo.lock is excluded by !**/*.lock
  • plugins/deeplink2/js/bindings.gen.ts is excluded by !**/*.gen.ts
  • plugins/deeplink2/permissions/autogenerated/commands/ping.toml is excluded by !plugins/**/permissions/**
  • plugins/deeplink2/permissions/autogenerated/reference.md is excluded by !plugins/**/permissions/**
  • plugins/deeplink2/permissions/default.toml is excluded by !plugins/**/permissions/**
  • plugins/deeplink2/permissions/schemas/schema.json is excluded by !plugins/**/permissions/**
  • plugins/importer/js/bindings.gen.ts is excluded by !**/*.gen.ts
  • plugins/importer/permissions/autogenerated/commands/ping.toml is excluded by !plugins/**/permissions/**
  • plugins/importer/permissions/autogenerated/reference.md is excluded by !plugins/**/permissions/**
  • plugins/importer/permissions/default.toml is excluded by !plugins/**/permissions/**
  • plugins/importer/permissions/schemas/schema.json is excluded by !plugins/**/permissions/**
  • plugins/settings/js/bindings.gen.ts is excluded by !**/*.gen.ts
  • plugins/settings/permissions/autogenerated/commands/load.toml is excluded by !plugins/**/permissions/**
  • plugins/settings/permissions/autogenerated/commands/path.toml is excluded by !plugins/**/permissions/**
  • plugins/settings/permissions/autogenerated/commands/save.toml is excluded by !plugins/**/permissions/**
  • plugins/settings/permissions/autogenerated/reference.md is excluded by !plugins/**/permissions/**
  • plugins/settings/permissions/default.toml is excluded by !plugins/**/permissions/**
  • plugins/settings/permissions/schemas/schema.json is excluded by !plugins/**/permissions/**
  • plugins/webhook/js/bindings.gen.ts is excluded by !**/*.gen.ts
  • plugins/webhook/permissions/autogenerated/commands/ping.toml is excluded by !plugins/**/permissions/**
  • plugins/webhook/permissions/autogenerated/reference.md is excluded by !plugins/**/permissions/**
  • plugins/webhook/permissions/default.toml is excluded by !plugins/**/permissions/**
  • plugins/webhook/permissions/schemas/schema.json is excluded by !plugins/**/permissions/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (37)
  • Cargo.toml (1 hunks)
  • apps/desktop/package.json (1 hunks)
  • apps/desktop/src-tauri/Cargo.toml (1 hunks)
  • apps/desktop/src-tauri/capabilities/default.json (2 hunks)
  • apps/desktop/src-tauri/src/lib.rs (1 hunks)
  • apps/desktop/src/components/main/sidebar/banner/index.tsx (2 hunks)
  • apps/desktop/src/components/settings/ai/llm/health.tsx (1 hunks)
  • apps/desktop/src/components/settings/ai/llm/select.tsx (3 hunks)
  • apps/desktop/src/components/settings/ai/shared/index.tsx (2 hunks)
  • apps/desktop/src/components/settings/ai/stt/configure.tsx (3 hunks)
  • apps/desktop/src/components/settings/ai/stt/select.tsx (3 hunks)
  • apps/desktop/src/components/settings/general/index.tsx (3 hunks)
  • apps/desktop/src/components/settings/general/notification.tsx (2 hunks)
  • apps/desktop/src/config/use-config.ts (4 hunks)
  • apps/desktop/src/hooks/useCurrentModelModalitySupport.ts (1 hunks)
  • apps/desktop/src/hooks/useLLMConnection.ts (2 hunks)
  • apps/desktop/src/hooks/useSTTConnection.ts (1 hunks)
  • apps/desktop/src/main.tsx (2 hunks)
  • apps/desktop/src/store/tinybase/jsonPersister.test.ts (2 hunks)
  • apps/desktop/src/store/tinybase/jsonPersister.ts (3 hunks)
  • apps/desktop/src/store/tinybase/keys.ts (0 hunks)
  • apps/desktop/src/store/tinybase/settings.ts (1 hunks)
  • apps/web/content/docs/faq/7.settings.mdx (1 hunks)
  • plugins/importer/build.rs (1 hunks)
  • plugins/settings/.gitignore (1 hunks)
  • plugins/settings/Cargo.toml (1 hunks)
  • plugins/settings/build.rs (1 hunks)
  • plugins/settings/js/index.ts (1 hunks)
  • plugins/settings/package.json (1 hunks)
  • plugins/settings/src/commands.rs (1 hunks)
  • plugins/settings/src/error.rs (1 hunks)
  • plugins/settings/src/ext.rs (1 hunks)
  • plugins/settings/src/lib.rs (1 hunks)
  • plugins/settings/src/state.rs (1 hunks)
  • plugins/settings/tsconfig.json (1 hunks)
  • plugins/webhook/build.rs (1 hunks)
  • plugins/webhook/openapi.gen.json (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/desktop/src/store/tinybase/keys.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, use cn (import from @hypr/utils). It is similar to clsx. Always pass an array and split by logical grouping.
Use motion/react instead of framer-motion.

Files:

  • apps/desktop/src/components/settings/general/notification.tsx
  • plugins/settings/js/index.ts
  • apps/desktop/src/components/settings/ai/stt/configure.tsx
  • apps/desktop/src/config/use-config.ts
  • apps/desktop/src/components/settings/ai/stt/select.tsx
  • apps/desktop/src/components/main/sidebar/banner/index.tsx
  • apps/desktop/src/components/settings/ai/llm/select.tsx
  • apps/desktop/src/hooks/useCurrentModelModalitySupport.ts
  • apps/desktop/src/store/tinybase/settings.ts
  • apps/desktop/src/components/settings/ai/shared/index.tsx
  • apps/desktop/src/components/settings/general/index.tsx
  • apps/desktop/src/store/tinybase/jsonPersister.test.ts
  • apps/desktop/src/main.tsx
  • apps/desktop/src/hooks/useSTTConnection.ts
  • apps/desktop/src/hooks/useLLMConnection.ts
  • apps/desktop/src/components/settings/ai/llm/health.tsx
  • apps/desktop/src/store/tinybase/jsonPersister.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Agent implementations should use TypeScript and follow the established architectural patterns defined in the agent framework
Agent communication should use defined message protocols and interfaces

Files:

  • plugins/settings/js/index.ts
  • apps/desktop/src/config/use-config.ts
  • apps/desktop/src/hooks/useCurrentModelModalitySupport.ts
  • apps/desktop/src/store/tinybase/settings.ts
  • apps/desktop/src/store/tinybase/jsonPersister.test.ts
  • apps/desktop/src/hooks/useSTTConnection.ts
  • apps/desktop/src/hooks/useLLMConnection.ts
  • apps/desktop/src/store/tinybase/jsonPersister.ts
plugins/*/src/lib.rs

📄 CodeRabbit inference engine (plugins/AGENTS.md)

After updating commands in plugins/<NAME>/src/lib.rs, run codegen, update plugins/<NAME>/permissions/default.toml, and apps/desktop/src-tauri/capabilities/default.json

Files:

  • plugins/settings/src/lib.rs
🧠 Learnings (7)
📚 Learning: 2025-11-24T16:32:01.459Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:32:01.459Z
Learning: Applies to **/*.ts : Agent implementations should use TypeScript and follow the established architectural patterns defined in the agent framework

Applied to files:

  • plugins/settings/tsconfig.json
📚 Learning: 2025-11-24T16:32:01.459Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T16:32:01.459Z
Learning: Applies to **/*.config.{ts,json} : Agent configuration should be centralized and externalized from implementation logic

Applied to files:

  • plugins/settings/tsconfig.json
📚 Learning: 2025-11-24T16:32:19.706Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T16:32:19.706Z
Learning: After some amount of TypeScript changes, run `pnpm -r typecheck`.

Applied to files:

  • plugins/settings/tsconfig.json
📚 Learning: 2025-11-27T11:40:22.782Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: plugins/AGENTS.md:0-0
Timestamp: 2025-11-27T11:40:22.782Z
Learning: Applies to plugins/*/src/lib.rs : After updating commands in `plugins/<NAME>/src/lib.rs`, run `codegen`, update `plugins/<NAME>/permissions/default.toml`, and `apps/desktop/src-tauri/capabilities/default.json`

Applied to files:

  • plugins/settings/src/ext.rs
  • plugins/settings/src/lib.rs
  • plugins/importer/build.rs
  • Cargo.toml
  • apps/desktop/src-tauri/Cargo.toml
  • plugins/settings/Cargo.toml
  • plugins/settings/package.json
  • plugins/settings/build.rs
  • apps/desktop/src-tauri/src/lib.rs
  • plugins/webhook/build.rs
  • plugins/settings/src/commands.rs
📚 Learning: 2025-11-24T16:32:19.706Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T16:32:19.706Z
Learning: Applies to **/*.{ts,tsx} : If there are many classNames with conditional logic, use `cn` (import from `hypr/utils`), similar to `clsx`. Always pass an array and split by logical grouping.

Applied to files:

  • apps/desktop/src/components/main/sidebar/banner/index.tsx
📚 Learning: 2025-11-24T16:32:23.055Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T16:32:23.055Z
Learning: Applies to **/*.{ts,tsx} : If there are many classNames with conditional logic, use `cn` (import from `hypr/utils`). It is similar to `clsx`. Always pass an array and split by logical grouping.

Applied to files:

  • apps/desktop/src/components/main/sidebar/banner/index.tsx
📚 Learning: 2025-11-24T16:32:19.706Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T16:32:19.706Z
Learning: Applies to **/*.{ts,tsx,js,jsx,rs,json,toml,yaml,yml} : Format using `dprint fmt` from the root. Do not use `cargo fmt`.

Applied to files:

  • plugins/settings/.gitignore
🧬 Code graph analysis (10)
plugins/settings/src/state.rs (1)
plugins/settings/src/commands.rs (3)
  • path (5-8)
  • load (12-17)
  • save (21-27)
plugins/settings/src/lib.rs (3)
apps/desktop/src-tauri/src/lib.rs (3)
  • make_specta_builder (210-218)
  • make_specta_builder (228-228)
  • export_types (225-239)
plugins/settings/src/state.rs (4)
  • new (10-16)
  • path (18-20)
  • load (22-30)
  • save (32-54)
plugins/settings/src/commands.rs (6)
  • path (5-8)
  • load (12-17)
  • save (21-27)
  • app (6-6)
  • app (15-15)
  • app (25-25)
apps/desktop/src/hooks/useCurrentModelModalitySupport.ts (2)
apps/desktop/src/components/settings/ai/shared/list-common.ts (1)
  • InputModality (14-14)
apps/desktop/src/auth.tsx (1)
  • useAuth (299-307)
apps/desktop/src/store/tinybase/settings.ts (3)
apps/desktop/src/store/tinybase/jsonPersister.ts (2)
  • migrateKeysJsonToSettings (139-183)
  • createSettingsPersister (185-212)
plugins/windows/js/index.ts (1)
  • getCurrentWebviewWindowLabel (9-12)
apps/desktop/src/store/tinybase/save.ts (1)
  • registerSaveHandler (5-10)
apps/desktop/src/components/settings/ai/shared/index.tsx (1)
packages/store/src/schema-internal.ts (1)
  • AIProvider (43-43)
apps/desktop/src/store/tinybase/jsonPersister.test.ts (1)
apps/desktop/src/store/tinybase/jsonPersister.ts (2)
  • settingsToContent (111-117)
  • storeToSettings (119-137)
plugins/settings/build.rs (4)
apps/desktop/src-tauri/src/lib.rs (1)
  • main (13-208)
plugins/importer/build.rs (1)
  • main (3-5)
plugins/webhook/build.rs (1)
  • main (3-5)
plugins/settings/src/state.rs (1)
  • new (10-16)
apps/desktop/src-tauri/src/lib.rs (1)
plugins/settings/src/lib.rs (1)
  • init (25-37)
plugins/settings/src/commands.rs (2)
plugins/settings/src/state.rs (3)
  • path (18-20)
  • load (22-30)
  • save (32-54)
owhisper/owhisper-config/src/lib.rs (1)
  • settings (73-73)
apps/desktop/src/store/tinybase/jsonPersister.ts (1)
apps/desktop/src/store/tinybase/settings.ts (1)
  • SETTINGS_MAPPING (23-65)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Redirect rules - hyprnote
  • GitHub Check: Header rules - hyprnote
  • GitHub Check: Pages changed - hyprnote
  • GitHub Check: desktop_ci (linux, depot-ubuntu-22.04-8)
  • GitHub Check: desktop_ci (linux, depot-ubuntu-24.04-8)
  • GitHub Check: desktop_ci (macos, depot-macos-14)
  • GitHub Check: ci
  • GitHub Check: fmt
🔇 Additional comments (35)
apps/desktop/src-tauri/capabilities/default.json (1)

72-77: LGTM!

Both changes follow the existing capability patterns. The fs:allow-remove permission is appropriately scoped to $DATA/hyprnote/**/*, consistent with other file system operations, and the settings:default permission correctly enables the new settings plugin for the application.

Also applies to: 118-118

plugins/webhook/build.rs (1)

1-4: Add "todo" to the COMMANDS array in build.rs.

The webhook plugin defines a #[tauri::command] function commands::todo() in src/commands.rs but the COMMANDS array in build.rs is empty. This will prevent the command from being properly registered. Update build.rs line 1 to include the command name: const COMMANDS: &[&str] = &["todo"];

⛔ Skipped due to learnings
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: plugins/AGENTS.md:0-0
Timestamp: 2025-11-27T11:40:22.782Z
Learning: Applies to plugins/*/src/lib.rs : After updating commands in `plugins/<NAME>/src/lib.rs`, run `codegen`, update `plugins/<NAME>/permissions/default.toml`, and `apps/desktop/src-tauri/capabilities/default.json`
plugins/importer/build.rs (1)

1-4: Update build.rs COMMANDS array to include "ping".

The importer plugin registers the ping command in lib.rs via tauri_specta::collect_commands![commands::ping::<Wry>,], but build.rs shows COMMANDS: &[]. Update to const COMMANDS: &[&str] = &["ping"]; to match the actual command being exposed, consistent with how other plugins declare their commands in build.rs.

⛔ Skipped due to learnings
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: plugins/AGENTS.md:0-0
Timestamp: 2025-11-27T11:40:22.782Z
Learning: Applies to plugins/*/src/lib.rs : After updating commands in `plugins/<NAME>/src/lib.rs`, run `codegen`, update `plugins/<NAME>/permissions/default.toml`, and `apps/desktop/src-tauri/capabilities/default.json`
plugins/webhook/openapi.gen.json (1)

407-407: LGTM!

Cosmetic change only—adds trailing newline for cleaner formatting.

plugins/settings/tsconfig.json (1)

1-5: LGTM!

Standard TypeScript configuration for the settings plugin. Correctly extends the base config and includes the generated bindings.

apps/web/content/docs/faq/7.settings.mdx (1)

1-5: LGTM!

Documentation addition for the new settings.json file location, aligning with the settings migration.

plugins/settings/js/index.ts (1)

1-1: LGTM!

Standard re-export pattern for generated Tauri plugin bindings.

apps/desktop/src/components/main/sidebar/banner/index.tsx (2)

7-7: LGTM!

Import updated to use the new settings-based configuration hook.


23-35: Config keys are properly defined in the settings store.

The config keys current_llm_provider, current_llm_model, current_stt_provider, and current_stt_model are all correctly defined in the settings store schema (lines 45, 49, 50, and 54 in apps/desktop/src/store/tinybase/settings.ts). The migration to useConfigValues is sound.

plugins/settings/src/ext.rs (1)

1-3: LGTM!

Standard Rust extension trait pattern. The empty trait with blanket implementation sets up an extension point for future methods on tauri::Manager types.

plugins/settings/.gitignore (1)

1-17: LGTM!

Standard ignore patterns for a Tauri plugin with Rust and TypeScript components.

apps/desktop/src/components/settings/ai/stt/configure.tsx (1)

26-298: LGTM: consistent migration to settings store hooks/STORE_ID.
No behavioral change beyond selecting the new store.

Also applies to: 552-557

plugins/settings/Cargo.toml (1)

1-26: The concerns in this review are not valid issues. Both edition = "2024" and the tauri dependency with features = ["test"] are workspace-wide standards—all 25 plugins in plugins/ consistently use the same configuration. This is clearly an intentional design pattern, not a misconfiguration or oversight in this file.

apps/desktop/src-tauri/src/lib.rs (1)

85-91: No action needed—the tauri-plugin-settings integration is properly wired. The capability ("settings:default") is present in apps/desktop/src-tauri/capabilities/default.json, the plugin is initialized in lib.rs, and codegen via the test harness (cargo test -p tauri-plugin-settings) handles TypeScript binding generation. All three commands (path, load, save) are correctly exported via tauri_specta.

apps/desktop/package.json (1)

50-50: LGTM! New settings plugin dependency added correctly.

The addition of the @hypr/plugin-settings workspace dependency aligns with the unified settings store migration described in the PR objectives.

apps/desktop/src/components/settings/general/index.tsx (1)

8-42: LGTM! Store migration implemented correctly.

The migration from the main store to the settings store is clean and consistent. The useSetPartialValuesCallback hook now correctly references settings.UI and settings.STORE_ID, while preserving all data transformation logic.

apps/desktop/src/hooks/useLLMConnection.ts (1)

23-68: LGTM! Store references updated consistently.

The hook now correctly reads current_llm_provider, current_llm_model, and provider configuration from the unified settings store. All connection resolution logic remains intact.

apps/desktop/src/hooks/useSTTConnection.ts (1)

11-27: LGTM! STT connection hook migrated successfully.

The migration to the settings store is consistent with other hooks. Provider and model configuration now correctly sources from settings.UI using settings.STORE_ID.

apps/desktop/src/components/settings/general/notification.tsx (1)

18-110: LGTM! All notification setting handlers migrated correctly.

All five useSetValueCallback handlers now correctly use settings.UI and settings.STORE_ID. The form submission and state management logic remains unchanged and continues to work with the migrated handlers.

apps/desktop/src/hooks/useCurrentModelModalitySupport.ts (1)

20-32: LGTM! Modality support hook migrated correctly.

The hook now sources current model and provider data from the settings store while maintaining all modality detection logic. The migration is consistent with other hooks in this PR.

apps/desktop/src/components/settings/ai/llm/select.tsx (2)

16-53: LGTM! Provider/model selection migrated correctly.

Both useSetValueCallback handlers for provider and model selection now correctly use settings.UI and settings.STORE_ID. The form logic and submission handlers work seamlessly with the migrated callbacks.


191-194: LGTM! Query reference updated to settings store.

The useResultTable hook correctly uses settings.QUERIES.llmProviders with settings.STORE_ID. The llmProviders query is properly defined in the settings module and queries the ai_providers table with the expected schema (type, base_url). The provider configuration mapping logic remains unchanged.

Cargo.toml (1)

128-128: Plugin registration is complete.

The tauri-plugin-settings dependency is correctly added to Cargo.toml. Verification confirms:

  • ✓ Plugin implementation exists in plugins/settings/src/lib.rs with init function
  • ✓ Capabilities registered in apps/desktop/src-tauri/capabilities/default.json as settings:default

No additional action required.

apps/desktop/src/config/use-config.ts (1)

1-4: LGTM! Clean migration to settings store.

The import update and subsequent hook changes correctly migrate the configuration hooks from the main store to the new unified settings store. The logic and behavior remain unchanged.

apps/desktop/src/components/settings/ai/stt/select.tsx (2)

18-18: LGTM! Store migration looks correct.

The import and callback updates properly migrate to the unified settings store.

Also applies to: 37-49


224-227: The settings.QUERIES.sttProviders query is properly defined.

The query is defined in the settings module at line 111 and is correctly accessible via settings.QUERIES.sttProviders. The code change is valid.

apps/desktop/src/main.tsx (1)

27-27: LGTM! Settings store component properly integrated.

The new SettingsStoreComponent is correctly added alongside the existing StoreComponent, with both receiving the same persistence configuration. This enables the unified settings store while maintaining backward compatibility with the main store.

Also applies to: 110-111

plugins/settings/src/commands.rs (1)

3-27: LGTM! Clean command implementations.

The three Tauri commands (path, load, save) are well-structured wrappers around the SettingsState methods. The async/sync distinction is appropriate, and error handling via Result<_, String> follows Tauri conventions.

apps/desktop/src/store/tinybase/jsonPersister.test.ts (2)

6-49: LGTM! Comprehensive round-trip testing.

The test suite properly validates bidirectional conversion between settings format and store format. The full data test covers AI providers (both LLM and STT), notification settings, and general settings.


102-116: Good edge case coverage for empty data.

The empty data test correctly verifies that the conversion creates the expected default structure with empty nested objects for ai, notification, and general sections.

plugins/settings/src/lib.rs (1)

1-37: No action needed. The settings:default permission is already configured in apps/desktop/src-tauri/capabilities/default.json. The repository does not use plugins/settings/permissions/default.toml files; permissions are managed directly in the capabilities configuration file where the settings plugin is already listed.

Likely an incorrect or invalid review comment.

apps/desktop/src/store/tinybase/settings.ts (1)

114-199: Well-structured store component with proper lifecycle management.

The component correctly handles:

  • Schema-based store creation
  • Migration from legacy keys.json and main store
  • Conditional persistence based on window label
  • Broadcast channel synchronization for multi-window support
  • Query definitions for provider filtering
apps/desktop/src/store/tinybase/jsonPersister.ts (3)

185-212: Clean persister implementation with proper error handling.

The persister correctly:

  • Returns undefined on load errors (allowing graceful degradation)
  • Logs save errors without throwing (prevents persister from breaking)
  • Uses reasonable 1-second polling interval
  • Properly integrates with commands.load and commands.save

131-134: No action needed. The spread operation at lines 131-134 is safe.

The keys mapped under the ai path in SETTINGS_MAPPING are current_llm_provider, current_llm_model, current_stt_provider, and current_stt_model — all scalar config values. The providerRowsToSettings function returns an object with keys llm and stt containing nested provider configurations. Since these key names do not overlap, spreading providers into settings.ai will not overwrite any existing settings from storeValuesToSettings.


14-16: The circular dependency exists but is safe. jsonPersister.ts imports SETTINGS_MAPPING from ./settings, and settings.ts imports createSettingsPersister and migrateKeysJsonToSettings from ./jsonPersister. However, this does not cause runtime issues because the imported functions are only called inside the useCreatePersister hook callback (lines 116–119), which executes during component render after both modules are fully initialized. SETTINGS_MAPPING is used only within function bodies, not at module initialization time. This pattern is safe and does not require refactoring.

Comment on lines 52 to 56
tauri-plugin-process = { workspace = true }
tauri-plugin-sentry = { workspace = true }
tauri-plugin-settings = { workspace = true }
tauri-plugin-shell = { workspace = true }
tauri-plugin-single-instance = { workspace = true }
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check workspace root Cargo.toml for tauri-plugin-settings definition
rg "tauri-plugin-settings" --type toml -B 2 -A 2

Repository: fastrepl/hyprnote

Length of output: 1544


🏁 Script executed:

# Look for tauri-plugin-settings initialization in the codebase
rg "tauri_plugin_settings" --type rust -B 2 -A 2

Repository: fastrepl/hyprnote

Length of output: 479


🏁 Script executed:

# Check if capabilities/default.json exists and what it contains
cat apps/desktop/src-tauri/capabilities/default.json 2>/dev/null | head -100

Repository: fastrepl/hyprnote

Length of output: 2551


🏁 Script executed:

# Check permissions files in plugins
find . -name "default.toml" -path "*/permissions/*" -exec cat {} \;

Repository: fastrepl/hyprnote

Length of output: 43


Add permissions to apps/desktop/src-tauri/capabilities/default.json.

The tauri-plugin-settings dependency addition requires a corresponding permissions entry. Add "settings:default" to the permissions array in apps/desktop/src-tauri/capabilities/default.json to complete the plugin integration.

🤖 Prompt for AI Agents
In apps/desktop/src-tauri/capabilities/default.json around the permissions array
(near lines 52-56 in Cargo.toml context), add the permission string
"settings:default" to the JSON permissions array so the tauri-plugin-settings
dependency is granted the required capability; update the array to include
"settings:default" alongside existing entries and ensure the JSON remains valid
(commas and quoting correct).

Comment on lines +168 to +178
const result = await commands.save(settings);
if (result.status === "error") {
console.error("[migrateKeysJsonToSettings] save error:", result.error);
return false;
}

await remove(keysPath, options);
console.info(
"[migrateKeysJsonToSettings] migrated keys.json to settings.json",
);
return true;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

File removal failure after successful save leaves inconsistent state.

If commands.save succeeds but remove(keysPath) fails, the migration will be marked as failed (returns false) but settings were already saved. On next app start, migrateKeysJsonToSettings will run again, potentially causing duplicate migrations.

Consider separating the concerns or handling removal failure differently:

    const result = await commands.save(settings);
    if (result.status === "error") {
      console.error("[migrateKeysJsonToSettings] save error:", result.error);
      return false;
    }

-    await remove(keysPath, options);
+    try {
+      await remove(keysPath, options);
+    } catch (removeError) {
+      console.warn("[migrateKeysJsonToSettings] remove failed, but migration succeeded:", removeError);
+      // Migration was successful even if file removal failed
+    }
    console.info(
      "[migrateKeysJsonToSettings] migrated keys.json to settings.json",
    );
    return true;
🤖 Prompt for AI Agents
In apps/desktop/src/store/tinybase/jsonPersister.ts around lines 168-178, the
code treats a failed remove(keysPath) as a migration failure after successfully
saving settings, which leaves an inconsistent state and can cause duplicate
runs; change the flow so that after commands.save succeeds you attempt
remove(keysPath) inside a try/catch, and if removal fails do NOT mark the
migration as failed—log the removal error (and emit telemetry) and return true
so the app won’t retry the migration, or alternatively attempt a rollback by
deleting the newly saved settings (using the appropriate commands API) and
return false only if the rollback succeeds; ensure the removal error is logged
with context and surfaced for later retry/cleanup.

);

useProvideStore(STORE_ID, store);
useProvideQueries(STORE_ID, queries!);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Non-null assertion on queries may cause runtime errors.

useCreateQueries could return undefined before the store is ready. Using ! assertion bypasses TypeScript's safety check.

Consider adding a null check or using optional chaining:

-  useProvideQueries(STORE_ID, queries!);
+  useProvideQueries(STORE_ID, queries ?? undefined);

Or conditionally provide queries only when defined:

-  useProvideQueries(STORE_ID, queries!);
+  if (queries) {
+    useProvideQueries(STORE_ID, queries);
+  }
🤖 Prompt for AI Agents
In apps/desktop/src/store/tinybase/settings.ts around line 194, the code uses a
non-null assertion on `queries` (useProvideQueries(STORE_ID, queries!)), which
can cause runtime errors if `useCreateQueries` returns undefined; change this to
only call useProvideQueries when `queries` is defined (e.g., check `if (queries)
{ useProvideQueries(STORE_ID, queries) }` or otherwise guard/early-return until
the store is ready) so you avoid the `!` assertion and preserve type safety.

Comment on lines +209 to +218
for (const [rowId, row] of Object.entries(mainProviders)) {
if (row.api_key || row.base_url) {
settingsStore.setRow("ai_providers", rowId, {
type: row.type ?? "",
base_url: row.base_url ?? "",
api_key: row.api_key ?? "",
});
}
mainStore.delRow("ai_providers", rowId);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Provider rows deleted from main store even when not migrated.

If a row has neither api_key nor base_url, it won't be copied to settings store (line 210-216), but will still be deleted from main store (line 217). This could cause unintended data loss for incomplete provider entries.

Consider moving the delete inside the condition block or removing the condition:

    for (const [rowId, row] of Object.entries(mainProviders)) {
-      if (row.api_key || row.base_url) {
-        settingsStore.setRow("ai_providers", rowId, {
-          type: row.type ?? "",
-          base_url: row.base_url ?? "",
-          api_key: row.api_key ?? "",
-        });
-      }
-      mainStore.delRow("ai_providers", rowId);
+      settingsStore.setRow("ai_providers", rowId, {
+        type: row.type ?? "",
+        base_url: row.base_url ?? "",
+        api_key: row.api_key ?? "",
+      });
+      mainStore.delRow("ai_providers", rowId);
    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (const [rowId, row] of Object.entries(mainProviders)) {
if (row.api_key || row.base_url) {
settingsStore.setRow("ai_providers", rowId, {
type: row.type ?? "",
base_url: row.base_url ?? "",
api_key: row.api_key ?? "",
});
}
mainStore.delRow("ai_providers", rowId);
}
for (const [rowId, row] of Object.entries(mainProviders)) {
settingsStore.setRow("ai_providers", rowId, {
type: row.type ?? "",
base_url: row.base_url ?? "",
api_key: row.api_key ?? "",
});
mainStore.delRow("ai_providers", rowId);
}
🤖 Prompt for AI Agents
In apps/desktop/src/store/tinybase/settings.ts around lines 209 to 218, provider
rows are always deleted from mainStore even when they are not migrated because
the mainStore.delRow call is outside the if that checks for api_key/base_url;
move the deletion into the if block (or otherwise only call delRow when you
actually setRow successfully) so that rows lacking api_key and base_url are not
removed, ensuring deletion happens only after a successful migration.

Comment on lines +1 to +5
const COMMANDS: &[&str] = &["path", "load", "save"];

fn main() {
tauri_plugin::Builder::new(COMMANDS).build();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify settings plugin permissions and capabilities are configured

echo "=== Checking permissions file ==="
if [ -f "plugins/settings/permissions/default.toml" ]; then
  cat plugins/settings/permissions/default.toml
else
  echo "WARNING: permissions/default.toml not found"
fi

echo -e "\n=== Checking capabilities file ==="
if [ -f "apps/desktop/src-tauri/capabilities/default.json" ]; then
  rg -A 2 -B 2 "settings" apps/desktop/src-tauri/capabilities/default.json || echo "No 'settings' found in capabilities"
else
  echo "WARNING: capabilities/default.json not found"
fi

echo -e "\n=== Checking lib.rs for command registration ==="
if [ -f "plugins/settings/src/lib.rs" ]; then
  rg -n "invoke_handler|path|load|save" plugins/settings/src/lib.rs
else
  echo "WARNING: src/lib.rs not found"
fi

echo -e "\n=== Listing plugins/settings directory ==="
ls -la plugins/settings/

Repository: fastrepl/hyprnote

Length of output: 1162


Create plugins/settings/permissions/default.toml.

The commands are properly registered in src/lib.rs and the capabilities have been updated in apps/desktop/src-tauri/capabilities/default.json, but plugins/settings/permissions/default.toml is missing. This file should define the permission scopes for the plugin's commands (path, load, save).

🤖 Prompt for AI Agents
In plugins/settings/build.rs around lines 1 to 5, the plugin registers commands
("path", "load", "save") but the permissions file
plugins/settings/permissions/default.toml is missing; create that file and
declare permission scopes for each command (path, load, save) mapping each
command to an appropriate permission key and human-readable description so the
desktop capability JSON can map to these scopes—ensure the TOML has entries for
each command scope, a short description, and default access level so the
plugin's commands are covered by permissions.

Comment on lines +1 to +15
use serde::{Serialize, ser::Serializer};

pub type Result<T> = std::result::Result<T, Error>;

#[derive(Debug, thiserror::Error)]
pub enum Error {}

impl Serialize for Error {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.to_string().as_ref())
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Uninhabited enum Error {} is effectively unusable; add variants or remove the public API.
Right now Result<T, Error> suggests fallible APIs but you can’t ever produce an Error.

One option (minimal) is to define string-backed variants and keep the “serialize as string” behavior:

 use serde::{Serialize, ser::Serializer};

 pub type Result<T> = std::result::Result<T, Error>;

 #[derive(Debug, thiserror::Error)]
-pub enum Error {}
+pub enum Error {
+    #[error("io: {0}")]
+    Io(String),
+    #[error("json: {0}")]
+    Json(String),
+    #[error("{0}")]
+    Other(String),
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
use serde::{Serialize, ser::Serializer};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, thiserror::Error)]
pub enum Error {}
impl Serialize for Error {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.to_string().as_ref())
}
}
use serde::{Serialize, ser::Serializer};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("io: {0}")]
Io(String),
#[error("json: {0}")]
Json(String),
#[error("{0}")]
Other(String),
}
impl Serialize for Error {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.to_string().as_ref())
}
}
🤖 Prompt for AI Agents
In plugins/settings/src/error.rs around lines 1 to 15, the public type alias
Result<T, Error> references an uninhabited enum Error which makes the API
unusable; update the file to make Error instantiable by either (a) adding
meaningful variants (e.g., String-backed or named variants for expected failure
cases) and derive/implement Display/thiserror so to_string() works with the
existing Serialize impl, or (b) if there are truly no errors, remove the public
Result alias and expose std::result::Result<T, Never> or use a non-fallible API;
prefer adding explicit variants (and annotate with #[derive(Debug,
thiserror::Error, Serialize)] or implement Display) to preserve the current
“serialize as string” behavior.

Comment on lines +32 to +54
pub async fn save(&self, settings: serde_json::Value) -> Result<(), String> {
let _guard = self.lock.write().await;

if let Some(parent) = self.path.parent() {
tokio::fs::create_dir_all(parent)
.await
.map_err(|e| format!("create dir: {}", e))?;
}

let tmp_path = self.path.with_extension("json.tmp");
let content =
serde_json::to_string_pretty(&settings).map_err(|e| format!("serialize: {}", e))?;

tokio::fs::write(&tmp_path, &content)
.await
.map_err(|e| format!("write tmp: {}", e))?;

tokio::fs::rename(&tmp_path, &self.path)
.await
.map_err(|e| format!("rename: {}", e))?;

Ok(())
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Windows bug: rename(tmp, path) fails when path already exists → subsequent saves can error.
This will likely break “save settings twice” on Windows.

-        tokio::fs::rename(&tmp_path, &self.path)
-            .await
-            .map_err(|e| format!("rename: {}", e))?;
+        // On Windows, rename fails if destination exists. Remove first and retry.
+        match tokio::fs::rename(&tmp_path, &self.path).await {
+            Ok(()) => {}
+            Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
+                tokio::fs::remove_file(&self.path)
+                    .await
+                    .map_err(|e| format!("remove existing: {}", e))?;
+                tokio::fs::rename(&tmp_path, &self.path)
+                    .await
+                    .map_err(|e| format!("rename: {}", e))?;
+            }
+            Err(e) => return Err(format!("rename: {}", e)),
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub async fn save(&self, settings: serde_json::Value) -> Result<(), String> {
let _guard = self.lock.write().await;
if let Some(parent) = self.path.parent() {
tokio::fs::create_dir_all(parent)
.await
.map_err(|e| format!("create dir: {}", e))?;
}
let tmp_path = self.path.with_extension("json.tmp");
let content =
serde_json::to_string_pretty(&settings).map_err(|e| format!("serialize: {}", e))?;
tokio::fs::write(&tmp_path, &content)
.await
.map_err(|e| format!("write tmp: {}", e))?;
tokio::fs::rename(&tmp_path, &self.path)
.await
.map_err(|e| format!("rename: {}", e))?;
Ok(())
}
pub async fn save(&self, settings: serde_json::Value) -> Result<(), String> {
let _guard = self.lock.write().await;
if let Some(parent) = self.path.parent() {
tokio::fs::create_dir_all(parent)
.await
.map_err(|e| format!("create dir: {}", e))?;
}
let tmp_path = self.path.with_extension("json.tmp");
let content =
serde_json::to_string_pretty(&settings).map_err(|e| format!("serialize: {}", e))?;
tokio::fs::write(&tmp_path, &content)
.await
.map_err(|e| format!("write tmp: {}", e))?;
// On Windows, rename fails if destination exists. Remove first and retry.
match tokio::fs::rename(&tmp_path, &self.path).await {
Ok(()) => {}
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
tokio::fs::remove_file(&self.path)
.await
.map_err(|e| format!("remove existing: {}", e))?;
tokio::fs::rename(&tmp_path, &self.path)
.await
.map_err(|e| format!("rename: {}", e))?;
}
Err(e) => return Err(format!("rename: {}", e)),
}
Ok(())
}
🤖 Prompt for AI Agents
In plugins/settings/src/state.rs around lines 32 to 54, the atomic save uses
tokio::fs::rename(tmp, path) which fails on Windows if the destination already
exists; update save to handle that by attempting to rename and, on failure due
to existing destination, remove the existing self.path (ignoring NotFound) and
retry the rename once, propagating any other errors; ensure removal and retry
are awaited and mapped to appropriate error messages so the save succeeds when
overwriting an existing file on Windows.

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.

1 participant