12-task plan covering CSS foundation, top nav, Cmd+K search, kanban cards, detail panel, analytics, mappings, and polish. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
32 KiB
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 <title> from tasktracker-web to TaskTracker.
Step 3: Replace index.css with the new design foundation
Replace the entire contents of src/index.css with:
@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:
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
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
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
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
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
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
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
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:
<div className="rotate-2 scale-105">
to:
<div className="rotate-1 scale-[1.03] opacity-90">
Step 4: Commit
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-motionfor 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:
- Replace the overlay
divwith amotion.divthat hasbackdrop-blur-sm - Replace the panel
divwith amotion.divusinginitial={{ x: '100%' }}andanimate={{ x: 0 }}with a spring transition - Change panel width to
w-[480px] - Change panel background to
bg-[var(--color-elevated)]/95 backdrop-blur-xl - 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"> - Replace all hardcoded colors with CSS variable references
- Status badge: small, muted pill style
- 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
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-fulltorounded-md - Make chips smaller:
text-[10px]instead oftext-[11px],px-2 py-0.5padding
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)]/60bg-indigo-600→bg-gradient-to-r from-[var(--color-accent)] to-[var(--color-accent-end)]
Step 3: Commit
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
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
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
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
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
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
git add -A
git commit -m "fix: resolve build errors from UI redesign"