commit 86e8c56194c317b68458dfbcc2258a98684a6b56
parent 07c87fa6a514712c209f8e54d3678105a0c06e30
Author: Damian Myrda <damian@prime8.dev>
Date: Wed, 21 Oct 2026 08:01:05 +0000
feat: show all-time wakatime total, hide daily
WakaTime is back on, but only the all-time "N+ hrs coded" total in the
web status bar and the TUI footer. The daily breakdown (wakatime --week)
stays hidden behind config.WakaTimeWeeklyEnabled. /api/wakatime and the
TUI now fetch only all_time_since_today unless weekly is enabled.
Diffstat:
8 files changed, 111 insertions(+), 25 deletions(-)
diff --git a/cmd/server/handlers/wakatime.go b/cmd/server/handlers/wakatime.go
@@ -3,10 +3,16 @@ package handlers
import (
"encoding/json"
"net/http"
+
+ "git.prime8.dev/moncheeta/prime8dev/internal/config"
)
func (h *Handler) WakaTimeHandler(w http.ResponseWriter, r *http.Request) {
- stats, err := h.wakaTimeService.FetchStats()
+ fetch := h.wakaTimeService.FetchTotal
+ if config.WakaTimeWeeklyEnabled {
+ fetch = h.wakaTimeService.FetchStats
+ }
+ stats, err := fetch()
if err != nil {
http.Error(w, "Failed to fetch stats", http.StatusInternalServerError)
return
diff --git a/internal/config/site.go b/internal/config/site.go
@@ -5,5 +5,10 @@ const (
SiteURL = "https://" + SiteDomain
)
-// WakaTimeEnabled toggles the WakaTime stats on the web and SSH frontends.
-const WakaTimeEnabled = false
+// WakaTimeEnabled toggles the all-time WakaTime total ("N+ hrs coded") in the
+// web status bar and the SSH TUI footer.
+const WakaTimeEnabled = true
+
+// WakaTimeWeeklyEnabled toggles the daily breakdown (wakatime --week) on the
+// about page. Off: only the all-time total is shown.
+const WakaTimeWeeklyEnabled = false
diff --git a/internal/external/wakatime.go b/internal/external/wakatime.go
@@ -66,6 +66,36 @@ func (s *WakaTime) FetchStats() (*WakaTimeStats, error) {
return stats, nil
}
+// FetchTotal returns only the all-time total ("N+ hrs coded"), skipping the
+// daily/weekly endpoints. It returns nil when no API key is configured.
+func (s *WakaTime) FetchTotal() (*WakaTimeStats, error) {
+ if s.apiKey == "" {
+ return nil, nil
+ }
+
+ cacheKey := "wakatime:total"
+ if val, found := s.cache.Get(cacheKey); found {
+ return val.(*WakaTimeStats), nil
+ }
+
+ resp, err := s.makeRequest("/users/current/all_time_since_today")
+ if err != nil {
+ return nil, err
+ }
+ var data allTimeResponse
+ if err := json.Unmarshal(resp, &data); err != nil {
+ return nil, err
+ }
+
+ stats := &WakaTimeStats{AllTime: AllTimeStats{TotalText: formatTotalHours(data.Data.TotalSeconds)}}
+ s.cache.Set(cacheKey, stats, 5*time.Minute)
+ return stats, nil
+}
+
+func formatTotalHours(totalSeconds float64) string {
+ return fmt.Sprintf("%d+ hrs coded", int(totalSeconds/3600))
+}
+
type allTimeResponse struct {
Data struct {
TotalSeconds float64 `json:"total_seconds"`
@@ -140,8 +170,7 @@ func (s *WakaTime) fetchAllTimeStats() (*AllTimeStats, error) {
if stats != nil {
totalSeconds = stats.Data.TotalSeconds
}
- totalHours := int(totalSeconds / 3600)
- totalText := fmt.Sprintf("%d+ hrs coded", totalHours)
+ totalText := formatTotalHours(totalSeconds)
dailyAvgText := "0 mins"
if langStats != nil && langStats.Data.DailyAverageIncludingOtherLanguage > 0 {
diff --git a/internal/external/wakatime_test.go b/internal/external/wakatime_test.go
@@ -0,0 +1,41 @@
+package external
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+ "time"
+
+ "github.com/patrickmn/go-cache"
+)
+
+func TestFetchTotalOnlyHitsAllTime(t *testing.T) {
+ var paths []string
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ paths = append(paths, r.URL.Path)
+ _, _ = w.Write([]byte(`{"data":{"total_seconds":3603600}}`))
+ }))
+ defer srv.Close()
+
+ w := &WakaTime{httpClient: srv.Client(), baseURL: srv.URL, apiKey: "k", cache: cache.New(time.Minute, time.Minute)}
+ stats, err := w.FetchTotal()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if stats.AllTime.TotalText != "1001+ hrs coded" {
+ t.Fatalf("total = %q", stats.AllTime.TotalText)
+ }
+ if stats.AllTime.DailyAverageText != "" || len(stats.Weekly.Days) != 0 {
+ t.Fatalf("daily stats leaked: %+v", stats)
+ }
+ if len(paths) != 1 || paths[0] != "/users/current/all_time_since_today" {
+ t.Fatalf("requests = %v", paths)
+ }
+}
+
+func TestFetchTotalWithoutKey(t *testing.T) {
+ w := &WakaTime{apiKey: "", cache: cache.New(time.Minute, time.Minute)}
+ if stats, err := w.FetchTotal(); stats != nil || err != nil {
+ t.Fatalf("got %v, %v", stats, err)
+ }
+}
diff --git a/internal/tui/content.go b/internal/tui/content.go
@@ -30,7 +30,7 @@ func (m portfolioModel) aboutDetail() string {
"",
m.shellView(shellW),
}
- if appconfig.WakaTimeEnabled {
+ if appconfig.WakaTimeEnabled && appconfig.WakaTimeWeeklyEnabled {
parts = append(parts, "", renderPrompt(m.about.Handle, "wakatime --week"), m.aboutWeeklyStats())
}
diff --git a/internal/tui/waka_footer.go b/internal/tui/waka_footer.go
@@ -31,7 +31,11 @@ func (m *portfolioModel) maybeLoadWakaFooterCmd() tea.Cmd {
func fetchWakaFooterCmd(service *external.WakaTime) tea.Cmd {
return func() tea.Msg {
- stats, err := service.FetchStats()
+ fetch := service.FetchTotal
+ if config.WakaTimeWeeklyEnabled {
+ fetch = service.FetchStats
+ }
+ stats, err := fetch()
if err != nil || stats == nil {
return wakaFooterMsg{ok: false}
}
diff --git a/internal/web/renderer.go b/internal/web/renderer.go
@@ -54,23 +54,24 @@ func New() (*Renderer, error) {
}
funcs := template.FuncMap{
- "tabPath": utils.TabPath,
- "tabURL": utils.TabURL,
- "tagClass": utils.TagClass,
- "tagColor": func(label string) template.CSS { return template.CSS(utils.TagColor(label)) },
- "shellPath": ShellPath,
- "tagCSS": func() template.CSS { return template.CSS(utils.TagCSS()) },
- "inc": func(i int) int { return i + 1 },
- "techMeta": utils.TechMeta,
- "formatMonthYear": utils.FormatMonthYear,
- "formatEndDate": utils.FormatEndDate,
- "formatDuration": utils.FormatDuration,
- "durationHasYear": utils.DurationHasYear,
- "timelineYear": utils.TimelineYear,
- "toHTML": toHTML,
- "gitCloneURL": config.GitCloneURL,
- "gitSSHCloneURL": config.GitSSHCloneURL,
- "wakaTimeEnabled": func() bool { return config.WakaTimeEnabled },
+ "tabPath": utils.TabPath,
+ "tabURL": utils.TabURL,
+ "tagClass": utils.TagClass,
+ "tagColor": func(label string) template.CSS { return template.CSS(utils.TagColor(label)) },
+ "shellPath": ShellPath,
+ "tagCSS": func() template.CSS { return template.CSS(utils.TagCSS()) },
+ "inc": func(i int) int { return i + 1 },
+ "techMeta": utils.TechMeta,
+ "formatMonthYear": utils.FormatMonthYear,
+ "formatEndDate": utils.FormatEndDate,
+ "formatDuration": utils.FormatDuration,
+ "durationHasYear": utils.DurationHasYear,
+ "timelineYear": utils.TimelineYear,
+ "toHTML": toHTML,
+ "gitCloneURL": config.GitCloneURL,
+ "gitSSHCloneURL": config.GitSSHCloneURL,
+ "wakaTimeEnabled": func() bool { return config.WakaTimeEnabled },
+ "wakaTimeWeeklyEnabled": func() bool { return config.WakaTimeEnabled && config.WakaTimeWeeklyEnabled },
"inlineCSS": func(name string) template.CSS {
if css, ok := inlineStyles[name]; ok {
return css
diff --git a/internal/web/templates/partials/about.gohtml b/internal/web/templates/partials/about.gohtml
@@ -46,7 +46,7 @@
<div class="shell-prompt"><span class="shell-user">{{.About.Handle}}</span>@<span class="shell-host">prime8</span>:<span class="shell-path">~</span><span class="shell-dollar">$</span> <span class="shell-cursor" aria-hidden="true"> </span></div>
</div>
- {{if wakaTimeEnabled}}
+ {{if wakaTimeWeeklyEnabled}}
<div class="section" id="activity-section">
<div class="shell-prompt"><span class="shell-user">{{.About.Handle}}</span>@<span class="shell-host">prime8</span>:<span class="shell-path">~</span><span class="shell-dollar">$</span> <span class="shell-cmd">wakatime --week</span></div>
<div class="weekly-stats" id="activity-stats">