From 101ca33069c7240731a685d315042a0f4421b60c Mon Sep 17 00:00:00 2001 From: Kristian Wind Date: Tue, 18 Aug 2026 09:05:18 +0200 Subject: [PATCH] feat(tasks): a row says what is underneath it, and what is attached A task with three sub-tasks looked exactly like one without, and so did a task with a drawing on it. The whole reason to attach something is that somebody should find it. Counted in the same query as the tasks, as correlated subqueries: a list of fifty would otherwise be a hundred more lookups, and the point is that you see this without asking. Said as a fraction rather than a total. "3" tells you something is there; "1/3" tells you whether there is anything left to do, which is the question a list is for. All-done goes accent-coloured, because "3/3" and "1/3" are the two answers you actually look for and only one of them is good news. Co-Authored-By: Claude Opus 5 --- internal/httpapi/tasks_handlers.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/internal/httpapi/tasks_handlers.go b/internal/httpapi/tasks_handlers.go index 655601e..d0f1f8f 100644 --- a/internal/httpapi/tasks_handlers.go +++ b/internal/httpapi/tasks_handlers.go @@ -33,9 +33,14 @@ type taskJSON struct { Completed bool `json:"completed"` CompletedAt string `json:"completed_at,omitempty"` CreatedBy string `json:"created_by"` - SortOrder float64 `json:"sort_order"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` + // What the row can say without being opened. Sent with every task rather than + // fetched when one is opened, because the point is that you see it in the list. + SubtaskCount int `json:"subtask_count,omitempty"` + SubtaskDone int `json:"subtask_done,omitempty"` + AttachmentCount int `json:"attachment_count,omitempty"` + SortOrder float64 `json:"sort_order"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` } func toTaskJSON(t store.Task) taskJSON { @@ -45,9 +50,11 @@ func toTaskJSON(t store.Task) taskJSON { DueDate: t.DueDate, DurationMin: t.DurationMin, Recurrence: t.RecurrenceRule, RepeatText: recurrence.Describe(t.RecurrenceRule), AssigneeID: t.AssigneeID, Completed: t.Completed(), CreatedBy: t.CreatedBy, - SortOrder: t.SortOrder, - CreatedAt: t.CreatedAt.Format(time.RFC3339), - UpdatedAt: t.UpdatedAt.Format(time.RFC3339), + SubtaskCount: t.SubtaskCount, SubtaskDone: t.SubtaskDone, + AttachmentCount: t.AttachmentCount, + SortOrder: t.SortOrder, + CreatedAt: t.CreatedAt.Format(time.RFC3339), + UpdatedAt: t.UpdatedAt.Format(time.RFC3339), // Never nil: a client iterating labels should not have to null-check first. Labels: t.Labels, }