docs: add Razor Pages migration design

Design for moving the TaskTracker web UI from React/npm to
Razor Pages + htmx, eliminating the Node toolchain and unifying
on a single .NET stack with one-binary deployment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 21:47:08 -05:00
parent 864e5b712c
commit e04e9573ea

View File

@@ -0,0 +1,160 @@
# TaskTracker Web UI: React to Razor Pages Migration
## Date: 2026-03-01
## Motivation
- **Single tech stack:** Eliminate the npm/Node toolchain; everything in C#/.NET
- **Single-binary deployment:** UI bundled into the API project — one process, one port, no separate build step
## Approach: Razor Pages + htmx
Server-rendered HTML with Razor syntax. htmx handles partial page updates via AJAX. Vanilla JS for drag-and-drop (SortableJS), charts (Chart.js), and keyboard shortcuts.
## Architecture
### Single Process
Razor Pages are added directly to the existing `TaskTracker.Api` project. No separate web project.
- One `Program.cs`, one binary, one port
- Razor Pages call repositories directly (same DI container) — no API round-trips for the UI
- API controllers remain at `/api/*` for external consumers (MCP, WindowWatcher, Chrome extension)
- `wwwroot/` hosts static JS/CSS files
### File Structure
```
TaskTracker.Api/
├── Pages/
│ ├── _ViewImports.cshtml
│ ├── _ViewStart.cshtml
│ ├── Shared/
│ │ └── _Layout.cshtml (shell: nav bar, search modal, script tags)
│ ├── Board.cshtml + Board.cshtml.cs
│ ├── Analytics.cshtml + Analytics.cshtml.cs
│ └── Mappings.cshtml + Mappings.cshtml.cs
├── Pages/Partials/
│ ├── _KanbanColumn.cshtml (single column, htmx swappable)
│ ├── _TaskCard.cshtml (single card)
│ ├── _TaskDetail.cshtml (slide-in detail panel)
│ ├── _FilterBar.cshtml (category chip bar)
│ ├── _SubtaskList.cshtml
│ ├── _NotesList.cshtml
│ ├── _CreateTaskForm.cshtml
│ ├── _MappingRow.cshtml (single table row, inline edit)
│ └── _SearchResults.cshtml (command palette results)
├── wwwroot/
│ ├── css/site.css (dark theme, design tokens, animations)
│ ├── lib/htmx.min.js (~14KB gzipped)
│ ├── lib/Sortable.min.js (~40KB)
│ ├── lib/chart.min.js (~65KB gzipped)
│ └── js/app.js (command palette, keyboard shortcuts, DnD wiring)
└── Program.cs (adds AddRazorPages + MapRazorPages)
```
### Program.cs Changes
```csharp
builder.Services.AddRazorPages();
// ... existing setup ...
app.MapRazorPages(); // Add alongside MapControllers()
app.MapControllers(); // Keep for API consumers
```
## Client-Side Libraries (all vendored, no npm)
| Library | Size (gzipped) | Purpose |
|---------|---------------|---------|
| htmx | ~14KB | Partial page updates via AJAX |
| SortableJS | ~40KB | Drag-and-drop Kanban columns |
| Chart.js | ~65KB | Timeline bar chart, category donut |
| app.js | ~100 lines | Command palette, keyboard shortcuts, DnD wiring |
## Styling
Plain CSS (no Tailwind, no build step). Same dark design system ported from the React app:
- CSS custom properties for all design tokens (`--color-page`, `--color-surface`, `--color-accent`, etc.)
- Same animations: `animate-pulse-glow`, `animate-live-dot`, `card-glow`
- Custom scrollbars, selection highlight, grain texture
## Page-by-Page Design
### Board Page
- 4 Kanban columns rendered server-side as partials
- **Drag-and-drop:** SortableJS on each column; `onEnd` callback reads task ID + target column status, fires htmx request:
- Pending → Active: `PUT /board/tasks/{id}/start`
- Active → Paused: `PUT /board/tasks/{id}/pause`
- Paused → Active: `PUT /board/tasks/{id}/resume`
- Any → Completed: `PUT /board/tasks/{id}/complete`
- Server returns updated column partials
- **Task detail panel:** `hx-get="/board/tasks/{id}/detail"` loads partial into right-side container; CSS transition handles slide-in
- **Inline editing:** Click-to-edit fields use `hx-put` on blur/Enter
- **Subtasks/notes:** Rendered as partials inside detail panel; htmx handles add/complete
- **Filter bar:** htmx re-requests board with query params (`?category=Development&hasSubtasks=true`)
- **Create task:** Inline form in Pending column, htmx POST
### Analytics Page
- Stat cards rendered server-side (open tasks count, active time, top category)
- **Charts:** Chart.js renders from JSON data serialized into `<script>` tags by the server
- Timeline: bar chart with events bucketed by hour, colored by category
- Category breakdown: donut chart with legend
- **Activity feed:** Initial batch server-rendered; "Load more" button uses htmx to append items
### Mappings Page
- Table rows rendered server-side
- **Inline edit:** Click "Edit" → htmx swaps row with editable form row partial
- **Add new:** htmx inserts empty form row at top
- **Delete:** `hx-delete` with `hx-confirm` for confirmation
### Search Modal (Ctrl+K)
- `app.js` handles keyboard shortcut and modal open/close
- On keystroke: htmx fetches `/board/search?q={query}`, swaps results list
- Arrow key navigation + Enter-to-select in JS (~80 lines)
- When empty: shows recent Active/Paused/Pending tasks
## htmx Endpoint Design
Razor Pages handler methods return HTML partials (not JSON). Examples:
| Endpoint | Method | Returns |
|----------|--------|---------|
| `/board` | GET | Full board page |
| `/board?handler=Column&status=Active` | GET | Single column partial |
| `/board?handler=TaskDetail&id=5` | GET | Detail panel partial |
| `/board?handler=Search&q=auth` | GET | Search results partial |
| `/board?handler=Start&id=5` | PUT | Updated board columns |
| `/board?handler=Pause&id=5` | PUT | Updated board columns |
| `/board?handler=Resume&id=5` | PUT | Updated board columns |
| `/board?handler=Complete&id=5` | PUT | Updated board columns |
| `/board?handler=CreateTask` | POST | Updated Pending column |
| `/board?handler=UpdateTask&id=5` | PUT | Updated task card |
| `/board?handler=AddSubtask&id=5` | POST | Updated subtask list |
| `/board?handler=AddNote&id=5` | POST | Updated notes list |
| `/analytics` | GET | Full analytics page |
| `/analytics?handler=ActivityFeed&offset=20` | GET | More activity items |
| `/mappings` | GET | Full mappings page |
| `/mappings?handler=EditRow&id=3` | GET | Editable row partial |
| `/mappings?handler=Save` | POST/PUT | Updated row partial |
| `/mappings?handler=Delete&id=3` | DELETE | Empty (row removed) |
## What Gets Removed
- `TaskTracker.Web/` directory (React app: node_modules, package.json, src/, dist/, etc.)
- `UseDefaultFiles()` in Program.cs (Razor Pages handle routing)
- CORS configuration becomes optional (UI is same-origin)
## What Stays Unchanged
- All API controllers (`/api/*`) — used by MCP, WindowWatcher, Chrome extension
- `TaskTracker.Core` — entities, DTOs, interfaces
- `TaskTracker.Infrastructure` — EF Core, repositories, migrations
- `TaskTracker.MCP` — MCP server
- `WindowWatcher` — context watcher service