From 04abdafa9a011a5ca13b8295d8a0cd520f8f990d Mon Sep 17 00:00:00 2001 From: teernisse Date: Thu, 19 Feb 2026 15:01:26 -0500 Subject: [PATCH] feat: zero-fill missing days in aggregator for continuous chart data The daily aggregation now iterates from the since date through the until date and inserts a zero-valued DailyStats entry for any day not already present in the day map. This ensures sparklines and bar charts render a continuous time axis with explicit zeros for idle days, rather than connecting adjacent data points across gaps. Also switch config file creation to os.OpenFile with explicit 0600 permissions and O_WRONLY|O_CREATE|O_TRUNC flags, matching the intent of the original os.Create call while making the restricted permission bits explicit for security. --- internal/config/config.go | 2 +- internal/pipeline/aggregator.go | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/internal/config/config.go b/internal/config/config.go index 5913425..db0fca7 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -107,7 +107,7 @@ func Save(cfg Config) error { return fmt.Errorf("creating config dir: %w", err) } - f, err := os.Create(ConfigPath()) + f, err := os.OpenFile(ConfigPath(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) if err != nil { return fmt.Errorf("creating config file: %w", err) } diff --git a/internal/pipeline/aggregator.go b/internal/pipeline/aggregator.go index 906d6a1..39f191b 100644 --- a/internal/pipeline/aggregator.go +++ b/internal/pipeline/aggregator.go @@ -97,6 +97,17 @@ func AggregateDays(sessions []model.SessionStats, since, until time.Time) []mode ds.EstimatedCost += s.EstimatedCost } + // Fill in every day in the range so the chart shows gaps as zeros + day := since.Local().Truncate(24 * time.Hour) + end := until.Local().Truncate(24 * time.Hour) + for !day.After(end) { + dayKey := day.Format("2006-01-02") + if _, ok := dayMap[dayKey]; !ok { + dayMap[dayKey] = &model.DailyStats{Date: day} + } + day = day.AddDate(0, 0, 1) + } + // Convert to sorted slice (most recent first) days := make([]model.DailyStats, 0, len(dayMap)) for _, ds := range dayMap {