diff --git a/src/habits.test.ts b/src/habits.test.ts index 4ea965f..8e526af 100644 --- a/src/habits.test.ts +++ b/src/habits.test.ts @@ -4,6 +4,7 @@ import { toggleCompletion, isCompletedOn, dayStr, + shiftDay, currentStreak, bestStreak, completionsInRange, @@ -47,6 +48,25 @@ describe("dayStr", () => { }); }); +describe("shiftDay", () => { + it("shifts forward by positive delta", () => { + expect(shiftDay("2026-06-01", 1)).toBe("2026-06-02"); + }); + + it("shifts backward by negative delta", () => { + expect(shiftDay("2026-06-01", -1)).toBe("2026-05-31"); + }); + + it("returns the same day for delta 0", () => { + expect(shiftDay("2026-06-15", 0)).toBe("2026-06-15"); + }); + + it("handles month boundaries correctly", () => { + expect(shiftDay("2026-05-31", 1)).toBe("2026-06-01"); + expect(shiftDay("2026-06-01", -29)).toBe("2026-05-03"); + }); +}); + describe("currentStreak", () => { const today = "2026-06-10"; diff --git a/src/habits.ts b/src/habits.ts index 7065e8b..965d7bc 100644 --- a/src/habits.ts +++ b/src/habits.ts @@ -46,7 +46,7 @@ export function toggleCompletion(habit: Habit, day: string): Habit { return { ...habit, completions }; } -function shiftDay(day: string, delta: number): string { +export function shiftDay(day: string, delta: number): string { const d = new Date(day + "T00:00:00Z"); d.setUTCDate(d.getUTCDate() + delta); return d.toISOString().slice(0, 10); diff --git a/src/main.ts b/src/main.ts index d330cff..d15b3ca 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,7 +5,9 @@ import { toggleCompletion, isCompletedOn, dayStr, + shiftDay, currentStreak, + last7Count, } from "./habits"; const STORAGE_KEY = "zenith.habits.v1"; @@ -53,14 +55,24 @@ function render(): void { streak === 0 ? "" : `${streak}${streak >= 3 ? " 🔥" : ""}`; + const heatmapCells = Array.from({ length: 30 }, (_, i) => { + const day = shiftDay(today, i - 29); + const cellDone = isCompletedOn(h, day); + return `
`; + }).join(""); + const weeklyCount = last7Count(h, today); return `