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

13 KiB

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

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.

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