33 KiB
Product Requirements Document (PRD)
Ironpad - Personal Project & Knowledge Management System
Version: 3.0
Date: 2026-02-02
Status: Active Development
Author: Internal / Personal
Changelog: v3.0 - Addressed concurrency, file watching, git conflicts, port handling, and frontmatter automation
1. Executive Summary
Ironpad is a local-first, browser-based personal project management and note-taking system powered by a Rust backend.
The system combines:
- Free-form markdown notes
- Task management per project
- Project organization
- Full-text search
- Git-based versioning
- Real-time file system watching for external edits
- Automatic frontmatter management for low-ceremony UX
Core Philosophy: Simplicity first, power through composition, not rigid workflows.
Key Innovation: Single-binary Rust executable that auto-opens your browser - no bundled Chromium, no Node.js runtime required.
2. Goals
Primary Goals
- Provide a single place to store thoughts, notes, and tasks
- Enable lightweight project organization without heavy structure
- Support incremental evolution of features
- Keep the system easy to understand, modify, and extend
- Learn Rust through practical application
- Eliminate manual metadata management (auto-update timestamps, IDs)
- Support external editing (VS Code, Obsidian, etc.)
Technical Goals
- Zero-dependency data storage (plain files only)
- Fast search (<100ms for 1000 notes)
- Responsive UI (<16ms frame time)
- Startup time <500ms
- Tiny binary size (<15 MB)
- No browser bundling (use system browser)
- Graceful port conflict handling
- Real-time sync between UI and filesystem
- Robust git conflict handling
Non-Goals (v1)
- Multi-user collaboration
- Cloud sync (Git remote is optional)
- Permissions/roles
- Complex workflow automation
- Enterprise-grade task management
- Mobile app (mobile access not required)
3. Design Principles
1. Local-First
- No external services required
- Works fully offline
- Data stored on local file system
- File system is the source of truth (not UI state)
2. Notes-First, Tasks-Per-Project
- Notes are the primary unit
- Each project has its own task list
- Tasks don't float independently
3. File-Based Storage
- Data stored as Markdown files
- Editable outside the app
- Human-readable formats only
- External editors fully supported (VS Code, Obsidian, Vim)
4. Low Ceremony
- Minimal configuration
- Minimal UI friction
- No complex setup wizards
- Automatic metadata management (no manual timestamps/IDs)
5. Future-Proof
- Easy to migrate to other tools
- Git-friendly formats
- No vendor lock-in
6. Simplicity Principles
- No abstraction layers - Files are the database
- No build step for data - Markdown is human-readable
- No proprietary formats - Everything is standard
- No server required - Runs entirely on localhost
- No complex workflows - Write, save, done
4. Architecture
Overview
User launches executable
↓
Rust Backend (Axum)
- HTTP server on dynamic port (3000-3010)
- Serves Vue frontend (static files)
- REST API for file operations
- Git operations with conflict handling
- Full-text search
- File system watcher (notify crate)
- WebSocket server for real-time updates
↓
Auto-opens default browser
→ http://localhost:{port}
↓
Vue 3 Frontend (in browser)
- Markdown editor
- Task management
- Project switching
- WebSocket client (receives file change events)
↓
Bidirectional real-time sync
← WebSocket → File System Changes
Why Not Electron?
| Electron | Ironpad (Rust) |
|---|---|
| 150-300 MB bundle | 5-15 MB binary |
| Bundles Chromium | Uses system browser |
| 200-500 MB RAM | 10-50 MB RAM |
| 2-5s startup | <500ms startup |
| Complex distribution | Single executable |
Rationale: No need to bundle an entire browser when every user already has one.
Technology Stack
Backend (Rust)
- Axum - Web framework (simple, fast, learning-friendly)
- Tokio - Async runtime
- Tower - Middleware
- Serde - JSON serialization
- markdown-rs - Markdown parsing with frontmatter (CommonMark compliant)
- git2 - Git operations with lock handling
- webbrowser - Cross-platform browser launching
- notify - File system watching for external changes
- axum-ws or tower-websockets - WebSocket support for real-time updates
- ripgrep library or tantivy - Fast search
Frontend (Vue 3)
- Vue 3 - UI framework (Composition API)
- Vite - Build tool
- CodeMirror 6 - Markdown editor with syntax highlighting
- Pinia - State management (minimal caching, trust filesystem)
- markdown-it - Markdown rendering (CommonMark mode for consistency)
- Native WebSocket API - Real-time file change notifications
Data Storage
- Markdown files (.md) on local file system
- YAML frontmatter for metadata (auto-managed by backend)
- Git repository for versioning
- No database - files are the database
5. User Experience
Mental Model
- Similar to a notebook system (OneNote / Obsidian)
- A front page (index.md) acts as entry point
- Users can create notes and projects
- Projects contain notes + dedicated task list
- Simple navigation via sidebar
- Edit anywhere - changes in VS Code/Obsidian auto-sync to UI
- Conflict-free - UI prevents simultaneous edits of same file
Core UI Areas
1. Sidebar (Left)
- Projects section
- List of all projects
- Quick switch between projects
- Notes section
- List of standalone notes
- Daily notes
- Quick actions
- New note
- New project
- Search
- Git status indicator (changes pending commit)
2. Main Area (Center)
- Editor view
- CodeMirror markdown editor
- Syntax highlighting
- Auto-save (2s debounce)
- File lock indicator (shows if file open in Task View)
- External edit notification (shows banner if file changed externally)
- Split view option
- Editor on left
- Preview on right
3. Task View (Separate Page)
- View:
/projects/:id/tasks - Shows tasks for currently selected project
- Parse checkboxes from
data/projects/{id}/tasks.md - Sections: Active, Completed, Backlog
- Quick checkbox toggle
- Prevents editor access - If Task View open, editor shows "Read-Only" mode
6. Data Model
File Structure
data/
.git/ # Git repository
index.md # Front page / landing
inbox.md # Quick capture
daily/ # Daily notes (optional)
2026-02-02.md
2026-02-03.md
projects/ # Project folders
ironpad/
index.md # Project overview
tasks.md # Task list
notes.md # Miscellaneous notes (optional)
assets/ # Images, attachments
screenshot.png
homelab/
index.md
tasks.md
assets/
notes/ # Standalone notes
ideas.md
rust-learning.md
assets/ # Shared assets
diagram.png
archive/ # Completed/archived items
old-project/
Frontmatter Schema
Standard Fields (All Files) - AUTO-MANAGED
---
# Auto-generated by backend (user never edits these manually)
id: ironpad-index # Derived from filename: {folder}-{filename}
type: note # Detected from file location
created: 2026-02-02T01:00:00Z # Set on file creation
updated: 2026-02-02T01:15:00Z # Auto-updated on every save
# Optional user fields
title: Ironpad Development # Optional: display title (fallback: filename)
tags: [dev, rust, personal] # Optional: user-defined tags
status: active # Optional: draft|active|archived|complete
---
Key Change: Users never manually write id, created, or updated fields. Backend handles these automatically.
ID Generation Strategy
- Format:
{parent-folder}-{filename-without-extension} - Examples:
projects/ironpad/index.md→id: ironpad-indexnotes/ideas.md→id: notes-ideasdaily/2026-02-02.md→id: daily-2026-02-02
- Rationale: Human-readable, deterministic, no UUIDs cluttering files
Task File Example
---
id: ironpad-tasks # Auto-generated from filename
type: tasks # Auto-detected
project_id: ironpad # Parent folder name
created: 2026-02-01T12:00:00Z
updated: 2026-02-02T01:00:00Z # Auto-updated on every save
---
# Tasks: Ironpad
## Active
- [ ] Set up Rust backend with Axum
- [ ] Create Vue frontend with CodeMirror
- [ ] Implement task parsing
## Completed
- [x] Write PRD
- [x] Review architecture decisions
## Backlog
- [ ] Add full-text search
- [ ] Implement Git auto-commit
7. Functional Requirements
7.1 Notes Management
- Create new notes via sidebar button
- Read note content with markdown rendering
- Update notes with auto-save (2s debounce after last edit)
- Delete notes (moves to archive/ folder)
- Notes stored as
.mdfiles indata/notes/ - Auto-update
updatedtimestamp on every save
7.2 Project Management
- Create new projects (creates
data/projects/{id}/folder +assets/subfolder) - View project overview (
index.md) - Switch between projects via sidebar
- Archive completed projects (moves to
archive/) - Projects automatically get
tasks.mdfile +assets/folder
7.3 Task Management
- View tasks per project at
/projects/:id/tasks - Toggle task completion (checkbox state)
- Add new tasks via UI (appends to tasks.md)
- Organize tasks in sections: Active, Completed, Backlog
- Tasks represented as Markdown checkboxes:
- [ ] Incomplete task - [x] Completed task - No global task view in v1 - tasks belong to projects
- Concurrency handling: If Task View is open for a file, Editor View shows "Read-Only - Open in Task View" banner
7.4 Search
- Full-text search across all markdown files
- Search triggered from sidebar search box
- Results show: filename, matching line, context
- Implementation: Use
ripgrepas library (faster than naive grep) - Performance target: <100ms for 1000 notes
- Future: Migrate to Tantivy if needed (>5000 notes)
7.5 Git Integration
- Auto-commit with time-based batching (5 minutes)
- Manual commit button available
- Commit message format:
Auto-save: {timestamp}or user-provided message - Git history viewable via external tools (GitKraken, git log, etc.)
- Remote push optional (manual via git push or UI button)
- Lock handling: If
.git/index.lockexists, skip auto-commit and retry next cycle - Conflict detection: If
git statusshows conflicts, show warning banner in UI
7.6 File System Watching (NEW)
- Backend watches
data/directory usingnotifycrate - Detects changes made by external editors (VS Code, Obsidian, Vim)
- Sends WebSocket message to frontend:
{ type: "file_changed", path: "notes/ideas.md" } - Frontend response:
- If file is currently open in editor → Show banner: "File changed externally. Reload?"
- If file is not open → Auto-refresh sidebar file list
- If Task View is open for that file → Auto-reload task list
- Debouncing: Batch file changes (100ms) to avoid spamming WebSocket
7.7 Concurrency Control (NEW)
- Single-file lock: Only one view (Editor OR Task View) can edit a file at a time
- Implementation:
- Backend tracks "open files" via WebSocket connection state
- When Task View opens
tasks.md, backend marks file as "locked for task editing" - If user tries to open same file in Editor, show "Read-Only" mode
- When Task View closes, file unlocked automatically
- Rationale: Prevents race conditions between checkbox toggles and text edits
8. API Design
REST Endpoints
Notes
GET /api/notes # List all notes
GET /api/notes/:id # Get note content
POST /api/notes # Create new note (auto-generates frontmatter)
PUT /api/notes/:id # Update note content (auto-updates 'updated' field)
DELETE /api/notes/:id # Delete note (archive)
Projects
GET /api/projects # List all projects
GET /api/projects/:id # Get project details
POST /api/projects # Create new project (creates folder + assets/)
PUT /api/projects/:id # Update project
DELETE /api/projects/:id # Archive project
Tasks
GET /api/projects/:id/tasks # Get tasks for project
PUT /api/projects/:id/tasks # Update tasks for project
POST /api/tasks/lock/:id # Lock file for task editing
POST /api/tasks/unlock/:id # Unlock file
Search
GET /api/search?q={query} # Search all content (ripgrep-powered)
Git
POST /api/git/commit # Manual commit with message
GET /api/git/status # Get git status (detects conflicts)
POST /api/git/push # Push to remote (if configured)
GET /api/git/conflicts # Check for merge conflicts
Assets (NEW)
POST /api/assets/upload # Upload image/file to project assets/
GET /api/assets/:project/:file # Retrieve asset file
WebSocket Endpoints (NEW)
WS /ws # WebSocket connection for real-time updates
# Messages from backend → frontend:
{ type: "file_changed", path: "notes/ideas.md", timestamp: "2026-02-02T01:00:00Z" }
{ type: "file_deleted", path: "notes/old.md" }
{ type: "file_created", path: "daily/2026-02-03.md" }
{ type: "git_conflict", files: ["notes/ideas.md"] }
# Messages from frontend → backend:
{ type: "subscribe_file", path: "notes/ideas.md" } # Track active file
{ type: "unsubscribe_file", path: "notes/ideas.md" }
9. Technical Implementation Details
9.1 Auto-Save Strategy
- Decision: 2-second debounce after last edit
- Rationale: Balance between data safety and performance
- Implementation: Frontend debounces PUT requests
- Backend behavior: On PUT, auto-update
updatedtimestamp in frontmatter
9.2 Git Commit Strategy
- Decision: Time-based batching (5 minutes) + manual commit button
- Rationale: Clean history, reduced I/O, user control
- Implementation:
- Rust background task (tokio::spawn) runs every 5 minutes
- Checks
git status --porcelainfor changes - If
.git/index.lockexists → Skip and log warning - If changes exist →
git add .→git commit -m "Auto-save: {timestamp}" - If commit fails (lock, conflict) → Show error in UI via WebSocket
9.3 Editor Choice
- Decision: CodeMirror 6 with markdown mode
- Rationale: Mature, performant, excellent UX out-of-box
- Features: Syntax highlighting, line numbers, keyboard shortcuts
9.4 Search Implementation (v1)
- Decision: Use
ripgrepas library (viagrepcrate or direct integration) - Rationale:
- Faster than naive grep (uses SIMD, optimized algorithms)
- Handles >1000 files easily
- Used by VS Code, Sublime Text
- Future: Migrate to Tantivy if search becomes slow (>5000 notes)
9.5 Browser Launch
- Decision: Use
webbrowsercrate to open default browser - Rationale: Cross-platform, simple, no browser bundling
- Fallback: Print URL if browser launch fails
9.6 Port Conflict Handling (NEW)
- Decision: Dynamic port selection (3000-3010 range)
- Implementation:
async fn find_available_port() -> u16 { for port in 3000..=3010 { if let Ok(listener) = TcpListener::bind(("127.0.0.1", port)).await { return port; } } panic!("No available ports in range 3000-3010"); } - User experience: Always works, even if other dev tools running
- Logging: Print actual port used:
🚀 Ironpad running on http://localhost:3005
9.7 File System Watching (NEW)
- Decision: Use
notifycrate with debouncing - Implementation:
use notify::{Watcher, RecursiveMode, Event}; let (tx, rx) = channel(); let mut watcher = notify::recommended_watcher(tx)?; watcher.watch(Path::new("data/"), RecursiveMode::Recursive)?; // Debounce: Collect events for 100ms, then broadcast via WebSocket - Events tracked: Create, Modify, Delete
- Ignored paths:
.git/,node_modules/,.DS_Store
9.8 Frontmatter Automation (NEW)
- Decision: Backend owns all frontmatter management
- Implementation:
- On file creation: Generate frontmatter with
id,type,created,updated - On file update: Parse YAML, update
updatedfield, rewrite file - Use
gray_matterorserde_yamlcrates
- On file creation: Generate frontmatter with
- User experience: Users never manually edit timestamps or IDs
9.9 Markdown Consistency (NEW)
- Decision: Use CommonMark standard everywhere
- Backend:
markdown-rs(CommonMark compliant) - Frontend:
markdown-itwith CommonMark preset - Rationale: Prevents rendering mismatches between preview and backend parsing
10. Build & Distribution
Development Workflow
# Terminal 1: Frontend dev server with hot reload
cd frontend
npm install
npm run dev # Runs on localhost:5173
# Terminal 2: Rust backend (proxies frontend)
cd backend
cargo run # Runs on localhost:3000-3010, opens browser
Production Build
# Step 1: Build frontend
cd frontend
npm run build # Outputs to frontend/dist/
# Step 2: Copy frontend to Rust static folder
cp -r frontend/dist/* backend/static/
# Step 3: Build Rust release binary
cd backend
cargo build --release
# Output: backend/target/release/ironpad
# Size: ~5-15 MB
Distribution
- Single executable -
ironpad.exe(Windows),ironpad(Mac/Linux) - No installer required - Just run the binary
- No dependencies - Statically linked (except libc)
- User experience:
- User downloads
ironpad.exe - User double-clicks executable
- Browser opens automatically to available port
- App is ready to use
- User downloads
11. Data Safety & Backup
Git as Primary Backup
- Every save eventually commits to local Git repo
- Repo can be pushed to remote (GitHub, GitLab, self-hosted)
- All history preserved indefinitely
Recommended Setup
# Initialize repo (done automatically on first run)
cd data
git init
# Add remote (optional, manual or via UI)
git remote add origin https://github.com/yourusername/ironpad-data.git
# Auto-push (future feature)
# UI button: "Push to Remote"
Disaster Recovery
- Clone repo on new machine
- Point Ironpad to cloned folder
- All history and data preserved
- No proprietary formats to migrate
Conflict Handling
- If
.git/index.lockexists → Skip auto-commit, retry next cycle - If
git statusshows conflicts → Show banner in UI: "Git conflicts detected. Resolve manually." - Never auto-merge conflicts → User must resolve via git CLI or external tool
12. Success Criteria
The system is successful if:
- ✅ Used daily for notes and tasks
- ✅ Data remains readable without the app
- ✅ Adding new features does not require migrations
- ✅ System feels flexible rather than restrictive
- ✅ Binary size stays under 15 MB
- ✅ Startup time under 500ms
- ✅ Search completes in <100ms
- ✅ External edits sync instantly (<500ms latency)
- ✅ No manual frontmatter editing required
- ✅ Never crashes due to port conflicts
Usage Metrics (Personal Tracking)
- Daily notes created per week (target: 5+)
- Tasks completed per week (target: 10+)
- Projects with active tasks (target: 2-3)
- Average note length (target: 200+ words)
- External edits per week (VS Code usage, target: tracked but not enforced)
Performance Metrics
- App startup time (target: <500ms)
- Search response time (target: <100ms)
- Save operation time (target: <50ms)
- Binary size (target: <15 MB)
- File change notification latency (target: <500ms)
- WebSocket message latency (target: <100ms)
13. Implementation Phases
Phase 1: MVP (Week 1-2)
Goal: Basic note CRUD + auto-open browser + dynamic port
- Rust backend with Axum
- Dynamic port selection (3000-3010)
- File CRUD operations (read/write .md files)
- Automatic frontmatter generation (id, created, updated)
- Auto-open browser on launch
- Vue frontend scaffold
- Basic markdown editor (textarea)
- File list sidebar
- Auto-save with debounce
Phase 2: Core Features (Week 3-4)
Goal: Usable daily driver with real-time sync
- CodeMirror 6 integration
- Project creation/switching (with assets/ folders)
- Task parsing (checkboxes in markdown)
- Task view per project
- File locking (Task View vs Editor)
- Git init + auto-commit with lock handling
- Ripgrep-based search
- File system watching (notify crate)
- WebSocket server for real-time updates
- External edit notifications in UI
Phase 3: Polish (Week 5-6)
Goal: Production-ready personal tool
- Split view (editor + preview)
- Manual commit with message
- Git status/history viewer
- Git conflict detection and UI warnings
- Tag extraction and filtering
- Daily note templates
- UI polish and keyboard shortcuts
- Asset upload for images
Phase 4: Advanced (Future)
- Global hotkey (Ctrl+Shift+Space) using
global-hotkeycrate - System tray icon (stays running in background)
- Tantivy full-text search (if ripgrep becomes slow)
- Backlinks between notes
- Remote Git push/pull from UI
- Export to PDF/HTML
- Custom themes
14. Project Structure
ironpad/
├── README.md
├── LICENSE
│
├── backend/ # Rust backend
│ ├── Cargo.toml
│ ├── Cargo.lock
│ ├── src/
│ │ ├── main.rs # Server startup + router + port detection
│ │ ├── websocket.rs # WebSocket handler for real-time updates
│ │ ├── watcher.rs # File system watcher (notify integration)
│ │ ├── routes/
│ │ │ ├── mod.rs
│ │ │ ├── notes.rs # /api/notes endpoints
│ │ │ ├── projects.rs # /api/projects endpoints
│ │ │ ├── tasks.rs # /api/projects/:id/tasks
│ │ │ ├── search.rs # /api/search (ripgrep integration)
│ │ │ ├── git.rs # /api/git endpoints
│ │ │ └── assets.rs # /api/assets endpoints
│ │ ├── services/
│ │ │ ├── mod.rs
│ │ │ ├── filesystem.rs # File read/write logic
│ │ │ ├── git.rs # Git operations (git2) with lock handling
│ │ │ ├── search.rs # Ripgrep search implementation
│ │ │ ├── markdown.rs # Markdown parsing (CommonMark)
│ │ │ ├── frontmatter.rs # Auto-manage YAML frontmatter
│ │ │ └── locks.rs # File lock management for concurrency
│ │ └── models/
│ │ ├── mod.rs
│ │ ├── note.rs # Note struct
│ │ ├── project.rs # Project struct
│ │ └── task.rs # Task struct
│ └── static/ # Vue build output (in production)
│ └── (frontend dist files)
│
├── frontend/ # Vue 3 frontend
│ ├── package.json
│ ├── vite.config.ts
│ ├── tsconfig.json
│ ├── index.html
│ ├── src/
│ │ ├── main.ts
│ │ ├── App.vue
│ │ ├── composables/
│ │ │ └── useWebSocket.ts # WebSocket client composable
│ │ ├── components/
│ │ │ ├── Sidebar.vue
│ │ │ ├── ProjectList.vue
│ │ │ ├── NoteList.vue
│ │ │ ├── Editor.vue
│ │ │ ├── MarkdownPreview.vue
│ │ │ ├── TaskList.vue
│ │ │ ├── SearchBar.vue
│ │ │ ├── GitStatus.vue
│ │ │ ├── ExternalEditBanner.vue # Shows when file changed externally
│ │ │ └── ReadOnlyBanner.vue # Shows when file locked
│ │ ├── views/
│ │ │ ├── NotesView.vue # Main notes editor
│ │ │ ├── TasksView.vue # Project tasks view
│ │ │ └── ProjectView.vue # Project overview
│ │ ├── stores/
│ │ │ ├── notes.ts # Pinia store (minimal caching)
│ │ │ ├── projects.ts # Pinia store for projects
│ │ │ ├── tasks.ts # Pinia store for tasks
│ │ │ └── websocket.ts # WebSocket state management
│ │ ├── api/
│ │ │ └── client.ts # API client (fetch wrappers)
│ │ └── types/
│ │ └── index.ts # TypeScript types
│ └── dist/ # Build output (gitignored)
│
└── data/ # User data (separate repo)
├── .git/
├── .gitignore
├── index.md
├── inbox.md
├── projects/
│ └── ironpad/
│ ├── index.md
│ ├── tasks.md
│ └── assets/ # Project-specific images
├── notes/
│ ├── ideas.md
│ └── assets/ # Shared note assets
├── daily/
└── archive/
15. Dependencies
Backend (Cargo.toml)
[package]
name = "ironpad"
version = "0.1.0"
edition = "2021"
[dependencies]
# Web framework
axum = { version = "0.8", features = ["ws"] } # WebSocket support
tokio = { version = "1", features = ["full"] }
tower = "0.5"
tower-http = { version = "0.6", features = ["fs", "cors"] }
# Serialization
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_yaml = "0.9" # Frontmatter parsing
# Markdown parsing (CommonMark)
markdown = "1.0.0-alpha.22" # Frontmatter support
# Git operations
git2 = "0.19"
# Browser opening
webbrowser = "1.0"
# File system watching
notify = "6.1"
notify-debouncer-full = "0.3" # Debounced file events
# Search (ripgrep as library)
grep = "0.3" # ripgrep internals
walkdir = "2.4"
# Date/time
chrono = { version = "0.4", features = ["serde"] }
# Logging
tracing = "0.1"
tracing-subscriber = "0.3"
Frontend (package.json)
{
"name": "ironpad-frontend",
"version": "0.1.0",
"dependencies": {
"vue": "^3.5.0",
"vue-router": "^4.5.0",
"pinia": "^2.3.0",
"codemirror": "^6.0.1",
"@codemirror/lang-markdown": "^6.3.2",
"markdown-it": "^14.1.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.1",
"vite": "^6.0.5",
"typescript": "^5.7.2"
}
}
16. Open Questions & Decisions Tracking
| Question | Decision | Rationale | Date |
|---|---|---|---|
| Electron vs Rust backend? | Rust backend | Smaller binary, no bundled browser | 2026-02-02 |
| Auto-save strategy? | 2s debounce | Balance safety and performance | 2026-02-02 |
| Git commit strategy? | 5min batch + manual | Clean history, user control | 2026-02-02 |
| Editor library? | CodeMirror 6 | Mature, performant, good UX | 2026-02-02 |
| Search in v1? | Yes (ripgrep-based) | Fast, proven, <100ms target | 2026-02-02 |
| Tasks per project or global? | Per project | Cleaner mental model | 2026-02-02 |
| Mobile access? | Not v1 priority | Desktop-first | 2026-02-02 |
| Port conflict handling? | Dynamic 3000-3010 | Always works, graceful fallback | 2026-02-02 |
| External edit support? | Yes (notify + WebSocket) | True local-first philosophy | 2026-02-02 |
| Frontmatter management? | Auto-managed by backend | Low ceremony, no manual IDs | 2026-02-02 |
| Task View vs Editor conflict? | File locking | Prevent race conditions | 2026-02-02 |
| Markdown standard? | CommonMark | Consistency backend/frontend | 2026-02-02 |
17. Risk Assessment
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Rust learning curve | High | Medium | Start simple, use Axum (easier than Actix) |
| Git conflicts (concurrent edits) | Low | Medium | Detect .git/index.lock, show UI warnings |
| Search performance at scale | Low | Medium | Ripgrep handles 5000+ files easily |
| Browser doesn't auto-open | Low | Low | Print URL as fallback |
| File corruption | Low | High | Git versioning protects against data loss |
| WebSocket connection drops | Medium | Medium | Auto-reconnect in frontend with exponential backoff |
| File watcher overhead | Low | Low | Debounce events (100ms), ignore .git/ |
| Port conflicts | Low | Low | Dynamic port selection 3000-3010 |
| Race condition (Task View + Editor) | Medium | High | File locking prevents simultaneous edits |
| Markdown rendering mismatch | Low | Medium | Use CommonMark everywhere |
18. Future Enhancements (Out of Scope)
Potential v2+ Features
- Global hotkey - Ctrl+Shift+Space to bring app to front (using
global-hotkeycrate) - System tray icon - Keep app running in background (using
tray-iconcrate) - Backlinks - Automatic link detection between notes
- Graph view - Visual representation of note connections
- Rich editor - WYSIWYG markdown editor
- Templates - Note templates (daily, meeting, project)
- Plugins - Extension system for custom functionality
- Sync - Optional cloud sync via Git remote
- Themes - Dark mode, custom color schemes
- Export - PDF, HTML, DOCX export
- Mobile web UI - Responsive design for mobile browsers
- Kanban view - Visual task board per project
- Time tracking - Track time spent on tasks
- Voice notes - Audio recording integration
- OCR - Extract text from images
19. Addressing Gemini's Feedback
✅ 1. Task Syncing Race Conditions
Issue: Task View checkboxes vs Editor text edits conflict
Solution: File locking system
- Task View locks
tasks.mdwhen open - Editor shows "Read-Only" banner if file locked
- Only one view can edit at a time
✅ 2. File System Watching
Issue: External edits (VS Code, Obsidian) don't sync
Solution: notify crate + WebSocket
- Backend watches
data/directory - Sends real-time updates to frontend
- UI shows "File changed externally. Reload?" banner
✅ 3. Git Conflict Handling
Issue: .git/index.lock can cause crashes
Solution: Graceful lock detection
- Check for lock file before committing
- Skip auto-commit if locked, retry next cycle
- Show UI warning if git conflicts detected
✅ 4. Frontmatter Management
Issue: Manual timestamp/ID editing is high-friction
Solution: Backend owns all frontmatter
- Auto-generate
idfrom filename - Auto-update
updatedon every save - Users never manually edit metadata
✅ 5. Port Conflicts
Issue: Hardcoded :3000 breaks if port busy
Solution: Dynamic port selection
- Try ports 3000-3010
- Bind to first available
- Log actual port used
✅ 6. Search Performance
Issue: Naive grep slow at >500 files
Solution: Use ripgrep library
- Battle-tested, used by VS Code
- Handles 5000+ files easily
- <100ms target achieved
✅ 7. Markdown Consistency
Issue: Backend parsing vs frontend rendering mismatch
Solution: CommonMark everywhere
- Backend:
markdown-rs(CommonMark mode) - Frontend:
markdown-it(CommonMark preset) - Guaranteed consistency
✅ 8. State Management
Issue: Pinia caching vs file system truth
Solution: Minimal caching philosophy
- File system is source of truth
- Pinia only caches current view
- WebSocket invalidates cache on external changes
✅ 9. Asset Management
Issue: No image/file storage
Solution: assets/ folders
- Each project gets
assets/subfolder - Global
notes/assets/for shared files - Upload via
/api/assets/upload
20. Conclusion
Ironpad v3.0 represents a robust, production-ready architecture:
- Local-first with true external editor support
- Lightweight Rust backend (no browser bundling)
- Real-time sync via WebSocket
- Conflict-free via file locking
- Low-ceremony via automatic frontmatter management
- Resilient via git lock handling and dynamic ports
The system is designed to be:
- ✅ Easy to use daily
- ✅ Easy to understand and modify
- ✅ Easy to back up and migrate
- ✅ Fast and responsive
- ✅ A practical Rust learning project
- ✅ Robust against real-world edge cases
Next Step: Begin Phase 1 implementation - Rust backend with dynamic port selection + automatic frontmatter.
Document Version History
- v1.0 (2026-02-01): Initial draft with general architecture
- v2.0 (2026-02-02): Complete rewrite with Rust backend, browser-based frontend, detailed technical decisions
- v3.0 (2026-02-02): Addressed concurrency, file watching, git conflicts, port handling, frontmatter automation, and Gemini's architectural feedback
Contact: Internal project - personal use