Files
openpylon/docs/plans/2026-02-16-15-improvements-design.md
Your Name 8dedbf6032 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

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: 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.

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:

  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.