11-task plan covering project setup, CSS/JS assets, Board page with Kanban drag-and-drop, detail panel, search modal, Analytics page with Chart.js, Mappings page with inline CRUD, and React app removal. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
26 KiB
Razor Pages Migration Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Replace the React/npm web UI with Razor Pages + htmx served from the existing TaskTracker.Api project, eliminating the Node toolchain entirely.
Architecture: Razor Pages added to the existing TaskTracker.Api project. Pages call repositories directly via DI (no API round-trip). htmx handles partial updates, SortableJS handles drag-and-drop, Chart.js handles analytics charts. All JS vendored as static files in wwwroot — zero npm.
Tech Stack: ASP.NET Razor Pages, htmx 2.0, SortableJS, Chart.js 4, vanilla JS
Reference files:
- Design doc:
docs/plans/2026-03-01-razor-pages-migration-design.md - Current React source:
TaskTracker.Web/src/(reference for feature parity) - Current CSS/tokens:
TaskTracker.Web/src/index.css - API controllers:
TaskTracker.Api/Controllers/(keep unchanged) - Entities:
TaskTracker.Core/Entities/(WorkTask, TaskNote, ContextEvent, AppMapping) - Repositories:
TaskTracker.Core/Interfaces/(ITaskRepository, IContextEventRepository, IAppMappingRepository) - Enums:
TaskTracker.Core/Enums/(WorkTaskStatus, NoteType)
Task 1: Project Setup — Add Razor Pages to TaskTracker.Api
Files:
- Modify:
TaskTracker.Api/Program.cs - Create:
TaskTracker.Api/Pages/_ViewImports.cshtml - Create:
TaskTracker.Api/Pages/_ViewStart.cshtml - Create:
TaskTracker.Api/Pages/Shared/_Layout.cshtml
Step 1: Update Program.cs to register Razor Pages
Add builder.Services.AddRazorPages() after the existing service registrations. Add app.MapRazorPages() before app.MapControllers(). Remove app.UseDefaultFiles() (Razor Pages handle routing now). Keep app.UseStaticFiles() for wwwroot.
// In Program.cs, after builder.Services.AddCors(...)
builder.Services.AddRazorPages();
// After app.UseCors()
app.UseStaticFiles();
app.MapRazorPages();
app.MapControllers();
// Remove: app.UseDefaultFiles();
Step 2: Create _ViewImports.cshtml
@using TaskTracker.Core.Entities
@using TaskTracker.Core.Enums
@using TaskTracker.Core.DTOs
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
Step 3: Create _ViewStart.cshtml
@{
Layout = "_Layout";
}
Step 4: Create _Layout.cshtml — the app shell
This is the shared layout with navigation bar, search modal placeholder, and script tags. Port the exact nav structure from TaskTracker.Web/src/components/Layout.tsx. Include the three vendored JS libraries and app.js.
The layout should have:
<header>with logo, nav links (Board, Analytics, Mappings), search button (Ctrl+K hint), and "New Task" button<main>with@RenderBody()<div id="search-modal">empty container for the search modal<div id="detail-panel">empty container for the task detail slide-in- Script tags for htmx, Sortable, Chart.js, and app.js
Nav links use <a> tags with asp-page tag helpers. Active state uses a CSS class toggled by checking ViewContext.RouteData.
Step 5: Build and verify the app starts
Run: dotnet build TaskTracker.Api
Expected: Build succeeds with no errors.
Step 6: Commit
feat(web): add Razor Pages scaffolding to API project
Task 2: Static Assets — CSS and Vendored JS
Files:
- Create:
TaskTracker.Api/wwwroot/css/site.css - Create:
TaskTracker.Api/wwwroot/js/app.js(empty placeholder) - Download:
TaskTracker.Api/wwwroot/lib/htmx.min.js - Download:
TaskTracker.Api/wwwroot/lib/Sortable.min.js - Download:
TaskTracker.Api/wwwroot/lib/chart.umd.min.js
Step 1: Create site.css
Port the design tokens and animations from TaskTracker.Web/src/index.css. Convert Tailwind utility patterns used across all React components into reusable CSS classes. Key sections:
- CSS custom properties (
:rootblock with all--color-*tokens) - Reset / base styles (dark background, font, box-sizing)
- Animations (
pulse-glow,live-dot,card-glow) - Scrollbar styles
- Selection color
- Noise grain texture overlay
- Layout utilities (
.flex,.grid,.flex-col,.items-center,.gap-*, etc. — only the ones actually used) - Component classes:
.nav-link,.nav-link--active,.btn,.btn--primary,.btn--danger,.btn--amber,.btn--emerald,.stat-card,.badge,.input,.select, etc. - Kanban-specific:
.kanban-grid,.kanban-column,.task-card,.task-card--active - Detail panel:
.detail-overlay,.detail-panel, slide-in transition classes - Table styles for Mappings page
- Responsive: the app is desktop-first, minimal responsive needed
Reference: Read every React component's className strings to ensure complete coverage. The CSS file will be ~400-600 lines.
Step 2: Download vendored JS libraries
Use curl to download from CDNs:
- htmx 2.0:
https://unpkg.com/htmx.org@2.0.4/dist/htmx.min.js - SortableJS:
https://cdn.jsdelivr.net/npm/sortablejs@1.15.6/Sortable.min.js - Chart.js 4:
https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js
Step 3: Create empty app.js placeholder
// TaskTracker app.js — command palette, keyboard shortcuts, drag-and-drop wiring
// Will be populated in later tasks
Step 4: Verify static files serve
Run the app, navigate to /css/site.css, /lib/htmx.min.js — verify 200 responses.
Step 5: Commit
feat(web): add CSS design system and vendored JS libraries
Task 3: Board Page — Kanban Columns (Server-Rendered)
Files:
- Create:
TaskTracker.Api/Pages/Board.cshtml - Create:
TaskTracker.Api/Pages/Board.cshtml.cs - Create:
TaskTracker.Api/Pages/Partials/_KanbanColumn.cshtml - Create:
TaskTracker.Api/Pages/Partials/_TaskCard.cshtml - Create:
TaskTracker.Api/Pages/Partials/_CreateTaskForm.cshtml - Create:
TaskTracker.Api/Pages/Partials/_FilterBar.cshtml
Step 1: Create Board.cshtml.cs (PageModel)
The PageModel should:
- Inject
ITaskRepository OnGetAsync(): Load all tasks with subtasks (GetAllAsync(includeSubTasks: true)), filter to top-level only (ParentTaskId == null), group by status into 4 column view models. Accept optionalcategoryandhasSubtasksquery params for filtering.OnGetColumnAsync(WorkTaskStatus status): Return a single column partial (for htmx swap after drag-and-drop).OnPostCreateTaskAsync(string title, string? category): Create a task, return updated Pending column partial.OnPutStartAsync(int id): Start task (pause current active), return updated board columns.OnPutPauseAsync(int id): Pause task, return updated board columns.OnPutResumeAsync(int id): Resume task (pause current active), return updated board columns.OnPutCompleteAsync(int id): Complete task, return updated board columns.OnDeleteAbandonAsync(int id): Abandon (delete) task, return updated board columns.
For htmx handlers, detect Request.Headers["HX-Request"] and return Partial("Partials/_KanbanColumn", columnModel) instead of the full page.
Define a ColumnViewModel record: record ColumnViewModel(WorkTaskStatus Status, string Label, string Color, List<WorkTask> Tasks).
Use the same column config as the React app:
static readonly ColumnViewModel[] Columns = [
new(WorkTaskStatus.Pending, "Pending", "#64748b", []),
new(WorkTaskStatus.Active, "Active", "#3b82f6", []),
new(WorkTaskStatus.Paused, "Paused", "#eab308", []),
new(WorkTaskStatus.Completed, "Completed", "#22c55e", []),
];
Category colors dictionary — same as CATEGORY_COLORS in constants.ts:
public static readonly Dictionary<string, string> CategoryColors = new()
{
["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 2: Create Board.cshtml
Renders the filter bar partial and a 4-column grid. Each column rendered via _KanbanColumn partial.
@page
@model TaskTracker.Api.Pages.BoardModel
<div class="board-page">
<partial name="Partials/_FilterBar" model="Model" />
<div id="kanban-board" class="kanban-grid">
@foreach (var col in Model.Columns)
{
<partial name="Partials/_KanbanColumn" model="col" />
}
</div>
</div>
Step 3: Create _KanbanColumn.cshtml
Each column has:
- Column header with status label, colored dot, and task count
id="column-{Status}"for htmx targeting and SortableJS groupdata-status="{Status}"for JS to read on drag-and-drop- List of
_TaskCardpartials - If Pending column: include
_CreateTaskFormpartial at the bottom
Step 4: Create _TaskCard.cshtml
Each card has:
id="task-{Id}"anddata-task-id="{Id}"for SortableJS- Card glow class, active pulse class if status == Active
- Live dot indicator if Active
- Title, category dot, elapsed time
- Subtask progress dots (green = completed, dim = incomplete) + count
hx-get="/board?handler=TaskDetail&id={Id}"hx-target="#detail-panel"on click
Reference: TaskTracker.Web/src/components/TaskCard.tsx for exact structure.
Elapsed time formatting — port formatElapsed to a C# helper method on the PageModel or a static helper:
public static string FormatElapsed(DateTime? startedAt, DateTime? completedAt)
{
if (startedAt is null) return "--";
var start = startedAt.Value;
var end = completedAt ?? DateTime.UtcNow;
var mins = (int)(end - start).TotalMinutes;
if (mins < 60) return $"{mins}m";
var hours = mins / 60;
var remainder = mins % 60;
if (hours < 24) return $"{hours}h {remainder}m";
var days = hours / 24;
return $"{days}d {hours % 24}h";
}
Step 5: Create _FilterBar.cshtml
Category filter chips. Each chip is an <a> with htmx attributes:
hx-get="/board?category={cat}"hx-target="#kanban-board"hx-swap="innerHTML"- Active chip styled with category color background
- "All" chip to clear filter
Step 6: Create _CreateTaskForm.cshtml
Inline form at bottom of Pending column:
- Text input for title
- htmx POST:
hx-post="/board?handler=CreateTask"hx-target="#column-Pending"hx-swap="outerHTML" - Form submits on Enter
Step 7: Build and manually test
Run: dotnet run --project TaskTracker.Api
Navigate to /board. Verify 4 columns render with tasks from the database.
Step 8: Commit
feat(web): add Board page with Kanban columns and task cards
Task 4: Board Page — Task Detail Panel
Files:
- Create:
TaskTracker.Api/Pages/Partials/_TaskDetail.cshtml - Create:
TaskTracker.Api/Pages/Partials/_SubtaskList.cshtml - Create:
TaskTracker.Api/Pages/Partials/_NotesList.cshtml - Modify:
TaskTracker.Api/Pages/Board.cshtml.cs(add handler methods)
Step 1: Add handler methods to Board.cshtml.cs
OnGetTaskDetailAsync(int id): Load task by ID (with subtasks, notes), return_TaskDetailpartial.OnPutUpdateTaskAsync(int id, string? title, string? description, string? category, int? estimatedMinutes): Update task fields, return updated_TaskDetailpartial.OnPostAddSubtaskAsync(int id, string title): Create subtask withparentTaskId = id, return updated_SubtaskListpartial.OnPutCompleteSubtaskAsync(int id): Complete a subtask, return updated_SubtaskListpartial.OnPostAddNoteAsync(int id, string content): Add a General note, return updated_NotesListpartial.OnGetSearchAsync(string q): Search tasks by title/description/category (case-insensitive contains), return_SearchResultspartial.
Step 2: Create _TaskDetail.cshtml
Port the structure from TaskTracker.Web/src/components/TaskDetailPanel.tsx:
- Close button (
onclickcalls JS to hide the panel) - Title: displayed as text, with
hx-getto swap in an edit form on click (or use JScontenteditablewith htmxhx-puton blur) - Status badge (colored pill)
- Category: click-to-edit (same pattern as title)
- Description section: click-to-edit textarea
- Time section: elapsed vs estimate, progress bar
- Estimate: click-to-edit number input
- Subtask list partial
- Notes list partial
- Action buttons at bottom (status-dependent: Start/Pause/Resume/Complete/Abandon)
Inline editing approach: Each editable field has two states (display and edit). Use htmx hx-get to swap the display element with an edit form, and hx-put on the form to save and swap back to display. Or use a small JS helper that toggles visibility and fires htmx on blur.
Action buttons use htmx:
<button hx-put="/board?handler=Start&id=@task.Id"
hx-target="#kanban-board" hx-swap="innerHTML"
class="btn btn--primary">Start</button>
After a status-change action, the board columns should refresh AND the detail panel should update. Use hx-swap-oob (out-of-band swap) to update both targets in one response, or have the JS close the panel after the action completes.
Step 3: Create _SubtaskList.cshtml
- List of subtasks with checkbox icons
- Completed subtasks show line-through
- Click non-completed → htmx PUT to complete, swap subtask list
- Inline input to add new subtask → htmx POST
Step 4: Create _NotesList.cshtml
- Notes sorted chronologically
- Type badge (Pause=amber, Resume=blue, General=subtle)
- Relative timestamps (port the JS
formatRelativeTimelogic to C#) - Inline input to add new note → htmx POST
Step 5: Build and manually test
Click a task card → detail panel should slide in. Test inline editing, subtask creation, note creation, and action buttons.
Step 6: Commit
feat(web): add task detail panel with inline editing, subtasks, and notes
Task 5: Board Page — Drag-and-Drop with SortableJS
Files:
- Modify:
TaskTracker.Api/wwwroot/js/app.js
Step 1: Implement SortableJS wiring in app.js
function initKanban() {
document.querySelectorAll('.kanban-column-body').forEach(el => {
new Sortable(el, {
group: 'kanban',
animation: 150,
ghostClass: 'task-card--ghost',
dragClass: 'task-card--dragging',
onEnd: function(evt) {
const taskId = evt.item.dataset.taskId;
const fromStatus = evt.from.dataset.status;
const toStatus = evt.to.dataset.status;
if (fromStatus === toStatus) return;
let handler = null;
if (toStatus === 'Active' && fromStatus === 'Paused') handler = 'Resume';
else if (toStatus === 'Active') handler = 'Start';
else if (toStatus === 'Paused') handler = 'Pause';
else if (toStatus === 'Completed') handler = 'Complete';
else { evt.from.appendChild(evt.item); return; } // Revert if invalid
htmx.ajax('PUT', `/board?handler=${handler}&id=${taskId}`, {
target: '#kanban-board',
swap: 'innerHTML'
});
}
});
});
}
Add ghost card CSS: .task-card--ghost gets rotation, scale, opacity matching the React DragOverlay.
Call initKanban() on DOMContentLoaded and after htmx swaps (listen for htmx:afterSwap event on #kanban-board).
Step 2: Add htmx:afterSwap listener to re-init Sortable after board updates
document.addEventListener('htmx:afterSwap', function(evt) {
if (evt.detail.target.id === 'kanban-board' ||
evt.detail.target.closest('#kanban-board')) {
initKanban();
}
});
Step 3: Manually test drag-and-drop
Drag a Pending task to Active → should fire Start API call and refresh board. Drag Active to Paused → Pause. Drag Paused to Active → Resume. Drag to Completed → Complete. Drag to Pending → should revert (snap back).
Step 4: Commit
feat(web): add drag-and-drop between Kanban columns via SortableJS
Task 6: Board Page — Search Modal (Ctrl+K)
Files:
- Modify:
TaskTracker.Api/wwwroot/js/app.js - Create:
TaskTracker.Api/Pages/Partials/_SearchResults.cshtml - Modify:
TaskTracker.Api/Pages/Board.cshtml.cs(add search handler)
Step 1: Add search handler to Board.cshtml.cs
OnGetSearchAsync(string? q):
- If
qis empty/null: return recent Active/Paused/Pending tasks (up to 8) - If
qhas value: search tasks where title, description, or category containsq(case-insensitive), limit 10 - Return
_SearchResultspartial
Step 2: Create _SearchResults.cshtml
List of results, each with:
- Status color dot
- Title
- Category badge
- Each result is a link/button that navigates to
/board?task={id}or fires JS to open the detail panel
Step 3: Implement search modal in app.js
~80 lines of vanilla JS:
- Ctrl+K / Cmd+K opens the modal (toggle
#search-modalvisibility) - Escape closes
- Input field with debounced htmx fetch:
hx-get="/board?handler=Search&q={value}"hx-target="#search-results"hx-trigger="input changed delay:200ms" - Arrow key navigation: track selected index, move highlight, Enter to navigate
- Backdrop click closes
The search modal HTML structure can be in _Layout.cshtml (hidden by default) with the results container inside it.
Step 4: Manually test
Press Ctrl+K → modal opens. Type a search term → results appear. Arrow keys move selection. Enter opens task. Escape closes.
Step 5: Commit
feat(web): add Ctrl+K command palette search modal
Task 7: Analytics Page
Files:
- Create:
TaskTracker.Api/Pages/Analytics.cshtml - Create:
TaskTracker.Api/Pages/Analytics.cshtml.cs
Step 1: Create Analytics.cshtml.cs
Inject ITaskRepository, IContextEventRepository, IAppMappingRepository.
OnGetAsync(int minutes = 1440, int? taskId = null):
- Load all tasks
- Load context events for the time range
- Load mappings
- Compute stat cards: open tasks count, total active time, top category
- Compute timeline data: bucket events by hour, resolve category via mappings, serialize as JSON for Chart.js
- Compute category breakdown: group events by resolved category, count, serialize as JSON
- Load activity feed (first 20 events, most recent first)
OnGetActivityFeedAsync(int minutes, int? taskId, int offset):
- Return next batch of activity feed items as a partial (for htmx "Load more")
Step 2: Create Analytics.cshtml
Port structure from TaskTracker.Web/src/pages/Analytics.tsx:
- Header with time range and task filter dropdowns (use
<select>with htmxhx-geton change to reload the page with new params) - 3 stat cards (rendered server-side)
- Timeline section:
<canvas id="timeline-chart">+ inline<script>that reads JSON from a<script type="application/json">tag and renders a Chart.js bar chart - Category breakdown:
<canvas id="category-chart">+ Chart.js donut - Activity feed: server-rendered list with "Load more" button using htmx
Chart.js config should match the React Recharts appearance:
- Timeline: vertical bar chart, bars colored by category, custom tooltip
- Category: donut chart (cutout 60%), padding angle, legend on right with colored dots and percentages
The category color resolution logic (matching app names to categories via mappings) should be done server-side and the resolved data passed to Chart.js as JSON.
Step 3: Build and manually test
Navigate to /analytics. Verify stat cards, charts render, activity feed loads, "Load more" works, dropdowns filter data.
Step 4: Commit
feat(web): add Analytics page with Chart.js charts and activity feed
Task 8: Mappings Page
Files:
- Create:
TaskTracker.Api/Pages/Mappings.cshtml - Create:
TaskTracker.Api/Pages/Mappings.cshtml.cs - Create:
TaskTracker.Api/Pages/Partials/_MappingRow.cshtml - Create:
TaskTracker.Api/Pages/Partials/_MappingEditRow.cshtml
Step 1: Create Mappings.cshtml.cs
Inject IAppMappingRepository.
Handlers:
OnGetAsync(): Load all mappings, render full pageOnGetEditRowAsync(int id): Load mapping by ID, return_MappingEditRowpartial (for inline edit)OnGetAddRowAsync(): Return empty_MappingEditRowpartial (for adding new)OnPostSaveAsync(int? id, string pattern, string matchType, string category, string? friendlyName): Create or update mapping. Ifidis null, create; otherwise update. Return_MappingRowpartial for the saved row.OnDeleteAsync(int id): Delete mapping, return empty response (htmx removes the row)
Step 2: Create Mappings.cshtml
Port structure from TaskTracker.Web/src/pages/Mappings.tsx:
- Header with "App Mappings" title and "Add Rule" button
- Table with columns: Pattern, Match Type, Category, Friendly Name, Actions
- Each row rendered via
_MappingRowpartial - "Add Rule" button uses htmx to insert
_MappingEditRowat the top of the tbody - Empty state when no mappings
Step 3: Create _MappingRow.cshtml
Display row with:
- Pattern (monospace)
- Match type badge (colored: ProcessName=indigo, TitleContains=cyan, UrlContains=orange)
- Category with colored dot
- Friendly name
- Edit button:
hx-get="/mappings?handler=EditRow&id={Id}"hx-target="closest tr"hx-swap="outerHTML" - Delete button:
hx-delete="/mappings?handler=Delete&id={Id}"hx-target="closest tr"hx-swap="outerHTML"hx-confirm="Delete this mapping rule?"
Step 4: Create _MappingEditRow.cshtml
Inline edit row (replaces the display row) with:
- Pattern text input (autofocus)
- Match type select (ProcessName, TitleContains, UrlContains)
- Category text input
- Friendly name text input
- Save button (check icon):
hx-post="/mappings?handler=Save"with form values - Cancel button (x icon):
hx-get="/mappings?handler=Row&id={Id}"to swap back to display (or for new rows, just remove the row)
Step 5: Build and manually test
Navigate to /mappings. Add a new mapping, edit it, delete it. Verify inline edit and table updates.
Step 6: Commit
feat(web): add Mappings page with inline CRUD table
Task 9: Detail Panel Slide-In Animation and Polish
Files:
- Modify:
TaskTracker.Api/wwwroot/css/site.css - Modify:
TaskTracker.Api/wwwroot/js/app.js
Step 1: Implement slide-in animation in CSS
The detail panel uses CSS transitions instead of Framer Motion:
.detail-overlay {
position: fixed;
inset: 0;
z-index: 40;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
opacity: 0;
transition: opacity 0.2s ease;
pointer-events: none;
}
.detail-overlay--open {
opacity: 1;
pointer-events: auto;
}
.detail-panel {
position: fixed;
top: 0;
right: 0;
height: 100%;
width: 480px;
z-index: 50;
transform: translateX(100%);
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
/* ... background, border, shadow */
}
.detail-panel--open {
transform: translateX(0);
}
Step 2: Implement panel open/close logic in app.js
- Opening: When htmx loads the detail partial into
#detail-panel, add--openclasses to overlay and panel - Closing: Remove
--openclasses, wait for transition end, then clear the panel content - Backdrop click closes
- Escape key closes (unless editing inline)
Step 3: Implement inline editing helpers in app.js
Small helper functions for click-to-edit fields:
startEdit(fieldId): hide display element, show input, focuscancelEdit(fieldId): hide input, show display elementsaveEdit(fieldId): read input value, fire htmx request, on success update display
~30 lines of JS.
Step 4: Manually test
Click a task card → panel slides in smoothly. Click backdrop or press Escape → panel slides out. Inline edit title, description, category, estimate. Verify smooth transitions.
Step 5: Commit
feat(web): add detail panel slide-in animation and inline editing
Task 10: Remove React App and Clean Up
Files:
- Delete:
TaskTracker.Web/directory (entire React app) - Modify:
.gitignore(remove node_modules entry if no longer needed)
Step 1: Verify all features work
Before deleting, do a final pass:
- Board: 4 columns render, drag-and-drop works, task creation works, filters work
- Detail panel: slides in, inline edit works, subtasks/notes work, action buttons work
- Search: Ctrl+K opens, search works, keyboard navigation works
- Analytics: stat cards, charts, activity feed, filters all work
- Mappings: CRUD table works with inline editing
Step 2: Delete the React app directory
rm -rf TaskTracker.Web/
Step 3: Update .gitignore
Remove any React/Node-specific entries. Keep entries relevant to .NET.
Step 4: Build the full solution
Run: dotnet build
Expected: All projects build successfully. No references to TaskTracker.Web.
Step 5: Commit
feat(web): remove React app — migration to Razor Pages complete
Task 11: Final Integration Test
Step 1: Run the app and test end-to-end
Start the API: dotnet run --project TaskTracker.Api
Test checklist:
- Navigate to
/board— Kanban columns load with tasks - Create a new task via inline form
- Drag task from Pending to Active
- Drag task from Active to Paused
- Drag task from Paused to Active (resume)
- Drag task to Completed
- Click task card — detail panel slides in
- Edit title, description, category, estimate inline
- Add a subtask, complete it
- Add a note
- Use action buttons (Start, Pause, Resume, Complete, Abandon)
- Ctrl+K search — type query, arrow navigate, enter to select
- Category filter chips on board
- Navigate to
/analytics— stat cards, charts, activity feed - Change time range and task filter dropdowns
- Click "Load more" on activity feed
- Navigate to
/mappings— table renders - Add a new mapping rule
- Edit an existing mapping
- Delete a mapping
- Verify API endpoints still work (
/api/tasks,/api/mappings,/swagger)
Step 2: Verify external consumers still work
- MCP server: uses
/api/*endpoints — unchanged - WindowWatcher: uses
/api/context— unchanged - Chrome extension: uses
/api/*— unchanged - Swagger UI:
/swagger— still accessible
Step 3: Commit any final fixes
fix(web): address integration test findings