Skip to content

Commit 88e0840

Browse files
authored
Merge branch 'main' into refactor/extract-renderRadarScan-helper
2 parents 82c4ea4 + e6b5723 commit 88e0840

102 files changed

Lines changed: 10234 additions & 1634 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55
branches: [main, dev]
66
pull_request:
77
branches: [main, dev]
8+
merge_group: # Run CI on the merged commit before it lands on main
89

910
jobs:
1011
quality:

.github/workflows/conflict-notifier.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ jobs:
8181
});
8282
}
8383
84-
// Check existing comments to enforce a 2-hour notification gap
84+
// Check existing comments to enforce a 24-hour notification gap
8585
const { data: comments } = await github.rest.issues.listComments({
8686
owner: context.repo.owner,
8787
repo: context.repo.repo,
@@ -101,8 +101,8 @@ jobs:
101101
const lastComment = conflictComments[conflictComments.length - 1];
102102
const lastCommentTime = new Date(lastComment.created_at).getTime();
103103
const nowTime = new Date().getTime();
104-
const TWO_HOURS_MS = 2 * 60 * 60 * 1000;
105-
if (nowTime - lastCommentTime >= TWO_HOURS_MS) {
104+
const TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1000;
105+
if (nowTime - lastCommentTime >= TWENTY_FOUR_HOURS_MS) {
106106
shouldComment = true;
107107
}
108108
}

CONTRIBUTING.md

Lines changed: 137 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
- [The Standard We Hold](#-the-standard-we-hold)
1111
- [Local Setup](#-local-setup)
12-
- [Testing Your Changes](#-testing-your-changes)
12+
- [Testing & Previewing Your Changes](#-testing--previewing-your-changes)
1313
- [What to Contribute](#-what-to-contribute)
1414
- [Automated Issue Management & Claiming](#-automated-issue-management--claiming)
1515
- [Branch & Commit Conventions](#-branch--commit-conventions)
@@ -89,13 +89,13 @@ http://localhost:3000/api/streak?user=YOUR_GITHUB_USERNAME
8989
9090
---
9191

92-
## 🧪 Testing Your Changes
92+
## 🧪 Testing & Previewing Your Changes
9393

94-
This section covers everything you need to verify your changes work correctly **before opening a PR**. There are two layers of testing: visual browser preview and the automated test suite.
94+
Before opening a PR, every contributor is expected to verify their changes locally across three areas: **visual SVG output**, **unit tests**, and **branch coverage**. This section explains how to do all three.
9595

9696
### 1. Visual Browser Preview
9797

98-
With your dev server running (`npm run dev`), open your browser and visit these URLs to preview the SVG output directly:
98+
With your dev server running (`npm run dev`), open your browser and visit these URLs to preview the SVG output directly**open them as raw URLs, not embedded in an `<img>` tag or Markdown preview**:
9999

100100
**Standard badge — valid username:**
101101

@@ -127,46 +127,90 @@ http://localhost:3000/api/streak?user=vivek%20Sangani
127127
http://localhost:3000/api/streak?user=xyzabc999notreallll
128128
```
129129

130+
**Useful DevTools tricks:**
131+
132+
- **Inspect SVG structure:** Open DevTools (`Cmd+Option+I` / `F12`) → Elements panel → expand the `<svg>` node to inspect every path, rect, and text element
133+
- **Force a fresh fetch:** Add `&refresh=true` to bypass the cache: `http://localhost:3000/api/streak?user=YOUR_GITHUB_USERNAME&refresh=true`
134+
- **Test different params live:** `?theme=obsidian`, `?size=large`, `?grace=2`, `?autoTheme=true`
135+
130136
> **⚠️ Browser XML error warning:** If your browser shows an `EntityRef: expecting ';'` error instead of rendering the SVG, it means an unescaped `&` character exists somewhere in the SVG output. All `&` characters inside SVG `<style>` blocks (e.g. in Google Fonts `@import` URLs) must be written as `&amp;`. Check `lib/svg/generator.ts` for any raw `&` in template literals.
131137
132138
> **💡 Tip:** For a cleaner SVG preview, open the URL in **Firefox** — it renders SVG directly in the browser with no wrapper page. Chrome wraps it in an XML viewer which can show false parse warnings.
133139
134-
### 2. Running the Vitest Test Suite
140+
### 2. Offline SVG Inspection with SVG Viewer
141+
142+
When you cannot run a dev server — or you want to inspect the raw SVG geometry in isolation — use **[SVG Viewer](https://www.svgviewer.dev/)**.
143+
144+
**Step-by-step:**
145+
146+
1. With the dev server running, open the badge URL in your browser
147+
2. Right-click anywhere on the page → **View Page Source** (`Cmd+U` / `Ctrl+U`)
148+
3. Select all (`Cmd+A`) → Copy (`Cmd+C`)
149+
4. Go to [https://www.svgviewer.dev/](https://www.svgviewer.dev/)
150+
5. Paste the SVG source into the left panel — the right panel renders it instantly
151+
152+
**What to check in SVG Viewer:**
153+
154+
- Tower geometry is correctly positioned on the isometric grid
155+
- No overlapping paths or misaligned elements
156+
- Text labels are readable and not clipped by the SVG boundary
157+
- Glow filter (`<feGaussianBlur>`) renders at the correct intensity
158+
- The `<title>` accessibility tag is present on every tower group (required by CONTRIBUTING.md a11y rules)
159+
160+
### 3. Running the Vitest Test Suite
135161

136162
CommitPulse uses **Vitest** for unit and integration tests. Run the full test suite with:
137163

138164
```bash
165+
# Run all tests once (use before committing)
139166
npm run test
140-
```
141167

142-
To run tests in watch mode while you develop (reruns on every file save):
143-
144-
```bash
168+
# Re-run automatically on every file save (use during active development)
145169
npm run test -- --watch
146-
```
147170

148-
To run only a specific test file:
149-
150-
```bash
171+
# Run only a specific test file in isolation (fastest way to debug a failure)
151172
npm run test -- lib/calculate.test.ts
173+
174+
# Run only tests whose name matches a pattern
175+
npm run test -- --reporter=verbose -t "generateVersusSVG"
152176
```
153177

154178
**What a passing run looks like:**
155179

156180
```
157-
✓ lib/calculate.test.ts (12 tests)
158-
✓ lib/svg/generator.test.ts (8 tests)
159-
✓ app/api/streak/route.test.ts (6 tests)
181+
✓ lib/calculate.test.ts (43 tests)
182+
✓ lib/svg/generator.test.ts (19 tests)
183+
✓ app/api/streak/route.test.ts (112 tests)
160184
161-
Test Files 3 passed (3)
162-
Tests 26 passed (26)
185+
Test Files 70 passed (70)
186+
Tests 819 passed (819)
163187
```
164188

165189
> **🚨 All tests must pass before you open a PR.** The CI pipeline runs `npm run test` automatically on every pull request and will block merging if any test fails.
166190
167-
### 3. Interpreting SVG Output in the Browser
191+
### 4. Interpreting Vitest Output
192+
193+
Vitest output can look intimidating at first. Here is how to read it:
194+
195+
```
196+
FAIL lib/svg/generator.test.ts
197+
198+
AssertionError: expected '<svg width="600"…' to include 'width="100%"'
199+
200+
- Expected: width="100%"
201+
+ Received: width="600"
202+
```
203+
204+
| Part of output | What it means |
205+
| ------------------------------------ | ----------------------------------------------------------------- |
206+
| `FAIL lib/svg/generator.test.ts` | The test file that contains the failing test |
207+
| `AssertionError` | The `expect()` assertion did not match |
208+
| `- Expected` | What the test wanted to see in your output |
209+
| `+ Received` | What your code actually produced |
210+
| `stderr \| …` | Expected console output from error-handling tests — **not a bug** |
211+
| `✓ lib/calculate.test.ts (43 tests)` | This file passed — all good |
168212

169-
When you open a badge URL in your browser, here is what each response means:
213+
**Interpreting SVG URL responses in the browser:**
170214

171215
| What you see | What it means |
172216
| -------------------------------------------- | ---------------------------------------------------------- |
@@ -177,15 +221,61 @@ When you open a badge URL in your browser, here is what each response means:
177221
| Browser XML parse error / blank white page | ❌ Bug — unescaped `&` or malformed SVG in generator |
178222
| `401 Unauthorized` in the terminal | ❌ Your `GITHUB_PAT` in `.env.local` is missing or invalid |
179223

180-
### 4. Lint and Format
224+
### 5. Checking Branch Coverage Before Pushing
225+
226+
The CI pipeline enforces a **minimum 70% branch coverage** across all `lib/` files. If your PR drops coverage below this threshold, it will be **blocked from merging automatically** — no exceptions.
227+
228+
Always run the coverage report locally before pushing:
229+
230+
```bash
231+
npm run test:coverage
232+
```
233+
234+
The output shows a table like this:
235+
236+
```
237+
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
238+
-------------------|---------|----------|---------|---------|------------------
239+
lib/svg/generator | 72.22 | 49.73 | 72.22 | 72.72 | 823-885,1116,1136
240+
lib/calculate | 99.00 | 93.54 | 100.00 | 98.92 | 167
241+
```
242+
243+
**What each column means:**
244+
245+
| Column | CI Gate | What it measures |
246+
| ------------------- | --------- | -------------------------------------------------------------------------- |
247+
| `% Branch` | **≥ 70%** | Every `if/else`, ternary, `??`, and `&&` path covered by at least one test |
248+
| `% Funcs` | ≥ 70% | Every exported function called at least once in tests |
249+
| `% Stmts` | ≥ 70% | Every statement executed at least once |
250+
| `Uncovered Line #s` || Exact lines with no test coverage — start here when fixing gaps |
251+
252+
**The most important file:** `lib/svg/generator.ts` has the most complex branch logic (auto-theme vs static, size scaling, particle generation, ghost city mode, versus mode). If you add any new `if/else` or ternary logic here, you **must** add tests that exercise both branches — otherwise coverage will drop and block your PR.
253+
254+
**If coverage drops below 70%:**
255+
256+
1. Look at the `Uncovered Line #s` column for the failing file
257+
2. Open the file and find those exact lines
258+
3. Identify which branch condition is untested (the `if` path? the `else`? a ternary fallback?)
259+
4. Add a test that reaches that code path
260+
5. Re-run `npm run test:coverage` and confirm the percentage recovered
261+
262+
> **Tip:** The coverage report is also generated as an HTML file. Open it in your browser for a visual, line-by-line view of exactly which branches are hit (green) and which are missed (red):
263+
264+
```bash
265+
open coverage/index.html # macOS
266+
xdg-open coverage/index.html # Linux
267+
start coverage/index.html # Windows
268+
```
269+
270+
### 6. Lint and Format
181271

182272
Run these before every commit:
183273

184274
```bash
185-
# Auto-format all files
275+
# Auto-format all files to match the project's Prettier config
186276
npm run format
187277

188-
# Check for linting errors
278+
# Check for any remaining linting errors
189279
npm run lint
190280
```
191281

@@ -386,6 +476,7 @@ Use the following format: `type/short-description`
386476
| Timezone work | `fix/utc-midnight-edge-case` |
387477
| Documentation | `docs/readme-update` |
388478
| Refactor | `refactor/generator-cleanup` |
479+
| Testing | `test/generator-coverage` |
389480

390481
### Commit Messages
391482

@@ -399,6 +490,7 @@ feat(themes): add aurora preset with teal-pink palette
399490
fix(calculate): handle grace period when today has zero contributions
400491
docs(readme): add aurora theme to parameter table
401492
refactor(generator): extract tower path builder into helper function
493+
test(generator): add Vitest coverage for generateVersusSVG
402494
```
403495

404496
**Types:** `feat`, `fix`, `docs`, `refactor`, `chore`, `test`
@@ -433,6 +525,8 @@ Fixes # (issue number)
433525
- [ ] I have read the `CONTRIBUTING.md` file.
434526
- [ ] I have tested these changes locally (`localhost:3000/api/streak?user=YOUR_USERNAME`).
435527
- [ ] I have run `npm run format` and `npm run lint` locally and resolved all errors (CI will fail otherwise).
528+
- [ ] I have run `npm run test` and all tests pass locally.
529+
- [ ] I have run `npm run test:coverage` and branch coverage is at or above 70%.
436530
- [ ] My commits follow the Conventional Commits format (e.g., `feat(themes): ...`, `fix(calculate): ...`).
437531
- [ ] I have updated `README.md` if I added a new theme or URL parameter.
438532
- [ ] I have started the repo.
@@ -464,10 +558,29 @@ npm run lint
464558

465559
# 3. Ensure all tests pass
466560
npm run test
561+
562+
# 4. Verify branch coverage stays at or above 70%
563+
npm run test:coverage
467564
```
468565

469566
Fix every error before pushing. If you add new logic or features, you are expected to write tests for them.
470567

568+
### Coverage Threshold
569+
570+
The CI pipeline enforces a **minimum 70% branch coverage** across all measured files. This is not optional — PRs that drop coverage below this threshold are automatically blocked from merging.
571+
572+
The threshold exists because branch coverage (covering every `if/else`, ternary, and `??` path) is the most reliable indicator that edge cases are tested. Statement coverage alone is not enough.
573+
574+
**Files most likely to affect the gate:**
575+
576+
| File | Why it matters |
577+
| ------------------------- | -------------------------------------------------------------------------- |
578+
| `lib/svg/generator.ts` | Most complex branching — auto-theme, size scaling, ghost city, versus mode |
579+
| `lib/calculate.ts` | Streak logic — grace period, timezone edge cases, empty calendars |
580+
| `app/api/streak/route.ts` | Request handling — validation, rate limit, not-found, error paths |
581+
582+
If you add a new `if/else`, ternary, or optional-chain (`?.`) anywhere in `lib/`, you must add a test covering both the truthy and falsy paths before pushing.
583+
471584
### Testing Guidelines
472585

473586
We use **Vitest** alongside **React Testing Library** for our test suite.
@@ -479,7 +592,7 @@ We use **Vitest** alongside **React Testing Library** for our test suite.
479592
- **Humanic Comments:** Comments in test files should explain _why_ a test exists or what specific edge-case it covers, rather than just repeating what the code does line-by-line.
480593

481594
> **🚨 GitHub Actions CI Gate**
482-
> Our CI pipeline runs `npm run lint`, `npm run format --check`, and `npm run test` automatically on **every pull request**. If your code fails any check, **the PR will be blocked from merging** until the issues are resolved. There is no way to bypass this gate — so run the commands locally first and save yourself the round-trip.
595+
> Our CI pipeline runs `npm run lint`, `npm run format --check`, `npm run test`, and `npm run test:coverage` automatically on **every pull request**. If your code fails any check, **the PR will be blocked from merging** until the issues are resolved. There is no way to bypass this gate — so run the commands locally first and save yourself the round-trip.
483596
484597
**Key style rules:**
485598

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ URL Parameter > Theme Default > System Fallback
177177
| `tz` | `string` | No | Omitted = UTC | IANA timezone (e.g. `Asia/Kolkata`, `America/New_York`) — aligns "today" with the user local midnight. Note: `?tz=UTC` is valid but cached separately from omitting `tz`. |
178178
| `lang` | `string` | No | `en` | Language code for labels (`en`, `es`, `hi`, `fr`, `pt`, `ko`, `ja`, `de`, `zh`) |
179179
| `view` | `string` | No | `default` | Rendering mode: `default` (3D Monolith), `monthly` (Compact monthly stats), or `heatmap` (flat 2D contribution heatmap) |
180+
| `entrance` | `string` | No | `rise` | Entrance animation for towers: `rise` (default), `fade`, `slide`, or `none`. |
180181
| `delta_format` | `string` | No | `percent` | Format for month-over-month delta in monthly view: `percent` (e.g. +12%), `absolute` (e.g. +15 commits), or `both` |
181182
| `width` | `number` | No | `300` | Custom width for the SVG canvas (currently only applies to `view=monthly`) |
182183
| `height` | `number` | No | `120` | Custom height for the SVG canvas (currently only applies to `view=monthly`) |
@@ -188,6 +189,7 @@ URL Parameter > Theme Default > System Fallback
188189
| `labelColor` | `hex` | No || Custom text color for the isometric labels — **without** `#` |
189190
| `versus` | `string` | No || GitHub username of an opponent to compare against in side-by-side versus mode |
190191
| `shading` | `boolean` | No | `false` | Apply intensity-based opacity shading to tower faces so lower intensity levels appear slightly dimmer |
192+
| `opacity` | `number` | No | `1.0` | Global opacity scalar for all tower fill-opacity values (0.1–1.0). `opacity=0.5` = semi-transparent ghost look. `opacity=0.8` = faded, great on light backgrounds. |
191193
| `gradient` | `boolean` | No | `false` | Opt-in to show volumetric gradients on the monolith floor |
192194

193195
### Grace Period Examples
@@ -220,6 +222,7 @@ URL Parameter > Theme Default > System Fallback
220222
| `random` | Surprise theme on reload | _varies_ | _varies_ | _varies_ |
221223
| `highcontrast` | Accessibility high contrast | `0a0a0a` | `ff4500` | `ffffff` |
222224
| `cyber-pulse` | AMOLED true-black & cyan | `000000` | `00ffee` | `ffffff` |
225+
| `obsidian` | Deep charcoal & amber gold | `1a1a2e` | `f59e0b` | `e2e8f0` |
223226

224227
> **`auto` uses CSS `@media (prefers-color-scheme)`** inside the SVG so the badge switches between the `light` and `dark` palettes based on the viewer's OS setting — no JavaScript required. This is ideal for GitHub profile READMEs where visitors may use either mode.
225228
@@ -320,6 +323,14 @@ Explore some of the built-in CommitPulse themes and quickly copy the style you l
320323

321324
![](https://commitpulse.vercel.app/api/streak?user=jhasourav07&gradient=true&shading=true)
322325

326+
<!-- Semi-transparent ghost city look -->
327+
328+
![](https://commitpulse.vercel.app/api/streak?user=jhasourav07&opacity=0.5)
329+
330+
<!-- Slightly faded — perfect for light background embeds -->
331+
332+
![](https://commitpulse.vercel.app/api/streak?user=jhasourav07&opacity=0.8)
333+
323334
<!-- GitHub-style Heatmap View -->
324335

325336
![](https://commitpulse.vercel.app/api/streak?user=jhasourav07&view=heatmap)

THEMES.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# 🎨 CommitPulse Themes
22

3-
All 19 available themes for your CommitPulse badge. Use the `?theme=<slug>` query parameter to apply a theme.
3+
All 20 available themes for your CommitPulse badge. Use the `?theme=<slug>` query parameter to apply a theme.
44

55
```
66
https://commitpulse.vercel.app/api/streak?user=YOUR_USERNAME&theme=<slug>
@@ -30,6 +30,7 @@ https://commitpulse.vercel.app/api/streak?user=YOUR_USERNAME&theme=<slug>
3030
| solarized_light | `#fdf6e3` | `#586e75` | `#268bd2` |
3131
| gruvbox_light | `#fbf1c7` | `#3c3836` | `#d65d0e` |
3232
| nord_light | `#eceff4` | `#2e3440` | `#5e81ac` |
33+
| obsidian | `#1a1a2e` | `#e2e8f0` | `#f59e0b` |
3334
| cyber-pulse | `#000000` | `#ffffff` | `#00ffee` |
3435

3536
---
@@ -264,6 +265,18 @@ https://commitpulse.vercel.app/api/streak?user=YOUR_USERNAME&theme=<slug>
264265

265266
---
266267

268+
### 🪨 Obsidian
269+
270+
![obsidian](https://commitpulse.vercel.app/api/streak?user=jhasourav07&theme=obsidian)
271+
272+
| Parameter | Value |
273+
| --------- | ------ |
274+
| `bg` | 1a1a2e |
275+
| `text` | e2e8f0 |
276+
| `accent` | f59e0b |
277+
278+
---
279+
267280
## 🎛️ Custom Theme
268281

269282
Not finding what you want? Build your own using raw color parameters — all values are hex codes **without** the `#` prefix:

0 commit comments

Comments
 (0)