Skip to content

Commit 4861227

Browse files
authored
fix(svg): pulse today's tile even when commit count is zero (JhaSourav07#1666)
## Description Fixes JhaSourav07#1659 In `lib/svg/generator.ts`, a local `sanitizeFont` helper was redefined inside both `generateMonthlySVG` and `generateAutoThemeMonthlySVG` functions instead of reusing the global `sanitizeFont` already imported from `./sanitizer`. The local regex (`/[^a-zA-Z0-9\s-]/g`) was also inconsistent with the global one (`/[^a-zA-Z0-9\s\-']/g`), stripping out single quotes and potentially breaking custom fonts like `'Courier New'`. **Changes made:** - Removed the redundant local `sanitizeFont` definitions inside `generateMonthlySVG` and `generateAutoThemeMonthlySVG` - Both functions now use the shared `sanitizeFont` utility from `./sanitizer` - Font sanitization is now consistent across all SVG rendering functions ## Pillar - [ ] 🎨 Pillar 1 — New Theme Design - [ ] 📐 Pillar 2 — Geometric SVG Improvement - [ ] 🕐 Pillar 3 — Timezone Logic Optimization - [x] 🛠️ Other (Bug fix, refactoring, docs) ## Visual Preview No visual change — this is a code quality and consistency fix. ## Checklist before requesting a review: - [x] I have read the `CONTRIBUTING.md` file. - [x] I have tested these changes locally (`localhost:3000/api/streak?user=Pranav-IIITM`). - [x] I have run `npm run format` and `npm run lint` locally and resolved all errors (CI will fail otherwise). - [x] My commits follow the Conventional Commits format (e.g., `feat(themes): ...`, `fix(calculate): ...`). - [ ] I have updated `README.md` if I added a new theme or URL parameter. - [x] I have started the repo. - [x] I have made sure that i have only one commit to merge in this PR. - [ ] The SVG output matches the CommitPulse "premium quality" aesthetic standard (no raw elements, smooth animations, correct fonts). - [x] (Recommended) I joined the CommitPulse Discord community for contributor discussions, mentorship, and faster PR support.
2 parents 0f06a12 + 4c15caf commit 4861227

4 files changed

Lines changed: 86 additions & 16 deletions

File tree

lib/svg/generator.test.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -633,8 +633,8 @@ describe('generateSVG', () => {
633633
expect(svg).toContain('attributeName="opacity" values="1;0.4;1"');
634634
});
635635

636-
it('does not pulse when todayDate has no commits even if another day does', () => {
637-
// todayDate = '2024-06-13' (0 commits) — no pulse
636+
it('pulses even when todayDate has no commits', () => {
637+
// todayDate = '2024-06-13' (0 commits) — should pulse under new design requirements
638638
const stats: StreakStats = {
639639
currentStreak: 0,
640640
longestStreak: 2,
@@ -644,7 +644,26 @@ describe('generateSVG', () => {
644644

645645
const svg = generateSVG(stats, { user: 'avi' } as unknown as BadgeParams, calendar);
646646

647-
expect(svg).not.toContain('attributeName="opacity" values="1;0.4;1"');
647+
expect(svg).toContain('attributeName="opacity" values="1;0.4;1"');
648+
});
649+
650+
it("applies a prominent top-face accent stroke highlight to today's zero-commit tile", () => {
651+
const stats: StreakStats = {
652+
currentStreak: 0,
653+
longestStreak: 2,
654+
totalContributions: 10,
655+
todayDate: '2024-06-13',
656+
};
657+
658+
const svg = generateSVG(
659+
stats,
660+
{ user: 'avi', accent: '00ffaa' } as unknown as BadgeParams,
661+
calendar
662+
);
663+
664+
// Verify today's zero-commit tile has the correct top-face stroke highlight
665+
// For static theme, todayStrokeColor is resolved to accentColorHex ('#00ffaa')
666+
expect(svg).toContain('stroke="#00ffaa" stroke-opacity="0.8" stroke-width="1.2"');
648667
});
649668
it('includes accessible title and description metadata', () => {
650669
const svg = generateSVG(

lib/svg/generator.ts

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -291,16 +291,30 @@ function renderTowers(
291291
const strokeAttr = isGhost
292292
? `stroke="${strokeColor}" stroke-opacity="${t.strokeOpacity}" stroke-width="${t.strokeWidth}"`
293293
: '';
294+
295+
let leftStrokeAttr = strokeAttr;
296+
let rightStrokeAttr = strokeAttr;
297+
let topStrokeAttr = strokeAttr;
298+
299+
if (t.isToday && t.contributionCount === 0) {
300+
const todayStrokeColor = isAutoTheme ? 'var(--cp-accent)' : strokeColor;
301+
leftStrokeAttr = isGhost
302+
? `stroke="${strokeColor}" stroke-opacity="${t.strokeOpacity}" stroke-width="${t.strokeWidth}"`
303+
: '';
304+
rightStrokeAttr = leftStrokeAttr;
305+
topStrokeAttr = `stroke="${todayStrokeColor}" stroke-opacity="0.8" stroke-width="${1.2 * sf}"`;
306+
}
307+
294308
const delay = ((t.row + t.col) * 0.015).toFixed(3);
295309

296310
towers += `
297311
<g transform="translate(${t.x}, ${t.y})">
298312
<g class="cp-tower" style="animation-delay: ${delay}s;">
299-
${t.isTodayWithCommits ? '<animate attributeName="opacity" values="1;0.4;1" dur="1.5s" repeatCount="indefinite" />' : ''}
313+
${t.isToday ? '<animate attributeName="opacity" values="1;0.4;1" dur="1.5s" repeatCount="indefinite" />' : ''}
300314
<title>${escapeXML(t.tooltip)}</title>
301-
<path d="M0 ${10 - t.h} L0 10 L-16 0 L-16 ${-t.h} Z" ${leftFillAttr} fill-opacity="${leftFaceOpacity}" ${strokeAttr} />
302-
<path d="M0 ${10 - t.h} L0 10 L16 0 L16 ${-t.h} Z" ${rightFillAttr} fill-opacity="${rightFaceOpacity}" ${strokeAttr} />
303-
<path d="M0 ${-t.h} L16 ${10 - t.h} L0 ${20 - t.h} L-16 ${10 - t.h} Z" ${finalTopFillAttr} fill-opacity="${topFaceOpacity}" ${strokeAttr} />
315+
<path d="M0 ${10 - t.h} L0 10 L-16 0 L-16 ${-t.h} Z" ${leftFillAttr} fill-opacity="${leftFaceOpacity}" ${leftStrokeAttr} />
316+
<path d="M0 ${10 - t.h} L0 10 L16 0 L16 ${-t.h} Z" ${rightFillAttr} fill-opacity="${rightFaceOpacity}" ${rightStrokeAttr} />
317+
<path d="M0 ${-t.h} L16 ${10 - t.h} L0 ${20 - t.h} L-16 ${10 - t.h} Z" ${finalTopFillAttr} fill-opacity="${topFaceOpacity}" ${topStrokeAttr} />
304318
${t.contributionCount > 5 ? `<path d="M0 ${-t.h} L16 ${10 - t.h} L0 ${20 - t.h} L-16 ${10 - t.h} Z" fill="white" fill-opacity="0.2" />` : ''}
305319
</g>
306320
</g>`;
@@ -1374,14 +1388,27 @@ function generateAutoThemeVersusSVG(
13741388
const fillClass = t.isGhost ? 'cp-text-fill' : 'cp-accent-fill';
13751389
const strokeColor = t.isGhost ? 'var(--cp-text)' : 'var(--cp-accent)';
13761390
const delay = ((t.row + t.col) * 0.015).toFixed(3);
1391+
1392+
let leftStrokeAttr = `stroke="${strokeColor}" stroke-opacity="${t.strokeOpacity}" stroke-width="${t.strokeWidth}"`;
1393+
let rightStrokeAttr = leftStrokeAttr;
1394+
let topStrokeAttr = leftStrokeAttr;
1395+
1396+
if (t.isToday && t.contributionCount === 0) {
1397+
leftStrokeAttr = t.isGhost
1398+
? `stroke="${strokeColor}" stroke-opacity="${t.strokeOpacity}" stroke-width="${t.strokeWidth}"`
1399+
: '';
1400+
rightStrokeAttr = leftStrokeAttr;
1401+
topStrokeAttr = `stroke="var(--cp-accent)" stroke-opacity="0.8" stroke-width="${1.2 * sf}"`;
1402+
}
1403+
13771404
towers1 += `
13781405
<g transform="translate(${t.x}, ${t.y})">
13791406
<g class="cp-tower" style="animation-delay: ${delay}s;">
1380-
${t.isTodayWithCommits ? '<animate attributeName="opacity" values="1;0.4;1" dur="1.5s" repeatCount="indefinite" />' : ''}
1407+
${t.isToday ? '<animate attributeName="opacity" values="1;0.4;1" dur="1.5s" repeatCount="indefinite" />' : ''}
13811408
<title>${escapeXML(t.tooltip)}</title>
1382-
<path d="M0 ${10 - t.h} L0 10 L-16 0 L-16 ${-t.h} Z" class="${fillClass}" fill-opacity="${t.faceOpacity.left}" stroke="${strokeColor}" stroke-opacity="${t.strokeOpacity}" stroke-width="${t.strokeWidth}" />
1383-
<path d="M0 ${10 - t.h} L0 10 L16 0 L16 ${-t.h} Z" class="${fillClass}" fill-opacity="${t.faceOpacity.right}" stroke="${strokeColor}" stroke-opacity="${t.strokeOpacity}" stroke-width="${t.strokeWidth}" />
1384-
<path d="M0 ${-t.h} L16 ${10 - t.h} L0 ${20 - t.h} L-16 ${10 - t.h} Z" class="${fillClass}" fill-opacity="${t.faceOpacity.top}" stroke="${strokeColor}" stroke-opacity="${t.strokeOpacity}" stroke-width="${t.strokeWidth}" />
1409+
<path d="M0 ${10 - t.h} L0 10 L-16 0 L-16 ${-t.h} Z" class="${fillClass}" fill-opacity="${t.faceOpacity.left}" ${leftStrokeAttr} />
1410+
<path d="M0 ${10 - t.h} L0 10 L16 0 L16 ${-t.h} Z" class="${fillClass}" fill-opacity="${t.faceOpacity.right}" ${rightStrokeAttr} />
1411+
<path d="M0 ${-t.h} L16 ${10 - t.h} L0 ${20 - t.h} L-16 ${10 - t.h} Z" class="${fillClass}" fill-opacity="${t.faceOpacity.top}" ${topStrokeAttr} />
13851412
${t.contributionCount > 5 ? `<path d="M0 ${-t.h} L16 ${10 - t.h} L0 ${20 - t.h} L-16 ${10 - t.h} Z" fill="white" fill-opacity="0.2" />` : ''}
13861413
</g>
13871414
</g>`;
@@ -1394,14 +1421,27 @@ function generateAutoThemeVersusSVG(
13941421
const fillClass = t.isGhost ? 'cp-text-fill' : 'cp-accent-fill';
13951422
const strokeColor = t.isGhost ? 'var(--cp-text)' : 'var(--cp-accent)';
13961423
const delay = ((t.row + t.col) * 0.015).toFixed(3);
1424+
1425+
let leftStrokeAttr = `stroke="${strokeColor}" stroke-opacity="${t.strokeOpacity}" stroke-width="${t.strokeWidth}"`;
1426+
let rightStrokeAttr = leftStrokeAttr;
1427+
let topStrokeAttr = leftStrokeAttr;
1428+
1429+
if (t.isToday && t.contributionCount === 0) {
1430+
leftStrokeAttr = t.isGhost
1431+
? `stroke="${strokeColor}" stroke-opacity="${t.strokeOpacity}" stroke-width="${t.strokeWidth}"`
1432+
: '';
1433+
rightStrokeAttr = leftStrokeAttr;
1434+
topStrokeAttr = `stroke="var(--cp-accent)" stroke-opacity="0.8" stroke-width="${1.2 * sf}"`;
1435+
}
1436+
13971437
towers2 += `
13981438
<g transform="translate(${t.x}, ${t.y})">
13991439
<g class="cp-tower" style="animation-delay: ${delay}s;">
1400-
${t.isTodayWithCommits ? '<animate attributeName="opacity" values="1;0.4;1" dur="1.5s" repeatCount="indefinite" />' : ''}
1440+
${t.isToday ? '<animate attributeName="opacity" values="1;0.4;1" dur="1.5s" repeatCount="indefinite" />' : ''}
14011441
<title>${escapeXML(t.tooltip)}</title>
1402-
<path d="M0 ${10 - t.h} L0 10 L-16 0 L-16 ${-t.h} Z" class="${fillClass}" fill-opacity="${t.faceOpacity.left}" stroke="${strokeColor}" stroke-opacity="${t.strokeOpacity}" stroke-width="${t.strokeWidth}" />
1403-
<path d="M0 ${10 - t.h} L0 10 L16 0 L16 ${-t.h} Z" class="${fillClass}" fill-opacity="${t.faceOpacity.right}" stroke="${strokeColor}" stroke-opacity="${t.strokeOpacity}" stroke-width="${t.strokeWidth}" />
1404-
<path d="M0 ${-t.h} L16 ${10 - t.h} L0 ${20 - t.h} L-16 ${10 - t.h} Z" class="${fillClass}" fill-opacity="${t.faceOpacity.top}" stroke="${strokeColor}" stroke-opacity="${t.strokeOpacity}" stroke-width="${t.strokeWidth}" />
1442+
<path d="M0 ${10 - t.h} L0 10 L-16 0 L-16 ${-t.h} Z" class="${fillClass}" fill-opacity="${t.faceOpacity.left}" ${leftStrokeAttr} />
1443+
<path d="M0 ${10 - t.h} L0 10 L16 0 L16 ${-t.h} Z" class="${fillClass}" fill-opacity="${t.faceOpacity.right}" ${rightStrokeAttr} />
1444+
<path d="M0 ${-t.h} L16 ${10 - t.h} L0 ${20 - t.h} L-16 ${10 - t.h} Z" class="${fillClass}" fill-opacity="${t.faceOpacity.top}" ${topStrokeAttr} />
14051445
${t.contributionCount > 5 ? `<path d="M0 ${-t.h} L16 ${10 - t.h} L0 ${20 - t.h} L-16 ${10 - t.h} Z" fill="white" fill-opacity="0.2" />` : ''}
14061446
</g>
14071447
</g>`;

lib/svg/layout.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,17 @@ describe('computeTowers edge cases', () => {
2020
expect(towers[0].isTodayWithCommits).toBe(true);
2121
});
2222

23+
it('adds TODAY prefix for today tower without commits', () => {
24+
const calendar = {
25+
totalContributions: 0,
26+
weeks: [{ contributionDays: [{ contributionCount: 0, date: '2024-06-12' }] }],
27+
} as unknown as ContributionCalendar;
28+
const towers = computeTowers(calendar, 'linear', '2024-06-12');
29+
expect(towers[0].tooltip).toContain('TODAY:');
30+
expect(towers[0].isToday).toBe(true);
31+
expect(towers[0].isTodayWithCommits).toBe(false);
32+
});
33+
2334
it('does not add TODAY prefix for non-today tower', () => {
2435
const calendar = {
2536
totalContributions: 0,

lib/svg/layout.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export function computeTowers(
118118
const isTodayWithCommits = isToday && hasCommits;
119119

120120
const unit = mode === 'loc' ? 'lines of code' : 'contributions';
121-
const tooltip = isTodayWithCommits
121+
const tooltip = isToday
122122
? `TODAY: ${day.date}: ${count} ${unit}`
123123
: `${day.date}: ${count} ${unit}`;
124124

0 commit comments

Comments
 (0)