Add comprehensive implementation plan for v3.7.0 refactor that
separates Codex MCP configuration from unified MCP structure.
Key design decisions:
- Codex MCP stored as raw TOML string in codexMcp.rawToml
- Unified MCP (mcp.servers) only supports Claude/Gemini
- Complete isolation: no apps.codex in unified structure
- Migration clears mcp.codex.servers to prevent pollution
Architecture improvements:
- Single responsibility: each data source has one purpose
- No priority conflicts: completely independent data paths
- Simplified switching logic: no conditional branches
- UI constraints: Tab1 limited to claude/gemini only
Implementation phases:
- Phase 0: Setup (0.5d)
- Phase 1: Backend foundation (1.5d)
- Phase 2: Command layer (1d)
- Phase 3: Switching logic (1d)
- Phase 4: Frontend API (0.5d)
- Phase 5: UI implementation (2d)
- Phase 6: Enhancements (1d, optional)
- Phase 7: Testing & docs (1d)
Total: 8.5 days (MVP: 7 days)
Addresses TOML-JSON conversion data loss issue by preserving
raw TOML format for Codex while maintaining structured approach
for Claude/Gemini.
Add `center: true` to main window configuration to improve initial
window positioning on Windows and other platforms. This addresses
user feedback about the window appearing in the top-left corner.
Previously, only Codex provider switches triggered MCP synchronization,
which could cause MCP configuration loss when switching Claude or Gemini
providers.
Changes:
- Enable MCP sync for all app types (Claude, Codex, Gemini) during provider switch
- Migrate to v3.7.0 unified MCP sync mechanism using McpService::sync_all_enabled()
- Replace app-specific sync_enabled_to_codex() with unified sync for all apps
- Remove unused mcp module import
This ensures MCP servers remain properly configured across all applications
after provider switches, preventing configuration loss.
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
Fixes two critical issues with the MCP JSON input:
1. Format button failed with wrapped format like "server": {...}
2. Submit button failed despite input validation passing
Changes:
- Updated formatJSON to use smart parser (supports wrapped format)
- Simplified submit validation logic (removed redundant validateJsonConfig call)
- Improved UX: input preserves original format, cleanup happens on format/submit
* Prevents confusing "instant disappearance" when pasting wrapped JSON
* Auto-fills ID/Name fields while keeping input unchanged
* Format button now strips wrapper key and formats cleanly
* Submit button correctly extracts config regardless of format
Code quality:
- Reduced code by 5 lines (20 changes, 11 insertions, 16 deletions)
- Consistent use of parseSmartMcpJson across all JSON operations
- No type errors introduced
Support multiple MCP configuration input formats:
- Pure config object: { "command": "npx", ... }
- Key-value pair fragment: "server-name": { "command": "npx", ... }
- Wrapped object: { "server-name": { "command": "npx", ... } }
The parser automatically:
- Detects and wraps JSON fragments into complete objects
- Extracts server name from single-key objects
- Auto-fills ID and Name fields when applicable
- Formats the config for display
This improves UX by allowing users to paste configs directly from
.claude.json or .codex/config.toml without manual editing.
- Simplify config label from "Full JSON configuration or use" to "Full JSON Configuration"
- Align wizard button to the right using justify-between layout
- Apply same pattern to TOML configuration label
- Improve visual balance with cleaner left-right alignment
- Enable all apps (Claude, Codex, Gemini) by default when adding MCP servers
- Improve config label with clearer wording: "Full JSON configuration or use [Config Wizard]"
- Add JSON format button to beautify configuration with 2-space indentation
- Update tests to reflect new default behavior
- Clean up redundant explicit prop passing
This provides a more streamlined experience by enabling all apps out of the box
and making it easier to format JSON configurations.
**Changes:**
- Remove all comments from custom template (align with Claude/Gemini)
- Remove base_url field from template (user fills in form instead)
- Simplify getCodexCustomTemplate() - no locale parameter needed
- Keep preset configurations unchanged with their baseUrl values
**Template now contains only:**
- model_provider, model, model_reasoning_effort
- disable_response_storage
- [model_providers.custom] section with minimal fields
**Benefits:**
- ✅ Cleaner, more focused template
- ✅ Consistent with other apps (no comments)
- ✅ Forces users to fill base_url via form field
- ✅ Reduced template size from 35+ lines to 12 lines
Net change: -74 lines (codexTemplates.ts)
**Changes:**
- Create src/config/codexTemplates.ts with getCodexCustomTemplate factory
- Support both Chinese and English templates based on i18n.language
- Remove 70 lines of duplicated template strings from ProviderForm.tsx
- Update both useEffect and handlePresetChange to use template factory
- Clean up unused "Custom (Blank Template)" preset entry
**Benefits:**
- ✅ Eliminates code duplication (35-line template repeated twice)
- ✅ Adds internationalization support for English users
- ✅ Follows project architecture (templates in config/ directory)
- ✅ Improves maintainability (single source of truth)
- ✅ Net reduction: 34 lines (81 additions, 115 deletions)
**Technical Details:**
- Template selection logic: (i18n.language || "zh").startsWith("zh") ? "zh" : "en"
- Templates are identical except for comments language
- Both auth and config are returned as a single CodexTemplate object
Addresses DRY principle violation and architectural concerns identified
in code review.
- Replace checkboxes with toggle switches for app selection (more intuitive for enable/disable actions)
- Change switch color from blue to emerald to match MCP button theme
- Stack app options vertically with labels on left to save horizontal space
- Reduce panel width from max-w-4xl to max-w-3xl for more compact design
- Move docs button next to server name for better information grouping
- Update primary blue from Linear style (#3498db) to macOS system blue (#0A84FF)
- Align gray scale with macOS dark mode palette (#1C1C1E, #2C2C2E, etc.)
- Adjust dark mode background to match macOS systemBackground (HSL 240 5% 12%)
- Refine scrollbar colors to use native macOS gray tones
- Remove transparent titleBarStyle for better stability
Complete the v3.7.0 MCP refactoring by updating the form layer to match
the unified architecture already implemented in data/service/API layers.
**Breaking Changes:**
- Remove confusing `appId` parameter from McpFormModal
- Replace with `defaultFormat` (json/toml) and `defaultEnabledApps` (array)
**Form Enhancements:**
- Add app enablement checkboxes (Claude/Codex/Gemini) directly in the form
- Smart defaults: new servers default to Claude enabled, editing preserves state
- Support "draft" mode: servers can be created without enabling any apps
**Architecture Improvements:**
- Eliminate semantic confusion: format selection separate from app targeting
- One-step workflow: configure and enable apps in single form submission
- Consistent with unified backend: `apps: { claude, codex, gemini }`
**Testing:**
- Update test mocks to use `useUpsertMcpServer` hook
- Add test case for creating servers with no apps enabled
- Fix parameter references from `appId` to `defaultFormat`
**i18n:**
- Add `mcp.form.enabledApps` translation (zh/en)
- Add `mcp.form.noAppsWarning` translation (zh/en)
This completes the MCP management refactoring, ensuring all layers
(data, service, API, UI) follow the same unified architecture pattern.
- Add unified `common_config_snippets` structure to MultiAppConfig
- Implement `get_common_config_snippet` and `set_common_config_snippet` commands
- Replace localStorage with config.json persistence for Codex and Gemini
- Auto-migrate legacy `claude_common_config_snippet` to new unified structure
- Deprecate individual API methods in favor of unified interface
- Add automatic migration from localStorage on first load
BREAKING CHANGE: Common config snippets now stored in unified `common_config_snippets` object instead of separate fields
- Change auto_import_prompt_if_exists return type to Result<bool>
- Remove redundant save() calls by introducing updated flag
- Eliminate unnecessary clone() in app type iterations
- Remove semantic contradiction in let _ = ...? pattern
- Improve code semantics and maintainability
Performance: Reduce disk I/O by 50%+ through unified save logic
Fix backfill logic in prompt enable workflow:
- Prioritize backfilling live file content to currently enabled prompt (prevent data loss)
- Create backup only when no enabled prompt exists and content is new (avoid duplicate backups)
- Implement staged persistence (save after backfill + save after enable)
- Add explicit logging for backfill/backup operations
Also simplify string formatting in prompt_files.rs with inline format strings.
- 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.
Problem:
- On first launch, McpRoot::default() created `servers: None`
- McpService::get_all_servers() would incorrectly return error "old structure detected"
- This confused new users who had no legacy MCP data
Root Cause:
- Derived Default trait sets Option<T> fields to None
- New installations should start with v3.7.0 structure immediately
Solution:
- Explicitly implement Default for McpRoot
- Initialize `servers: Some(HashMap::new())` for v3.7.0+ structure
- Legacy fields (claude/codex/gemini) remain empty, only used for deserializing old configs
Impact:
- First-time users get correct v3.7.0 structure immediately
- migrate_mcp_to_unified() correctly detects already-migrated state
- No false "old structure" errors on fresh installs
All MCP operations already auto-sync to live configs:
- upsert_server() → sync_server_to_apps()
- toggle_app() → sync_server_to_app() or remove_server_from_app()
- delete_server() → remove_server_from_all_apps()
The manual 'Sync All' button was redundant and could confuse users
into thinking they need to manually sync after each change.
Changes:
- Remove 'Sync All' button from UnifiedMcpPanel header
- Remove useSyncAllMcpServers hook
- Remove handleSyncAll function and syncAllMutation state
- Remove RefreshCw icon import
- Remove sync-related i18n translations (en/zh)
Note: Backend sync_all_mcp_servers command remains for potential
future use (e.g., recovery tool), but is no longer exposed in UI.
Auto-migration at startup is sufficient for upgrading from v3.6.x.
Manual import adds unnecessary complexity since:
- Gemini MCP support launches with v3.7.0 (no legacy data)
- Existing Claude/Codex MCP configs are auto-migrated on first run
- All MCP management should happen within CC Switch
Changes:
- Remove McpImportDialog component
- Remove "Import" button from UnifiedMcpPanel
- Remove import-related i18n translations (en/zh)
- Simplify user experience with single management interface
Note: Backend import commands (import_mcp_from_*) remain for
backward compatibility but are no longer exposed in UI.
Complete Phase 3 (P0) frontend implementation for unified MCP management:
**New Files:**
- src/hooks/useMcp.ts: React Query hooks for unified MCP operations
- src/components/mcp/UnifiedMcpPanel.tsx: Unified MCP management panel
- src/components/ui/checkbox.tsx: Checkbox component from shadcn/ui
**Features:**
- Unified panel with three-column layout: server info + app checkboxes + actions
- Multi-app control: Claude/Codex/Gemini checkboxes for each server
- Real-time stats: Show enabled server counts per app
- Full CRUD operations: Add, edit, delete, sync all servers
**Integration:**
- Replace old app-specific McpPanel with UnifiedMcpPanel in App.tsx
- Update McpFormModal to support unified mode with apps field
- Add i18n support: mcp.unifiedPanel namespace (zh/en)
**Type Safety:**
- Ensure McpServer.apps field always initialized
- Fix all test files to include apps field
- TypeScript type check passes ✅
**Architecture:**
- Single source of truth: mcp.servers manages all MCP configs
- Per-server app control: apps.claude/codex/gemini boolean flags
- Backward compatible: McpFormModal supports both unified and legacy modes
Next: P1 tasks (import dialogs, sub-components, tests)
## Type Definitions
- Update McpServer interface with new apps field (McpApps)
- Add McpApps interface for multi-app enable state
- Add McpServersMap type for server collections
- Mark enabled field as deprecated (use apps instead)
- Maintain backward compatibility with optional fields
## API Layer Updates
- Add unified MCP management methods to mcpApi:
* getAllServers() - retrieve all servers with apps state
* upsertUnifiedServer() - add/update server with apps
* deleteUnifiedServer() - remove server
* toggleApp() - enable/disable server for specific app
* syncAllServers() - sync all enabled servers to live configs
- Import new McpServersMap type
## Code Organization
- Keep all types in src/types.ts (removed duplicate types/mcp.ts)
- Follow existing project structure conventions
Related: v3.7.0 unified MCP management
## Compilation Fixes
- Add deprecated compatibility methods to McpService:
* get_servers() - filters servers by app
* set_enabled() - delegates to toggle_app()
* sync_enabled() - syncs enabled servers for specific app
* import_from_claude/codex/gemini() - wraps mcp:: functions
- Fix toml_edit type conversion in sync_single_server_to_codex():
* Add json_server_to_toml_table() helper function
* Manually construct toml_edit::Table instead of invalid serde conversion
- Fix get_codex_config_path() calls (returns PathBuf, not Result)
- Update upsert_mcp_server_in_config() to work with unified structure:
* Converts old per-app API to unified McpServer structure
* Preserves existing server data when updating
* Supports sync_other_side parameter for multi-app enable
- Update delete_mcp_server_in_config() to ignore app parameter
## Backward Compatibility
- All old v3.6.x commands continue to work with deprecation warnings
- Frontend migration can be done incrementally
- Old commands transparently use new unified backend
## Status
✅ Backend compiles successfully (cargo check passes)
⚠️ 16 warnings (8 deprecation + 8 unused functions - expected)
- Add mcp.geminiTitle to both zh.json and en.json
- Fix McpPanel title logic to handle all three apps (claude/codex/gemini)
- Previous logic would incorrectly display codexTitle for gemini
- Add gemini_mcp.rs module for Gemini MCP file I/O operations
- Implement sync_enabled_to_gemini to export enabled MCPs to ~/.gemini/settings.json
- Implement import_from_gemini to import MCPs from Gemini config
- Add Gemini sync logic in services/mcp.rs (upsert_server, delete_server, set_enabled)
- Register Tauri commands for Gemini MCP sync and import
- Update frontend API calls and McpPanel to support Gemini
Fixes the issue where adding MCP servers in Gemini tab would not sync to ~/.gemini/settings.json
Implements dual-editor pattern for Gemini providers, following the Codex architecture:
- Environment variables (.env format) editor
- Extended configuration (config.json) editor with common config support
New Components:
- GeminiConfigSections: Separate sections for env and config editing
- GeminiCommonConfigModal: Modal for editing common config snippets
New Hooks:
- useGeminiConfigState: Manages env/config separation and conversion
- Converts between .env string format and JSON object
- Validates JSON config structure
- Extracts API Key and Base URL from env
- useGeminiCommonConfig: Handles common config snippets
- Deep merge algorithm for combining configs
- Remove common config logic for toggling off
- localStorage persistence for snippets
Features:
- Format buttons for both env and config editors
- Common config toggle with deep merge/remove
- Error validation and display
- Auto-open modal on common config errors
Configuration Structure:
{
"env": {
"GOOGLE_GEMINI_BASE_URL": "https://...",
"GEMINI_API_KEY": "sk-...",
"GEMINI_MODEL": "gemini-2.5-pro"
},
"config": {
"timeout": 30000,
"maxRetries": 3
}
}
This brings Gemini providers to feature parity with Claude and Codex.
- Change ProviderForm to use providerForm.category* instead of providerPreset.category*
- Remove duplicate category keys from providerPreset namespace in both zh.json and en.json
- Fix naming inconsistency: use categoryAggregation (not categoryAggregator)
- Fixes issue where English UI would show Chinese defaultValue fallbacks
This ensures single source of truth for category labels and improves maintainability.
Migrate the Claude common config snippet storage from browser localStorage
to the persistent config.json file for better cross-device sync and backup support.
**Backend Changes:**
- Add `claude_common_config_snippet` field to `MultiAppConfig` struct
- Add `get_claude_common_config_snippet` and `set_claude_common_config_snippet` Tauri commands
- Include JSON validation in the setter command
**Frontend Changes:**
- Create new `lib/api/config.ts` API module
- Refactor `useCommonConfigSnippet` hook to use config.json instead of localStorage
- Add automatic one-time migration from localStorage to config.json
- Add loading state during initialization
**Benefits:**
- Cross-device synchronization via backup/restore
- More reliable persistence than browser storage
- Centralized configuration management
- Seamless migration for existing users
- Use hardcoded "Claude", "Codex", "Gemini" instead of i18n keys
- Brand names should not be translated across different locales
- Simplifies code by removing useTranslation hook from AppSwitcher
- Reduces maintenance overhead in translation files
- Add i18n for Claude/Codex/Gemini app names in AppSwitcher
- Use useTranslation hook with existing translation keys
- Fix ASCII diagram alignment in README files
Address code review feedback for PR #214:
- Replace hardcoded Chinese strings with English in auto-imported prompts
- Prompt name: "Auto-imported Prompt" instead of "初始提示词"
- Description: "Automatically imported on first launch"
- Remove panic risk by replacing expect() with proper error propagation
- Use AppError::localized for bilingual error messages
- Extract get_base_dir_with_fallback() helper to eliminate code duplication
- Update test assertions to match new English strings
- Suppress false-positive dead_code warning on TempHome.dir field
All 5 tests passing with zero compiler warnings.
- Extract prompt file path logic to dedicated prompt_files module
- Refactor PromptService to use centralized path resolution
- Implement auto-import for existing prompt files on first startup
- Add comprehensive unit tests for auto-import functionality
Allow users to create Gemini provider configurations without API key
and fill it in later. Split validation into two modes:
- validate_gemini_settings: Basic structure check (used when adding)
- validate_gemini_settings_strict: Full validation (used when switching)
This fixes the error 'Gemini config missing required field: GEMINI_API_KEY'
when trying to add Gemini providers from presets like PackyCode or Google.
Changes:
- Add validate_gemini_settings_strict for switching validation
- Update write_gemini_live to use strict validation
- Add comprehensive tests for both validation modes
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
Update Google Gemini preset to match Claude Official styling:
- Rename 'Google' to 'Google Official'
- Add GeminiIcon support in preset selector
- Add custom theme with Google blue (#4285F4) background
- Update PresetTheme type to support 'gemini' icon type
Changes:
- Add GeminiPresetTheme interface
- Add theme config to Google Official preset
- Import and render GeminiIcon in ProviderPresetSelector
- Update PresetTheme icon type to include 'gemini'
* feat(prompts): add prompt management across Tauri service and React UI
- backend: add commands/prompt.rs, services/prompt.rs, register in commands/mod.rs and lib.rs, refine app_config.rs
- frontend: add PromptPanel, PromptFormModal, PromptListItem, MarkdownEditor, usePromptActions, integrate in App.tsx
- api: add src/lib/api/prompts.ts
- i18n: update src/i18n/locales/{en,zh}.json
- build: update package.json and pnpm-lock.yaml
* feat(i18n): improve i18n for prompts and Markdown editor
- update src/i18n/locales/{en,zh}.json keys and strings
- apply i18n in PromptFormModal, PromptPanel, and MarkdownEditor
- align prompt text with src-tauri/src/services/prompt.rs
* feat(prompts): add enable/disable toggle and simplify panel UI
- Add PromptToggle component and integrate in prompt list items
- Implement toggleEnabled with optimistic update; enable via API, disable via upsert with enabled=false;
reload after success
- Simplify PromptPanel: remove file import and current-file preview to keep CRUD flow focused
- Tweak header controls style (use mcp variant) and minor copy: rename “Prompt Management” to “Prompts”
- i18n: add disableSuccess/disableFailed messages
- Backend (Tauri): prevent duplicate backups when importing original prompt content
* style: unify code formatting with trailing commas
* feat(prompts): add Gemini filename support to PromptFormModal
Update filename mapping to use Record<AppId, string> pattern, supporting
GEMINI.md alongside CLAUDE.md and AGENTS.md.
* fix(prompts): sync enabled prompt to file when updating
When updating a prompt that is currently enabled, automatically sync
the updated content to the corresponding live file (CLAUDE.md/AGENTS.md/GEMINI.md).
This ensures the active prompt file always reflects the latest content
when editing enabled prompts.
Optimize custom endpoint management logic to distinguish between edit and create modes:
- Edit mode: endpoints are read/written directly to backend via API
- Create mode: use draftCustomEndpoints to stage, save on submit
- Remove duplicate endpoint loading in useSpeedTestEndpoints
- Add isSaving state and initialCustomUrls tracking