Files
IronPad-Docker/ai-context.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

16 KiB

AI Context — Ironpad

Paste this into every new AI chat for project context.


What It Is

Ironpad is a local-first, file-based personal project & knowledge management system.

  • Backend: Rust (Axum 0.8, Tokio)
  • Frontend: Vue 3 (Vite)
  • Data: Plain Markdown files with YAML frontmatter
  • Versioning: Local Git repository
  • UI: System browser (no Electron)

Core Principles

  1. Files are the database — filesystem is source of truth
  2. Local-first — works fully offline
  3. External editing supported — VS Code, Obsidian, Vim all work
  4. Backend owns metadataid, created, updated are auto-managed
  5. Low ceremony — minimal config, no manual metadata editing

Current Architecture

ironpad/
├── backend/           # Rust Axum server
│   └── src/
│       ├── main.rs           # Server bootstrap, WebSocket, routes
│       ├── routes/           # API endpoints
│       ├── services/         # Business logic (filesystem, git, search)
│       ├── models/           # Data structures
│       ├── websocket.rs      # Real-time sync
│       └── watcher.rs        # File system watching
├── frontend/          # Vue 3 SPA
│   └── src/
│       ├── App.vue           # Root component with router-view
│       ├── main.ts           # Entry point (Pinia + Vue Router)
│       ├── router/           # Vue Router config
│       ├── stores/           # Pinia stores (notes, projects, tasks, ui, websocket, git)
│       ├── views/            # Route views (DashboardView, ProjectView, TasksView, CalendarView, DailyView)
│       ├── components/       # Reusable components (Sidebar, MarkdownEditor, etc.)
│       ├── composables/      # Vue composables (useWebSocket)
│       ├── api/              # API client (client.ts)
│       └── types/            # TypeScript types (index.ts)
└── data/              # User data (separate git repo)
    ├── notes/         # Standalone notes
    ├── projects/      # Project folders
    │   └── {project}/
    │       ├── index.md    # Project overview
    │       ├── notes/      # Project-specific notes
    │       └── tasks/      # Individual task files (task-YYYYMMDD-HHMMSS.md)
    ├── daily/         # Daily notes (YYYY-MM-DD.md)
    ├── archive/       # Archived items
    ├── index.md       # Landing page
    └── inbox.md       # Quick capture

Implemented Features

Backend

  • Dual-mode server: API-only in development; frontend-serving + system tray in production
  • Dynamic port (3000-3010)
  • Notes CRUD with atomic writes
  • Frontmatter auto-management
  • WebSocket server for real-time sync + file locking
  • File watcher (filters own saves)
  • Search (ripgrep with fallback)
  • Git status + auto-commit (60s batching) + push + conflict detection
  • Full Git panel with commit history, diff viewer, custom commit messages
  • Git remote info (ahead/behind tracking), fetch support
  • Projects API with notes management
  • File-based Tasks API — each task is a markdown file with frontmatter
    • Fields: id, title, completed, section, priority, due_date, is_active, tags, parent_id, recurrence, recurrence_interval, comments
    • Rich text descriptions with markdown support
    • Sorted by created date (stable ordering)
    • Subtasks — tasks with parent_id link to a parent task
    • Tags — YAML sequence in frontmatter, per-task labels for filtering
    • Recurring tasks — when completing a recurring task, auto-creates next instance with advanced due date
    • Comments — date-stamped comment entries stored as YAML sequence in frontmatter; last comment shown as summary in list/dashboard
  • Daily notes API (/api/daily, /api/daily/today, /api/daily/:date)
  • Assets API (upload + serve)

Frontend

  • Vue Router navigation
  • Pinia state management
  • Milkdown WYSIWYG editor — ProseMirror-based, renders markdown as you type
    • CommonMark + GFM support (tables, strikethrough, task lists)
    • Toolbar with formatting buttons (bold, italic, headings, links, images, code, lists, quotes)
    • Image upload — click image button, select file, auto-uploads and inserts markdown
    • History (undo/redo), clipboard, indentation plugins
  • Dark theme by default with toggle button (persists to localStorage)
  • Sidebar with Notes/Projects/Daily/Calendar sections + task counts
  • Search panel (Ctrl+K)
  • Dashboard view (home page) — all projects as cards with active task summaries
    • Click project to navigate, click task to open detail
    • Shows active/backlog/overdue counts per project
  • Split-panel Task view with:
    • Task list (Active/Backlog/Completed sections)
    • Task detail editor with markdown descriptions
    • Preview toggle for rendered markdown
    • Active/Backlog toggle button
    • Due date picker — inline date input to set/clear due dates
    • Due date display with color-coded urgency
    • Tag system — add/remove tags with autocomplete from project tags
    • Tag filter bar — click tags to filter task list
    • Subtasks — expandable subtasks under parent tasks, add subtask inline
    • Recurrence picker — set daily/weekly/monthly/yearly recurrence
    • Task comments — date-stamped comments section in task detail, newest first, add/delete
    • Last comment summary — most recent comment shown in task list and dashboard cards
    • Inline title editing (double-click)
  • Calendar view — month grid showing tasks by due date + recurring task expansion
    • Tasks with due dates plotted on calendar cells
    • Recurring tasks expanded — daily/weekly/monthly/yearly tasks shown on computed occurrences
    • Recurring occurrences use anchor date (due_date or created), respect recurrence_interval
    • Recurring entries shown with dashed border and ↻ icon to distinguish from regular tasks
    • Daily notes shown as blue dots
    • Color-coded urgency (overdue, today, soon)
    • Month navigation + Today button
    • Click task to navigate to detail, click date to open daily note
  • Split-panel Notes view (per project)
  • Git panel — slide-out panel with:
    • Commit history with expandable diffs
    • Working directory changes with line-by-line diff
    • Custom commit messages (Ctrl+Enter to commit)
    • Push/Fetch buttons with ahead/behind indicators
    • File status icons (added/modified/deleted/renamed)
  • Git status indicator in sidebar (click to open panel)
  • WebSocket real-time updates
  • File lock banners (read-only mode when locked)
  • Conflict warning banner
  • Fullscreen layout (uses all available space)

API Endpoints

GET/POST    /api/notes
GET/PUT/DEL /api/notes/:id
GET/POST    /api/projects
GET/PUT     /api/projects/:id
GET/PUT     /api/projects/:id/content

# Project Notes (file-based)
GET/POST    /api/projects/:id/notes
GET/PUT/DEL /api/projects/:id/notes/:note_id

# Project Tasks (file-based, each task is a .md file)
GET/POST    /api/projects/:id/tasks
GET/PUT/DEL /api/projects/:id/tasks/:task_id
PUT         /api/projects/:id/tasks/:task_id/toggle
PUT         /api/projects/:id/tasks/:task_id/meta
POST        /api/projects/:id/tasks/:task_id/comments
DELETE      /api/projects/:id/tasks/:task_id/comments/:index

GET         /api/tasks              # All tasks across projects
GET         /api/daily
GET         /api/daily/today
GET/POST    /api/daily/:date
POST        /api/assets/upload
GET         /api/assets/:project/:file
GET         /api/search?q=
GET         /api/git/status
POST        /api/git/commit
POST        /api/git/push
GET         /api/git/conflicts
GET         /api/git/log              # Commit history (limit param)
GET         /api/git/diff             # Working directory diff
GET         /api/git/diff/:commit_id  # Diff for specific commit
GET         /api/git/remote           # Remote repository info
POST        /api/git/fetch            # Fetch from remote
WS          /ws

Implemented in Phase 3

  • CodeMirror 6 editor with markdown syntax highlighting
  • Markdown preview with split view
  • Vue Router for navigation (/, /projects/:id, /projects/:id/tasks, /calendar, /daily)
  • Pinia state management (notes, projects, tasks, ui, websocket, git stores)
  • Project-specific task view with toggle and add functionality
  • File locking via WebSocket (Task View vs Editor)
  • Daily notes (data/daily/) with templates
  • Assets upload API (/api/assets/upload, /api/assets/:project/:file)
  • Git push and conflict detection (/api/git/push, /api/git/conflicts)

Implemented in Phase 4

  • File-based tasks — each task is a separate markdown file with YAML frontmatter
    • Supports rich text descriptions with images
    • New fields: due_date, is_active
    • Task files stored in projects/{id}/tasks/task-YYYYMMDD-HHMMSS.md
  • Split-panel task view — list on left, detail editor on right
  • Markdown preview toggle — side-by-side raw/rendered view
  • Active/Backlog toggle — button to move tasks between states
  • Project notes — separate notes folder per project with split-panel view
  • Stable list sorting — sorted by created date (not updated)
  • Backend API-only mode — no frontend serving, no browser auto-open
  • Fullscreen layout — uses all available browser space

Known Issues / Technical Debt

  1. Project index note ID format: Project notes use {slug}-index as their ID (e.g., ferrite-index). Projects created before this fix have incorrect IDs in frontmatter.

  2. Axum nested route limitation: Path parameters from parent routes are NOT automatically available in nested route handlers. Project task routes use explicit routes instead of .nest().

  3. Some warnings remain: Unused methods in locks.rs and git.rs (reserved for future use).


Implemented in Phase 5

  • Dashboard view — cross-project home page with task summaries per project
  • Tags system — per-task tags stored in frontmatter, filter bar in task list, autocomplete
  • Subtasks — tasks with parent_id, grouped under parents in list, inline creation
  • Recurring tasks — daily/weekly/monthly/yearly with auto-creation on completion
  • Calendar view — month grid with tasks by due date + daily note indicators
  • Due date picker — inline date input in task detail panel
  • Clickable app title — "Ironpad" navigates to dashboard

Implemented in Phase 6

  • Recurring tasks on calendar — frontend expands daily/weekly/monthly/yearly tasks into the visible month grid
    • Anchor date: due_date if set, otherwise created; respects recurrence_interval
    • Deduplicates against regular due-date entries; visual indicator (dashed border, ↻ icon)
  • System tray mode — production binary runs in system tray (Windows, macOS, Linux)
    • #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] hides console on Windows
    • Server on background thread, tray event loop on main thread (cross-platform safe)
    • Tray menu: "Open in Browser" / "Quit"
    • Uses tray-item crate with platform-specific icon loading (windows-sys on Windows)
    • Development mode unchanged (no tray, API-only)

Not Yet Implemented (Phase 7+)

  • UI polish and animations
  • Responsive sidebar
  • Global hotkey (Ctrl+Shift+Space)
  • Backlinks between notes
  • Graph view
  • Export (PDF / HTML)
  • Custom themes
  • Tantivy search (if >5000 notes)
  • Production packaging (Tauri or similar)
  • Task dependencies (blocked by)
  • Time estimates on tasks
  • Calendar drag-and-drop rescheduling
  • Week/day calendar views

Key Technical Decisions

Decision Choice
Data path ../data relative to backend
Port Dynamic 3000-3010
Auto-save 1s debounce in frontend
Git commits 60s batch + manual button
File watcher notify crate, 500ms debounce
Search ripgrep CLI, fallback to manual
Frontmatter serde_yaml, auto-generated IDs
Editor Milkdown (WYSIWYG ProseMirror-based)
Editor Legacy CodeMirror 6 (MarkdownEditor.vue, kept for reference)
State management Pinia stores
Routing Vue Router (history mode)
File locking WebSocket-based, per-client locks
Project note ID {slug}-index format
Task storage Individual .md files in tasks/ folder
List sorting By created date (stable, not affected by edits)
Backend mode API-only (dev); frontend-serving + system tray (production)
Theme Dark by default, toggle to light, persists to localStorage
Tags YAML sequence in frontmatter, project-scoped filtering
Subtasks Separate task files with parent_id field linking to parent
Recurring tasks On completion, backend auto-creates next instance with advanced due date
Calendar Pure frontend month grid, tasks by due_date + recurring expansion
System tray tray-item crate; main thread tray, background thread server; windows-sys for icon on Windows
Task comments YAML sequence in frontmatter, date-stamped, last comment as list/dashboard summary
Dashboard Home route /, loads all projects + all tasks for cross-project summary

Critical: Milkdown Editor Lifecycle

The Milkdown editor requires careful handling when switching between notes/tasks:

Components:

  • MilkdownEditor.vue — Wrapper with :key prop for recreation
  • MilkdownEditorCore.vue — Actual editor using useEditor hook from @milkdown/vue

Pattern for switching content:

// Views use a separate editorKey ref (not the noteId/taskId directly)
// Content MUST be set BEFORE updating editorKey

// CORRECT order:
editorContent.value = loadedContent  // Set content first
editorKey.value = noteId             // Then trigger editor recreation

// WRONG order (causes stale content):
editorKey.value = noteId             // Editor recreates with empty/stale defaultValue
editorContent.value = loadedContent  // Too late - editor already initialized

Why: The editor uses defaultValue from props at creation time. If the key changes before content is set, the editor initializes with wrong content and replaceAll() updates may fail during the async initialization window.

State in MilkdownEditorCore must be refs, not module-level variables — ensures clean state on component recreation.


Development Commands

# Backend (from backend/)
cargo run              # API server on :3000 (no GUI)

# Frontend (from frontend/)
npm run dev            # Dev server on :5173 (connects to backend on :3000)
npm run build          # Build to dist/

# Development: Run both backend and frontend separately
# Backend is API-only, does not serve frontend

Documentation

For detailed information, see:

Document Description
/README.md Project overview, quick start, installation
/frontend/README.md Frontend architecture, component structure, Milkdown editor patterns
/docs/ARCHITECTURE.md System design, service layer, data models, security considerations
/docs/API.md Complete REST API reference with examples
/HANDOVER.md Session handover notes, recent fixes, context for continuing work
/CHECKLIST.md Current implementation status and progress
/PRD.md Full product requirements document

Rules for AI

  • Prefer incremental, verifiable changes
  • File system is source of truth
  • No databases, no cloud services
  • Windows + PowerShell environment
  • Rust 2021 edition
  • Check CHECKLIST.md for current status
  • Check PRD.md for full requirements
  • Check HANDOVER.md for recent session context