taskly · board & time tracking
The Taskly Kanban board in dark mode: task cards spread across the Open, To-Do, In Progress, Testing and Done columns.

Overview

Project and issue tracking with a Kanban board, epics, time tracking, reporting and real-time notifications, on .NET 10 and Angular 22.

Someone triages the overnight cards, moves three onto the board and starts a timer. Everything underneath exists so two people editing the same task never silently lose an edit.

  • Issues with priority, status, labels, epics and a position on the board
  • Board, backlog, epic and resolved views for every project
  • Time tracking, a calendar and reporting charts
  • Live notifications, deadline reminders and a weekly digest
Four actors, grouped by the job they come to do.
Key facts
RoleSole author, backend, frontend, infrastructure and tooling
ShapeTwelve .NET projects, an Angular 22 client, an admin app and a deploy CLI
DataEF Core on SQL Server, soft delete, row-version concurrency
Real timeSignalR, with per-user groups and eight background workers
Tests3,666 backend test methods across 13 projects, 2,241 frontend specs
Public repositoryA demo variant: no sign-in, localStorage in place of the API

Architecture

Six layers, one direction, and controllers that only dispatch.

Every arrow points down; the admin app is a client of the API, not a back door.
  • A project may only reference the layer below it, so any handler can be tested against a fake.
  • A controller builds a command or a query and dispatches it. Permissions come from attributes, not from checks inside handlers.
  • The Angular client is standalone components on signal stores, one per domain slice, rendered on the server.
  • I wrote the CQRS layer, the Result type and a small mapper myself: cheaper to own than to configure.

Domain design

Two aggregates, and two rules the database enforces instead of convention.

The tables behind the board. SD marks soft delete, RV a row version.
  • Project and TaskItem are the aggregate roots. Handlers load and save only those.
  • Each save carries the row version the client read, so an edit made against a stale copy is refused with a 409.
  • A global query filter makes delete a flag. Hard delete is for administrators only.
  • One specification per entity keeps queries out of the handlers.

Data and control flow

One card moved on the board, followed to the other browser that sees it.

Checked against what the mover saw, then shown to everyone with the board open.

A board column loads every card and subtask in one batch request, not one per card. Eight background services dispatch the same commands controllers do, so a trial ends without anyone clicking anything.

Testing strategy

3,666 backend test methods and 2,241 frontend specs, with the handler as the unit.

  • A handler is tested with a fake repository and a principal, and asserted through its Result.
  • Routing, auth or the database make it an integration test by definition.

What I would change

  • Tasks and workflows share their shapes but diverged in code. One generic feature would remove about a thousand lines.
  • The reflective mapper was right at ten DTOs and is wrong at fifty: slower, and a rename only fails at runtime.
  • Subtasks do not send their row version or broadcast their moves yet. The task path is the template to copy.
  • A reorder writes one UPDATE per card inside a transaction. A single CASE statement would make it one round trip.
  • Permissions live in the API and are mirrored in the client. Sending them with the session would remove the drift.