# TaskTracker Web UI Redesign — Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Replace the vanilla JS SPA with a React Kanban board app featuring rich analytics, subtask management, time estimates, and search/filters. **Architecture:** Separate Vite+React project (`TaskTracker.Web`) calling the existing ASP.NET API at `localhost:5200`. Two small API changes needed first (time estimate field, task update endpoint). Frontend uses TanStack Query for server state, dnd-kit for drag-and-drop, Recharts for analytics. **Tech Stack:** React 19, TypeScript, Vite, Tailwind CSS, @dnd-kit, @tanstack/react-query, react-router-dom, Recharts, Axios, lucide-react **Design doc:** `docs/plans/2026-02-26-web-ui-redesign-design.md` --- ## Task 1: API — Add EstimatedMinutes field and Update endpoint The React UI needs two things the API doesn't have yet: a time estimate field on tasks, and a general-purpose update endpoint for inline editing. **Files:** - Modify: `TaskTracker.Core/Entities/WorkTask.cs` - Modify: `TaskTracker.Core/DTOs/CreateTaskRequest.cs` - Create: `TaskTracker.Core/DTOs/UpdateTaskRequest.cs` - Modify: `TaskTracker.Api/Controllers/TasksController.cs` - Modify: `TaskTracker.Infrastructure/Data/AppDbContext.cs` (if migration needed) **Step 1: Add EstimatedMinutes to WorkTask entity** In `TaskTracker.Core/Entities/WorkTask.cs`, add: ```csharp public int? EstimatedMinutes { get; set; } ``` **Step 2: Add EstimatedMinutes to CreateTaskRequest** In `TaskTracker.Core/DTOs/CreateTaskRequest.cs`, add: ```csharp public int? EstimatedMinutes { get; set; } ``` **Step 3: Create UpdateTaskRequest DTO** Create `TaskTracker.Core/DTOs/UpdateTaskRequest.cs`: ```csharp namespace TaskTracker.Core.DTOs; public class UpdateTaskRequest { public string? Title { get; set; } public string? Description { get; set; } public string? Category { get; set; } public int? EstimatedMinutes { get; set; } } ``` **Step 4: Wire EstimatedMinutes in TasksController.Create** In `TasksController.cs`, update the `Create` method's `new WorkTask` block to include: ```csharp EstimatedMinutes = request.EstimatedMinutes, ``` **Step 5: Add PUT update endpoint to TasksController** Add to `TasksController.cs`: ```csharp [HttpPut("{id:int}")] public async Task Update(int id, [FromBody] UpdateTaskRequest request) { var task = await taskRepo.GetByIdAsync(id); if (task is null) return NotFound(ApiResponse.Fail("Task not found")); if (request.Title is not null) task.Title = request.Title; if (request.Description is not null) task.Description = request.Description; if (request.Category is not null) task.Category = request.Category; if (request.EstimatedMinutes.HasValue) task.EstimatedMinutes = request.EstimatedMinutes; await taskRepo.UpdateAsync(task); return Ok(ApiResponse.Ok(task)); } ``` **Step 6: Create and apply EF migration** Run: ```bash cd TaskTracker.Infrastructure dotnet ef migrations add AddEstimatedMinutes --startup-project ../TaskTracker.Api dotnet ef database update --startup-project ../TaskTracker.Api ``` **Step 7: Verify with Swagger** Run the API (`dotnet run --project TaskTracker.Api`) and test the new PUT endpoint at `/swagger`. **Step 8: Commit** ```bash git add TaskTracker.Core/Entities/WorkTask.cs TaskTracker.Core/DTOs/CreateTaskRequest.cs TaskTracker.Core/DTOs/UpdateTaskRequest.cs TaskTracker.Api/Controllers/TasksController.cs TaskTracker.Infrastructure/ git commit -m "feat: add EstimatedMinutes field and general PUT update endpoint for tasks" ``` --- ## Task 2: Scaffold React project with Vite + Tailwind **Files:** - Create: `TaskTracker.Web/` (entire project scaffold) **Step 1: Create Vite React TypeScript project** ```bash cd C:/Users/AJ/Desktop/Projects/TaskTracker npm create vite@latest TaskTracker.Web -- --template react-ts ``` **Step 2: Install dependencies** ```bash cd TaskTracker.Web npm install axios @tanstack/react-query react-router-dom @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities recharts lucide-react npm install -D tailwindcss @tailwindcss/vite ``` **Step 3: Configure Tailwind** Replace `TaskTracker.Web/src/index.css` with: ```css @import "tailwindcss"; ``` Add Tailwind plugin to `TaskTracker.Web/vite.config.ts`: ```typescript import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' export default defineConfig({ plugins: [react(), tailwindcss()], server: { port: 5173, proxy: { '/api': { target: 'http://localhost:5200', changeOrigin: true, }, }, }, }) ``` **Step 4: Clean up scaffold** - Delete `src/App.css`, `src/assets/` - Replace `src/App.tsx` with a minimal placeholder: ```tsx function App() { return

TaskTracker

} export default App ``` - Replace `src/main.tsx`: ```tsx import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' import App from './App' createRoot(document.getElementById('root')!).render( , ) ``` **Step 5: Verify it runs** ```bash npm run dev ``` Open `http://localhost:5173` — should see "TaskTracker" on a dark background. **Step 6: Commit** ```bash git add TaskTracker.Web/ git commit -m "feat: scaffold TaskTracker.Web with Vite, React, TypeScript, Tailwind" ``` --- ## Task 3: TypeScript types + API client + TanStack Query provider **Files:** - Create: `TaskTracker.Web/src/types/index.ts` - Create: `TaskTracker.Web/src/api/client.ts` - Create: `TaskTracker.Web/src/api/tasks.ts` - Create: `TaskTracker.Web/src/api/context.ts` - Create: `TaskTracker.Web/src/api/mappings.ts` - Modify: `TaskTracker.Web/src/main.tsx` **Step 1: Create TypeScript types matching the API** Create `TaskTracker.Web/src/types/index.ts`: ```typescript export enum WorkTaskStatus { Pending = 0, Active = 1, Paused = 2, Completed = 3, Abandoned = 4, } export enum NoteType { PauseNote = 0, ResumeNote = 1, General = 2, } export interface WorkTask { id: number title: string description: string | null status: WorkTaskStatus category: string | null createdAt: string startedAt: string | null completedAt: string | null estimatedMinutes: number | null parentTaskId: number | null subTasks: WorkTask[] notes: TaskNote[] contextEvents: ContextEvent[] } export interface TaskNote { id: number workTaskId: number content: string type: NoteType createdAt: string } export interface ContextEvent { id: number workTaskId: number | null source: string appName: string windowTitle: string url: string | null timestamp: string } export interface AppMapping { id: number pattern: string matchType: string category: string friendlyName: string | null } export interface ContextSummaryItem { appName: string category: string eventCount: number firstSeen: string lastSeen: string } export interface ApiResponse { success: boolean data: T error: string | null } ``` **Step 2: Create Axios client** Create `TaskTracker.Web/src/api/client.ts`: ```typescript import axios from 'axios' import type { ApiResponse } from '../types' const api = axios.create({ baseURL: '/api' }) export async function request(config: Parameters[0]): Promise { const { data } = await api.request>(config) if (!data.success) throw new Error(data.error ?? 'API error') return data.data } export default api ``` **Step 3: Create task API hooks** Create `TaskTracker.Web/src/api/tasks.ts`: ```typescript import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { request } from './client' import type { WorkTask } from '../types' export function useTasks(includeSubTasks = true) { return useQuery({ queryKey: ['tasks', { includeSubTasks }], queryFn: () => request({ url: '/tasks', params: { includeSubTasks } }), }) } export function useActiveTask() { return useQuery({ queryKey: ['tasks', 'active'], queryFn: () => request({ url: '/tasks/active' }), refetchInterval: 30_000, }) } export function useTask(id: number) { return useQuery({ queryKey: ['tasks', id], queryFn: () => request({ url: `/tasks/${id}` }), }) } function useInvalidateTasks() { const qc = useQueryClient() return () => { qc.invalidateQueries({ queryKey: ['tasks'] }) } } export function useCreateTask() { const invalidate = useInvalidateTasks() return useMutation({ mutationFn: (body: { title: string; description?: string; category?: string; parentTaskId?: number; estimatedMinutes?: number }) => request({ method: 'POST', url: '/tasks', data: body }), onSuccess: invalidate, }) } export function useUpdateTask() { const invalidate = useInvalidateTasks() return useMutation({ mutationFn: ({ id, ...body }: { id: number; title?: string; description?: string; category?: string; estimatedMinutes?: number }) => request({ method: 'PUT', url: `/tasks/${id}`, data: body }), onSuccess: invalidate, }) } export function useStartTask() { const invalidate = useInvalidateTasks() return useMutation({ mutationFn: (id: number) => request({ method: 'PUT', url: `/tasks/${id}/start` }), onSuccess: invalidate, }) } export function usePauseTask() { const invalidate = useInvalidateTasks() return useMutation({ mutationFn: ({ id, note }: { id: number; note?: string }) => request({ method: 'PUT', url: `/tasks/${id}/pause`, data: { note } }), onSuccess: invalidate, }) } export function useResumeTask() { const invalidate = useInvalidateTasks() return useMutation({ mutationFn: ({ id, note }: { id: number; note?: string }) => request({ method: 'PUT', url: `/tasks/${id}/resume`, data: { note } }), onSuccess: invalidate, }) } export function useCompleteTask() { const invalidate = useInvalidateTasks() return useMutation({ mutationFn: (id: number) => request({ method: 'PUT', url: `/tasks/${id}/complete` }), onSuccess: invalidate, }) } export function useAbandonTask() { const invalidate = useInvalidateTasks() return useMutation({ mutationFn: (id: number) => request({ method: 'DELETE', url: `/tasks/${id}` }), onSuccess: invalidate, }) } ``` **Step 4: Create context API hooks** Create `TaskTracker.Web/src/api/context.ts`: ```typescript import { useQuery } from '@tanstack/react-query' import { request } from './client' import type { ContextEvent, ContextSummaryItem } from '../types' export function useRecentContext(minutes = 30) { return useQuery({ queryKey: ['context', 'recent', minutes], queryFn: () => request({ url: '/context/recent', params: { minutes } }), refetchInterval: 60_000, }) } export function useContextSummary() { return useQuery({ queryKey: ['context', 'summary'], queryFn: () => request({ url: '/context/summary' }), refetchInterval: 60_000, }) } ``` **Step 5: Create mappings API hooks** Create `TaskTracker.Web/src/api/mappings.ts`: ```typescript import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { request } from './client' import type { AppMapping } from '../types' export function useMappings() { return useQuery({ queryKey: ['mappings'], queryFn: () => request({ url: '/mappings' }), }) } export function useCreateMapping() { const qc = useQueryClient() return useMutation({ mutationFn: (body: { pattern: string; matchType: string; category: string; friendlyName?: string }) => request({ method: 'POST', url: '/mappings', data: body }), onSuccess: () => qc.invalidateQueries({ queryKey: ['mappings'] }), }) } export function useUpdateMapping() { const qc = useQueryClient() return useMutation({ mutationFn: ({ id, ...body }: { id: number; pattern: string; matchType: string; category: string; friendlyName?: string }) => request({ method: 'PUT', url: `/mappings/${id}`, data: body }), onSuccess: () => qc.invalidateQueries({ queryKey: ['mappings'] }), }) } export function useDeleteMapping() { const qc = useQueryClient() return useMutation({ mutationFn: (id: number) => request({ method: 'DELETE', url: `/mappings/${id}` }), onSuccess: () => qc.invalidateQueries({ queryKey: ['mappings'] }), }) } ``` **Step 6: Add QueryClientProvider to main.tsx** ```tsx import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import './index.css' import App from './App' const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 10_000, retry: 1 } }, }) createRoot(document.getElementById('root')!).render( , ) ``` **Step 7: Verify compilation** ```bash cd TaskTracker.Web && npm run build ``` Expected: clean build with no errors. **Step 8: Commit** ```bash git add TaskTracker.Web/src/types/ TaskTracker.Web/src/api/ TaskTracker.Web/src/main.tsx git commit -m "feat: add TypeScript types, API client, and TanStack Query hooks" ``` --- ## Task 4: Layout shell — sidebar, top bar, routing **Files:** - Create: `TaskTracker.Web/src/components/Layout.tsx` - Create: `TaskTracker.Web/src/pages/Board.tsx` (placeholder) - Create: `TaskTracker.Web/src/pages/Analytics.tsx` (placeholder) - Create: `TaskTracker.Web/src/pages/Mappings.tsx` (placeholder) - Modify: `TaskTracker.Web/src/App.tsx` **Step 1: Create Layout component** Create `TaskTracker.Web/src/components/Layout.tsx` with: - Collapsible sidebar (60px collapsed, 200px expanded) with gradient background - Nav items: Board (LayoutGrid icon), Analytics (BarChart3 icon), Mappings (Link icon) - Top bar with app title and SearchBar placeholder - `` for page content - Use `lucide-react` for icons - Use `react-router-dom` `NavLink` with active styling (indigo highlight) **Step 2: Create placeholder pages** Each page as a simple component with just a heading, e.g.: ```tsx export default function Board() { return

Board

} ``` **Step 3: Wire up routing in App.tsx** ```tsx import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom' import Layout from './components/Layout' import Board from './pages/Board' import Analytics from './pages/Analytics' import Mappings from './pages/Mappings' export default function App() { return ( }> } /> } /> } /> } /> ) } ``` **Step 4: Verify navigation works** Run `npm run dev`. Click sidebar links — URL changes and placeholder pages render. **Step 5: Commit** ```bash git add TaskTracker.Web/src/ git commit -m "feat: add layout shell with sidebar navigation and routing" ``` --- ## Task 5: Kanban board — columns, cards, drag-and-drop This is the core feature. Build it incrementally. **Files:** - Create: `TaskTracker.Web/src/lib/constants.ts` (colors, status labels) - Create: `TaskTracker.Web/src/components/KanbanColumn.tsx` - Create: `TaskTracker.Web/src/components/TaskCard.tsx` - Create: `TaskTracker.Web/src/components/KanbanBoard.tsx` - Modify: `TaskTracker.Web/src/pages/Board.tsx` **Step 1: Create constants for status/category config** Create `TaskTracker.Web/src/lib/constants.ts`: ```typescript import { WorkTaskStatus } from '../types' export const COLUMN_CONFIG = [ { status: WorkTaskStatus.Pending, label: 'Pending', color: '#94a3b8' }, { status: WorkTaskStatus.Active, label: 'Active', color: '#06b6d4' }, { status: WorkTaskStatus.Paused, label: 'Paused', color: '#f59e0b' }, { status: WorkTaskStatus.Completed, label: 'Completed', color: '#10b981' }, ] as const export const CATEGORY_COLORS: Record = { Development: '#6366f1', Research: '#06b6d4', Communication: '#8b5cf6', DevOps: '#f97316', Documentation: '#14b8a6', Design: '#ec4899', Unknown: '#64748b', } ``` **Step 2: Build TaskCard component** Create `TaskTracker.Web/src/components/TaskCard.tsx`: - Renders task title, category badge (color-coded from `CATEGORY_COLORS`), elapsed time - If `estimatedMinutes` set, show progress bar (elapsed / estimated) - If task has subtasks, show "N/M done" indicator - Active task gets a pulsing cyan border (`animate-pulse` or custom keyframe) - Colored left border matching category - Uses `useSortable` from dnd-kit for drag handle - `onClick` prop to open detail panel **Step 3: Build KanbanColumn component** Create `TaskTracker.Web/src/components/KanbanColumn.tsx`: - Column header with status label + count + colored underline - Uses `useDroppable` from dnd-kit - Renders list of `TaskCard` components - "Pending" column has "+ Add Task" button at bottom - Visual drop indicator when dragging over **Step 4: Build KanbanBoard component** Create `TaskTracker.Web/src/components/KanbanBoard.tsx`: - `DndContext` + `SortableContext` from dnd-kit wrapping the columns - `useTasks()` hook to fetch all tasks - Groups tasks by status into columns - `onDragEnd` handler that: - Determines source/target column - Maps column transitions to API calls (start/pause/resume/complete) - Blocks invalid transitions (e.g., moving to Pending) - Shows error toast on failure - Filters out subtasks from board (only show top-level tasks, `parentTaskId === null`) **Step 5: Wire Board page** Update `TaskTracker.Web/src/pages/Board.tsx` to render `` and pass an `onTaskClick` callback that opens the detail panel (wired in Task 6). **Step 6: Verify board renders with real data** Start the API (`dotnet run --project TaskTracker.Api`), then `npm run dev`. Create a few tasks via Swagger, verify they show in the correct columns. Test drag-and-drop transitions. **Step 7: Commit** ```bash git add TaskTracker.Web/src/ git commit -m "feat: implement Kanban board with drag-and-drop task management" ``` --- ## Task 6: Task detail slide-over panel **Files:** - Create: `TaskTracker.Web/src/components/TaskDetailPanel.tsx` - Create: `TaskTracker.Web/src/components/SubtaskList.tsx` - Create: `TaskTracker.Web/src/components/NotesList.tsx` - Modify: `TaskTracker.Web/src/pages/Board.tsx` **Step 1: Build TaskDetailPanel** Create `TaskTracker.Web/src/components/TaskDetailPanel.tsx`: - Slide-over panel from right (~400px), board dimmed behind with semi-transparent overlay - Fetches task detail via `useTask(id)` - Sections: - **Header**: Title (click to edit inline, blur to save via `useUpdateTask`), status badge, category dropdown - **Description**: Click-to-edit text area, saves on blur - **Time**: Show elapsed time. Input for estimated minutes, saves on blur. Progress bar if estimate set. - **Subtasks**: `` component - **Notes**: `` component - **Actions**: Context-aware buttons — Start/Pause/Resume/Complete/Abandon based on current status - Close on Escape keypress or overlay click - Animate slide-in/out with CSS transition **Step 2: Build SubtaskList** Create `TaskTracker.Web/src/components/SubtaskList.tsx`: - Renders `task.subTasks` as checkboxes - Checking a subtask calls `useCompleteTask()` on it - "+" button at top shows inline text input → `useCreateTask()` with `parentTaskId` - Shows subtask status (completed = strikethrough + checkmark) **Step 3: Build NotesList** Create `TaskTracker.Web/src/components/NotesList.tsx`: - Renders `task.notes` chronologically - Each note shows: type badge (PauseNote/ResumeNote/General), content, relative timestamp - "+" button shows inline text input → `POST /api/tasks/{id}/notes` with type General **Step 4: Wire panel into Board page** In `Board.tsx`: - Add `selectedTaskId` state - Pass `onTaskClick={(id) => setSelectedTaskId(id)}` to `KanbanBoard` - Render ` setSelectedTaskId(null)} />` when set **Step 5: Verify full workflow** Test: click card → panel opens → edit title → add subtask → add note → change status → close panel. All changes persist. **Step 6: Commit** ```bash git add TaskTracker.Web/src/ git commit -m "feat: add task detail slide-over panel with inline editing, subtasks, and notes" ``` --- ## Task 7: Create task form **Files:** - Create: `TaskTracker.Web/src/components/CreateTaskForm.tsx` - Modify: `TaskTracker.Web/src/components/KanbanColumn.tsx` **Step 1: Build CreateTaskForm** Create `TaskTracker.Web/src/components/CreateTaskForm.tsx`: - Inline form that expands in the Pending column when "+ Add Task" is clicked - Fields: Title (required), Description (optional textarea), Category (dropdown from known categories), Estimated Minutes (optional number) - Submit calls `useCreateTask()` - Escape or click away cancels - Auto-focus title input on open **Step 2: Wire into Pending column** Modify `KanbanColumn.tsx`: - When column is Pending, show "+ Add Task" button - On click, toggle showing `` at bottom of column - On submit/cancel, hide the form **Step 3: Verify** Create a task via the inline form. It should appear in the Pending column immediately. **Step 4: Commit** ```bash git add TaskTracker.Web/src/ git commit -m "feat: add inline create task form in Pending column" ``` --- ## Task 8: Search bar + board filters **Files:** - Create: `TaskTracker.Web/src/components/SearchBar.tsx` - Create: `TaskTracker.Web/src/components/FilterBar.tsx` - Modify: `TaskTracker.Web/src/components/Layout.tsx` - Modify: `TaskTracker.Web/src/pages/Board.tsx` **Step 1: Build SearchBar** Create `TaskTracker.Web/src/components/SearchBar.tsx`: - Input with search icon (lucide `Search`) - On typing, filters tasks client-side (title + description match) - Results shown as dropdown list of matching tasks - Click result → opens TaskDetailPanel (via callback prop) - Keyboard navigation: arrow keys to move, Enter to select, Escape to close - Debounce input by 200ms **Step 2: Build FilterBar** Create `TaskTracker.Web/src/components/FilterBar.tsx`: - Row of filter chips rendered below the board header - Category chips: derived from unique categories across all tasks + mappings - "Has subtasks" toggle chip - Active filters shown as colored chips with "x" to dismiss - Exposes `filters` state and `filteredTasks` computation to parent **Step 3: Wire SearchBar into Layout** Add `` to the top bar in `Layout.tsx`. It needs access to task data — use `useTasks()` inside the component. **Step 4: Wire FilterBar into Board page** In `Board.tsx`, add `` above the ``. Pass filtered tasks down to the board instead of all tasks. Lift task fetching up to `Board.tsx` and pass tasks as props to both `FilterBar` and `KanbanBoard`. **Step 5: Verify** - Type in search bar → dropdown shows matching tasks - Click category chip → board filters to that category - Combine filters → board shows intersection **Step 6: Commit** ```bash git add TaskTracker.Web/src/ git commit -m "feat: add global search bar and board filter chips" ``` --- ## Task 9: Analytics page — timeline, charts, activity feed **Files:** - Create: `TaskTracker.Web/src/components/analytics/Timeline.tsx` - Create: `TaskTracker.Web/src/components/analytics/CategoryBreakdown.tsx` - Create: `TaskTracker.Web/src/components/analytics/ActivityFeed.tsx` - Modify: `TaskTracker.Web/src/pages/Analytics.tsx` **Step 1: Build Analytics page shell** Update `TaskTracker.Web/src/pages/Analytics.tsx`: - Filter bar at top: time range dropdown (Today/7d/30d), task filter dropdown - Time range maps to `minutes` param for `/context/recent` (1440 for today, 10080 for 7d, 43200 for 30d) - Three sections stacked vertically: Timeline, Category Breakdown, Activity Feed **Step 2: Build Timeline component** Create `TaskTracker.Web/src/components/analytics/Timeline.tsx`: - Recharts `BarChart` with custom rendering - X-axis: time of day (hourly buckets) - Bars: colored by category (from mappings) - Tooltip on hover: app name, window title, duration - Uses `useRecentContext()` data, groups events into time buckets **Step 3: Build CategoryBreakdown component** Create `TaskTracker.Web/src/components/analytics/CategoryBreakdown.tsx`: - Left side: Recharts `PieChart` (donut style with inner radius) - Right side: list of categories with horizontal bar, time, percentage - Uses `useContextSummary()` data - Colors from `CATEGORY_COLORS` constant **Step 4: Build ActivityFeed component** Create `TaskTracker.Web/src/components/analytics/ActivityFeed.tsx`: - Reverse-chronological list of context events - Each row: colored category dot, relative timestamp, app name (bold), window title/URL - "Load more" button at bottom (increase `minutes` param or paginate client-side) - Uses `useRecentContext()` with the selected time range **Step 5: Verify analytics page** Need some context data — either run WindowWatcher/Chrome extension to generate real data, or POST a few test events via Swagger. Verify all three visualizations render. **Step 6: Commit** ```bash git add TaskTracker.Web/src/ git commit -m "feat: add analytics page with timeline, category breakdown, and activity feed" ``` --- ## Task 10: Mappings page **Files:** - Modify: `TaskTracker.Web/src/pages/Mappings.tsx` **Step 1: Build Mappings page** Update `TaskTracker.Web/src/pages/Mappings.tsx`: - Table with columns: Pattern, Match Type, Category (color badge), Friendly Name, Actions (edit/delete) - Uses `useMappings()` to fetch data - "+ Add Rule" button at top → inserts inline form row at top of table - Form fields: Pattern (text), Match Type (dropdown: ProcessName/TitleContains/UrlContains), Category (text), Friendly Name (text) - Submit → `useCreateMapping()` - Edit button → row becomes editable inline → save → `useUpdateMapping()` - Delete button → confirm dialog → `useDeleteMapping()` **Step 2: Verify CRUD** Create a mapping, edit it, delete it. Verify all operations work. **Step 3: Commit** ```bash git add TaskTracker.Web/src/ git commit -m "feat: add mappings page with inline CRUD table" ``` --- ## Task 11: Visual polish and animations **Files:** - Modify: Various component files for animation/transition polish - Modify: `TaskTracker.Web/index.html` (add Inter font) **Step 1: Add Inter font** In `TaskTracker.Web/index.html`, add to ``: ```html ``` Add to `index.css` (inside `@theme`): ```css @theme { --font-sans: 'Inter', sans-serif; } ``` **Step 2: Add animations** - Active task card: glowing cyan border pulse (CSS `@keyframes` animation) - Detail panel: slide-in from right with opacity transition - Drag-and-drop: smooth card movement via dnd-kit's built-in transitions - Hover effects: subtle card lift with colored shadow - Column drop target: border highlight on drag-over **Step 3: Polish visual details** - Sidebar gradient background - Colored left border on task cards - Category badge colors consistent everywhere - Column header colored underlines - Colored-tint shadows on cards (not plain gray) - Consistent spacing and typography **Step 4: Verify visual quality** Walk through every page and interaction. Check dark background rendering, color contrast, animation smoothness. **Step 5: Commit** ```bash git add TaskTracker.Web/ git commit -m "feat: add visual polish — Inter font, animations, colored shadows, hover effects" ``` --- ## Task 12: Final integration testing and cleanup **Step 1: Full workflow test** Run both the API and the React dev server. Walk through the complete workflow: 1. Create a task from the board 2. Start it (drag or button) 3. Add a subtask, complete it 4. Add a note 5. Set time estimate, verify progress bar 6. Pause with note, resume 7. Complete the task 8. Check analytics page 9. Add/edit/delete a mapping 10. Test search and filters 11. Test sidebar collapse/expand **Step 2: Fix any issues found** Address bugs or visual issues found during testing. **Step 3: Clean up** - Remove any unused imports or dead code - Ensure no console warnings in browser - Verify `npm run build` produces a clean production build **Step 4: Commit** ```bash git add -A git commit -m "chore: final cleanup and integration testing" ```