Feature/quick search#3
Conversation
- Add .kiro configuration for quick-settings-search spec with requirements-first workflow - Add comprehensive requirements document defining Quick Settings Search functionality with 7 key requirements covering activation, search, results display, navigation, registry, schema, and backend integration - Add design document placeholder for UI/UX specifications - Add tasks document placeholder for implementation tracking - Add preferences-config documentation to wordai-editor for feature reference - Establishes foundation for implementing command palette-style settings search with Cmd+Shift+P/Ctrl+Shift+P activation
- Create src/types/preferences.ts with Tab union type, SettingEntry interface, Preferences interface (general/aiEngine/typography/privacy groups), and defaultPreferences constant - Requirements: 6.1, 6.2, 6.3, 6.4
… property tests - Create src/data/settingRegistry.ts with 20 SETTING_REGISTRY entries across 4 tabs (general, ai-engine, typography, privacy), each with id in 'tab.settingName' format - Create src/data/settingRegistry.test.ts with property tests: Property 1: all required fields non-empty Property 2: id format and tab prefix match - Requirements: 5.1-5.6
…(tasks 2.1-2.4)
- Add src-tauri/src/preferences_store.rs with load/save/reset logic
- load_preferences: reads user_{userId}.json, merges with default.json
- save_preferences: writes user_{userId}.json, creates dir if needed
- reset_preferences: resets one group or all to default.json values
- merge_with_defaults: deep-merge helper for filling missing keys
- 10 unit tests covering merge, load, save, reset, and round-trip
- Add public/preferences/default.json with all default preference values
matching defaultPreferences in src/types/preferences.ts
- Register preferences_store module and 3 Tauri commands in lib.rs:
load_preferences, save_preferences, reset_preferences
- Add tempfile dev-dependency to Cargo.toml for unit tests
All 48 Rust tests pass.
…ckend - Create src-tauri/src/preferences_store.rs with load/save/reset commands and merge_with_defaults deep-merge helper - Create public/preferences/default.json with all default preference values - Register preferences commands in lib.rs invoke_handler - Add unit tests for merge, load, save, round-trip, and reset logic - Add tempfile dev-dependency to Cargo.toml - Requirements: 7.1-7.7, 8.1-8.4
- Create src/services/preferencesService.ts with loadPreferences, savePreferences, resetPreferences Tauri IPC wrappers - Update src/mocks/tauri.ts with in-memory mock handlers for all 3 commands - Create src/services/preferencesService.test.ts with 6 unit tests covering correct IPC args and error propagation - Requirements: 6.1, 7.1, 7.3, 7.4, 7.7
…sk 5.1) - Create QuickSearchPopup.tsx with centered overlay modal and backdrop - Export filterSettings() utility for property-based testing - Auto-focus search input on open via useEffect + ref - Real-time case-insensitive filtering by label, description, keywords - Show full list when empty; 'No settings found' when no matches - Cap results at 8 with overflow-y scroll - Highlight first result by default; ArrowUp/ArrowDown navigation - Show label, description, and tab badge per result item - Close on Escape key or backdrop click - Call onSelect(entry) on Enter or result click - Return null when isOpen is false
…ask 5.2) - Property 3: For any non-empty query, every result contains the query in label, description, or at least one keyword (case-insensitive) - Property 4: Empty/whitespace query returns full SETTING_REGISTRY - 27 tests passing across diverse query inputs - Validates: Requirements 2.2, 2.3, 2.4
…k 5.3) - Test Escape key calls onClose (Req 1.3) - Test backdrop click calls onClose (Req 1.4) - Test 'No settings found' renders when no matches (Req 2.5) - Test ArrowDown/ArrowUp navigation moves highlight (Req 3.4) - Test Enter on highlighted item calls onSelect with correct entry (Req 4.2) - Test click on result calls onSelect (Req 4.1) - Test label, description, and tab badge display (Req 3.1, 3.2) - Test first result highlighted by default (Req 3.5) - Fix scrollIntoView guard in QuickSearchPopup for jsdom compatibility - 16 tests passing
- Create src/components/QuickSearchPopup.tsx with overlay modal, auto-focus, real-time filtering, 8-result cap, arrow key navigation, tab badges, Escape/backdrop close, Enter/click selection - Export filterSettings() utility for property testing - Create QuickSearchPopup.property.test.ts with Property 3 & 4 tests (27 tests) - Create QuickSearchPopup.test.tsx with 16 unit tests - Requirements: 1.3, 1.4, 2.2-2.6, 3.1-3.5, 4.1, 4.2, 4.4
…vigation wiring - App.tsx: add isQuickSearchOpen state, Cmd+Shift+P/Ctrl+Shift+P keydown listener, handleQuickSearchSelect, preferencesInitialTab/targetSettingId state, render QuickSearchPopup, update PreferencesDialog props - PreferencesDialog.tsx: import Tab from types/preferences, add initialTab/targetSettingId props, scroll-to-setting useEffect, data-setting-id attributes on all 16 setting rows - Create QuickSearchShortcut.test.tsx with 4 keyboard shortcut tests - Requirements: 1.1, 1.2, 4.1, 4.2, 4.3
…ssing All quick-settings-search feature tests pass (240/243). 3 pre-existing failures in EditorCanvas/VisualEffects are unrelated to this feature.
…cut detection - Add console logging for QuickSearch initialization and keyboard events - Fix keyboard shortcut detection by converting key to lowercase for case-insensitive matching - Simplify event listener registration by removing unnecessary document existence check - Switch from document to window for keydown event listener attachment - Improve debugging visibility for keyboard event handling flow
…ave enabling - Add isFilePersisted state flag to track when document has been saved to disk at least once - Add markFilePersisted action and dispatcher to update persistence state on successful save - Add enabled parameter to useAutoSave hook to conditionally disable auto-save when file not persisted - Pass isFilePersisted to useAutoSave to prevent auto-save attempts before initial file save - Update handleSaveSuccess callback to mark file as persisted after successful save - Add null coalescing for filePath in useAutoSave invocation to handle undefined paths safely - Update dependency arrays in useAutoSave effects to include enabled flag for proper re-evaluation
There was a problem hiding this comment.
Pull request overview
Adds a command-palette style Quick Settings Search to WordAI Editor and introduces a preferences storage system (types, registry, frontend IPC service, and a Rust Tauri backend) to support loading/saving/resetting user preferences.
Changes:
- Implement QuickSearchPopup UI + keyboard shortcut wiring to open PreferencesDialog on the correct tab and scroll to a selected setting.
- Add a centralized SettingRegistry and TypeScript preferences schema/defaults.
- Add Tauri IPC commands + Rust preferences store for load/save/reset, along with mocks and tests.
Reviewed changes
Copilot reviewed 22 out of 24 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/wordai-editor/src/types/preferences.ts | Adds preferences schema/types and defaultPreferences. |
| apps/wordai-editor/src/services/stateManager.tsx | Adds isFilePersisted flag and related actions to global state. |
| apps/wordai-editor/src/services/preferencesService.ts | Adds IPC wrappers for load/save/reset preferences. |
| apps/wordai-editor/src/services/preferencesService.test.ts | Unit tests for preferencesService IPC calls. |
| apps/wordai-editor/src/mocks/tauri.ts | Adds mock handlers for preferences IPC in browser dev mode. |
| apps/wordai-editor/src/hooks/useAutoSave.ts | Adds enabled flag to control auto-save effects. |
| apps/wordai-editor/src/data/settingRegistry.ts | Introduces flat registry of searchable settings metadata. |
| apps/wordai-editor/src/data/settingRegistry.test.ts | Property tests validating registry completeness/consistency. |
| apps/wordai-editor/src/components/QuickSearchShortcut.test.tsx | Tests for Cmd/Ctrl+Shift+P shortcut behavior. |
| apps/wordai-editor/src/components/QuickSearchPopup.tsx | Implements quick search modal UI + filtering/navigation behavior. |
| apps/wordai-editor/src/components/QuickSearchPopup.test.tsx | Unit tests for popup interactions and keyboard navigation. |
| apps/wordai-editor/src/components/QuickSearchPopup.property.test.ts | Property tests for filter correctness. |
| apps/wordai-editor/src/components/PreferencesDialog.tsx | Adds initialTab/targetSettingId support and setting anchors. |
| apps/wordai-editor/src/App.tsx | Wires keyboard shortcut, popup, and preferences navigation; adjusts autosave enablement. |
| apps/wordai-editor/src-tauri/src/preferences_store.rs | Adds Rust preferences store (load/save/reset + merge defaults) and tests. |
| apps/wordai-editor/src-tauri/src/lib.rs | Registers new preferences IPC commands. |
| apps/wordai-editor/src-tauri/Cargo.toml | Adds dev dependency for Rust tests. |
| apps/wordai-editor/src-tauri/Cargo.lock | Locks new Rust dev dependency. |
| apps/wordai-editor/public/preferences/default.json | Adds bundled default preferences JSON. |
| apps/wordai-editor/docs/features/preferences-config.md | Documents preferences config system and quick search flow. |
| .kiro/specs/quick-settings-search/tasks.md | Implementation plan/spec checklist for the feature. |
| .kiro/specs/quick-settings-search/requirements.md | Requirements spec for quick settings search + preferences. |
| .kiro/specs/quick-settings-search/.config.kiro | Kiro spec metadata for the feature. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (!targetSettingId || !isOpen) return; | ||
| const timer = setTimeout(() => { | ||
| const el = document.querySelector(`[data-setting-id="${targetSettingId}"]`); | ||
| if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' }); |
There was a problem hiding this comment.
This effect calls el.scrollIntoView(...) without checking that scrollIntoView exists. In test/SSR environments (and some older webviews), scrollIntoView can be undefined, which would throw when opening the dialog via Quick Search. Add a type/feature check before calling it (similar to the guard used in QuickSearchPopup).
| if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' }); | |
| if (el && typeof (el as any).scrollIntoView === 'function') { | |
| (el as any).scrollIntoView({ behavior: 'smooth', block: 'center' }); | |
| } |
| {sliders.map((s, i) => ( | ||
| <div key={s.label}> | ||
| <div key={s.label} data-setting-id={i === 0 ? 'ai-engine.creativity' : 'ai-engine.contextWindowTokens'}> | ||
| {i > 0 && <div style={{ marginBottom: '1.5rem' }} />} |
There was a problem hiding this comment.
Using the slider loop index to assign data-setting-id is brittle (it silently breaks if the sliders array is reordered or extended). Prefer adding an explicit settingId field to each slider definition and using that, similar to how smartFeatures does it.
| load_preferences({ userId }: { userId: string }) { | ||
| return preferencesStore[userId] ?? defaultPreferences; | ||
| }, |
There was a problem hiding this comment.
load_preferences returns defaultPreferences by reference. If any caller mutates the returned object, it will mutate the shared default object for all users/sessions. Return a deep clone (and similarly clone on save) to keep defaults immutable and avoid cross-test/session state leaks.
| prefs_dir: &PathBuf, | ||
| defaults: &Value, | ||
| user_id: &str, | ||
| ) -> Result<Value, IPCError> { | ||
| let user_file = prefs_dir.join(format!("user_{}.json", user_id)); |
There was a problem hiding this comment.
user_id is interpolated directly into the filename (user_{user_id}.json). If user_id can contain path separators (e.g., ../), this can lead to path traversal and reading/writing outside the preferences directory. Validate/sanitize user_id (e.g., allow only [A-Za-z0-9_-]), or derive the filename from a safe encoding/hash.
| console.log('[QuickSearch] typeof window:', typeof window); | ||
| console.log('[QuickSearch] registering keydown listener on window'); | ||
| const handleKeyDown = (e: KeyboardEvent) => { | ||
| console.log('[QuickSearch] keydown:', e.key, 'meta:', e.metaKey, 'ctrl:', e.ctrlKey, 'shift:', e.shiftKey); | ||
| if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === 'p') { | ||
| e.preventDefault(); | ||
| console.log('[QuickSearch] opening popup'); |
There was a problem hiding this comment.
Remove the console.log debug statements from the global QuickSearch keydown listener (or guard them behind a dev-only flag). Leaving these in will spam logs on every keypress in production and can impact performance/privacy.
| console.log('[QuickSearch] typeof window:', typeof window); | |
| console.log('[QuickSearch] registering keydown listener on window'); | |
| const handleKeyDown = (e: KeyboardEvent) => { | |
| console.log('[QuickSearch] keydown:', e.key, 'meta:', e.metaKey, 'ctrl:', e.ctrlKey, 'shift:', e.shiftKey); | |
| if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === 'p') { | |
| e.preventDefault(); | |
| console.log('[QuickSearch] opening popup'); | |
| const handleKeyDown = (e: KeyboardEvent) => { | |
| if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === 'p') { | |
| e.preventDefault(); |
| } else if (e.key === 'ArrowDown') { | ||
| e.preventDefault(); |
There was a problem hiding this comment.
When results.length === 0, results.length - 1 becomes -1, so ArrowDown sets highlightIndex to -1. That can put the component into an invalid state (negative index) and breaks the assumption that a highlighted option index is >= 0. Guard ArrowUp/ArrowDown/Enter when there are no results, or clamp the max index to at least 0.
| } else if (e.key === 'ArrowDown') { | |
| e.preventDefault(); | |
| return; | |
| } | |
| const isNavigationKey = | |
| e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter'; | |
| if (isNavigationKey && results.length === 0) { | |
| // No results to navigate or select; just prevent default behavior. | |
| e.preventDefault(); | |
| return; | |
| } | |
| if (e.key === 'ArrowDown') { | |
| e.preventDefault(); |
| <button style={{ background: 'rgba(255,255,255,0.2)', backdropFilter: 'blur(12px)', border: 'none', borderRadius: '0.5rem', padding: '0.5rem 1rem', fontSize: '0.75rem', fontWeight: 700, color: '#ffffff', cursor: 'pointer' }}>Audit Security</button> | ||
| </div> | ||
| </div> | ||
| <div data-setting-id="privacy.localProcessingOnly" style={{ display: 'none' }} /> |
There was a problem hiding this comment.
privacy.localProcessingOnly is represented only by a display: none placeholder, so Quick Search navigation will scroll to something invisible and users still can’t change the setting. Either add a real UI control for this setting in the Privacy tab or remove it from SETTING_REGISTRY until it’s supported.
| <div data-setting-id="privacy.localProcessingOnly" style={{ display: 'none' }} /> | |
| <div data-setting-id="privacy.localProcessingOnly" style={{ padding: '1.25rem 1.5rem', borderRadius: '1rem', border: '1px solid #e0e0e0', backgroundColor: '#fafafa', display: 'flex', flexDirection: 'column', gap: '0.75rem' }}> | |
| <div> | |
| <h3 style={{ margin: 0, fontSize: '0.9rem', fontWeight: 600 }}>Local processing only</h3> | |
| <p style={{ margin: '0.35rem 0 0', fontSize: '0.8rem', color: '#555' }}> | |
| When enabled, WordAI Editor will process your content only on this device where possible, and avoid sending data to remote services except when strictly required. | |
| </p> | |
| </div> | |
| <label style={{ display: 'inline-flex', alignItems: 'center', gap: '0.5rem', fontSize: '0.85rem', color: '#333', cursor: 'pointer' }}> | |
| <input type="checkbox" style={{ width: '14px', height: '14px' }} /> | |
| <span>Prefer on-device processing only</span> | |
| </label> | |
| </div> |
| case 'SET_DOCUMENT': | ||
| return { | ||
| ...state, | ||
| document: action.payload.document, | ||
| filePath: action.payload.filePath, | ||
| hasUnsavedChanges: false, | ||
| isFilePersisted: false, | ||
| }; |
There was a problem hiding this comment.
SET_DOCUMENT always resets isFilePersisted to false, which disables auto-save (since App passes isFilePersisted as the enabled flag) even for documents that were just created or loaded from disk. Consider setting isFilePersisted based on how the document was obtained (e.g., true for load/create flows that already persist), or include an explicit flag in the action payload so existing docs don’t lose auto-save.
| markFilePersisted(); | ||
| }, [markSaved, markFilePersisted]); |
There was a problem hiding this comment.
handleSaveSuccess calls both markSaved(doc) and markFilePersisted(), but MARK_SAVED already sets isFilePersisted: true in the reducer. Consider removing the extra dispatch (or removing isFilePersisted mutation from MARK_SAVED) to avoid redundant state updates and confusion about the source of truth.
| markFilePersisted(); | |
| }, [markSaved, markFilePersisted]); | |
| }, [markSaved]); |
| const [activeTab, setActiveTab] = useState<Tab>(initialTab ?? 'general'); | ||
| const [searchQuery, setSearchQuery] = useState(''); | ||
|
|
||
| useEffect(() => { |
There was a problem hiding this comment.
activeTab is initialized from initialTab only on the first mount. Since the component returns null when closed (but stays mounted), subsequent opens won’t respect a new initialTab from Quick Search and may show the previously selected tab. Sync activeTab from initialTab when isOpen becomes true (or when initialTab changes), and consider resetting preferencesInitialTab on close in App.
| useEffect(() => { | |
| useEffect(() => { | |
| if (!isOpen || !initialTab) return; | |
| if (activeTab !== initialTab) { | |
| setActiveTab(initialTab); | |
| } | |
| }, [isOpen, initialTab, activeTab]); | |
| useEffect(() => { |
|
@copilot apply changes based on the comments in this thread |
Agent-Logs-Url: https://github.com/khuepm/WordAI/sessions/e03252cc-551b-4b6d-be01-4ad1425db1fb Co-authored-by: khuepm <8740125+khuepm@users.noreply.github.com>
Applied all changes from the review thread in commit dfc3444:
|
Agent-Logs-Url: https://github.com/khuepm/WordAI/sessions/5c41ec0e-2752-4df8-9b81-212fdfaef6af Co-authored-by: khuepm <8740125+khuepm@users.noreply.github.com>
- Add effect to reset saveError state when auto-save is disabled - Add null check for saveError.message before rendering error banner - Prevents stale error messages from displaying when document not yet persisted
Uh oh!
There was an error while loading. Please reload this page.