This document describes the current system structure. It is the primary reference for any AI assistant or contributor working in this repo.
Tasklog is a two-process application. The backend and frontend run as separate servers and communicate over HTTP. They share no code.
Browser
│
├── GET http://localhost:3000 Next.js frontend (React, Tailwind)
│ │
│ └── fetch http://localhost:5115/api/... .NET Web API (ASP.NET Core, EF Core)
│ │
│ SQLite database
│ (TasklogDatabase.db)
└── GET http://localhost:3000/tasks/[id]
└── Server Component fetches from API on the server, returns rendered HTML
Tasklog/
├── backend/
│ └── Tasklog.Api/ .NET Web API project
│ ├── Controllers/ HTTP endpoint handlers
│ ├── Data/ EF Core DbContext
│ ├── Migrations/ EF Core schema migrations
│ ├── Models/ Data model classes
│ ├── Properties/ Launch settings (ports)
│ ├── Program.cs App startup and service registration
│ ├── appsettings.json Config (connection string, logging)
│ └── TasklogDatabase.db SQLite data file
│
├── frontend/
│ └── src/
│ ├── app/ Next.js App Router pages and layout
│ │ ├── layout.tsx Root layout (header, fonts, body wrapper)
│ │ ├── page.tsx Home route /
│ │ ├── globals.css Tailwind import + font tokens
│ │ └── tasks/[id]/
│ │ └── page.tsx Task detail route /tasks/:id
│ ├── components/ Reusable UI components
│ │ ├── ProjectLayout.tsx Sidebar + task list wrapper, owns activeView (Client Component)
│ │ ├── ProjectSidebar.tsx Project navigation and management (Client Component)
│ │ ├── TasksClient.tsx Task list + add form, filters by activeView (Client Component)
│ │ ├── AddTaskForm.tsx Add task form with optional project dropdown (Client Component)
│ │ ├── AssignProjectButton.tsx Project reassignment on detail page (Client Component)
│ │ ├── DeleteTaskButton.tsx Delete action on detail page (Client Component)
│ │ └── CompleteTaskButton.tsx Complete/incomplete toggle on detail page (Client Component)
│ └── lib/
│ └── api.ts Typed API call functions (used by both server and client)
│
├── docs/
│ ├── architecture.md This file
│ └── plans/ Implementation plans from planning sessions
│
├── CLAUDE.md Instructions for AI assistants
├── LESSONS.md Session learnings log
├── UI-SPEC.md Design tokens and UX rules for v2 frontend
└── Readme.md Human-facing project overview
Runtime: .NET 10 / ASP.NET Core Web API
Database: SQLite via Entity Framework Core 9
Default ports: HTTP 5115, HTTPS 7243 (see launchSettings.json)
HTTP request
│
▼
TasksController Handles routing, validation, HTTP response codes.
ProjectsController No business logic beyond input checking.
│
▼
TasklogDbContext EF Core context. Direct DbSet access - no repository layer.
│
▼
TasklogDatabase.db SQLite file. Two tables: Tasks, Projects.
Projects
Id INTEGER primary key, autoincrement
Name TEXT not null
CreatedAt TEXT not null (ISO 8601 datetime string)
Tasks
Id INTEGER primary key, autoincrement
Title TEXT not null
Deadline TEXT nullable (ISO 8601 date string)
CreatedAt TEXT not null (ISO 8601 datetime string)
IsCompleted INTEGER not null default 0 (boolean: 0 = pending, 1 = complete)
CompletedAt TEXT nullable (ISO 8601 datetime string, set when marked complete, cleared on un-complete)
ProjectId INTEGER nullable foreign key -> Projects.Id (null = Inbox)
Labels
Id INTEGER primary key, autoincrement
Name TEXT not null
ColorIndex INTEGER not null (0-9, maps to VIBGYOR palette in frontend)
CreatedAt TEXT not null (ISO 8601 datetime string)
LabelTaskModel (join table - implicit many-to-many)
LabelsId INTEGER not null foreign key -> Labels.Id (cascade delete)
TasksId INTEGER not null foreign key -> Tasks.Id (cascade delete)
| Method | Path | Description |
|---|---|---|
| GET | /api/tasks |
All tasks, ordered by CreatedAt descending |
| GET | /api/tasks/{id} |
Single task by ID. 404 if not found |
| POST | /api/tasks |
Create task. Body: { title, deadline?, projectId? } |
| DELETE | /api/tasks/{id} |
Delete task. 204 on success, 404 if not found |
| PATCH | /api/tasks/{id}/complete |
Mark task complete or incomplete. Body: { isCompleted: bool }. Returns updated task |
| PATCH | /api/tasks/{id}/project |
Reassign task to a project or Inbox. Body: { projectId: int? } |
| GET | /api/projects |
All projects, ordered by name |
| POST | /api/projects |
Create project. Body: { name: string }. Returns created project |
| PATCH | /api/projects/{id} |
Rename project. Body: { name: string }. Returns updated project |
| DELETE | /api/projects/{id} |
Delete project and cascade delete all its tasks. 204 on success |
| GET | /api/labels |
All labels, ordered by name |
| POST | /api/labels |
Create label. Body: { name, colorIndex }. Returns created label |
| PATCH | /api/labels/{id} |
Update label name and/or color. Body: { name, colorIndex }. Returns updated label |
| DELETE | /api/labels/{id} |
Delete label. Unlinks from all tasks (does not delete tasks). 204 on success |
| PATCH | /api/tasks/{id}/labels |
Replace task's label set. Body: { labelIds: int[] }. Returns updated task |
Enabled in Development mode only (Program.cs). Allows http://localhost:3000.
In production, a reverse proxy on the same host is assumed - no CORS needed.
Known issue: see GitHub issue #1 - LAN access currently breaks without this fix.
Runtime: Node.js / Next.js 16 (App Router)
Styling: Tailwind CSS v4
Fonts: Space Grotesk (headings), DM Sans (body) via next/font/google
Default port: 3000
Next.js uses file-based routing. Every page.tsx file maps to a URL.
Components are either Server Components (run on the server, no interactivity)
or Client Components (run in the browser, marked with "use client").
src/app/
layout.tsx Server Component. Runs on every request.
Loads fonts, renders header, wraps all pages in <main>.
page.tsx Server Component. Route: /
Renders <ProjectLayout />.
tasks/[id]/
page.tsx Server Component. Route: /tasks/:id
Fetches task and projects from API, renders detail card.
Returns 404 if task not found. Projects fallback to [] silently.
ProjectLayout.tsx Client Component.
- Owns activeView state ("all" | "inbox" | projectId)
- Fetches projects on mount
- Handles create/rename/delete project actions
- Shows error feedback banner for project operation failures
- Renders ProjectSidebar and TasksClient side by side
- On mobile: renders sidebar as a slide-in drawer
ProjectSidebar.tsx Client Component.
- Renders "All Tasks", "Inbox", and project list
- Highlights the active selection
- Create project: inline input at the bottom
- Rename project: opens an Edit Project modal dialog
- Delete project: opens a confirmation dialog (warns about cascade)
- Delegates all data operations to ProjectLayout via callbacks
TasksClient.tsx Client Component.
- Owns the task list state
- Fetches all tasks on mount (useEffect)
- Filters tasks client-side by activeView prop
- Handles add, delete, and completion operations
- Shows loading spinner, inline feedback messages
- Renders task table with AddTaskForm below
- Project column shown only in "all" view
- Toggle to show/hide completed tasks
AddTaskForm.tsx Client Component.
- Owns title, deadline, and project dropdown state
- Project dropdown pre-selected to active project view
- Syncs dropdown when defaultProjectId prop changes
- Validates title not empty before calling parent's onAdd
- Shows inline field-level error messages
DeleteTaskButton.tsx Client Component.
- Used only on the task detail page
- Calls DELETE API, then redirects to home on success
- Shows spinner during request, error on failure
CompleteTaskButton.tsx Client Component.
- Used only on the task detail page
- Toggles IsCompleted via PATCH API, then calls router.refresh()
- Shows "Mark complete" or "Mark incomplete" based on current state
- Shows spinner during request, error on failure
AssignProjectButton.tsx Client Component.
- Used only on the task detail page
- Dropdown to reassign task to a project or Inbox
- Calls PATCH /api/tasks/{id}/project, then router.refresh()
- State only updates after API confirms (no optimistic update)
LabelChip.tsx Client Component.
- Shared colored pill chip for label display
- Optional onRemove callback renders an × button
- Background color derived from label.colorIndex via labelColor()
AssignLabelsButton.tsx Client Component.
- Used only on the task detail page
- Shows current labels as LabelChip components with remove buttons
- Select dropdown to add unassigned labels
- Calls PATCH /api/tasks/{id}/labels on each change, then router.refresh()
LabelsClient.tsx Client Component.
- Used on the /labels page
- Full CRUD: fetch, create, inline rename, color picker, delete
- Desktop: table layout. Mobile: card list.
ColorPicker.tsx Client Component.
- 10-color swatch grid popover
- Used by LabelsClient for color assignment/editing
FilterPanel.tsx Client Component.
- Popover opened from the task list header three-dot button
- Filter sections: Labels (multi-select chips), Project (checkboxes), Date (radio)
- Apply and Clear buttons; draft state committed only on Apply
All fetch calls go through src/lib/api.ts. This is the only place
that knows the API base URL and constructs request shapes.
getTasks() GET /api/tasks Used by TasksClient (client-side)
getTask(id) GET /api/tasks/:id Used by tasks/[id]/page.tsx (server-side)
createTask() POST /api/tasks Used by TasksClient via AddTaskForm callback
deleteTask(id) DELETE /api/tasks/:id Used by TasksClient and DeleteTaskButton
completeTask(id, bool) PATCH /api/tasks/:id/complete Used by TasksClient and CompleteTaskButton
assignTaskProject(id, pid) PATCH /api/tasks/:id/project Used by AssignProjectButton
getProjects() GET /api/projects Used by ProjectLayout and tasks/[id]/page.tsx
createProject(name) POST /api/projects Used by ProjectLayout
renameProject(id, name) PATCH /api/projects/:id Used by ProjectLayout
deleteProject(id) DELETE /api/projects/:id Used by ProjectLayout
Known issue: getTask() uses NEXT_PUBLIC_API_URL which resolves to localhost
from a server-side Node.js process. Breaks in production. See GitHub issue #1.
NEXT_PUBLIC_API_URL Base URL for API calls. Defined in frontend/.env.local.
Currently: http://localhost:5115
NEXT_PUBLIC_ prefix = available in both browser and server code.
Opening the home page:
1. Browser requests http://localhost:3000/
2. Next.js renders layout.tsx + page.tsx on server (Server Components)
3. HTML shell sent to browser
4. Browser loads TasksClient.tsx JavaScript
5. TasksClient useEffect fires: GET http://localhost:5115/api/tasks
6. .NET queries SQLite, returns JSON array
7. React renders the task table
Adding a task:
1. User fills form and clicks "Add Task"
2. AddTaskForm validates input, calls onAdd() callback
3. TasksClient calls createTask() in api.ts
4. POST http://localhost:5115/api/tasks with JSON body
5. .NET inserts row in SQLite, returns new task JSON
6. TasksClient prepends task to local state
7. React re-renders the list - no page reload
Viewing a task detail:
1. Browser requests http://localhost:3000/tasks/42
2. Next.js runs tasks/[id]/page.tsx on the server
3. Server calls getTask(42): GET http://localhost:5115/api/tasks/42
4. .NET returns task JSON
5. Server renders HTML with task data
6. Browser receives complete HTML - no client-side fetch needed
These are tracked as GitHub issues:
| Issue | Description |
|---|---|
| #1 | CORS and server-side localhost URL break app outside dev |
| #2 | Optimistic delete and feedback timer state bugs |
| #3 | Fragile database path and silent API URL failure |
These are planned but not built:
- Pagination
- Authentication
- Production deployment configuration