Skip to content

Conversation

@zetaloop
Copy link

@zetaloop zetaloop commented Jun 13, 2024

Add custom theme support to the cards.

Here are the new query parameters that can be used to customize the theme of the cards:
title_color, text_color, bg_color, border_color, icon_color, chart_color

The custom color will override the color of the selected theme.
For example, use theme=dark&bg_color=00000000 as dark theme with a transparent background.

Resolve:

#110 - Custom theme
#152 - Custom background color

Example:

https://github-profile-summary-cards.vercel.app/api/cards/productive-time?username=vn7n24fzkq&title_color=ff0&text_color=0ff&bg_color=665544&border_color=f0f&icon_color=1234ff&chart_color=ff4321a0

image

Summary by CodeRabbit

  • New Features

    • Added custom color theming to multiple cards (most-commit-language, repos-per-language, productive time, profile details, stats): users can override title, text, background, border, icon, and chart colors.
    • Language and repository views now support excluding specified languages from visuals via exclusion lists.
  • Chores

    • Test config adjusted to ensure Node-compatible HTTP client resolution during tests.

@coderabbitai
Copy link

coderabbitai bot commented Jun 13, 2024

Walkthrough

Cards and internal SVG generators were extended to accept an optional custom Theme built from six new color query parameters; numerous card APIs and card modules now pass a Theme object through updated function signatures to drive SVG color overrides.

Changes

Cohort / File(s) Summary
API card endpoints
api/cards/most-commit-language.ts, api/cards/repos-per-language.ts
Added validation for new color query params (title_color, text_color, bg_color, border_color, icon_color, chart_color); build a Theme from them and pass customTheme into SVG generation calls; preserve existing error/token rotation handling.
Card modules — commits / repos / productive-time / profile / stats / languages
src/cards/most-commit-language-card.ts, src/cards/repos-per-language-card.ts, src/cards/productive-time-card.ts, src/cards/profile-details-card.ts, src/cards/stats-card.ts, src/cards/repos-per-language-card.ts, src/cards/most-commit-language-card.ts
Introduced Theme type import and threaded a `customTheme: Theme
Public API/signature changes
(examples) getReposPerLanguageSVGWithThemeName, getCommitsLanguageSVGWithThemeName, getProductiveTimeSVGWithThemeName, getProfileDetailsSVGWithThemeName, getStatsSVGWithThemeName
Several exported functions gained an additional customTheme: Theme parameter and related internal functions accept `customTheme: Theme
Test config
jest.config.json
Added moduleNameMapper entry aliasing axios to axios/dist/node/axios.cjs for Node-specific Axios resolution in tests.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API as Card API Endpoint
    participant Theme as Theme Constructor
    participant CardModule as Card SVG Generator
    participant ThemeMap as ThemeMap
    participant SVG as SVG Template

    Note over Client,API: Request with optional color query params
    Client->>API: GET /api/card?username=...&title_color=...&...
    API->>Theme: validate params & new Theme(...)
    Theme-->>API: customTheme
    API->>CardModule: get...SVGWithThemeName(username, themeName, customTheme, ...)
    CardModule->>ThemeMap: base = ThemeMap.get(themeName)
    alt customTheme provided
        CardModule->>CardModule: merge base with customTheme (prefix '#' where needed, set strokeOpacity)
    end
    CardModule->>SVG: render with merged theme
    SVG-->>API: SVG string
    API-->>Client: 200 SVG
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas to focus on:
    • All updated public/exported function signatures — verify every caller across the repo was updated to match new parameter order.
    • The merge/override logic for customTheme (prefixing #, strokeOpacity handling) — confirm consistent behavior and no unintended color corruption.
    • Tests and build impacts from the Jest axios alias change.
    • Type imports/exports for Theme and any downstream type effects.

Poem

🐰 I nibbled code and found new hues,

title, text, bg — a rainbow fuse,
Signatures hopped, the SVGs gleam,
Custom themes now color the dream,
Little rabbit cheers — hop, commit, and beam! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "feat: Add custom theme support" directly and accurately describes the primary change in the pull request. The changeset focuses on introducing custom theme support by adding new query parameters (title_color, text_color, bg_color, border_color, icon_color, chart_color) and propagating them through multiple card generation functions. The title is specific and descriptive, avoiding vague terms, and clearly conveys the feature being added. A teammate scanning the pull request history would immediately understand that this PR adds the ability to customize themes, which aligns precisely with the modifications made across the codebase.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Outside diff range and nitpick comments (9)
api/cards/stats.ts (1)

Line range hint 4-64: Refactor repeated type checks to a utility function for cleaner code.

+ function validateParameter(param, paramName, res) {
+     if (typeof param !== 'string') {
+         res.status(400).send(`${paramName} must be a string`);
+         return false;
+     }
+     return true;
+ }

-    if (typeof theme !== 'string') {
+    if (!validateParameter(theme, 'theme', res)) {
-        res.status(400).send('theme must be a string');
-        return;
-    }
...
api/cards/profile-details.ts (1)

Line range hint 4-64: Refactor repeated type checks to a utility function for cleaner code, similar to the suggestion in stats.ts.

api/cards/productive-time.ts (1)

Line range hint 4-69: Refactor repeated type checks to a utility function for cleaner code, similar to the suggestion in stats.ts.

src/cards/most-commit-language-card.ts (1)

Line range hint 26-59: Consider converting this function expression to an arrow function to reduce complexity and improve readability.

- const getCommitsLanguageSVG = function (
+ const getCommitsLanguageSVG = (
    langData: {name: string; value: number; color: string}[],
    themeName: string,
    customTheme: Theme | undefined
): string => {
Tools
GitHub Check: lint

[failure] 48-48:
'theme' is never reassigned. Use 'const' instead


[failure] 48-48:
Replace ·...ThemeMap.get(themeName)!· with ...ThemeMap.get(themeName)!


[failure] 50-50:
Replace "#" with '#'


[failure] 51-51:
Replace "#" with '#'


[failure] 52-52:
Replace "#" with '#'


[failure] 53-53:
Replace ·theme.stroke·=·"#"·+·customTheme.stroke;·theme.strokeOpacity·=·1; with ⏎············theme.stroke·=·'#'·+·customTheme.stroke;⏎············theme.strokeOpacity·=·1;⏎·······


[failure] 54-54:
Replace "#" with '#'


[failure] 55-55:
Replace "#" with '#'

api/cards/repos-per-language.ts (1)

Line range hint 67-69: Consider using an arrow function for the forEach callback to enhance readability and align with modern JavaScript practices.

- exclude.split(',').forEach(function (val) {
+ exclude.split(',').forEach(val => {
src/cards/stats-card.ts (1)

Line range hint 47-112: Consider converting this function into an arrow function to make the code more concise and to use lexical scoping.

- const getStatsData = async function (
+ const getStatsData = async (
src/cards/profile-details-card.ts (3)

Line range hint 9-24: Use arrow functions for these kinds of simple transformations to improve readability and maintain lexical scoping.

- export const createProfileDetailsCard = async function (username: string) {
+ export const createProfileDetailsCard = async (username: string) => {
Tools
Biome

[error] 25-34: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.


[error] 36-54: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.


Line range hint 56-72: Consider using arrow functions to simplify these utility functions, which do not require their own this context.

- const getProfileDateJoined = function (profileDetails: ProfileDetails): string {
+ const getProfileDateJoined = (profileDetails: ProfileDetails): string => {

Line range hint 74-147: This function could benefit from the arrow function syntax for better readability and scoping.

- const getProfileDetailsData = async function (
+ const getProfileDetailsData = async (
Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 28c14b9 and 10bce53.

Files selected for processing (10)
  • api/cards/most-commit-language.ts (2 hunks)
  • api/cards/productive-time.ts (2 hunks)
  • api/cards/profile-details.ts (2 hunks)
  • api/cards/repos-per-language.ts (3 hunks)
  • api/cards/stats.ts (2 hunks)
  • src/cards/most-commit-language-card.ts (3 hunks)
  • src/cards/productive-time-card.ts (2 hunks)
  • src/cards/profile-details-card.ts (2 hunks)
  • src/cards/repos-per-language-card.ts (2 hunks)
  • src/cards/stats-card.ts (2 hunks)
Additional context used
Biome
src/cards/repos-per-language-card.ts

[error] 6-13: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.


[error] 15-24: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.


[error] 26-42: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.


[error] 56-58: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.


[error] 44-61: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.

src/cards/most-commit-language-card.ts

[error] 6-13: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.


[error] 15-24: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.


[error] 26-59: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.


[error] 76-78: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.


[error] 61-82: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.

api/cards/repos-per-language.ts

[error] 67-69: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.

api/cards/most-commit-language.ts

[error] 58-60: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.

src/cards/productive-time-card.ts

[error] 6-13: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.


[error] 15-24: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.


[error] 26-43: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.


[error] 49-60: This else clause can be omitted because previous branches break early. (lint/style/noUselessElse)

Unsafe fix: Omit the else clause.


[error] 53-60: This else clause can be omitted because previous branches break early. (lint/style/noUselessElse)


[error] 57-60: This else clause can be omitted because previous branches break early. (lint/style/noUselessElse)


[error] 45-61: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.


[error] 63-91: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.

src/cards/stats-card.ts

[error] 9-16: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.


[error] 18-26: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.


[error] 28-45: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.


[error] 47-112: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.

src/cards/profile-details-card.ts

[error] 9-24: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.


[error] 25-34: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.


[error] 36-54: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.


[error] 56-72: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.


[error] 74-147: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.

GitHub Check: lint
src/cards/most-commit-language-card.ts

[failure] 48-48:
'theme' is never reassigned. Use 'const' instead


[failure] 48-48:
Replace ·...ThemeMap.get(themeName)!· with ...ThemeMap.get(themeName)!


[failure] 50-50:
Replace "#" with '#'


[failure] 51-51:
Replace "#" with '#'


[failure] 52-52:
Replace "#" with '#'


[failure] 53-53:
Replace ·theme.stroke·=·"#"·+·customTheme.stroke;·theme.strokeOpacity·=·1; with ⏎············theme.stroke·=·'#'·+·customTheme.stroke;⏎············theme.strokeOpacity·=·1;⏎·······


[failure] 54-54:
Replace "#" with '#'


[failure] 55-55:
Replace "#" with '#'

src/cards/productive-time-card.ts

[failure] 32-32:
'theme' is never reassigned. Use 'const' instead


[failure] 32-32:
Replace ·...ThemeMap.get(themeName)!· with ...ThemeMap.get(themeName)!

Additional comments not posted (7)
src/cards/most-commit-language-card.ts (2)

18-18: The addition of customTheme parameter allows for dynamic theming which is a great feature enhancement.

Also applies to: 23-23


9-9: Consider using an arrow function for better consistency and to leverage ES6 features.

-  for (const themeName of ThemeMap.keys()) {
+  ThemeMap.keys().forEach(themeName => {

Likely invalid or redundant comment.

api/cards/repos-per-language.ts (1)

9-19: The addition of multiple theme-related parameters enhances customization capabilities. Ensure thorough validation for each parameter to prevent potential security risks or errors.

api/cards/most-commit-language.ts (1)

9-19: Similar to the previous file, ensure that all new query parameters are validated properly to avoid errors and potential security issues.

src/cards/productive-time-card.ts (2)

18-23: The addition of customTheme parameter allows for dynamic theming which is a great feature enhancement.


9-9: Using an arrow function here would align with ES6 best practices and improve readability.

- for (const themeName of ThemeMap.keys()) {
+ ThemeMap.keys().forEach(themeName => {

Likely invalid or redundant comment.

src/cards/stats-card.ts (1)

18-26: Refactor to use arrow function for consistency and to leverage lexical this.
[REFACTOR_SUGGESTion]

- export const getStatsSVGWithThemeName = async function (
+ export const getStatsSVGWithThemeName = async (
Tools
Biome

[error] 18-26: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.

Comment on lines 29 to 45
StatsData: {index: number; icon: string; name: string; value: string}[],
themeName: string
themeName: string,
customTheme: Theme | undefined
) {
const title = 'Stats';
const svgString = statsCard(`${title}`, StatsData, ThemeMap.get(themeName)!);
let theme = { ...ThemeMap.get(themeName)! };
if (customTheme !== undefined) {
if (customTheme.title) theme.title = "#" + customTheme.title;
if (customTheme.text) theme.text = "#" + customTheme.text;
if (customTheme.background) theme.background = "#" + customTheme.background;
if (customTheme.stroke) { theme.stroke = "#" + customTheme.stroke; theme.strokeOpacity = 1; }
if (customTheme.icon) theme.icon = "#" + customTheme.icon;
if (customTheme.chart) theme.chart = "#" + customTheme.chart;
}
const svgString = statsCard(`${title}`, StatsData, theme);
return svgString;
};

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function can be simplified using an arrow function, improving readability and scoping.

- const getStatsSVG = function (
+ const getStatsSVG = (
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const getStatsSVG = function (
StatsData: {index: number; icon: string; name: string; value: string}[],
themeName: string
themeName: string,
customTheme: Theme | undefined
) {
const title = 'Stats';
const svgString = statsCard(`${title}`, StatsData, ThemeMap.get(themeName)!);
let theme = { ...ThemeMap.get(themeName)! };
if (customTheme !== undefined) {
if (customTheme.title) theme.title = "#" + customTheme.title;
if (customTheme.text) theme.text = "#" + customTheme.text;
if (customTheme.background) theme.background = "#" + customTheme.background;
if (customTheme.stroke) { theme.stroke = "#" + customTheme.stroke; theme.strokeOpacity = 1; }
if (customTheme.icon) theme.icon = "#" + customTheme.icon;
if (customTheme.chart) theme.chart = "#" + customTheme.chart;
}
const svgString = statsCard(`${title}`, StatsData, theme);
return svgString;
};
const getStatsSVG = (
StatsData: {index: number; icon: string; name: string; value: string}[],
themeName: string,
customTheme: Theme | undefined
) => {
const title = 'Stats';
let theme = { ...ThemeMap.get(themeName)! };
if (customTheme !== undefined) {
if (customTheme.title) theme.title = "#" + customTheme.title;
if (customTheme.text) theme.text = "#" + customTheme.text;
if (customTheme.background) theme.background = "#" + customTheme.background;
if (customTheme.stroke) { theme.stroke = "#" + customTheme.stroke; theme.strokeOpacity = 1; }
if (customTheme.icon) theme.icon = "#" + customTheme.icon;
if (customTheme.chart) theme.chart = "#" + customTheme.chart;
}
const svgString = statsCard(`${title}`, StatsData, theme);
return svgString;
};
Tools
Biome

[error] 28-45: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.

Comment on lines 15 to 24
export const getReposPerLanguageSVGWithThemeName = async function (
username: string,
themeName: string,
customTheme: Theme,
exclude: Array<string>
) {
if (!ThemeMap.has(themeName)) throw new Error('Theme does not exist');
const langData = await getRepoLanguageData(username, exclude);
return getReposPerLanguageSVG(langData, themeName);
return getReposPerLanguageSVG(langData, themeName, customTheme);
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convert to arrow function for improved readability and consistency.

- export const getReposPerLanguageSVGWithThemeName = async function (
+ export const getReposPerLanguageSVGWithThemeName = async (
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const getReposPerLanguageSVGWithThemeName = async function (
username: string,
themeName: string,
customTheme: Theme,
exclude: Array<string>
) {
if (!ThemeMap.has(themeName)) throw new Error('Theme does not exist');
const langData = await getRepoLanguageData(username, exclude);
return getReposPerLanguageSVG(langData, themeName);
return getReposPerLanguageSVG(langData, themeName, customTheme);
};
export const getReposPerLanguageSVGWithThemeName = async (
username: string,
themeName: string,
customTheme: Theme,
exclude: Array<string>
) => {
if (!ThemeMap.has(themeName)) throw new Error('Theme does not exist');
const langData = await getRepoLanguageData(username, exclude);
return getReposPerLanguageSVG(langData, themeName, customTheme);
};
Tools
Biome

[error] 15-24: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.

Comment on lines +33 to +55
if (typeof title_color !== 'string') {
res.status(400).send('title_color must be a string');
return;
}
if (typeof text_color !== 'string') {
res.status(400).send('text_color must be a string');
return;
}
if (typeof bg_color !== 'string') {
res.status(400).send('bg_color must be a string');
return;
}
if (typeof border_color !== 'string') {
res.status(400).send('border_color must be a string');
return;
}
if (typeof icon_color !== 'string') {
res.status(400).send('icon_color must be a string');
return;
}
if (typeof chart_color !== 'string') {
res.status(400).send('chart_color must be a string');
return;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The repeated pattern of parameter validation can be refactored into a utility function to adhere to DRY principles.

+ function validateParam(param, paramName) {
+   if (typeof param !== 'string') {
+       res.status(400).send(`${paramName} must be a string`);
+       return false;
+   }
+   return true;
+ }
- if (typeof title_color !== 'string') {
-     res.status(400).send('title_color must be a string');
-     return;
- }
+ if (!validateParam(title_color, 'title_color')) return;

Committable suggestion was skipped due to low confidence.

Comment on lines 58 to 60
exclude.split(',').forEach(function (val) {
excludeArr.push(translateLanguage(val));
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using an arrow function for the forEach callback to improve consistency with modern JavaScript syntax.

- exclude.split(',').forEach(function (val) {
+ exclude.split(',').forEach(val => {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
exclude.split(',').forEach(function (val) {
excludeArr.push(translateLanguage(val));
});
exclude.split(',').forEach(val => {
excludeArr.push(translateLanguage(val));
});
Tools
Biome

[error] 58-60: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)

Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.

Comment on lines +33 to +55
if (typeof title_color !== 'string') {
res.status(400).send('title_color must be a string');
return;
}
if (typeof text_color !== 'string') {
res.status(400).send('text_color must be a string');
return;
}
if (typeof bg_color !== 'string') {
res.status(400).send('bg_color must be a string');
return;
}
if (typeof border_color !== 'string') {
res.status(400).send('border_color must be a string');
return;
}
if (typeof icon_color !== 'string') {
res.status(400).send('icon_color must be a string');
return;
}
if (typeof chart_color !== 'string') {
res.status(400).send('chart_color must be a string');
return;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor the repeated validation code into a utility function to reduce redundancy and enhance maintainability.

+ function validateParam(param, paramName) {
+   if (typeof param !== 'string') {
+       res.status(400).send(`${paramName} must be a string`);
+       return false;
+   }
+   return true;
+ }
- if (typeof title_color !== 'string') {
-     res.status(400).send('title_color must be a string');
-     return;
- }
+ if (!validateParam(title_color, 'title_color')) return;

Committable suggestion was skipped due to low confidence.

@vn7n24fzkq
Copy link
Owner

vn7n24fzkq commented Mar 28, 2025

Hi, sorry for the delayed reply.
Thank you for your PR!
And since this PR has been open for a while, I'll merge it and make a few modifications.
If you're still available and willing to help refine it further, I'd really appreciate your assistance.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
api/cards/most-commit-language.ts (1)

33-56: Refactor repetitive validation into a utility function.

The validation logic for the six color parameters is highly repetitive and duplicated across both card endpoint files. This violates DRY principles and increases maintenance burden.

Create a shared validation utility function:

// api/utils/query-validator.ts
export function validateStringParam(
    value: any, 
    paramName: string, 
    res: VercelResponse
): boolean {
    if (typeof value !== 'string') {
        res.status(400).send(`${paramName} must be a string`);
        return false;
    }
    return true;
}

Then refactor this validation block:

-    if (typeof title_color !== 'string') {
-        res.status(400).send('title_color must be a string');
-        return;
-    }
-    if (typeof text_color !== 'string') {
-        res.status(400).send('text_color must be a string');
-        return;
-    }
-    if (typeof bg_color !== 'string') {
-        res.status(400).send('bg_color must be a string');
-        return;
-    }
-    if (typeof border_color !== 'string') {
-        res.status(400).send('border_color must be a string');
-        return;
-    }
-    if (typeof icon_color !== 'string') {
-        res.status(400).send('icon_color must be a string');
-        return;
-    }
-    if (typeof chart_color !== 'string') {
-        res.status(400).send('chart_color must be a string');
-        return;
-    }
+    const colorParams = [
+        { value: title_color, name: 'title_color' },
+        { value: text_color, name: 'text_color' },
+        { value: bg_color, name: 'bg_color' },
+        { value: border_color, name: 'border_color' },
+        { value: icon_color, name: 'icon_color' },
+        { value: chart_color, name: 'chart_color' }
+    ];
+    for (const param of colorParams) {
+        if (!validateStringParam(param.value, param.name, res)) return;
+    }

Apply the same refactoring to api/cards/repos-per-language.ts.

api/cards/repos-per-language.ts (1)

33-56: Apply the same validation refactoring as most-commit-language.ts.

This validation block is identical to lines 33-56 in api/cards/most-commit-language.ts. The same refactoring using a shared utility function should be applied here to eliminate duplication across both files.

🧹 Nitpick comments (1)
api/cards/most-commit-language.ts (1)

67-67: Consider making strokeOpacity optional in Theme constructor.

The hardcoded -1 value with the comment "strokeOpacity is not used in custom themes" suggests this parameter isn't relevant for custom themes. Consider refactoring the Theme class to make strokeOpacity optional or creating a separate factory method for custom themes to avoid passing placeholder values.

Example refactoring in src/const/theme.ts:

export class Theme {
    // ... existing properties
    
    constructor(
        title: string,
        text: string,
        background: string,
        stroke: string,
        strokeOpacity: number,
        icon: string,
        chart: string
    ) { /* ... */ }
    
    static fromCustomColors(
        title: string,
        text: string,
        background: string,
        stroke: string,
        icon: string,
        chart: string
    ): Theme {
        return new Theme(title, text, background, stroke, -1, icon, chart);
    }
}

Then use: let customTheme = Theme.fromCustomColors(title_color, text_color, bg_color, border_color, icon_color, chart_color);

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10bce53 and 0046e1e.

📒 Files selected for processing (2)
  • api/cards/most-commit-language.ts (2 hunks)
  • api/cards/repos-per-language.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
api/cards/repos-per-language.ts (3)
api/cards/most-commit-language.ts (1)
  • req (8-91)
src/const/theme.ts (1)
  • Theme (3-28)
src/cards/repos-per-language-card.ts (1)
  • getReposPerLanguageSVGWithThemeName (15-24)
api/cards/most-commit-language.ts (3)
src/utils/translator.ts (1)
  • translateLanguage (1-273)
src/const/theme.ts (1)
  • Theme (3-28)
src/cards/most-commit-language-card.ts (1)
  • getCommitsLanguageSVGWithThemeName (15-24)
🔇 Additional comments (6)
api/cards/most-commit-language.ts (3)

5-5: LGTM!

The Theme import is correctly added to support custom theming functionality.


76-76: LGTM!

The function call correctly passes the customTheme parameter to enable custom theme rendering.


9-19: Empty string color parameters are correctly handled—no changes required.

Empty strings default for color parameters (when not provided in query) are safely ignored through truthiness checks in the card rendering logic. When customTheme properties contain empty strings, the conditions like if (customTheme.title) evaluate to false and don't override the selected theme's defaults. The implementation is correct and secure.

api/cards/repos-per-language.ts (3)

5-5: LGTM!

The Theme import is correctly added, consistent with the implementation in api/cards/most-commit-language.ts.


9-19: Verify empty string handling (same concern as most-commit-language.ts).

Like in most-commit-language.ts, these color parameters default to empty strings. Ensure consistent handling of empty values across both card endpoints.


76-76: LGTM!

The function call is correctly updated to pass the customTheme parameter, consistent with the updated signature.

Comment on lines +62 to +70
let customTheme = new Theme(
title_color,
text_color,
bg_color,
border_color,
-1, // strokeOpacity is not used in custom themes
icon_color,
chart_color
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Consider validating color format to prevent rendering issues.

The color parameters are passed directly to the Theme constructor without validating that they're valid color values (e.g., hex codes like #ffffff, rgb(), or named colors). Malformed color values could cause unexpected SVG rendering behavior or visual glitches.

Consider adding basic color format validation:

function isValidColor(color: string): boolean {
    if (color === '') return true; // Empty string handled as fallback
    // Validate hex colors (with or without alpha)
    const hexPattern = /^[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/;
    // Validate rgb/rgba
    const rgbPattern = /^rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+(\s*,\s*[\d.]+)?\s*\)$/;
    
    return hexPattern.test(color) || rgbPattern.test(color) || CSS_COLOR_NAMES.includes(color.toLowerCase());
}

// Validate each color parameter before creating the theme
const colors = { title_color, text_color, bg_color, border_color, icon_color, chart_color };
for (const [name, value] of Object.entries(colors)) {
    if (value && !isValidColor(value)) {
        res.status(400).send(`${name} must be a valid color format`);
        return;
    }
}
🤖 Prompt for AI Agents
In api/cards/most-commit-language.ts around lines 62 to 70, the Theme is
constructed with raw color inputs which can produce broken SVGs if malformed;
validate each incoming color (title_color, text_color, bg_color, border_color,
icon_color, chart_color) before building the Theme by: ensure values are strings
or empty (empty = fallback), accept and normalize common formats (hex with
optional leading '#', 3/6/8 hex digits, rgb(...) and rgba(...), and known CSS
color names), reject anything else and respond early with a 400 and a clear
message identifying the invalid parameter; perform normalization (e.g.,
add/strip leading '#' for hex consistently) and only then call new Theme(...) so
rendering always receives valid color values.

Comment on lines +57 to +65
let customTheme = new Theme(
title_color,
text_color,
bg_color,
border_color,
-1, // strokeOpacity is not used in custom themes
icon_color,
chart_color
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Same color validation and strokeOpacity concerns apply here.

The Theme construction in this file has the same issues noted in api/cards/most-commit-language.ts:

  • No color format validation before passing to Theme constructor
  • strokeOpacity placeholder value of -1

Consider applying the same color validation logic and strokeOpacity refactoring suggestions mentioned for the other file.

🤖 Prompt for AI Agents
In api/cards/repos-per-language.ts around lines 57 to 65, the Theme is
constructed without validating color inputs and uses a placeholder strokeOpacity
of -1; apply the same fixes as in api/cards/most-commit-language.ts by
validating/normalizing each color (title_color, text_color, bg_color,
border_color, icon_color, chart_color) before passing them to Theme (use the
shared color validation helper or implement the same validation logic), and stop
using -1 as a magic value for strokeOpacity—either pass a proper opacity value
(0-1) or make strokeOpacity optional in Theme and supply a sensible default when
constructing the Theme; ensure invalid colors fall back to safe defaults or
cause a clear error, and update callers accordingly.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/cards/most-commit-language-card.ts (1)

15-60: Avoid breaking consumers; accept customTheme as optional

Just like the productive-time card, making customTheme a required Theme here breaks existing call sites that only pass (username, themeName, exclude) and clashes with the undefined guard. Please keep it optional (and partial) so the old API contract still works.

 export const getCommitsLanguageSVGWithThemeName = async function (
     username: string,
     themeName: string,
-    customTheme: Theme,
+    customTheme?: Partial<Theme>,
     exclude: Array<string>
 ): Promise<string> {
@@
-const getCommitsLanguageSVG = function (
-    langData: {name: string; value: number; color: string}[],
-    themeName: string,
-    customTheme: Theme | undefined
+const getCommitsLanguageSVG = function (
+    langData: {name: string; value: number; color: string}[],
+    themeName: string,
+    customTheme?: Partial<Theme>
 ): string {
@@
-    const theme = {...ThemeMap.get(themeName)!};
-    if (customTheme !== undefined) {
+    const theme = {...ThemeMap.get(themeName)!};
+    if (customTheme) {
🧹 Nitpick comments (3)
src/cards/profile-details-card.ts (3)

51-59: Consider more explicit checks for optional properties.

The truthiness checks (e.g., if (customTheme.title)) will treat empty strings as falsy. While empty strings aren't valid colors, using explicit checks like !== undefined would make the intent clearer and improve code readability.

Example refactor:

-    if (customTheme.title) theme.title = '#' + customTheme.title;
+    if (customTheme.title !== undefined) theme.title = '#' + customTheme.title;

51-59: Consider defensive handling for color values with existing '#' prefix.

The code unconditionally prepends '#' to color values. If a user accidentally includes the '#' prefix in their query parameter (e.g., title_color=%23ff0), this would result in '##ff0'. While the API layer might strip this, adding a defensive check would improve robustness.

Example:

if (customTheme.title) {
    theme.title = customTheme.title.startsWith('#') ? customTheme.title : '#' + customTheme.title;
}

54-57: Review the strokeOpacity hardcoding behavior.

Setting strokeOpacity = 1 whenever a custom stroke color is provided will override the base theme's stroke opacity. If the base theme uses a semi-transparent stroke (e.g., strokeOpacity: 0.5), this behavior would force it to be fully opaque.

Consider whether:

  1. The opacity should be preserved from the base theme when only the color is customized
  2. A separate stroke_opacity query parameter should be supported
  3. The current behavior (forcing opacity to 1) is the desired UX
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0046e1e and 598bb0d.

📒 Files selected for processing (6)
  • jest.config.json (1 hunks)
  • src/cards/most-commit-language-card.ts (3 hunks)
  • src/cards/productive-time-card.ts (2 hunks)
  • src/cards/profile-details-card.ts (2 hunks)
  • src/cards/repos-per-language-card.ts (2 hunks)
  • src/cards/stats-card.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
src/cards/repos-per-language-card.ts (3)
dist/index.js (4)
  • username (179-179)
  • exclude (181-181)
  • getRepoLanguageData (562-580)
  • getReposPerLanguageSVG (558-561)
src/const/theme.ts (2)
  • ThemeMap (1-1)
  • Theme (3-28)
src/templates/donut-chart-card.ts (1)
  • createDonutChartCard (6-77)
src/cards/most-commit-language-card.ts (3)
dist/index.js (3)
  • username (179-179)
  • exclude (181-181)
  • getCommitsLanguageData (254-272)
src/const/theme.ts (2)
  • ThemeMap (1-1)
  • Theme (3-28)
src/templates/donut-chart-card.ts (1)
  • createDonutChartCard (6-77)
src/cards/productive-time-card.ts (2)
dist/index.js (4)
  • username (179-179)
  • utcOffset (180-180)
  • getProductiveTimeData (341-372)
  • getProductiveTimeSVG (317-320)
src/const/theme.ts (2)
  • ThemeMap (1-1)
  • Theme (3-28)
src/cards/profile-details-card.ts (3)
src/utils/file-writer.ts (1)
  • writeSVG (14-18)
src/const/theme.ts (2)
  • Theme (3-28)
  • ThemeMap (1-1)
src/templates/profile-details-card.ts (1)
  • createDetailCard (6-155)
src/cards/stats-card.ts (2)
dist/index.js (1)
  • getStatsSVG (628-632)
src/const/theme.ts (2)
  • Theme (3-28)
  • ThemeMap (1-1)
🔇 Additional comments (4)
src/cards/profile-details-card.ts (4)

1-1: LGTM!

The import of the Theme type is necessary for the new custom theme parameter typing.


18-19: LGTM!

Correctly passes undefined for the custom theme during batch generation, maintaining existing behavior for the default card generation workflow.


25-40: LGTM!

The function signature extension and pass-through of customTheme are implemented correctly.


49-49: LGTM!

The shallow clone using the spread operator is appropriate since Theme contains only primitive values (strings and numbers).

Comment on lines 15 to +44
export const getProductiveTimeSVGWithThemeName = async function (
username: string,
themeName: string,
customTheme: Theme,
utcOffset: number
) {
if (!ThemeMap.has(themeName)) throw new Error('Theme does not exist');
const productiveTimeData = await getProductiveTimeData(username, utcOffset);
return getProductiveTimeSVG(productiveTimeData, themeName, utcOffset);
return getProductiveTimeSVG(productiveTimeData, themeName, customTheme, utcOffset);
};

const getProductiveTimeSVG = function (
productiveTimeData: Array<number>,
themeName: string,
customTheme: Theme | undefined,
utcOffset: number
): string {
const svgString = productiveTimeCard(productiveTimeData, ThemeMap.get(themeName)!, utcOffset);
const theme = {...ThemeMap.get(themeName)!};
if (customTheme !== undefined) {
if (customTheme.title) theme.title = '#' + customTheme.title;
if (customTheme.text) theme.text = '#' + customTheme.text;
if (customTheme.background) theme.background = '#' + customTheme.background;
if (customTheme.stroke) {
theme.stroke = '#' + customTheme.stroke;
theme.strokeOpacity = 1;
}
if (customTheme.icon) theme.icon = '#' + customTheme.icon;
if (customTheme.chart) theme.chart = '#' + customTheme.chart;
}
const svgString = productiveTimeCard(productiveTimeData, theme, utcOffset);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep the API backward-compatible by making customTheme optional

Previously this export only required (username, themeName, utcOffset). Making customTheme mandatory is now a breaking change for existing callers (including our own routes/tests) and contradicts the downstream if (customTheme !== undefined) guard. Please keep the override optional (and type it as a partial) so consumers without overrides keep working.

 export const getProductiveTimeSVGWithThemeName = async function (
     username: string,
     themeName: string,
-    customTheme: Theme,
+    customTheme?: Partial<Theme>,
     utcOffset: number
 ) {
@@
-const getProductiveTimeSVG = function (
-    productiveTimeData: Array<number>,
-    themeName: string,
-    customTheme: Theme | undefined,
-    utcOffset: number
+const getProductiveTimeSVG = function (
+    productiveTimeData: Array<number>,
+    themeName: string,
+    customTheme?: Partial<Theme>,
+    utcOffset: number
 ): string {
     const theme = {...ThemeMap.get(themeName)!};
-    if (customTheme !== undefined) {
+    if (customTheme) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const getProductiveTimeSVGWithThemeName = async function (
username: string,
themeName: string,
customTheme: Theme,
utcOffset: number
) {
if (!ThemeMap.has(themeName)) throw new Error('Theme does not exist');
const productiveTimeData = await getProductiveTimeData(username, utcOffset);
return getProductiveTimeSVG(productiveTimeData, themeName, utcOffset);
return getProductiveTimeSVG(productiveTimeData, themeName, customTheme, utcOffset);
};
const getProductiveTimeSVG = function (
productiveTimeData: Array<number>,
themeName: string,
customTheme: Theme | undefined,
utcOffset: number
): string {
const svgString = productiveTimeCard(productiveTimeData, ThemeMap.get(themeName)!, utcOffset);
const theme = {...ThemeMap.get(themeName)!};
if (customTheme !== undefined) {
if (customTheme.title) theme.title = '#' + customTheme.title;
if (customTheme.text) theme.text = '#' + customTheme.text;
if (customTheme.background) theme.background = '#' + customTheme.background;
if (customTheme.stroke) {
theme.stroke = '#' + customTheme.stroke;
theme.strokeOpacity = 1;
}
if (customTheme.icon) theme.icon = '#' + customTheme.icon;
if (customTheme.chart) theme.chart = '#' + customTheme.chart;
}
const svgString = productiveTimeCard(productiveTimeData, theme, utcOffset);
export const getProductiveTimeSVGWithThemeName = async function (
username: string,
themeName: string,
customTheme?: Partial<Theme>,
utcOffset: number
) {
if (!ThemeMap.has(themeName)) throw new Error('Theme does not exist');
const productiveTimeData = await getProductiveTimeData(username, utcOffset);
return getProductiveTimeSVG(productiveTimeData, themeName, customTheme, utcOffset);
};
const getProductiveTimeSVG = function (
productiveTimeData: Array<number>,
themeName: string,
customTheme?: Partial<Theme>,
utcOffset: number
): string {
const theme = {...ThemeMap.get(themeName)!};
if (customTheme) {
if (customTheme.title) theme.title = '#' + customTheme.title;
if (customTheme.text) theme.text = '#' + customTheme.text;
if (customTheme.background) theme.background = '#' + customTheme.background;
if (customTheme.stroke) {
theme.stroke = '#' + customTheme.stroke;
theme.strokeOpacity = 1;
}
if (customTheme.icon) theme.icon = '#' + customTheme.icon;
if (customTheme.chart) theme.chart = '#' + customTheme.chart;
}
const svgString = productiveTimeCard(productiveTimeData, theme, utcOffset);
🤖 Prompt for AI Agents
In src/cards/productive-time-card.ts around lines 15 to 44, the top-level export
made customTheme mandatory which breaks backward compatibility; change the
parameter to be optional and a Partial<Theme> so existing callers that omit
overrides still work, and update the internal getProductiveTimeSVG signature to
accept customTheme?: Partial<Theme>; keep the existing undefined checks and
property concatenation logic as-is, and adjust any local typing references to
treat customTheme properties as possibly undefined.

Comment on lines +50 to +60
if (customTheme !== undefined) {
if (customTheme.title) theme.title = '#' + customTheme.title;
if (customTheme.text) theme.text = '#' + customTheme.text;
if (customTheme.background) theme.background = '#' + customTheme.background;
if (customTheme.stroke) {
theme.stroke = '#' + customTheme.stroke;
theme.strokeOpacity = 1;
}
if (customTheme.icon) theme.icon = '#' + customTheme.icon;
if (customTheme.chart) theme.chart = '#' + customTheme.chart;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify color format validation at the API layer.

The code doesn't validate whether custom color values are valid hexadecimal colors. Invalid values will be prepended with '#' and passed through, potentially causing rendering issues or unexpected visual output.

Ensure that the API layer (where customTheme is constructed from query parameters) validates the color format before creating the Theme object.

Run the following script to check if color validation exists in the API layer:


🏁 Script executed:

#!/bin/bash
# Description: Search for color validation logic in API route handlers

# Look for query parameter parsing and Theme construction
rg -n -C 5 'title_color|text_color|bg_color|border_color|icon_color|chart_color'

# Look for hex color validation patterns
rg -n -C 3 '[0-9a-fA-F]{6}|[0-9a-fA-F]{8}|hex.*valid'

Length of output: 294646


API color validation is missing at the route handler layer—review concern is confirmed.

The script output confirms the review comment's observation. The API route handlers in api/cards/ accept color parameters (title_color, text_color, bg_color, border_color, icon_color, chart_color) with only type validation (string check) but no format validation (hexadecimal check).

Key findings:

  1. All five API handlers perform typeof !== 'string' checks but do not validate that these strings are valid hex colors.
  2. No regex patterns matching hexadecimal format ([0-9a-fA-F]{6} or similar) exist in any API route handler.
  3. The code in src/cards/profile-details-card.ts lines 50–60 confirms colors are prepended with '#' unconditionally: theme.title = '#' + customTheme.title (and similar for other colors).
  4. Hex validation patterns exist only in theme definitions (src/const/theme.ts) and tests, not in the request validation layer.

Why this matters:

Invalid color strings will be passed downstream as malformed CSS/SVG attributes (e.g., fill="#invalid_color"), which can cause rendering failures or injection vulnerabilities if not sanitized.

🤖 Prompt for AI Agents
In src/cards/profile-details-card.ts around lines 50-60, the code
unconditionally prepends '#' to incoming color strings and relies on upstream
handlers for validity; update the API route handlers in api/cards/ to validate
and sanitize color parameters using a hex regex (accept 3- or 6-digit hex,
case-insensitive), strip any leading '#', normalize (e.g., to lowercase), and
return a 400 error for invalid colors instead of allowing them through; then
make profile-details-card.ts prepend '#' only to already-validated/sanitized
values (or check and add if missing) to avoid malformed values or double-#.

Comment on lines 15 to +43
export const getReposPerLanguageSVGWithThemeName = async function (
username: string,
themeName: string,
customTheme: Theme,
exclude: Array<string>
) {
if (!ThemeMap.has(themeName)) throw new Error('Theme does not exist');
const langData = await getRepoLanguageData(username, exclude);
return getReposPerLanguageSVG(langData, themeName);
return getReposPerLanguageSVG(langData, themeName, customTheme);
};

const getReposPerLanguageSVG = function (langData: {name: string; value: number; color: string}[], themeName: string) {
const svgString = createDonutChartCard('Top Languages by Repo', langData, ThemeMap.get(themeName)!);
const getReposPerLanguageSVG = function (
langData: {name: string; value: number; color: string}[],
themeName: string,
customTheme: Theme | undefined
) {
const theme = {...ThemeMap.get(themeName)!};
if (customTheme !== undefined) {
if (customTheme.title) theme.title = '#' + customTheme.title;
if (customTheme.text) theme.text = '#' + customTheme.text;
if (customTheme.background) theme.background = '#' + customTheme.background;
if (customTheme.stroke) {
theme.stroke = '#' + customTheme.stroke;
theme.strokeOpacity = 1;
}
if (customTheme.icon) theme.icon = '#' + customTheme.icon;
if (customTheme.chart) theme.chart = '#' + customTheme.chart;
}
const svgString = createDonutChartCard('Top Languages by Repo', langData, theme);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Preserve previous signature by making customTheme optional

This export also used to accept only (username, themeName, exclude). Requiring a full Theme object now breaks any caller that doesn’t supply overrides. Please make the parameter optional (and partial) to retain the prior contract.

 export const getReposPerLanguageSVGWithThemeName = async function (
     username: string,
     themeName: string,
-    customTheme: Theme,
+    customTheme?: Partial<Theme>,
     exclude: Array<string>
 ) {
@@
-const getReposPerLanguageSVG = function (
-    langData: {name: string; value: number; color: string}[],
-    themeName: string,
-    customTheme: Theme | undefined
+const getReposPerLanguageSVG = function (
+    langData: {name: string; value: number; color: string}[],
+    themeName: string,
+    customTheme?: Partial<Theme>
 ) {
     const theme = {...ThemeMap.get(themeName)!};
-    if (customTheme !== undefined) {
+    if (customTheme) {
🤖 Prompt for AI Agents
In src/cards/repos-per-language-card.ts around lines 15 to 43, the exported
function now requires a full Theme which breaks callers that relied on the
previous (username, themeName, exclude) signature; change the customTheme
parameter to be optional and allow partial overrides by using Partial<Theme>
(e.g., customTheme?: Partial<Theme>) for both
getReposPerLanguageSVGWithThemeName and the internal getReposPerLanguageSVG,
leaving the existing property checks as-is so callers can pass nothing or only
the specific theme fields they want to override.

Comment on lines +18 to +42
export const getStatsSVGWithThemeName = async function (username: string, themeName: string, customTheme: Theme) {
if (!ThemeMap.has(themeName)) throw new Error('Theme does not exist');
const statsData = await getStatsData(username);
return getStatsSVG(statsData, themeName);
return getStatsSVG(statsData, themeName, customTheme);
};

const getStatsSVG = function (
StatsData: {index: number; icon: string; name: string; value: string}[],
themeName: string
themeName: string,
customTheme: Theme | undefined
) {
const title = 'Stats';
const svgString = statsCard(`${title}`, StatsData, ThemeMap.get(themeName)!);
const theme = {...ThemeMap.get(themeName)!};
if (customTheme !== undefined) {
if (customTheme.title) theme.title = '#' + customTheme.title;
if (customTheme.text) theme.text = '#' + customTheme.text;
if (customTheme.background) theme.background = '#' + customTheme.background;
if (customTheme.stroke) {
theme.stroke = '#' + customTheme.stroke;
theme.strokeOpacity = 1;
}
if (customTheme.icon) theme.icon = '#' + customTheme.icon;
if (customTheme.chart) theme.chart = '#' + customTheme.chart;
}
const svgString = statsCard(`${title}`, StatsData, theme);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Retain the old API contract; customTheme should be optional

getStatsSVGWithThemeName used to take three arguments. Forcing callers to always pass a full Theme now breaks existing usage and contradicts the downstream undefined guard. Please leave the override optional (and partial).

-export const getStatsSVGWithThemeName = async function (username: string, themeName: string, customTheme: Theme) {
+export const getStatsSVGWithThemeName = async function (
+    username: string,
+    themeName: string,
+    customTheme?: Partial<Theme>
+) {
@@
-const getStatsSVG = function (
-    StatsData: {index: number; icon: string; name: string; value: string}[],
-    themeName: string,
-    customTheme: Theme | undefined
+const getStatsSVG = function (
+    StatsData: {index: number; icon: string; name: string; value: string}[],
+    themeName: string,
+    customTheme?: Partial<Theme>
 ) {
@@
-    const theme = {...ThemeMap.get(themeName)!};
-    if (customTheme !== undefined) {
+    const theme = {...ThemeMap.get(themeName)!};
+    if (customTheme) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const getStatsSVGWithThemeName = async function (username: string, themeName: string, customTheme: Theme) {
if (!ThemeMap.has(themeName)) throw new Error('Theme does not exist');
const statsData = await getStatsData(username);
return getStatsSVG(statsData, themeName);
return getStatsSVG(statsData, themeName, customTheme);
};
const getStatsSVG = function (
StatsData: {index: number; icon: string; name: string; value: string}[],
themeName: string
themeName: string,
customTheme: Theme | undefined
) {
const title = 'Stats';
const svgString = statsCard(`${title}`, StatsData, ThemeMap.get(themeName)!);
const theme = {...ThemeMap.get(themeName)!};
if (customTheme !== undefined) {
if (customTheme.title) theme.title = '#' + customTheme.title;
if (customTheme.text) theme.text = '#' + customTheme.text;
if (customTheme.background) theme.background = '#' + customTheme.background;
if (customTheme.stroke) {
theme.stroke = '#' + customTheme.stroke;
theme.strokeOpacity = 1;
}
if (customTheme.icon) theme.icon = '#' + customTheme.icon;
if (customTheme.chart) theme.chart = '#' + customTheme.chart;
}
const svgString = statsCard(`${title}`, StatsData, theme);
export const getStatsSVGWithThemeName = async function (
username: string,
themeName: string,
customTheme?: Partial<Theme>
) {
if (!ThemeMap.has(themeName)) throw new Error('Theme does not exist');
const statsData = await getStatsData(username);
return getStatsSVG(statsData, themeName, customTheme);
};
const getStatsSVG = function (
StatsData: {index: number; icon: string; name: string; value: string}[],
themeName: string,
customTheme?: Partial<Theme>
) {
const title = 'Stats';
const theme = {...ThemeMap.get(themeName)!};
if (customTheme) {
if (customTheme.title) theme.title = '#' + customTheme.title;
if (customTheme.text) theme.text = '#' + customTheme.text;
if (customTheme.background) theme.background = '#' + customTheme.background;
if (customTheme.stroke) {
theme.stroke = '#' + customTheme.stroke;
theme.strokeOpacity = 1;
}
if (customTheme.icon) theme.icon = '#' + customTheme.icon;
if (customTheme.chart) theme.chart = '#' + customTheme.chart;
}
const svgString = statsCard(`${title}`, StatsData, theme);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants