Files
IronPad-Docker/docs/ARCHITECTURE.md
skepsismusic 781ea28097 Release v0.2.0: Task comments, recurring calendar, system tray, app branding
New features:
- Task comments with date-stamped entries and last-comment summary
- Recurring tasks expanded on calendar (daily/weekly/monthly/yearly)
- System tray mode replacing CMD window (Windows/macOS/Linux)
- Ironpad logo as exe icon, tray icon, favicon, and header logo

Technical changes:
- Backend restructured for dual-mode: dev (API-only) / prod (tray + server)
- tray-item crate for cross-platform tray, winresource for icon embedding
- Calendar view refactored with CalendarEntry interface for recurring merging
- Added CHANGELOG.md, build-local.ps1, version bumped to 0.2.0

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-16 13:48:54 +01:00

14 KiB

Ironpad Architecture

This document describes the technical architecture of Ironpad.

System Overview

┌─────────────────────────────────────────────────────────────────┐
│                         Browser                                  │
│  ┌─────────────────────────────────────────────────────────────┐│
│  │                    Vue 3 SPA                                ││
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌─────────────┐ ││
│  │  │  Views   │  │Components│  │  Stores  │  │ Composables │ ││
│  │  └────┬─────┘  └────┬─────┘  └────┬─────┘  └──────┬──────┘ ││
│  │       │             │             │               │         ││
│  │       └─────────────┴──────┬──────┴───────────────┘         ││
│  │                            │                                ││
│  │                    ┌───────▼───────┐                        ││
│  │                    │  API Client   │                        ││
│  └────────────────────┴───────┬───────┴────────────────────────┘│
└───────────────────────────────┼─────────────────────────────────┘
                                │
                    HTTP REST   │   WebSocket
                                │
┌───────────────────────────────┼─────────────────────────────────┐
│                               │                                  │
│  ┌────────────────────────────▼────────────────────────────────┐│
│  │                    Axum Router                               ││
│  │  ┌─────────────────────────────────────────────────────────┐││
│  │  │                    Routes                                │││
│  │  │  /api/notes  /api/projects  /api/tasks  /api/git  /ws   │││
│  │  └───────────────────────────┬─────────────────────────────┘││
│  └──────────────────────────────┼───────────────────────────────┘│
│                                 │                                │
│  ┌──────────────────────────────▼───────────────────────────────┐│
│  │                      Services                                 ││
│  │  ┌──────────┐  ┌───────────┐  ┌──────┐  ┌───────┐  ┌──────┐ ││
│  │  │Filesystem│  │Frontmatter│  │ Git  │  │Search │  │Locks │ ││
│  │  └────┬─────┘  └─────┬─────┘  └──┬───┘  └───┬───┘  └──┬───┘ ││
│  └───────┼──────────────┼───────────┼──────────┼─────────┼──────┘│
│          │              │           │          │         │       │
│          └──────────────┴─────┬─────┴──────────┴─────────┘       │
│                               │                                  │
│                        ┌──────▼──────┐                           │
│                        │  File System │                          │
│                        │    (data/)   │                          │
│                        └─────────────┘                           │
│                                                                  │
│                      Rust Backend                                │
└──────────────────────────────────────────────────────────────────┘

Core Principles

1. Files Are the Database

All data is stored as Markdown files with YAML frontmatter:

---
id: note-20260205-123456
title: My Note
created: 2026-02-05T12:34:56Z
updated: 2026-02-05T12:34:56Z
---

# My Note

Content goes here...

Benefits:

  • Portable — files can be copied, backed up, synced
  • Editable — any text editor works
  • Versionable — Git tracks all changes
  • Debuggable — human-readable format

2. Backend Owns Metadata

The backend automatically manages:

  • id — Generated from timestamp (YYYYMMDD-HHMMSS)
  • created — Set once when file is created
  • updated — Updated on every save

Clients send content; backend handles metadata consistency.

3. Local-First

The application works fully offline:

  • No cloud dependencies
  • No external API calls
  • Git push is optional

Backend Architecture

Technology Stack

  • Rust — Memory safety, performance
  • Axum 0.8 — Async web framework
  • Tokio — Async runtime
  • serde/serde_yaml — Serialization
  • notify — File system watching

Service Layer

services/
├── filesystem.rs   # File read/write operations
├── frontmatter.rs  # YAML parsing/generation
├── git.rs          # Git CLI wrapper
├── locks.rs        # File locking state
├── markdown.rs     # Markdown utilities
└── search.rs       # ripgrep integration

Filesystem Service

Handles all file operations with atomic writes:

// Atomic write pattern
fn write_note(path: &Path, content: &str) -> Result<()> {
    let temp = path.with_extension("tmp");
    fs::write(&temp, content)?;
    fs::rename(temp, path)?;  // Atomic on most filesystems
    Ok(())
}

Frontmatter Service

Parses and generates YAML frontmatter:

struct Frontmatter {
    id: String,
    title: Option<String>,
    created: DateTime<Utc>,
    updated: DateTime<Utc>,
    // ... other fields
}

Git Service

Wraps Git CLI commands:

impl GitService {
    fn status(&self) -> Result<GitStatus>;
    fn commit(&self, message: &str) -> Result<()>;
    fn push(&self) -> Result<()>;
    fn log(&self, limit: usize) -> Result<Vec<Commit>>;
    fn diff(&self, commit: Option<&str>) -> Result<String>;
}

Auto-commit runs every 60 seconds when changes exist.

WebSocket System

Real-time updates via WebSocket:

Client                  Server
   │                       │
   │──── connect ─────────▶│
   │◀─── accepted ─────────│
   │                       │
   │──── lock_file ───────▶│
   │◀─── file_locked ──────│
   │                       │
   │                       │ (file changed on disk)
   │◀─── file_modified ────│
   │                       │
   │──── unlock_file ─────▶│
   │◀─── file_unlocked ────│

Message Types:

  • lock_file / unlock_file — File locking for concurrent editing
  • file_modified — Broadcast when files change on disk
  • git_status — Git status updates

File Watcher

Uses notify crate to watch the data directory:

// Debounce: 500ms to batch rapid changes
// Filter: Ignores changes from own writes
watcher.watch(data_path, RecursiveMode::Recursive)?;

Frontend Architecture

Technology Stack

  • Vue 3 — Composition API
  • TypeScript — Type safety
  • Vite — Build tooling
  • Pinia — State management
  • Vue Router — Navigation
  • Milkdown — WYSIWYG editor

Component Hierarchy

App.vue
├── Sidebar.vue
│   ├── NoteList.vue
│   ├── ProjectList.vue
│   └── GitStatus.vue
├── TopBar.vue
├── SearchPanel.vue
├── GitPanel.vue
└── <router-view>
    ├── NotesView.vue
    ├── ProjectView.vue
    ├── ProjectNotesView.vue
    ├── TasksView.vue
    └── DailyView.vue

State Management (Pinia)

Each domain has a dedicated store:

// Example: notesStore
export const useNotesStore = defineStore('notes', () => {
  const notes = ref<Note[]>([])
  const currentNote = ref<NoteWithContent | null>(null)
  const saveStatus = ref<'idle' | 'saving' | 'saved' | 'error'>('idle')
  
  async function loadNote(id: string) { ... }
  async function saveNote(content: string) { ... }
  
  return { notes, currentNote, saveStatus, loadNote, saveNote }
})

Milkdown Editor Integration

The editor uses a two-component architecture:

MilkdownEditor.vue (wrapper)
└── MilkdownEditorCore.vue (actual editor)

Critical Lifecycle:

  1. MilkdownProvider provides Vue context
  2. useEditor hook creates Crepe instance
  3. Crepe.editor is the ProseMirror editor
  4. editor.action(replaceAll(content)) updates content

Key Pattern: Content must be set BEFORE the editor key changes:

// View component
watch(noteId, async (newId) => {
  const note = await api.getNote(newId)
  
  // CORRECT ORDER:
  editorContent.value = note.content  // 1. Set content
  editorKey.value = newId             // 2. Recreate editor
})

Auto-save System

Smart auto-save that prevents unnecessary saves:

// Track original content
const lastSavedContent = ref<string | null>(null)

// Only save when content differs
watch(editorContent, (newContent) => {
  if (lastSavedContent.value !== null && 
      newContent !== lastSavedContent.value) {
    scheduleAutoSave()  // 1-second debounce
  }
})

Data Model

Note

interface Note {
  id: string           // e.g., "20260205-123456"
  title?: string
  path: string         // e.g., "notes/20260205-123456.md"
  created: string      // ISO 8601
  updated: string
}

interface NoteWithContent extends Note {
  content: string      // Markdown body
}

Project

interface Project {
  id: string           // e.g., "ferrite" (slug)
  title: string
  description?: string
  path: string
  created: string
  updated: string
}

Task

interface Comment {
  date: string         // ISO 8601 timestamp
  text: string         // Comment body
}

interface Task {
  id: string           // e.g., "task-20260205-123456"
  title: string
  completed: boolean
  section?: string     // "Active" | "Backlog"
  priority?: string
  due_date?: string
  is_active: boolean
  tags: string[]
  parent_id?: string   // Links subtask to parent
  recurrence?: string  // "daily" | "weekly" | "monthly" | "yearly"
  recurrence_interval?: number
  last_comment?: string // Most recent comment text (list views)
  path: string
  created: string
  updated: string
}

interface TaskWithContent extends Task {
  content: string      // Markdown description
  comments: Comment[]  // Full comment history
}

Task File Format

---
id: ferrite-task-20260216-120000
type: task
title: Implement feature X
completed: false
section: Active
priority: normal
is_active: true
tags:
  - backend
  - api
comments:
  - date: "2026-02-16T10:30:00+00:00"
    text: Started initial research
  - date: "2026-02-16T14:00:00+00:00"
    text: API endpoint done, moving to frontend
created: "2026-02-16T12:00:00+00:00"
updated: "2026-02-16T14:00:00+00:00"
---

# Implement feature X

Detailed description in markdown...

Comments are stored as a YAML sequence directly in frontmatter, keeping everything in a single file. The last_comment field in list views is derived at read time from the last entry in the sequence.

API Design

REST Conventions

  • GET /api/resource — List all
  • POST /api/resource — Create new
  • GET /api/resource/:id — Get one
  • PUT /api/resource/:id — Update
  • DELETE /api/resource/:id — Delete (usually archives)

Error Handling

{
  "error": "Note not found",
  "code": "NOT_FOUND"
}

HTTP status codes:

  • 200 — Success
  • 201 — Created
  • 400 — Bad request
  • 404 — Not found
  • 500 — Server error

Security Considerations

Current State

Ironpad is designed for local, single-user operation:

  • No authentication (local access assumed)
  • No HTTPS (localhost only)
  • No input sanitization for XSS (trusted user)

Production Deployment

For multi-user or remote deployment:

  1. Add authentication (JWT, session-based)
  2. Enable HTTPS
  3. Sanitize markdown output
  4. Rate limit API endpoints
  5. Validate file paths to prevent directory traversal

Performance Considerations

Backend

  • Atomic writes — Prevent corruption on crash
  • File caching — Read once, cache in memory (not yet implemented)
  • Ripgrep search — Fast full-text search

Frontend

  • Virtual scrolling — For large note lists (not yet needed)
  • Debounced saves — 1-second delay batches rapid edits
  • Lazy loading — Routes loaded on demand

Future Considerations

Scalability

Current design handles ~5000 files comfortably. For larger datasets:

  • Add Tantivy full-text search index
  • Implement pagination for note lists
  • Add lazy loading for project trees

Features

See ai-context.md for planned features:

  • Tag extraction and filtering
  • Backlinks between notes
  • Graph view
  • Export (PDF/HTML)
  • Custom themes