10 KiB
OpenPylon: 15 Improvements Design
Overview
15 improvements to OpenPylon organized into 5 phases (0-4), designed for incremental delivery. Each phase builds on the previous. You can ship after any phase and have a coherent improvement.
Decisions
- Phasing: 4 phases (quick wins first, progressively bigger features)
- Data compatibility: New fields use Zod
.default()values. No migration code. Old boards load cleanly. - Templates storage: JSON files in
data/templates/ - Backup storage: Timestamped files in
data/backups/{boardId}/, keep last 10
Phase 0: Data Model Foundation
All type/schema changes that later features depend on. Done first so everything builds on a stable base.
Card type additions
// Added to Card interface + cardSchema
priority: "none" | "low" | "medium" | "high" | "urgent"; // default: "none"
comments: Comment[]; // default: []
// New type + schema
interface Comment {
id: string;
text: string;
createdAt: string;
}
Column type additions
// Added to Column interface + columnSchema
collapsed: boolean; // default: false
wipLimit: number | null; // default: null
Files touched
src/types/board.ts— Add fields to Card, Column interfaces. Add Comment interface.src/lib/schemas.ts— Add Zod fields with defaults to cardSchema, columnSchema. Add commentSchema.
Phase 1: Quick Wins
Minimal changes, high value. ~4 files each.
#8 — Consume defaultColumnWidth Setting
In board-store.ts, addColumn reads useAppStore.getState().settings.defaultColumnWidth instead of hardcoding "standard".
Files: src/stores/board-store.ts (1 line change)
#4 — Due Date Visual Indicators
Replace binary overdue/not logic in CardThumbnail with 4-tier color system:
| Status | Condition | Color |
|---|---|---|
| Overdue | past + not today | pylon-danger (red) |
| Approaching | due within 2 days | amber oklch(65% 0.15 70) |
| Comfortable | due but >2 days | green oklch(55% 0.12 145) |
| No date | null | pylon-text-secondary (gray) |
Helper function getDueDateStatus(dueDate: string | null) returns { color, label }.
Files: src/components/board/CardThumbnail.tsx
#9 — Card Aging Visualization
Compute days since card.updatedAt. Apply opacity:
| Days stale | Opacity |
|---|---|
| 0-7 | 1.0 |
| 7-14 | 0.85 |
| 14-30 | 0.7 |
| 30+ | 0.55 |
Applied as inline opacity on the card motion.button.
Files: src/components/board/CardThumbnail.tsx
#12 — Open Attachments
Add "Open" button to each attachment in AttachmentSection. Uses open() from @tauri-apps/plugin-opener (already registered).
Files: src/components/card-detail/AttachmentSection.tsx
Phase 2: Card Interactions & UI Enhancements
5 features that transform how cards feel to use.
#2 — Card Priority Levels
Thumbnail indicator: Colored dot in footer row. Color map:
none: hiddenlow: bluemedium: yellowhigh: orangeurgent: red with pulse animation
Detail modal: Priority picker section in left column (like LabelPicker). Row of 5 clickable chips with colors.
Files: CardThumbnail.tsx, CardDetailModal.tsx (new PriorityPicker component inline or separate), board-store.ts (no new action needed — updateCard handles it)
#5 — Card Context Menu
Wrap CardThumbnail in Radix ContextMenu.
Menu items:
- Move to → (submenu listing columns except current)
- Set priority → (submenu with 5 options)
- Duplicate (new card with same fields,
(copy)suffix, new ID, inserted below original) - Separator
- Delete (confirmation dialog)
New store action: duplicateCard(cardId): string — clones card, inserts after original in same column.
Files: CardThumbnail.tsx, board-store.ts
#10 — WIP Limits
Column header display: Shows 3/5 when wipLimit set. Background tint:
- Under limit: normal
- At limit: amber tint
oklch(75% 0.08 70 / 15%) - Over limit: red tint
oklch(70% 0.08 25 / 15%)
Setting UI: New "Set WIP Limit" item in ColumnHeader dropdown menu. Preset choices: None / 3 / 5 / 7 / 10 / Custom.
New store action: setColumnWipLimit(columnId: string, limit: number | null)
Files: ColumnHeader.tsx, KanbanColumn.tsx, board-store.ts
#3 — Column Collapse/Expand
When collapsed, render a 40px-wide strip instead of full column:
- Vertical text via
writing-mode: vertical-rl; rotate: 180deg - Card count badge
- Click to expand
Animate width from full to 40px using existing animate={{ width }} on outer motion.div with springs.bouncy.
New store action: toggleColumnCollapse(columnId: string)
Collapse button: Added to ColumnHeader dropdown menu + a small chevron icon on the collapsed strip.
Files: KanbanColumn.tsx, ColumnHeader.tsx, board-store.ts
#11 — Checklist Item Reordering
Wrap checklist <ul> in ChecklistSection with DndContext + SortableContext (vertical strategy). Each ChecklistRow becomes sortable.
Drag handle: GripVertical icon on left of each item, visible on hover.
Drop indicator: Horizontal glow line (same as card drag — vertical list so horizontal line is correct).
New store action: reorderChecklistItems(cardId: string, fromIndex: number, toIndex: number)
Files: ChecklistSection.tsx, board-store.ts
Phase 3: Navigation & Power User Features
Features that make power users fall in love.
#1 — Card Filtering & Quick Search
Filter bar: Slides down below TopBar. Triggered by filter icon in TopBar or / keyboard shortcut.
Filter controls (horizontal row):
- Text input (debounced 200ms, title search)
- Label multi-select dropdown (ANY match)
- Due date dropdown: All / Overdue / Due this week / Due today / No date
- Priority dropdown: All / Urgent / High / Medium / Low
- Clear all button
State: Local state in BoardView (not persisted — filters are ephemeral).
Rendering: KanbanColumn receives filtered card IDs. Non-matching cards fade out. Column counts show 3 of 7 when filtering.
Files: New FilterBar.tsx component, BoardView.tsx, KanbanColumn.tsx, TopBar.tsx (filter button)
#7 — Keyboard Card Navigation
State: focusedCardId in BoardView local state.
Key bindings (when no input/textarea focused):
| Key | Action |
|---|---|
J / ArrowDown |
Focus next card in column |
K / ArrowUp |
Focus previous card in column |
H / ArrowLeft |
Focus same-index card in previous column |
L / ArrowRight |
Focus same-index card in next column |
Enter |
Open focused card detail |
Escape |
Clear focus / close modal |
Visual: Focused card gets ring-2 ring-pylon-accent ring-offset-2. Column auto-scrolls via scrollIntoView({ block: "nearest" }).
Implementation: useKeyboardNavigation hook. Passes isFocused through KanbanColumn to CardThumbnail.
Files: New useKeyboardNavigation.ts hook, BoardView.tsx, KanbanColumn.tsx, CardThumbnail.tsx
#6 — Desktop Notifications for Due Dates
Plugin: Add tauri-plugin-notification to Cargo.toml and capabilities.
Trigger: On useAppStore.init(), after loading boards, scan all cards:
- Cards due today → "You have X cards due today"
- Cards overdue → "You have X overdue cards"
Batched (one notification per category). Store lastNotificationCheck in settings to skip if checked within last hour.
Files: src-tauri/Cargo.toml, src-tauri/capabilities/default.json, src/stores/app-store.ts, src/types/settings.ts (add lastNotificationCheck), src/lib/schemas.ts
#13 — Card Comments / Activity Log
UI in CardDetailModal: New section in right column below description.
- Scrollable comment list (newest first)
- Each: text, relative timestamp, delete button (hover)
- Add input: textarea + "Add" button. Enter submits, Shift+Enter newline.
Store actions: addComment(cardId, text), deleteComment(cardId, commentId). Comments get ULID IDs and createdAt.
Files: New CommentsSection.tsx, CardDetailModal.tsx, board-store.ts
Phase 4: System Features & Infrastructure
Deeper features touching storage and templates.
#14 — Board Templates & Saved Structures
Template type:
interface BoardTemplate {
id: string;
name: string;
color: string;
columns: { title: string; width: ColumnWidth; color: string | null; wipLimit: number | null }[];
labels: Label[];
settings: BoardSettings;
}
Saving: Context menu item on BoardCard — "Save as Template". Prompts for name. Strips cards/timestamps. Writes to data/templates/{id}.json.
Creating: NewBoardDialog shows built-in templates (Blank, Kanban, Sprint) + user templates below a separator. Delete button (X) on user templates.
Storage functions: listTemplates(), saveTemplate(), deleteTemplate() in storage.ts. board-factory.ts gets createBoardFromTemplate().
Files: storage.ts, board-factory.ts, NewBoardDialog.tsx, BoardCard.tsx, new src/types/template.ts
#15 — Auto-Backup & Version History
Storage: data/backups/{boardId}/ directory. Timestamped files: {boardId}-{ISO timestamp}.json.
Save flow in board-store.ts:
- Read current file as previous version
- Write new board to
{boardId}.json - Write previous version to
data/backups/{boardId}/{boardId}-{timestamp}.json - Prune backups beyond 10
UI — Version History dialog: Accessible from board settings dropdown menu ("Version History"). Shows:
- List of backups sorted newest-first
- Each entry: relative timestamp, card count, column count
- "Restore" button with confirmation dialog
- Current board auto-backed-up before restore (restore is reversible)
Storage functions: listBackups(boardId), restoreBackup(boardId, filename), pruneBackups(boardId, keep).
Files: storage.ts, board-store.ts, new VersionHistoryDialog.tsx, TopBar.tsx (menu item)
Dependency Graph
Phase 0 (data model)
└── Phase 1 (quick wins) — no deps on Phase 0 except #8
└── Phase 2 (card interactions) — needs priority + collapsed + wipLimit from Phase 0
└── Phase 3 (power user) — needs priority for filtering, context menu patterns from Phase 2
└── Phase 4 (infrastructure) — needs wipLimit in templates from Phase 0+2
Phase 1 can actually run in parallel with Phase 0 since its features don't touch the new fields. Phases 2-4 are strictly sequential.