Skip to content

Commit 1072fbe

Browse files
Merge pull request #118 from deariary/fix/card-header-date-range
fix: replace username with date range in card header
2 parents 91104f3 + 02b8780 commit 1072fbe

10 files changed

Lines changed: 117 additions & 22 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ Embed an animated news ticker in your GitHub Profile README. AI-generated headli
9898
<picture>
9999
<source media="(prefers-color-scheme: dark)" srcset="https://deariary.github.io/github-weekly-reporter/screenshots/card-dark.svg" />
100100
<source media="(prefers-color-scheme: light)" srcset="https://deariary.github.io/github-weekly-reporter/screenshots/card.svg" />
101-
<img alt="Weekly News Ticker" src="https://deariary.github.io/github-weekly-reporter/screenshots/card.svg" />
101+
<img alt="Weekly News Ticker" src="https://deariary.github.io/github-weekly-reporter/screenshots/card.svg" height="48" />
102102
</picture>
103103
104104
Generated automatically as part of the `render` command. Add this to your profile README:
@@ -108,7 +108,7 @@ Generated automatically as part of the `render` command. Add this to your profil
108108
<picture>
109109
<source media="(prefers-color-scheme: dark)" srcset="https://{username}.github.io/{repo}/card-dark.svg" />
110110
<source media="(prefers-color-scheme: light)" srcset="https://{username}.github.io/{repo}/card.svg" />
111-
<img alt="Weekly Report" src="https://{username}.github.io/{repo}/card.svg" />
111+
<img alt="Weekly Report" src="https://{username}.github.io/{repo}/card.svg" height="48" />
112112
</picture>
113113
</a>
114114
```

scripts/screenshots/card-dark.svg

Lines changed: 2 additions & 2 deletions
Loading

scripts/screenshots/card.svg

Lines changed: 2 additions & 2 deletions
Loading

src/cli/commands/render.test.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,14 @@ vi.mock("../../renderer/rss.js", () => ({
4747
buildRSSFeed: (...args: unknown[]) => mockBuildRSSFeed(...args),
4848
}));
4949

50-
// Mock week
51-
vi.mock("../../deployer/week.js", () => ({
52-
getWeekId: () => ({ year: 2026, week: 14, path: "2026/W14" }),
53-
}));
50+
// Mock week (keep real isoWeekToMonday)
51+
vi.mock("../../deployer/week.js", async (importOriginal) => {
52+
const actual = await importOriginal<typeof import("../../deployer/week.js")>();
53+
return {
54+
...actual,
55+
getWeekId: () => ({ year: 2026, week: 14, path: "2026/W14" }),
56+
};
57+
});
5458

5559
const GITHUB_DATA_YAML = `
5660
username: testuser
@@ -167,6 +171,17 @@ describe("registerRender", () => {
167171

168172
// Should generate index OG image
169173
expect(mockGenerateIndexOGImage).toHaveBeenCalled();
174+
175+
// Should write card SVGs with correct date range from weekId (2026 W14 = Mar 30 - Apr 5)
176+
const cardCall = mockWriteFile.mock.calls.find(
177+
(call: unknown[]) => typeof call[0] === "string" && (call[0] as string).endsWith("card.svg"),
178+
);
179+
expect(cardCall).toBeDefined();
180+
const cardSvg = cardCall![1] as string;
181+
expect(cardSvg).toContain("Week 14");
182+
expect(cardSvg).toContain("Mar 30");
183+
expect(cardSvg).toContain("Apr 5");
184+
expect(cardSvg).toContain("2026");
170185
});
171186

172187
it("exits when github-data.yaml is missing", async () => {

src/cli/commands/render.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { join } from "node:path";
66
import { parse as parseYaml } from "yaml";
77
import { renderReport } from "../../renderer/index.js";
88
import { renderIndexPage, buildReportEntry, type ReportEntry } from "../../deployer/index-page.js";
9-
import { getWeekId } from "../../deployer/week.js";
9+
import { getWeekId, isoWeekToMonday } from "../../deployer/week.js";
1010
import { parseLocalDate } from "../../collector/date-range.js";
1111
import { generateOGImage, generateIndexOGImage } from "../../renderer/og-image.js";
1212
import { generateCard, generateDarkCard } from "../../renderer/card.js";
@@ -174,9 +174,16 @@ const run = async (options: RenderOptions): Promise<void> => {
174174
console.log(`OG image written to ${ogPath}`);
175175

176176
// Generate animated SVG summary cards (light + dark)
177+
// Compute Mon-Sun date range from ISO week number (independent of data file)
178+
const fmtShort = (d: Date): string =>
179+
d.toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" });
180+
const monday = isoWeekToMonday(weekId.year, weekId.week);
181+
const sunday = new Date(monday.getTime() + 6 * 86_400_000);
182+
const dateRange = `${fmtShort(monday)} - ${fmtShort(sunday)}, ${weekId.year}`;
177183
const cardData = {
178184
username: githubData.username,
179-
weekLabel: `${weekId.year} Week ${weekId.path.split("/")[1].replace("W", "")}`,
185+
weekLabel: `Week ${weekId.path.split("/")[1].replace("W", "")}`,
186+
dateRange,
180187
title: aiContent.title,
181188
summaries: aiContent.summaries,
182189
ticker: aiContent.ticker,

src/cli/commands/setup/workflows.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ ${opts.pagesUrl}
200200
<picture>
201201
<source media="(prefers-color-scheme: dark)" srcset="${opts.pagesUrl}/card-dark.svg" />
202202
<source media="(prefers-color-scheme: light)" srcset="${opts.pagesUrl}/card.svg" />
203-
<img alt="Weekly Report" src="${opts.pagesUrl}/card.svg" />
203+
<img alt="Weekly Report" src="${opts.pagesUrl}/card.svg" height="48" />
204204
</picture>
205205
</a>
206206
@@ -211,7 +211,7 @@ Add this to your [GitHub Profile README](https://docs.github.com/en/account-and-
211211
<picture>
212212
<source media="(prefers-color-scheme: dark)" srcset="${opts.pagesUrl}/card-dark.svg" />
213213
<source media="(prefers-color-scheme: light)" srcset="${opts.pagesUrl}/card.svg" />
214-
<img alt="Weekly Report" src="${opts.pagesUrl}/card.svg" />
214+
<img alt="Weekly Report" src="${opts.pagesUrl}/card.svg" height="48" />
215215
</picture>
216216
</a>
217217
\`\`\`

src/deployer/week.test.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, it, expect } from "vitest";
2-
import { getWeekId, getCurrentWeekId } from "./week.js";
2+
import { getWeekId, getCurrentWeekId, isoWeekToMonday } from "./week.js";
33

44
describe("getWeekId", () => {
55
// -------------------------------------------------------------------
@@ -220,3 +220,63 @@ describe("getCurrentWeekId", () => {
220220
expect(result.path).toBe("2026/W01");
221221
});
222222
});
223+
224+
describe("isoWeekToMonday", () => {
225+
const dayOfWeek = (d: Date): string =>
226+
d.toLocaleDateString("en-US", { weekday: "long", timeZone: "UTC" });
227+
const fmt = (d: Date): string =>
228+
d.toISOString().slice(0, 10);
229+
230+
it("returns Monday for 2026 W14 (Mar 30)", () => {
231+
const mon = isoWeekToMonday(2026, 14);
232+
expect(fmt(mon)).toBe("2026-03-30");
233+
expect(dayOfWeek(mon)).toBe("Monday");
234+
});
235+
236+
it("returns Monday for 2026 W1 (Dec 29, 2025)", () => {
237+
// ISO 2026 W1 starts on Mon Dec 29 2025
238+
const mon = isoWeekToMonday(2026, 1);
239+
expect(fmt(mon)).toBe("2025-12-29");
240+
expect(dayOfWeek(mon)).toBe("Monday");
241+
});
242+
243+
it("returns Monday for 2025 W1 (Dec 30, 2024)", () => {
244+
const mon = isoWeekToMonday(2025, 1);
245+
expect(fmt(mon)).toBe("2024-12-30");
246+
expect(dayOfWeek(mon)).toBe("Monday");
247+
});
248+
249+
it("handles 2024 W1 (Jan 1, 2024 is Monday)", () => {
250+
const mon = isoWeekToMonday(2024, 1);
251+
expect(fmt(mon)).toBe("2024-01-01");
252+
expect(dayOfWeek(mon)).toBe("Monday");
253+
});
254+
255+
it("handles last week of year: 2026 W52", () => {
256+
const mon = isoWeekToMonday(2026, 52);
257+
expect(fmt(mon)).toBe("2026-12-21");
258+
expect(dayOfWeek(mon)).toBe("Monday");
259+
});
260+
261+
it("handles W53 in a long year (2020 has 53 weeks)", () => {
262+
const mon = isoWeekToMonday(2020, 53);
263+
expect(fmt(mon)).toBe("2020-12-28");
264+
expect(dayOfWeek(mon)).toBe("Monday");
265+
});
266+
267+
it("Sunday is always 6 days after Monday", () => {
268+
const cases = [
269+
{ year: 2026, week: 1 },
270+
{ year: 2026, week: 14 },
271+
{ year: 2026, week: 52 },
272+
{ year: 2020, week: 53 },
273+
{ year: 2024, week: 1 },
274+
];
275+
cases.forEach(({ year, week }) => {
276+
const mon = isoWeekToMonday(year, week);
277+
const sun = new Date(mon.getTime() + 6 * 86_400_000);
278+
expect(dayOfWeek(mon)).toBe("Monday");
279+
expect(dayOfWeek(sun)).toBe("Sunday");
280+
});
281+
});
282+
});

src/deployer/week.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ export const getWeekId = (
3434

3535
// Current ISO week ID. Used by daily-fetch to store events for the
3636
// week that is still in progress.
37+
// Monday (00:00 UTC) of the given ISO week. Week 1 contains January 4.
38+
export const isoWeekToMonday = (year: number, week: number): Date => {
39+
const jan4 = new Date(Date.UTC(year, 0, 4));
40+
const dow = jan4.getUTCDay() || 7;
41+
const w1Mon = new Date(jan4.getTime() - (dow - 1) * 86_400_000);
42+
return new Date(w1Mon.getTime() + (week - 1) * 7 * 86_400_000);
43+
};
44+
3745
export const getCurrentWeekId = (
3846
date: Date = new Date(),
3947
timezone: string = "UTC",

src/renderer/card.test.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { generateCard, generateDarkCard } from "./card.js";
33

44
const data = {
55
username: "testuser",
6-
weekLabel: "2026 Week 14",
6+
weekLabel: "Week 14",
7+
dateRange: "Mar 30 - Apr 5, 2026",
78
title: "Auth refactor completed",
89
summaries: [
910
{
@@ -34,10 +35,10 @@ describe("generateCard", () => {
3435
expect(svg).toContain("WEEKLY NEWS");
3536
});
3637

37-
it("includes username and week label", () => {
38+
it("includes week label and date range", () => {
3839
const svg = generateCard(data);
39-
expect(svg).toContain("@testuser");
40-
expect(svg).toContain("2026 Week 14");
40+
expect(svg).toContain("Week 14");
41+
expect(svg).toContain("Mar 30 - Apr 5, 2026");
4142
});
4243

4344
it("includes summary headings with fallback labels", () => {
@@ -103,6 +104,6 @@ describe("generateDarkCard", () => {
103104
it("includes the same ticker content", () => {
104105
const svg = generateDarkCard(data);
105106
expect(svg).toContain("Commit Summary");
106-
expect(svg).toContain("@testuser");
107+
expect(svg).toContain("Week 14");
107108
});
108109
});

src/renderer/card.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { SummarySection, TickerItem } from "../types.js";
77
export type CardData = {
88
username: string;
99
weekLabel: string;
10+
dateRange: string; // e.g. "Mar 30 - Apr 5, 2026"
1011
title: string;
1112
summaries: SummarySection[];
1213
ticker?: TickerItem[];
@@ -47,7 +48,8 @@ const DARK: CardColors = {
4748
};
4849

4950
const WIDTH = 900;
50-
const HEIGHT = 48;
51+
export const CARD_HEIGHT = 48;
52+
const HEIGHT = CARD_HEIGHT;
5153
const TICKER_H = 24;
5254
const REPEATS = 50;
5355
const SCROLL_PX_PER_SEC = 30;
@@ -136,11 +138,13 @@ const buildSVG = (data: CardData, colors: CardColors): string => {
136138
const labelY = (topH - labelH) / 2;
137139
const weekX = labelX + labelW + 6;
138140

141+
const dateX = weekX + data.weekLabel.length * 7 + 8;
142+
139143
const topBar = [
140144
`<rect x="${labelX}" y="${labelY}" width="${labelW}" height="${labelH}" rx="2" fill="${colors.accentBg}"/>`,
141145
`<text x="${labelX + labelW / 2}" y="${midY}" font-family="${font}" text-anchor="middle" font-size="9" font-weight="800" fill="#fff" letter-spacing="0.08em">WEEKLY NEWS</text>`,
142146
`<text x="${weekX}" y="${midY}" font-family="${font}" font-size="11" font-weight="700" fill="${colors.text}">${escapeXml(data.weekLabel)}</text>`,
143-
`<text x="${WIDTH}" y="${midY}" font-family="${font}" font-size="10" fill="${colors.textSecondary}" text-anchor="end">@${escapeXml(data.username)}</text>`,
147+
`<text x="${dateX}" y="${midY}" font-family="${font}" font-size="10" fill="${colors.textSecondary}">${escapeXml(data.dateRange)}</text>`,
144148
`<line x1="0" y1="${topH}" x2="10000" y2="${topH}" stroke="${colors.border}" stroke-width="0.5"/>`,
145149
].join("\n");
146150

0 commit comments

Comments
 (0)