Files
IronKanban/Notes/dev-spec.md
Keith Solomon 812e5c2f2a feature: Initial MVP
2026-04-05 16:20:39 -05:00

636 lines
8.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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