Commit Graph

10 Commits

  • Refactor Codex live-write routing and cover default auth overwrite
    Collapse the two duplicated write_codex_live_atomic branches in
    write_codex_live_for_provider into a single should_write_auth guard.
    This is behavior-preserving: `if A {X} else if B {X} else {Y}` becomes
    `if A || B {X} else {Y}`.
    
    Adapt the Codex switch tests to the new opt-in default for
    preserve_codex_official_auth_on_switch (flipped off in 3f59ab37):
    add an enable_codex_official_auth_preservation() test helper for the
    cases that assert the auth-preserving path, and tag the official login
    provider with category="official" so it routes through the official
    branch rather than relying on the global preservation flag.
    
    Add a regression test locking the default (preservation off) behavior:
    switching to a third-party provider rewrites auth.json with the new
    API key and discards the existing ChatGPT OAuth login. This is the
    dual of the existing preserve-and-backfill test, which only covered
    the opt-in path.
  • feat(tray): show cached provider usage in the system tray menu (#2184)
    * feat: add Rust-side write-through usage cache
    
    Introduce an in-memory UsageCache on AppState that the existing usage
    query commands populate on success. The cache is read-only to the rest
    of the app today; the next commit consumes it from the tray menu.
    
    - New services::usage_cache module with split maps: subscription keyed
      by AppType, script keyed by (AppType, provider_id).
    - AppType gains Eq + Hash so it can be used as a HashMap key.
    - commands::subscription::get_subscription_quota now takes State<AppState>
      and writes through on success (signature change is invisible to the
      frontend — Tauri injects State automatically).
    - commands::provider::queryProviderUsage body extracted into an inner
      async fn; the public command wraps it with write-through, covering
      Copilot, coding-plan, balance, and generic script paths uniformly.
    
    Cache is in-memory only; auto-query interval and the upcoming tray
    refresh action rebuild it after restarts.
    
    * feat(tray): surface cached usage in the system tray menu
    
    Read UsageCache populated by the previous commit and render it in three
    places, scoped to whatever TRAY_SECTIONS covers (Claude/Codex/Gemini):
    
    1. Inline suffix on each provider submenu item
       "AnyProvider  · 🟢 5h 18% / 7d 23%"
    2. Disabled summary row per visible app under "Show Main"
       "Claude · Anthropic Official · 🟢 5h 18% / 7d 23%"
    3. "Refresh all usage" menu item that triggers get_subscription_quota +
       queryProviderUsage for every applicable provider, then rebuilds the
       tray menu via the existing refresh_tray_menu path.
    
    Color encoding uses emoji (🟢 <70% / 🟠 70-89% / 🔴 ≥90%) since Tauri 2
    tray labels are plain text. Missing cache entry leaves the label
    unchanged — tray never issues network requests when opened. Three new
    i18n-ready strings live in TrayTexts (en/zh/ja), following the existing
    pattern for tray text.
    
    Closes #2178.
    
    * feat(usage): bridge tray UsageCache writes to frontend React Query
    
    Why: tray hover triggers backend-only refresh that wrote to UsageCache but
    never notified the frontend, leaving main UI stale while tray showed fresh
    numbers. Emit a payload-carrying event after each cache write so React Query
    can setQueryData directly, keeping both views in sync without duplicate fetches.
    
    * fix(tray): skip hidden apps on hover refresh and drop stale disabled-script cache
    
    Address P2 findings from automated review on #2184:
    
    1. refresh_all_usage_in_tray now filters TRAY_SECTIONS by settings.visible_apps
       before scheduling subscription/script queries, matching create_tray_menu and
       preventing wasted external API calls (and rate-limit/auth-error log noise)
       for apps the user has hidden.
    
    2. format_usage_suffix only trusts the script cache when provider.meta.usage_script
       is still enabled; when a script is disabled/removed the cached suffix is now
       invalidated so the tray label no longer shows stale data indefinitely.
    
    * refactor: consolidate codex provider helpers and fix test semantics
    
    - Add Provider::is_codex_oauth() and Provider::codex_fast_mode_enabled()
      to eliminate duplicated meta extraction in claude.rs and stream_check.rs
    - Fix non-codex-oauth tests to pass codex_fast_mode=false (was true, harmless
      but semantically misleading)
    - Remove redundant is_dir() guard after resolve_skill_source_dir already
      guarantees the returned path is a directory
    
    * style: apply cargo fmt
    
    * fix(tray): reflect failed refreshes in cache and support Gemini flash-lite
    
    Follow-up to the tray usage-display feature addressing review feedback:
    
    - Write snapshots for both Ok(success:false) and Err paths in
      queryProviderUsage / get_subscription_quota so stale success data
      no longer persists across failed refreshes; the original Err is
      still returned to the frontend onError handler.
    - Include gemini_flash_lite tier in the tray summary with label "l".
      Matches the frontend SubscriptionQuotaFooter and keeps the worst
      emoji correct when lite is the highest utilization.
    - Add TIER_GEMINI_PRO / _FLASH / _FLASH_LITE constants in
      services/subscription.rs and reuse them in classify_gemini_model
      and sort_order.
    - Extract Provider::has_usage_script_enabled() to remove the
      duplicated meta.usage_script chain at two call sites.
    - Use db.get_provider_by_id in refresh_all_usage_in_tray instead of
      materialising the full provider map, and parallelise subscription
      and script futures via futures::future::join.
    - Narrow refresh_all_usage_in_tray to each section's effective
      current provider (script if enabled, else subscription when the
      provider is official). Hover refreshes now issue at most
      TRAY_SECTIONS.len() outbound requests.
    - Add 10 unit tests in tray::tests covering Claude/Codex h/w dispatch,
      Gemini p/f/l dispatch (including lite-only and lite-worst cases),
      and success/failure guards.
    
    ---------
    
    Co-authored-by: Jason <farion1231@gmail.com>
  • fix: replace implicit app inference with explicit selection for Skills import and sync
    Skills import previously inferred app enablement from filesystem presence,
    causing incorrect multi-app activation when the same skill directory existed
    under multiple app paths. Now the frontend submits explicit app selections
    via ImportSkillSelection, and schema migration preserves a snapshot of
    legacy app mappings to avoid lossy reconstruction.
    
    Also adds reconciliation to sync_to_app (removes disabled/orphaned symlinks)
    and MCP sync_all_enabled (removes disabled servers from live config).
  • fix(windows): restore default home dir resolution to prevent data loss
    v3.10.3 introduced HOME env priority on Windows for test isolation,
    which caused database path to change when HOME differs from USERPROFILE
    (common in Git/MSYS environments), making providers appear to disappear.
    
    Changes:
    - Use CC_SWITCH_TEST_HOME for test isolation instead of HOME
    - Add legacy fallback to detect v3.10.3 database location on Windows
    - Add logging for legacy path detection to aid debugging
  • fix: resolve test failures and clippy warnings
    - tests/App.test.tsx: remove outdated SettingsPage mock, use dynamic import
    - database/tests.rs: remove unused field, use struct init syntax
    - deeplink/tests.rs: use idiomatic assert!() instead of assert_eq!(true)
    - support.rs: add #[allow(dead_code)] for test utilities
    - usage_stats.rs: code formatting
  • fix(proxy): update live backup when hot-switching provider in proxy mode
    When proxy is active, switching providers only updated the database flags
    but not the live backup. This caused the wrong provider config to be
    restored when stopping the proxy.
    
    Added `update_live_backup_from_provider()` method to ProxyService that
    generates backup from provider's settings_config instead of reading from
    live files (which are already taken over by proxy).
  • fix(proxy): wait for server shutdown before exiting app
    The previous cleanup logic only sent a shutdown signal but didn't wait
    for the proxy server to actually stop. This caused a race condition
    where the app would exit before cleanup completed, leaving Live configs
    in an inconsistent state.
    
    Changes:
    - Add `server_handle` field to ProxyServer to track the spawned task
    - Modify `stop()` to wait for server task completion (5s timeout)
    - Add 100ms delay before process exit to ensure I/O flush
    - Export ProxyService and fix test files that were missing proxy_service field
  • test: migrate tests to SQLite database architecture
    This commit refactors all tests to work with the new database-based
    architecture, replacing the previous JSON config approach.
    
    Key changes:
    - Add Database export to lib.rs for test access
    - Create test helper functions in support.rs:
      - create_test_state(): Creates empty test state with fresh DB
      - create_test_state_with_config(): Migrates JSON config to DB
    - Fix environment isolation in provider_service tests:
      - provider_service_switch_missing_provider_returns_error
      - provider_service_switch_codex_missing_auth_returns_error
    - Replace ignored export tests with working alternatives:
      - export_sql_writes_to_target_path (tests Database::export_sql)
      - export_sql_returns_error_for_invalid_path (tests error handling)
    - Update error type matching to align with current implementation
    
    All tests now:
    - Use isolated test environments (test_mutex + reset_test_fs)
    - Access data via Database API instead of RwLock<MultiAppConfig>
    - Work with SQLite persistence layer
    - Pass without environment pollution or race conditions
    
    Fixes test compilation errors after database migration.
  • feat(gemini): add Gemini provider integration (#202)
    * feat(gemini): add Gemini provider integration
    
    - Add gemini_config.rs module for .env file parsing
    - Extend AppType enum to support Gemini
    - Implement GeminiConfigEditor and GeminiFormFields components
    - Add GeminiIcon with standardized 1024x1024 viewBox
    - Add Gemini provider presets configuration
    - Update i18n translations for Gemini support
    - Extend ProviderService and McpService for Gemini
    
    * fix(gemini): resolve TypeScript errors, add i18n support, and fix MCP logic
    
    **Critical Fixes:**
    - Fix TS2741 errors in tests/msw/state.ts by adding missing Gemini type definitions
    - Fix ProviderCard.extractApiUrl to support GOOGLE_GEMINI_BASE_URL display
    - Add missing apps.gemini i18n keys (zh/en) for proper app name display
    - Fix MCP service Gemini cross-app duplication logic to prevent self-copy
    
    **Technical Details:**
    - tests/msw/state.ts: Add gemini default providers, current ID, and MCP config
    - ProviderCard.tsx: Check both ANTHROPIC_BASE_URL and GOOGLE_GEMINI_BASE_URL
    - services/mcp.rs: Skip Gemini in sync_other_side logic with unreachable!() guards
    - Run pnpm format to auto-fix code style issues
    
    **Verification:**
    -  pnpm typecheck passes
    -  pnpm format completed
    
    * feat(gemini): enhance authentication and config parsing
    
    - Add strict and lenient .env parsing modes
    - Implement PackyCode partner authentication detection
    - Support Google OAuth official authentication
    - Auto-configure security.auth.selectedType for PackyCode
    - Add comprehensive test coverage for all auth types
    - Update i18n for OAuth hints and Gemini config
    
    ---------
    
    Co-authored-by: Jason <farion1231@gmail.com>
  • refactor(backend): phase 3 - unify error handling and fix backup timestamp bug
    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