Skip to content

Ported Major Components to uhtml#298

Merged
n-ce merged 6 commits into
mainfrom
uhtml
May 19, 2025
Merged

Ported Major Components to uhtml#298
n-ce merged 6 commits into
mainfrom
uhtml

Conversation

@n-ce
Copy link
Copy Markdown
Owner

@n-ce n-ce commented May 18, 2025

Summary by CodeRabbit

  • New Features

    • Added explicit section headings with translation keys for better semantic structure and accessibility.
    • Introduced new components for rendering media items and lists using the uhtml library.
  • Refactor

    • Replaced Solid.js components with lightweight uhtml-based components for improved rendering performance and maintainability.
    • Enhanced image loading and error handling in media item components.
    • Simplified app initialization and update prompt dialog behavior.
    • Improved pagination and search result rendering with refined token management.
    • Streamlined collection data fetching and rendering with direct DOM updates using uhtml.
  • Style

    • Updated font stack, removing Noto Sans for a modernized look.
    • Adjusted layout spacing, padding, and margins for consistency.
    • Enhanced heading styles in sections and settings for clearer visual hierarchy.
    • Modified suggestions dropdown positioning for better UI alignment.
  • Bug Fixes

    • Fixed lyric line highlighting to ensure only one active line is highlighted at any time.
  • Chores

    • Simplified project documentation by removing detailed build logs and specific technology mentions.
    • Added and enabled new CSS utility dependencies for styling improvements.
    • Cleaned up and reorganized CSS files, removing unused styles and improving selector scoping.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2025

Walkthrough

This update migrates UI components from Solid.js to the uhtml templating library, refactoring rendering logic and component structure throughout the codebase. Several new functional components are introduced in TypeScript, while their previous .tsx Solid.js counterparts are removed. CSS styles are adjusted for improved layout and font handling. The build configuration is updated to integrate open-props and postcss-jit-props, and documentation is revised to reflect these changes.

Changes

File(s) Change Summary
README.md, netlify/edge-functions/landing.ts Updated project description by removing specific quantitative claims and detailed build logs; simplified acknowledgements; revised landing page alert message about cache clearing for update.
package.json, vite.config.ts Added open-props and postcss-jit-props dependencies; enabled postcss-jit-props plugin in Vite CSS configuration.
src/components/ItemsLoader.ts, src/components/ListItem.ts, src/components/StreamItem.ts Added new uhtml-based functional components for rendering collections, streams, and list items with event handling and conditional image loading.
src/components/ItemsLoader.tsx, src/components/ListItem.tsx, src/components/StreamItem.tsx Removed Solid.js components previously responsible for rendering items, replaced by new uhtml components.
src/components/SuperCollectionList.ts Refactored from Solid.js reactive component to imperative async rendering using uhtml; introduced exported async main() and loadData() functions for explicit data fetching and UI updates.
src/components/Lyrics.ts, src/components/Settings.tsx Improved lyric line active class management by clearing all active classes before setting new; added an <h1> heading in settings component for semantic structure.
src/components/ActionsMenu.css, src/components/Settings.css, src/stylesheets/global.css, src/stylesheets/layout.css, src/stylesheets/library.css, src/stylesheets/search.css Adjusted CSS styles including font stack changes, heading margins and widths, removal of unused font-face and select styles, and refined margins/paddings for UI elements like suggestions and selectors.
src/lib/libraryUtils.ts, src/lib/utils.ts Refactored rendering utilities to use uhtml templating; removed Solid.js-specific rendering functions; improved pagination by rendering new batches into separate document fragments to avoid reuse issues.
src/main.ts Simplified dynamic imports by invoking default exports immediately; removed explicit .tsx extension from import; adjusted update prompt dialog handling by appending after initialization and removing direct id/open properties.
src/modules/fetchList.ts, src/modules/fetchSearchResults.ts, src/scripts/queue.ts Replaced Solid.js rendering calls with uhtml render and new ItemsLoader component; improved pagination token tracking and incremental loading; updated search results rendering to use uhtml and component-based approach.
index.html Added explicit <h1> headers with translation keys inside the "upcoming," "search," and "library" sections for semantic structure.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UIComponent (uhtml)
    participant DataModule
    participant DOM

    User->>UIComponent (uhtml): Triggers action (e.g., view list)
    UIComponent (uhtml)->>DataModule: Requests data (items, streams, collections)
    DataModule-->>UIComponent (uhtml): Returns data array
    UIComponent (uhtml)->>DOM: Renders items using uhtml templates
    User->>UIComponent (uhtml): Scrolls or interacts (pagination/search)
    UIComponent (uhtml)->>DataModule: Fetches more data if needed
    DataModule-->>UIComponent (uhtml): Returns next batch
    UIComponent (uhtml)->>DOM: Updates DOM with new items
Loading

Poem

Hopping from Solid to uhtml’s light,
The code now renders swift and bright.
With props and styles, we leap anew,
Font stacks fresh and margins too.
The rabbit’s joy in every line—
Components simple, UI divine!
🐇✨

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.


Note

⚡️ Faster reviews with caching

CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.
Enjoy the performance boost—your workflow just got faster.

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown
Contributor

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

🧹 Nitpick comments (14)
src/stylesheets/layout.css (1)

12-13: Remove duplicate font name in font stack.

The font-family declaration contains a duplicate entry for "Arial" which should be removed.

-  font-family: ui-rounded, Hiragino Maru Gothic ProN, Quicksand, Comfortaa, Manjari, Arial Rounded MT, Arial Rounded MT Bold, Calibri, source-sans-pro, sans-serif;
+  font-family: ui-rounded, Hiragino Maru Gothic ProN, Quicksand, Comfortaa, Manjari, Arial Rounded MT Bold, Calibri, source-sans-pro, sans-serif;
🧰 Tools
🪛 Biome (1.9.4)

[error] 12-12: Duplicate font names are redundant and unnecessary: Arial

Remove duplicate font names within the property

(lint/suspicious/noDuplicateFontNames)

src/components/ListItem.ts (3)

15-15: Initialize the reference variable properly.

The non-null assertion (!) tells TypeScript to ignore potential nullability, but it's better to initialize properly.

-let img!: HTMLImageElement;
+let img: HTMLImageElement | null = null;

Then update the unravel function to handle the null case:

function unravel() {
-  img.parentElement!.classList.remove('ravel');
+  img?.parentElement?.classList.remove('ravel');
}
function handleError() {
  unravel();
-  img.src = '/logo192.png';
+  if (img) img.src = '/logo192.png';
}

7-13: Add JSDoc documentation for the component.

Adding proper documentation helps other developers understand the component's purpose and expected inputs.

+/**
+ * ListItem component that renders a list item with image (if enabled), title, uploader data, and stats.
+ * @param data An object containing item details
+ * @param data.title The title of the item
+ * @param data.stats Statistics about the item (e.g., subscriber count)
+ * @param data.thumbnail URL to the item's thumbnail image
+ * @param data.uploader_data Information about the uploader
+ * @param data.url The URL to navigate to when clicked
+ * @returns A uhtml template for a list item
+ */
export default function(data: {
  title: string,
  stats: string,
  thumbnail: string,
  uploader_data: string,
  url: string,
}) {

17-23: Use safer property access with optional chaining.

To make the code more robust against null or undefined values, use optional chaining.

function unravel() {
-  img.parentElement!.classList.remove('ravel');
+  img?.parentElement?.classList.remove('ravel');
}
function handleError() {
  unravel();
-  img.src = '/logo192.png';
+  if (img) img.src = '/logo192.png';
}
src/modules/fetchList.ts (2)

74-75: Use a more descriptive variable name for token tracking.

The variable name tokens doesn't clearly indicate its purpose for preventing duplicate fetch operations.

-const tokens = [group.nextpage];
+const seenPageTokens = [group.nextpage];

This makes it clearer that the array is tracking tokens we've already seen/processed.


80-85: Improve token handling with comments and better naming.

The token handling logic would benefit from comments explaining the purpose and improved variable naming.

const token = await callback();
observer.disconnect();
-if (!token || tokens.indexOf(token) !== -1) return;
-tokens.push(token);
+// Stop observing if no token returned or if we've already processed this token
+if (!token || seenPageTokens.includes(token)) return;
+// Track this token to prevent duplicate fetches
+seenPageTokens.push(token);
setObserver(callback);
src/components/ItemsLoader.ts (3)

9-14: Define the reservedCollections type for better type safety.

Using a TypeScript interface or type would improve type checking and provide better editor support.

+type CollectionKey = 'discover' | 'history' | 'favorites' | 'listenLater';
+type CollectionValue = [string, string]; // [icon class, i18n key]

-const reservedCollections = {
+const reservedCollections: Record<CollectionKey, CollectionValue> = {
  discover: ['ri-compass-3-line', 'library_discover'],
  history: ['ri-memories-line', 'library_history'],
  favorites: ['ri-heart-fill', 'library_favorites'],
  listenLater: ['ri-calendar-schedule-line', 'library_listen_later']
};

58-59: Improve type handling for non-array inputs.

The current type casting is potentially unsafe and could cause runtime errors.

-      itemsArray as unknown as string}`;
+      String(itemsArray)}`;

Using String() explicitly converts the value to a string, which is safer and more intentional than type casting.


31-42: Extract StreamItem property mapping to improve readability.

The property mapping for StreamItem is complex and could be simplified for better readability.

(item.type === 'stream' || item.type === 'video') ?
-  StreamItem({
-    id: item.videoId || item.url.substring(9),
-    href: hostResolver(item.url || ('/watch?v=' + item.videoId)),
-    title: item.title,
-    author: (item.uploaderName || item.author) + (location.search.endsWith('music_songs') ? ' - Topic' : ''),
-    duration: (item.duration || item.lengthSeconds) > 0 ? convertSStoHHMMSS(item.duration || item.lengthSeconds) : 'LIVE',
-    uploaded: item.uploadedDate || item.publishedText,
-    channelUrl: item.uploaderUrl || item.authorUrl,
-    views: item.viewCountText || (item.views > 0 ? numFormatter(item.views) + ' views' : ''),
-    img: getThumbIdFromLink(item.thumbnail || 'https://i.ytimg.com/vi_webp/' + item.videoId + '/mqdefault.webp?host=i.ytimg.com'),
-  }) :
+  (() => {
+    const id = item.videoId || item.url.substring(9);
+    const isMusic = location.search.endsWith('music_songs');
+    
+    return StreamItem({
+      id,
+      href: hostResolver(item.url || ('/watch?v=' + item.videoId)),
+      title: item.title,
+      author: (item.uploaderName || item.author) + (isMusic ? ' - Topic' : ''),
+      duration: (item.duration || item.lengthSeconds) > 0 ? 
+                convertSStoHHMMSS(item.duration || item.lengthSeconds) : 'LIVE',
+      uploaded: item.uploadedDate || item.publishedText,
+      channelUrl: item.uploaderUrl || item.authorUrl,
+      views: item.viewCountText || (item.views > 0 ? numFormatter(item.views) + ' views' : ''),
+      img: getThumbIdFromLink(item.thumbnail || 
+           `https://i.ytimg.com/vi_webp/${id}/mqdefault.webp?host=i.ytimg.com`),
+    });
+  })() :

Using an immediately-invoked function expression (IIFE) allows you to create temporary variables that improve readability.

src/modules/fetchSearchResults.ts (2)

52-55: Extract filtering logic into a named function.

The filtering logic is embedded directly in the render call, making it harder to understand at a glance.

+const filterShortAndLowViewVideos = (items: StreamItem[]) => 
+  items.filter(item => (item.lengthSeconds > 62) && (item.viewCount > 1000));

render(searchlist, ItemsLoader(
-  items.filter(
-    (item: StreamItem) => (item.lengthSeconds > 62) && (item.viewCount > 1000)
-  )));
+  filterShortAndLowViewVideos(items)
+));

83-86: Add loading indicator for better user experience.

There's currently no visual feedback when search results are loading.

+// Show loading indicator
+searchlist.innerHTML = '<div class="loading">Loading results...</div>';

// filter out shorts
render(searchlist, ItemsLoader(
  items?.filter((item: StreamItem) => !item.isShort)
));

You'll need to add appropriate styling for the loading class in your CSS.

src/components/StreamItem.ts (1)

30-33: Avoid automatic anchor.click() side-effects

Calling anchor.click() inside an image-load handler can unexpectedly trigger navigation while the user is interacting (e.g. scrolling). Prefer signalling the deletion intent explicitly:

-      anchor.classList.add('delete');
-      anchor.click();
+      anchor.classList.add('delete');
+      anchor.dispatchEvent(new CustomEvent('streamitem:delete', { bubbles: true }));

Down-stream code can listen for streamitem:delete and decide whether to navigate or just remove the element.

src/components/SuperCollectionList.ts (2)

18-22: Type the dbchange custom event

Adding an explicit type improves tooling support and prevents accidental property mismatches.

-addEventListener('dbchange', (e) => {
+addEventListener('dbchange', (e: CustomEvent<{ db: Library; change: boolean }>) => {

143-157: Handle fetch/network errors for featured playlists

loadFeaturedPls() does not catch network failures, which would surface as unhandled promise rejections. Consider a graceful fallback:

 const loadFeaturedPls = () => fetch('https://raw.githubusercontent.com/wiki/n-ce/ytify/ytm_pls.md')
   .then(res => res.text())
   .then(text => text.split('\n'))
   .then(data => {
     ...
     return array;
-  });
+  })
+  .catch(() => 'Unable to load featured playlists right now');
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between d5f6e4f and 82d6227.

⛔ Files ignored due to path filters (2)
  • public/logo192.png is excluded by !**/*.png
  • public/logo512.png is excluded by !**/*.png
📒 Files selected for processing (26)
  • README.md (2 hunks)
  • index.html (3 hunks)
  • netlify/edge-functions/landing.ts (1 hunks)
  • package.json (1 hunks)
  • src/components/ActionsMenu.css (2 hunks)
  • src/components/ItemsLoader.ts (1 hunks)
  • src/components/ItemsLoader.tsx (0 hunks)
  • src/components/ListItem.ts (1 hunks)
  • src/components/ListItem.tsx (0 hunks)
  • src/components/Lyrics.ts (1 hunks)
  • src/components/Settings.css (2 hunks)
  • src/components/Settings.tsx (1 hunks)
  • src/components/StreamItem.ts (1 hunks)
  • src/components/StreamItem.tsx (0 hunks)
  • src/components/SuperCollectionList.ts (1 hunks)
  • src/lib/libraryUtils.ts (2 hunks)
  • src/lib/utils.ts (2 hunks)
  • src/main.ts (1 hunks)
  • src/modules/fetchList.ts (3 hunks)
  • src/modules/fetchSearchResults.ts (3 hunks)
  • src/scripts/queue.ts (2 hunks)
  • src/stylesheets/global.css (2 hunks)
  • src/stylesheets/layout.css (4 hunks)
  • src/stylesheets/library.css (1 hunks)
  • src/stylesheets/search.css (1 hunks)
  • vite.config.ts (1 hunks)
💤 Files with no reviewable changes (3)
  • src/components/StreamItem.tsx
  • src/components/ListItem.tsx
  • src/components/ItemsLoader.tsx
🧰 Additional context used
🧬 Code Graph Analysis (6)
src/components/Settings.tsx (1)
src/lib/utils.ts (1)
  • i18n (39-44)
src/lib/libraryUtils.ts (2)
src/lib/utils.ts (1)
  • renderDataIntoFragment (147-168)
src/lib/dom.ts (1)
  • listContainer (27-27)
src/modules/fetchList.ts (2)
src/lib/dom.ts (2)
  • listContainer (27-27)
  • listSection (25-25)
src/lib/utils.ts (1)
  • goTo (17-17)
src/components/ListItem.ts (2)
src/lib/store.ts (1)
  • store (5-100)
src/lib/utils.ts (1)
  • hostResolver (33-37)
src/components/ItemsLoader.ts (3)
src/lib/libraryUtils.ts (1)
  • reservedCollections (5-5)
src/lib/utils.ts (3)
  • i18n (39-44)
  • hostResolver (33-37)
  • convertSStoHHMMSS (79-92)
src/lib/imageUtils.ts (2)
  • getThumbIdFromLink (21-38)
  • generateImageUrl (11-17)
src/modules/fetchSearchResults.ts (1)
src/lib/dom.ts (1)
  • searchlist (15-15)
🪛 Biome (1.9.4)
src/stylesheets/layout.css

[error] 12-12: Duplicate font names are redundant and unnecessary: Arial

Remove duplicate font names within the property

(lint/suspicious/noDuplicateFontNames)

🔇 Additional comments (26)
package.json (1)

11-12: Dependencies added appropriately

The addition of open-props and postcss-jit-props is well-aligned with the PR objective of porting major components to uhtml. These packages provide CSS utility and standardization that will help maintain consistent design tokens across the refactored components.

src/components/ActionsMenu.css (2)

36-36: LGTM: UI spacing adjustment

Reducing the gap from 5dvmin to 3dvmin provides tighter spacing between items in the actions menu, which should improve the visual cohesion after the component refactoring.


59-59: LGTM: Minor formatting fix

The whitespace adjustment at the end of the rule is a harmless formatting improvement.

vite.config.ts (1)

98-98: Configuration activated correctly

Uncommenting the PostCSS plugin activation properly enables the OpenProps CSS custom properties with JIT generation. This change correctly complements the new dependencies added in package.json.

src/components/Settings.tsx (1)

66-66: LGTM: Semantic heading enhancement

Adding a properly localized <h1> element improves accessibility and creates consistent semantic structure across the UI, aligning with similar changes made elsewhere in the application.

The i18n function correctly displays the localized string from the translation key 'nav_settings', making the UI fully translatable.

src/stylesheets/library.css (1)

44-47: Padding and margin adjustments look good.

The changes to the padding and margin of #superCollectionSelector align well with the migration to uhtml templating. The horizontal padding increase (from 2vmin to 3vmin) and vertical padding removal provide better spacing for the refactored component.

src/stylesheets/search.css (1)

47-53: Approved margin adjustment for suggestion box.

The increased fixed-size component (from 1.5rem to 5rem) with slightly reduced viewport-relative size (from 8.8vmin to 6.8vmin) creates better vertical positioning for the suggestions dropdown, likely accounting for the new heading elements added to sections.

src/stylesheets/layout.css (1)

36-37: Good CSS selector specificity improvement.

Changing from general descendant selectors to direct child selectors (>header) improves specificity and prevents potential styling conflicts with nested headers. This change is aligned with good CSS practices.

Also applies to: 61-62

index.html (1)

154-155: Excellent accessibility improvement with semantic headers.

Adding <h1> headers with translation attributes to each main section improves the document structure and accessibility. This is a valuable improvement that makes the UI more semantic and screen-reader friendly.

Also applies to: 183-184, 212-213

src/scripts/queue.ts (2)

4-5: Good migration from Solid.js to uhtml.

The import changes correctly update the framework dependencies for the project migration.


46-54: Rendering mechanism properly migrated to uhtml.

The rendering logic has been successfully migrated from Solid.js JSX-style components to uhtml templating while maintaining the same component structure and data flow. The function parameters and overall behavior remain consistent with the previous implementation.

src/main.ts (2)

17-17: Code simplification looks good

The direct invocation of the default export from the start module simplifies the code while maintaining the same functionality.


19-19: Import simplification aligns with uhtml migration

The direct invocation pattern is consistent with the broader refactoring from Solid.js to uhtml. The file extension is now properly omitted in the import path.

src/components/Lyrics.ts (1)

46-46: Improved active lyric highlighting

This change ensures only one line is highlighted at a time by properly clearing all elements with the 'active' class before setting the new active line. The previous approach might have left multiple elements highlighted simultaneously.

src/stylesheets/global.css (2)

248-252: Good addition of section header styling

Adding consistent styling for section headings improves the UI hierarchy and readability across the application. The width, margin, and color settings create a uniform look.


254-256: Queue tools padding adjustment

Setting padding-top to 0 for #queuetools fixes spacing issues that might have caused inconsistent layout. This small adjustment can significantly improve visual alignment.

netlify/edge-functions/landing.ts (1)

15-15: Simplified landing page content

The landing page description has been streamlined to focus on the core functionality of ytify without references to domain changes or migration instructions. This makes the messaging clearer and more timeless.

README.md (1)

99-99: Good documentation update!

The Acknowledgements section correctly reflects the architectural change from SolidJS to uhtml as the HTML templating library. This keeps the documentation in sync with the implementation changes in the codebase.

src/components/Settings.css (2)

7-10: LGTM: Improved h1 styling for settings container

Adding specific styling for h1 elements inside the settings container ensures consistent header presentation.


145-148: LGTM: Improved CSS structure with nesting

Converting the placeholder styling to use nested SCSS-style syntax improves code organization and maintainability.

src/lib/libraryUtils.ts (1)

170-177: Improved fragment handling for pagination

Creating a new fragment for each batch of items ensures proper isolation of DOM nodes during pagination. This approach is more reliable than reusing the original fragment and aligns well with the uhtml rendering paradigm.

src/lib/utils.ts (2)

5-5: LGTM: Switched to uhtml templating library

Correctly updated imports to use the uhtml library instead of Solid.js, which aligns with the PR objective.


153-167: Successfully refactored rendering to use uhtml

The renderDataIntoFragment function has been well-adapted to use uhtml's templating approach. The code now leverages uhtml's tagged template literals and efficient rendering while maintaining the same functionality.

Key improvements:

  • Uses a single render call instead of multiple calls
  • Properly filters and maps data entries
  • Maintains the same component interface
src/modules/fetchList.ts (2)

61-65: LGTM! Well-structured filtering function.

The filterOutMembersOnly function is well-defined, with clear logic for filtering out members-only streams based on the view count.


66-68: Well implemented component-based rendering.

The transition from imperative DOM manipulation to component-based rendering with uhtml is a good improvement for maintainability.

src/modules/fetchSearchResults.ts (1)

91-96: Well implemented fragment-based incremental loading.

Using DocumentFragment for appending additional search results is an efficient approach that minimizes DOM reflows.

Comment thread src/modules/fetchList.ts
Comment on lines 91 to 95
setObserver(async () => {
const data = await fetch(
api + '/nextpage/' +
url.substring(1) + '?nextpage=' + encodeURIComponent(token)
url.substring(1) + '?nextpage=' + encodeURIComponent(tokens[tokens.length - 1])
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling for the fetch operation.

The current implementation only logs errors to the console without proper handling or user feedback.

const data = await fetch(
  api + '/nextpage/' +
  url.substring(1) + '?nextpage=' + encodeURIComponent(tokens[tokens.length - 1])
)
  .then(res => res.json())
-  .catch(e => console.log(e));
+  .catch(e => {
+    console.error('Error fetching next page:', e);
+    notify(i18n('fetchlist_nextpage_error', e.message));
+    return null;
+  });

You'll need to add a new translation key 'fetchlist_nextpage_error' to your i18n system.

📝 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
setObserver(async () => {
const data = await fetch(
api + '/nextpage/' +
url.substring(1) + '?nextpage=' + encodeURIComponent(token)
url.substring(1) + '?nextpage=' + encodeURIComponent(tokens[tokens.length - 1])
)
setObserver(async () => {
const data = await fetch(
api + '/nextpage/' +
url.substring(1) + '?nextpage=' + encodeURIComponent(tokens[tokens.length - 1])
)
.then(res => res.json())
.catch(e => {
console.error('Error fetching next page:', e);
notify(i18n('fetchlist_nextpage_error', e.message));
return null;
});
// …use data below…
});
🤖 Prompt for AI Agents
In src/modules/fetchList.ts around lines 91 to 95, the fetch operation error
handling only logs errors to the console without proper handling or user
feedback. Modify the code to catch fetch errors, handle them gracefully by
updating the UI or state to inform the user, and use the new i18n translation
key 'fetchlist_nextpage_error' to display a localized error message. Ensure the
error handling does not disrupt the app flow and provides clear feedback.

Comment on lines +18 to +23
const collectionTemplate = (name: string) => html`
<a href=${'./list?collection=' + name} class='clxn_item'>${(name in reservedCollections) ?
html`<i class=${reservedCollections[name as 'history'][0]}></i>${i18n(reservedCollections[name as 'history'][1] as 'library_history')}` :
html`<i class='ri-play-list-2-fill'></i>${name}`
}</a>
`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve type safety in collectionTemplate.

The current type assertion name as 'history' could lead to runtime errors if the name isn't a valid key.

const collectionTemplate = (name: string) => html`
  <a href=${'./list?collection=' + name} class='clxn_item'>${(name in reservedCollections) ?
-    html`<i class=${reservedCollections[name as 'history'][0]}></i>${i18n(reservedCollections[name as 'history'][1] as 'library_history')}` :
+    html`<i class=${reservedCollections[name as keyof typeof reservedCollections][0]}></i>
+         ${i18n(reservedCollections[name as keyof typeof reservedCollections][1])}` :
    html`<i class='ri-play-list-2-fill'></i>${name}`
  }</a>
`;

This ensures that TypeScript understands we're using a valid key from the reservedCollections object.

📝 Committable suggestion

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

Suggested change
const collectionTemplate = (name: string) => html`
<a href=${'./list?collection=' + name} class='clxn_item'>${(name in reservedCollections) ?
html`<i class=${reservedCollections[name as 'history'][0]}></i>${i18n(reservedCollections[name as 'history'][1] as 'library_history')}` :
html`<i class='ri-play-list-2-fill'></i>${name}`
}</a>
`;
const collectionTemplate = (name: string) => html`
<a href=${'./list?collection=' + name} class='clxn_item'>${(name in reservedCollections) ?
html`<i class=${reservedCollections[name as keyof typeof reservedCollections][0]}></i>
${i18n(reservedCollections[name as keyof typeof reservedCollections][1])}` :
html`<i class='ri-play-list-2-fill'></i>${name}`
}</a>
`;
🤖 Prompt for AI Agents
In src/components/ItemsLoader.ts around lines 18 to 23, the type assertion 'name
as "history"' is unsafe because 'name' may not be a valid key of
reservedCollections, risking runtime errors. To fix this, refine the type of
'name' to be a key of reservedCollections when accessing its properties, or use
a type guard to ensure 'name' is a valid key before accessing
reservedCollections[name]. This will improve type safety and prevent invalid key
access.

Comment on lines +54 to +56
class=${'streamItem ' + (store.loadImage ? 'ravel' : '')}
href=${data.href}
ref=${(_: HTMLAnchorElement) => { anchor = _ }}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Guard against "undefined" navigation target

href=${data.href} will literally emit href="undefined" when data.href is undefined, causing the browser to navigate to /undefined on click. Provide a fallback or conditionally omit the attribute.

-      href=${data.href}
+      href=${data.href ?? '#'}

If navigation should be disabled entirely when href is absent, consider omitting the attribute instead of using "#".

📝 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
class=${'streamItem ' + (store.loadImage ? 'ravel' : '')}
href=${data.href}
ref=${(_: HTMLAnchorElement) => { anchor = _ }}
class=${'streamItem ' + (store.loadImage ? 'ravel' : '')}
- href=${data.href}
+ href=${data.href ?? '#'}
ref=${(_: HTMLAnchorElement) => { anchor = _ }}
🤖 Prompt for AI Agents
In src/components/StreamItem.ts around lines 54 to 56, the href attribute is set
directly to data.href, which can result in href="undefined" if data.href is
undefined, causing unwanted navigation. To fix this, conditionally render the
href attribute only if data.href is defined; otherwise, omit the href attribute
entirely to prevent navigation. This ensures no invalid href value is emitted
and disables navigation when href is absent.

Comment on lines +68 to +73
html`<img
crossorigin='anonymous'
@error=${handleThumbnailError}
@load=${handleThumbnailLoad}
src=${imgsrc}
/>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add missing accessibility & performance attributes to <img>

The thumbnail lacks an alt description and lazy–loading hint. This hurts screen-reader usability and wastes bandwidth on images outside the viewport.

-      html`<img
-            crossorigin='anonymous'
+      html`<img
+            crossorigin='anonymous'
+            loading="lazy"
+            alt=${data.title}
📝 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
html`<img
crossorigin='anonymous'
@error=${handleThumbnailError}
@load=${handleThumbnailLoad}
src=${imgsrc}
/>
html`<img
crossorigin='anonymous'
loading="lazy"
alt=${data.title}
@error=${handleThumbnailError}
@load=${handleThumbnailLoad}
src=${imgsrc}
/>`
🤖 Prompt for AI Agents
In src/components/StreamItem.ts around lines 68 to 73, the <img> tag is missing
an alt attribute for accessibility and a loading attribute for performance
optimization. Add a meaningful alt attribute describing the image content to
improve screen-reader usability, and add loading="lazy" to defer loading images
outside the viewport, reducing bandwidth usage.

Comment on lines +40 to +43
const db = getDB();
const data = await loadData(name, db);
const template = ItemsLoader(data as StreamItem[]);
render(superCollectionList, template);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Unsafe cast to StreamItem[] breaks type-safety

loadData() may return a string (e.g. “No favorites in library”) or other collection shapes. Forcing it to StreamItem[] masks type errors and risks run-time failures inside ItemsLoader.

-  const template = ItemsLoader(data as StreamItem[]);
+  const template = ItemsLoader(data);

Update ItemsLoader’s type signature if necessary.

📝 Committable suggestion

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

Suggested change
const db = getDB();
const data = await loadData(name, db);
const template = ItemsLoader(data as StreamItem[]);
render(superCollectionList, template);
const db = getDB();
const data = await loadData(name, db);
const template = ItemsLoader(data);
render(superCollectionList, template);
🤖 Prompt for AI Agents
In src/components/SuperCollectionList.ts around lines 40 to 43, the code
unsafely casts the result of loadData() to StreamItem[], which breaks type
safety because loadData() can return a string or other types. To fix this,
update the ItemsLoader function's type signature to accept the actual possible
return types of loadData(), and add proper type checks or handling before
passing data to ItemsLoader to avoid runtime errors.

Copy link
Copy Markdown
Contributor

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

♻️ Duplicate comments (1)
src/components/ItemsLoader.ts (1)

18-23: Improve type safety in collectionTemplate.

The current type assertion name as 'history' could lead to runtime errors if the name isn't a valid key.

const collectionTemplate = (name: string) => html`
  <a href=${'./list?collection=' + name} class='clxn_item'>${(name in reservedCollections) ?
-    html`<i class=${reservedCollections[name as 'history'][0]}></i>${i18n(reservedCollections[name as 'history'][1] as 'library_history')}` :
+    html`<i class=${reservedCollections[name as keyof typeof reservedCollections][0]}></i>
+         ${i18n(reservedCollections[name as keyof typeof reservedCollections][1])}` :
    html`<i class='ri-play-list-2-fill'></i>${name}`
  }</a>
`;

This ensures that TypeScript understands we're using a valid key from the reservedCollections object.

🧹 Nitpick comments (4)
src/components/ItemsLoader.ts (4)

24-52: Consider refactoring the complex conditional rendering logic.

The template function uses deeply nested conditional expressions, making it harder to read and maintain. Consider refactoring to use early returns or separate helper functions for each item type.

- const template = (item: StreamItem) =>
-   item.type === 'collection' ?
-     collectionTemplate(item.name) :
-     (item.type === 'stream' || item.type === 'video') ?
-       StreamItem({
-         // props...
-       }) :
-       ListItem({
-         // props...
-       })
-   ;

+ const template = (item: StreamItem) => {
+   if (item.type === 'collection') {
+     return collectionTemplate(item.name);
+   }
+   
+   if (item.type === 'stream' || item.type === 'video') {
+     return StreamItem({
+       // props...
+     });
+   }
+   
+   return ListItem({
+     // props...
+   });
+ };

32-32: Handle potential undefined values in author concatenation.

The concatenation of strings in the author field may lead to "undefined - Topic" if item.uploaderName and item.author are both undefined.

- author: (item.uploaderName || item.author) + (location.search.endsWith('music_songs') ? ' - Topic' : ''),
+ author: ((item.uploaderName || item.author) || 'Unknown') + (location.search.endsWith('music_songs') ? ' - Topic' : ''),

54-58: Simplify the return statement for better readability.

The nested ternary expressions can be hard to read. Consider using a more explicit approach.

- return Array.isArray(itemsArray) ?
-   (itemsArray.length ?
-     html`${itemsArray.map(template)}` :
-     html`No Data Found`)
-   : html`${itemsArray}`;

+ if (!Array.isArray(itemsArray)) {
+   return html`${itemsArray}`;
+ }
+ 
+ return itemsArray.length 
+   ? html`${itemsArray.map(template)}` 
+   : html`No Data Found`;

16-16: Consider adding a return type annotation.

Adding a return type to the default exported function would improve code documentation and type safety.

- export default function(itemsArray: string | StreamItem[]) {
+ export default function(itemsArray: string | StreamItem[]): import("uhtml").TemplateResult {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between dd2dcf1 and 08ab041.

📒 Files selected for processing (4)
  • src/components/ItemsLoader.ts (1 hunks)
  • src/components/SuperCollectionList.ts (1 hunks)
  • src/lib/libraryUtils.ts (5 hunks)
  • src/scripts/list.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/scripts/list.ts
  • src/lib/libraryUtils.ts
  • src/components/SuperCollectionList.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/components/ItemsLoader.ts (3)
src/lib/libraryUtils.ts (1)
  • reservedCollections (6-6)
src/lib/utils.ts (3)
  • i18n (39-44)
  • hostResolver (33-37)
  • convertSStoHHMMSS (79-92)
src/lib/imageUtils.ts (2)
  • getThumbIdFromLink (21-38)
  • generateImageUrl (11-17)
🔇 Additional comments (5)
src/components/ItemsLoader.ts (5)

1-5: Imports are properly organized.

The imports are well-structured, bringing in necessary utilities and components for rendering items with uhtml.


7-7: Good use of Intl API for number formatting.

The numFormatter function uses the browser's Internationalization API for compact number formatting, which is a clean approach for handling view counts and subscriber numbers.


9-14: Well-structured mapping for reserved collections.

The reservedCollections object provides a clean mapping between collection keys and their visual representation (icon class and i18n key).


33-33: Clarify LIVE label logic.

The code assigns 'LIVE' when duration is zero or not present, but this assumption might not always be correct. Consider adding a more explicit check or comment.

- duration: (item.duration || item.lengthSeconds) > 0 ? convertSStoHHMMSS(item.duration || item.lengthSeconds) : 'LIVE',
+ duration: (item.duration || item.lengthSeconds) > 0 ? 
+   convertSStoHHMMSS(item.duration || item.lengthSeconds) : 
+   (item.isLive ? 'LIVE' : 'LIVE'), // Ideally check explicit isLive property if available

Are all streams with zero duration or missing duration values actually live streams? If not, consider adding a more explicit check.


16-60: Overall implementation looks good.

The component successfully migrates from Solid.js to uhtml, maintaining the same functionality while adapting to the new templating approach. The code handles different item types appropriately and uses utility functions effectively.

Comment on lines +29 to +30
id: item.videoId || item.url.substring(9),
href: hostResolver(item.url || ('/watch?v=' + item.videoId)),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Handle potential undefined values in URL processing.

The code assumes item.url exists when using item.url.substring(9), which could cause runtime errors if item.url is undefined or shorter than 9 characters.

- id: item.videoId || item.url.substring(9),
+ id: item.videoId || (item.url?.length >= 9 ? item.url.substring(9) : ''),
- href: hostResolver(item.url || ('/watch?v=' + item.videoId)),
+ href: hostResolver((item.url && item.url.trim()) ? item.url : ('/watch?v=' + item.videoId)),
📝 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
id: item.videoId || item.url.substring(9),
href: hostResolver(item.url || ('/watch?v=' + item.videoId)),
id: item.videoId || (item.url?.length >= 9 ? item.url.substring(9) : ''),
href: hostResolver((item.url && item.url.trim()) ? item.url : ('/watch?v=' + item.videoId)),
🤖 Prompt for AI Agents
In src/components/ItemsLoader.ts around lines 29 to 30, the code uses
item.url.substring(9) without checking if item.url is defined or long enough,
which can cause runtime errors. Add a check to ensure item.url is defined and
has at least 9 characters before calling substring; otherwise, provide a safe
fallback value to avoid errors.

@n-ce n-ce merged commit ab65c20 into main May 19, 2025
1 of 2 checks passed
@n-ce n-ce deleted the uhtml branch May 19, 2025 18:39
@coderabbitai coderabbitai Bot mentioned this pull request Jun 2, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Jul 29, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Feb 20, 2026
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