Files
TaskTracker/docs/plans/2026-02-26-ui-redesign-plan.md
AJ Isaacs baf941c63c docs: add UI redesign implementation plan
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>
2026-02-26 23:57:11 -05:00

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">&uarr;&darr;</kbd>
              Navigate
            </span>
            <span className="flex items-center gap-1">
              <kbd className="font-mono bg-white/[0.06] px-1 py-0.5 rounded">&crarr;</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-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

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-500bg-[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/10border-[var(--color-border)]
  • text-[#64748b]text-[var(--color-text-secondary)]
  • focus:ring-indigo-500/60focus:ring-[var(--color-accent)]/60
  • bg-indigo-600bg-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/5border-[var(--color-border)]
  • border-white/10border-[var(--color-border)]
  • bg-indigo-600bg-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"