feature: Initial MVP

This commit is contained in:
Keith Solomon
2026-04-05 16:20:39 -05:00
parent 3af0b9cd0f
commit 812e5c2f2a
60 changed files with 5917 additions and 5 deletions
+635
View File
@@ -0,0 +1,635 @@
# 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 “dont 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