diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs index a02921e1..4ec1294a 100644 --- a/src-tauri/src/settings.rs +++ b/src-tauri/src/settings.rs @@ -10,6 +10,10 @@ pub struct Settings { pub github_token: Option, pub github_username: Option, pub auto_pull_interval_minutes: Option, + pub telemetry_consent: Option, + pub crash_reporting_enabled: Option, + pub analytics_enabled: Option, + pub anonymous_id: Option, } fn settings_path() -> Result { @@ -56,6 +60,13 @@ fn save_settings_at(path: &PathBuf, settings: Settings) -> Result<(), String> { .map(|k| k.trim().to_string()) .filter(|k| !k.is_empty()), auto_pull_interval_minutes: settings.auto_pull_interval_minutes, + telemetry_consent: settings.telemetry_consent, + crash_reporting_enabled: settings.crash_reporting_enabled, + analytics_enabled: settings.analytics_enabled, + anonymous_id: settings + .anonymous_id + .map(|k| k.trim().to_string()) + .filter(|k| !k.is_empty()), }; let json = serde_json::to_string_pretty(&cleaned) @@ -122,6 +133,10 @@ mod tests { assert!(s.github_token.is_none()); assert!(s.github_username.is_none()); assert!(s.auto_pull_interval_minutes.is_none()); + assert!(s.telemetry_consent.is_none()); + assert!(s.crash_reporting_enabled.is_none()); + assert!(s.analytics_enabled.is_none()); + assert!(s.anonymous_id.is_none()); } #[test] @@ -132,6 +147,10 @@ mod tests { google_key: Some("AIza-test".to_string()), github_token: Some("gho_xyz789".to_string()), github_username: Some("lucaong".to_string()), + telemetry_consent: Some(true), + crash_reporting_enabled: Some(true), + analytics_enabled: Some(false), + anonymous_id: Some("abc-123-uuid".to_string()), ..Default::default() }; let json = serde_json::to_string(&settings).unwrap(); @@ -140,6 +159,10 @@ mod tests { assert_eq!(parsed.google_key, settings.google_key); assert_eq!(parsed.github_token, settings.github_token); assert_eq!(parsed.github_username, settings.github_username); + assert_eq!(parsed.telemetry_consent, Some(true)); + assert_eq!(parsed.crash_reporting_enabled, Some(true)); + assert_eq!(parsed.analytics_enabled, Some(false)); + assert_eq!(parsed.anonymous_id.as_deref(), Some("abc-123-uuid")); } #[test] @@ -159,6 +182,7 @@ mod tests { github_token: Some("gho_token123".to_string()), github_username: Some("lucaong".to_string()), auto_pull_interval_minutes: Some(10), + ..Default::default() }); assert_eq!(loaded.anthropic_key.as_deref(), Some("sk-ant-key")); assert_eq!(loaded.openai_key.as_deref(), Some("sk-openai")); @@ -223,6 +247,35 @@ mod tests { assert!(err.contains("Failed to parse settings")); } + #[test] + fn test_telemetry_fields_roundtrip() { + let loaded = save_and_reload(Settings { + telemetry_consent: Some(true), + crash_reporting_enabled: Some(true), + analytics_enabled: Some(false), + anonymous_id: Some("test-uuid-v4".to_string()), + ..Default::default() + }); + assert_eq!(loaded.telemetry_consent, Some(true)); + assert_eq!(loaded.crash_reporting_enabled, Some(true)); + assert_eq!(loaded.analytics_enabled, Some(false)); + assert_eq!(loaded.anonymous_id.as_deref(), Some("test-uuid-v4")); + } + + #[test] + fn test_old_settings_json_missing_telemetry_fields() { + let dir = tempfile::TempDir::new().unwrap(); + let path = dir.path().join("settings.json"); + // Simulate old settings.json without telemetry fields + fs::write(&path, r#"{"anthropic_key":"sk-test"}"#).unwrap(); + let loaded = get_settings_at(&path).unwrap(); + assert_eq!(loaded.anthropic_key.as_deref(), Some("sk-test")); + assert!(loaded.telemetry_consent.is_none()); + assert!(loaded.crash_reporting_enabled.is_none()); + assert!(loaded.analytics_enabled.is_none()); + assert!(loaded.anonymous_id.is_none()); + } + #[test] fn test_settings_path_returns_ok() { let result = settings_path(); diff --git a/src/App.test.tsx b/src/App.test.tsx index c1ce65e3..41521798 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -67,7 +67,7 @@ const mockCommandResults: Record = { get_modified_files: [], get_note_content: mockAllContent['/vault/project/test.md'] || '', get_file_history: [], - get_settings: { anthropic_key: null, openai_key: null, google_key: null, github_token: null, github_username: null, auto_pull_interval_minutes: null }, + get_settings: { anthropic_key: null, openai_key: null, google_key: null, github_token: null, github_username: null, auto_pull_interval_minutes: null, telemetry_consent: true, crash_reporting_enabled: null, analytics_enabled: null, anonymous_id: null }, git_pull: { status: 'up_to_date', message: 'Already up to date', updatedFiles: [], conflictFiles: [] }, save_settings: null, check_vault_exists: true, diff --git a/src/components/SettingsPanel.test.tsx b/src/components/SettingsPanel.test.tsx index 27d73b12..dcce7839 100644 --- a/src/components/SettingsPanel.test.tsx +++ b/src/components/SettingsPanel.test.tsx @@ -24,6 +24,10 @@ const emptySettings: Settings = { github_token: null, github_username: null, auto_pull_interval_minutes: null, + telemetry_consent: null, + crash_reporting_enabled: null, + analytics_enabled: null, + anonymous_id: null, } const populatedSettings: Settings = { @@ -33,6 +37,10 @@ const populatedSettings: Settings = { github_token: null, github_username: null, auto_pull_interval_minutes: 5, + telemetry_consent: null, + crash_reporting_enabled: null, + analytics_enabled: null, + anonymous_id: null, } describe('SettingsPanel', () => { @@ -87,14 +95,14 @@ describe('SettingsPanel', () => { fireEvent.click(screen.getByTestId('settings-save')) - expect(onSave).toHaveBeenCalledWith({ + expect(onSave).toHaveBeenCalledWith(expect.objectContaining({ anthropic_key: null, openai_key: 'sk-openai-test', google_key: null, github_token: null, github_username: null, auto_pull_interval_minutes: 5, - }) + })) expect(onClose).toHaveBeenCalled() }) @@ -108,14 +116,14 @@ describe('SettingsPanel', () => { fireEvent.click(screen.getByTestId('settings-save')) - expect(onSave).toHaveBeenCalledWith({ + expect(onSave).toHaveBeenCalledWith(expect.objectContaining({ anthropic_key: null, openai_key: null, google_key: null, github_token: null, github_username: null, auto_pull_interval_minutes: 5, - }) + })) }) it('calls onClose when Cancel is clicked', () => { @@ -150,14 +158,14 @@ describe('SettingsPanel', () => { fireEvent.change(openaiInput, { target: { value: 'sk-openai-test' } }) fireEvent.keyDown(screen.getByTestId('settings-panel'), { key: 'Enter', metaKey: true }) - expect(onSave).toHaveBeenCalledWith({ + expect(onSave).toHaveBeenCalledWith(expect.objectContaining({ anthropic_key: null, openai_key: 'sk-openai-test', google_key: null, github_token: null, github_username: null, auto_pull_interval_minutes: 5, - }) + })) }) it('calls onClose when clicking backdrop', () => { diff --git a/src/components/SettingsPanel.tsx b/src/components/SettingsPanel.tsx index 4d61dd20..552240e4 100644 --- a/src/components/SettingsPanel.tsx +++ b/src/components/SettingsPanel.tsx @@ -142,7 +142,11 @@ function SettingsPanelInner({ settings, onSave, onClose }: Omit { onSave(buildSettings()) diff --git a/src/hooks/useSettings.test.ts b/src/hooks/useSettings.test.ts index 7bc05e7b..06c7d5e6 100644 --- a/src/hooks/useSettings.test.ts +++ b/src/hooks/useSettings.test.ts @@ -10,6 +10,10 @@ const defaultSettings: Settings = { github_token: null, github_username: null, auto_pull_interval_minutes: null, + telemetry_consent: null, + crash_reporting_enabled: null, + analytics_enabled: null, + anonymous_id: null, } const savedSettings: Settings = { @@ -19,6 +23,10 @@ const savedSettings: Settings = { github_token: null, github_username: null, auto_pull_interval_minutes: null, + telemetry_consent: null, + crash_reporting_enabled: null, + analytics_enabled: null, + anonymous_id: null, } let mockSettingsStore: Settings = { ...defaultSettings } @@ -80,6 +88,10 @@ describe('useSettings', () => { github_token: null, github_username: null, auto_pull_interval_minutes: null, + telemetry_consent: null, + crash_reporting_enabled: null, + analytics_enabled: null, + anonymous_id: null, } await act(async () => { diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts index 7419fc26..3f1c95e4 100644 --- a/src/hooks/useSettings.ts +++ b/src/hooks/useSettings.ts @@ -14,6 +14,10 @@ const EMPTY_SETTINGS: Settings = { github_token: null, github_username: null, auto_pull_interval_minutes: null, + telemetry_consent: null, + crash_reporting_enabled: null, + analytics_enabled: null, + anonymous_id: null, } export function useSettings() { diff --git a/src/mock-tauri/mock-handlers.ts b/src/mock-tauri/mock-handlers.ts index 1593540b..ea360365 100644 --- a/src/mock-tauri/mock-handlers.ts +++ b/src/mock-tauri/mock-handlers.ts @@ -81,6 +81,10 @@ let mockSettings: Settings = { github_token: null, github_username: null, auto_pull_interval_minutes: 5, + telemetry_consent: null, + crash_reporting_enabled: null, + analytics_enabled: null, + anonymous_id: null, } let mockLastVaultPath: string | null = null diff --git a/src/types.ts b/src/types.ts index 40ee763c..a1f77117 100644 --- a/src/types.ts +++ b/src/types.ts @@ -77,6 +77,10 @@ export interface Settings { github_token: string | null github_username: string | null auto_pull_interval_minutes: number | null + telemetry_consent: boolean | null + crash_reporting_enabled: boolean | null + analytics_enabled: boolean | null + anonymous_id: string | null } export interface GitPullResult {