Skip to content

Dev#1033

Merged
VampireChicken12 merged 114 commits into
mainfrom
dev
Oct 23, 2025
Merged

Dev#1033
VampireChicken12 merged 114 commits into
mainfrom
dev

Conversation

@VampireChicken12
Copy link
Copy Markdown
Member

@VampireChicken12 VampireChicken12 commented Oct 23, 2025

Summary by CodeRabbit

Release Notes

  • New Features

    • Added automatic closed captions enablement on watch pages
    • Added automatic player maximization toggle
    • Added playlist management buttons to mark videos as unwatched and remove from playlists
    • Added "Save to Watch Later" button for quick access
    • Added screenshot file format configuration option
  • Improvements

    • Replaced YouTube Data API integration with enhanced backend processing for playlist duration calculation
    • Enhanced player styling with frosted glass effects and improved tooltip positioning
    • Updated button placement and layout system
  • Localization

    • Added new UI text for playlist management and watch later features across all supported languages
    • Updated existing translations for consistency
  • Dependencies

    • Updated multiple development and runtime dependencies

dnicolson and others added 30 commits September 2, 2025 20:55
…ve format can't be configured with 'Clipboard' save type
Bumps [semantic-release](https://github.com/semantic-release/semantic-release) from 24.2.7 to 24.2.9.
- [Release notes](https://github.com/semantic-release/semantic-release/releases)
- [Commits](semantic-release/semantic-release@v24.2.7...v24.2.9)

---
updated-dependencies:
- dependency-name: semantic-release
  dependency-version: 24.2.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [@rollup/rollup-linux-x64-gnu](https://github.com/rollup/rollup) from 4.52.0 to 4.52.3.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](rollup/rollup@v4.52.0...v4.52.3)

---
updated-dependencies:
- dependency-name: "@rollup/rollup-linux-x64-gnu"
  dependency-version: 4.52.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [fs-extra](https://github.com/jprichardson/node-fs-extra) from 11.3.0 to 11.3.2.
- [Changelog](https://github.com/jprichardson/node-fs-extra/blob/master/CHANGELOG.md)
- [Commits](jprichardson/node-fs-extra@11.3.0...11.3.2)

---
updated-dependencies:
- dependency-name: fs-extra
  dependency-version: 11.3.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 19.1.13 to 19.1.15.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-version: 19.1.15
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [@eslint/compat](https://github.com/eslint/rewrite/tree/HEAD/packages/compat) from 1.3.1 to 1.4.0.
- [Release notes](https://github.com/eslint/rewrite/releases)
- [Changelog](https://github.com/eslint/rewrite/blob/main/packages/compat/CHANGELOG.md)
- [Commits](https://github.com/eslint/rewrite/commits/compat-v1.4.0/packages/compat)

---
updated-dependencies:
- dependency-name: "@eslint/compat"
  dependency-version: 1.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) from 9.36.0 to 9.37.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/commits/v9.37.0/packages/js)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.37.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [prettier](https://github.com/prettier/prettier) from 3.4.2 to 3.6.2.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](prettier/prettier@3.4.2...3.6.2)

---
updated-dependencies:
- dependency-name: prettier
  dependency-version: 3.6.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [dotenv](https://github.com/motdotla/dotenv) from 17.2.1 to 17.2.3.
- [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md)
- [Commits](motdotla/dotenv@v17.2.1...v17.2.3)

---
updated-dependencies:
- dependency-name: dotenv
  dependency-version: 17.2.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.42.0 to 8.45.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.45.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.45.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [monaco-editor](https://github.com/microsoft/monaco-editor) from 0.52.2 to 0.53.0.
- [Release notes](https://github.com/microsoft/monaco-editor/releases)
- [Changelog](https://github.com/microsoft/monaco-editor/blob/main/CHANGELOG.md)
- [Commits](microsoft/monaco-editor@v0.52.2...v0.53.0)

---
updated-dependencies:
- dependency-name: monaco-editor
  dependency-version: 0.53.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
VampireChicken12 and others added 20 commits October 20, 2025 23:34
Bumps [zod](https://github.com/colinhacks/zod) from 4.1.11 to 4.1.12.
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](colinhacks/zod@v4.1.11...v4.1.12)

---
updated-dependencies:
- dependency-name: zod
  dependency-version: 4.1.12
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Oct 23, 2025

Caution

Review failed

Failed to post review comments

Walkthrough

This PR introduces multiple new YouTube enhancement features (playlist management buttons, save-to-watch-later, automatically enable/maximize), replaces YouTube Data API with Innertube for playlist data, refactors maximize-player functionality, updates localization across 25 languages, upgrades dependencies and GitHub Actions workflows, and adds UI support for disabling conflicting options via enhanced Checkbox component.

Changes

Cohort / File(s) Summary
Configuration & Type Definitions
src/types/index.ts, src/utils/constants.ts, src/pages/content/index.ts
Added four new configuration flags: enable_automatically_enable_closed_captions, enable_automatically_maximize_player, enable_playlist_management_buttons, enable_save_to_watch_later_button. Introduced corresponding extension message types for feature state changes. Removed deprecated YouTubeAPIQuotaError type.
Localization Files
public/locales/*.json (25 languages)
Added error message optionConflict, removed deepDarkCustomTheme option, added screenshotButtonFileFormat option, and introduced new miscellaneous features and playlist/watch-later button translations across all supported locales.
New Feature: Auto-Enable Closed Captions
src/features/automaticallyEnableClosedCaptions/index.ts
New module exporting enableAutomaticallyEnableClosedCaptions and disableAutomaticallyEnableClosedCaptions to manage caption state based on configuration.
New Feature: Auto-Maximize Player
src/features/automaticallyMaximizePlayer/index.ts
New module exporting enableAutomaticallyMaximizePlayer and disableAutomaticallyMaximizePlayer to toggle player maximization based on options.
New Feature: Playlist Management Buttons
src/features/playlistManagementButtons/index.ts, index.css
New module injecting remove and mark-as-unwatched buttons into playlist items using Innertube API with spinner UI and error tooltips.
New Feature: Save to Watch Later Button
src/features/saveToWatchLaterButton/index.ts, index.css
New module adding save-to-watch-later button to video items on home/subscriptions pages using Innertube with dynamic UI feedback.
Refactored: Maximize Player
src/features/maximizePlayerButton/index.ts, utils.ts, index.css
Simplified maximize/minimize workflow; replaced event-driven logic with centralized state; added minimizePlayer() and removed setupVideoPlayerTimeUpdate(), updateProgressBarPositions(); introduced adjustPlayer() helper.
Refactored: Playlist Length
src/features/playlistLength/index.ts, utils.ts
Migrated from YouTube Data API (removed apiKey dependency) to Innertube-based duration retrieval; replaced fetchPlaylistVideos, getPlaylistDuration, getVideoDuration with getDurationFromAPI; added immersive header detection and cache invalidation.
Refactored: Hide End Screen Cards
src/features/hideEndScreenCards/index.ts
Added updateHideEndScreenCardsButtonState() to centralize UI state updates for menu items and feature buttons; added DOM class toggling for .yte-hide-end-screen-cards.
Updated: Hide Shorts
src/features/hideShorts/utils.ts
Changed Shorts section selector from ytd-rich-shelf-renderer[is-shorts] to ytd-rich-section-renderer:has([is-shorts]).
Updated: Screenshot Button
src/features/screenshotButton/index.ts
Dynamic MIME type based on screenshot_format; improved clipboard handling with try/catch and async/await for clipboard write operations.
Updated: Feature Menu
src/features/featureMenu/index.ts
Simplified player container selection to use #movie_player ID; updated icon SVG from 48x36 to 24x24.
UI Components
src/components/Inputs/CheckBox/CheckBox.tsx, src/components/Settings/...
Enhanced Checkbox with disabled and disabledReason props to display disabled state with explanatory text. Updated Settings component to conditionally render features and handle option conflicts.
Button Placement
src/features/buttonPlacement/index.css, utils.ts
Added new CSS classes (.yte-button-player-controls-left, .yte-button-below-player) and simplified inline styles to flex-based layout with conditional class assignment.
Deep Dark Material Styling
src/deepDarkMaterialCSS.ts
Version bump to 4.6.3; added frosted-glass backdrop support, new badge styling, miniplayer/toast styling, immersive header support, and multiple new color token selectors.
Utilities & Helpers
src/utils/utilities.ts, EventManager.ts, global.d.ts
Refactored URL section parsing with extractSectionsFromYouTubeURL(); added isHomePage() and isSubscriptionsPage() guards; removed parseISO8601Duration; added optional trustedTypes to Window interface.
Embedded & Content Pages
src/pages/embedded/index.ts, public/contentStyle.css
Wired new feature enable/disable pairs; updated maximize button logic to use minimizePlayer(); added event handlers for new message types. Removed maximized UI layout blocks from tooltip styling; added frosted-glass backdrop filters.
Build & Workflow
.github/workflows/release.yml, package.json
Upgraded GitHub Actions (checkout v2→v4, setup-node v2→v4); added npm caching step, Git config, concurrency control. Bumped dependencies: React 19, Vite, ESLint, Prettier, Tailwind, TypeScript types, and added youtubei.js.
Metadata & Documentation
.all-contributorsrc, README.md, src/i18n/constants.ts
JSON formatting adjustment in contributors config; contributor entry update; adjusted locale percentage distributions for multiple languages.

Sequence Diagram

sequenceDiagram
    participant User
    participant ContentPage as Content Page<br/>(index.ts)
    participant Feature as Feature Module
    participant Storage as Storage
    participant YouTube as YouTube DOM
    
    autonumber
    User->>Storage: Enable Feature (e.g., playlist_mgmt)
    activate Storage
    Storage-->>ContentPage: Storage Change Event
    deactivate Storage
    
    ContentPage->>ContentPage: Detect enable_playlist_management_buttons
    ContentPage->>ContentPage: Build featuresChange message
    ContentPage->>Feature: Send feature enable request
    
    activate Feature
    Feature->>Feature: Wait for options message
    Feature->>YouTube: Scan video items (yt-lockup-view-model)
    YouTube-->>Feature: Video items found
    
    Feature->>Feature: Initialize Innertube client
    Feature->>Feature: Inject remove/mark-unwatched buttons
    
    User->>YouTube: Click remove button
    YouTube->>Feature: Button click event
    activate Feature
    Feature->>Feature: Show spinner
    Feature->>Feature: Call Innertube removeFromPlaylist()
    Feature-->>YouTube: Remove item from DOM on success
    Feature-->>YouTube: Show error tooltip on failure
    deactivate Feature
    deactivate Feature
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Multiple interrelated feature additions (playlist management, watch-later, auto-captions, auto-maximize), significant refactoring of maximize-player and playlist-length with API migration, 25 localization file updates, enhanced UI component logic for option conflicts, and dependencies upgrade require careful verification of control flow, type safety, Innertube API integration, and consistency across new UI surfaces.

Possibly related PRs

  • Dev #988: Updates to localization files and i18n constants (locale percentage adjustments) alongside fullscreen-scrolling style refinements suggest shared translation/styling work.
  • Add playlist management buttons #951: Directly related—introduces the playlist management buttons feature module including CSS, localization keys, configuration flags, and runtime wiring.
  • Changed the translations too #855: Related through bulk localization updates across multiple public/locales/*.json files with similar key additions and translation patterns.

Poem

🐰 Hops with glee, new buttons appear,
Playlists managed, captions crystal clear,
No more API keys weighing the load,
Innertube flows down a swifter road,
Watch Later beckons, the player stands tall,
This enhanced extension has features for all! 🎬✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.90% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The pull request title "Dev" is extremely vague and generic, failing to convey any meaningful information about the substantial changes in the changeset. While the PR involves merging the dev branch into main and includes major additions (four new features: automatically enable closed captions, automatically maximize player, playlist management buttons, and save to watch later button; extensive localization updates across 22+ locales; GitHub Actions workflow improvements; dependency updates including new Innertube integration; and updates to multiple feature modules and components), the title does not communicate any of this. A teammate scanning the git history would have no idea what this PR introduces or its scope. Consider replacing "Dev" with a descriptive title that captures the main objective, such as "Add playlist management, automatic captions, and watch later features" or "Release v4.6.3: add player enhancements and playlist management". The title should be specific enough that a developer reviewing the git history understands the primary changes without opening the PR details.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

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.

@VampireChicken12 VampireChicken12 merged commit fcf8915 into main Oct 23, 2025
5 of 6 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done - Released in YouTube Enhancer Oct 23, 2025
Copy link
Copy Markdown

@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: 41

Caution

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

⚠️ Outside diff range comments (6)
src/features/automaticallyDisableAmbientMode/index.ts (1)

81-81: Fragile attribute check.

Checking checked === "" is fragile and non-idiomatic. Use hasAttribute('checked') instead for more robust attribute presence detection.

-const ambientModeEnabled = ambientModeItemToggle.getAttribute("checked") === "";
+const ambientModeEnabled = ambientModeItemToggle.hasAttribute("checked");
public/locales/sv-SE.json (1)

2-2: Fix incorrect langCode value.

The langCode is set to "en-US" but this is the Swedish (sv-SE) locale file. This mismatch could cause language detection or routing issues in the extension.

Apply this fix:

-	"langCode": "en-US",
+	"langCode": "sv-SE",
src/utils/constants.ts (1)

241-241: Remove unused hardcoded YouTube Data API key

The constant YouTube_Enhancer_Public_Youtube_Data_API_V3_Key at line 241 is exported but never imported or used anywhere in the codebase. While the active implementation correctly uses a user-configurable setting (src/components/Settings/Settings.tsx, lines 1234–1238), this hardcoded constant should be removed to eliminate unnecessary exposure of sensitive credentials in source code.

public/locales/vi-VN.json (1)

2-3: Locale metadata is incorrect.

langCode and langName should reflect Vietnamese, not en-US.

-  "langCode": "en-US",
-  "langName": "English (US)",
+  "langCode": "vi-VN",
+  "langName": "Tiếng Việt (Việt Nam)",
public/locales/he-IL.json (1)

292-315: All new feature strings are in English instead of Hebrew.

Lines 292-315 contain entirely English text for the new automatic caption, maximize player, playlist management, and watch later features. None of these have been translated to Hebrew.

All labels and titles in this section need Hebrew translations to provide proper localization for Hebrew-speaking users. This includes:

  • automaticallyEnableClosedCaptions (lines 292-295)
  • automaticallyMaximizePlayer (lines 296-299)
  • enablePlaylistManagementButtons (lines 308-311)
  • enableSaveToWatchLaterButton (lines 312-315)
src/features/playlistLength/utils.ts (1)

349-353: URL parsing bug for videoId extraction.

videoIdElement.href is already absolute on YouTube. Prepending https://youtube.com can yield an invalid URL.

- const url = new URL(`https://youtube.com${videoIdElement.href}`);
+ const url = new URL(videoIdElement.href, window.location.origin);
🧹 Nitpick comments (36)
src/deepDarkMaterialCSS.ts (6)

459-463: Prefer background-color over background.

Avoid wiping potential background images/gradients on #frosted-glass.

Apply:

-#frosted-glass.with-chipbar.ytd-app {
-  background: var(--main-background) !important;
-}
+#frosted-glass.with-chipbar.ytd-app {
+  background-color: var(--main-background) !important;
+}

482-491: Poll bar contrast: verify accessibility.

Selected at 0.9 and others at 0.2 over dark surfaces may fall below 3:1. Consider 0.3–0.4 for non-selected or a tokenized alpha.


535-539: Tabs: added modern class variants and slider height.

Looks correct. Optional: extract the slider height to a CSS var for easier tuning.

- .yt-tab-group-shape-wiz__slider,
- .yt-tab-shape__tab-bar--tab-bar-selected {
-   background-color: var(--main-color) !important;
-   height: 2px !important;
- }
+ :root { --dd-tab-indicator-height: 2px; }
+ .yt-tab-group-shape-wiz__slider,
+ .yt-tab-shape__tab-bar--tab-bar-selected {
+   background-color: var(--main-color) !important;
+   height: var(--dd-tab-indicator-height) !important;
+ }

Also applies to: 543-546, 553-557


922-927: Use :first-child for clarity (optional).

Semantically equivalent here; :first-child is a tad clearer than :nth-child(1).

-.yt-content-metadata-view-model__metadata-row:nth-child(1)>.yt-core-attributed-string {
+.yt-content-metadata-view-model__metadata-row:first-child > .yt-core-attributed-string {

971-976: Background shorthand here can be narrowed (optional).

To avoid unintended resets, prefer background-color when only color is set.

-#description:not([class*="yt-music"]):not([class*="ytd-channel-video-player-renderer"]),
+#description:not([class*="yt-music"]):not([class*="ytd-channel-video-player-renderer"]),
 #bio,
 #right-column>yt-formatted-string {
-  background: var(--second-background) !important;
+  background-color: var(--second-background) !important;
   color: var(--dimmer-text) !important;
 }

2456-2460: SVG icon coloring: verify currentColor usage.

Setting color on the SVG assumes icons inherit currentColor. If they use fills, this may no-op; consider targeting the shape element or ensuring currentColor is respected.

src/features/automaticallyDisableAmbientMode/index.ts (1)

51-51: Consider using a structured logging utility.

The console.log statements for error conditions should use a centralized logging utility for consistency and proper log level management (e.g., console.error or a custom logger).

Example:

// Instead of:
return console.log("Menu button not found");

// Consider:
console.error("Menu button not found");
// or use a custom logger if available:
logger.error("Menu button not found");

Also applies to: 64-64, 72-72, 78-78

public/locales/zh-CN.json (3)

138-140: Minor punctuation consistency

Consider ending both messages with a full-width period "。" to match surrounding CN style.


308-315: Playlist/watch‑later toggles: terminology consistency

You use “移除” in failure messages below but “删除” here. Prefer one term (建议统一为“移除”).


503-510: Playlist management strings: LGTM, with tiny nit

“正在移除视频…” vs “删除视频” above—align verbs for UX consistency.

public/locales/fr-FR.json (6)

105-105: Localize newly added error text

Currently English. Propose: "Cette option ne peut pas être activée lorsque « {{OPTION}} » est activée."


138-140: Localize specificOption helper texts

These are English; translate to maintain FR UX consistency.


292-299: Localize new auto features

Both keys are English; please provide FR translations.


308-315: Localize new playlist/save buttons

Add FR translations for labels/titles.


503-510: Localize playlist management actions

These remain English; translate for consistency.


511-514: Localize Watch Later texts

Translate “Failed to add…” and “Save to Watch Later”.

public/locales/es-ES.json (6)

105-105: Localize newly added error text

Currently English. Proposal: "Esta opción no se puede habilitar mientras '{{OPTION}}' esté habilitada."


138-140: Localize specificOption helper texts

Translate to ES for consistency.


292-299: Localize new auto features

Provide ES translations for these two new toggles.


308-315: Localize new playlist/save buttons

Both label/title strings are English; translate.


503-510: Localize playlist management actions

Translate these action/status strings.


511-514: Localize Watch Later texts

Translate error and CTA.

src/features/playlistLength/index.ts (1)

39-47: Throttle resize re-initialization.

Recreating playlist-length UI on every resize can be chatty. Debounce the callback or coalesce via requestAnimationFrame to avoid repeated reflows.

- resizeObserver = new ResizeObserver(async () => {
-   documentObserver = await initPlaylistLength({ pageType, playlistLengthGetMethod, playlistWatchTimeGetMethod });
- });
+ let raf = 0;
+ resizeObserver = new ResizeObserver(() => {
+   cancelAnimationFrame(raf);
+   raf = requestAnimationFrame(async () => {
+     documentObserver = await initPlaylistLength({ pageType, playlistLengthGetMethod, playlistWatchTimeGetMethod });
+   });
+ });
src/features/playlistManagementButtons/index.ts (3)

51-55: Ensure authenticated requests carry credentials.

fetch defaults may omit cookies for cross-origin. Force credentials to include for Innertube.

- const youtube = await Innertube.create({
-   cookie: document.cookie,
-   fetch: (...args) => fetch(...args)
- });
+ const youtube = await Innertube.create({
+   cookie: document.cookie,
+   fetch: (input, init) => fetch(input as RequestInfo, { credentials: "include", ...init })
+ });

83-90: Tooltip id collision risk.

Both buttons use the same tooltip id; showing both could clash. Add uniqueness (e.g., include video id + action).

- id: "yte-feature-playlistManagementButtons-tooltip",
+ id: `yte-feature-playlistManagementButtons-tooltip-${setVideoId}-remove`,

and

- id: "yte-feature-playlistManagementButtons-tooltip",
+ id: `yte-feature-playlistManagementButtons-tooltip-${setVideoId}-reset`,

Also applies to: 114-121


28-38: Detach listeners on disable to prevent leaks.

DOMContentLoaded listener isn’t removed in disablePlaylistManagementButtons. Store the handler and remove it on disable.

-let playlistObserver: MutationObserver | null = null;
+let playlistObserver: MutationObserver | null = null;
+let domReadyListener: ((this: Window, ev: Event) => any) | null = null;
@@
 export async function disablePlaylistManagementButtons() {
   if (playlistObserver) {
     playlistObserver.disconnect();
     playlistObserver = null;
   }
+  if (domReadyListener) {
+    window.removeEventListener("DOMContentLoaded", domReadyListener);
+    domReadyListener = null;
+  }
@@
- window.addEventListener("DOMContentLoaded", observePlaylist);
+ domReadyListener = observePlaylist;
+ window.addEventListener("DOMContentLoaded", domReadyListener);

Also applies to: 149-153

src/features/screenshotButton/index.ts (1)

43-52: Consider using console.error for error logging.

The error handling is well-structured, but line 51 uses console.log(err) instead of console.error(err), which would be more semantically appropriate for error logging.

Apply this diff:

 				} catch (err) {
 					remove();
-					console.log(err);
+					console.error(err);
 				}
src/components/Inputs/CheckBox/CheckBox.tsx (1)

39-43: Consider accessibility for the disabled reason text.

The disabled reason text is displayed but may not be programmatically associated with the checkbox for screen readers. Consider adding aria-describedby to link the checkbox with its disabled reason.

 <input
   checked={checked}
   className={cn("form-checkbox mt-0.5 size-3.5 text-indigo-600 transition duration-150 ease-in-out", { "!cursor-not-allowed": disabled })}
   disabled={disabled}
+  aria-describedby={disabled && disabledReason ? `${id}-reason` : undefined}
   id={id}
   onChange={onChange}
   type="checkbox"
 />
 <div
   className={cn("min-w-0 max-w-[400px]", {
     "ml-2": direction === "ltr",
     "mr-2": direction === "rtl"
   })}
 >
   <label className="block text-sm text-black dark:text-white" htmlFor={id}>
     {label}
   </label>
   {disabled && disabledReason && (
-    <span className="block cursor-default whitespace-normal break-words text-xs leading-tight text-gray-500 dark:text-gray-300">
+    <span id={`${id}-reason`} className="block cursor-default whitespace-normal break-words text-xs leading-tight text-gray-500 dark:text-gray-300">
       {disabledReason}
     </span>
   )}
 </div>
src/features/saveToWatchLaterButton/index.ts (4)

58-66: Replace innerHTML swaps with safer DOM updates.

Even if sources are controlled, repeated innerHTML assignments are unnecessary and flagged. Create SVG nodes and swap via replaceChild/append, or use a Trusted Types policy explicitly.

- saveButton.innerHTML = renderToString(React.createElement(AiOutlineVideoCameraAdd, { color: "white", size: 18 }));
+ const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ icon.setAttribute("width", "18"); icon.setAttribute("height", "18"); icon.setAttribute("viewBox", "0 0 24 24");
+ const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
+ path.setAttribute("fill", "white");
+ path.setAttribute("d", "M21 7v10l-5-3-3 2V8l3 2 5-3z"); // camera-add path (pick the exact d you want)
+ icon.appendChild(path);
+ saveButton.replaceChildren(icon);
...
- const { innerHTML: originalHTML } = saveButton;
+ const originalChild = saveButton.firstChild;
...
- saveButton.innerHTML = renderToString(React.createElement(FaSpinner, { color: "gray", size: 18 }));
+ const spinner = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ spinner.setAttribute("width", "18"); spinner.setAttribute("height", "18"); spinner.setAttribute("viewBox", "0 0 24 24");
+ spinner.classList.add("yte-spinning");
+ saveButton.replaceChildren(spinner);
...
- saveButton.innerHTML = originalHTML;
+ if (originalChild) saveButton.replaceChildren(originalChild);

Alternatively, if you must use HTML strings, create and use a named Trusted Types policy and assign TrustedHTML, not raw strings.

Also applies to: 80-83


27-36: Detach listeners/observers on disable().

enableSaveToWatchLaterButton adds a DOMContentLoaded listener but disableSaveToWatchLaterButton doesn’t remove it. Store the handler and remove it in disable() to avoid leaks when toggling.

+let onDOMContentLoaded: (() => void) | null = null;
...
- window.addEventListener("DOMContentLoaded", observePlaylist);
+ onDOMContentLoaded = observePlaylist;
+ window.addEventListener("DOMContentLoaded", onDOMContentLoaded);
...
 export async function disableSaveToWatchLaterButton() {
   if (videosObserver) {
     videosObserver.disconnect();
     videosObserver = null;
   }
+  if (onDOMContentLoaded) {
+    window.removeEventListener("DOMContentLoaded", onDOMContentLoaded);
+    onDOMContentLoaded = null;
+  }
   document.querySelectorAll(".yte-save-to-watch-later-button").forEach((saveButton) => {
     removeSaveToWatchLaterButton(saveButton as HTMLElement);
   });
 }

Also applies to: 109-113


60-63: Prefer dataset.title for tooltip consistency and add aria-label.

createTooltip reads element.dataset.title. Align by setting both.

- saveButton.title = window.i18nextInstance?.t("settings.sections.saveToWatchLaterButton.save");
+ const label = window.i18nextInstance?.t("settings.sections.saveToWatchLaterButton.save") ?? "Save to Watch Later";
+ saveButton.title = label;
+ saveButton.dataset.title = label;
+ saveButton.setAttribute("aria-label", label);

55-57: Defensive access to rawProps on yt-lockup-view-model.

rawProps is not guaranteed. Add a guard to avoid exceptions on unexpected nodes.

- const { contentId: videoId } = (video as YTLockupViewModel).rawProps.data();
+ const props = (video as Partial<YTLockupViewModel>)?.rawProps?.data?.();
+ const videoId = props?.contentId;
+ if (!videoId) return;
src/features/maximizePlayerButton/utils.ts (2)

26-29: Make theater-mode detection resilient; avoid brittle path-d string.

Matching the exact SVG path d is fragile. Prefer DOM state (e.g., ytd-watch-flexy[theater]) or computed attribute/class.

- const inTheaterMode = document.querySelector<HTMLButtonElement>(`button.ytp-size-button svg path[d='${theaterModeButtonPathD}']`) === null;
+ const inTheaterMode =
+   document.querySelector("ytd-watch-flexy[theater]") !== null ||
+   document.body.hasAttribute("theater"); // fallback if YouTube sets a body attr

Optionally keep the path check as a last resort if both queries fail.


49-51: Avoid hard dependency on i18next presence.

window.i18nextInstance may not be ready; add optional chaining/fallback to prevent crashes.

- window.i18nextInstance.t("pages.content.features.maximizePlayerButton.button.toggle.off")
+ window.i18nextInstance?.t("pages.content.features.maximizePlayerButton.button.toggle.off") ?? "Disable maximize"
src/features/playlistLength/utils.ts (1)

1-4: Remove brittle internal type import from dist/src; use duck typing instead.

youtubei.js does not expose a public PlaylistVideo type, making the import from "youtubei.js/dist/src/parser/classes/PlaylistVideo" fragile across versions.

Replace the import and both casts (lines 312 and 321) with duck typing:

-import type PlaylistVideo from "youtubei.js/dist/src/parser/classes/PlaylistVideo";
-
 import { Innertube } from "youtubei.js/web";

Then in both loops:

-const playlistVideo = video as PlaylistVideo;
-if (playlistVideo?.duration?.seconds) {
-  totalSeconds += playlistVideo.duration.seconds;
+const seconds = (video as any)?.duration?.seconds;
+if (typeof seconds === "number") {
+  totalSeconds += seconds;
src/pages/embedded/index.ts (1)

586-586: Unnecessary await on synchronous function.

The function isEndScreenCardsHidden() returns boolean (not Promise<boolean>), so the await keyword is unnecessary here. While this won't cause an error (the value will simply be immediately resolved), it's cleaner to remove it.

Apply this diff:

-const endScreenCardsHidden = await isEndScreenCardsHidden();
+const endScreenCardsHidden = isEndScreenCardsHidden();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 144fc13 and 52f614d.

⛔ Files ignored due to path filters (4)
  • assets/translation context screenshots/Miscellaneous Settings/Automatically_Enable_Closed_Captions.png is excluded by !**/*.png
  • assets/translation context screenshots/Option Disabled/Deep_Dark_Custom_Theme.png is excluded by !**/*.png
  • assets/translation context screenshots/Option Disabled/Set_Screenshot_Save_Type_To_FILE_To_Configure_This_Option.png is excluded by !**/*.png
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (66)
  • .all-contributorsrc (1 hunks)
  • .github/workflows/release.yml (2 hunks)
  • README.md (1 hunks)
  • package.json (1 hunks)
  • public/contentStyle.css (1 hunks)
  • public/locales/ca-ES.json (5 hunks)
  • public/locales/cs-CZ.json (5 hunks)
  • public/locales/de-DE.json (5 hunks)
  • public/locales/en-GB.json (5 hunks)
  • public/locales/en-US.json (5 hunks)
  • public/locales/en-US.json.d.ts (5 hunks)
  • public/locales/es-ES.json (5 hunks)
  • public/locales/fa-IR.json (5 hunks)
  • public/locales/fr-FR.json (5 hunks)
  • public/locales/he-IL.json (5 hunks)
  • public/locales/hi-IN.json (5 hunks)
  • public/locales/it-IT.json (5 hunks)
  • public/locales/ja-JP.json (5 hunks)
  • public/locales/ko-KR.json (5 hunks)
  • public/locales/nl-NL.json (5 hunks)
  • public/locales/pl-PL.json (14 hunks)
  • public/locales/pt-BR.json (5 hunks)
  • public/locales/ru-RU.json (7 hunks)
  • public/locales/sv-SE.json (5 hunks)
  • public/locales/tr-TR.json (7 hunks)
  • public/locales/uk-UA.json (5 hunks)
  • public/locales/vi-VN.json (5 hunks)
  • public/locales/zh-CN.json (5 hunks)
  • public/locales/zh-TW.json (5 hunks)
  • src/components/Inputs/CheckBox/CheckBox.tsx (1 hunks)
  • src/components/Settings/Settings.tsx (8 hunks)
  • src/components/Settings/components/Setting.tsx (1 hunks)
  • src/deepDarkMaterialCSS.ts (16 hunks)
  • src/features/automaticallyDisableAmbientMode/index.ts (2 hunks)
  • src/features/automaticallyEnableClosedCaptions/index.ts (1 hunks)
  • src/features/automaticallyMaximizePlayer/index.ts (1 hunks)
  • src/features/buttonPlacement/index.css (1 hunks)
  • src/features/buttonPlacement/index.ts (1 hunks)
  • src/features/buttonPlacement/utils.ts (1 hunks)
  • src/features/featureMenu/index.ts (3 hunks)
  • src/features/featureMenu/utils.ts (2 hunks)
  • src/features/hideEndScreenCards/index.ts (3 hunks)
  • src/features/hidePlayables/index.css (1 hunks)
  • src/features/hideShorts/index.css (1 hunks)
  • src/features/hideShorts/utils.ts (1 hunks)
  • src/features/maximizePlayerButton/index.css (1 hunks)
  • src/features/maximizePlayerButton/index.ts (2 hunks)
  • src/features/maximizePlayerButton/utils.ts (1 hunks)
  • src/features/playlistLength/index.ts (1 hunks)
  • src/features/playlistLength/utils.ts (4 hunks)
  • src/features/playlistManagementButtons/index.css (1 hunks)
  • src/features/playlistManagementButtons/index.ts (1 hunks)
  • src/features/restoreFullscreenScrolling/index.css (1 hunks)
  • src/features/saveToWatchLaterButton/index.css (1 hunks)
  • src/features/saveToWatchLaterButton/index.ts (1 hunks)
  • src/features/screenshotButton/index.ts (2 hunks)
  • src/global.d.ts (1 hunks)
  • src/i18n/constants.ts (1 hunks)
  • src/pages/content/index.ts (3 hunks)
  • src/pages/embedded/index.ts (12 hunks)
  • src/pages/options/index.css (1 hunks)
  • src/pages/popup/index.css (1 hunks)
  • src/types/index.ts (4 hunks)
  • src/utils/EventManager.ts (1 hunks)
  • src/utils/constants.ts (4 hunks)
  • src/utils/utilities.ts (7 hunks)
🧰 Additional context used
🧬 Code graph analysis (14)
src/features/automaticallyMaximizePlayer/index.ts (2)
src/features/maximizePlayerButton/utils.ts (2)
  • minimizePlayer (60-66)
  • maximizePlayer (14-58)
src/utils/utilities.ts (1)
  • waitForSpecificMessage (775-802)
src/components/Inputs/CheckBox/CheckBox.tsx (2)
src/components/Settings/Settings.tsx (1)
  • useSettings (1564-1570)
src/utils/utilities.ts (1)
  • cn (178-180)
src/features/saveToWatchLaterButton/index.ts (1)
src/utils/utilities.ts (4)
  • waitForSpecificMessage (775-802)
  • isHomePage (521-524)
  • isSubscriptionsPage (547-550)
  • createTooltip (231-320)
src/features/maximizePlayerButton/index.ts (4)
src/features/buttonPlacement/index.ts (1)
  • addFeatureButton (11-35)
src/features/buttonPlacement/utils.ts (2)
  • getFeatureButton (35-37)
  • updateFeatureButtonTitle (176-180)
src/utils/utilities.ts (1)
  • createTooltip (231-320)
src/features/maximizePlayerButton/utils.ts (2)
  • maximizePlayer (14-58)
  • minimizePlayer (60-66)
src/features/playlistManagementButtons/index.ts (2)
src/utils/utilities.ts (3)
  • waitForSpecificMessage (775-802)
  • isPlaylistPage (539-542)
  • createTooltip (231-320)
src/features/playlistLength/utils.ts (1)
  • getPlaylistId (178-181)
src/components/Settings/Settings.tsx (1)
src/components/Settings/components/Setting.tsx (2)
  • parentSetting (15-27)
  • Setting (42-74)
src/features/hideEndScreenCards/index.ts (5)
src/utils/utilities.ts (1)
  • modifyElementsClassList (560-562)
src/types/index.ts (1)
  • ButtonPlacement (125-125)
src/icons.ts (1)
  • ToggleIcon (5-5)
src/features/featureMenu/utils.ts (1)
  • getFeatureMenuItem (120-123)
src/features/buttonPlacement/utils.ts (3)
  • getFeatureButton (35-37)
  • updateFeatureButtonIcon (171-175)
  • updateFeatureButtonTitle (176-180)
src/features/screenshotButton/index.ts (1)
src/types/index.ts (1)
  • Nullable (29-29)
src/pages/content/index.ts (1)
src/utils/utilities.ts (1)
  • sendExtensionOnlyMessage (686-700)
src/features/maximizePlayerButton/utils.ts (5)
src/utils/EventManager.ts (1)
  • eventManager (63-167)
src/features/buttonPlacement/utils.ts (4)
  • getFeatureButton (35-37)
  • updateFeatureButtonIcon (171-175)
  • modifyIconForLightTheme (119-134)
  • updateFeatureButtonTitle (176-180)
src/icons.ts (1)
  • getFeatureIcon (359-361)
src/utils/utilities.ts (2)
  • ModifyElementAction (135-135)
  • modifyElementsClassList (560-562)
src/types/index.ts (1)
  • YouTubePlayerDiv (533-533)
src/features/featureMenu/index.ts (1)
src/utils/utilities.ts (2)
  • isWatchPage (551-554)
  • createSVGElement (211-229)
src/features/automaticallyEnableClosedCaptions/index.ts (2)
src/utils/utilities.ts (4)
  • isWatchPage (551-554)
  • isLivePage (525-528)
  • waitForElement (749-766)
  • waitForSpecificMessage (775-802)
src/types/index.ts (1)
  • YouTubePlayerDiv (533-533)
src/features/playlistLength/utils.ts (1)
src/types/index.ts (1)
  • playlistLengthGetMethod (128-128)
src/pages/embedded/index.ts (7)
src/features/saveToWatchLaterButton/index.ts (2)
  • enableSaveToWatchLaterButton (38-113)
  • disableSaveToWatchLaterButton (27-36)
src/features/automaticallyEnableClosedCaptions/index.ts (2)
  • enableAutomaticallyEnableClosedCaptions (16-34)
  • disableAutomaticallyEnableClosedCaptions (5-15)
src/features/playlistManagementButtons/index.ts (2)
  • enablePlaylistManagementButtons (40-153)
  • disablePlaylistManagementButtons (28-38)
src/features/automaticallyMaximizePlayer/index.ts (2)
  • enableAutomaticallyMaximizePlayer (7-15)
  • disableAutomaticallyMaximizePlayer (4-6)
src/features/hideEndScreenCards/index.ts (4)
  • addHideEndScreenCardsButton (29-72)
  • isEndScreenCardsHidden (78-81)
  • enableHideEndScreenCards (18-28)
  • updateHideEndScreenCardsButtonState (112-127)
src/icons.ts (1)
  • getFeatureIcon (359-361)
src/features/maximizePlayerButton/utils.ts (1)
  • minimizePlayer (60-66)
🪛 ast-grep (0.39.6)
src/features/saveToWatchLaterButton/index.ts

[warning] 58-58: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: saveButton.innerHTML = renderToString(React.createElement(AiOutlineVideoCameraAdd, { color: "white", size: 18 }))
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 64-64: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: saveButton.innerHTML = renderToString(React.createElement(FaSpinner, { color: "gray", size: 18 }))
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 80-80: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: saveButton.innerHTML = originalHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 58-58: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: saveButton.innerHTML = renderToString(React.createElement(AiOutlineVideoCameraAdd, { color: "white", size: 18 }))
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 64-64: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: saveButton.innerHTML = renderToString(React.createElement(FaSpinner, { color: "gray", size: 18 }))
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 80-80: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: saveButton.innerHTML = originalHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)

src/features/playlistManagementButtons/index.ts

[warning] 68-68: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: removeButton.innerHTML = renderToString(React.createElement(FaTrash, { color: "red", size: 18 }))
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 75-75: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: removeButton.innerHTML = renderToString(React.createElement(FaSpinner, { color: "gray", size: 18 }))
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 91-91: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: removeButton.innerHTML = originalHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 98-98: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: resetButton.innerHTML = renderToString(React.createElement(FaUndoAlt, { color: "red", size: 18 }))
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 105-105: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: resetButton.innerHTML = renderToString(React.createElement(FaSpinner, { color: "gray", size: 18 }))
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 122-122: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: resetButton.innerHTML = originalHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 68-68: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: removeButton.innerHTML = renderToString(React.createElement(FaTrash, { color: "red", size: 18 }))
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 75-75: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: removeButton.innerHTML = renderToString(React.createElement(FaSpinner, { color: "gray", size: 18 }))
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 91-91: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: removeButton.innerHTML = originalHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 98-98: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: resetButton.innerHTML = renderToString(React.createElement(FaUndoAlt, { color: "red", size: 18 }))
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 105-105: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: resetButton.innerHTML = renderToString(React.createElement(FaSpinner, { color: "gray", size: 18 }))
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 122-122: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: resetButton.innerHTML = originalHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)

🪛 Biome (2.1.2)
public/contentStyle.css

[error] 19-19: Unexpected shorthand property font after line-height

(lint/suspicious/noShorthandPropertyOverrides)

Comment thread public/contentStyle.css
Comment on lines +17 to 20
.ytp-big-mode .yte-button-tooltip {
line-height: 22px !important;
padding: 8px 9px !important;
transform: translate(-50%, -75%) !important;
font: 20px !important;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t use font shorthand after line-height; it resets line-height.

font: 20px !important; overrides and resets earlier line-height. Replace with font-size only.

 .ytp-big-mode .yte-button-tooltip {
-  line-height: 22px !important;
-  font: 20px !important;
+  line-height: 22px !important;
+  font-size: 20px !important;
 }

Based on static analysis hints.

📝 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
.ytp-big-mode .yte-button-tooltip {
line-height: 22px !important;
padding: 8px 9px !important;
transform: translate(-50%, -75%) !important;
font: 20px !important;
}
.ytp-big-mode .yte-button-tooltip {
line-height: 22px !important;
font-size: 20px !important;
}
🧰 Tools
🪛 Biome (2.1.2)

[error] 19-19: Unexpected shorthand property font after line-height

(lint/suspicious/noShorthandPropertyOverrides)

🤖 Prompt for AI Agents
In public/contentStyle.css around lines 17 to 20, the rule uses the font
shorthand which resets the previously set line-height; replace "font: 20px
!important;" with an explicit "font-size: 20px !important;" (remove the
shorthand) so the line-height remains intact and the size is applied.

Comment thread public/locales/de-DE.json
"options": {
"notifications": {
"error": {
"optionConflict": "This option cannot be enabled while '{{OPTION}}' is enabled.",
Copy link
Copy Markdown

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 German translations for new strings.

Several new localization entries appear to be in English rather than German:

  • Line 105: "This option cannot be enabled while '{{OPTION}}' is enabled."
  • Lines 292-315: Feature labels and titles
  • Lines 503-514: Playlist management and watch later button strings

Ensure these are properly translated to German before release.

Also applies to: 138-139, 292-315, 503-514


🌐 Web query:

Are the following strings correctly translated to German in the de-DE locale file, or are they English placeholders that need translation?

💡 Result:

I can check — please paste the strings (or the de-DE locale file).

If you want to self-check first, quick checklist and methods:

  • If entries are in a PO file: untranslated entries usually have an empty msgstr ("msgstr """). See GNU gettext docs. [1]
  • Compare de-DE to the source (en) file: identical text usually means untranslated; use diff/merge tools (git diff, diff, or a translation tool). [2][3]
  • Look for common placeholder markers: "TODO", "TBD", "EN:", "///", developer comments, or English phrases left in translations — those indicate placeholders.
  • For JSON/JS locales: search for strings that are plain English or equal to the en locale value (grep/diff).
  • Use localization tooling (Transifex/Smartling/gettext helpers) to list untranslated strings or export only untranslated entries. [2][4]

If you paste the strings (or a file snippet), I’ll mark which are already German and which appear to be English placeholders.

Sources: [1] GNU gettext manual — Untranslated entries. [2] Transifex Gettext guide (download untranslated). [3] gettext_ops example (list untranslated). [4] Smartling troubleshooting untranslated content.


I need to examine the locale files to verify which strings are untranslated. Let me retrieve the de-DE.json file and compare it with the English source.


🏁 Script executed:

#!/bin/bash
# Find the locale files
echo "=== Locating locale files ==="
fd -e json -path "*/locales/*" | head -20

echo -e "\n=== Checking de-DE.json content at specified lines ==="
# Extract lines 105, 138-139, 292-315, 503-514 from de-DE.json
if [ -f "public/locales/de-DE.json" ]; then
  echo "Lines 105:"
  sed -n '105p' public/locales/de-DE.json
  echo -e "\nLines 138-139:"
  sed -n '138,139p' public/locales/de-DE.json
  echo -e "\nLines 292-315:"
  sed -n '292,315p' public/locales/de-DE.json
  echo -e "\nLines 503-514:"
  sed -n '503,514p' public/locales/de-DE.json
fi

Length of output: 2144


🏁 Script executed:

#!/bin/bash
# Check if English locale file exists and compare
echo "=== Looking for English locale file ==="
ls -la public/locales/ 2>/dev/null || echo "Directory not found"

echo -e "\n=== Extract full de-DE.json for context ==="
if [ -f "public/locales/de-DE.json" ]; then
  head -150 public/locales/de-DE.json | tail -50
fi

Length of output: 3535


🏁 Script executed:

#!/bin/bash
# Read both files to compare translations
echo "=== Comparing en and de-DE locales at line 105 ==="
if [ -f "public/locales/de-DE.json" ] && [ -f "public/locales/en-US.json" ]; then
  echo "English (line 105):"
  sed -n '105p' public/locales/en-US.json
  echo "German (line 105):"
  sed -n '105p' public/locales/de-DE.json
fi

echo -e "\n=== Full comparison of line 105 across all locale files ==="
for file in public/locales/*.json; do
  echo "File: $(basename "$file")"
  sed -n '105p' "$file" 2>/dev/null | head -1
done

Length of output: 2686


Let me do a targeted comparison of the feature sections to confirm the translation status:


🏁 Script executed:

#!/bin/bash
# Use jq to parse and compare translations more accurately
echo "=== Checking key sections in en-US vs de-DE ==="

# Check a sample key from lines 292-315 range
echo "Sample: 'automaticallyEnableClosedCaptions' label"
echo "English:"
jq '.settings.sections.automaticQuality.enable // .settings.sections.automaticFeatures // empty' public/locales/en-US.json 2>/dev/null | head -5 || echo "Structure may differ"

echo "German:"
jq '.settings.sections.automaticQuality.enable // .settings.sections.automaticFeatures // empty' public/locales/de-DE.json 2>/dev/null | head -5 || echo "Structure may differ"

# Compare optionDisabled section
echo -e "\n=== optionDisabled section (lines 138-139 area) ==="
echo "English:"
jq '.settings.optionDisabled' public/locales/en-US.json 2>/dev/null
echo "German:"
jq '.settings.optionDisabled' public/locales/de-DE.json 2>/dev/null

Length of output: 1456


🏁 Script executed:

#!/bin/bash
# Direct line-by-line comparison for the problem areas
echo "=== Detailed line comparison for reported sections ==="

echo "Lines 138-139 comparison:"
echo "en-US:"
sed -n '138,139p' public/locales/en-US.json
echo "de-DE:"
sed -n '138,139p' public/locales/de-DE.json

echo -e "\n=== Checking if 138-139 are identical (indicator of untranslated) ==="
if diff <(sed -n '138,139p' public/locales/en-US.json) <(sed -n '138,139p' public/locales/de-DE.json) > /dev/null; then
  echo "Lines are IDENTICAL - untranslated (English text in German file)"
else
  echo "Lines DIFFER - translation exists"
fi

echo -e "\n=== Checking if 292-315 sections have translations ==="
echo "Sampling line 295:"
echo "en-US: $(sed -n '295p' public/locales/en-US.json)"
echo "de-DE: $(sed -n '295p' public/locales/de-DE.json)"

if diff <(sed -n '295p' public/locales/en-US.json) <(sed -n '295p' public/locales/de-DE.json) > /dev/null; then
  echo "→ IDENTICAL (untranslated)"
else
  echo "→ DIFFERENT (translation may exist)"
fi

Length of output: 930


Let me verify the exact structure and translation status for the feature sections:


🏁 Script executed:

#!/bin/bash
# Parse JSON to check specific keys mentioned in review comment
echo "=== Checking feature sections for translations ==="

# Check automaticallyEnableClosedCaptions
echo "automaticallyEnableClosedCaptions:"
echo "English:"
jq '.settings.sections.automaticallyEnableClosedCaptions' public/locales/en-US.json 2>/dev/null
echo "German:"
jq '.settings.sections.automaticallyEnableClosedCaptions' public/locales/de-DE.json 2>/dev/null

echo -e "\n=== enablePlaylistManagementButtons ==="
echo "English:"
jq '.settings.sections.enablePlaylistManagementButtons' public/locales/en-US.json 2>/dev/null
echo "German:"
jq '.settings.sections.enablePlaylistManagementButtons' public/locales/de-DE.json 2>/dev/null

Length of output: 180


🏁 Script executed:

#!/bin/bash
# Check the watch later section
echo "=== Checking saveToWatchLaterButton ==="
echo "English:"
jq '.notifications.playlistManagementButtons, .notifications.saveToWatchLaterButton' public/locales/en-US.json 2>/dev/null

echo -e "\nGerman:"
jq '.notifications.playlistManagementButtons, .notifications.saveToWatchLaterButton' public/locales/de-DE.json 2>/dev/null

# Also check if these exist at different paths
echo -e "\n=== Alternative path search ==="
jq 'keys' public/locales/de-DE.json 2>/dev/null | head -20

Length of output: 180


Let me search for the exact location of these feature strings in the JSON structure:


🏁 Script executed:

#!/bin/bash
# Search for the feature keys in the entire JSON
echo "=== Searching for automaticallyEnableClosedCaptions in de-DE.json ==="
jq '.. | objects | select(has("automaticallyEnableClosedCaptions")) | keys' public/locales/de-DE.json 2>/dev/null | head -20

echo -e "\n=== Searching for the full path ==="
jq 'paths(select(type == "object" and has("automaticallyEnableClosedCaptions")))' public/locales/de-DE.json 2>/dev/null

echo -e "\n=== Get content where this key exists ==="
jq 'to_entries | map(select(.value | tostring | contains("automaticallyEnableClosedCaptions"))) | .[0]' public/locales/de-DE.json 2>/dev/null

Length of output: 21545


🏁 Script executed:

#!/bin/bash
# Simpler approach - search the JSON structure
echo "=== Full structure search in de-DE ==="
jq '.settings | keys' public/locales/de-DE.json 2>/dev/null

echo -e "\n=== All settings keys to understand structure ==="
jq '.settings | to_entries | map(.key)' public/locales/de-DE.json 2>/dev/null

Length of output: 247


🏁 Script executed:

#!/bin/bash
# Use grep to find the line context around the feature sections
echo "=== Grep for automaticallyEnableClosedCaptions context ==="
grep -n "automaticallyEnableClosedCaptions" public/locales/de-DE.json

echo -e "\n=== Grep for playlistManagementButtons context ==="
grep -n "playlistManagementButtons" public/locales/de-DE.json

Length of output: 196


Translate untranslated strings in German locale file.

The German locale file contains multiple untranslated English strings that must be converted to German before release:

  • Line 105: optionConflict error message
  • Lines 138-139: featureMenu and screenshotButtonFileFormat configuration messages
  • Lines 292-315: Feature labels and titles (automaticallyEnableClosedCaptions, automaticallyMaximizePlayer, copyTimestampUrlButton, defaultToOriginalAudioTrack, enablePlaylistManagementButtons, enableSaveToWatchLaterButton)
  • Lines 503-514: Playlist management button strings (failedToMarkAsUnwatched, failedToRemoveVideo, markAsUnwatched, removeVideo) and Watch Later button strings

Comment thread public/locales/he-IL.json
"options": {
"notifications": {
"error": {
"optionConflict": "This option cannot be enabled while '{{OPTION}}' is enabled.",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing Hebrew translation for error message.

Line 105 contains English text instead of Hebrew: "This option cannot be enabled while '{{OPTION}}' is enabled."

This error message should be translated to Hebrew to maintain consistency with the locale file's purpose.

🤖 Prompt for AI Agents
public/locales/he-IL.json around line 105: the value for "optionConflict" is in
English; replace it with a Hebrew translation — e.g. use: "אפשרות זו לא ניתנת
להפעלה כאשר '{{OPTION}}' מופעלת." — ensure you preserve the '{{OPTION}}'
interpolation token and maintain JSON string escaping and formatting.

Comment thread public/locales/he-IL.json
Comment on lines +503 to +514
"playlistManagementButtons": {
"failedToMarkAsUnwatched": "Failed to mark video as unwatched",
"failedToRemoveVideo": "Failed to remove video",
"markAsUnwatched": "Mark as unwatched",
"markingAsUnwatched": "Marking as unwatched…",
"removeVideo": "Remove video",
"removingVideo": "Removing video…"
},
"saveToWatchLaterButton": {
"error": "Failed to add video to Watch Later",
"save": "Save to Watch Later"
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing Hebrew translations for playlist management and watch later features.

Lines 503-514 contain all English strings for the new playlist management and watch later button features. These need to be translated to Hebrew.

Required translations include:

  • playlistManagementButtons section (lines 503-510): All 6 strings
  • saveToWatchLaterButton section (lines 511-514): Both strings
🤖 Prompt for AI Agents
In public/locales/he-IL.json around lines 503 to 514, replace the English
strings for playlistManagementButtons and saveToWatchLaterButton with Hebrew
translations; set playlistManagementButtons.failedToMarkAsUnwatched to "נכשל
בסימון הווידאו כלא נצפה", failedToRemoveVideo to "נכשל בהסרת הווידאו",
markAsUnwatched to "סמן כלא נצפה", markingAsUnwatched to "מסמן כלא נצפה…",
removeVideo to "הסר את הווידאו", removingVideo to "מסיר את הווידאו…", and set
saveToWatchLaterButton.error to "נכשל בהוספת הווידאו ל'צפה מאוחר יותר'" and save
to "שמור ל'צפה מאוחר יותר'".

Comment thread public/locales/it-IT.json
Comment on lines +308 to +315
"enablePlaylistManagementButtons": {
"label": "Abilita i pulsanti di gestione della lista riproduzioni",
"title": "Adds buttons to playlist items to remove videos from the playlist or mark them as unwatched"
},
"enableSaveToWatchLaterButton": {
"label": "Enable save to Watch Later button",
"title": "Adds a button to Home and Subscriptions videos to save the video to the 'Watch Later' playlist"
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Incomplete Italian translations for new features.

Lines 310 and 314 contain English text in the title fields instead of Italian translations:

  • Line 310: "Adds buttons to playlist items..." (should be Italian)
  • Line 314: "Adds a button to Home and Subscriptions..." (should be Italian)

These should be translated to Italian to maintain consistency with the rest of the locale file and provide proper user experience for Italian-speaking users.

🤖 Prompt for AI Agents
In public/locales/it-IT.json around lines 308 to 315, the "title" values for
enablePlaylistManagementButtons and enableSaveToWatchLaterButton are still in
English; replace them with Italian translations: for
enablePlaylistManagementButtons set the title to "Aggiunge pulsanti agli
elementi della playlist per rimuovere video dalla playlist o segnare i video
come non visti" and for enableSaveToWatchLaterButton set the title to "Aggiunge
un pulsante nelle pagine Home e Iscrizioni per salvare il video nella playlist
'Guarda più tardi'"; ensure proper JSON string escaping and keep
quotation/commas consistent with surrounding entries.

Comment on lines +1 to +12
@keyframes yte-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

.yte-spinning {
animation: yte-spin 2s linear infinite;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Extract duplicated animation to shared CSS.

The yte-spin animation (lines 1-8) and .yte-spinning class (lines 10-12) are identical to the definitions in src/features/playlistManagementButtons/index.css (lines 18-29). This duplication creates a maintenance burden if the animation needs updates.

Consider extracting the shared animation to a common CSS file (e.g., src/styles/animations.css or src/features/shared.css) and importing it in both feature modules:

/* src/styles/animations.css */
@keyframes yte-spin {
	from {
		transform: rotate(0deg);
	}
	to {
		transform: rotate(360deg);
	}
}

.yte-spinning {
	animation: yte-spin 2s linear infinite;
}

Then import in both files:

import "@/src/styles/animations.css";
🤖 Prompt for AI Agents
In src/features/saveToWatchLaterButton/index.css lines 1-12 (and the duplicate
in src/features/playlistManagementButtons/index.css lines ~18-29), the yte-spin
keyframes and .yte-spinning class are duplicated; extract these rules into a new
shared stylesheet (e.g., src/styles/animations.css or src/features/shared.css)
containing the @keyframes yte-spin and .yte-spinning rule, remove the duplicated
rules from both feature CSS files, and update the corresponding feature entry
points (TypeScript/JS files that currently import those CSS files) to import the
new shared stylesheet (import "@/src/styles/animations.css") so both features
use the single shared animation definition.

Comment on lines +66 to +69
saveButton.classList.add("yte-spinning");
try {
saveButton.classList.add("yte-spinning");
await youtube.playlist.addVideos("WL", [videoId]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Duplicate classList.add("yte-spinning").

Redundant add at Lines 66 and 68. Remove one.

- saveButton.classList.add("yte-spinning");
  try {
-   saveButton.classList.add("yte-spinning");
+   // already spinning
📝 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
saveButton.classList.add("yte-spinning");
try {
saveButton.classList.add("yte-spinning");
await youtube.playlist.addVideos("WL", [videoId]);
saveButton.classList.add("yte-spinning");
try {
await youtube.playlist.addVideos("WL", [videoId]);
🤖 Prompt for AI Agents
In src/features/saveToWatchLaterButton/index.ts around lines 66 to 69, there is
a duplicate call to saveButton.classList.add("yte-spinning") (both before and
inside the try block); remove one of the two so the class is only added once
(keep the single call in the most appropriate place — e.g., before the try or
inside it — and delete the other duplicate) to eliminate redundancy.

Comment on lines +89 to +93
heading.style.paddingRight = "40px";
const buttonClasses = Array.from(buttons.children[0].classList).join(" ");
saveButton.className += " " + buttonClasses;
buttons.prepend(saveButton);
Array.from(buttons.children).forEach((child) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard on buttons.children[0] before copying classes.

buttons.children[0] can be undefined and cause a runtime error.

- const buttonClasses = Array.from(buttons.children[0].classList).join(" ");
- saveButton.className += " " + buttonClasses;
+ const firstChild = buttons.children[0] as HTMLElement | undefined;
+ if (firstChild) {
+   const buttonClasses = Array.from(firstChild.classList).join(" ");
+   saveButton.className += " " + buttonClasses;
+ }
📝 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
heading.style.paddingRight = "40px";
const buttonClasses = Array.from(buttons.children[0].classList).join(" ");
saveButton.className += " " + buttonClasses;
buttons.prepend(saveButton);
Array.from(buttons.children).forEach((child) => {
heading.style.paddingRight = "40px";
const firstChild = buttons.children[0] as HTMLElement | undefined;
if (firstChild) {
const buttonClasses = Array.from(firstChild.classList).join(" ");
saveButton.className += " " + buttonClasses;
}
buttons.prepend(saveButton);
Array.from(buttons.children).forEach((child) => {
🤖 Prompt for AI Agents
In src/features/saveToWatchLaterButton/index.ts around lines 89 to 93, the code
assumes buttons.children[0] exists when copying classes which can throw if it's
undefined; guard that access by checking that buttons.children[0] (or
buttons.firstElementChild) is truthy before reading its classList, and only
concatenate/join and assign classes when it exists (otherwise skip copying or
use a safe default string), then proceed to prepend saveButton and iterate
children as before.

Comment on lines +115 to +119
function removeSaveToWatchLaterButton(button: HTMLElement) {
const heading = button.closest("yt-lockup-view-model")!.querySelector("h3") as HTMLElement;
heading.style.paddingRight = "0";
button.style.display = "none";
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Avoid non-null assertion; guard and fully remove the button.

button.closest("yt-lockup-view-model") or its h3 can be null; the current non-null assertion can throw. Also prefer removing the node (and restoring original padding) over display: none to prevent leaks.

-function removeSaveToWatchLaterButton(button: HTMLElement) {
-  const heading = button.closest("yt-lockup-view-model")!.querySelector("h3") as HTMLElement;
-  heading.style.paddingRight = "0";
-  button.style.display = "none";
-}
+function removeSaveToWatchLaterButton(button: HTMLElement) {
+  const host = button.closest("yt-lockup-view-model");
+  const heading = host?.querySelector("h3") as HTMLElement | null;
+  if (heading) {
+    const original = heading.dataset.yteOriginalPaddingRight ?? "0";
+    heading.style.paddingRight = original;
+    delete heading.dataset.yteOriginalPaddingRight;
+  }
+  // Detach listeners implicitly and free the node
+  button.remove();
+}

And when adding the button, persist original padding before overriding:

- if (heading && buttons) {
-   heading.style.paddingRight = "40px";
+ if (heading && buttons) {
+   if (!heading.dataset.yteOriginalPaddingRight) {
+     heading.dataset.yteOriginalPaddingRight = getComputedStyle(heading).paddingRight || "0";
+   }
+   heading.style.paddingRight = "40px";

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/features/saveToWatchLaterButton/index.ts around lines 115 to 119, avoid
the non-null assertion and fully remove the button: first guard that
button.closest("yt-lockup-view-model") and its h3 exist before touching them,
restore the original heading padding (which must have been persisted when the
button was added—store original padding on the button or parent via a data-*
attribute at add time), then remove the button node from the DOM (e.g.,
button.remove()) instead of setting display:none; if required, clean up any
dataset keys you used to persist the padding.

Comment thread src/global.d.ts
Comment on lines +111 to +114
trustedTypes?: {
createPolicy(name: string, options: { createHTML: (input: string) => string });
defaultPolicy;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Incomplete Trusted Types API definition.

The type definition for trustedTypes is missing critical information:

  1. createPolicy has no return type (should return TrustedTypePolicy)
  2. defaultPolicy has no type annotation (should be TrustedTypePolicy | null)
  3. The options parameter is incomplete (can also include createScript and createScriptURL)

Consider using the @types/trusted-types package or referencing the standard TypeScript DOM types instead of manually defining this API, which would provide complete and accurate type definitions.

If you prefer to keep the manual definition, apply this diff for a more complete type:

-		trustedTypes?: {
-			createPolicy(name: string, options: { createHTML: (input: string) => string });
-			defaultPolicy;
-		};
+		trustedTypes?: {
+			createPolicy(
+				name: string,
+				options: {
+					createHTML?: (input: string) => string;
+					createScript?: (input: string) => string;
+					createScriptURL?: (input: string) => string;
+				}
+			): TrustedTypePolicy;
+			defaultPolicy: TrustedTypePolicy | null;
+		};

You'll also need to define the TrustedTypePolicy interface or import it from @types/trusted-types.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/global.d.ts around lines 111 to 114, the trustedTypes definition is
incomplete: update createPolicy to return TrustedTypePolicy, type defaultPolicy
as TrustedTypePolicy | null, and expand the options parameter to include
createScript and createScriptURL (in addition to createHTML); either import
TrustedTypePolicy from @types/trusted-types or add a local TrustedTypePolicy
interface and use it for the return type and defaultPolicy to ensure full,
accurate typings (or alternatively remove the manual block and depend on the
standard TypeScript DOM/@types/trusted-types definitions).

@github-actions
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 1.29.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

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

Labels

Projects

Status: Done - Released

Development

Successfully merging this pull request may close these issues.

3 participants