From c4966f5f6d6ddc24f39c4365a9f4a6d50e0976d6 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 15 Feb 2026 18:09:22 +0200 Subject: [PATCH] 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-openpylon-kanban-design.md | 345 ++++++++++++++++++ 1 file changed, 345 insertions(+) create mode 100644 docs/plans/2026-02-15-openpylon-kanban-design.md diff --git a/docs/plans/2026-02-15-openpylon-kanban-design.md b/docs/plans/2026-02-15-openpylon-kanban-design.md new file mode 100644 index 0000000..06a3d65 --- /dev/null +++ b/docs/plans/2026-02-15-openpylon-kanban-design.md @@ -0,0 +1,345 @@ +# 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-.json # One file per board +│ └── board-.json +└── attachments/ + └── board-/ # Copied attachments (when setting enabled) + └── -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; // 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`) 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-.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