Skip to content

feat: load addational fonts dynamic#132

Open
igorovh wants to merge 5 commits intomasterfrom
feat/load-addational-fonts-dynamic
Open

feat: load addational fonts dynamic#132
igorovh wants to merge 5 commits intomasterfrom
feat/load-addational-fonts-dynamic

Conversation

@igorovh
Copy link
Member

@igorovh igorovh commented Feb 1, 2026

No description provided.

Copilot AI review requested due to automatic review settings February 1, 2026 23:12
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @igorovh, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces dynamic font loading capabilities, allowing the application to fetch and apply custom fonts based on user settings in both global and current channel contexts. It refines the font loading mechanism to efficiently manage font stylesheets in the DOM and enhances the channel joining API to provide clearer feedback and emit a dedicated event upon successful channel entry.

Highlights

  • Dynamic Font Loading: Fonts are now dynamically loaded based on user preferences rather than a static, hardcoded list, allowing for greater customization.
  • Improved Font Management: The font loading mechanism has been refined to prevent duplicate font link elements in the DOM and efficiently update existing ones when font lists change.
  • Channel Join Event: A new event, 'extension:joined-channel', is now emitted upon successful channel joining, providing a clear hook for other modules to react to this state change.
  • API Enhancement: The joinChannel API method now returns a boolean value, explicitly indicating whether the channel join operation was successful or not.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the additional fonts module to load fonts dynamically based on user settings, which is a great enhancement. The changes are generally good, but I've provided feedback on improving the font collection logic for better performance and readability. I've also pointed out a potential issue with long font lists creating URLs that exceed browser limits and suggested a truncation mechanism to make the feature more robust. This truncation logic is needed in two places, and I've suggested extracting it to a helper method to avoid duplication.


private async run(elements: Element[]) {
this.additionalFontsHelper.loadFonts(elements, AdditionalFontsModule.FONT_LIST);
this.additionalFontsHelper.loadFonts(elements, this.getUsedFonts());
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The list of fonts from getUsedFonts() can potentially be very long, which might create a URL that exceeds the browser's maximum length limit when calling the Google Fonts API. This could cause font loading to fail. It's advisable to truncate the font list to a reasonable maximum size before passing it to loadFonts.

Suggested change
this.additionalFontsHelper.loadFonts(elements, this.getUsedFonts());
let fonts = this.getUsedFonts();
const MAX_FONTS = 50;
if (fonts.length > MAX_FONTS) {
this.logger.warn(`Too many fonts to load (${fonts.length}), truncating to ${MAX_FONTS}`);
fonts = fonts.slice(0, MAX_FONTS);
}
this.additionalFontsHelper.loadFonts(elements, fonts);

public async updateFonts() {
const head = document.querySelector("head");
if (!head) return;
this.additionalFontsHelper.loadFonts([head], this.getUsedFonts());
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Similar to my other comment, the list of fonts should be truncated here as well to avoid creating an overly long URL. To avoid code duplication between this method and run, you could extract this font loading logic into a private helper method.

        let fonts = this.getUsedFonts();
		const MAX_FONTS = 50;
		if (fonts.length > MAX_FONTS) {
			this.logger.warn(`Too many fonts to load (${fonts.length}), truncating to ${MAX_FONTS}`);
			fonts = fonts.slice(0, MAX_FONTS);
		}
		this.additionalFontsHelper.loadFonts([head], fonts);

Comment on lines 32 to 59
getUsedFonts(): string[] {
const globalFonts =
this.enhancerApi()
.getGlobalChannel()
?.users.map((user) => user.customFont) ?? [];
const currentFonts =
this.enhancerApi()
.getCurrentChannel()
?.users.map((user) => user.customFont) ?? [];

const allRawFonts = [...globalFonts, ...currentFonts];

const uniqueFonts: string[] = [];
const seen = new Set<string>();

for (const font of allRawFonts) {
if (!font) continue;

const normalized = font.toLowerCase().trim();

if (!seen.has(normalized)) {
seen.add(normalized);
uniqueFonts.push(font);
}
}

return uniqueFonts;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This method can be simplified and made more efficient. You can combine the user arrays from global and current channels first, and then iterate once to build a map of unique fonts. This avoids multiple iterations and intermediate arrays.

	getUsedFonts(): string[] {
		const allUsers = [
			...(this.enhancerApi().getGlobalChannel()?.users ?? []),
			...(this.enhancerApi().getCurrentChannel()?.users ?? []),
		];

		const uniqueFonts = new Map<string, string>();
		for (const user of allUsers) {
			const font = user.customFont;
			if (!font) {
				continue;
			}

			const normalized = font.toLowerCase().trim();
			if (!uniqueFonts.has(normalized)) {
				uniqueFonts.set(normalized, font);
			}
		}

		return Array.from(uniqueFonts.values());
	}

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request implements dynamic font loading for user custom fonts. However, the title contains a spelling error ("addational" should be "additional"). The PR adds an event system to trigger font reloading when users join a channel, but the implementation is incomplete.

Changes:

  • Added "extension:joined-channel" event type to common events
  • Modified EnhancerApi.joinChannel to return boolean indicating success
  • Updated font loading helper to update existing font links instead of always appending new ones
  • Replaced static font list in Kick's AdditionalFontsModule with dynamic font retrieval from user data

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/types/platforms/common.events.ts Added new event type for channel join notifications
src/shared/module/helpers/additional-fonts.helper.ts Modified to update existing font links and added fontList length check
src/shared/apis/enhancer.api.ts Changed return type from void to boolean to indicate join success
src/platforms/twitch/modules/chat/chat.module.tsx Added event emission when successfully joining a channel
src/platforms/kick/modules/additional-fonts/additional-fonts.module.tsx Replaced static font list with dynamic retrieval and added updateFonts method
Comments suppressed due to low confidence (1)

src/shared/apis/enhancer.api.ts:65

  • The method's return type is Promise, but when an error occurs in the try block, the method throws instead of returning false. This creates an inconsistent API where callers need to handle both the boolean return value and potential exceptions. Consider either always returning false on error (and logging it) or updating the calling code to properly handle the exception case.
		} catch (error) {
			this.logger.error(`Failed to join channel ${channelId}:`, error);
			throw error;
		}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 28 to +30
};

private async run(elements: Element[]) {
this.additionalFontsHelper.loadFonts(elements, AdditionalFontsModule.FONT_LIST);
this.additionalFontsHelper.loadFonts(elements, this.additionalFontsHelper.getUsedFonts());
}
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

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

The updateFonts method is defined but never called. To enable dynamic font loading when a channel is joined, this module should register an event listener for the "extension:joined-channel" event. Consider adding an event applier to the config that calls updateFonts when the event is emitted, similar to how the chat module listens to "twitch:chatInitialized".

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +24

for (const head of elements) {
head.appendChild(fontLink.cloneNode(true));
const existingLink = head.querySelector("link.enhancer-additional-font") as HTMLLinkElement;
if (existingLink) {
existingLink.href = url;
} else {
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

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

When updating an existing link's href, this creates a potential race condition. If the previous font is still loading when the href is changed, it could lead to incomplete font loading or visual inconsistencies. Consider removing the old link and appending a new one instead, or ensure that the previous load is complete before updating the href.

Suggested change
for (const head of elements) {
head.appendChild(fontLink.cloneNode(true));
const existingLink = head.querySelector("link.enhancer-additional-font") as HTMLLinkElement;
if (existingLink) {
existingLink.href = url;
} else {
head.removeChild(existingLink);
}
head.appendChild(newFontLink.cloneNode(true));
}
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +22
newFontLink.setAttribute("onload", "this.media='all'");
newFontLink.classList.add("enhancer-additional-font");

for (const head of elements) {
head.appendChild(fontLink.cloneNode(true));
const existingLink = head.querySelector("link.enhancer-additional-font") as HTMLLinkElement;
if (existingLink) {
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

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

When there are multiple head elements and some have existing font links while others don't, the logic becomes inconsistent. If the first head element has an existing link, it will be updated with the new URL, but then the loop continues and subsequent heads without existing links will append new cloned links. This creates an inconsistent state where some heads have updated links and others have newly appended links. Consider handling all elements uniformly.

Suggested change
newFontLink.setAttribute("onload", "this.media='all'");
newFontLink.classList.add("enhancer-additional-font");
for (const head of elements) {
head.appendChild(fontLink.cloneNode(true));
const existingLink = head.querySelector("link.enhancer-additional-font") as HTMLLinkElement;
if (existingLink) {
const existingLinks = head.querySelectorAll("link.enhancer-additional-font");
existingLinks.forEach((link) => link.remove());
head.appendChild(newFontLink.cloneNode(true));

Copilot uses AI. Check for mistakes.
Comment on lines 1 to +4
export class AdditionalFontsHelper {
constructor(private readonly enhancerApi: EnhancerApi) {}
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

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

While adding the fontList.length check is good defensive programming, when an empty font list is passed, any existing font link elements with the "enhancer-additional-font" class will remain in the DOM. Consider whether this is the intended behavior, or if existing font links should be removed when there are no fonts to load.

Suggested change
export class AdditionalFontsHelper {
constructor(private readonly enhancerApi: EnhancerApi) {}
if (elements.length === 0) return;
if (fontList.length === 0) {
for (const head of elements) {
const existingLinks = head.querySelectorAll("link.enhancer-additional-font");
existingLinks.forEach((link) => {
if (link.parentNode) {
link.parentNode.removeChild(link);
}
});
}
return;
}

Copilot uses AI. Check for mistakes.
Comment on lines +8 to +14

const familyQuery = fontList.map((font) => `family=${font.replace(/\s+/g, "+")}`).join("&");
const url = `https://fonts.googleapis.com/css2?${familyQuery}&display=swap`;
const fontLink = document.createElement("link");
fontLink.rel = "stylesheet";
fontLink.href = url;

const newFontLink = document.createElement("link");
newFontLink.rel = "stylesheet";
newFontLink.href = url;
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

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

The variable is renamed from fontLink to newFontLink, but this name is slightly misleading since the link may not always be "new" - it's only used when creating a new link element, while existing links are updated in-place. Consider a more neutral name like fontLinkTemplate or fontLinkElement.

Copilot uses AI. Check for mistakes.
Comment on lines +42 to +46
const joined = await this.enhancerApi().joinChannel(channelId);
if (joined) {
this.emitter.emit("extension:joined-channel");
this.logger.info(`Joined channel ${channelId}`);
}
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

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

The Kick chat module's initializeChannel method hasn't been updated to handle the new boolean return value from joinChannel and emit the "extension:joined-channel" event. This creates an inconsistency where the event is only emitted on Twitch but not on Kick, preventing the dynamic font loading feature from working properly on Kick. The Kick implementation should be updated to match the Twitch implementation pattern.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@igorovh igorovh requested a review from a team February 4, 2026 19:41
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.

1 participant