# 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