Files

1004 lines
33 KiB
Markdown

# 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
```yaml
---
# 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-index`
- `notes/ideas.md``id: notes-ideas`
- `daily/2026-02-02.md``id: daily-2026-02-02`
- **Rationale**: Human-readable, deterministic, no UUIDs cluttering files
#### Task File Example
```yaml
---
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 `.md` files in `data/notes/`
- **Auto-update** `updated` timestamp 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.md` file + `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:
```markdown
- [ ] 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 `ripgrep` as 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.lock` exists, skip auto-commit and retry next cycle
- **Conflict detection**: If `git status` shows conflicts, show warning banner in UI
### 7.6 File System Watching (NEW)
- **Backend watches** `data/` directory using `notify` crate
- **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 `updated` timestamp 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 --porcelain` for changes
- If `.git/index.lock` exists → 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 `ripgrep` as library (via `grep` crate 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 `webbrowser` crate 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**:
```rust
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 `notify` crate with debouncing
- **Implementation**:
```rust
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 `updated` field, rewrite file
- Use `gray_matter` or `serde_yaml` crates
- **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-it` with CommonMark preset
- **Rationale**: Prevents rendering mismatches between preview and backend parsing
---
## 10. Build & Distribution
### Development Workflow
```bash
# 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
```bash
# 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**:
1. User downloads `ironpad.exe`
2. User double-clicks executable
3. Browser opens automatically to available port
4. App is ready to use
---
## 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
```bash
# 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.lock` exists → Skip auto-commit, retry next cycle
- If `git status` shows 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-hotkey` crate
- [ ] 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)
```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)
```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-hotkey` crate)
- **System tray icon** - Keep app running in background (using `tray-icon` crate)
- **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.md` when 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 `id` from filename
- Auto-update `updated` on 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