From 56e2bc4cbc309cdfcd9bdfaf592c90604e65e264 Mon Sep 17 00:00:00 2001 From: Dawid Zbinski Date: Sun, 15 Mar 2026 23:33:59 +0100 Subject: [PATCH] refactor: use "(no task)" label for task-less log entries in reports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Log entries without an explicit task were previously displayed using their message text as the task/row name, which was misleading — it conflated the entry message with the grouping key. Now they consistently show "(no task)" so the report clearly distinguishes intentional task labels from untagged entries. Co-Authored-By: Claude Opus 4.6 (1M context) --- internal/timetrack/export_test.go | 4 ++-- internal/timetrack/timetrack.go | 2 +- internal/timetrack/timetrack_test.go | 29 +++++++++++++++++++++++++++- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/internal/timetrack/export_test.go b/internal/timetrack/export_test.go index ad9e1b4..eaaad1d 100644 --- a/internal/timetrack/export_test.go +++ b/internal/timetrack/export_test.go @@ -32,11 +32,11 @@ func TestBuildExportData_LogEntriesGroupedByTask(t *testing.T) { assert.Equal(t, 225, day.TotalMinutes) // 60+90+75 assert.Equal(t, 225, data.TotalMinutes) - // Should have 2 groups: "API design research" (no task, uses message) and "feature-auth" + // Should have 2 groups: "(no task)" and "feature-auth" require.Equal(t, 2, len(day.Groups)) // Groups sorted alphabetically - assert.Equal(t, "API design research", day.Groups[0].Task) + assert.Equal(t, "(no task)", day.Groups[0].Task) assert.Equal(t, 75, day.Groups[0].TotalMinutes) assert.Equal(t, 1, len(day.Groups[0].Entries)) diff --git a/internal/timetrack/timetrack.go b/internal/timetrack/timetrack.go index 60c7278..8012256 100644 --- a/internal/timetrack/timetrack.go +++ b/internal/timetrack/timetrack.go @@ -496,7 +496,7 @@ func logTaskKey(e entry.Entry) string { if e.Task != "" { return e.Task } - return e.Message + return "(no task)" } // daysIn returns the number of days in the given month. diff --git a/internal/timetrack/timetrack_test.go b/internal/timetrack/timetrack_test.go index acff18b..6261b16 100644 --- a/internal/timetrack/timetrack_test.go +++ b/internal/timetrack/timetrack_test.go @@ -123,7 +123,7 @@ func TestBuildReport_LogTaskKeyFallback(t *testing.T) { report := BuildReport(nil, logs, nil, days, year, month, afterMonth(year, month), nil) assert.Equal(t, 1, len(report.Rows)) - assert.Equal(t, "did research", report.Rows[0].Name) + assert.Equal(t, "(no task)", report.Rows[0].Name) assert.Equal(t, 120, report.Rows[0].TotalMinutes) } @@ -318,6 +318,33 @@ func TestBuildDetailedReport_LogEntries(t *testing.T) { assert.True(t, cd.Entries[1].Persisted) } +func TestBuildDetailedReport_LogTaskKeyFallback(t *testing.T) { + year, month := 2025, time.January + from := time.Date(year, month, 1, 0, 0, 0, 0, time.UTC) + to := time.Date(year, month, 31, 0, 0, 0, 0, time.UTC) + + days := []schedule.DaySchedule{workday(year, month, 2)} + + logs := []entry.Entry{ + {ID: "l1", Start: time.Date(2025, 1, 2, 10, 0, 0, 0, time.UTC), Minutes: 60, Message: "did research", Task: ""}, + {ID: "l2", Start: time.Date(2025, 1, 2, 11, 0, 0, 0, time.UTC), Minutes: 60, Message: "wrote docs", Task: ""}, + } + + report := BuildDetailedReport(nil, logs, nil, days, from, to, afterMonth(year, month)) + + assert.Equal(t, 1, len(report.Rows)) + row := findDetailedRow(report, "(no task)") + assert.NotNil(t, row) + assert.Equal(t, 120, row.TotalMinutes) + + cd := row.Days[2] + assert.NotNil(t, cd) + assert.Equal(t, 120, cd.TotalMinutes) + assert.Equal(t, 2, len(cd.Entries)) + assert.Equal(t, "did research", cd.Entries[0].Message) + assert.Equal(t, "wrote docs", cd.Entries[1].Message) +} + func TestBuildDetailedReport_CheckoutDeductedByLogs(t *testing.T) { year, month := 2025, time.January from := time.Date(year, month, 1, 0, 0, 0, 0, time.UTC)