Files

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.mdid: ironpad-index
    • notes/ideas.mdid: notes-ideas
    • daily/2026-02-02.mdid: 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 .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:
    - [ ] 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
  • 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
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:
    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:
    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

# 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:
    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
# 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)

[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-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