Initial release: Ironpad v0.1.0 - Local-first, file-based project and knowledge management system. Rust backend, Vue 3 frontend, Milkdown editor, Git integration, cross-platform builds. Built with AI using Open Method.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
659
docs/API.md
Normal file
659
docs/API.md
Normal file
@@ -0,0 +1,659 @@
|
||||
# Ironpad API Reference
|
||||
|
||||
Base URL: `http://localhost:3000`
|
||||
|
||||
## Notes
|
||||
|
||||
### List Notes
|
||||
|
||||
```http
|
||||
GET /api/notes
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "20260205-123456",
|
||||
"title": "My Note",
|
||||
"path": "notes/20260205-123456.md",
|
||||
"created": "2026-02-05T12:34:56Z",
|
||||
"updated": "2026-02-05T12:34:56Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Create Note
|
||||
|
||||
```http
|
||||
POST /api/notes
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"title": "Optional Title",
|
||||
"content": "# My Note\n\nContent here"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** `201 Created`
|
||||
```json
|
||||
{
|
||||
"id": "20260205-123456",
|
||||
"title": "Optional Title",
|
||||
"path": "notes/20260205-123456.md",
|
||||
"content": "# My Note\n\nContent here",
|
||||
"created": "2026-02-05T12:34:56Z",
|
||||
"updated": "2026-02-05T12:34:56Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Get Note
|
||||
|
||||
```http
|
||||
GET /api/notes/:id
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"id": "20260205-123456",
|
||||
"title": "My Note",
|
||||
"path": "notes/20260205-123456.md",
|
||||
"content": "# My Note\n\nFull content...",
|
||||
"created": "2026-02-05T12:34:56Z",
|
||||
"updated": "2026-02-05T12:34:56Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Update Note
|
||||
|
||||
```http
|
||||
PUT /api/notes/:id
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"content": "# Updated Content\n\nNew content here"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"id": "20260205-123456",
|
||||
"title": "Updated Content",
|
||||
"path": "notes/20260205-123456.md",
|
||||
"content": "# Updated Content\n\nNew content here",
|
||||
"created": "2026-02-05T12:34:56Z",
|
||||
"updated": "2026-02-05T12:35:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Delete (Archive) Note
|
||||
|
||||
```http
|
||||
DELETE /api/notes/:id
|
||||
```
|
||||
|
||||
**Response:** `200 OK`
|
||||
|
||||
Note: The note is moved to `archive/`, not permanently deleted.
|
||||
|
||||
---
|
||||
|
||||
## Projects
|
||||
|
||||
### List Projects
|
||||
|
||||
```http
|
||||
GET /api/projects
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "ferrite",
|
||||
"title": "Ferrite",
|
||||
"description": "A Rust project",
|
||||
"path": "projects/ferrite",
|
||||
"created": "2026-02-04T10:00:00Z",
|
||||
"updated": "2026-02-05T12:00:00Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Create Project
|
||||
|
||||
```http
|
||||
POST /api/projects
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"title": "New Project",
|
||||
"description": "Project description"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** `201 Created`
|
||||
```json
|
||||
{
|
||||
"id": "new-project",
|
||||
"title": "New Project",
|
||||
"description": "Project description",
|
||||
"path": "projects/new-project",
|
||||
"created": "2026-02-05T12:34:56Z",
|
||||
"updated": "2026-02-05T12:34:56Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Get Project
|
||||
|
||||
```http
|
||||
GET /api/projects/:id
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"id": "ferrite",
|
||||
"title": "Ferrite",
|
||||
"description": "A Rust project",
|
||||
"path": "projects/ferrite",
|
||||
"created": "2026-02-04T10:00:00Z",
|
||||
"updated": "2026-02-05T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Get Project Content
|
||||
|
||||
```http
|
||||
GET /api/projects/:id/content
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"content": "# Ferrite\n\nProject overview content..."
|
||||
}
|
||||
```
|
||||
|
||||
### Update Project Content
|
||||
|
||||
```http
|
||||
PUT /api/projects/:id/content
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"content": "# Updated Overview\n\nNew content..."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Project Notes
|
||||
|
||||
### List Project Notes
|
||||
|
||||
```http
|
||||
GET /api/projects/:id/notes
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "20260205-123456",
|
||||
"title": "Project Note",
|
||||
"path": "projects/ferrite/notes/20260205-123456.md",
|
||||
"created": "2026-02-05T12:34:56Z",
|
||||
"updated": "2026-02-05T12:34:56Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Create Project Note
|
||||
|
||||
```http
|
||||
POST /api/projects/:id/notes
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"title": "New Note",
|
||||
"content": "Note content..."
|
||||
}
|
||||
```
|
||||
|
||||
### Get Project Note
|
||||
|
||||
```http
|
||||
GET /api/projects/:id/notes/:noteId
|
||||
```
|
||||
|
||||
### Update Project Note
|
||||
|
||||
```http
|
||||
PUT /api/projects/:id/notes/:noteId
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"content": "Updated content..."
|
||||
}
|
||||
```
|
||||
|
||||
### Delete Project Note
|
||||
|
||||
```http
|
||||
DELETE /api/projects/:id/notes/:noteId
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Project Tasks
|
||||
|
||||
### List Project Tasks
|
||||
|
||||
```http
|
||||
GET /api/projects/:id/tasks
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "task-20260205-123456",
|
||||
"title": "Implement feature X",
|
||||
"completed": false,
|
||||
"section": "Active",
|
||||
"priority": "high",
|
||||
"due_date": "2026-02-10",
|
||||
"is_active": true,
|
||||
"content": "## Requirements\n\n- Item 1\n- Item 2",
|
||||
"path": "projects/ferrite/tasks/task-20260205-123456.md",
|
||||
"created": "2026-02-05T12:34:56Z",
|
||||
"updated": "2026-02-05T12:34:56Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Create Task
|
||||
|
||||
```http
|
||||
POST /api/projects/:id/tasks
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"title": "New Task",
|
||||
"content": "Task description..."
|
||||
}
|
||||
```
|
||||
|
||||
### Get Task
|
||||
|
||||
```http
|
||||
GET /api/projects/:id/tasks/:taskId
|
||||
```
|
||||
|
||||
### Update Task Content
|
||||
|
||||
```http
|
||||
PUT /api/projects/:id/tasks/:taskId
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"content": "Updated task description..."
|
||||
}
|
||||
```
|
||||
|
||||
### Update Task Metadata
|
||||
|
||||
```http
|
||||
PUT /api/projects/:id/tasks/:taskId/meta
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"title": "New Title",
|
||||
"is_active": false,
|
||||
"section": "Backlog",
|
||||
"priority": "low",
|
||||
"due_date": "2026-02-15"
|
||||
}
|
||||
```
|
||||
|
||||
### Toggle Task Completion
|
||||
|
||||
```http
|
||||
PUT /api/projects/:id/tasks/:taskId/toggle
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"completed": true
|
||||
}
|
||||
```
|
||||
|
||||
### Delete Task
|
||||
|
||||
```http
|
||||
DELETE /api/projects/:id/tasks/:taskId
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## All Tasks
|
||||
|
||||
### List All Tasks (across projects)
|
||||
|
||||
```http
|
||||
GET /api/tasks
|
||||
```
|
||||
|
||||
Returns tasks from all projects, useful for global task views.
|
||||
|
||||
---
|
||||
|
||||
## Daily Notes
|
||||
|
||||
### List Daily Notes
|
||||
|
||||
```http
|
||||
GET /api/daily
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"date": "2026-02-05",
|
||||
"path": "daily/2026-02-05.md",
|
||||
"created": "2026-02-05T08:00:00Z",
|
||||
"updated": "2026-02-05T12:00:00Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Get Today's Note
|
||||
|
||||
```http
|
||||
GET /api/daily/today
|
||||
```
|
||||
|
||||
Creates the daily note if it doesn't exist.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"date": "2026-02-05",
|
||||
"content": "# 2026-02-05\n\n## Todo\n\n- [ ] Task 1",
|
||||
"path": "daily/2026-02-05.md",
|
||||
"created": "2026-02-05T08:00:00Z",
|
||||
"updated": "2026-02-05T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Get/Create Daily Note by Date
|
||||
|
||||
```http
|
||||
GET /api/daily/:date
|
||||
POST /api/daily/:date
|
||||
```
|
||||
|
||||
Date format: `YYYY-MM-DD`
|
||||
|
||||
---
|
||||
|
||||
## Assets
|
||||
|
||||
### Upload Asset
|
||||
|
||||
```http
|
||||
POST /api/assets/upload
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
project: ferrite
|
||||
file: (binary data)
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"url": "/api/assets/ferrite/image-20260205-123456.png",
|
||||
"filename": "image-20260205-123456.png"
|
||||
}
|
||||
```
|
||||
|
||||
### Get Asset
|
||||
|
||||
```http
|
||||
GET /api/assets/:project/:filename
|
||||
```
|
||||
|
||||
Returns the binary file with appropriate Content-Type header.
|
||||
|
||||
---
|
||||
|
||||
## Search
|
||||
|
||||
### Search Content
|
||||
|
||||
```http
|
||||
GET /api/search?q=search+term
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"path": "notes/20260205-123456.md",
|
||||
"title": "My Note",
|
||||
"matches": [
|
||||
{
|
||||
"line": 5,
|
||||
"text": "This is a **search term** example"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Git Operations
|
||||
|
||||
### Get Status
|
||||
|
||||
```http
|
||||
GET /api/git/status
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"branch": "main",
|
||||
"ahead": 2,
|
||||
"behind": 0,
|
||||
"staged": [],
|
||||
"modified": ["notes/20260205-123456.md"],
|
||||
"untracked": [],
|
||||
"has_conflicts": false
|
||||
}
|
||||
```
|
||||
|
||||
### Commit Changes
|
||||
|
||||
```http
|
||||
POST /api/git/commit
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"message": "Update notes"
|
||||
}
|
||||
```
|
||||
|
||||
### Push to Remote
|
||||
|
||||
```http
|
||||
POST /api/git/push
|
||||
```
|
||||
|
||||
### Fetch from Remote
|
||||
|
||||
```http
|
||||
POST /api/git/fetch
|
||||
```
|
||||
|
||||
### Get Commit Log
|
||||
|
||||
```http
|
||||
GET /api/git/log?limit=20
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "abc123...",
|
||||
"message": "Update notes",
|
||||
"author": "User Name",
|
||||
"date": "2026-02-05T12:34:56Z",
|
||||
"files_changed": 3
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Get Working Directory Diff
|
||||
|
||||
```http
|
||||
GET /api/git/diff
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"diff": "diff --git a/notes/... "
|
||||
}
|
||||
```
|
||||
|
||||
### Get Commit Diff
|
||||
|
||||
```http
|
||||
GET /api/git/diff/:commitId
|
||||
```
|
||||
|
||||
### Get Remote Info
|
||||
|
||||
```http
|
||||
GET /api/git/remote
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"name": "origin",
|
||||
"url": "git@github.com:user/repo.git",
|
||||
"ahead": 2,
|
||||
"behind": 0
|
||||
}
|
||||
```
|
||||
|
||||
### Check for Conflicts
|
||||
|
||||
```http
|
||||
GET /api/git/conflicts
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"has_conflicts": false,
|
||||
"files": []
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## WebSocket
|
||||
|
||||
### Connect
|
||||
|
||||
```
|
||||
WS /ws
|
||||
```
|
||||
|
||||
### Messages (Client → Server)
|
||||
|
||||
**Lock File:**
|
||||
```json
|
||||
{
|
||||
"type": "lock_file",
|
||||
"path": "notes/20260205-123456.md",
|
||||
"lock_type": "editor"
|
||||
}
|
||||
```
|
||||
|
||||
**Unlock File:**
|
||||
```json
|
||||
{
|
||||
"type": "unlock_file",
|
||||
"path": "notes/20260205-123456.md"
|
||||
}
|
||||
```
|
||||
|
||||
### Messages (Server → Client)
|
||||
|
||||
**File Locked:**
|
||||
```json
|
||||
{
|
||||
"type": "file_locked",
|
||||
"path": "notes/20260205-123456.md",
|
||||
"client_id": "client-123"
|
||||
}
|
||||
```
|
||||
|
||||
**File Unlocked:**
|
||||
```json
|
||||
{
|
||||
"type": "file_unlocked",
|
||||
"path": "notes/20260205-123456.md"
|
||||
}
|
||||
```
|
||||
|
||||
**File Modified (broadcast):**
|
||||
```json
|
||||
{
|
||||
"type": "file_modified",
|
||||
"path": "notes/20260205-123456.md"
|
||||
}
|
||||
```
|
||||
|
||||
**Git Status Update:**
|
||||
```json
|
||||
{
|
||||
"type": "git_status",
|
||||
"status": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Responses
|
||||
|
||||
All endpoints return errors in this format:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Human-readable error message",
|
||||
"code": "ERROR_CODE"
|
||||
}
|
||||
```
|
||||
|
||||
### Common Error Codes
|
||||
|
||||
| Code | HTTP Status | Description |
|
||||
|------|-------------|-------------|
|
||||
| `NOT_FOUND` | 404 | Resource doesn't exist |
|
||||
| `BAD_REQUEST` | 400 | Invalid request data |
|
||||
| `CONFLICT` | 409 | Resource conflict (e.g., Git) |
|
||||
| `INTERNAL_ERROR` | 500 | Server error |
|
||||
416
docs/ARCHITECTURE.md
Normal file
416
docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,416 @@
|
||||
# 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:
|
||||
|
||||
```markdown
|
||||
---
|
||||
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:
|
||||
|
||||
```rust
|
||||
// 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:
|
||||
|
||||
```rust
|
||||
struct Frontmatter {
|
||||
id: String,
|
||||
title: Option<String>,
|
||||
created: DateTime<Utc>,
|
||||
updated: DateTime<Utc>,
|
||||
// ... other fields
|
||||
}
|
||||
```
|
||||
|
||||
#### Git Service
|
||||
|
||||
Wraps Git CLI commands:
|
||||
|
||||
```rust
|
||||
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:
|
||||
|
||||
```rust
|
||||
// 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:
|
||||
|
||||
```typescript
|
||||
// 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:
|
||||
|
||||
```javascript
|
||||
// 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:
|
||||
|
||||
```javascript
|
||||
// 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
|
||||
|
||||
```typescript
|
||||
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
|
||||
|
||||
```typescript
|
||||
interface Project {
|
||||
id: string // e.g., "ferrite" (slug)
|
||||
title: string
|
||||
description?: string
|
||||
path: string
|
||||
created: string
|
||||
updated: string
|
||||
}
|
||||
```
|
||||
|
||||
### Task
|
||||
|
||||
```typescript
|
||||
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
|
||||
content: string // Markdown description
|
||||
path: string
|
||||
created: string
|
||||
updated: string
|
||||
}
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
```json
|
||||
{
|
||||
"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
|
||||
221
docs/ai-workflow/CHECKLIST.md
Normal file
221
docs/ai-workflow/CHECKLIST.md
Normal file
@@ -0,0 +1,221 @@
|
||||
# Ironpad — Implementation Checklist
|
||||
|
||||
This checklist tracks **what is planned vs what is done**, based on the PRD.
|
||||
It is the authoritative execution status for the project.
|
||||
|
||||
---
|
||||
|
||||
## Phase 0 — Preparation ✅ (COMPLETED)
|
||||
|
||||
### Repository & Tooling
|
||||
- [x] Create project root structure
|
||||
- [x] Initialize Rust backend (`ironpad`, edition 2021)
|
||||
- [x] Add backend dependencies (Axum, Tokio, notify, git2, etc.)
|
||||
- [x] Verify backend builds (`cargo check`)
|
||||
|
||||
### Backend Scaffolding
|
||||
- [x] Create `routes/`, `services/`, `models/` modules
|
||||
- [x] Create placeholder files for all planned backend components
|
||||
- [x] Prepare WebSocket and file watcher modules
|
||||
|
||||
### Data Layer
|
||||
- [x] Create `data/` directory structure
|
||||
- [x] Create initial files (`index.md`, `inbox.md`)
|
||||
- [x] Initialize `data/` as its own Git repository
|
||||
|
||||
### Project Meta
|
||||
- [x] Create `ai-context.md`
|
||||
- [x] Create implementation checklist
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — MVP ✅ (COMPLETED)
|
||||
|
||||
### Backend Core
|
||||
- [x] Implement `main.rs` (Axum server bootstrap)
|
||||
- [x] Dynamic port selection (3000–3010)
|
||||
- [x] Auto-open system browser on startup
|
||||
- [x] Serve static frontend files (production path)
|
||||
|
||||
### Notes (CRUD)
|
||||
- [x] List notes from filesystem
|
||||
- [x] Read markdown file by ID
|
||||
- [x] Create new note with auto-generated frontmatter
|
||||
- [x] Update note with auto-save + timestamp update
|
||||
- [x] Archive note on delete (move to `data/archive/`)
|
||||
|
||||
### Frontmatter Automation
|
||||
- [x] Parse/serialize frontmatter
|
||||
- [x] Deterministic ID from path
|
||||
- [x] Auto-manage `created`/`updated` timestamps
|
||||
- [x] Preserve user-defined fields
|
||||
|
||||
### Frontend (Basic)
|
||||
- [x] Vue 3 + Vite setup
|
||||
- [x] Note list sidebar
|
||||
- [x] Note viewer/editor (textarea)
|
||||
- [x] Create/archive note actions
|
||||
- [x] Auto-save on edit
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Core Daily Driver ✅ (COMPLETED)
|
||||
|
||||
### Real-Time Sync
|
||||
- [x] File system watching (`notify` crate)
|
||||
- [x] WebSocket server for real-time updates
|
||||
- [x] External edit detection + UI notifications
|
||||
- [x] Filter out own saves from notifications
|
||||
|
||||
### Search
|
||||
- [x] Full-text search (ripgrep with fallback)
|
||||
- [x] Search endpoint (`GET /api/search?q=`)
|
||||
- [x] UI search integration (Ctrl+K)
|
||||
|
||||
### Git Integration
|
||||
- [x] Git status endpoint
|
||||
- [x] Auto-commit (60-second batching)
|
||||
- [x] Manual commit button
|
||||
- [x] Git status indicator in UI
|
||||
|
||||
### Projects
|
||||
- [x] Project creation (folder + `index.md` + `assets/`)
|
||||
- [x] List projects API
|
||||
- [x] Project task file creation (`tasks.md`)
|
||||
|
||||
### Tasks (Basic)
|
||||
- [x] Task parsing from markdown checkboxes
|
||||
- [x] Tasks API (list all tasks)
|
||||
- [x] Task view in sidebar
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Full PRD Compliance ✅ (COMPLETED)
|
||||
|
||||
### Projects & Tasks (Per PRD Section 7.2, 7.3)
|
||||
- [x] Project-specific task endpoint (`GET /api/projects/:id/tasks`)
|
||||
- [x] Task toggle endpoint (update checkbox state)
|
||||
- [x] Add task via UI (append to `tasks.md`)
|
||||
- [x] Task sections: Active, Completed, Backlog
|
||||
- [x] Project task view at `/projects/:id/tasks` route
|
||||
|
||||
### File Locking (Per PRD Section 7.7)
|
||||
- [x] Backend tracks open files via WebSocket
|
||||
- [x] File lock when Task View opens
|
||||
- [x] Editor shows "Read-Only" if file locked
|
||||
- [x] Auto-unlock when view closes
|
||||
|
||||
### Daily Notes (Per PRD Section 6)
|
||||
- [x] Create `data/daily/` directory
|
||||
- [x] Daily note endpoint (create/get today's note)
|
||||
- [x] Daily note templates
|
||||
- [x] Daily notes in sidebar
|
||||
|
||||
### CodeMirror 6 Editor (Per PRD Section 9.3)
|
||||
- [x] Install CodeMirror 6 dependencies
|
||||
- [x] Replace textarea with CodeMirror
|
||||
- [x] Markdown syntax highlighting
|
||||
- [x] Line numbers
|
||||
- [x] Keyboard shortcuts
|
||||
|
||||
### Markdown Preview (Per PRD Section 5)
|
||||
- [x] Split view (editor + preview)
|
||||
- [x] Markdown-it rendering
|
||||
- [x] CommonMark consistency
|
||||
|
||||
### Assets API (Per PRD Section 8)
|
||||
- [x] `POST /api/assets/upload` endpoint
|
||||
- [x] `GET /api/assets/:project/:file` endpoint
|
||||
- [x] Image upload UI in editor
|
||||
|
||||
### Git Advanced (Per PRD Section 7.5)
|
||||
- [x] Git conflict detection
|
||||
- [x] Conflict warning banner in UI
|
||||
- [x] `POST /api/git/push` endpoint
|
||||
- [x] `GET /api/git/conflicts` endpoint
|
||||
|
||||
### Frontend Architecture (Per PRD Section 14)
|
||||
- [x] Vue Router for navigation
|
||||
- [x] Pinia state management
|
||||
- [x] Separate view components (NotesView, TasksView, ProjectView)
|
||||
- [x] WebSocket composable
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Enhanced Task System ✅ (COMPLETED)
|
||||
|
||||
### Dashboard
|
||||
- [x] Cross-project dashboard as home page (`/`)
|
||||
- [x] Project cards with active task counts and summaries
|
||||
- [x] Click-through to project or task detail
|
||||
- [x] Clickable "Ironpad" title navigates to dashboard
|
||||
|
||||
### Tags
|
||||
- [x] Tags field in task frontmatter (YAML sequence)
|
||||
- [x] Backend parses/writes tags on task CRUD
|
||||
- [x] Tag pills displayed on task list items
|
||||
- [x] Tag filter bar — click to filter tasks by tag
|
||||
- [x] Tag editor in task detail panel with autocomplete
|
||||
- [x] `projectTags` computed getter for all unique tags in project
|
||||
|
||||
### Subtasks
|
||||
- [x] `parent_id` field in task frontmatter
|
||||
- [x] Backend accepts `parent_id` on task creation
|
||||
- [x] Task list groups subtasks under parent (indented)
|
||||
- [x] Subtask count badge on parent tasks (completed/total)
|
||||
- [x] Subtask panel in task detail with inline add
|
||||
- [x] Subtasks clickable to view/edit
|
||||
|
||||
### Recurring Tasks
|
||||
- [x] `recurrence` and `recurrence_interval` fields in frontmatter
|
||||
- [x] Backend auto-creates next instance on recurring task completion
|
||||
- [x] Due date advanced by interval (daily/weekly/monthly/yearly)
|
||||
- [x] Recurrence picker (dropdown) in task detail panel
|
||||
- [x] Recurrence indicator on task list items
|
||||
|
||||
### Calendar View
|
||||
- [x] Month grid calendar at `/calendar`
|
||||
- [x] Tasks with due dates plotted on day cells
|
||||
- [x] Daily notes shown as blue dots
|
||||
- [x] Color-coded urgency (overdue/today/soon)
|
||||
- [x] Month navigation (prev/next) + Today button
|
||||
- [x] Click task → navigate to detail, click date → daily note
|
||||
- [x] Calendar link in sidebar navigation
|
||||
|
||||
### Due Date
|
||||
- [x] Inline date picker in task detail panel
|
||||
- [x] Clear due date button
|
||||
- [x] Due date display with color-coded urgency on task items
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 — Polish
|
||||
|
||||
- [ ] UI polish and animations
|
||||
- [ ] Responsive sidebar
|
||||
- [ ] Better error handling/messages
|
||||
- [ ] Loading states
|
||||
|
||||
---
|
||||
|
||||
## Phase 6 — Future / Optional
|
||||
|
||||
- [ ] Global hotkey (Ctrl+Shift+Space)
|
||||
- [ ] System tray mode
|
||||
- [ ] Backlinks between notes
|
||||
- [ ] Graph view
|
||||
- [ ] Export (PDF / HTML)
|
||||
- [ ] Custom themes
|
||||
- [ ] Tantivy search (if >5000 notes)
|
||||
- [ ] Task dependencies (blocked by)
|
||||
- [ ] Time estimates on tasks
|
||||
- [ ] Calendar drag-and-drop rescheduling
|
||||
- [ ] Week/day calendar views
|
||||
|
||||
---
|
||||
|
||||
## Rules
|
||||
|
||||
- No item is marked complete unless it is implemented and verified.
|
||||
- New features must be added to this checklist before implementation.
|
||||
- If it's not on this list, it's out of scope.
|
||||
128
docs/ai-workflow/HANDOVER.md
Normal file
128
docs/ai-workflow/HANDOVER.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Ironpad - Chat Handover Document
|
||||
|
||||
**Date:** 2026-02-05
|
||||
**Context:** See `ai-context.md` for full project overview
|
||||
|
||||
---
|
||||
|
||||
## Session Summary
|
||||
|
||||
This session focused on fixing a critical bug where **notes and tasks displayed stale/wrong content** when switching between items. The issue caused data loss as the wrong content was being saved to the wrong files.
|
||||
|
||||
---
|
||||
|
||||
## What Was Fixed
|
||||
|
||||
### Problem: Stale Content When Switching Notes/Tasks
|
||||
|
||||
**Symptoms:**
|
||||
- Click note A → shows content correctly
|
||||
- Click note B → still shows note A's content
|
||||
- Refresh sometimes fixes it, sometimes shows blank
|
||||
- Auto-save then overwrites note B with note A's content (DATA LOSS)
|
||||
|
||||
**Root Cause:**
|
||||
The Milkdown WYSIWYG editor wasn't properly recreating when switching items. Two issues:
|
||||
|
||||
1. **Module-level variables in `MilkdownEditorCore.vue`** - State like `currentContent` was persisting across component recreations because they were `let` variables instead of Vue `ref`s.
|
||||
|
||||
2. **Race condition in view components** - The editor key was changing BEFORE content was loaded:
|
||||
```
|
||||
noteId changes → editor recreates with empty content → content loads → too late
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
1. Converted module-level `let` variables to `ref`s in `MilkdownEditorCore.vue`
|
||||
2. Added retry mechanism for applying pending content
|
||||
3. Introduced separate `editorKey` ref in all view components that only updates AFTER content is loaded
|
||||
4. Added guards to prevent emitting stale content
|
||||
|
||||
**Files Modified:**
|
||||
- `frontend/src/components/MilkdownEditorCore.vue`
|
||||
- `frontend/src/components/MilkdownEditor.vue`
|
||||
- `frontend/src/views/ProjectNotesView.vue`
|
||||
- `frontend/src/views/TasksView.vue`
|
||||
- `frontend/src/views/NotesView.vue`
|
||||
|
||||
---
|
||||
|
||||
## Outstanding Issues
|
||||
|
||||
All major issues from this session have been resolved:
|
||||
|
||||
1. **Auto-save aggressiveness** - FIXED: Now tracks "last saved content" and only saves when actual changes are made
|
||||
2. **Documentation** - FIXED: Added README.md, docs/ARCHITECTURE.md, docs/API.md
|
||||
|
||||
---
|
||||
|
||||
## Technical Context for Future Sessions
|
||||
|
||||
### Milkdown Editor Lifecycle (Critical Knowledge)
|
||||
|
||||
The Milkdown editor (WYSIWYG markdown) has a complex lifecycle:
|
||||
|
||||
1. `MilkdownProvider` provides Vue context
|
||||
2. `useEditor` hook creates the `Crepe` instance
|
||||
3. `Crepe.editor` is the actual Milkdown Editor
|
||||
4. `editor.action(replaceAll(content))` updates content
|
||||
5. BUT `editor.action` isn't immediately available after `useEditor` returns
|
||||
|
||||
**Key Pattern:** Always set content BEFORE changing the editor key:
|
||||
```javascript
|
||||
// CORRECT
|
||||
editorContent.value = newContent
|
||||
editorKey.value = newId // Editor recreates with correct defaultValue
|
||||
|
||||
// WRONG
|
||||
editorKey.value = newId // Editor recreates with stale/empty content
|
||||
editorContent.value = newContent // Too late!
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
ironpad/
|
||||
├── backend/ # Rust Axum server (API only)
|
||||
├── frontend/ # Vue 3 SPA
|
||||
│ └── src/
|
||||
│ ├── components/
|
||||
│ │ ├── MilkdownEditor.vue # Wrapper component
|
||||
│ │ └── MilkdownEditorCore.vue # Actual editor (key file!)
|
||||
│ ├── views/
|
||||
│ │ ├── NotesView.vue # Standalone notes
|
||||
│ │ ├── ProjectNotesView.vue # Project-specific notes
|
||||
│ │ └── TasksView.vue # Project tasks
|
||||
│ └── stores/ # Pinia state management
|
||||
└── data/ # Markdown files (source of truth)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Recommended Next Steps
|
||||
|
||||
1. ~~**Fix auto-save aggressiveness**~~ - DONE: Uses `lastSavedContent` to track actual changes
|
||||
2. ~~**Create proper README.md**~~ - DONE: See `/README.md`, `/frontend/README.md`
|
||||
3. ~~**Add developer documentation**~~ - DONE: See `/docs/ARCHITECTURE.md`, `/docs/API.md`
|
||||
4. **Consider adding tests** - At minimum, test the content switching logic
|
||||
|
||||
---
|
||||
|
||||
## Commands Reference
|
||||
|
||||
```bash
|
||||
# Backend (from backend/)
|
||||
cargo run # API server on :3000
|
||||
|
||||
# Frontend (from frontend/)
|
||||
npm run dev # Dev server on :5173
|
||||
npm run build # Production build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Windows + PowerShell environment
|
||||
- Files are the database (no SQL)
|
||||
- Git auto-commits every 60 seconds
|
||||
- See `ai-context.md` for full feature list and API endpoints
|
||||
1004
docs/ai-workflow/PRD.md
Normal file
1004
docs/ai-workflow/PRD.md
Normal file
File diff suppressed because it is too large
Load Diff
22
docs/ai-workflow/README.md
Normal file
22
docs/ai-workflow/README.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# AI-Assisted Development: How Ironpad Was Built
|
||||
|
||||
Ironpad was built entirely using AI-assisted development. Not just the code -- the architecture, the PRD, the task breakdowns, and the debugging were all done through structured collaboration with AI models.
|
||||
|
||||
We share this process openly as part of the **Open Method** philosophy: not just open source code, but open development process.
|
||||
|
||||
## What's in This Folder
|
||||
|
||||
| Document | Description |
|
||||
|----------|-------------|
|
||||
| [method.md](method.md) | The complete AI development workflow: PRD-first, task decomposition, handover system |
|
||||
| [tools.md](tools.md) | Every tool used, how it fits in, and why |
|
||||
| [lessons-learned.md](lessons-learned.md) | What worked, what didn't, and the 200K to 1M context window shift |
|
||||
|
||||
## Articles
|
||||
|
||||
- [The AI Development Workflow I Actually Use](https://dev.to/olaproeis/the-ai-development-workflow-i-actually-use-549i) -- The original workflow article describing the method
|
||||
- [From 200K to 1M: How Claude Opus 4.6 Changed My AI Development Workflow](docs/article-opus-4.6-workflow.md) -- Follow-up on the context window leap
|
||||
|
||||
## Key Takeaway
|
||||
|
||||
The tools keep getting better, but **the process of using them well still matters**. A 1 million token context window doesn't help if you don't know what to ask for. Structure your work, be specific, maintain context, verify results.
|
||||
133
docs/ai-workflow/lessons-learned.md
Normal file
133
docs/ai-workflow/lessons-learned.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# Lessons Learned
|
||||
|
||||
What worked, what didn't, and what changed when the context window went from 200K to 1M tokens.
|
||||
|
||||
## What Worked Well
|
||||
|
||||
### 1. PRD-First Development
|
||||
|
||||
Writing a detailed PRD before any code was the single highest-leverage activity. The AI produces dramatically better code when it knows exactly what success looks like.
|
||||
|
||||
Ironpad's PRD went through 3 versions. Each round of multi-AI review caught issues that would have been expensive to fix later:
|
||||
- Concurrent editing race conditions (file locking needed)
|
||||
- File watcher integration (external editor support)
|
||||
- Git conflict handling (graceful degradation, not crashes)
|
||||
- Frontmatter automation (users shouldn't manually edit metadata)
|
||||
|
||||
**Lesson:** Time spent on the PRD pays off 10x during implementation.
|
||||
|
||||
### 2. Rust's Strict Compiler
|
||||
|
||||
Rust stands out for AI-assisted development because the compiler is extraordinarily strict. It catches:
|
||||
- Memory safety issues
|
||||
- Type mismatches
|
||||
- Lifetime problems
|
||||
- Unused variables and imports
|
||||
- Missing error handling
|
||||
|
||||
With dynamic languages, bugs hide until runtime. With Rust, the AI gets immediate, precise feedback on what's broken. The feedback loop is tighter and more reliable.
|
||||
|
||||
`cargo check` became the primary verification tool. If it compiles, a large category of bugs is already eliminated.
|
||||
|
||||
**Lesson:** A strict compiler is a massive advantage for AI-generated code.
|
||||
|
||||
### 3. The ai-context.md Pattern
|
||||
|
||||
Maintaining a lean (~100 line) architectural reference that tells the AI how to write code for this specific project eliminated a whole class of "doesn't fit the codebase" problems.
|
||||
|
||||
Without it, the AI would invent new patterns, use different naming conventions, or structure code differently from the existing codebase. With it, code consistently matched existing patterns.
|
||||
|
||||
**Lesson:** A small context document is worth more than a large architecture doc. The AI needs a cheat sheet, not a textbook.
|
||||
|
||||
### 4. Fresh Chats Over Long Conversations
|
||||
|
||||
Context accumulates noise. By the third task in a single chat, the AI references irrelevant earlier context. Starting fresh with a focused handover produced consistently better results.
|
||||
|
||||
**Lesson:** Shorter, focused sessions beat long wandering ones.
|
||||
|
||||
## What Didn't Work
|
||||
|
||||
### 1. Trusting "This Should Work"
|
||||
|
||||
The AI confidently says "this should work" when it doesn't. Every single time. Without exception.
|
||||
|
||||
Early on, I'd take the AI's word and move on. Then things would break two features later when the untested code interacted with something else.
|
||||
|
||||
**Fix:** Test everything yourself. Run the feature. Click the buttons. Try the edge cases. The AI writes code; you verify the product.
|
||||
|
||||
### 2. Vague Requirements
|
||||
|
||||
"Add search" produces mediocre results. "Add full-text search with ripgrep, triggered by Ctrl+K, showing filename and matching line with context, limited to 5 matches per file, falling back to manual string search if ripgrep isn't available" produces excellent results.
|
||||
|
||||
**Fix:** Be specific. The more precise the requirement, the better the code.
|
||||
|
||||
### 3. Over-Engineering
|
||||
|
||||
The AI tends to add abstractions, patterns, and generalization that aren't needed yet. It builds for a future that may never come.
|
||||
|
||||
**Fix:** Explicitly state YAGNI in the context. Call out when something is over-engineered. The AI responds well to "simplify this."
|
||||
|
||||
### 4. Ignoring the Editor Lifecycle
|
||||
|
||||
The Milkdown WYSIWYG editor had a complex initialization lifecycle that the AI didn't fully understand. This caused a critical bug where switching between notes showed stale content, leading to data loss.
|
||||
|
||||
**Fix:** Document critical component lifecycles in ai-context.md. The "Milkdown Editor Lifecycle" section was added after this bug and prevented similar issues.
|
||||
|
||||
## The 200K to 1M Context Shift
|
||||
|
||||
This was the most significant change in the project's development workflow.
|
||||
|
||||
### Before: 200K Tokens (Claude Opus 4.5)
|
||||
|
||||
| Aspect | Reality |
|
||||
|--------|---------|
|
||||
| Files in context | ~3-5 at once |
|
||||
| Task granularity | Must split features into 3-5 micro-tasks |
|
||||
| Handovers | Required between every task |
|
||||
| Cross-file bugs | Hard to find (AI can't see all files) |
|
||||
| Refactors | Multi-session, risk of inconsistency |
|
||||
| Overhead per task | ~15-20 min (handover + context setup) |
|
||||
|
||||
### After: 1M Tokens (Claude Opus 4.6)
|
||||
|
||||
| Aspect | Reality |
|
||||
|--------|---------|
|
||||
| Files in context | Entire codebase (80+ files) |
|
||||
| Task granularity | Full features in one session |
|
||||
| Handovers | Only needed between days/sessions |
|
||||
| Cross-file bugs | Found automatically (AI sees everything) |
|
||||
| Refactors | Single session, guaranteed consistency |
|
||||
| Overhead per task | ~0 min |
|
||||
|
||||
### The Codebase Audit
|
||||
|
||||
The clearest demonstration of the shift: loading the entire Ironpad codebase into a single context and asking "what's wrong?"
|
||||
|
||||
The AI found 16 issues, including:
|
||||
- **Auto-commit silently broken** -- A flag that was never set to `true` anywhere in the codebase. Finding this required reading `main.rs`, `git.rs`, and every route handler simultaneously.
|
||||
- **Operator precedence bug** -- `0 > 0` evaluated before `??` due to JavaScript precedence rules. Subtle and easy to miss.
|
||||
- **Missing atomic writes** -- Only one of eight write paths used the safe atomic write pattern.
|
||||
|
||||
14 of 16 issues were fixed in a single session. Zero compilation errors introduced.
|
||||
|
||||
This type of comprehensive audit was not practical at 200K tokens.
|
||||
|
||||
### What Didn't Change
|
||||
|
||||
The 1M context window doesn't change everything:
|
||||
- **PRDs are still essential.** More context doesn't substitute for clear requirements.
|
||||
- **Testing is still essential.** The AI still says "this should work" when it doesn't.
|
||||
- **Specificity still matters.** Vague asks still produce vague results.
|
||||
- **Handovers still matter** between sessions (sleep, context switches, etc.)
|
||||
|
||||
The context window is a force multiplier, not a replacement for process.
|
||||
|
||||
## Advice for Others
|
||||
|
||||
1. **Start with the PRD.** It's the highest-leverage activity.
|
||||
2. **Use a strict language if you can.** Rust, TypeScript (strict mode), Go -- anything with a compiler that catches bugs.
|
||||
3. **Maintain ai-context.md.** Keep it under 100 lines. Update it when patterns change.
|
||||
4. **Test everything.** Don't read code. Run the thing.
|
||||
5. **Use multiple AI models.** They have different blind spots.
|
||||
6. **Be specific.** The more precise the requirement, the better the result.
|
||||
7. **Keep sessions focused.** One task, one chat (at 200K). One feature, one chat (at 1M).
|
||||
119
docs/ai-workflow/method.md
Normal file
119
docs/ai-workflow/method.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# The Development Method
|
||||
|
||||
This document describes the AI-assisted development workflow used to build Ironpad from scratch.
|
||||
|
||||
## Overview
|
||||
|
||||
The method has six phases, applied iteratively for each feature:
|
||||
|
||||
1. **Multi-AI Consultation** -- Get perspectives from multiple AI models before coding
|
||||
2. **PRD Creation** -- Write a detailed product requirements document
|
||||
3. **Task Decomposition** -- Break the PRD into ordered, dependency-aware tasks
|
||||
4. **Context Loading** -- Feed the AI current documentation and project context
|
||||
5. **Implementation** -- Build features in focused sessions with handovers
|
||||
6. **Verification** -- Test everything yourself; don't trust "this should work"
|
||||
|
||||
## Phase 1: Multi-AI Consultation
|
||||
|
||||
Before writing any code, discuss the idea with different AI assistants:
|
||||
|
||||
- **Claude** for architecture and code design
|
||||
- **Perplexity** for research on libraries, crate versions, and known issues
|
||||
- **Gemini** for alternative perspectives and catching blind spots
|
||||
|
||||
Each AI has different strengths and blind spots. Five minutes getting multiple opinions saves hours of rework later.
|
||||
|
||||
**Example from Ironpad:** When designing the task system, one model suggested storing tasks as checkboxes in a single `tasks.md` file. Another pointed out that individual task files with frontmatter would be more flexible and avoid concurrent edit conflicts. We went with individual files, which turned out to be the right call.
|
||||
|
||||
## Phase 2: PRD Creation
|
||||
|
||||
Task Master (and AI in general) produces dramatically better results when it knows exactly what success looks like. The PRD captures:
|
||||
|
||||
- Problem statement and goals
|
||||
- Detailed feature specifications
|
||||
- Technical architecture decisions
|
||||
- API design
|
||||
- Data model
|
||||
- Edge cases and error handling
|
||||
- Non-goals (equally important)
|
||||
|
||||
After drafting, run the PRD through other AIs for review. Iterate until it's tight.
|
||||
|
||||
**Ironpad's PRD** went through 3 versions, incorporating feedback about concurrency control, file watching, git conflict handling, and frontmatter automation -- all before a single line of code was written.
|
||||
|
||||
## Phase 3: Task Decomposition
|
||||
|
||||
Use Task Master to parse the PRD into structured tasks with dependencies. Each task should have:
|
||||
|
||||
- Clear inputs (what files/context are needed)
|
||||
- Clear outputs (what gets created/changed)
|
||||
- Explicit dependencies (what must be done first)
|
||||
- Acceptance criteria (how to verify it works)
|
||||
|
||||
You don't need Task Master specifically. What matters is having explicit, ordered tasks rather than vague goals like "add search."
|
||||
|
||||
## Phase 4: Context Loading
|
||||
|
||||
AI models have training cutoffs. The library docs they know might be outdated.
|
||||
|
||||
- **Context7** (MCP tool) pulls current documentation into context for any library
|
||||
- **ai-context.md** is a lean architectural reference (~100 lines) telling the AI how to write code that fits the codebase
|
||||
- **Handover documents** carry context between sessions
|
||||
|
||||
### The ai-context.md Pattern
|
||||
|
||||
This file tells the AI *how* to write code that belongs in this project:
|
||||
|
||||
- Module structure and naming conventions
|
||||
- Key types and their relationships
|
||||
- Framework idioms (Axum patterns, Vue composition API patterns)
|
||||
- Critical gotchas that cause bugs (e.g., Milkdown editor lifecycle)
|
||||
- Current implementation status
|
||||
|
||||
It's not a full architecture doc. It's a cheat sheet for the AI.
|
||||
|
||||
## Phase 5: Implementation
|
||||
|
||||
### The Handover System (200K Context)
|
||||
|
||||
With 200K token models, each task gets a fresh chat:
|
||||
|
||||
1. Open new chat
|
||||
2. Paste handover document with: rules, relevant files, current task
|
||||
3. Work on task until done
|
||||
4. AI updates the handover document for the next task
|
||||
5. Close chat, repeat
|
||||
|
||||
**Why fresh chats?** Context accumulates noise. Three tasks in, the AI references irrelevant stuff from earlier. Starting clean with a focused handover produces better results.
|
||||
|
||||
### The Full-Context Approach (1M Context)
|
||||
|
||||
With 1M token models (Claude Opus 4.6), the workflow simplifies:
|
||||
|
||||
1. Load the entire codebase into context
|
||||
2. Work on features directly -- the AI sees everything
|
||||
3. Use handovers only for session boundaries (end of day, sleep, etc.)
|
||||
|
||||
The handover system doesn't disappear -- it shifts from "required between every task" to "useful between sessions."
|
||||
|
||||
## Phase 6: Verification
|
||||
|
||||
The AI writes the code. You verify the product.
|
||||
|
||||
- Run the feature and see if it works
|
||||
- Test edge cases manually
|
||||
- Check that nothing else broke
|
||||
- Use compiler output (`cargo check`) and linters as mechanical verification
|
||||
|
||||
Don't read code line by line. Run the thing and see if it works. When something's wrong, describe the problem clearly. The AI debugs from there.
|
||||
|
||||
## How This Played Out for Ironpad
|
||||
|
||||
| Phase | What Happened |
|
||||
|-------|---------------|
|
||||
| 1-3 | PRD v3.0 with architecture decisions, reviewed by multiple AIs |
|
||||
| 4 | ai-context.md maintained throughout, Context7 for Axum/Vue/Milkdown docs |
|
||||
| 5 | Phases 1-3 built with Opus 4.5 (200K), phases 4-5 with Opus 4.6 (1M) |
|
||||
| 6 | Every feature manually tested in the browser |
|
||||
|
||||
Total development time: approximately 2 weeks from PRD to working application with dashboard, calendar, git panel, WYSIWYG editor, subtasks, recurring tasks, and real-time sync.
|
||||
116
docs/ai-workflow/tools.md
Normal file
116
docs/ai-workflow/tools.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# Tools Used
|
||||
|
||||
Every tool in the Ironpad development workflow and how it fits in.
|
||||
|
||||
## IDE
|
||||
|
||||
### Cursor IDE
|
||||
|
||||
The primary development environment. Cursor is a fork of VS Code with built-in AI integration.
|
||||
|
||||
- **Why:** Direct integration with Claude models, inline code editing, multi-file context
|
||||
- **How used:** All coding, file editing, terminal commands, and AI conversations happen in Cursor
|
||||
- **Alternative:** VS Code + Copilot, Windsurf, or any editor with AI integration
|
||||
|
||||
## AI Models
|
||||
|
||||
### Claude Opus 4.5 (Anthropic)
|
||||
|
||||
Used for the majority of Ironpad's development (Phases 1-3 of the implementation).
|
||||
|
||||
- **Context window:** 200K tokens
|
||||
- **Strengths:** Excellent at Rust code, understands Axum patterns well, good at architecture
|
||||
- **Limitation:** Can only hold ~5 files at once; required task splitting and handover documents
|
||||
- **How used:** Feature implementation, debugging, code review
|
||||
|
||||
### Claude Opus 4.6 (Anthropic)
|
||||
|
||||
Used for later phases and the full codebase audit.
|
||||
|
||||
- **Context window:** 1M tokens (5x increase)
|
||||
- **Strengths:** Can hold the entire codebase at once; finds cross-file bugs; handles complex refactors in one session
|
||||
- **How used:** Codebase audit (found 16 issues), cross-cutting refactors, feature implementation without task splitting
|
||||
|
||||
### Perplexity AI
|
||||
|
||||
Used for research before coding.
|
||||
|
||||
- **Why:** Has internet access, provides current information with citations
|
||||
- **How used:** Checking library versions, finding known issues with crates, researching approaches
|
||||
- **Example:** Verified that `serde_yaml` was deprecated before we chose to use it (accepted risk for v1)
|
||||
|
||||
### Google Gemini
|
||||
|
||||
Used as a second opinion on architecture.
|
||||
|
||||
- **Why:** Different training data and perspective from Claude
|
||||
- **How used:** PRD review, architecture review, catching blind spots
|
||||
- **Example:** Flagged the need for file locking between Task View and Editor (race condition that Claude's initial design missed)
|
||||
|
||||
## MCP Tools
|
||||
|
||||
### Task Master
|
||||
|
||||
A Model Context Protocol (MCP) tool for structured task management.
|
||||
|
||||
- **What it does:** Parses a PRD and generates ordered task lists with dependencies
|
||||
- **How used:** Fed the PRD to Task Master to generate the implementation plan; tasks tracked through completion
|
||||
- **Why it matters:** Turns a document into actionable, sequenced work items
|
||||
|
||||
### Context7
|
||||
|
||||
A Model Context Protocol (MCP) tool for pulling current library documentation.
|
||||
|
||||
- **What it does:** Fetches up-to-date documentation for any library and loads it into the AI's context
|
||||
- **How used:** Pulled current Axum 0.8 docs, Vue 3 Composition API docs, Milkdown editor API docs
|
||||
- **Why it matters:** Eliminates bugs caused by the AI using outdated API knowledge
|
||||
|
||||
## Build Tools
|
||||
|
||||
### Rust / Cargo
|
||||
|
||||
- Backend language and build system
|
||||
- `cargo check` for fast compilation checks
|
||||
- `cargo build --release` for production binaries
|
||||
- Strict compiler catches entire categories of bugs before runtime
|
||||
|
||||
### Node.js / Vite
|
||||
|
||||
- Frontend build tooling
|
||||
- `npm run dev` for development with hot reload
|
||||
- `npm run build` for production static files
|
||||
- Vue SFC compilation via `@vitejs/plugin-vue`
|
||||
|
||||
### Git
|
||||
|
||||
- Version control for all data files
|
||||
- `git2` crate for programmatic access from Rust
|
||||
- Automatic 60-second commit batching
|
||||
- Full diff viewer in the UI
|
||||
|
||||
## The Tool Stack in Practice
|
||||
|
||||
```
|
||||
Idea
|
||||
|
|
||||
v
|
||||
[Perplexity] -- Research libraries, check feasibility
|
||||
[Gemini] -- Second opinion on approach
|
||||
|
|
||||
v
|
||||
[Claude] -- Draft PRD
|
||||
[Gemini] -- Review PRD
|
||||
|
|
||||
v
|
||||
[Task Master] -- Generate ordered task list
|
||||
|
|
||||
v
|
||||
[Cursor + Claude] -- Implement each task
|
||||
[Context7] -- Current docs when needed
|
||||
|
|
||||
v
|
||||
[Manual Testing] -- Verify in browser
|
||||
[cargo check] -- Compiler verification
|
||||
```
|
||||
|
||||
No single tool does everything. The value is in how they compose.
|
||||
BIN
docs/screenshot.jpg
Normal file
BIN
docs/screenshot.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 214 KiB |
Reference in New Issue
Block a user