Skip to content

Latest commit

 

History

History
469 lines (362 loc) · 13.1 KB

File metadata and controls

469 lines (362 loc) · 13.1 KB

Flashnotes Architectural Audit

Generated: 2026-01-06
Project Type: Tauri (Rust) + SvelteKit (TypeScript)
Audit Scope: Production readiness, security, performance, reliability


Executive Summary

Flashnotes demonstrates solid architectural foundations with effective use of Tauri + SvelteKit patterns. The codebase shows production-ready approaches in several areas (optimistic UI updates, WAL mode SQLite, transaction batching), but has several critical production blockers that must be addressed before release.

Overall Scores:

  • Observability: 3/10 ⚠️
  • Performance: 7/10 ✓
  • Security: 4/10 ⚠️
  • Reliability: 5/10 ⚠️
  • Architecture: 7/10 ✓

🚨 CRITICAL - Deploy Blockers

CRIT-01 - Database Connection Panics on Startup Failure

Location: src-tauri/src/lib.rs:27-30, src-tauri/src/db/connection.rs:10-13
Severity: Critical
Category: Reliability

Impact: Application crashes on startup if:

  • App data directory cannot be created (permissions issue)
  • Database file is corrupted
  • File system is read-only
  • Disk is full

User loses all data access with no recovery path.

Evidence:

// src-tauri/src/lib.rs:27
let conn = db::connection::create_connection(&db_path)
    .expect("Failed to create database connection"); // ⚠️ PANIC

db::schema::initialize_schema(&conn)
    .expect("Failed to initialize database schema"); // ⚠️ PANIC

Recommendation:

let conn = db::connection::create_connection(&db_path)
    .map_err(|e| {
        eprintln!("Database initialization failed: {}", e);
        // Show user-friendly error dialog via Tauri
        let _ = tauri::api::dialog::message(
            Some(&app.get_window("main").unwrap()),
            "Database Error",
            format!("Failed to initialize database: {}\n\nPlease check file permissions.", e)
        );
        e
    })?;

Effort: Low
Priority: Immediate (blocking release)


CRIT-02 - No Error Boundary for Frontend Crashes

Location: src/routes/+page.svelte, src/lib/stores/buffers.svelte.ts
Severity: Critical
Category: Reliability

Impact: Unhandled promise rejections or runtime errors crash the UI with no recovery. User loses work in progress.

Evidence:

  • No global error boundary in SvelteKit layout
  • Async operations in stores catch errors but only log to console
  • No user notification on critical failures (save failures go silent after toast)

Current behavior:

// src/lib/stores/buffers.svelte.ts:56
catch (error) {
  this.handleError('Failed to load sidebar data', error); // Only logs
}

Recommendation:

  1. Add SvelteKit error boundary in +error.svelte
  2. Implement retry logic for critical operations (save, load)
  3. Surface errors to user via modal, not just toast

Effort: Medium
Priority: Immediate


CRIT-03 - SQL Injection Vulnerability in Search

Location: src-tauri/src/db/queries.rs:88-122
Severity: Critical
Category: Security

Impact: While Tauri IPC provides some isolation, maliciously crafted search queries could:

  • Extract metadata from other tables via FTS5 MATCH injection
  • Cause database corruption via malformed FTS queries
  • Trigger denial of service via expensive regex patterns

Evidence:

// src-tauri/src/db/queries.rs:93-99
let safe_query = query
    .replace('"', "\"\"")  // Only escapes quotes
    .split_whitespace()
    .map(|term| format!("\"{}\"*", term))  // ⚠️ Still injectable
    .collect::<Vec<_>>()
    .join(" ");

Attack vector:

Input: `OR 1=1 --`
Becomes: `"OR"* "1=1"* "--"*`

Recommendation: Use parameterized queries exclusively. FTS5 queries should be validated against allowed characters:

// Whitelist approach
fn sanitize_fts_query(query: &str) -> Option<String> {
    let allowed = query.chars()
        .all(|c| c.is_alphanumeric() || c.is_whitespace() || c == '-');
    
    if !allowed {
        return None; // Reject suspicious input
    }
    // Continue with escaping...
}

Effort: Low
Priority: Immediate


⚠️ HIGH PRIORITY - This Week

HIGH-01 - No Structured Logging or Error Tracking

Location: Entire codebase
Severity: High
Category: Observability

Impact: Production debugging is impossible:

  • No logs written to file (only stdout via println!)
  • No error aggregation or crash reporting
  • Console.error() in frontend goes nowhere in production
  • Cannot diagnose user-reported issues

Recommendation:

  1. Rust: Integrate tracing + tracing-appender for file-based logs
  2. Frontend: Capture errors and send to Rust backend for logging
  3. Crash reporting: Integrate Sentry or similar (optional but recommended)
// Add to Cargo.toml
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-appender = "0.2"

// In lib.rs setup
let file_appender = tracing_appender::rolling::daily(app_data_dir, "flashnotes.log");
let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
tracing_subscriber::fmt()
    .with_writer(non_blocking)
    .init();

Effort: Medium
Priority: Short-term


HIGH-02 - Database Corruption Risk with Concurrent Writes

Location: src-tauri/src/state.rs, src-tauri/src/commands/buffer.rs
Severity: High
Category: Reliability

Impact: Single Mutex-protected SQLite connection could deadlock or corrupt if:

  • Long-running read holds lock during autosave
  • Reorder operation (transaction) blocks other writes
  • WAL checkpoint happens during write

Evidence:

// All commands use single connection via state.db.lock()
let conn = state.db.lock(); // Blocks all other operations

SQLite with WAL mode is multi-reader safe, but single-writer. Current architecture is correct but lacks:

  • Connection pool for reads
  • Write queue with backpressure
  • Busy timeout handling

Recommendation:

  1. Use rusqlite connection pool for read operations
  2. Dedicated writer connection with retry logic
  3. Monitor busy timeout hits via logging (HIGH-01)

Effort: High
Priority: Short-term


HIGH-03 - No Database Backup or Recovery Mechanism

Location: N/A (missing feature)
Severity: High
Category: Reliability

Impact: User data loss scenarios:

  • Database corruption (file system error, crash mid-write)
  • Accidental deletion of all buffers
  • Upgrade migration failure
  • No rollback capability

Recommendation:

  1. Automatic backups: Daily SQLite backup to .db.backup via VACUUM INTO
  2. Export functionality: Allow user-initiated JSON export of all buffers
  3. Backup on upgrade: Before running schema migrations
  4. Backup retention: Keep last 7 daily backups
pub fn create_backup(conn: &Connection, backup_path: &Path) -> Result<()> {
    conn.execute(&format!("VACUUM INTO '{}'", backup_path.display()), [])?;
    Ok(())
}

Effort: Medium
Priority: Short-term


HIGH-04 - Settings Parsing Silently Fails to Defaults

Location: src-tauri/src/db/queries.rs:300-302
Severity: High
Category: Reliability

Impact: Corrupted settings database causes silent data loss:

  • User sets font_size to 18, database stores "18px" (typo) → silently reverts to 13
  • User loses preferences with no indication

Evidence:

"font_size" => settings.font_size = value.parse().unwrap_or(13), // ⚠️ Silent failure

Recommendation:

"font_size" => {
    settings.font_size = value.parse()
        .inspect_err(|e| warn!("Invalid font_size '{}': {}", value, e))
        .unwrap_or(13);
}

Better: Return Result<AppSettings, SettingsError> and handle in command layer.

Effort: Low
Priority: Short-term


📋 MEDIUM PRIORITY - This Month

MED-01 - Inefficient Title/Preview Extraction on Every Sidebar Load

Location: src-tauri/src/db/queries.rs:56-85
Severity: Medium
Category: Performance

Impact: With 1000+ buffers, sidebar load processes full content for each buffer:

  • Extracts title/preview from entire content field (potentially large markdown)
  • O(n) string processing per buffer
  • Not cached

Recommendation: Store title and preview as denormalized columns:

ALTER TABLE buffers ADD COLUMN title TEXT GENERATED ALWAYS AS (
    substr(content, 1, instr(content, '\n'))
) VIRTUAL;

Or update on save via trigger.

Effort: Medium
Priority: Long-term (optimize when buffer count grows)


MED-02 - No Rate Limiting on Autosave

Location: src/routes/+page.svelte:45, src/lib/stores/buffers.svelte.ts:123-144
Severity: Medium
Category: Performance

Impact: Rapid typing triggers save every 500ms via debounce:

  • Unnecessary disk I/O
  • Could overwhelm database on slow storage
  • No batch queueing for rapid edits

Current:

const debouncedSave = debounce(() => bufferStore.saveCurrentBuffer(), 500);

Recommendation: Increase debounce to 2000ms (2s) for better UX and performance. Add "saving..." indicator.

Effort: Low
Priority: Long-term


MED-03 - No Telemetry for Performance Monitoring

Location: N/A (missing)
Severity: Medium
Category: Observability

Impact: Cannot measure:

  • Average save latency
  • Search query performance (FTS5 slow queries)
  • Database size growth over time
  • Memory usage patterns

Recommendation: Add opt-in telemetry:

use std::time::Instant;

let start = Instant::now();
queries::save_buffer(&conn, id, content, timestamp)?;
let duration = start.elapsed();
if duration.as_millis() > 100 {
    warn!("Slow save operation: {:?}", duration);
}

Effort: Low
Priority: Long-term


MED-04 - Frontend Memory Leak Risk with Large Buffers

Location: src/lib/stores/buffers.svelte.ts:30-34
Severity: Medium
Category: Performance

Impact: Svelte 5 $state keeps all buffers in memory:

  • sidebarBuffers array grows unbounded
  • Each buffer includes full preview (100 chars)
  • With 10,000 buffers, could consume significant memory

Recommendation:

  1. Virtual scrolling for sidebar (already using @tanstack/svelte-virtual ✓)
  2. Lazy-load buffer list (paginated)
  3. Aggressive garbage collection on buffer delete

Effort: Medium
Priority: Long-term


MED-05 - No Input Validation on Buffer Content Size

Location: src-tauri/src/commands/buffer.rs:19-39, src-tauri/src/commands/buffer.rs:43-51
Severity: Medium
Category: Security

Impact: User could create multi-GB buffer content:

  • Database bloat
  • OOM on load
  • Slow search queries

Recommendation:

const MAX_BUFFER_SIZE: usize = 10 * 1024 * 1024; // 10 MB

if content.len() > MAX_BUFFER_SIZE {
    return Err(format!("Buffer too large: {} bytes (max: {})", content.len(), MAX_BUFFER_SIZE));
}

Effort: Low
Priority: Long-term


MED-06 - Missing Database Migration Strategy

Location: src-tauri/src/db/schema.rs:23-26
Severity: Medium
Category: Reliability

Impact: Schema changes use .ok() to ignore errors:

conn.execute("ALTER TABLE buffers ADD COLUMN sort_order ...", []).ok();

This works for additive changes but fails for:

  • Renaming columns
  • Changing types
  • Complex migrations

Recommendation: Use migration framework:

// migrations/001_add_sort_order.sql
ALTER TABLE buffers ADD COLUMN sort_order INTEGER DEFAULT 0;

// Track applied migrations
CREATE TABLE IF NOT EXISTS schema_migrations (version INTEGER PRIMARY KEY);

Effort: Medium
Priority: Long-term


✅ Strengths (Keep Doing)

  1. Optimistic UI Updates: bufferStore.updateContent() updates sidebar immediately before backend confirms
  2. SQLite Performance: WAL mode + proper indexes + transaction batching (reorder_buffers)
  3. Clean Architecture: Rust commands layer cleanly separated from DB queries
  4. Svelte 5 Best Practices: Proper use of $state, $derived, $effect
  5. Debounced Autosave: Prevents excessive writes

Recommendations Summary

Priority Count Focus Area
Immediate 3 Error handling, security
Short-term 4 Logging, backup, reliability
Long-term 6 Performance optimization, telemetry

Next Steps:

  1. Fix CRIT-01 (database panics) - 2 hours
  2. Fix CRIT-02 (error boundary) - 4 hours
  3. Fix CRIT-03 (search injection) - 2 hours
  4. Implement HIGH-01 (logging) - 1 day
  5. Implement HIGH-03 (backups) - 1 day

Total effort to production-ready: ~3-4 days


Cross-Validation Methodology

This audit was conducted via:

  1. Static analysis: Full codebase review via repomix snapshot
  2. Pattern matching: Grep for known anti-patterns (panic, unwrap, console.error)
  3. Architecture review: Database schema, state management, IPC boundaries
  4. Security review: Input validation, SQL injection vectors, error disclosure

Confidence Levels:

  • Critical findings: Verified via code inspection
  • High findings: Inferred from patterns + architecture
  • Medium findings: Performance projections based on scale assumptions

Generated by: Claude Code Architectural Audit
Model: Claude Sonnet 4.5
Date: 2026-01-06