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
+23
View File
@@ -0,0 +1,23 @@
# IronKanban
I'm currently using this project <https://github.com/OlaProeis/ironPad> in a custom docker container I wrapped it in. While it works, and I like the flat-file git-backed nature of it, I'm not thrilled with the interaction pattern. I would like to keep the markdown powered (using the same format for the notes and projects if at all possible) git repo aspect of it, but build it out into a kanban-style interface. I would also like to move away from the stack IronPad is using, and move to something I'm more comfortable with.
I would prefer PHP with vanilla javascript as needed. That said, I would like to have the abilty to drag and drop cards between columns, and have the interface update in real(ish) time as changes are made. I would also like to have the ability to create new columns and cards, and have those changes reflected in the underlying markdown files. Each Project is currently built as a folder that is laid out like this:
```plain
project-name/
├── index.md
├── notes/
│ └── 20260314-154222.md
├── tasks/
│ ├── task-20260227-124827.md
│ ├── task-20260227-124901.md
│ ├── task-20260227-165334.md
│ └── task-20260314-154158.md
```
The index.md file contains the project description and any relevant information about the project. The optional notes folder contains any notes related to the project, and the tasks folder contains the individual tasks for the project.
The UI should be dark mode by default, and should be responsive. The columns should be able to be reordered, and the tasks within the columns should also be able to be reordered. The tasks should also be able to be moved between columns. The UI should also have a way to create new columns and tasks, and to edit existing ones. The UI should also have a way to delete columns and tasks, and to move tasks to a "trash" column before they are permanently deleted.
See `dev-spec.md` for more detailed specifications on the system architecture, request lifecycle, and filesystem contract.
+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