Skip to content

Commit debdf11

Browse files
committed
Merge feature/task-priority-#64: task priority P1-P4 (v2.10.5)
2 parents c7f95c5 + b4a0297 commit debdf11

32 files changed

Lines changed: 832 additions & 52 deletions

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22

33
---
44

5+
## v2.10.5 - Task priority (P1-P4)
6+
*May 2026*
7+
8+
### Added
9+
10+
- Tasks now have a priority on the Todoist P1-P4 scale: P1 = Urgent (red), P2 = High (orange), P3 = Medium (blue), P4 = None (the default). Stored as a new `Priority` column (DB migration; existing tasks default to P4) (#64)
11+
- Set priority on the add-task form and the edit modal; a small colored dot appears next to the title for P1-P3 (P4 shows nothing). A priority filter row on the filter panel narrows the list by one or more priorities
12+
- MCP: `create_task` and `update_task` accept a `priority` param (1-4), `list_tasks` accepts a `priorities` filter, and the priority is included in returned task objects - so Claude can "make this P1" or answer "what's P1 and due this week?" in one call
13+
14+
---
15+
516
## v2.10.4 - Bulk task operations
617
*May 2026*
718

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ I wanted a task system I understood completely - one where the data, the workflo
5050
- Deadline color coding - red when overdue, yellow when due within 3 days
5151
- Edit a task's title, deadline, project, and labels from the list; quick-set deadlines (Today, Tomorrow, This weekend, Next week) or clear them with one tap
5252
- Multi-select to act on many tasks at once - complete, move to a project, or set a deadline in one step
53+
- Set a priority (P1-P4) per task, shown as a colored dot and filterable
5354
- Checkbox completion with a clean animation - done tasks step aside, not deleted
5455
- Show/hide completed tasks and undo completion at any time
5556
- Task detail page with full status and completion history
@@ -122,7 +123,6 @@ That's it. The app opens in your browser.
122123

123124
- Dark mode and custom themes
124125
- Dedicated device setup guide (Raspberry Pi, Term home server)
125-
- Task priority levels
126126

127127
### v3.0 - AI and integrations
128128

backend/Tasklog.Api/Controllers/TasksController.cs

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,13 @@ public async Task<IActionResult> GetAll([FromQuery] TaskFilterQuery filter)
9292
query = query.Where(t => t.Title.ToLower().Contains(lowered));
9393
}
9494

95+
// priorities: OR within the list - a task matches if its priority is any of
96+
// the requested values (e.g. ?priorities=1&priorities=2 = P1 or P2).
97+
if (filter.Priorities is { Length: > 0 })
98+
{
99+
query = query.Where(t => filter.Priorities.Contains(t.Priority));
100+
}
101+
95102
var tasks = await query
96103
.OrderByDescending(t => t.CreatedAt)
97104
.ToListAsync();
@@ -124,13 +131,20 @@ public async Task<IActionResult> Create([FromBody] CreateTaskRequest request)
124131
if (string.IsNullOrWhiteSpace(request.Title))
125132
return BadRequest(new { message = "Title is required." });
126133

134+
// Priority is optional on create; omitted defaults to 4 (P4 = none).
135+
// When provided it must be on the P1-P4 scale.
136+
var priority = request.Priority ?? 4;
137+
if (priority < 1 || priority > 4)
138+
return BadRequest(new { message = "Priority must be between 1 (P1) and 4 (P4)." });
139+
127140
var task = new TaskModel
128141
{
129142
Title = request.Title.Trim(),
130143
Deadline = request.Deadline,
131144
CreatedAt = DateTime.Now,
132145
// Null means the task goes to Inbox (uncategorized).
133-
ProjectId = request.ProjectId
146+
ProjectId = request.ProjectId,
147+
Priority = priority
134148
};
135149

136150
_context.Tasks.Add(task);
@@ -187,6 +201,15 @@ public async Task<IActionResult> Update(int id, [FromBody] JsonElement body)
187201
}
188202
}
189203

204+
// priority: present must be a number on the P1-P4 scale (1-4). There is no
205+
// "clear" - P4 is the no-priority state. Absent leaves it unchanged.
206+
if (body.TryGetProperty("priority", out var priorityEl))
207+
{
208+
if (priorityEl.ValueKind != JsonValueKind.Number || !priorityEl.TryGetInt32(out var p) || p < 1 || p > 4)
209+
return BadRequest(new { message = "Priority must be a number between 1 (P1) and 4 (P4)." });
210+
task.Priority = p;
211+
}
212+
190213
await _context.SaveChangesAsync();
191214

192215
return Ok(task);
@@ -356,8 +379,8 @@ public async Task<IActionResult> Bulk([FromBody] BulkTaskRequest request)
356379
}
357380
}
358381

359-
// Request body shape for task creation.
360-
public record CreateTaskRequest(string Title, DateTime? Deadline, int? ProjectId);
382+
// Request body shape for task creation. Priority is optional (defaults to 4 = none).
383+
public record CreateTaskRequest(string Title, DateTime? Deadline, int? ProjectId, int? Priority = null);
361384

362385
// Request body shape for toggling task completion.
363386
public record CompleteTaskRequest(bool IsCompleted);
@@ -386,12 +409,14 @@ public record BulkTaskData(bool? IsCompleted, int? ProjectId, string? Deadline);
386409
// dueBefore / dueAfter - ISO 8601 dates (yyyy-MM-dd)
387410
// completed / inbox - "true" or "false"
388411
// text - free substring for case-insensitive title match
412+
// priorities - repeated keys (P1-P4 values), OR within
389413
public record TaskFilterQuery(
390414
int[]? ProjectIds,
391415
bool? Inbox,
392416
int[]? LabelIds,
393417
DateTime? DueBefore,
394418
DateTime? DueAfter,
395419
bool? Completed,
396-
string? Text);
420+
string? Text,
421+
int[]? Priorities = null);
397422
}

backend/Tasklog.Api/Data/TasklogDbContext.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
1818
modelBuilder.Entity<TaskModel>()
1919
.HasMany(t => t.Labels)
2020
.WithMany(l => l.Tasks);
21+
22+
// Priority defaults to 4 (P4 = none) at the DB level. This is what existing
23+
// rows migrate to - without it, EF would use the CLR default of 0, which is
24+
// outside the valid 1-4 range. New rows still set 4 via the model initializer.
25+
modelBuilder.Entity<TaskModel>()
26+
.Property(t => t.Priority)
27+
.HasDefaultValue(4);
2128
}
2229
}
2330
}

backend/Tasklog.Api/Migrations/20260526211758_AddPriority.Designer.cs

Lines changed: 145 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using Microsoft.EntityFrameworkCore.Migrations;
2+
3+
#nullable disable
4+
5+
namespace Tasklog.Api.Migrations
6+
{
7+
/// <inheritdoc />
8+
public partial class AddPriority : Migration
9+
{
10+
/// <inheritdoc />
11+
protected override void Up(MigrationBuilder migrationBuilder)
12+
{
13+
migrationBuilder.AddColumn<int>(
14+
name: "Priority",
15+
table: "Tasks",
16+
type: "INTEGER",
17+
nullable: false,
18+
defaultValue: 4);
19+
}
20+
21+
/// <inheritdoc />
22+
protected override void Down(MigrationBuilder migrationBuilder)
23+
{
24+
migrationBuilder.DropColumn(
25+
name: "Priority",
26+
table: "Tasks");
27+
}
28+
}
29+
}

backend/Tasklog.Api/Migrations/TasklogDbContextModelSnapshot.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ protected override void BuildModel(ModelBuilder modelBuilder)
8989
b.Property<bool>("IsCompleted")
9090
.HasColumnType("INTEGER");
9191

92+
b.Property<int>("Priority")
93+
.ValueGeneratedOnAdd()
94+
.HasColumnType("INTEGER")
95+
.HasDefaultValue(4);
96+
9297
b.Property<int?>("ProjectId")
9398
.HasColumnType("INTEGER");
9499

backend/Tasklog.Api/Models/TaskModel.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ public class TaskModel
1818
public int? ProjectId { get; set; }
1919
public Project? Project { get; set; }
2020

21+
// Priority on the Todoist P1-P4 scale: 1 = Urgent, 2 = High, 3 = Medium, 4 = None.
22+
// P1 is the highest urgency (ascending sort = most urgent first). Non-null;
23+
// P4 is the default "no priority" state, so existing rows migrate to 4.
24+
public int Priority { get; set; } = 4;
25+
2126
// Labels applied to this task. Many-to-many - a task can have multiple labels.
2227
public ICollection<Label> Labels { get; set; } = new List<Label>();
2328

0 commit comments

Comments
 (0)