diff --git a/docs/plans/2026-02-26-ui-redesign-plan.md b/docs/plans/2026-02-26-ui-redesign-plan.md new file mode 100644 index 0000000..861524a --- /dev/null +++ b/docs/plans/2026-02-26-ui-redesign-plan.md @@ -0,0 +1,1007 @@ +# UI Redesign Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Transform the TaskTracker web UI from a generic dark dashboard into a Linear/Vercel-inspired premium interface with top navigation, refined kanban cards, muted color system, command-K search, and frosted detail panel. + +**Architecture:** Replace the sidebar layout with a horizontal top nav. Centralize all colors into CSS custom properties in `index.css`. Restyle every component with the new muted palette, subtle effects (noise texture, gradient glows, backdrop blur), and improved typography hierarchy. No API or backend changes. + +**Tech Stack:** React 19, Tailwind CSS 4, Vite 7, dnd-kit, Recharts, Lucide React. Add `framer-motion` for panel/search animations. + +--- + +### Task 1: Install framer-motion and update CSS foundation + +**Files:** +- Modify: `TaskTracker.Web/package.json` +- Modify: `TaskTracker.Web/src/index.css` +- Modify: `TaskTracker.Web/src/lib/constants.ts` +- Modify: `TaskTracker.Web/index.html` + +**Step 1: Install framer-motion** + +Run: `cd TaskTracker.Web && npm install framer-motion` + +**Step 2: Update `index.html` title** + +Change the `` from `tasktracker-web` to `TaskTracker`. + +**Step 3: Replace `index.css` with the new design foundation** + +Replace the entire contents of `src/index.css` with: + +```css +@import "tailwindcss"; + +@theme { + --font-sans: 'Inter', system-ui, sans-serif; + --color-page: #0a0a0f; + --color-surface: #12131a; + --color-elevated: #1a1b26; + --color-border: rgba(255, 255, 255, 0.06); + --color-border-hover: rgba(255, 255, 255, 0.12); + --color-text-primary: #e2e8f0; + --color-text-secondary: #64748b; + --color-text-tertiary: #334155; + --color-accent: #8b5cf6; + --color-accent-end: #6366f1; + --color-status-active: #3b82f6; + --color-status-paused: #eab308; + --color-status-completed: #22c55e; + --color-status-pending: #64748b; +} + +/* Noise grain texture */ +body::before { + content: ''; + position: fixed; + inset: 0; + z-index: 9999; + pointer-events: none; + opacity: 0.03; + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E"); + background-repeat: repeat; + background-size: 256px 256px; +} + +/* Active task pulse */ +@keyframes pulse-glow { + 0%, 100% { + box-shadow: 0 0 6px rgba(59, 130, 246, 0.3); + } + 50% { + box-shadow: 0 0 16px rgba(59, 130, 246, 0.5); + } +} + +.animate-pulse-glow { + animation: pulse-glow 2.5s ease-in-out infinite; +} + +/* Live dot pulse */ +@keyframes live-dot { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.4; } +} + +.animate-live-dot { + animation: live-dot 1.5s ease-in-out infinite; +} + +/* Card hover glow border */ +.card-glow { + position: relative; +} + +.card-glow::before { + content: ''; + position: absolute; + inset: -1px; + border-radius: inherit; + background: linear-gradient(135deg, rgba(139, 92, 246, 0.2), rgba(99, 102, 241, 0.1), transparent); + opacity: 0; + transition: opacity 0.2s ease; + z-index: -1; + pointer-events: none; +} + +.card-glow:hover::before { + opacity: 1; +} + +/* Custom scrollbar */ +::-webkit-scrollbar { + width: 6px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: #1a1b26; + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: #2a2d37; +} + +/* Selection color */ +::selection { + background: rgba(139, 92, 246, 0.3); +} +``` + +**Step 4: Update `constants.ts` with new status colors** + +Replace the entire contents of `src/lib/constants.ts` with: + +```typescript +export const COLUMN_CONFIG = [ + { status: 'Pending' as const, label: 'Pending', color: '#64748b' }, + { status: 'Active' as const, label: 'Active', color: '#3b82f6' }, + { status: 'Paused' as const, label: 'Paused', color: '#eab308' }, + { status: 'Completed' as const, label: 'Completed', color: '#22c55e' }, +] as const + +export const CATEGORY_COLORS: Record<string, string> = { + Development: '#6366f1', + Research: '#06b6d4', + Communication: '#8b5cf6', + DevOps: '#f97316', + Documentation: '#14b8a6', + Design: '#ec4899', + Testing: '#3b82f6', + General: '#64748b', + Email: '#f59e0b', + Engineering: '#6366f1', + LaserCutting: '#ef4444', + Unknown: '#475569', +} +``` + +**Step 5: Commit** + +```bash +git add -A +git commit -m "feat(ui): add design foundation — CSS tokens, noise texture, card glow effects" +``` + +--- + +### Task 2: Replace sidebar with top navigation bar + +**Files:** +- Rewrite: `TaskTracker.Web/src/components/Layout.tsx` + +**Step 1: Replace `Layout.tsx` entirely** + +```tsx +import { NavLink, Outlet, useNavigate } from 'react-router-dom' +import { LayoutGrid, BarChart3, Link, Plus, Search } from 'lucide-react' +import { useState, useEffect, useCallback } from 'react' +import SearchModal from './SearchModal.tsx' + +const navItems = [ + { to: '/board', label: 'Board', icon: LayoutGrid }, + { to: '/analytics', label: 'Analytics', icon: BarChart3 }, + { to: '/mappings', label: 'Mappings', icon: Link }, +] + +export default function Layout() { + const navigate = useNavigate() + const [searchOpen, setSearchOpen] = useState(false) + const [showCreateHint, setShowCreateHint] = useState(false) + + // Global Cmd+K handler + const handleGlobalKeyDown = useCallback((e: KeyboardEvent) => { + if ((e.metaKey || e.ctrlKey) && e.key === 'k') { + e.preventDefault() + setSearchOpen(true) + } + }, []) + + useEffect(() => { + document.addEventListener('keydown', handleGlobalKeyDown) + return () => document.removeEventListener('keydown', handleGlobalKeyDown) + }, [handleGlobalKeyDown]) + + return ( + <div className="flex flex-col h-screen bg-[var(--color-page)] text-[var(--color-text-primary)] overflow-hidden"> + {/* Top navigation bar */} + <header className="flex items-center h-12 px-4 border-b border-[var(--color-border)] shrink-0 bg-[var(--color-page)]"> + {/* Logo */} + <div className="flex items-center gap-2 mr-8"> + <div className="w-5 h-5 rounded bg-gradient-to-br from-[var(--color-accent)] to-[var(--color-accent-end)] flex items-center justify-center"> + <span className="text-[10px] font-bold text-white">T</span> + </div> + <span className="text-sm font-semibold tracking-tight">TaskTracker</span> + </div> + + {/* Nav tabs */} + <nav className="flex items-center gap-1"> + {navItems.map(({ to, label, icon: Icon }) => ( + <NavLink + key={to} + to={to} + className={({ isActive }) => + `flex items-center gap-1.5 px-3 py-1.5 rounded-md text-[13px] font-medium transition-colors ${ + isActive + ? 'text-white bg-white/[0.08]' + : 'text-[var(--color-text-secondary)] hover:text-white hover:bg-white/[0.04]' + }` + } + > + <Icon size={15} /> + {label} + </NavLink> + ))} + </nav> + + {/* Spacer */} + <div className="flex-1" /> + + {/* Search trigger */} + <button + onClick={() => setSearchOpen(true)} + className="flex items-center gap-2 h-7 px-2.5 rounded-md text-[12px] text-[var(--color-text-secondary)] bg-white/[0.04] border border-[var(--color-border)] hover:border-[var(--color-border-hover)] hover:text-[var(--color-text-primary)] transition-colors mr-2" + > + <Search size={13} /> + <span className="hidden sm:inline">Search</span> + <kbd className="hidden sm:inline text-[10px] font-mono text-[var(--color-text-tertiary)] bg-white/[0.06] px-1 py-0.5 rounded"> + Ctrl K + </kbd> + </button> + + {/* New task button */} + <button + onClick={() => { + navigate('/board') + setShowCreateHint(true) + setTimeout(() => setShowCreateHint(false), 100) + }} + className="flex items-center gap-1 h-7 px-2.5 rounded-md text-[12px] font-medium text-white bg-gradient-to-r from-[var(--color-accent)] to-[var(--color-accent-end)] hover:brightness-110 transition-all" + > + <Plus size={14} /> + <span className="hidden sm:inline">New Task</span> + </button> + </header> + + {/* Content */} + <main className="flex-1 overflow-auto p-5"> + <Outlet context={{ showCreateHint }} /> + </main> + + {/* Search modal */} + {searchOpen && ( + <SearchModal + onSelect={(taskId) => { + setSearchOpen(false) + navigate(`/board?task=${taskId}`) + }} + onClose={() => setSearchOpen(false)} + /> + )} + </div> + ) +} +``` + +**Step 2: Commit** + +```bash +git add -A +git commit -m "feat(ui): replace sidebar with slim top navigation bar" +``` + +--- + +### Task 3: Create Command-K search modal + +**Files:** +- Create: `TaskTracker.Web/src/components/SearchModal.tsx` +- Delete logic from: `TaskTracker.Web/src/components/SearchBar.tsx` (this file will be deleted) + +**Step 1: Create `SearchModal.tsx`** + +```tsx +import { useState, useRef, useEffect, useCallback } from 'react' +import { Search, ArrowRight } from 'lucide-react' +import { motion, AnimatePresence } from 'framer-motion' +import { useTasks } from '../api/tasks.ts' +import { CATEGORY_COLORS, COLUMN_CONFIG } from '../lib/constants.ts' +import type { WorkTask } from '../types/index.ts' + +interface SearchModalProps { + onSelect: (taskId: number) => void + onClose: () => void +} + +export default function SearchModal({ onSelect, onClose }: SearchModalProps) { + const { data: tasks } = useTasks() + const [query, setQuery] = useState('') + const [selectedIndex, setSelectedIndex] = useState(0) + const inputRef = useRef<HTMLInputElement>(null) + + useEffect(() => { + inputRef.current?.focus() + }, []) + + // Filter tasks + const results: WorkTask[] = (() => { + if (!tasks) return [] + if (!query.trim()) { + // Show recent/active tasks when no query + return tasks + .filter((t) => t.status === 'Active' || t.status === 'Paused' || t.status === 'Pending') + .slice(0, 8) + } + const q = query.toLowerCase() + return tasks + .filter( + (t) => + t.title.toLowerCase().includes(q) || + (t.description && t.description.toLowerCase().includes(q)) || + (t.category && t.category.toLowerCase().includes(q)) + ) + .slice(0, 8) + })() + + useEffect(() => { + setSelectedIndex(0) + }, [query]) + + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + switch (e.key) { + case 'ArrowDown': + e.preventDefault() + setSelectedIndex((prev) => Math.min(prev + 1, results.length - 1)) + break + case 'ArrowUp': + e.preventDefault() + setSelectedIndex((prev) => Math.max(prev - 1, 0)) + break + case 'Enter': + e.preventDefault() + if (results[selectedIndex]) { + onSelect(results[selectedIndex].id) + } + break + case 'Escape': + e.preventDefault() + onClose() + break + } + }, + [results, selectedIndex, onSelect, onClose] + ) + + const getStatusColor = (status: string) => { + const col = COLUMN_CONFIG.find((c) => c.status === status) + return col ? col.color : '#64748b' + } + + return ( + <AnimatePresence> + <motion.div + className="fixed inset-0 z-50 flex items-start justify-center pt-[20vh]" + initial={{ opacity: 0 }} + animate={{ opacity: 1 }} + exit={{ opacity: 0 }} + transition={{ duration: 0.15 }} + > + {/* Backdrop */} + <div + className="absolute inset-0 bg-black/60 backdrop-blur-sm" + onClick={onClose} + /> + + {/* Modal */} + <motion.div + className="relative w-full max-w-lg bg-[var(--color-elevated)] border border-[var(--color-border)] rounded-xl shadow-2xl shadow-black/50 overflow-hidden" + initial={{ opacity: 0, scale: 0.95, y: -10 }} + animate={{ opacity: 1, scale: 1, y: 0 }} + exit={{ opacity: 0, scale: 0.95, y: -10 }} + transition={{ duration: 0.15 }} + > + {/* Search input */} + <div className="flex items-center gap-3 px-4 py-3 border-b border-[var(--color-border)]"> + <Search size={16} className="text-[var(--color-text-secondary)] shrink-0" /> + <input + ref={inputRef} + type="text" + value={query} + onChange={(e) => setQuery(e.target.value)} + onKeyDown={handleKeyDown} + placeholder="Search tasks..." + className="flex-1 bg-transparent text-[var(--color-text-primary)] text-sm placeholder-[var(--color-text-tertiary)] outline-none" + /> + <kbd className="text-[10px] font-mono text-[var(--color-text-tertiary)] bg-white/[0.06] px-1.5 py-0.5 rounded border border-[var(--color-border)]"> + ESC + </kbd> + </div> + + {/* Results */} + {results.length > 0 ? ( + <div className="max-h-[300px] overflow-y-auto py-1"> + {!query.trim() && ( + <div className="px-4 py-1.5 text-[10px] font-medium uppercase tracking-wider text-[var(--color-text-tertiary)]"> + Recent tasks + </div> + )} + {results.map((task, index) => { + const categoryColor = CATEGORY_COLORS[task.category ?? 'Unknown'] ?? CATEGORY_COLORS['Unknown'] + const statusColor = getStatusColor(task.status) + + return ( + <button + key={task.id} + onClick={() => onSelect(task.id)} + onMouseEnter={() => setSelectedIndex(index)} + className={`w-full flex items-center gap-3 px-4 py-2.5 text-left transition-colors ${ + index === selectedIndex ? 'bg-white/[0.06]' : '' + }`} + > + {/* Status dot */} + <span + className="shrink-0 w-2 h-2 rounded-full" + style={{ backgroundColor: statusColor }} + /> + + {/* Title */} + <span className="flex-1 text-sm text-[var(--color-text-primary)] truncate"> + {task.title} + </span> + + {/* Category */} + {task.category && ( + <span + className="shrink-0 text-[10px] font-medium px-1.5 py-0.5 rounded" + style={{ + backgroundColor: categoryColor + '15', + color: categoryColor, + }} + > + {task.category} + </span> + )} + + {/* Arrow hint on selected */} + {index === selectedIndex && ( + <ArrowRight size={12} className="shrink-0 text-[var(--color-text-tertiary)]" /> + )} + </button> + ) + })} + </div> + ) : query.trim() ? ( + <div className="px-4 py-8 text-center text-sm text-[var(--color-text-secondary)]"> + No tasks found + </div> + ) : null} + + {/* Footer */} + <div className="flex items-center gap-4 px-4 py-2 border-t border-[var(--color-border)] text-[10px] text-[var(--color-text-tertiary)]"> + <span className="flex items-center gap-1"> + <kbd className="font-mono bg-white/[0.06] px-1 py-0.5 rounded">↑↓</kbd> + Navigate + </span> + <span className="flex items-center gap-1"> + <kbd className="font-mono bg-white/[0.06] px-1 py-0.5 rounded">↵</kbd> + Open + </span> + </div> + </motion.div> + </motion.div> + </AnimatePresence> + ) +} +``` + +**Step 2: Delete `SearchBar.tsx`** + +Run: `rm TaskTracker.Web/src/components/SearchBar.tsx` + +**Step 3: Commit** + +```bash +git add -A +git commit -m "feat(ui): add Command-K search modal, remove inline search bar" +``` + +--- + +### Task 4: Redesign kanban columns and task cards + +**Files:** +- Rewrite: `TaskTracker.Web/src/components/KanbanColumn.tsx` +- Rewrite: `TaskTracker.Web/src/components/TaskCard.tsx` +- Modify: `TaskTracker.Web/src/components/KanbanBoard.tsx` + +**Step 1: Rewrite `TaskCard.tsx`** + +```tsx +import { useSortable } from '@dnd-kit/sortable' +import { CSS } from '@dnd-kit/utilities' +import { Clock } from 'lucide-react' +import { WorkTaskStatus } from '../types/index.ts' +import type { WorkTask } from '../types/index.ts' +import { CATEGORY_COLORS } from '../lib/constants.ts' + +function formatElapsed(task: WorkTask): string | null { + if (!task.startedAt) return null + const start = new Date(task.startedAt).getTime() + const end = task.completedAt ? new Date(task.completedAt).getTime() : Date.now() + const mins = Math.floor((end - start) / 60_000) + if (mins < 60) return `${mins}m` + const hours = Math.floor(mins / 60) + const remainder = mins % 60 + if (hours < 24) return `${hours}h ${remainder}m` + const days = Math.floor(hours / 24) + return `${days}d ${hours % 24}h` +} + +interface TaskCardProps { + task: WorkTask + onClick: (id: number) => void +} + +export default function TaskCard({ task, onClick }: TaskCardProps) { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id: task.id }) + + const style = { + transform: CSS.Transform.toString(transform), + transition, + opacity: isDragging ? 0.4 : 1, + scale: isDragging ? '1.02' : '1', + } + + const categoryColor = CATEGORY_COLORS[task.category ?? 'Unknown'] ?? CATEGORY_COLORS['Unknown'] + const isActive = task.status === WorkTaskStatus.Active + const elapsed = formatElapsed(task) + + const completedSubTasks = task.subTasks?.filter( + (s) => s.status === WorkTaskStatus.Completed + ).length ?? 0 + const totalSubTasks = task.subTasks?.length ?? 0 + + return ( + <div + ref={setNodeRef} + style={style} + {...attributes} + {...listeners} + onClick={() => onClick(task.id)} + className={` + card-glow rounded-xl cursor-grab active:cursor-grabbing + bg-[var(--color-surface)] border transition-all duration-200 + hover:-translate-y-0.5 + ${isActive + ? 'border-[color:var(--color-status-active)]/30 animate-pulse-glow' + : 'border-[var(--color-border)] hover:border-[var(--color-border-hover)]' + } + ${isDragging ? 'shadow-xl shadow-black/40' : ''} + `} + > + <div className="px-3.5 py-3"> + {/* Title row */} + <div className="flex items-start gap-2 mb-1.5"> + {isActive && ( + <span className="shrink-0 mt-1.5 w-1.5 h-1.5 rounded-full bg-[var(--color-status-active)] animate-live-dot" /> + )} + <p className="text-[13px] font-medium text-[var(--color-text-primary)] leading-snug flex-1"> + {task.title} + </p> + </div> + + {/* Meta row */} + <div className="flex items-center gap-2 text-[11px] text-[var(--color-text-secondary)]"> + {task.category && ( + <span className="flex items-center gap-1"> + <span + className="w-1.5 h-1.5 rounded-full" + style={{ backgroundColor: categoryColor }} + /> + {task.category} + </span> + )} + + {elapsed && ( + <span className="flex items-center gap-1"> + <Clock size={10} /> + {elapsed} + </span> + )} + + {totalSubTasks > 0 && ( + <span className="ml-auto flex items-center gap-1"> + {Array.from({ length: totalSubTasks }, (_, i) => ( + <span + key={i} + className={`w-1 h-1 rounded-full ${ + i < completedSubTasks + ? 'bg-[var(--color-status-completed)]' + : 'bg-white/10' + }`} + /> + ))} + <span className="ml-0.5">{completedSubTasks}/{totalSubTasks}</span> + </span> + )} + </div> + </div> + </div> + ) +} +``` + +**Step 2: Rewrite `KanbanColumn.tsx`** + +```tsx +import { useState } from 'react' +import { useDroppable } from '@dnd-kit/core' +import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable' +import { Plus } from 'lucide-react' +import { WorkTaskStatus, type WorkTask } from '../types/index.ts' +import TaskCard from './TaskCard.tsx' +import CreateTaskForm from './CreateTaskForm.tsx' + +interface KanbanColumnProps { + status: string + label: string + color: string + tasks: WorkTask[] + onTaskClick: (id: number) => void + onAddTask?: () => void +} + +export default function KanbanColumn({ + status, + label, + color, + tasks, + onTaskClick, +}: KanbanColumnProps) { + const { setNodeRef, isOver } = useDroppable({ id: `column-${status}` }) + const [showForm, setShowForm] = useState(false) + + const taskIds = tasks.map((t) => t.id) + + return ( + <div className="flex flex-col min-h-[300px]"> + {/* Column header */} + <div className="mb-3"> + <div className="flex items-center gap-2 mb-2"> + <h2 className="text-[11px] font-semibold uppercase tracking-wider text-[var(--color-text-secondary)]"> + {label} + </h2> + <span className="text-[11px] text-[var(--color-text-tertiary)]"> + {tasks.length} + </span> + </div> + <div className="h-[2px] rounded-full" style={{ backgroundColor: color }} /> + </div> + + {/* Cards area */} + <div + ref={setNodeRef} + className={`flex-1 flex flex-col gap-2 rounded-lg transition-colors duration-200 py-1 ${ + isOver ? 'bg-white/[0.02]' : '' + }`} + > + <SortableContext items={taskIds} strategy={verticalListSortingStrategy}> + {tasks.map((task) => ( + <TaskCard key={task.id} task={task} onClick={onTaskClick} /> + ))} + </SortableContext> + + {/* Empty state */} + {tasks.length === 0 && !showForm && ( + <div className="flex-1 flex items-center justify-center min-h-[80px] rounded-lg border border-dashed border-white/[0.06]"> + <span className="text-[11px] text-[var(--color-text-tertiary)]">No tasks</span> + </div> + )} + </div> + + {/* Add task (Pending column only) */} + {status === WorkTaskStatus.Pending && ( + <div className="mt-2"> + {showForm ? ( + <CreateTaskForm onClose={() => setShowForm(false)} /> + ) : ( + <button + onClick={() => setShowForm(true)} + className="flex items-center gap-1.5 w-full py-2 rounded-lg + text-[11px] text-[var(--color-text-tertiary)] + hover:text-[var(--color-text-secondary)] hover:bg-white/[0.02] + transition-colors" + > + <Plus size={13} /> + New task + </button> + )} + </div> + )} + </div> + ) +} +``` + +**Step 3: Update `KanbanBoard.tsx` drag overlay style** + +In `KanbanBoard.tsx`, change the DragOverlay inner div from: +```tsx +<div className="rotate-2 scale-105"> +``` +to: +```tsx +<div className="rotate-1 scale-[1.03] opacity-90"> +``` + +**Step 4: Commit** + +```bash +git add -A +git commit -m "feat(ui): redesign kanban columns and task cards — borderless columns, glow cards, live dots" +``` + +--- + +### Task 5: Redesign the task detail panel + +**Files:** +- Rewrite: `TaskTracker.Web/src/components/TaskDetailPanel.tsx` + +**Step 1: Rewrite `TaskDetailPanel.tsx`** + +This is a large file. The key changes are: +- Width from 400px to 480px +- Add backdrop blur and frosted glass effect +- Use `framer-motion` for animation instead of manual CSS translate +- Larger, bolder title +- Status as small pill, not big badge +- Sections separated by faint rules, not uppercase labeled boxes +- Action buttons remain sticky at bottom +- Better visual hierarchy throughout + +Replace the entire component. Key structural changes to make: + +1. Replace the overlay `div` with a `motion.div` that has `backdrop-blur-sm` +2. Replace the panel `div` with a `motion.div` using `initial={{ x: '100%' }}` and `animate={{ x: 0 }}` with a spring transition +3. Change panel width to `w-[480px]` +4. Change panel background to `bg-[var(--color-elevated)]/95 backdrop-blur-xl` +5. Replace section headers from `<h3 className="text-xs font-semibold uppercase tracking-wider text-[#64748b]">` to `<h3 className="text-[11px] font-medium text-[var(--color-text-secondary)] mb-2">` +6. Replace all hardcoded colors with CSS variable references +7. Status badge: small, muted pill style +8. Action buttons: use gradient for primary action, outline for secondary + +The full rewrite is needed because the component is 449 lines with extensive inline styles. The implementer should rewrite it with the new design system colors and the framer-motion animation, keeping all the same editing logic (inline title/desc/category/estimate editing, save handlers, escape key handling, subtask/notes integration). + +**Step 2: Commit** + +```bash +git add -A +git commit -m "feat(ui): redesign detail panel — frosted glass, spring animation, refined hierarchy" +``` + +--- + +### Task 6: Restyle the filter bar and create task form + +**Files:** +- Modify: `TaskTracker.Web/src/components/FilterBar.tsx` +- Modify: `TaskTracker.Web/src/components/CreateTaskForm.tsx` + +**Step 1: Update FilterBar colors** + +Replace all hardcoded color references in `FilterBar.tsx`: +- `bg-[#2a2d37]` → `bg-white/[0.06]` +- `text-[#94a3b8]` → `text-[var(--color-text-secondary)]` +- `bg-indigo-500` → `bg-[var(--color-accent)]` +- Change chip border radius from `rounded-full` to `rounded-md` +- Make chips smaller: `text-[10px]` instead of `text-[11px]`, `px-2 py-0.5` padding + +**Step 2: Update CreateTaskForm colors** + +Replace all hardcoded colors in `CreateTaskForm.tsx`: +- `bg-[#0f1117]` → `bg-[var(--color-page)]` +- `bg-[#1a1d27]` → `bg-[var(--color-surface)]` +- `border-white/10` → `border-[var(--color-border)]` +- `text-[#64748b]` → `text-[var(--color-text-secondary)]` +- `focus:ring-indigo-500/60` → `focus:ring-[var(--color-accent)]/60` +- `bg-indigo-600` → `bg-gradient-to-r from-[var(--color-accent)] to-[var(--color-accent-end)]` + +**Step 3: Commit** + +```bash +git add -A +git commit -m "feat(ui): restyle filter bar and create task form with design tokens" +``` + +--- + +### Task 7: Restyle the Board page + +**Files:** +- Modify: `TaskTracker.Web/src/pages/Board.tsx` + +**Step 1: Update Board.tsx** + +The Board page just orchestrates components. Ensure it uses the new color tokens for any wrapper elements. The main change is removing `SearchBar` imports (already deleted) and ensuring the layout passes through cleanly with the new top-nav layout. + +No major structural changes needed — the components it renders (FilterBar, KanbanBoard, TaskDetailPanel) are already restyled. + +**Step 2: Commit** + +```bash +git add -A +git commit -m "feat(ui): update Board page to match new design system" +``` + +--- + +### Task 8: Redesign the Analytics page + +**Files:** +- Modify: `TaskTracker.Web/src/pages/Analytics.tsx` +- Modify: `TaskTracker.Web/src/components/analytics/Timeline.tsx` +- Modify: `TaskTracker.Web/src/components/analytics/CategoryBreakdown.tsx` +- Modify: `TaskTracker.Web/src/components/analytics/ActivityFeed.tsx` + +**Step 1: Add stat cards to Analytics page header** + +Add a row of 3 stat cards above the charts: +- "Open Tasks" — count of non-completed tasks +- "Active Time" — total elapsed time of active+completed tasks +- "Top Category" — most common category + +Each card: small, `bg-[var(--color-surface)]` with `border-[var(--color-border)]`, subtle rounded corners. + +**Step 2: Restyle section containers** + +Change section container backgrounds from `bg-[#161922]` to `bg-[var(--color-surface)]` and borders from `border-white/5` to `border-[var(--color-border)]`. + +Replace section headers from `text-[#94a3b8] uppercase tracking-wider` to `text-[11px] font-medium text-[var(--color-text-secondary)] uppercase tracking-wider`. + +**Step 3: Update chart colors in Timeline and CategoryBreakdown** + +Use accent gradient colors for chart fills. Update any hardcoded colors to reference the design system. + +**Step 4: Restyle ActivityFeed with timeline connector** + +Add a vertical line connecting activity dots (like a git log): +- Left side: colored dots with a thin vertical line connecting them +- Right side: relative timestamps +- Each entry: task title, event type, timestamp + +**Step 5: Commit** + +```bash +git add -A +git commit -m "feat(ui): redesign analytics page — stat cards, styled charts, git-log activity feed" +``` + +--- + +### Task 9: Redesign the Mappings page + +**Files:** +- Modify: `TaskTracker.Web/src/pages/Mappings.tsx` + +**Step 1: Update all hardcoded colors to design tokens** + +Replace throughout: +- `bg-[#1a1d27]` → `bg-[var(--color-surface)]` +- `bg-[#1e2230]` → `bg-white/[0.04]` +- `bg-[#161922]` → `bg-[var(--color-surface)]` +- `bg-[#0f1117]` → `bg-[var(--color-page)]` +- `text-[#94a3b8]` → `text-[var(--color-text-secondary)]` +- `border-white/5` → `border-[var(--color-border)]` +- `border-white/10` → `border-[var(--color-border)]` +- `bg-indigo-600` → `bg-gradient-to-r from-[var(--color-accent)] to-[var(--color-accent-end)]` + +**Step 2: Improve table row styling** + +- Remove heavy border dividers, use subtle `border-b border-[var(--color-border)]` +- Add `hover:bg-white/[0.03]` for rows +- Make the header row blend in more: `text-[10px] uppercase tracking-wider` + +**Step 3: Improve empty state** + +Add a muted icon (Link icon from Lucide, larger at 40px, with `text-[var(--color-text-tertiary)]`) above the "No mappings" text. + +**Step 4: Commit** + +```bash +git add -A +git commit -m "feat(ui): redesign mappings page with design tokens and refined table" +``` + +--- + +### Task 10: Update SubtaskList and NotesList styling + +**Files:** +- Modify: `TaskTracker.Web/src/components/SubtaskList.tsx` +- Modify: `TaskTracker.Web/src/components/NotesList.tsx` + +**Step 1: Update SubtaskList colors** + +Replace all hardcoded colors with CSS variable references. The structure stays the same — just update colors to match the new design system. + +**Step 2: Update NotesList colors** + +Same treatment. Also make the note type badges use muted pill styling consistent with the rest of the UI. + +**Step 3: Commit** + +```bash +git add -A +git commit -m "feat(ui): restyle subtask list and notes list with design tokens" +``` + +--- + +### Task 11: Visual QA and polish pass + +**Files:** +- Any files needing tweaks + +**Step 1: Run the dev server** + +Run: `cd TaskTracker.Web && npm run dev` + +**Step 2: Visual QA checklist** + +Open the browser and verify: +- [ ] Top nav renders correctly, active tab highlights +- [ ] Cmd+K opens search modal with backdrop blur +- [ ] Board columns show without boxes, colored underlines work +- [ ] Task cards have gradient glow on hover +- [ ] Active task shows pulsing blue dot +- [ ] Drag-and-drop still works correctly +- [ ] Detail panel slides in with spring animation and frosted glass +- [ ] Filter chips styled correctly +- [ ] Create task form matches design system +- [ ] Analytics page stat cards render +- [ ] Activity feed has timeline connector +- [ ] Mappings table rows are clean +- [ ] Background noise texture is visible but subtle +- [ ] No remnant old color values (cyan rings, old backgrounds) + +**Step 3: Fix any issues found during QA** + +**Step 4: Commit** + +```bash +git add -A +git commit -m "fix(ui): visual QA polish — fix alignment, spacing, and color consistency" +``` + +--- + +### Task 12: Build verification + +**Step 1: Run the production build** + +Run: `cd TaskTracker.Web && npm run build` + +Ensure no TypeScript errors or build failures. + +**Step 2: If errors, fix them and re-build** + +**Step 3: Final commit if any fixes were needed** + +```bash +git add -A +git commit -m "fix: resolve build errors from UI redesign" +```