|
1 | | -# Performance Audit: PaperCache V0.5.0-beta (Tauri Migration) |
| 1 | +# Performance Audit: PaperCache V0.5.8 |
2 | 2 |
|
3 | | -**Date:** June 22, 2026 |
| 3 | +**Date:** June 29, 2026 |
4 | 4 | **Auditor:** VariableThe |
5 | | -**App Version:** 0.5.0-beta (Tauri Migration) |
| 5 | +**App Version:** 0.5.8 (Tauri, custom arithmetic evaluator) |
6 | 6 |
|
7 | | -## 1. Executive Summary (The TL;DR) |
8 | | -This document details the performance improvements made in V0.5.0-beta by migrating from Electron to Tauri and Rust. The primary goals were drastically reducing the application size and background RAM usage by eliminating the embedded Node.js runtime and Chromium binaries. |
| 7 | +## 1. Executive Summary |
9 | 8 |
|
10 | | -| Metric | V0.4.0 (Electron) | V0.5.0-beta (Tauri) | Delta | |
| 9 | +PaperCache is built on Tauri v2, Rust, and React. After migrating from Electron (v0.5.0-beta), we've continued shrinking the footprint through dependency removal, custom evaluators, and lazy-loading. |
| 10 | + |
| 11 | +| Metric | V0.5.0-beta | V0.5.8 | Delta | |
11 | 12 | | :--- | :--- | :--- | :--- | |
12 | | -| **App Installer Size (DMG)** | ~80.0 MB | ~7.3 MB | **-90%** | |
13 | | -| **Idle RAM Usage** | ~120 MB | ~40 MB | **-66%** | |
| 13 | +| **App Installer Size (DMG)** | ~7.3 MB | ~6.8 MB | **-7%** | |
| 14 | +| **Idle RAM Usage** | ~40 MB | ~38 MB | **-5%** | |
14 | 15 | | **Idle CPU Usage** | 0.0% | 0.0% | **Maintained** | |
15 | | -| **IPC Save Latency (500 notes)**| 12 ms (Async) | <5 ms (Rust fs) | **Faster** | |
| 16 | +| **Bundle Dependencies** | ~600 packages | ~590 packages | **Smaller** | |
16 | 17 |
|
17 | 18 | ## 2. Testing Methodology & Environment |
18 | | -*To ensure reproducibility, all metrics were captured under the following conditions:* |
19 | | -- **Hardware:** MacBook Air M4, 16GB RAM (Baseline mid-tier dev machine). |
| 19 | +- **Hardware:** MacBook Air M4, 16GB RAM |
20 | 20 | - **OS:** macOS 15.7.5 |
21 | 21 | - **Dataset:** Workspace containing 500 markdown notes, averaging 2KB each. |
22 | | -- **Tooling:** Activity Monitor, `ls -lh`, and Rust `std::time::Instant`. |
| 22 | +- **Tooling:** Activity Monitor, `ls -lh`, `du -sh` |
| 23 | + |
| 24 | +## 3. Recent Performance Improvements (V0.5.3 — V0.5.8) |
| 25 | + |
| 26 | +### Custom Arithmetic Evaluator (V0.5.8) |
| 27 | +Replaced `expr-eval` (~15KB, high-severity prototype pollution vulnerability, no fix available) with a custom recursive-descent parser at `src/lib/evaluator.ts` (~2KB). |
| 28 | +- Zero external dependencies — no `eval`, no `Function` constructors. |
| 29 | +- ~150 lines of TypeScript with 26 unit tests. |
| 30 | +- Not susceptible to prototype pollution. |
| 31 | +- *Impact:* Eliminated a security vulnerability with a smaller, faster evaluator. |
| 32 | + |
| 33 | +### Unused Dependency Removal (V0.5.6 — V0.5.8) |
| 34 | +Removed 4 unused npm packages: `@tauri-apps/plugin-fs`, `@tauri-apps/plugin-shell`, `@emnapi/core`, `@emnapi/runtime`. |
| 35 | +- *Impact:* Reduced package.json weight and CI install times. |
23 | 36 |
|
24 | | -## 3. The Tauri Migration (V0.5.0-beta) |
25 | | -*Goal: Remove the massive Electron overhead for a background utility.* |
| 37 | +### Lazy-Loaded WebGL Graph View (V0.5.3) |
| 38 | +Replaced D3.js Canvas 2D graph with `react-force-graph-3d` (Three.js/WebGL), dynamically imported via `React.lazy() + Suspense`. |
| 39 | +- The Three.js bundle (~1.3 MB) loads **only** when the graph is first opened. |
| 40 | +- *Impact:* Graph rendering is offloaded to the GPU. No startup penalty. No UI freeze on large graphs. |
26 | 41 |
|
27 | | -- **Zero-Copy IPC via `serde`:** Electron relies on JSON stringification over a Node.js bridge. Tauri uses Rust's `serde` library, which serializes and deserializes IPC payloads with near-zero overhead, making data transfer between the UI and backend virtually instantaneous. |
28 | | -- **Native Async Runtime:** The Rust backend utilizes the `tokio` multi-threaded async runtime. Heavy operations like recursive directory walking (`get_notes`) and HTTP requests (`reqwest` for OpenAI) are executed off the main thread, ensuring the UI never stutters during disk I/O. |
29 | | -- **Native Security:** Replaced Electron's `safeStorage` with a custom Rust implementation using the `keyring` crate (for OS-level credential storage) and `aes-gcm` (for AES-256-GCM encryption). This provides hardware-backed security with a fraction of the memory footprint. |
30 | | -- **Strict Capability Scoping:** Migrated to Tauri v2's capability system, ensuring the frontend can only invoke explicitly whitelisted Rust commands and access strictly scoped file paths, eliminating entire classes of XSS-to-filesystem vulnerabilities present in Electron. |
| 42 | +### DSL Regex Engine — Visible-Range Scanning (V0.5.3) |
| 43 | +Created `dslPlugin.ts` — `createRegexPlugin()` scans only `view.visibleRanges` per update tick. |
| 44 | +- O(visible lines) complexity instead of O(document length). |
| 45 | +- *Impact:* Lag-free typing at any document size, even with many active regex rules. Eliminated the previous main-thread bottleneck for custom DSL parsing. |
31 | 46 |
|
32 | | ---- |
| 47 | +### Drift-Corrected Timer Countdowns (V0.5.3) |
| 48 | +Replaced `setInterval` with chained `setTimeout` loops in the timer panel. |
| 49 | +- *Impact:* No timer drift accumulation. Accurate countdowns regardless of browser event loop pressure. |
33 | 50 |
|
34 | | -## Historical: V0.4.0 Performance Audit |
| 51 | +### Debounced Saves (V0.4.0, maintained) |
| 52 | +500ms debounce on `saveNote` IPC calls. |
| 53 | +- Disk I/O reduced from ~3 writes/sec to ~1 write/sec during continuous typing. |
35 | 54 |
|
36 | | -## 3. Bundle & Ship Size Optimization |
37 | | -*Goal: Reduce the amount of JavaScript V8 must parse on cold start.* |
| 55 | +### TypeScript Strict Mode (V0.5.8) |
| 56 | +Enabled `strict: true` in tsconfig, catching null/type issues at compile time rather than runtime. |
| 57 | +- *Impact:* Zero new type errors at enablement — the codebase was already compatible. |
38 | 58 |
|
39 | | -- **Removed `mathjs` (16MB):** Replaced with `expr-eval` (160KB). |
40 | | - - *Impact:* Reduced initial JS parse time by ~400ms. |
41 | | -- **Removed `openai` Node SDK (16MB):** Replaced with native `fetch()` (20 lines of code). |
42 | | - - *Impact:* Eliminated 16MB of dead weight. The AI feature now lazy-loads only when triggered, but the base bundle is permanently smaller. |
43 | | -- **Vite Code Splitting:** Verified that heavy CodeMirror language parsers are dynamically imported only when a specific code block is rendered. |
| 59 | +### Coverage Thresholds (V0.5.8) |
| 60 | +Added minimum coverage guardrails to vitest config (statements 65%, branches 50%, functions 55%, lines 65%). |
| 61 | +- *Impact:* Prevents silent coverage regression in CI. |
44 | 62 |
|
45 | | -## 4. Main Process & IPC Architecture |
46 | | -*Goal: Prevent the Electron main thread from blocking on disk I/O, which causes global hotkey lag and tray menu freezes.* |
| 63 | +## 4. Desktop Integration Overhead |
47 | 64 |
|
48 | | -- **Async File I/O:** Migrated 8 synchronous `fs.*Sync` calls in IPC handlers (`get-notes`, `save-note`, etc.) to `fs.promises`. |
49 | | - - *Before:* Loading 500 notes blocked the main thread for 450ms. The UI was completely unresponsive. |
50 | | - - *After:* Loading 500 notes uses `Promise.all()` and takes 12ms of main-thread time. |
51 | | -- **Startup Bootstrap:** Left 2 synchronous `existsSync`/`mkdirSync` calls in the pre-app-ready bootstrap phase for creating `.papercache` and `commands` directories. |
52 | | - - *Justification:* These run before the `BrowserWindow` is created. Making them async adds complexity for zero user-perceptible benefit. |
| 65 | +- **Native API Key Storage:** Uses the OS keychain (macOS Keychain, Linux Secret Service, Windows Credential Manager) via the Rust `keyring` crate. No user-visible memory or latency cost. |
| 66 | +- **Global Shortcuts:** Registered via `tauri-plugin-global-shortcut` — zero ongoing CPU overhead, fires events only on keypress. |
| 67 | +- **Auto-Updates:** Uses `tauri-plugin-updater` — checks version metadata on startup (~1 HTTP HEAD request). No persistent overhead. |
| 68 | +- **Window State Persistence:** Reads/writes a small JSON file (~200 bytes) on exit/startup. No runtime cost. |
53 | 69 |
|
54 | | -## 5. Renderer & React State |
55 | | -*Goal: Eliminate UI stuttering during rapid typing and state updates.* |
| 70 | +## 5. Known Limitations & Future Bottlenecks |
56 | 71 |
|
57 | | -- **Debounced Saves:** Implemented a 500ms debounce on `window.electronAPI.saveNote`. |
58 | | - - *Impact:* Disk I/O reduced from ~3 writes/sec to 1 write/sec during continuous typing. |
59 | | -- **Pure State Updaters:** Refactored `App.tsx` to remove IPC side-effects from `setNotes` updaters. |
60 | | - - *Impact:* Eliminated React "Cannot update a component while rendering a different component" warnings and prevented double-saving race conditions. |
61 | | -- **Window Consolidation:** Removed the secondary `BrowserWindow` for Settings. |
62 | | - - *Impact:* Saved ~40MB of baseline RAM (no second Chromium renderer process) and eliminated the fragile `localStorage` event listener sync. |
| 72 | +1. **Full-Text Search:** Currently searches are done client-side via JavaScript string matching on loaded notes. For workspaces exceeding 5,000+ notes, this could introduce noticeable search latency. A future optimization could implement a persistent search index in Rust (e.g., via `tantivy` or `skim`). |
63 | 73 |
|
64 | | -## 6. Known Limitations & Future Bottlenecks |
65 | | -*Intellectual honesty: Where the app is still not perfectly optimized, and why.* |
| 74 | +2. **Graph Layout on Very Large Datasets:** The WebGL graph renders smoothly for typical workspaces (up to ~500 nodes). With 2,000+ nodes, force simulation convergence time and interaction framerate may degrade. Future optimization: implement LOD (level-of-detail) rendering or cluster-collapse for large folders. |
66 | 75 |
|
67 | | -1. **Graph View Rendering:** The D3.js graph view currently recalculates the entire force-directed layout on every node addition. With 1,000+ notes, this causes a 2-second UI freeze. |
68 | | - - *Mitigation:* We accept this for V0.4.0 as graph view is a secondary feature. V0.5.0 will implement WebGL (via `react-force-graph`) to offload layout calculations to the GPU. |
69 | | -2. **Regex Parsing on Large Files:** The custom DSL regex runs on the entire document string on every keystroke. For files >50KB, this causes minor input latency in the JS main thread. |
70 | | - - *Mitigation:* In V0.5.0, this parsing can be ported to a `#[tauri::command]` in Rust. Rust's regex engine is highly performant and completely bypasses the JS main thread, eliminating input latency without needing Web Workers. |
| 76 | +3. **Startup Time (Cold):** Initial load requires parsing note directory via Rust's `walkdir` (~5ms for 500 notes) plus React hydration. The JS bundle is ~200KB gzipped. Cold launch is typically sub-second but varies by filesystem speed and note count. |
0 commit comments