Files
openpylon/docs/plans/2026-02-15-openpylon-kanban-design.md
Your Name c4966f5f6d Add OpenPylon Kanban board design document
Comprehensive design spec for a local-first Kanban desktop app
covering architecture, data model, UI design, accessibility,
import/export, and interaction patterns.
2026-02-15 18:09:22 +02:00

346 lines
13 KiB
Markdown

# OpenPylon — Local-First Kanban Board Design Document
**Date:** 2026-02-15
**Status:** Approved
## Overview
OpenPylon is a local-first Kanban board desktop app for personal projects and task management. No account required, no cloud sync — a fast, drag-and-drop board that saves to local JSON files. Replaces Trello ($5-10/mo), Asana ($11/mo), Monday.com ($9/mo).
## Tech Stack
- **Runtime:** Tauri (Rust backend, system webview, ~5MB bundle)
- **Frontend:** React + TypeScript
- **State:** Zustand (monolithic store per board, debounced JSON persistence)
- **Styling:** Tailwind CSS + shadcn/ui
- **Drag & Drop:** dnd-kit
- **Undo/Redo:** zundo (Zustand temporal middleware)
## Architecture: Monolithic State Store
Single Zustand store per board, loaded entirely into memory from JSON on open. All mutations go through the store and auto-save back to disk with debounced writes (500ms). Board data is small (even 500 cards is ~1MB of JSON), so full in-memory loading is fine.
---
## Data Model
### Directory Structure
```
~/.openpylon/
├── settings.json # Global app settings
├── boards/
│ ├── board-<ulid>.json # One file per board
│ └── board-<ulid>.json
└── attachments/
└── board-<ulid>/ # Copied attachments (when setting enabled)
└── <ulid>-filename.png
```
### Schema
```typescript
interface Board {
id: string; // ULID
title: string;
color: string; // Accent color stripe for board list
createdAt: string; // ISO 8601
updatedAt: string;
columns: Column[];
cards: Record<string, Card>; // Flat map, referenced by columns
labels: Label[]; // Board-level label definitions
settings: BoardSettings; // Per-board settings (attachment mode, etc.)
}
interface Column {
id: string;
title: string;
cardIds: string[]; // Ordered references
width: "narrow" | "standard" | "wide"; // Collapsible widths
}
interface Card {
id: string;
title: string;
description: string; // Markdown
labels: string[]; // Label IDs
checklist: ChecklistItem[];
dueDate: string | null;
attachments: Attachment[];
createdAt: string;
updatedAt: string;
}
interface Label {
id: string;
name: string;
color: string;
}
interface ChecklistItem {
id: string;
text: string;
checked: boolean;
}
interface Attachment {
id: string;
name: string;
path: string; // Absolute (link mode) or relative (copy mode)
mode: "link" | "copy";
}
```
**Key decisions:**
- ULIDs instead of UUIDs — sortable by creation time, no collisions
- Cards stored flat (`cards: Record<string, Card>`) with columns referencing via `cardIds[]` — drag-and-drop reordering is a simple array splice
- Labels defined at board level, referenced by ID on cards
---
## State Management & Persistence
### Stores
- `useBoardStore` — active board's full state + all mutation actions
- `useAppStore` — global app state: theme, recent boards, settings, current view
### Persistence Flow
1. Board open: Tauri `fs.readTextFile()` → parse JSON → validate with Zod → hydrate Zustand store
2. On mutation: store subscribes to itself, debounces writes at 500ms
3. On board close / app quit: immediate flush via Tauri `window.onCloseRequested`
### Auto-Backup
On every successful save, rotate previous version to `board-<ulid>.backup.json` (one backup per board).
### Undo/Redo
zundo (Zustand temporal middleware) tracks state history. Ctrl+Z / Ctrl+Shift+Z. Capped at ~50 steps.
### Search
Global search reads all board JSON files from disk and searches card titles + descriptions. For personal Kanban (5-20 boards), this is instant. No index needed.
---
## UI Design
### Aesthetic Direction: Industrial Utility with Warmth
"Pylon" evokes infrastructure and strength. The app should feel like a well-made tool — a carpenter's organized workshop, not an IKEA showroom.
### Color Palette (OKLCH)
**Light mode:**
- Background: `oklch(97% 0.005 80)` — warm off-white
- Surface/cards: `oklch(99% 0.003 80)` — barely-there warmth
- Column background: `oklch(95% 0.008 80)` — subtle sand
- Primary accent: `oklch(55% 0.12 160)` — muted teal-green
- Text primary: `oklch(25% 0.015 50)` — warm near-black
- Text secondary: `oklch(55% 0.01 50)` — warm gray
- Danger/overdue: `oklch(55% 0.18 25)` — terracotta red
**Dark mode:**
- Background: `oklch(18% 0.01 50)` — warm dark
- Surface: `oklch(22% 0.01 50)`
- Cards: `oklch(25% 0.012 50)`
### Typography
- **Headings:** Instrument Serif — heritage serif with personality
- **Body/cards:** Satoshi — clean geometric sans, readable at small sizes
- **Metadata (labels, dates, counts):** Geist Mono — reinforces "tool" identity
**Scale:**
- Board title: `clamp(1.25rem, 2vw, 1.5rem)`, bold
- Column headers: `0.8rem` uppercase, `letter-spacing: 0.08em`, weight 600
- Card titles: `0.875rem`, weight 500
- Card metadata: `0.75rem` monospace
### App Shell Layout
```
┌──────────────────────────────────────────────────────┐
│ ← Boards Sprint Planning ⌘K ⚙ │
├──────────────────────────────────────────────────────┤
│ │
│ TO DO IN PROGRESS DONE │
│ ───── 4 ─────────── 2 ──── 3 │
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Card title │ │ Card title │ │ Card title │ │
│ │ 🟢🔵 Feb28 ▮▮▯│ │ 🟢 ▮▮▮▮│ │ ▮▮▯ │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ + Add card + Add card + Add card │
│ │
└──────────────────────────────────────────────────────┘
```
**Key layout decisions:**
- No vertical column dividers — whitespace gaps (24-32px) instead
- Column headers: uppercase, tracked-out, small — like section dividers
- Card count as quiet number beside underline, not a badge
- Command palette (`Ctrl+K`) replaces search icon
- Theme toggle lives in settings, not top bar
- Board title is click-to-edit inline, no `[edit]` button
### Card Design
- **Label dots:** 8px colored circles in a row, hover for tooltip with name
- **Due date:** Monospace, right-aligned, no icon. Overdue turns terracotta with subtle tint.
- **Checklist:** Tiny progress bar (filled/unfilled blocks), not "3/4" text
- **No card borders.** Subtle shadow (`0 1px 3px oklch(0% 0 0 / 0.06)`) for separation.
- **Hover:** `translateY(-1px)` lift with faint shadow deepening, spring physics, 150ms
- **Drag ghost:** 5-degree rotation, `scale(1.03)`, `opacity(0.9)`, elevated shadow
### Column Widths
Columns support three widths: narrow (titles only), standard, wide (active focus). Double-click header to cycle. Adds spatial meaning.
### Card Detail Modal (Two-Panel)
```
┌──────────────────────────────────────────────────────┐
│ Fix auth token refresh ✕ │
│ │
│ ┌─────────────────────────┐ ┌────────────────────┐ │
│ │ │ │ LABELS │ │
│ │ Markdown description │ │ 🟢 Bug 🔵 Backend │ │
│ │ with live preview │ │ │ │
│ │ │ │ DUE DATE │ │
│ │ │ │ Feb 28, 2026 │ │
│ │ │ │ │ │
│ │ │ │ CHECKLIST 3/4 │ │
│ │ │ │ ✓ Research APIs │ │
│ │ │ │ ✓ Write tests │ │
│ │ │ │ ✓ Implement │ │
│ │ │ │ ○ Code review │ │
│ │ │ │ │ │
│ │ │ │ ATTACHMENTS │ │
│ │ │ │ spec.pdf │ │
│ └─────────────────────────┘ └────────────────────┘ │
└──────────────────────────────────────────────────────┘
```
- Left panel (60%): Title (inline edit) + markdown description (edit/preview toggle)
- Right sidebar (40%): Labels, due date, checklist, attachments. Each collapsible.
- No save button — auto-persist with subtle "Saved" indicator
- **Card-to-modal morph animation** via Framer Motion `layoutId` — modal grows from card position
### Command Palette (`Ctrl+K`)
Using shadcn's `cmdk` component:
- Search all cards across all boards by title/description
- Switch between boards
- Create new cards/boards
- Toggle theme
- Open settings
- Navigate to specific column
- Filter current board by label/date
### Board List (Home Screen)
Grid of board cards with:
- Color accent stripe at top (user-chosen per board)
- Title, card count, column count
- Relative time ("2 min ago", "Yesterday")
- Right-click context menu: Duplicate, Export, Delete, Change color
- Empty state: "Create your first board" + single button
---
## Keyboard Shortcuts
### Global
| Action | Shortcut |
|---|---|
| Command palette | `Ctrl+K` |
| New card in focused column | `N` |
| New board | `Ctrl+N` |
| Undo | `Ctrl+Z` |
| Redo | `Ctrl+Shift+Z` |
| Settings | `Ctrl+,` |
| Close modal / cancel | `Escape` |
| Save & close card detail | `Ctrl+Enter` |
### Board Navigation
- `Arrow Left/Right` — focus prev/next column
- `Arrow Up/Down` — focus prev/next card in column
- `Enter` — open focused card detail
- `Space` — quick-toggle first unchecked checklist item
- `D` — set/edit due date on focused card
- `L` — open label picker on focused card
### Drag-and-Drop Keyboard
dnd-kit keyboard sensor: `Space` to pick up, arrows to move, `Space` to drop, `Escape` to cancel. Movements announced via `aria-live` region.
---
## Accessibility
- All interactive elements reachable via Tab
- Focus indicators: `2px solid` accent color, `2px offset`, visible in both themes
- Modal focus trapping
- Column/card counts via `aria-label`
- `prefers-reduced-motion`: all animations collapse to instant
- `prefers-contrast`: increased shadow intensity, subtle borders restored
- Minimum touch target: 44x44px on all buttons
---
## Import/Export
### Export
- **JSON:** The board file itself is the export. Save As dialog.
- **CSV:** Flattened — one row per card with all fields.
- **ZIP:** For boards with copy-mode attachments — board JSON + attachments folder.
### Import
- **OpenPylon JSON:** Drop file onto board list or use File > Import. Schema validation + preview before importing.
- **CSV:** Import wizard — map columns, preview rows, choose target board.
- **Trello JSON:** Dedicated adapter mapping Trello schema to OpenPylon.
- **Drag-and-drop import:** Dropping `.json` or `.csv` anywhere triggers import flow.
---
## Error Handling
- **Corrupted board file:** Recovery dialog — inspect in file explorer or restore from `.backup.json`
- **Data directory inaccessible:** Dialog to choose new directory on startup
- **Disk full:** Inline toast, changes preserved in memory, retry every 30s
- **File locked:** Warning dialog
- **Schema migration:** On load, validate with Zod, add missing fields with defaults, preserve unknown fields
- **Drag edge cases:** Empty column droppable, drop outside cancels with spring return
## Micro-Interactions
| Interaction | Animation | Duration |
|---|---|---|
| Card appears (new) | Fade in + slide down | 200ms, spring |
| Card drag start | Lift + rotate + shadow | 150ms |
| Card drop | Settle with slight bounce | 250ms, spring |
| Column add | Slide in from right | 300ms |
| Card detail open | Morph from card position | 250ms |
| Card detail close | Reverse morph to card | 200ms |
| Checklist check | Strikethrough sweep + fill | 200ms |
| Board switch | Crossfade | 300ms |
All animations respect `prefers-reduced-motion`.
---
## Empty States
- **No boards:** "Create your first board" + button + minimal illustration
- **Empty column:** Dashed border area + "Drag cards here or click + to add"
- **No search results:** "No matches" + suggestion to broaden
- **No labels:** "Create your first label" + color swatches