Flashnotes is a macOS-native scratchpad application built with Tauri (Rust backend) and SvelteKit (TypeScript frontend). It provides an infinite-buffer, database-backed note-taking experience with full-text search, vim mode support, and real-time markdown preview. The application follows a desktop app pattern rather than traditional CLI commands, with all interactions happening through the GUI and keyboard shortcuts.
| Command | Subcommand(s) | Description | Main Implementation File(s) |
|---|---|---|---|
| npm run dev | Development mode with Vite dev server | package.json |
|
| npm run build | Production build of SvelteKit frontend | package.json |
|
| npm run tauri dev | Development mode with hot-reload for both frontend and backend | src-tauri/ |
|
| npm run tauri build | Production build creating macOS app bundle | src-tauri/ |
|
| npm test:e2e | End-to-end test suite for buffer operations | tests/test-buffer-ops.sh |
|
| create_buffer | Tauri IPC: Create new note buffer | src-tauri/src/commands/buffer.rs |
|
| save_buffer | Tauri IPC: Save buffer content | src-tauri/src/commands/buffer.rs |
|
| get_buffer_content | Tauri IPC: Retrieve buffer content | src-tauri/src/commands/buffer.rs |
|
| get_sidebar_data | Tauri IPC: Get sidebar buffer list | src-tauri/src/commands/buffer.rs |
|
| search_buffers | Tauri IPC: Full-text search via FTS5 | src-tauri/src/commands/buffer.rs |
|
| delete_buffer | Tauri IPC: Delete current buffer | src-tauri/src/commands/buffer.rs |
|
| toggle_pin | Tauri IPC: Pin/unpin buffer | src-tauri/src/commands/buffer.rs |
|
| get_settings | Tauri IPC: Retrieve app settings | src-tauri/src/commands/settings.rs |
|
| set_setting | Tauri IPC: Update app setting | src-tauri/src/commands/settings.rs |
|
| reorder_buffers | Tauri IPC: Reorder buffers in sidebar | src-tauri/src/commands/buffer.rs |
|
| cleanup_empty_buffers | Tauri IPC: Delete all empty buffers | src-tauri/src/commands/buffer.rs |
|
| toggle_always_on_top | Tauri IPC: Toggle window stay-on-top | src-tauri/src/commands/settings.rs |
-
Process Entry
- Binary execution starts at
src-tauri/src/main.rs - Calls
lib.rs::run()which initializes Tauri application
- Binary execution starts at
-
Tauri Initialization (
src-tauri/src/lib.rs)- Loads configuration from
tauri.conf.json - Initializes plugins:
tauri-plugin-opener: For opening external URLstauri-plugin-window-state: Window persistencetauri-plugin-single-instance: Prevents multiple app windows
- Sets up
AppStatewith SQLite database connection - Registers all IPC command handlers
- Creates main window with overlay title bar
- Loads configuration from
-
Frontend Loading
- Development: Serves from Vite dev server at
http://127.0.0.1:1420 - Production: Loads from
../builddirectory (built SvelteKit app) - Establishes IPC connection between frontend and Rust backend
- Development: Serves from Vite dev server at
-
Database Initialization (
src-tauri/src/db/schema.rs)- Opens/creates SQLite database at:
- Development:
./flashnotes_dev.db - Production:
~/Library/Application Support/com.flashnotes.app/flashnotes.db
- Development:
- Enables WAL mode, foreign keys, and optimizing PRAGMAs
- Creates tables:
buffers,settings,buffers_fts - Sets up triggers for FTS5 auto-sync
- Opens/creates SQLite database at:
- Database Connection: Managed via
AppStatewithMutex<Connection>for thread safety - Error Handling: Unified
thiserror-based error types mapped to Tauri responses - Window Management: Single window instance with state persistence
- Menu System: Native macOS menu with command integration
Synopsis
npm run devDescription Starts Vite development server for the SvelteKit frontend at port 1420 with hot module replacement.
Entry Point and Files
- Primary file:
package.json(script definition) - Implementation: Vite dev server via SvelteKit
- Port: 1420 (configured in
tauri.conf.json)
Argument and Option Parsing No arguments. Uses SvelteKit's default Vite configuration.
Execution Flow (Step-by-Step)
-
Startup
- npm executes script from
package.json - Vite loads SvelteKit configuration
- Starts development server on port 1420
- npm executes script from
-
Development Features
- Hot Module Replacement for Svelte components
- TypeScript compilation and type checking
- Tailwind CSS processing with PostCSS
- Asset serving and optimization
-
Integration with Tauri
npm run tauri devcommand depends on this- Tauri loads frontend from
http://127.0.0.1:1420
External Interactions
- Filesystem: Serves static assets from project directory
- Network: Local HTTP server on port 1420
Error and Edge Case Flows
- Port conflict: Vite automatically selects next available port
- TypeScript errors: Displayed in terminal and browser overlay
- Failed builds: Clear error messages with file locations
Notes and Assumptions
- Must be running before
tauri devfor development mode - Uses SvelteKit's default development configuration
Synopsis
npm run tauri devDescription Launches the application in development mode with both frontend (Vite) and backend (Tauri) hot-reload enabled.
Entry Point and Files
- Primary files:
src-tauri/src/main.rs,src-tauri/src/lib.rs - Configuration:
src-tauri/tauri.conf.json - Frontend: Served from Vite dev server (npm run dev)
Argument and Option Parsing
No CLI arguments. Configuration comes from tauri.conf.json.
Execution Flow (Step-by-Step)
-
Pre-start
- Executes
beforeDevCommand:npm run dev - Waits for frontend server at
http://127.0.0.1:1420
- Executes
-
Rust Compilation
- Compiles Rust backend with debug symbols
- Links with system libraries (SQLite on macOS)
-
Tauri Runtime
- Initializes WebView with frontend URL
- Creates application window with overlay title bar
- Establishes IPC context for command communication
-
Development Features
- Auto-reloads on Rust code changes
- Hot-reloads on frontend changes via Vite
- Debug console available
- Database uses
flashnotes_dev.db
External Interactions
- Database: SQLite at
./flashnotes_dev.db - Frontend: HTTP connection to Vite dev server
- macOS: Creates application bundle in memory
Error and Edge Case Flows
- Frontend server down: Waits and retries connection
- Rust compilation errors: Displayed with full stack trace
- Database errors: Logged but doesn't crash app
Notes and Assumptions
- Requires
npm run devto be executed first - Uses development database (separate from production)
Synopsis
npm run tauri buildDescription Creates production build of the entire application, generating a macOS app bundle and DMG installer.
Entry Point and Files
- Primary files:
src-tauri/src/main.rs,src-tauri/Cargo.toml - Frontend: Built to
../builddirectory - Output:
src-tauri/target/release/bundle/macos/
Argument and Option Parsing
No arguments. Build configuration from tauri.conf.json and Cargo.toml.
Execution Flow (Step-by-Step)
-
Frontend Build
- Executes
beforeBuildCommand:npm run build - SvelteKit builds optimized static site to
../build - Assets minified and fingerprinted
- Executes
-
Rust Compilation
- Compiles in release mode with optimizations
- Bundles SQLite and all dependencies
- Links frontend assets into binary
-
App Bundle Creation
- Creates macOS app structure
- Copies icons and metadata
- Sets up Info.plist with app ID
com.flashnotes.notes
-
DMG Generation
- Creates disk image installer
- Configures with custom background and layout
- Outputs to
bundle/macos/directory
External Interactions
- Filesystem: Writes build artifacts
- macOS tools: Uses
codesignfor app signing (if configured)
Error and Edge Case Flows
- Frontend build failures: Stops before Rust compilation
- Missing dependencies: Clear cargo error messages
- Code signing failures: Warning but build continues
Notes and Assumptions
- Creates standalone app bundle
- Production database location:
~/Library/Application Support/com.flashnotes.app/
Synopsis
await invoke('create_buffer', { content?: string })Description Creates a new note buffer with optional initial content. Assigns unique ID, title, and sort order.
Entry Point and Files
- Primary file:
src-tauri/src/commands/buffer.rs - Function:
create_buffer(state: State<AppState>, content: Option<String>)
Argument and Option Parsing
| Argument/Option | Type | Required? | Default | Description | Affects Flow? (Y/N) |
|---|---|---|---|---|---|
| content | string | no | undefined | Initial buffer content | N |
Execution Flow (Step-by-Step)
-
Parameter Processing
- Extract optional content from command parameters
- Generate new UUID for buffer ID
-
Database Transaction
- Lock database connection via
state.db.lock() - Determine new sort_order (current max + 1)
- INSERT into buffers table with:
- Generated UUID
- Content (if provided)
- Current timestamps
- archived=false, pinned=false
- Lock database connection via
-
Response Generation
- Generate title from first line or "Untitled"
- Create preview from first 100 characters
- Return BufferSummary with all metadata
-
Frontend Integration
- BufferStore receives response
- Updates active buffer immediately
- Refreshes sidebar list
External Interactions
- Database: INSERT into buffers table
- FTS5: Trigger auto-updates search index
Error and Edge Case Flows
- Database lock timeout: Returns database error
- Invalid UUID: Extremely unlikely, but returns generic error
- Empty content: Creates empty buffer with "Untitled" title
Notes and Assumptions
- Always creates new buffer at top of sort order
- Title extraction handles markdown headers
- Preview strips markdown formatting
Synopsis
await invoke('save_buffer', { id: string, content: string })Description Saves buffer content and updates metadata. Returns new title and preview for UI updates.
Entry Point and Files
- Primary file:
src-tauri/src/commands/buffer.rs - Function:
save_buffer(state: State<AppState>, id: String, content: String)
Argument and Option Parsing
| Argument/Option | Type | Required? | Default | Description | Affects Flow? (Y/N) |
|---|---|---|---|---|---|
| id | string | yes | - | Buffer UUID | Y |
| content | string | yes | - | Buffer content | N |
Execution Flow (Step-by-Step)
-
Validation
- Parse buffer ID from string
- Validate content is not null
-
Database Update
- Lock database connection
- UPDATE buffers SET:
- content = provided content
- updated_at = current timestamp
- WHERE id matches buffer ID
-
Response Generation
- Generate new title from first line
- Create preview from content
- Return SaveResponse with title and preview
-
Frontend Integration
- Debounced save (500ms) in BufferStore
- Updates buffer title in sidebar
- Marks buffer as clean (not dirty)
External Interactions
- Database: UPDATE on buffers table
- FTS5: Trigger auto-updates search index
Error and Edge Case Flows
- Buffer not found: Returns "Buffer not found" error
- Database lock: Retries or returns timeout error
- Invalid UUID: Returns parsing error
Notes and Assumptions
- Called from frontend debounced save mechanism
- Always updates updated_at timestamp
- Title extraction is consistent with create_buffer
Synopsis
await invoke('get_sidebar_data')Description Retrieves all non-archived buffers for sidebar display, ordered by pinned status and sort order.
Entry Point and Files
- Primary file:
src-tauri/src/commands/buffer.rs - Function:
get_sidebar_data(state: State<AppState>)
Argument and Option Parsing No arguments.
Execution Flow (Step-by-Step)
-
Database Query
- Lock database connection
- SELECT all non-archived buffers
- ORDER BY pinned DESC, sort_order ASC
-
Response Mapping
- Convert each row to BufferSummary
- Include title, preview, timestamps
- Preserve sort_order for reordering
-
Frontend Integration
- Populates sidebar buffer list
- Separates pinned and unpinned sections
- Updates buffer selection state
External Interactions
- Database: SELECT query on buffers table
Error and Edge Case Flows
- Database empty: Returns empty vector
- Connection error: Returns database error
- No non-archived buffers: Returns empty vector
Notes and Assumptions
- Always returns results in correct order
- Archives buffers are filtered out
- Used for initial load and refreshes
Synopsis
await invoke('search_buffers', { query: string })Description Performs full-text search using SQLite FTS5, returning highlighted snippets and matching buffers.
Entry Point and Files
- Primary file:
src-tauri/src/commands/buffer.rs - Function:
search_buffers(state: State<AppState>, query: String)
Argument and Option Parsing
| Argument/Option | Type | Required? | Default | Description | Affects Flow? (Y/N) |
|---|---|---|---|---|---|
| query | string | yes | - | Search query text | Y |
Execution Flow (Step-by-Step)
-
Query Preparation
- Trim whitespace from query
- Prepare FTS5 MATCH query with highlighting
-
Database Search
- Join buffers table with buffers_fts
- Search using FTS5 MATCH syntax
- Return highlighted snippets (bm25() relevance)
- ORDER BY relevance score
-
Response Generation
- Map results to SearchResult objects
- Include buffer metadata and highlighted content
- Return vector of search results
-
Frontend Integration
- Updates search results in BufferStore
- Highlights matching terms in preview
- Allows direct buffer selection from search
External Interactions
- Database: FTS5 virtual table query
- Text processing: SQLite snippet highlighting
Error and Edge Case Flows
- Empty query: Returns empty results
- No matches: Returns empty vector
- FTS5 errors: Falls back to LIKE query
Notes and Assumptions
- Uses FTS5 for fast full-text search
- Highlighting preserves context around matches
- Results ordered by relevance (bm25 score)
Synopsis
await invoke('delete_buffer', { id: string })Description Deletes a buffer and determines which buffer should become active next.
Entry Point and Files
- Primary file:
src-tauri/src/commands/buffer.rs - Function:
delete_buffer(state: State<AppState>, id: String)
Argument and Option Parsing
| Argument/Option | Type | Required? | Default | Description | Affects Flow? (Y/N) |
|---|---|---|---|---|---|
| id | string | yes | - | Buffer UUID to delete | Y |
Execution Flow (Step-by-Step)
-
Database Transaction
- Begin transaction
- Find buffer with next highest sort_order (previous in list)
- If no previous, find buffer with next lowest sort_order (next in list)
- DELETE buffer with specified ID
- Commit transaction
-
Response Generation
- Return ID of buffer that should become active
- Return null if no buffers remain
-
Frontend Integration
- BufferStore handles buffer deletion
- Switches to returned buffer ID or creates new one
- Updates sidebar list immediately
External Interactions
- Database: DELETE from buffers table
- FTS5: Trigger auto-updates search index
Error and Edge Case Flows
- Buffer not found: Returns null for next buffer
- Last buffer deleted: Returns null to trigger new buffer creation
- Database error: Transaction rolls back
Notes and Assumptions
- Smart buffer selection preserves context
- Single buffer deletion handled gracefully
- Triggers archive cleanup if needed
Synopsis
npm test:e2eDescription End-to-end test suite that validates buffer creation, reordering, deletion, and application stability.
Entry Point and Files
- Primary file:
tests/test-buffer-ops.sh - Test app:
/Users/vampire/www/flashnotes/src-tauri/target/release/bundle/macos/Flashnotes.app
Argument and Option Parsing No arguments. Uses hardcoded paths and values.
Execution Flow (Step-by-Step)
-
Setup
- Kill any existing Flashnotes processes
- Launch production app bundle
- Wait for process to initialize
-
Buffer Creation Tests
- Create 3 test buffers (AAA-First, BBB-Second, CCC-Third)
- Each buffer created with Cmd+N keyboard shortcut
- Content typed to identify buffers
-
Reorder Tests
- Attempt buffer reordering with Cmd+Shift+↓
- Attempt buffer reordering with Cmd+Shift+↑
- Verify sort_order values in database
- Note: AppleScript limitations in testing webview keyboard events
-
Deletion Tests
- Delete all 3 test buffers with Cmd+W
- Verify application remains stable
- Confirm window still exists
-
Cleanup
- Quit application
- Report test results
External Interactions
- macOS: AppleScript for keyboard simulation and process control
- Database: Direct SQLite queries to verify state
- Filesystem: App bundle execution
Error and Edge Case Flows
- App not found: Fails with clear error
- Process crash: Detected and reported
- Database inaccessible: Logged but doesn't fail test
- Window closed unexpectedly: Test failure
Exit Codes
- 0: All tests passed
- 1: App failed to launch or crashed
- 2: Window closed unexpectedly
- 3: Sort order validation failed
Notes and Assumptions
- Requires production build to exist
- Hardcoded paths for test environment
- Some tests may require manual verification due to AppleScript limitations
Components:
schema.rs- Database initialization and table creationqueries.rs- High-level query functionsconnection.rs- Connection management
Used by: All IPC commands Responsibilities:
- SQLite database with WAL mode for concurrency
- FTS5 virtual table for full-text search
- Automatic triggers for search index sync
- Connection pooling via AppState
Used by: All command handlers Responsibilities:
- Thread-safe database access via Mutex
- Dependency injection for Tauri commands
- Shared state across command invocations
Used by: Frontend Svelte components Responsibilities:
- Reactive state management with Svelte 5 runes
- Debounced auto-save (500ms)
- Optimistic UI updates
- Command invocation wrapper
Used by: SettingsModal, main app Responsibilities:
- Persistent app settings
- Font configuration
- Window state (always-on-top)
- Editor mode toggles
Used by: Application initialization Responsibilities:
- Native macOS menu bar
- Command shortcuts
- "Stay on Top" toggle integration
- GitHub link integration
- macOS-only Application: Uses macOS-specific APIs and private APIs
- Single Instance: Enforced by Tauri plugin, prevents multiple windows
- SQLite Bundle: Assumes SQLite is available on target system
- Development Environment: Assumes npm and Rust toolchain are installed
- Database Location: Assumes standard macOS app support directory exists
- No CLI Arguments: Application doesn't support command-line arguments for direct file opening
- AppleScript Testing: E2E tests limited by AppleScript's ability to send keyboard events to webviews
- Single Window: Architecture doesn't support multiple editor windows
- No Export Functionality: No built-in export to other formats
- No Plugin System: Extensibility limited to built-in features
- Crash Reporting: No automated crash reporting or analytics
- Auto-Update: No auto-update mechanism configured
- Backup Strategy: No automatic backup or sync functionality
- Performance Metrics: No built-in performance monitoring
- Accessibility: No explicit accessibility features documented
- Why SQLite: Chosen for portability and FTS5 support
- Why Tauri v2: Explicit upgrade from v1 for improved capabilities
- Why Svelte 5: Early adoption for runes-based reactivity
- Why CodeMirror 6: For extensibility and vim support
- No Cloud Sync: Intentionally local-only for privacy