Existing ASP.NET API with vanilla JS SPA, WindowWatcher, Chrome extension, and MCP server. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.tsxwith 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-reactfor icons - Use
react-router-domNavLinkwith 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
estimatedMinutesset, show progress bar (elapsed / estimated) - If task has subtasks, show "N/M done" indicator
- Active task gets a pulsing cyan border (
animate-pulseor custom keyframe) - Colored left border matching category
- Uses
useSortablefrom dnd-kit for drag handle onClickprop 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
useDroppablefrom dnd-kit - Renders list of
TaskCardcomponents - "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+SortableContextfrom dnd-kit wrapping the columnsuseTasks()hook to fetch all tasks- Groups tasks by status into columns
onDragEndhandler 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
- Header: Title (click to edit inline, blur to save via
- 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.subTasksas checkboxes - Checking a subtask calls
useCompleteTask()on it - "+" button at top shows inline text input →
useCreateTask()withparentTaskId - Shows subtask status (completed = strikethrough + checkmark)
Step 3: Build NotesList
Create TaskTracker.Web/src/components/NotesList.tsx:
- Renders
task.noteschronologically - Each note shows: type badge (PauseNote/ResumeNote/General), content, relative timestamp
- "+" button shows inline text input →
POST /api/tasks/{id}/noteswith type General
Step 4: Wire panel into Board page
In Board.tsx:
- Add
selectedTaskIdstate - Pass
onTaskClick={(id) => setSelectedTaskId(id)}toKanbanBoard - 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
filtersstate andfilteredTaskscomputation 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
minutesparam 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
BarChartwith 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_COLORSconstant
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
minutesparam 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
@keyframesanimation) - 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:
- Create a task from the board
- Start it (drag or button)
- Add a subtask, complete it
- Add a note
- Set time estimate, verify progress bar
- Pause with note, resume
- Complete the task
- Check analytics page
- Add/edit/delete a mapping
- Test search and filters
- 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 buildproduces a clean production build
Step 4: Commit
git add -A
git commit -m "chore: final cleanup and integration testing"