Files
openpylon/docs/plans/2026-02-16-15-improvements-design.md
Your Name 6bbf4c973b docs: add 15-improvements design doc and implementation plan
Also tracks Cargo.lock and BoardCardOverlay component from prior sessions.
2026-02-16 14:56:22 +02:00

297 lines
10 KiB
Markdown

# 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
```typescript
// 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
```typescript
// 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`: hidden
- `low`: blue
- `medium`: yellow
- `high`: orange
- `urgent`: 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**:
```typescript
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`:
1. Read current file as previous version
2. Write new board to `{boardId}.json`
3. Write previous version to `data/backups/{boardId}/{boardId}-{timestamp}.json`
4. 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.