Files
TaskTracker/docs/plans/2026-02-26-web-ui-redesign.md
AJ Isaacs e12f78c479 chore: initial commit of TaskTracker project
Existing ASP.NET API with vanilla JS SPA, WindowWatcher, Chrome extension, and MCP server.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 22:08:45 -05:00

29 KiB

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:

public int? EstimatedMinutes { get; set; }

Step 2: Add EstimatedMinutes to CreateTaskRequest

In TaskTracker.Core/DTOs/CreateTaskRequest.cs, add:

public int? EstimatedMinutes { get; set; }

Step 3: Create UpdateTaskRequest DTO

Create TaskTracker.Core/DTOs/UpdateTaskRequest.cs:

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:

EstimatedMinutes = request.EstimatedMinutes,

Step 5: Add PUT update endpoint to TasksController

Add to TasksController.cs:

[HttpPut("{id:int}")]
public async Task<IActionResult> 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<WorkTask>.Ok(task));
}

Step 6: Create and apply EF migration

Run:

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

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

cd C:/Users/AJ/Desktop/Projects/TaskTracker
npm create vite@latest TaskTracker.Web -- --template react-ts

Step 2: Install dependencies

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:

@import "tailwindcss";

Add Tailwind plugin to TaskTracker.Web/vite.config.ts:

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:
function App() {
  return <div className="bg-[#0f1117] min-h-screen text-white p-8">
    <h1 className="text-2xl font-semibold">TaskTracker</h1>
  </div>
}
export default App
  • Replace src/main.tsx:
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

Step 5: Verify it runs

npm run dev

Open http://localhost:5173 — should see "TaskTracker" on a dark background.

Step 6: Commit

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:

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<T> {
  success: boolean
  data: T
  error: string | null
}

Step 2: Create Axios client

Create TaskTracker.Web/src/api/client.ts:

import axios from 'axios'
import type { ApiResponse } from '../types'

const api = axios.create({ baseURL: '/api' })

export async function request<T>(config: Parameters<typeof api.request>[0]): Promise<T> {
  const { data } = await api.request<ApiResponse<T>>(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:

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<WorkTask[]>({ url: '/tasks', params: { includeSubTasks } }),
  })
}

export function useActiveTask() {
  return useQuery({
    queryKey: ['tasks', 'active'],
    queryFn: () => request<WorkTask | null>({ url: '/tasks/active' }),
    refetchInterval: 30_000,
  })
}

export function useTask(id: number) {
  return useQuery({
    queryKey: ['tasks', id],
    queryFn: () => request<WorkTask>({ 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<WorkTask>({ 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<WorkTask>({ method: 'PUT', url: `/tasks/${id}`, data: body }),
    onSuccess: invalidate,
  })
}

export function useStartTask() {
  const invalidate = useInvalidateTasks()
  return useMutation({
    mutationFn: (id: number) => request<WorkTask>({ method: 'PUT', url: `/tasks/${id}/start` }),
    onSuccess: invalidate,
  })
}

export function usePauseTask() {
  const invalidate = useInvalidateTasks()
  return useMutation({
    mutationFn: ({ id, note }: { id: number; note?: string }) =>
      request<WorkTask>({ 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<WorkTask>({ method: 'PUT', url: `/tasks/${id}/resume`, data: { note } }),
    onSuccess: invalidate,
  })
}

export function useCompleteTask() {
  const invalidate = useInvalidateTasks()
  return useMutation({
    mutationFn: (id: number) => request<WorkTask>({ method: 'PUT', url: `/tasks/${id}/complete` }),
    onSuccess: invalidate,
  })
}

export function useAbandonTask() {
  const invalidate = useInvalidateTasks()
  return useMutation({
    mutationFn: (id: number) => request<void>({ method: 'DELETE', url: `/tasks/${id}` }),
    onSuccess: invalidate,
  })
}

Step 4: Create context API hooks

Create TaskTracker.Web/src/api/context.ts:

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<ContextEvent[]>({ url: '/context/recent', params: { minutes } }),
    refetchInterval: 60_000,
  })
}

export function useContextSummary() {
  return useQuery({
    queryKey: ['context', 'summary'],
    queryFn: () => request<ContextSummaryItem[]>({ url: '/context/summary' }),
    refetchInterval: 60_000,
  })
}

Step 5: Create mappings API hooks

Create TaskTracker.Web/src/api/mappings.ts:

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<AppMapping[]>({ url: '/mappings' }),
  })
}

export function useCreateMapping() {
  const qc = useQueryClient()
  return useMutation({
    mutationFn: (body: { pattern: string; matchType: string; category: string; friendlyName?: string }) =>
      request<AppMapping>({ 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<AppMapping>({ method: 'PUT', url: `/mappings/${id}`, data: body }),
    onSuccess: () => qc.invalidateQueries({ queryKey: ['mappings'] }),
  })
}

export function useDeleteMapping() {
  const qc = useQueryClient()
  return useMutation({
    mutationFn: (id: number) => request<void>({ method: 'DELETE', url: `/mappings/${id}` }),
    onSuccess: () => qc.invalidateQueries({ queryKey: ['mappings'] }),
  })
}

Step 6: Add QueryClientProvider to main.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(
  <StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </StrictMode>,
)

Step 7: Verify compilation

cd TaskTracker.Web && npm run build

Expected: clean build with no errors.

Step 8: Commit

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
  • <Outlet /> 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.:

export default function Board() {
  return <div><h1 className="text-xl font-semibold text-white">Board</h1></div>
}

Step 3: Wire up routing in App.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 (
    <BrowserRouter>
      <Routes>
        <Route element={<Layout />}>
          <Route path="/" element={<Navigate to="/board" replace />} />
          <Route path="/board" element={<Board />} />
          <Route path="/analytics" element={<Analytics />} />
          <Route path="/mappings" element={<Mappings />} />
        </Route>
      </Routes>
    </BrowserRouter>
  )
}

Step 4: Verify navigation works

Run npm run dev. Click sidebar links — URL changes and placeholder pages render.

Step 5: Commit

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:

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<string, string> = {
  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 <KanbanBoard /> 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

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: <SubtaskList /> component
    • Notes: <NotesList /> 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 <TaskDetailPanel taskId={selectedTaskId} onClose={() => 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

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 <CreateTaskForm /> 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

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 <SearchBar /> 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 <FilterBar /> above the <KanbanBoard />. 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

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

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

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 <head>:

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">

Add to index.css (inside @theme):

@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

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

git add -A
git commit -m "chore: final cleanup and integration testing"