* feat(providers): add notes field for provider management
- Add notes field to Provider model (backend and frontend)
- Display notes with higher priority than URL in provider card
- Style notes as non-clickable text to differentiate from URLs
- Add notes input field in provider form
- Add i18n support (zh/en) for notes field
* chore: format code and clean up unused props
- Run cargo fmt on Rust backend code
- Format TypeScript imports and code style
- Remove unused appId prop from ProviderPresetSelector
- Clean up unused variables in tests
- Integrate notes field handling in provider dialogs
* feat(deeplink): implement ccswitch:// protocol for provider import
Add deep link support to enable one-click provider configuration import via ccswitch:// URLs.
Backend:
- Implement URL parsing and validation (src-tauri/src/deeplink.rs)
- Add Tauri commands for parse and import (src-tauri/src/commands/deeplink.rs)
- Register ccswitch:// protocol in macOS Info.plist
- Add comprehensive unit tests (src-tauri/tests/deeplink_import.rs)
Frontend:
- Create confirmation dialog with security review UI (src/components/DeepLinkImportDialog.tsx)
- Add API wrapper (src/lib/api/deeplink.ts)
- Integrate event listeners in App.tsx
Configuration:
- Update Tauri config for deep link handling
- Add i18n support for Chinese and English
- Include test page for deep link validation (deeplink-test.html)
Files: 15 changed, 1312 insertions(+)
* chore(deeplink): integrate deep link handling into app lifecycle
Wire up deep link infrastructure with app initialization and event handling.
Backend Integration:
- Register deep link module and commands in mod.rs
- Add URL handling in app setup (src-tauri/src/lib.rs:handle_deeplink_url)
- Handle deep links from single instance callback (Windows/Linux CLI)
- Handle deep links from macOS system events
- Add tauri-plugin-deep-link dependency (Cargo.toml)
Frontend Integration:
- Listen for deeplink-import/deeplink-error events in App.tsx
- Update DeepLinkImportDialog component imports
Configuration:
- Enable deep link plugin in tauri.conf.json
- Update Cargo.lock for new dependencies
Localization:
- Add Chinese translations for deep link UI (zh.json)
- Add English translations for deep link UI (en.json)
Files: 9 changed, 359 insertions(+), 18 deletions(-)
* refactor(deeplink): enhance Codex provider template generation
Align deep link import with UI preset generation logic by:
- Adding complete config.toml template matching frontend defaults
- Generating safe provider name from sanitized input
- Including model_provider, reasoning_effort, and wire_api settings
- Removing minimal template that only contained base_url
- Cleaning up deprecated test file deeplink-test.html
* style: fix clippy uninlined_format_args warnings
Apply clippy --fix to use inline format arguments in:
- src/mcp.rs (8 fixes)
- src/services/env_manager.rs (10 fixes)
* style: apply code formatting and cleanup
- Format TypeScript files with Prettier (App.tsx, EnvWarningBanner.tsx, formatters.ts)
- Organize Rust imports and module order alphabetically
- Add newline at end of JSON files (en.json, zh.json)
- Update Cargo.lock for dependency changes
* feat: add model name configuration support for Codex and fix Gemini model handling
- Add visual model name input field for Codex providers
- Add model name extraction and update utilities in providerConfigUtils
- Implement model name state management in useCodexConfigState hook
- Add conditional model field rendering in CodexFormFields (non-official only)
- Integrate model name sync with TOML config in ProviderForm
- Fix Gemini deeplink model injection bug
- Correct environment variable name from GOOGLE_GEMINI_MODEL to GEMINI_MODEL
- Add test cases for Gemini model injection (with/without model)
- All tests passing (9/9)
- Fix Gemini model field binding in edit mode
- Add geminiModel state to useGeminiConfigState hook
- Extract model value during initialization and reset
- Sync model field with geminiEnv state to prevent data loss on submit
- Fix missing model value display when editing Gemini providers
Changes:
- 6 files changed, 245 insertions(+), 13 deletions(-)
BREAKING CHANGE: The [mcp.servers] format was completely incorrect and not
any official Codex format. The only correct format is [mcp_servers] at the
top level of config.toml.
Changes:
- Remove incorrect [mcp.servers] nested table support
- Always use [mcp_servers] top-level table (official Codex format)
- Auto-migrate and cleanup erroneous [mcp.servers] entries on write
- Preserve error-tolerant import for migrating old incorrect configs
- Simplify sync logic by removing format selection branches (~60 lines)
- Update all documentation and tests to reflect correct format
- Add warning logs when detecting and cleaning incorrect format
Backend (Rust):
- mcp.rs: Simplify sync_enabled_to_codex by removing Target enum
- mcp.rs: sync_single_server_to_codex now always uses [mcp_servers]
- mcp.rs: remove_server_from_codex cleans both locations
- mcp.rs: Update import_from_codex comments to clarify format status
- tests: Rename test to sync_enabled_to_codex_migrates_erroneous_*
- tests: Update assertions to verify migration behavior
Frontend (TypeScript):
- tomlUtils.ts: Prioritize [mcp_servers] format in parsing
- tomlUtils.ts: Update error messages to guide correct format
Documentation:
- README.md: Correct MCP format reference to [mcp_servers]
- CLAUDE.md: Add comprehensive format specification with examples
All 79 tests pass. This ensures backward compatibility while enforcing
the correct Codex official standard going forward.
Refs: https://github.com/openai/codex/issues/3441
- Rewrite import_from_claude/codex/gemini to write directly to mcp.servers
- Implement skip-on-error strategy for fault tolerance (single invalid item no longer aborts entire batch)
- Smart merge logic: existing servers only enable corresponding app, preserve other configs
- Remove deprecated markers from service layer
- Export McpApps type for test usage
- Update mcp_commands tests to use unified structure
Fixes runtime import issue where data was written to legacy structure (mcp.claude/codex.servers)
but unified panel reads from new structure (mcp.servers), causing "imported but invisible" bug.
Refactor tray menu system to support three applications (Claude/Codex/Gemini):
- Introduce generic TrayAppSection structure and TRAY_SECTIONS array
- Implement append_provider_section and handle_provider_tray_event helper functions
- Enhance Gemini provider service with .env config read/write support
- Implement Gemini LiveSnapshot for atomic operations and rollback
- Update README documentation to reflect Gemini tray quick switching feature
- Format code in app_config.rs to comply with rustfmt rules
- Remove extra blank line in config.rs
- Format test code in app_config_load.rs for consistency
BREAKING CHANGE: Runtime auto-migration from v1 to v2 config format has been removed.
Changes:
- Remove automatic v1→v2 migration logic from MultiAppConfig::load()
- Improve v1 detection using structural analysis (checks for 'apps' key absence)
- Return clear error with migration instructions when v1 config is detected
- Add comprehensive tests for config loading edge cases
- Fix false positive detection when v1 config contains 'version' or 'mcp' fields
Migration path for users:
1. Install v3.2.x to perform one-time auto-migration, OR
2. Manually edit ~/.cc-switch/config.json to v2 format
Rationale:
- Separates concerns: load() should be read-only, not perform side effects
- Fail-fast principle: unsupported formats should error immediately
- Simplifies code maintenance by removing migration logic from hot path
Tests added:
- load_v1_config_returns_error_and_does_not_write
- load_v1_with_extra_version_still_treated_as_v1
- load_invalid_json_returns_parse_error_and_does_not_write
- load_valid_v2_config_succeeds
- Implement TrayTexts struct to manage multilingual tray menu text
- Auto-refresh tray menu when language settings change
- Add missing notification message translations
- Format code for consistency
Switch from `toml` to `toml_edit` crate for incremental TOML editing,
preserving user-written comments, whitespace, and key ordering in
Codex config.toml.
Changes:
- Add toml_edit 0.22 dependency for preserving-style TOML editing
- Refactor sync_enabled_to_codex() to use toml_edit::DocumentMut API
- Implement smart style detection: inherit existing mcp.servers or
mcp_servers style, with fallback to sensible defaults
- Add deduplication logic to prevent both styles coexisting
- Add tests for format preservation and style inheritance
- Fix unused_mut and nonminimal_bool compiler warnings
- Apply cargo fmt to all modified files
Benefits:
- User comments and formatting are no longer lost during sync
- Respects user's preferred TOML structure (nested vs toplevel)
- Non-MCP fields in config.toml remain untouched
- Minimal surprise principle: only modifies necessary sections
Testing:
- All 47 tests passing (unit + integration)
- Clippy clean (0 warnings, 0 errors)
- Release build successful
- New tests verify comment preservation and style detection
This commit continues the backend refactoring initiative by extracting
configuration management and API speedtest logic into dedicated service
layers, completing phase 4 of the architectural improvement plan.
## Changes
### New Service Layers
- **ConfigService** (`services/config.rs`): Consolidates all config
import/export, backup management, and live sync operations
- `create_backup()`: Creates timestamped backups with auto-cleanup
- `export_config_to_path()`: Exports config to specified path
- `load_config_for_import()`: Loads and validates imported config
- `import_config_from_path()`: Full import with state update
- `sync_current_providers_to_live()`: Syncs current providers to live files
- Private helpers for Claude/Codex-specific sync logic
- **SpeedtestService** (`services/speedtest.rs`): Encapsulates endpoint
latency testing with proper validation and error handling
- `test_endpoints()`: Tests multiple URLs concurrently
- URL validation now unified in service layer
- Includes 3 unit tests for edge cases (empty list, invalid URLs, timeout clamping)
### Command Layer Refactoring
- Move all import/export commands to `commands/import_export.rs`
- Commands become thin wrappers: parse params → call service → return JSON
- Maintain `spawn_blocking` for I/O operations (phase 5 optimization)
- Lock acquisition happens after I/O completes (minimize contention)
### File Organization
- Delete: `import_export.rs`, `speedtest.rs` (root-level modules)
- Create: `commands/import_export.rs`, `services/config.rs`, `services/speedtest.rs`
- Update: Module declarations in `lib.rs`, `commands/mod.rs`, `services/mod.rs`
### Test Updates
- Update 20 integration tests in `import_export_sync.rs` to use `ConfigService` APIs
- All existing test cases pass without modification to test logic
- Add 3 new unit tests for `SpeedtestService`:
- `sanitize_timeout_clamps_values`: Boundary value testing
- `test_endpoints_handles_empty_list`: Empty input handling
- `test_endpoints_reports_invalid_url`: Invalid URL error reporting
## Benefits
1. **Improved Testability**: Service methods are `pub fn`, easily callable
from tests without Tauri runtime
2. **Better Separation of Concerns**: Business logic isolated from
command/transport layer
3. **Enhanced Maintainability**: Related operations grouped in cohesive
service structs
4. **Consistent Error Handling**: Services return `Result<T, AppError>`,
commands convert to `Result<T, String>`
5. **Performance**: I/O operations run in `spawn_blocking`, locks released
before file operations
## Testing
- ✅ All 43 tests passing (7 unit + 36 integration)
- ✅ `cargo fmt --check` passes
- ✅ `cargo clippy -- -D warnings` passes (zero warnings)
## Documentation
Updated `BACKEND_REFACTOR_PLAN.md` to reflect completion of config and
speedtest service extraction, marking phase 4 substantially complete.
Co-authored-by: Claude Code <code@anthropic.com>
Extract all MCP business logic from command layer into `services/mcp.rs`,
implementing snapshot isolation pattern to optimize lock granularity after
RwLock migration in Phase 5.
## Key Changes
### Service Layer (`services/mcp.rs`)
- Add `McpService` with 7 methods: `get_servers`, `upsert_server`,
`delete_server`, `set_enabled`, `sync_enabled`, `import_from_claude`,
`import_from_codex`
- Implement snapshot isolation: acquire write lock only for in-memory
modifications, clone config snapshot, release lock, then perform file I/O
with snapshot
- Use conditional cloning: only clone config when sync is actually needed
(e.g., when `enabled` flag is true or `sync_other_side` is requested)
### Command Layer (`commands/mcp.rs`)
- Reduce to thin wrappers: parse parameters and delegate to `McpService`
- Remove all `*_internal` and `*_test_hook` functions (-94 lines)
- Each command now 5-10 lines (parameter parsing + service call + error mapping)
### Core Logic Refactoring (`mcp.rs`)
- Rename `set_enabled_and_sync_for` → `set_enabled_flag_for`
- Remove file sync logic from low-level function, move sync responsibility
to service layer for better separation of concerns
### Test Adaptation (`tests/mcp_commands.rs`)
- Replace test hooks with direct `McpService` calls
- All 5 MCP integration tests pass
### Additional Fixes
- Add `Default` impl for `AppState` (clippy suggestion)
- Remove unnecessary auto-deref in `commands/provider.rs` and `lib.rs`
- Update Phase 4/5 progress in `BACKEND_REFACTOR_PLAN.md`
## Performance Impact
**Before**: Write lock held during file I/O (~10ms), blocking all readers
**After**: Write lock held only for memory ops (~100μs), file I/O lock-free
Estimated throughput improvement: ~2x in high-concurrency read scenarios
## Testing
- ✅ All tests pass: 5 MCP commands + 7 provider service tests
- ✅ Zero clippy warnings with `-D warnings`
- ✅ No behavioral changes, maintains original save semantics
Part of Phase 4 (Service Layer Abstraction) of backend refactoring roadmap.
Replace Mutex with RwLock for AppState.config to enable concurrent reads,
improving performance for tray menu building and query operations that
previously blocked each other unnecessarily.
Key changes:
- Migrate AppState.config from Mutex<MultiAppConfig> to RwLock<MultiAppConfig>
- Distinguish read-only operations (read()) from mutations (write()) across
all command handlers and service layers
- Offload blocking file I/O in import/export commands to spawn_blocking threads,
minimizing lock hold time and preventing main thread blocking
- Extract load_config_for_import() to separate I/O logic from state updates
- Update all integration tests to use RwLock semantics
Performance impact:
- Concurrent reads: Multiple threads can now query config simultaneously
(tray menu, provider list, MCP config)
- Reduced contention: Write locks only acquired during actual mutations
- Non-blocking I/O: Config import/export no longer freezes UI thread
All existing tests pass with new locking semantics.
- Extract internal functions in commands/mcp.rs and commands/provider.rs
to enable unit testing without Tauri context
- Add test hooks: set_mcp_enabled_test_hook, import_mcp_from_claude_test_hook,
import_mcp_from_codex_test_hook, import_default_config_test_hook
- Migrate error types from String to AppError for precise error matching in tests
- Extend ProviderService with delete() method to unify Codex/Claude cleanup logic
- Add comprehensive test coverage:
- tests/mcp_commands.rs: command-level tests for MCP operations
- tests/provider_service.rs: service-level tests for switch/delete operations
- Run cargo fmt to fix formatting issues (EOF newlines)
- Update BACKEND_REFACTOR_PLAN.md to mark phase 3 complete
Architecture improvements:
- Extract ProviderService with switch/backfill/write methods
- Reduce command layer from 160 to 13 lines via delegation
- Separate business logic (services) from state management (commands)
- Introduce precise error handling with structured validation
Refactoring details:
- Split Codex/Claude switching into symmetric private methods
- Add multi-layer validation for Codex auth field (existence + type)
- Extract import_config_from_path for command and test reuse
- Expose export_config_to_file and ProviderService in public API
Test coverage:
- Add 10+ integration tests for Claude/Codex switching flows
- Cover import/export success and failure scenarios (JSON parse, missing file)
- Verify state consistency on error paths (current remains unchanged)
- Test snapshot backfill for both old and new providers after switching
Key improvements:
- Extract switch_provider_internal() returning AppError for better testability
- Fix backup mtime inheritance: use read+write instead of fs::copy to ensure latest backup survives cleanup
- Add 15+ integration tests covering provider commands, atomic writes, and rollback scenarios
- Expose write_codex_live_atomic, AppState, and test hooks in public API
- Extract tests/support.rs with isolated HOME and mutex utilities
Test coverage:
- Provider switching with live config backfill and MCP sync
- Codex atomic write success and failure rollback
- Backup retention policy with proper mtime ordering
- Negative cases: missing auth field, invalid provider ID
Expand test suite from 3 to 11 integration tests, adding comprehensive coverage
for Codex dual-file atomicity and bidirectional MCP synchronization:
New Codex sync tests:
- sync_codex_provider_writes_auth_and_config: validates atomic write of auth.json
and config.toml, plus SSOT backfill of latest toml content
- sync_enabled_to_codex_writes_enabled_servers: MCP projection to config.toml
- sync_enabled_to_codex_removes_servers_when_none_enabled: cleanup when all disabled
- sync_enabled_to_codex_returns_error_on_invalid_toml: error handling for malformed TOML
New Codex MCP import tests:
- import_from_codex_adds_servers_from_mcp_servers_table: imports new servers from live config
- import_from_codex_merges_into_existing_entries: smart merge preserving SSOT server configs
New Claude MCP tests:
- sync_claude_enabled_mcp_projects_to_user_config: enabled/disabled filtering for .claude.json
- import_from_claude_merges_into_config: intelligent merge preserving existing configurations
Expand lib.rs API exports:
- Codex paths: get_codex_auth_path, get_codex_config_path
- Claude MCP: get_claude_mcp_path
- MCP sync: sync_enabled_to_claude, sync_enabled_to_codex
- MCP import: import_from_claude, import_from_codex
- Error type: AppError (for test assertions)
Test infrastructure improvements:
- Enhanced reset_test_fs() to clean .claude.json
- All tests use isolated HOME directory with sequential execution via mutex
Test results: 11/11 passed
Files changed: 3 (+394/-6 lines)
Next steps: Command layer integration tests and error recovery scenarios