prime8dev

prime8dev
git clone https://git.prime8.dev/prime8dev.git
Log | Files | Refs | README | LICENSE

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:
Mcmd/server/handlers/wakatime.go | 8+++++++-
Minternal/config/site.go | 9+++++++--
Minternal/external/wakatime.go | 33+++++++++++++++++++++++++++++++--
Ainternal/external/wakatime_test.go | 41+++++++++++++++++++++++++++++++++++++++++
Minternal/tui/content.go | 2+-
Minternal/tui/waka_footer.go | 6+++++-
Minternal/web/renderer.go | 35++++++++++++++++++-----------------
Minternal/web/templates/partials/about.gohtml | 2+-
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">&nbsp;</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">