diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b744425..6e466c9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,10 +16,11 @@ First of all, thank you for considering contributing to PaperCache! npm ci ``` -3. **Start the development server:** +3. **Start the Tauri development server:** ```bash - npm run dev + npm run tauri dev ``` + > **Prerequisite:** A [Rust toolchain](https://rustup.rs) is required to compile the native backend. ## Development Guidelines - **Pull Requests Required**: Never push new features directly to the `main` branch. Always create a new branch and push your changes as a Pull Request (PR) for review. diff --git a/features.md b/features.md index 4b6e16c..437c565 100644 --- a/features.md +++ b/features.md @@ -35,7 +35,7 @@ This document outlines every feature available in the PaperCache codebase, organ - **Note Context Injection**: Type `/ctx ` instead to automatically package the entire text of the current note along with your prompt, allowing the AI to read your document before answering. - **Configurable Models & Custom Endpoints**: Easily switch out your API Key, System Prompt, API Base URL, and specific AI Model in the settings panel. - **Free Top-Tier Defaults**: PaperCache comes fully pre-configured to point to OpenRouter's free tier, using powerful models like `nvidia/nemotron-3-super-120b-a12b:free` out of the box! -- **Secure API Storage**: API keys are securely encrypted at rest using your operating system's native keychain (via Electron's `safeStorage`). +- **Secure API Storage**: API keys are securely encrypted at rest using your operating system's native keychain (via the Rust `keyring` crate). ## Desktop System Integration diff --git a/package.json b/package.json index 88aac71..ddd714d 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "scripts": { "dev": "vite", "build": "tsc -b && vite build", + "tauri": "tauri", "lint": "eslint src --ext .ts,.tsx", "lint:fix": "eslint src --ext .ts,.tsx --fix", "format": "prettier --write src", diff --git a/src-tauri/src-tauri/icons/icon.png b/src-tauri/src-tauri/icons/icon.png deleted file mode 100644 index 7167384..0000000 Binary files a/src-tauri/src-tauri/icons/icon.png and /dev/null differ diff --git a/src-tauri/src/commands/fs.rs b/src-tauri/src/commands/fs.rs index 48eaad6..e71dcc4 100644 --- a/src-tauri/src/commands/fs.rs +++ b/src-tauri/src/commands/fs.rs @@ -26,7 +26,7 @@ pub fn get_safe_path(id: &str) -> Result { let parent = target.parent().ok_or("Invalid path parent")?; if !parent.exists() { - fs::create_dir_all(&parent).map_err(|e| e.to_string())?; + fs::create_dir_all(parent).map_err(|e| e.to_string())?; } let canonical_parent = parent.canonicalize().map_err(|e| e.to_string())?; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 3b077c0..3748dd5 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -58,10 +58,8 @@ pub fn run() { api.prevent_close(); let _ = w.hide(); } - tauri::WindowEvent::Focused(focused) => { - if !focused && !is_dialog_open.load(Ordering::SeqCst) { - let _ = w.hide(); - } + tauri::WindowEvent::Focused(focused) if !focused && !is_dialog_open.load(Ordering::SeqCst) => { + let _ = w.hide(); } _ => {} } diff --git a/src/assets/hero.png b/src/assets/hero.png deleted file mode 100644 index 02251f4..0000000 Binary files a/src/assets/hero.png and /dev/null differ diff --git a/src/assets/react.svg b/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/vite.svg b/src/assets/vite.svg deleted file mode 100644 index 5101b67..0000000 --- a/src/assets/vite.svg +++ /dev/null @@ -1 +0,0 @@ -Vite diff --git a/src/hooks/useVariables.ts b/src/hooks/useVariables.ts index 668083c..738b4ae 100644 --- a/src/hooks/useVariables.ts +++ b/src/hooks/useVariables.ts @@ -21,8 +21,9 @@ export function useVariables() { while ((varMatch = reVar.exec(note.content)) !== null) { const name = varMatch[1] try { - globals[name] = parser.evaluate(varMatch[2], globals as any) + globals[name] = parser.evaluate(varMatch[2], globals as Record) } catch (e) { + // eslint-disable-next-line no-console console.error(`useVariables evaluation error for ${name}:`, e) globals[name] = varMatch[2].trim() } diff --git a/tests/MathEvaluator.test.ts b/src/lib/editor/MathEvaluator.test.ts similarity index 79% rename from tests/MathEvaluator.test.ts rename to src/lib/editor/MathEvaluator.test.ts index 5a7a879..d95dcf0 100644 --- a/tests/MathEvaluator.test.ts +++ b/src/lib/editor/MathEvaluator.test.ts @@ -1,23 +1,19 @@ import { describe, it, expect, vitest } from 'vitest' -import { evaluateMath } from '../src/lib/editor/MathEvaluator' +import { evaluateMath } from './MathEvaluator' describe('MathEvaluator', () => { it('evaluates new math expressions ending with =', () => { const docStr = '1 + 2 =\n' const scope = {} const changes = evaluateMath(docStr, scope) - expect(changes).toEqual([ - { from: 7, to: 7, insert: '\u200B3' } - ]) + expect(changes).toEqual([{ from: 7, to: 7, insert: '\u200B3' }]) }) it('updates existing evaluations if they changed', () => { const docStr = '1 + 3 =\u200B3' const scope = {} const changes = evaluateMath(docStr, scope) - expect(changes).toEqual([ - { from: 8, to: 9, insert: '4' } - ]) + expect(changes).toEqual([{ from: 8, to: 9, insert: '4' }]) }) it('ignores invalid expressions', () => { @@ -40,8 +36,6 @@ describe('MathEvaluator', () => { const docStr = 'x * 2 =\n' const scope = { x: 5 } const changes = evaluateMath(docStr, scope) - expect(changes).toEqual([ - { from: 7, to: 7, insert: '\u200B10' } - ]) + expect(changes).toEqual([{ from: 7, to: 7, insert: '\u200B10' }]) }) }) diff --git a/src/lib/editor/MathEvaluator.ts b/src/lib/editor/MathEvaluator.ts index bd06057..2445703 100644 --- a/src/lib/editor/MathEvaluator.ts +++ b/src/lib/editor/MathEvaluator.ts @@ -4,7 +4,7 @@ import { Parser } from 'expr-eval' export function evaluateMath( docStr: string, - scope: Record + scope: Record ): { from: number; to: number; insert: string }[] { const parser = new Parser() const changes: { from: number; to: number; insert: string }[] = [] @@ -27,6 +27,7 @@ export function evaluateMath( insert: '\u200B' + result, }) } catch (e) { + // eslint-disable-next-line no-console console.error(`MathEvaluator new evaluation error for ${expr}:`, e) } } @@ -57,6 +58,7 @@ export function evaluateMath( } } } catch (e) { + // eslint-disable-next-line no-console console.error(`MathEvaluator old evaluation error for ${expr}:`, e) } } diff --git a/src/lib/editor/VariableScope.ts b/src/lib/editor/VariableScope.ts index 8295d67..d8224d7 100644 --- a/src/lib/editor/VariableScope.ts +++ b/src/lib/editor/VariableScope.ts @@ -30,9 +30,10 @@ export class VariableScope { const name = varMatch[1] try { const mergedScope = Object.assign({}, globalVars, newScope) - const val = parser.evaluate(varMatch[2], mergedScope as any) + const val = parser.evaluate(varMatch[2], mergedScope as Record) newScope[name] = val } catch (e) { + // eslint-disable-next-line no-console console.error(`VariableScope evaluation error for ${name}:`, e) newScope[name] = varMatch[2].trim() } diff --git a/src/lib/editor/checkboxPlugin.ts b/src/lib/editor/checkboxPlugin.ts index 6119158..ffd6321 100644 --- a/src/lib/editor/checkboxPlugin.ts +++ b/src/lib/editor/checkboxPlugin.ts @@ -66,6 +66,7 @@ export const checkboxPlugin = ViewPlugin.fromClass( const ranges = decos.map((d) => d.deco.range(d.from, d.to)) return Decoration.set(ranges, true) } catch (e) { + // eslint-disable-next-line no-console console.error('checkboxPlugin error:', e) return Decoration.none } diff --git a/src/lib/editor/codeBlockPlugin.ts b/src/lib/editor/codeBlockPlugin.ts index 5515c16..d16bfff 100644 --- a/src/lib/editor/codeBlockPlugin.ts +++ b/src/lib/editor/codeBlockPlugin.ts @@ -105,6 +105,7 @@ export const codeBlockPlugin = ViewPlugin.fromClass( const ranges = decos.map((d) => d.deco.range(d.from, d.to)) return Decoration.set(ranges, true) } catch (e) { + // eslint-disable-next-line no-console console.error('codeBlockPlugin error:', e) return Decoration.none } diff --git a/src/lib/editor/extensions.ts b/src/lib/editor/extensions.ts index d25cc3b..11b7075 100644 --- a/src/lib/editor/extensions.ts +++ b/src/lib/editor/extensions.ts @@ -152,11 +152,14 @@ export function useEditorExtensions() { messages.push({ role: 'user', content: finalPrompt }) - const completion: any = await window.electronAPI.openAIChat({ + const completion = (await window.electronAPI.openAIChat({ model: apiModel.trim() || 'nvidia/nemotron-3-super-120b-a12b:free', messages: messages, baseUrl: finalBaseUrl || '', - }) + })) as { + choices?: Array<{ message?: { content?: string } }> + error?: { message?: string } + } let response: string if (completion.choices && completion.choices.length > 0) { diff --git a/src/lib/editor/formatPlugin.ts b/src/lib/editor/formatPlugin.ts index 1f60a65..0e98a52 100644 --- a/src/lib/editor/formatPlugin.ts +++ b/src/lib/editor/formatPlugin.ts @@ -136,6 +136,7 @@ export const formatPlugin = ViewPlugin.fromClass( const ranges = decos.map((d) => d.deco.range(d.from, d.to)) return Decoration.set(ranges, true) } catch (e) { + // eslint-disable-next-line no-console console.error('formatPlugin error:', e) return Decoration.none } diff --git a/src/lib/editor/markdownPlugin.ts b/src/lib/editor/markdownPlugin.ts index d581b77..413976d 100644 --- a/src/lib/editor/markdownPlugin.ts +++ b/src/lib/editor/markdownPlugin.ts @@ -182,6 +182,7 @@ export const markdownPlugin = ViewPlugin.fromClass( const ranges = decos.map((d) => d.deco.range(d.from, d.to)) return Decoration.set(ranges, true) } catch (e) { + // eslint-disable-next-line no-console console.error('markdownPlugin error:', e) return Decoration.none } diff --git a/src/lib/editor/plugins.ts b/src/lib/editor/plugins.ts index 64c3e6a..3164778 100644 --- a/src/lib/editor/plugins.ts +++ b/src/lib/editor/plugins.ts @@ -63,6 +63,7 @@ export const aiPlugin = ViewPlugin.fromClass( const ranges = decos.map((d) => d.deco.range(d.from, d.to)) return Decoration.set(ranges, true) } catch (e) { + // eslint-disable-next-line no-console console.error('Failed to set AI decorations:', e) return Decoration.none } @@ -106,6 +107,7 @@ export const mathPlugin = ViewPlugin.fromClass( const ranges = decos.map((d) => d.deco.range(d.from, d.to)) return Decoration.set(ranges, true) } catch (e) { + // eslint-disable-next-line no-console console.error('Failed to set Math decorations:', e) return Decoration.none } diff --git a/src/lib/editor/taskPlugin.ts b/src/lib/editor/taskPlugin.ts index 530e731..5490b15 100644 --- a/src/lib/editor/taskPlugin.ts +++ b/src/lib/editor/taskPlugin.ts @@ -80,6 +80,7 @@ export const taskPlugin = ViewPlugin.fromClass( const ranges = decos.map((d) => d.deco.range(d.from, d.to)) return Decoration.set(ranges, true) } catch (e) { + // eslint-disable-next-line no-console console.error('taskPlugin buildDeco error:', e) return Decoration.none } diff --git a/src/lib/editor/variablePlugin.ts b/src/lib/editor/variablePlugin.ts index f8be6f3..93aa27a 100644 --- a/src/lib/editor/variablePlugin.ts +++ b/src/lib/editor/variablePlugin.ts @@ -69,6 +69,7 @@ export const variablePlugin = ViewPlugin.fromClass( const ranges = decos.map((d) => d.deco.range(d.from, d.to)) return Decoration.set(ranges, true) } catch (e) { + // eslint-disable-next-line no-console console.error('variablePlugin error:', e) return Decoration.none } diff --git a/tests/safeStorage.test.ts b/src/lib/safeStorage.test.ts similarity index 94% rename from tests/safeStorage.test.ts rename to src/lib/safeStorage.test.ts index 0cb5ea7..ba53d60 100644 --- a/tests/safeStorage.test.ts +++ b/src/lib/safeStorage.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest' -import { setSecure, getSecure } from '../src/lib/safeStorage' +import { setSecure, getSecure } from './safeStorage' describe('safeStorage (Renderer Flow)', () => { beforeEach(() => { diff --git a/src/main.tsx b/src/main.tsx index 44306be..d004dcb 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -8,6 +8,7 @@ import { ErrorBoundary } from './components/ErrorBoundary.tsx' import { tauriApi } from './api' window.electronAPI = tauriApi +// eslint-disable-next-line react-refresh/only-export-components function Root() { const [hash, setHash] = useState(window.location.hash) const [migrated, setMigrated] = useState(false) @@ -29,6 +30,7 @@ function Root() { localStorage.removeItem('papercache-apikey') } } catch (e) { + // eslint-disable-next-line no-console console.error('Failed to migrate plain API key', e) } } @@ -48,6 +50,7 @@ function Root() { } } } catch (e) { + // eslint-disable-next-line no-console console.error('Failed to migrate secure API key', e) } }