# IronKanban — Technical Blueprint (Dev Spec) ## 0. Scope MVP implementation of: - Git-backed markdown project/task system - Kanban board UI - Drag & drop between columns - Real-time-ish updates (polling → SSE-ready) - PHP backend + vanilla JS frontend - No framework (structured, but lightweight) ## 1. System Architecture ### 1.1 High-level flow ```plain Filesystem (markdown + git) ↑ ↓ Repositories (PHP) ↑ ↓ Services (business logic) ↑ ↓ API Endpoints (public/api/*.php) ↑ ↓ Frontend (vanilla JS + SortableJS) ``` ### 1.2 Request lifecycle (example: move task) ```plain Drag card → JS computes new position → POST /api/move-task.php → TaskRepository loads file → BoardService updates column/order → TaskRepository writes file → GitService commits → RevisionService updates revision → Response JSON → UI updates (already optimistic) ``` ## 2. Filesystem Contract ### 2.1 Root config ```php const PROJECT_ROOT = '/data/projects'; ``` All file access must resolve within this root. ### 2.2 Project layout ```plain /data/projects/{projectSlug}/ ├── index.md ├── board.md ├── notes/ │ └── *.md ├── tasks/ │ └── *.md ``` ## 3. Markdown Contract ### 3.1 Front matter parser contract ```php class FrontMatterDocument { public array $meta; public string $body; } ``` ### 3.2 Required keys by type #### Project (`index.md`) ```yaml type: project id: string title: string created: ISO8601 updated: ISO8601 ``` #### Board (`board.md`) ```yaml type: board project_id: string columns: - id: string label: string order: int updated: ISO8601 ``` #### Task (`tasks/*.md`) ```yaml type: task id: string title: string project_id: string column: string order: int completed: bool priority: string is_active: bool created: ISO8601 updated: ISO8601 ``` Optional (must be preserved if present): ```yaml status: string|null status_note: string|null assignee: string|null tags: array section: string # legacy ``` #### Note (`notes/*.md`) ```yaml type: note id: string title: string project_id: string created: ISO8601 updated: ISO8601 ``` ## 4. Domain Models ### 4.1 Project ```php class Project { public string $id; public string $title; public string $path; } ``` ### 4.2 Task ```php class Task { public string $id; public string $title; public string $projectId; public string $column; public int $order; public bool $completed; public string $priority; public bool $isActive; public array $meta; public string $body; } ``` ### 4.3 Board ```php class Board { public string $projectId; /** @var Column[] */ public array $columns; } ``` ### 4.4 Column ```php class Column { public string $id; public string $label; public int $order; } ``` ### 4.5 Note ```php class Note { public string $id; public string $title; public string $body; } ``` ## 5. Repositories ### 5.1 ProjectRepository ```php class ProjectRepository { public function getAll(): array; public function get(string $slug): Project; } ``` ### 5.2 TaskRepository ```php class TaskRepository { public function getAll(string $projectId): array; public function get(string $projectId, string $taskId): Task; public function save(Task $task): void; public function create(array $data): Task; public function delete(string $projectId, string $taskId): void; } ``` #### Responsibilities - read/write markdown - preserve unknown front matter keys - handle file paths - ensure atomic writes ### 5.3 BoardRepository ```php class BoardRepository { public function get(string $projectId): Board; public function save(Board $board): void; } ``` ### 5.4 NoteRepository ```php class NoteRepository { public function getAll(string $projectId): array; public function save(Note $note): void; } ``` ## 6. Services ### 6.1 BoardService ```php class BoardService { public function getBoardState(string $projectId): array; public function moveTask( string $projectId, string $taskId, string $column, int $newOrder ): void; } ``` ### 6.2 TaskOrderingService ```php class TaskOrderingService { public function computeOrder(array $tasks, int $targetIndex): int; public function rebalance(array $tasks): array; } ``` #### Rule - Use gaps: 100, 200, 300 - Insert = midpoint - Rebalance only if gap < threshold ### 6.3 GitService ```php class GitService { public function commit(string $message): void; } ``` #### Implementation (MVP) ```bash git add . git commit -m "..." ``` #### Future - scoped add - branch support ### 6.4 RevisionService ```php class RevisionService { public function get(string $projectId): string; public function bump(string $projectId): void; } ``` #### Storage ```plain /data/projects/{project}/.revision ``` ### 6.5 FileLock ```php class FileLock { public static function run(string $path, callable $callback); } ``` Uses `flock()`. ## 7. API Specification All endpoints return JSON. ### 7.1 GET `/api/board-state.php` #### Query `?project=personal-projects` #### Response ```json { "project": {}, "board": {}, "columns": [], "tasks": [], "notes": [], "revision": "timestamp" } ``` ### 7.2 POST `/api/move-task.php` #### Input ```json { "projectId": "personal-projects", "taskId": "task-123", "column": "doing", "index": 2 } ``` #### Flow ```plain load tasks in column → compute order → update task → save → git commit → revision bump ``` ### 7.3 POST `/api/create-task.php` ```json { "projectId": "...", "title": "...", "column": "backlog", "body": "" } ``` ### 7.4 POST `/api/save-task.php` Full update: ```json { "projectId": "...", "taskId": "...", "title": "...", "body": "...", "meta": {} } ``` ### 7.5 POST `/api/delete-task.php` ```json { "projectId": "...", "taskId": "..." } ``` ### 7.6 POST `/api/create-column.php` ```json { "projectId": "...", "label": "Blocked" } ``` ### 7.7 POST `/api/update-board.php` Reorder / rename columns. ### 7.8 POST `/api/save-note.php` ### 7.9 GET `/api/events.php` (Phase 2) SSE stream: ```plain event: boardUpdated data: { "revision": "..." } ``` ## 8. Frontend Spec ### 8.1 Stack - Vanilla JS - SortableJS (drag/drop) - Server-rendered HTML + JS hydration ### 8.2 State model ```javaScript const state = { projectId: null, columns: [], tasks: [], revision: null }; ``` ### 8.3 Drag/drop flow ```javaScript onEnd → get taskId get new column get index compute optimistic order update DOM POST move-task if fail → revert ``` ### 8.4 Polling loop (MVP) ```javaScript setInterval(async () => { const res = await fetch('/api/board-state?project=...'); if (res.revision !== state.revision) { reloadBoard(); } }, 5000); ``` ### 8.5 SSE (Phase 2) ```javaScript const evt = new EventSource('/api/events.php?project=...'); evt.onmessage = () => reloadBoard(); ``` ## 9. Migration Logic ### 9.1 Missing board.md ```plain scan tasks → collect unique section values → generate columns → write board.md ``` ### 9.2 Missing column ```php if (!column && section) { column = slugify(section); } ``` ### 9.3 Missing order Assign: `order = index * 100;` ## 10. File Write Rules ### 10.1 Atomic write ```plain write temp file → fsync → rename → unlock ``` ### 10.2 Preserve unknown metadata Never discard keys. ### 10.3 Encoding UTF-8 only. ## 11. Error Handling ### 11.1 API response format ```json { "success": false, "error": "message" } ``` ### 11.2 Conflict detection If revision mismatch: ```json { "success": false, "error": "conflict" } ``` ## 12. Security - No path traversal - Sanitize projectId - Whitelist file extensions - Escape shell commands - Run git in repo root only ## 13. Performance Considerations - Cache parsed board in memory per request - Avoid re-reading unchanged files - Lazy-load notes if needed - Debounce save operations (frontend) ## 14. MVP Milestones ### Phase 1 - File parsing - Board rendering - Basic UI ### Phase 2 - Drag/drop - Save APIs ### Phase 3 - Task CRUD - Column CRUD ### Phase 4 - Git integration - Revision tracking ### Phase 5 - Polling updates ### Phase 6 (optional) - SSE ## 15. Out-of-Scope (MVP) - Auth system - Multi-repo support - Comments - Subtasks - Attachments - Permissions - Websockets ## 16. Build Order (Practical) I suggest this order: 1. Markdown parser + writer 2. TaskRepository 3. BoardRepository 4. BoardService (read-only) 5. Render board (no JS yet) 6. Add SortableJS 7. Move-task endpoint 8. Create/edit task 9. GitService 10. Revision + polling ## 17. Key Architectural Decisions (Locked) These are your “don’t rethink this at 2am” decisions: - ✅ tasks remain flat files in /tasks - ✅ kanban state = column in front matter - ✅ board config = board.md - ✅ ordering = sparse integers - ✅ git is first-class - ✅ PHP handles everything server-side - ✅ SortableJS is allowed - ❌ no folder-per-column model - ❌ no heavy framework