# 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" ```