From f184ac84099b3aab5b6742a705b2d2f3fe93c1db Mon Sep 17 00:00:00 2001 From: Tien Nguyen Minh Date: Sun, 19 Apr 2026 11:49:40 +0700 Subject: [PATCH] fix(card): productive titles render at 15 px for every timezone (#24) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two cuts together keep the productive-time and productive-weekday titles at the same 15 px the rest of the gallery uses: 1. utcOffsetLabel is now compact. Integer offsets drop the '.00' padding ("UTC+7" vs the old "UTC+7.00") and non-integer offsets use the colon form ("UTC+5:30", "UTC+5:45"). Saves 3 chars for the common integer case, so "Commits by Hour (last year, UTC+7)" lands at 34 chars — inside the 15 px budget. 2. weekdayTitle no longer embeds the UTC label. Day-of-week aggregates by whole days; clock precision isn't informative there, and dropping it shortens the title to 30 chars so the full 15 px lands for every timezone. Quarter-hour-zone users (Kathmandu UTC+5:45) see the hour title drop to 14 px — 37 chars still exceeds the budget — but that's a rare case and only 1 px off. TestUTCOffsetLabel updated to the new format. TestFitTitleFontSize pinned the new titles. --- docs/design-guidelines.md | 2 +- internal/card/card_test.go | 8 ++++---- internal/card/productive_weekday.go | 14 +++++++------- main.go | 24 +++++++++++++++++++----- main_test.go | 12 ++++++------ 5 files changed, 37 insertions(+), 23 deletions(-) diff --git a/docs/design-guidelines.md b/docs/design-guidelines.md index 9a034ef5..211484e3 100644 --- a/docs/design-guidelines.md +++ b/docs/design-guidelines.md @@ -70,7 +70,7 @@ When there's **exactly one slice** (one language at 100%), the renderer emits tw | Bar fill | `theme.Accent` for the peak bar; `mixHex(Background, Accent, 0.55)` dim for the rest so the busiest period reads at a glance | | Y-axis ticks | `niceTicks(max, 5)` — 1/2/5 × 10^k ladder. `last = ceil(max/step) × step` so `yMax ≥ dataMax` always (bars can't poke above chartH into the title) | | Axis caption | "hour of day" bottom-center on productive-time; weekday / by-year omit the caption since the x labels are self-describing | -| Title format | `Commits by Hour (, UTC±N.NN)` / `Commits by Weekday (, UTC±N.NN)` / `Contributions by Year` | +| Title format | `Commits by Hour (, UTC±H[:MM])` / `Commits by Weekday ()` — weekday drops the UTC so the title always fits at 15 px / `Contributions by Year` | | Hover | `HH:00 — N commits` / `Mon — N commits` / `YYYY — N commits` | ## Heatmap card (contributions-heatmap) diff --git a/internal/card/card_test.go b/internal/card/card_test.go index 585e6a50..e00594ec 100644 --- a/internal/card/card_test.go +++ b/internal/card/card_test.go @@ -204,9 +204,9 @@ func TestFitTitleFontSize(t *testing.T) { {"Top Starred Repos", 15}, {"Most Commit Language (all time)", 15}, // 31 chars {"Contributions by Year", 15}, - {"Commits by Hour (last year, UTC+7.00)", 14}, // 37 chars - {"Commits by Weekday (last year, UTC+7.00)", 13}, // 40 chars - {"Commits by Weekday (last year, UTC+12.75)", 12}, // 41 chars + {"Commits by Hour (last year, UTC+7)", 15}, // 34 chars, common integer-zone case + {"Commits by Hour (last year, UTC+5:45)", 14}, // 37 chars, quarter-hour zone (Kathmandu) + {"Commits by Weekday (last year)", 15}, // 30 chars — weekday titles never include UTC {strings.Repeat("x", 200), 11}, // pathological } for _, c := range cases { @@ -369,7 +369,7 @@ func adversarialProfile() *github.Profile { p := &github.Profile{ Login: "user-with-a-very-long-login-name", Name: "A Very Long Display Name That Keeps Going", - UTCOffsetLabel: "UTC+12.75", // half-hour / quarter-hour zones widen the title + UTCOffsetLabel: "UTC+12:45", // quarter-hour zone — longest realistic UTC label Company: "A-Company-With-An-Unusually-Long-Name Pty Ltd", Location: "A Place With A Name That Is Way Too Long To Fit", diff --git a/internal/card/productive_weekday.go b/internal/card/productive_weekday.go index 11429f33..516adb5c 100644 --- a/internal/card/productive_weekday.go +++ b/internal/card/productive_weekday.go @@ -13,7 +13,7 @@ type productiveWeekdayCard struct{} func (productiveWeekdayCard) Filename() string { return "productive-weekday.svg" } func (productiveWeekdayCard) SVG(p *github.Profile, t theme.Theme) ([]byte, error) { - return renderWeekday(weekdayTitle("last year", p.UTCOffsetLabel), p.Weekday, t), nil + return renderWeekday(weekdayTitle("last year"), p.Weekday, t), nil } type productiveWeekdayAllTimeCard struct{} @@ -21,14 +21,14 @@ type productiveWeekdayAllTimeCard struct{} func (productiveWeekdayAllTimeCard) Filename() string { return "productive-weekday-all-time.svg" } func (productiveWeekdayAllTimeCard) SVG(p *github.Profile, t theme.Theme) ([]byte, error) { - return renderWeekday(weekdayTitle("all time", p.UTCOffsetLabel), p.WeekdayAllTime, t), nil + return renderWeekday(weekdayTitle("all time"), p.WeekdayAllTime, t), nil } -func weekdayTitle(window, utcLabel string) string { - if utcLabel == "" { - return "Commits by Weekday (" + window + ")" - } - return "Commits by Weekday (" + window + ", " + utcLabel + ")" +// weekdayTitle skips the UTC offset — the data aggregates into day-of-week +// buckets, so exact clock precision isn't informative and dropping it keeps +// the title short enough to render at the full 15 px. +func weekdayTitle(window string) string { + return "Commits by Weekday (" + window + ")" } // Index 0 = Sunday to match time.Weekday (which is what FetchProductive stores). diff --git a/main.go b/main.go index 121f993b..ccce1979 100644 --- a/main.go +++ b/main.go @@ -113,13 +113,27 @@ func main() { } } -// utcOffsetLabel formats the location's current offset from UTC as "UTC±N.NN" -// (two-decimal hours) so half-hour zones like India (UTC+5.30) or Nepal -// (UTC+5.75) render cleanly. Matches github-profile-summary-cards' style. +// utcOffsetLabel formats the location's current offset from UTC compactly: +// +// integer hours → "UTC+7" (no ".00" padding — 3 chars shorter than +// the old "UTC+7.00" format, keeps the +// productive-time title at 15 px) +// half-hour zone → "UTC+5:30" (India) +// quarter-hour → "UTC+5:45" (Nepal) +// negative zone → "UTC-3" / "UTC-3:30" func utcOffsetLabel(loc *time.Location) string { _, offsetSec := time.Now().In(loc).Zone() - hours := float64(offsetSec) / 3600.0 - return fmt.Sprintf("UTC%+.2f", hours) + sign := "+" + if offsetSec < 0 { + sign = "-" + offsetSec = -offsetSec + } + hours := offsetSec / 3600 + minutes := (offsetSec % 3600) / 60 + if minutes == 0 { + return fmt.Sprintf("UTC%s%d", sign, hours) + } + return fmt.Sprintf("UTC%s%d:%02d", sign, hours, minutes) } func resolveThemes(spec string) ([]theme.Theme, error) { diff --git a/main_test.go b/main_test.go index eb0b812f..50d94960 100644 --- a/main_test.go +++ b/main_test.go @@ -6,17 +6,17 @@ import ( "time" ) -// TestUTCOffsetLabel checks that half-hour and quarter-hour zones render -// with a decimal, matching github-profile-summary-cards' "UTC+X.NN" style. +// TestUTCOffsetLabel checks the compact format: integer hours drop the +// minutes suffix, non-zero offsets render as `UTC±H:MM`. func TestUTCOffsetLabel(t *testing.T) { cases := []struct { zone string want string // must appear in the label; exact value varies by DST }{ - {"UTC", "UTC+0.00"}, - {"Asia/Saigon", "UTC+7.00"}, - {"Asia/Kolkata", "UTC+5.50"}, // half-hour zone - {"Asia/Kathmandu", "UTC+5.75"}, // quarter-hour zone + {"UTC", "UTC+0"}, + {"Asia/Saigon", "UTC+7"}, + {"Asia/Kolkata", "UTC+5:30"}, // half-hour zone + {"Asia/Kathmandu", "UTC+5:45"}, // quarter-hour zone } for _, tc := range cases { loc, err := time.LoadLocation(tc.zone)