diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs index 7a335392..4731b939 100644 --- a/src-tauri/src/settings.rs +++ b/src-tauri/src/settings.rs @@ -4,8 +4,6 @@ use std::path::PathBuf; #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct Settings { - pub openai_key: Option, - pub google_key: Option, pub github_token: Option, pub github_username: Option, pub auto_pull_interval_minutes: Option, @@ -39,14 +37,6 @@ fn save_settings_at(path: &PathBuf, settings: Settings) -> Result<(), String> { // Trim whitespace and convert empty strings to None let cleaned = Settings { - openai_key: settings - .openai_key - .map(|k| k.trim().to_string()) - .filter(|k| !k.is_empty()), - google_key: settings - .google_key - .map(|k| k.trim().to_string()) - .filter(|k| !k.is_empty()), github_token: settings .github_token .map(|k| k.trim().to_string()) @@ -127,8 +117,6 @@ mod tests { #[test] fn test_default_settings_all_none() { let s = Settings::default(); - assert!(s.openai_key.is_none()); - assert!(s.google_key.is_none()); assert!(s.github_token.is_none()); assert!(s.github_username.is_none()); assert!(s.auto_pull_interval_minutes.is_none()); @@ -141,8 +129,6 @@ mod tests { #[test] fn test_settings_json_roundtrip() { let settings = Settings { - openai_key: None, - google_key: Some("AIza-test".to_string()), github_token: Some("gho_xyz789".to_string()), github_username: Some("lucaong".to_string()), telemetry_consent: Some(true), @@ -153,7 +139,6 @@ mod tests { }; let json = serde_json::to_string(&settings).unwrap(); let parsed: Settings = serde_json::from_str(&json).unwrap(); - 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)); @@ -167,20 +152,17 @@ mod tests { let dir = tempfile::TempDir::new().unwrap(); let path = dir.path().join("nonexistent.json"); let result = get_settings_at(&path).unwrap(); - assert!(result.openai_key.is_none()); + assert!(result.github_token.is_none()); } #[test] fn test_save_and_load_preserves_values() { let loaded = save_and_reload(Settings { - openai_key: Some("sk-openai".to_string()), - google_key: None, github_token: Some("gho_token123".to_string()), github_username: Some("lucaong".to_string()), auto_pull_interval_minutes: Some(10), ..Default::default() }); - assert_eq!(loaded.openai_key.as_deref(), Some("sk-openai")); assert_eq!(loaded.github_token.as_deref(), Some("gho_token123")); assert_eq!(loaded.github_username.as_deref(), Some("lucaong")); assert_eq!(loaded.auto_pull_interval_minutes, Some(10)); @@ -200,11 +182,9 @@ mod tests { #[test] fn test_save_filters_empty_and_whitespace_only() { let loaded = save_and_reload(Settings { - openai_key: Some(" ".to_string()), github_username: Some("".to_string()), ..Default::default() }); - assert!(loaded.openai_key.is_none()); assert!(loaded.github_username.is_none()); } @@ -216,15 +196,15 @@ mod tests { save_settings_at( &path, Settings { - openai_key: Some("key".to_string()), + github_token: Some("gho_test".to_string()), ..Default::default() }, ) .unwrap(); assert!(path.exists()); assert_eq!( - get_settings_at(&path).unwrap().openai_key.as_deref(), - Some("key") + get_settings_at(&path).unwrap().github_token.as_deref(), + Some("gho_test") ); } @@ -258,9 +238,9 @@ mod tests { let dir = tempfile::TempDir::new().unwrap(); let path = dir.path().join("settings.json"); // Simulate old settings.json without telemetry fields - fs::write(&path, r#"{"openai_key":"sk-test"}"#).unwrap(); + fs::write(&path, r#"{"github_token":"gho_test"}"#).unwrap(); let loaded = get_settings_at(&path).unwrap(); - assert_eq!(loaded.openai_key.as_deref(), Some("sk-test")); + assert_eq!(loaded.github_token.as_deref(), Some("gho_test")); assert!(loaded.telemetry_consent.is_none()); assert!(loaded.crash_reporting_enabled.is_none()); assert!(loaded.analytics_enabled.is_none()); diff --git a/src/App.test.tsx b/src/App.test.tsx index ed36a69f..f7c01f48 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -69,7 +69,7 @@ const mockCommandResults: Record = { get_modified_files: [], get_note_content: mockAllContent['/vault/project/test.md'] || '', get_file_history: [], - get_settings: { 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, release_channel: null }, + get_settings: { github_token: null, github_username: null, auto_pull_interval_minutes: null, telemetry_consent: true, crash_reporting_enabled: null, analytics_enabled: null, anonymous_id: null, release_channel: 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 46fa3bc7..e58dfd76 100644 --- a/src/components/SettingsPanel.test.tsx +++ b/src/components/SettingsPanel.test.tsx @@ -18,9 +18,6 @@ vi.mock('../utils/url', () => ({ })) const emptySettings: Settings = { - - openai_key: null, - google_key: null, github_token: null, github_username: null, auto_pull_interval_minutes: null, @@ -31,19 +28,6 @@ const emptySettings: Settings = { release_channel: null, } -const populatedSettings: Settings = { - openai_key: 'sk-openai-test456', - google_key: null, - github_token: null, - github_username: null, - auto_pull_interval_minutes: 5, - telemetry_consent: null, - crash_reporting_enabled: null, - analytics_enabled: null, - anonymous_id: null, - release_channel: null, -} - describe('SettingsPanel', () => { const onSave = vi.fn() const onClose = vi.fn() @@ -64,42 +48,26 @@ describe('SettingsPanel', () => { ) expect(screen.getByText('Settings')).toBeInTheDocument() - expect(screen.getByText('AI Provider Keys')).toBeInTheDocument() - expect(screen.getByText(/stored locally/)).toBeInTheDocument() + expect(screen.getByText('GitHub')).toBeInTheDocument() }) - it('shows two key fields with labels', () => { + it('does not show AI Provider Keys section', () => { render( ) - expect(screen.getByText('OpenAI')).toBeInTheDocument() - expect(screen.getByText('Google AI')).toBeInTheDocument() + expect(screen.queryByText('AI Provider Keys')).not.toBeInTheDocument() + expect(screen.queryByText('OpenAI')).not.toBeInTheDocument() + expect(screen.queryByText('Google AI')).not.toBeInTheDocument() }) - it('populates fields from settings', () => { - render( - - ) - const openaiInput = screen.getByTestId('settings-key-openai') as HTMLInputElement - const googleInput = screen.getByTestId('settings-key-google-ai') as HTMLInputElement - - expect(openaiInput.value).toBe('sk-openai-test456') - expect(googleInput.value).toBe('') - }) - - it('calls onSave with trimmed keys on save', () => { + it('calls onSave with settings on save', () => { render( ) - const openaiInput = screen.getByTestId('settings-key-openai') - fireEvent.change(openaiInput, { target: { value: ' sk-openai-test ' } }) fireEvent.click(screen.getByTestId('settings-save')) expect(onSave).toHaveBeenCalledWith(expect.objectContaining({ - - openai_key: 'sk-openai-test', - google_key: null, github_token: null, github_username: null, auto_pull_interval_minutes: 5, @@ -107,26 +75,6 @@ describe('SettingsPanel', () => { expect(onClose).toHaveBeenCalled() }) - it('converts empty/whitespace keys to null', () => { - render( - - ) - // Clear the openai key field - const openaiInput = screen.getByTestId('settings-key-openai') - fireEvent.change(openaiInput, { target: { value: ' ' } }) - - fireEvent.click(screen.getByTestId('settings-save')) - - expect(onSave).toHaveBeenCalledWith(expect.objectContaining({ - - openai_key: null, - google_key: null, - github_token: null, - github_username: null, - auto_pull_interval_minutes: 5, - })) - }) - it('calls onClose when Cancel is clicked', () => { render( @@ -155,14 +103,9 @@ describe('SettingsPanel', () => { render( ) - const openaiInput = screen.getByTestId('settings-key-openai') - fireEvent.change(openaiInput, { target: { value: 'sk-openai-test' } }) fireEvent.keyDown(screen.getByTestId('settings-panel'), { key: 'Enter', metaKey: true }) expect(onSave).toHaveBeenCalledWith(expect.objectContaining({ - - openai_key: 'sk-openai-test', - google_key: null, github_token: null, github_username: null, auto_pull_interval_minutes: 5, @@ -177,16 +120,6 @@ describe('SettingsPanel', () => { expect(onClose).toHaveBeenCalled() }) - it('clears a key field when X button is clicked', () => { - render( - - ) - const clearBtn = screen.getByTestId('clear-openai') - fireEvent.click(clearBtn) - - const openaiInput = screen.getByTestId('settings-key-openai') as HTMLInputElement - expect(openaiInput.value).toBe('') - }) it('shows keyboard shortcut hint in footer', () => { render( @@ -195,25 +128,6 @@ describe('SettingsPanel', () => { expect(screen.getByText(/to open settings/)).toBeInTheDocument() }) - it('resets fields when reopened with different settings', () => { - const { rerender } = render( - - ) - // Verify initial state - const openaiInput = screen.getByTestId('settings-key-openai') as HTMLInputElement - expect(openaiInput.value).toBe('sk-openai-test456') - - // Close and reopen with different settings - rerender( - - ) - const newSettings: Settings = { ...emptySettings, openai_key: 'new-key' } - rerender( - - ) - const updatedInput = screen.getByTestId('settings-key-openai') as HTMLInputElement - expect(updatedInput.value).toBe('new-key') - }) describe('GitHub OAuth section', () => { it('shows Login with GitHub button when not connected', () => { diff --git a/src/components/SettingsPanel.tsx b/src/components/SettingsPanel.tsx index 5cea4ee3..33e8ab7d 100644 --- a/src/components/SettingsPanel.tsx +++ b/src/components/SettingsPanel.tsx @@ -1,5 +1,5 @@ import { useState, useRef, useCallback, useEffect } from 'react' -import { X, Eye, EyeSlash, GithubLogo, SignOut } from '@phosphor-icons/react' +import { X, GithubLogo, SignOut } from '@phosphor-icons/react' import { GitHubDeviceFlow } from './GitHubDeviceFlow' import type { Settings } from '../types' import { trackEvent } from '../lib/telemetry' @@ -12,61 +12,6 @@ interface SettingsPanelProps { } -interface KeyFieldProps { - label: string - placeholder: string - value: string - onChange: (value: string) => void - onClear: () => void -} - -function KeyField({ label, placeholder, value, onChange, onClear }: KeyFieldProps) { - const [revealed, setRevealed] = useState(false) - const inputRef = useRef(null) - - return ( -
- -
- onChange(e.target.value)} - placeholder={placeholder} - className="w-full border border-border bg-transparent text-foreground rounded" - style={{ fontSize: 13, padding: '8px 60px 8px 10px', outline: 'none', fontFamily: 'inherit' }} - autoComplete="off" - data-testid={`settings-key-${label.toLowerCase().replace(/\s+/g, '-')}`} - /> -
- {value && ( - <> - - - - )} -
-
-
- ) -} - // --- GitHub OAuth Section --- interface GitHubSectionProps { @@ -120,8 +65,6 @@ export function SettingsPanel({ open, settings, onSave, onClose }: SettingsPanel } function SettingsPanelInner({ settings, onSave, onClose }: Omit) { - const [openaiKey, setOpenaiKey] = useState(settings.openai_key ?? '') - const [googleKey, setGoogleKey] = useState(settings.google_key ?? '') const [githubToken, setGithubToken] = useState(settings.github_token) const [githubUsername, setGithubUsername] = useState(settings.github_username) const [pullInterval, setPullInterval] = useState(settings.auto_pull_interval_minutes ?? 5) @@ -140,8 +83,6 @@ function SettingsPanelInner({ settings, onSave, onClose }: Omit ({ - openai_key: openaiKey.trim() || null, - google_key: googleKey.trim() || null, github_token: ghOverride ? ghOverride.token : (githubToken ?? null), github_username: ghOverride ? ghOverride.username : (githubUsername ?? null), auto_pull_interval_minutes: pullInterval, @@ -150,7 +91,7 @@ function SettingsPanelInner({ settings, onSave, onClose }: Omit { const prevAnalytics = settings.analytics_enabled ?? false @@ -199,8 +140,6 @@ function SettingsPanelInner({ settings, onSave, onClose }: Omit void }) { } interface SettingsBodyProps { - openaiKey: string; setOpenaiKey: (v: string) => void - googleKey: string; setGoogleKey: (v: string) => void githubToken: string | null; githubUsername: string | null onGitHubConnected: (token: string, username: string) => void onGitHubDisconnect: () => void @@ -247,18 +184,6 @@ interface SettingsBodyProps { function SettingsBody(props: SettingsBodyProps) { return (
-
-
AI Provider Keys
-
- API keys are stored locally on your device. Never sent to our servers. -
-
- - props.setOpenaiKey('')} /> - props.setGoogleKey('')} /> - -
-
GitHub
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 5ed47019..b7d2622b 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -144,6 +144,169 @@ function applyCustomization( // --- Sub-components --- +function SidebarGroupHeader({ label, collapsed, onToggle, count, children }: { + label: string + collapsed: boolean + onToggle: () => void + count?: number + children?: React.ReactNode +}) { + return ( + + ) +} + +function ViewItem({ view, isActive, onSelect, onEditView, onDeleteView }: { + view: ViewFile + isActive: boolean + onSelect: () => void + onEditView?: (filename: string) => void + onDeleteView?: (filename: string) => void +}) { + return ( +
+ +
+ {onEditView && ( + + )} + {onDeleteView && ( + + )} +
+
+ ) +} + +function ViewsSection({ views, selection, onSelect, collapsed, onToggle, onCreateView, onEditView, onDeleteView }: { + views: ViewFile[] + selection: SidebarSelection + onSelect: (sel: SidebarSelection) => void + collapsed: boolean + onToggle: () => void + onCreateView?: () => void + onEditView?: (filename: string) => void + onDeleteView?: (filename: string) => void +}) { + return ( +
+ + {onCreateView && ( + { e.stopPropagation(); onCreateView() }} + /> + )} + + {!collapsed && ( +
+ {views.map((v) => ( + onSelect({ kind: 'view', filename: v.filename })} + onEditView={onEditView} + onDeleteView={onDeleteView} + /> + ))} +
+ )} +
+ ) +} + +function TypesSection({ visibleSections, allSectionGroups, sectionIds, sensors, handleDragEnd, sectionProps, collapsed, onToggle, showCustomize, setShowCustomize, isSectionVisible, toggleVisibility, onCreateNewType, customizeRef }: { + visibleSections: SectionGroup[] + allSectionGroups: SectionGroup[] + sectionIds: string[] + sensors: ReturnType + handleDragEnd: (event: DragEndEvent) => void + sectionProps: { + entries: VaultEntry[]; selection: SidebarSelection; onSelect: (sel: SidebarSelection) => void + onContextMenu: (e: React.MouseEvent, type: string) => void + renamingType: string | null; renameInitialValue: string; onRenameSubmit: (v: string) => void; onRenameCancel: () => void + } + collapsed: boolean + onToggle: () => void + showCustomize: boolean + setShowCustomize: React.Dispatch> + isSectionVisible: (type: string) => boolean + toggleVisibility: (type: string) => void + onCreateNewType?: () => void + customizeRef: React.RefObject +}) { + return ( +
+
+ +
+ { e.stopPropagation(); setShowCustomize((v) => !v) }} + > + + + {onCreateNewType && ( + { e.stopPropagation(); onCreateNewType() }} + /> + )} +
+
+ {showCustomize && } +
+ {!collapsed && ( + + + {visibleSections.map((g) => ( + + ))} + + + )} +
+ ) +} + function SortableSection({ group, sectionProps }: { group: SectionGroup sectionProps: Omit @@ -225,19 +388,7 @@ function FavoritesSection({ entries, selection, onSelect, onSelectNote, onReorde return (
- + {!collapsed && ( @@ -432,107 +583,11 @@ export const Sidebar = memo(function Sidebar({ {/* Views */} {hasViews && ( -
- - {!groupCollapsed.views && ( -
- {views.map((v) => ( -
- onSelect({ kind: 'view', filename: v.filename })} - /> -
- {onEditView && ( - - )} - {onDeleteView && ( - - )} -
-
- ))} -
- )} -
+ toggleGroup('views')} onCreateView={onCreateView} onEditView={onEditView} onDeleteView={onDeleteView} /> )} - {/* Sections header + entries */} -
-
- - {showCustomize && } -
- - {/* Sortable section groups */} - {!groupCollapsed.sections && ( - - - {visibleSections.map((g) => ( - - ))} - - - )} -
+ {/* Types */} + toggleGroup('sections')} showCustomize={showCustomize} setShowCustomize={setShowCustomize} isSectionVisible={isSectionVisible} toggleVisibility={toggleVisibility} onCreateNewType={onCreateNewType} customizeRef={customizeRef} /> {/* Folder tree */} toggleGroup('folders')} /> diff --git a/src/hooks/useSettings.test.ts b/src/hooks/useSettings.test.ts index 91d5c570..b18bda38 100644 --- a/src/hooks/useSettings.test.ts +++ b/src/hooks/useSettings.test.ts @@ -4,9 +4,6 @@ import type { Settings } from '../types' import { useSettings } from './useSettings' const defaultSettings: Settings = { - - openai_key: null, - google_key: null, github_token: null, github_username: null, auto_pull_interval_minutes: null, @@ -18,9 +15,7 @@ const defaultSettings: Settings = { } const savedSettings: Settings = { - openai_key: null, - google_key: 'AIza-test', - github_token: null, + github_token: 'gho_saved_token', github_username: null, auto_pull_interval_minutes: null, telemetry_consent: null, @@ -70,7 +65,7 @@ describe('useSettings', () => { expect(result.current.loaded).toBe(true) }) - expect(result.current.settings.google_key).toBe('AIza-test') + expect(result.current.settings.github_token).toBe('gho_saved_token') expect(mockInvokeFn).toHaveBeenCalledWith('get_settings', {}) }) @@ -82,8 +77,6 @@ describe('useSettings', () => { }) const newSettings: Settings = { - openai_key: 'sk-openai-new', - google_key: null, github_token: null, github_username: null, auto_pull_interval_minutes: null, diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts index 165dee41..9ce6b6a6 100644 --- a/src/hooks/useSettings.ts +++ b/src/hooks/useSettings.ts @@ -8,8 +8,6 @@ function tauriCall(command: string, tauriArgs: Record, mockA } const EMPTY_SETTINGS: Settings = { - openai_key: null, - google_key: null, github_token: null, github_username: null, auto_pull_interval_minutes: null, diff --git a/src/hooks/useTelemetry.test.ts b/src/hooks/useTelemetry.test.ts index 38c0da6c..d3c5a7b8 100644 --- a/src/hooks/useTelemetry.test.ts +++ b/src/hooks/useTelemetry.test.ts @@ -18,7 +18,6 @@ vi.mock('../lib/telemetry', () => ({ })) const baseSettings: Settings = { - openai_key: null, google_key: null, github_token: null, github_username: null, auto_pull_interval_minutes: null, telemetry_consent: null, crash_reporting_enabled: null, analytics_enabled: null, anonymous_id: null, release_channel: null, diff --git a/src/mock-tauri/mock-handlers.ts b/src/mock-tauri/mock-handlers.ts index 3d80a299..8f9c706b 100644 --- a/src/mock-tauri/mock-handlers.ts +++ b/src/mock-tauri/mock-handlers.ts @@ -75,8 +75,6 @@ let mockHasChanges = true const mockSavedSinceCommit = new Set() let mockSettings: Settings = { - openai_key: null, - google_key: null, github_token: null, github_username: null, auto_pull_interval_minutes: 5, @@ -201,8 +199,6 @@ export const mockHandlers: Record any> = { save_settings: (args: { settings: Settings }) => { const s = args.settings mockSettings = { - openai_key: trimOrNull(s.openai_key), - google_key: trimOrNull(s.google_key), github_token: trimOrNull(s.github_token), github_username: trimOrNull(s.github_username), auto_pull_interval_minutes: s.auto_pull_interval_minutes ?? 5, diff --git a/src/types.ts b/src/types.ts index c6406032..1f2b3145 100644 --- a/src/types.ts +++ b/src/types.ts @@ -74,8 +74,6 @@ export interface ModifiedFile { } export interface Settings { - openai_key: string | null - google_key: string | null github_token: string | null github_username: string | null auto_pull_interval_minutes: number | null