Skip to content

Animation on hover w/ Gleam Logo #2807

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 24 commits into
base: main
Choose a base branch
from

Conversation

Arpan-206
Copy link
Contributor

@Arpan-206 Arpan-206 commented Apr 20, 2025

Checklist:

  • I've thoroughly self-reviewed my changes
  • I've added tests for my changes, unless they affect admin-only areas (or are otherwise not worth testing)
  • I've verified any visual changes using Percy (add a commit with [percy] in the message to trigger)

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features
    • Introduced a new animated Gleam logo component with responsive sizing and interactive hover effects.
    • Gleam logo now displays as an animated graphic when selecting the "color" variant for the Gleam language.
  • Bug Fixes
    • Ensured proper cleanup of animation resources when the component is removed.
  • Chores
    • Added the @rive-app/canvas dependency to support advanced animation features.
  • Tests
    • Added integration tests to verify the rendering, interactive behavior, and cleanup of the new Gleam logo component.

Copy link

linear bot commented Apr 20, 2025

Copy link
Contributor

coderabbitai bot commented Apr 20, 2025

Walkthrough

A new Ember component, gleam-logo, has been introduced to render an animated Gleam logo using the Rive animation library. The component includes a Handlebars template that sets up lifecycle event handlers and a container for the animation, along with TypeScript logic for animation initialization, user interaction handling, and cleanup. The language-logo component template was updated to render this new component when the language is "gleam" and the variant is "color". The Rive canvas dependency was added to package.json to support the animation. Integration tests with a mocked Rive class verify rendering, event handling, and resource cleanup.

Changes

File(s) Change Summary
app/components/gleam-logo.hbs
app/components/gleam-logo.ts
Added a new Ember component rendering a Rive animated Gleam logo. The template manages lifecycle hooks (did-insert and will-destroy) and styling, while the TypeScript file handles Rive animation setup, event listeners, hover state input triggering, animation playback control, error handling, and cleanup logic.
app/components/language-logo.hbs Modified template to render the new <GleamLogo> component when the language slug is "gleam" and variant is "color". Other variant cases remain unchanged.
package.json Added the @rive-app/canvas dependency with version ^2.27.0 to enable Rive animation support.
tests/integration/components/gleam-logo-test.js Added integration tests for the gleam-logo component using a MockRive class to simulate the Rive animation library. Tests cover rendering, animation loading, hover state triggering, and cleanup on component destruction.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant GleamLogoComponent
    participant RiveLibrary

    User->>GleamLogoComponent: Insert component into DOM
    GleamLogoComponent->>RiveLibrary: Initialize Rive with canvas and .riv file
    RiveLibrary-->>GleamLogoComponent: Animation loaded
    GleamLogoComponent->>RiveLibrary: Set hover state input true
    GleamLogoComponent->>RiveLibrary: Reset hover state input false after 1s
    User->>GleamLogoComponent: Component removed from DOM
    GleamLogoComponent->>RiveLibrary: Stop animation and cleanup
Loading

Poem

In the meadow of code, a Gleam logo hops,
Animated by Rive, it twirls and it stops.
Hover and watch, let the colors ignite,
With tests that ensure it behaves just right.
A new friend for Ember, so lively and bright—
This bunny approves, with ears full of delight!
🐇✨


🪧 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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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 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

codecov bot commented Apr 20, 2025

Bundle Report

Changes will increase total bundle size by 129.01kB (0.35%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
client-array-push 36.71MB 129.01kB (0.35%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: client-array-push

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/chunk.*.js 129.01kB 2.97MB 4.54%

Copy link

github-actions bot commented Apr 20, 2025

Test Results

  1 files  ±0    1 suites  ±0   7m 46s ⏱️ - 1m 58s
626 tests +2  580 ✅ +2  45 💤 ±0  0 ❌ ±0  1 🔥 ±0 
626 runs  +2  579 ✅ +2  45 💤 ±0  1 ❌ ±0  1 🔥 ±0 

For more details on these errors, see this check.

Results for commit 711a877. ± Comparison against base commit c9b1151.

♻️ This comment has been updated with latest results.

Copy link

codecov bot commented Apr 20, 2025

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
581 1 580 45
View the top 1 failed test(s) by shortest run time
Chrome 135.0::Integration | Component | gleam-logo: it renders and initializes correctly
Stack Traces | 0.073s run time
Height is set correctly

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Copy link
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: 4

🧹 Nitpick comments (4)
app/components/gleam-logo.ts (4)

21-21: Remove debugging border before production.

The red border around the canvas is useful for development but should be removed before merging to production.

-      canvas.style.border = '1px solid red'; // Visual debugging

30-44: Remove or disable console logging statements.

There are numerous console.log statements throughout the code that should be removed or disabled before production deployment.

Consider removing these debugging log statements or wrapping them in a conditional that only executes in development environments:

-          console.log('Gleam logo animation loaded');
-
-          // Log available state machines
-          const stateMachines = this.riveInstance?.stateMachineNames;
-          console.log('All State Machines:', stateMachines);
-
-          if (stateMachines && stateMachines.length > 0) {
-            // Log details about each state machine
-            stateMachines.forEach((name, index) => {
-              console.log(`State Machine ${index + 1}:`, {
-                name,
-                inputs: this.riveInstance?.stateMachineInputs(name),
-              });
-            });
-          }
+          const stateMachines = this.riveInstance?.stateMachineNames;

6-111: Consider implementing a more robust error handling strategy.

The current error handling only logs errors to the console, which may not be sufficient for production.

Consider implementing a more comprehensive error handling strategy:

  1. Add error state tracking to display fallback content when Rive fails to load
  2. Add error reporting to your application's error tracking system
  3. Gracefully degrade to a static image when the animation fails

This would provide a better user experience in case of animation loading failures.

🧰 Tools
🪛 ESLint

[error] 50-72: Expected blank line before this statement.

(padding-line-between-statements)


[error] 82-93: Member handleMouseEnter should be declared before member setupRive.

(@typescript-eslint/member-ordering)


[error] 86-91: Expected blank line before this statement.

(padding-line-between-statements)


[error] 104-110: Member cleanupRive should be declared before member handleMouseLeave.

(@typescript-eslint/member-ordering)

🪛 GitHub Actions: Test

[error] 50-50: ESLint: Expected blank line before this statement (padding-line-between-statements).


[error] 82-82: ESLint: Member 'handleMouseEnter' should be declared before member 'setupRive' (@typescript-eslint/member-ordering).


[error] 86-86: ESLint: Expected blank line before this statement (padding-line-between-statements).


[error] 104-104: ESLint: Member 'cleanupRive' should be declared before member 'handleMouseLeave' (@typescript-eslint/member-ordering).


83-93: Extract state machine selection to a dedicated method.

The same pattern for getting the first state machine is duplicated in both mouse event handlers.

Consider extracting this logic to a dedicated method:

+  private getDefaultStateMachine(): string | undefined {
+    if (this.riveInstance) {
+      const stateMachines = this.riveInstance.stateMachineNames;
+      if (stateMachines && stateMachines.length > 0) {
+        return stateMachines[0];
+      }
+    }
+    return undefined;
+  }

   @action
   handleMouseEnter() {
     if (this.riveInstance) {
-      const stateMachines = this.riveInstance.stateMachineNames;
-      if (stateMachines && stateMachines.length > 0) {
-        const stateMachineName = stateMachines[0];
+      const stateMachineName = this.getDefaultStateMachine();
+      if (stateMachineName) {
         console.log('Playing hover animation with state machine:', stateMachineName);
         this.riveInstance.reset();
         this.riveInstance.play(stateMachineName);
       }
     }
   }
🧰 Tools
🪛 ESLint

[error] 82-93: Member handleMouseEnter should be declared before member setupRive.

(@typescript-eslint/member-ordering)


[error] 86-91: Expected blank line before this statement.

(padding-line-between-statements)

🪛 GitHub Actions: Test

[error] 86-86: ESLint: Expected blank line before this statement (padding-line-between-statements).

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 803a7fe and 6859dfc.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • app/components/gleam-logo.hbs (1 hunks)
  • app/components/gleam-logo.ts (1 hunks)
  • app/components/language-logo.hbs (1 hunks)
  • app/components/language-logo.ts (1 hunks)
  • app/styles/components/gleam-logo.css (1 hunks)
  • package.json (1 hunks)
🧰 Additional context used
🪛 GitHub Actions: Test
app/components/language-logo.hbs

[error] 2-2: TS2345: Argument of type 'unknown' is not assignable to parameter of type 'HTMLDivElement'.

app/components/gleam-logo.ts

[error] 50-50: ESLint: Expected blank line before this statement (padding-line-between-statements).


[error] 82-82: ESLint: Member 'handleMouseEnter' should be declared before member 'setupRive' (@typescript-eslint/member-ordering).


[error] 86-86: ESLint: Expected blank line before this statement (padding-line-between-statements).


[error] 104-104: ESLint: Member 'cleanupRive' should be declared before member 'handleMouseLeave' (@typescript-eslint/member-ordering).

🪛 ESLint
app/components/gleam-logo.ts

[error] 50-72: Expected blank line before this statement.

(padding-line-between-statements)


[error] 82-93: Member handleMouseEnter should be declared before member setupRive.

(@typescript-eslint/member-ordering)


[error] 86-91: Expected blank line before this statement.

(padding-line-between-statements)


[error] 104-110: Member cleanupRive should be declared before member handleMouseLeave.

(@typescript-eslint/member-ordering)

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

205-205: LGTM - Proper dependency added for Rive animations.

The addition of @rive-app/canvas as a dependency is appropriate for implementing the Gleam logo animation feature. The caret version syntax (^2.27.0) follows project conventions and allows for compatible updates.

app/styles/components/gleam-logo.css (1)

1-12: CSS implementation looks clean and effective.

The CSS is well-structured with appropriate use of:

  • Block-level display for the container and canvas
  • 100% dimensions to ensure the animation fills its allocated space
  • object-fit: contain to maintain proper scaling of the canvas content

This implementation follows best practices for embedding canvas animations within responsive layouts.

app/components/language-logo.ts (1)

18-18: LGTM - Proper Glint registry registration.

The component is correctly registered in the Glint registry, which ensures proper type checking when using the <GleamLogo /> component in templates.

app/components/gleam-logo.hbs (2)

1-9: LGTM - Well-structured template with proper lifecycle hooks.

The template effectively:

  • Establishes a container for the Rive animation
  • Uses appropriate lifecycle hooks (did-insert and will-destroy) to manage animation setup and cleanup
  • Implements mouse event handlers for the hover animation effect
  • Includes a helpful comment about canvas insertion

This is a clean implementation that follows Ember best practices.


1-9:

✅ Verification successful

Verify that companion TS component implements all referenced methods.

Please ensure that the gleam-logo.ts component properly implements all the methods referenced in this template: setupRive, cleanupRive, handleMouseEnter, and handleMouseLeave.


🏁 Script executed:

#!/bin/bash
# Check if the TypeScript component file exists and contains the required methods
echo "Checking gleam-logo.ts implementation..."
file_path="app/components/gleam-logo.ts"

if [ -f "$file_path" ]; then
  echo "File exists: $file_path"
  echo "Checking for required methods:"
  
  grep -n "setupRive" "$file_path" && echo "✓ setupRive found" || echo "✗ setupRive NOT found"
  grep -n "cleanupRive" "$file_path" && echo "✓ cleanupRive found" || echo "✗ cleanupRive NOT found"
  grep -n "handleMouseEnter" "$file_path" && echo "✓ handleMouseEnter found" || echo "✗ handleMouseEnter NOT found"
  grep -n "handleMouseLeave" "$file_path" && echo "✓ handleMouseLeave found" || echo "✗ handleMouseLeave NOT found"
else
  echo "ERROR: File not found: $file_path"
  echo "The template references methods that should be implemented in this file."
fi

Length of output: 874


All referenced methods are implemented in app/components/gleam-logo.ts.
Verified that setupRive, cleanupRive, handleMouseEnter, and handleMouseLeave are present—no further changes needed.

app/components/gleam-logo.ts (1)

17-20: Verify if the fixed canvas dimensions are appropriate.

The canvas is set to fixed dimensions (141×144) but displayed with 100% width and height, which could lead to stretching or distortion.

Verify that the fixed dimensions match the aspect ratio of the Gleam logo and that the scaling approach produces crisp rendering without distortion across different display sizes. Consider if using viewBox or preserveAspectRatio equivalent settings might be more appropriate.

Copy link
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: 0

♻️ Duplicate comments (2)
app/components/gleam-logo.ts (2)

28-40: Fix ESLint member ordering issues.

The member ordering still doesn't match ESLint requirements as mentioned in previous reviews. Move the handleMouseEnter method before setupRive to resolve the linting error.

-  @action
-  handleMouseEnter() {
-    // ... current implementation
-  }
-
-  @action
-  setupRive(element: HTMLDivElement) {
-    // ... current implementation
-  }

+  @action
+  handleMouseEnter() {
+    // ... current implementation
+  }
+
+  @action
+  setupRive(element: HTMLDivElement) {
+    // ... current implementation
+  }

Also applies to: 51-122

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 32-32: app/components/gleam-logo.ts#L32
Added line #L32 was not covered by tests


[warning] 35-38: app/components/gleam-logo.ts#L35-L38
Added lines #L35 - L38 were not covered by tests


72-116: Remove development console logs and simplify initial animation.

There are numerous console logs and an unnecessary initial animation sequence that should be removed for production code. This was mentioned in a previous review but hasn't been addressed.

-          console.log('Gleam logo animation loaded');
-
-          // Log available state machines
-          const stateMachines = this.riveInstance?.stateMachineNames;
-          console.log('All State Machines:', stateMachines);
-
-          if (stateMachines && stateMachines.length > 0) {
-            // Log details about each state machine
-            stateMachines.forEach((name, index) => {
-              console.log(`State Machine ${index + 1}:`, {
-                name,
-                inputs: this.riveInstance?.stateMachineInputs(name),
-              });
-            });
-          }
-
-          // Play initial animation directly with longer delay
-          setTimeout(() => {
-            if (this.riveInstance) {
-              const stateMachines = this.riveInstance.stateMachineNames;
-
-              if (stateMachines && stateMachines.length > 0) {
-                // Try each state machine
-                stateMachines.forEach((stateMachineName) => {
-                  console.log('Attempting to play with state machine:', stateMachineName);
-
-                  // Reset and play
-                  this.riveInstance?.reset();
-                  this.riveInstance?.play(stateMachineName);
-
-                  // Log animation state after a short delay
-                  setTimeout(() => {
-                    if (this.riveInstance) {
-                      console.log('Animation state after play:', {
-                        stateMachine: stateMachineName,
-                        isPlaying: this.riveInstance.isPlaying,
-                        isPaused: this.riveInstance.isPaused,
-                        isStopped: this.riveInstance.isStopped,
-                        allStateMachines: this.riveInstance.stateMachineNames,
-                      });
-                    }
-                  }, 500);
-                });
-              }
-            }
-          }, 2000);
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 72-72: app/components/gleam-logo.ts#L72
Added line #L72 was not covered by tests


[warning] 75-76: app/components/gleam-logo.ts#L75-L76
Added lines #L75 - L76 were not covered by tests


[warning] 80-81: app/components/gleam-logo.ts#L80-L81
Added lines #L80 - L81 were not covered by tests


[warning] 89-89: app/components/gleam-logo.ts#L89
Added line #L89 was not covered by tests


[warning] 91-91: app/components/gleam-logo.ts#L91
Added line #L91 was not covered by tests


[warning] 95-96: app/components/gleam-logo.ts#L95-L96
Added lines #L95 - L96 were not covered by tests


[warning] 99-100: app/components/gleam-logo.ts#L99-L100
Added lines #L99 - L100 were not covered by tests


[warning] 103-103: app/components/gleam-logo.ts#L103
Added line #L103 was not covered by tests


[warning] 105-105: app/components/gleam-logo.ts#L105
Added line #L105 was not covered by tests

🧹 Nitpick comments (3)
app/components/gleam-logo.ts (3)

62-62: Remove visual debugging border.

The red border appears to be for development debugging purposes and should be removed before deploying to production.

-      canvas.style.border = '1px solid red'; // Visual debugging

35-35: Remove console log from mouse event handler.

Remove the debug console log from the production code:

-        console.log('Playing hover animation with state machine:', stateMachineName);
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 35-38: app/components/gleam-logo.ts#L35-L38
Added lines #L35 - L38 were not covered by tests


57-63: Consider making canvas dimensions configurable or responsive.

The canvas has hard-coded dimensions (141x144). Consider making these dimensions configurable through component arguments or using relative sizing based on the container for better reusability and responsiveness.

-      canvas.width = 141; // Fixed size for crisp rendering
-      canvas.height = 144;
+      canvas.width = this.args.width || 141; 
+      canvas.height = this.args.height || 144;

Also update the component interface:

  Args: {
    class?: string;
    style?: string;
+   width?: number;
+   height?: number;
    [key: string]: unknown;
  };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6859dfc and 175ee69.

📒 Files selected for processing (1)
  • app/components/gleam-logo.ts (1 hunks)
🧰 Additional context used
🪛 GitHub Check: codecov/patch
app/components/gleam-logo.ts

[warning] 18-19: app/components/gleam-logo.ts#L18-L19
Added lines #L18 - L19 were not covered by tests


[warning] 24-25: app/components/gleam-logo.ts#L24-L25
Added lines #L24 - L25 were not covered by tests


[warning] 32-32: app/components/gleam-logo.ts#L32
Added line #L32 was not covered by tests


[warning] 35-38: app/components/gleam-logo.ts#L35-L38
Added lines #L35 - L38 were not covered by tests


[warning] 47-48: app/components/gleam-logo.ts#L47-L48
Added lines #L47 - L48 were not covered by tests


[warning] 54-54: app/components/gleam-logo.ts#L54
Added line #L54 was not covered by tests


[warning] 56-56: app/components/gleam-logo.ts#L56
Added line #L56 was not covered by tests


[warning] 58-64: app/components/gleam-logo.ts#L58-L64
Added lines #L58 - L64 were not covered by tests


[warning] 67-67: app/components/gleam-logo.ts#L67
Added line #L67 was not covered by tests


[warning] 72-72: app/components/gleam-logo.ts#L72
Added line #L72 was not covered by tests


[warning] 75-76: app/components/gleam-logo.ts#L75-L76
Added lines #L75 - L76 were not covered by tests


[warning] 80-81: app/components/gleam-logo.ts#L80-L81
Added lines #L80 - L81 were not covered by tests


[warning] 89-89: app/components/gleam-logo.ts#L89
Added line #L89 was not covered by tests


[warning] 91-91: app/components/gleam-logo.ts#L91
Added line #L91 was not covered by tests


[warning] 95-96: app/components/gleam-logo.ts#L95-L96
Added lines #L95 - L96 were not covered by tests


[warning] 99-100: app/components/gleam-logo.ts#L99-L100
Added lines #L99 - L100 were not covered by tests


[warning] 103-103: app/components/gleam-logo.ts#L103
Added line #L103 was not covered by tests


[warning] 105-105: app/components/gleam-logo.ts#L105
Added line #L105 was not covered by tests


[warning] 121-121: app/components/gleam-logo.ts#L121
Added line #L121 was not covered by tests

🔇 Additional comments (1)
app/components/gleam-logo.ts (1)

1-123: Add unit tests for the new component.

The static analysis shows this new component has no test coverage. Consider adding unit tests to verify:

  1. Component initialization
  2. Animation lifecycle events
  3. Mouse interaction behavior

This will prevent regressions and ensure the component works as expected across browsers.

Would you like me to generate a basic test skeleton for this component?

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 18-19: app/components/gleam-logo.ts#L18-L19
Added lines #L18 - L19 were not covered by tests


[warning] 24-25: app/components/gleam-logo.ts#L24-L25
Added lines #L24 - L25 were not covered by tests


[warning] 32-32: app/components/gleam-logo.ts#L32
Added line #L32 was not covered by tests


[warning] 35-38: app/components/gleam-logo.ts#L35-L38
Added lines #L35 - L38 were not covered by tests


[warning] 47-48: app/components/gleam-logo.ts#L47-L48
Added lines #L47 - L48 were not covered by tests


[warning] 54-54: app/components/gleam-logo.ts#L54
Added line #L54 was not covered by tests


[warning] 56-56: app/components/gleam-logo.ts#L56
Added line #L56 was not covered by tests


[warning] 58-64: app/components/gleam-logo.ts#L58-L64
Added lines #L58 - L64 were not covered by tests


[warning] 67-67: app/components/gleam-logo.ts#L67
Added line #L67 was not covered by tests


[warning] 72-72: app/components/gleam-logo.ts#L72
Added line #L72 was not covered by tests


[warning] 75-76: app/components/gleam-logo.ts#L75-L76
Added lines #L75 - L76 were not covered by tests


[warning] 80-81: app/components/gleam-logo.ts#L80-L81
Added lines #L80 - L81 were not covered by tests


[warning] 89-89: app/components/gleam-logo.ts#L89
Added line #L89 was not covered by tests


[warning] 91-91: app/components/gleam-logo.ts#L91
Added line #L91 was not covered by tests


[warning] 95-96: app/components/gleam-logo.ts#L95-L96
Added lines #L95 - L96 were not covered by tests


[warning] 99-100: app/components/gleam-logo.ts#L99-L100
Added lines #L99 - L100 were not covered by tests


[warning] 103-103: app/components/gleam-logo.ts#L103
Added line #L103 was not covered by tests


[warning] 105-105: app/components/gleam-logo.ts#L105
Added line #L105 was not covered by tests


[warning] 121-121: app/components/gleam-logo.ts#L121
Added line #L121 was not covered by tests

@Arpan-206 Arpan-206 requested a review from rohitpaulk April 20, 2025 17:25
Copy link
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: 2

🧹 Nitpick comments (4)
app/components/gleam-logo.ts (4)

34-48: Extract state machine names to constants

State machine names like 'State Machine 1' and 'State Machine 2' are hardcoded in multiple places. Consider extracting these to class constants for better maintainability.

export default class GleamLogoComponent extends Component<GleamLogoSignature> {
+ // State machine names
+ private readonly HOVER_STATE_MACHINE = 'State Machine 1';
+ private readonly IDLE_STATE_MACHINE = 'State Machine 2';
+
  @tracked riveInstance: Rive | null = null;
  container: HTMLElement | null = null;
  animationInterval: number | null = null;

  // ... other methods

  @action
  handleMouseEnter() {
    if (this.riveInstance) {
      const stateMachines = this.riveInstance.stateMachineNames;

      if (stateMachines && stateMachines.length > 0) {
-        const stateMachineName = 'State Machine 1';
+        const stateMachineName = this.HOVER_STATE_MACHINE;

        if (stateMachines.includes(stateMachineName)) {
          this.riveInstance.reset();
          this.riveInstance.play(stateMachineName);
        }
      }
    }
  }

Also applies to: 50-56

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 38-38: app/components/gleam-logo.ts#L38
Added line #L38 was not covered by tests


[warning] 41-41: app/components/gleam-logo.ts#L41
Added line #L41 was not covered by tests


[warning] 44-45: app/components/gleam-logo.ts#L44-L45
Added lines #L44 - L45 were not covered by tests


70-75: Make animation source path configurable

The animation source path is hardcoded. Consider making it configurable through the component's arguments to improve reusability.

interface GleamLogoSignature {
  Element: HTMLDivElement;
  Args: {
    class?: string;
    style?: string;
+   animationSource?: string;
    [key: string]: unknown;
  };
  Blocks: Record<string, never>;
}

// Later in setupRive method:
this.riveInstance = new Rive({
-  src: '/assets/animations/gleam_logo_animation.riv',
+  src: this.args.animationSource || '/assets/animations/gleam_logo_animation.riv',
  canvas: canvas,
  // ...
});
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 71-71: app/components/gleam-logo.ts#L71
Added line #L71 was not covered by tests


96-117: Consider reducing animation complexity

The initial animation setup uses multiple timers with different durations (500ms to start, 800ms to reset). This adds unnecessary complexity and might cause issues if the component is rapidly mounted/unmounted.

Consider simplifying this to a single animation sequence or making the animation behavior more declarative.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 98-98: app/components/gleam-logo.ts#L98
Added line #L98 was not covered by tests


[warning] 100-100: app/components/gleam-logo.ts#L100
Added line #L100 was not covered by tests


[warning] 103-103: app/components/gleam-logo.ts#L103
Added line #L103 was not covered by tests


[warning] 106-107: app/components/gleam-logo.ts#L106-L107
Added lines #L106 - L107 were not covered by tests


[warning] 110-110: app/components/gleam-logo.ts#L110
Added line #L110 was not covered by tests


[warning] 112-112: app/components/gleam-logo.ts#L112
Added line #L112 was not covered by tests


120-123: Add more detailed error handling

While catching errors is good, the current implementation only logs to console. Consider adding more robust error handling like:

  1. Displaying a fallback static image
  2. Notifying the user or a monitoring system
  3. Providing more context in the error message
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 122-122: app/components/gleam-logo.ts#L122
Added line #L122 was not covered by tests

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between c864859 and a20cf9f.

📒 Files selected for processing (1)
  • app/components/gleam-logo.ts (1 hunks)
🧰 Additional context used
🪛 GitHub Check: codecov/patch
app/components/gleam-logo.ts

[warning] 18-20: app/components/gleam-logo.ts#L18-L20
Added lines #L18 - L20 were not covered by tests


[warning] 25-26: app/components/gleam-logo.ts#L25-L26
Added lines #L25 - L26 were not covered by tests


[warning] 30-31: app/components/gleam-logo.ts#L30-L31
Added lines #L30 - L31 were not covered by tests


[warning] 38-38: app/components/gleam-logo.ts#L38
Added line #L38 was not covered by tests


[warning] 41-41: app/components/gleam-logo.ts#L41
Added line #L41 was not covered by tests


[warning] 44-45: app/components/gleam-logo.ts#L44-L45
Added lines #L44 - L45 were not covered by tests


[warning] 54-55: app/components/gleam-logo.ts#L54-L55
Added lines #L54 - L55 were not covered by tests


[warning] 61-61: app/components/gleam-logo.ts#L61
Added line #L61 was not covered by tests


[warning] 63-69: app/components/gleam-logo.ts#L63-L69
Added lines #L63 - L69 were not covered by tests


[warning] 71-71: app/components/gleam-logo.ts#L71
Added line #L71 was not covered by tests


[warning] 78-78: app/components/gleam-logo.ts#L78
Added line #L78 was not covered by tests


[warning] 80-80: app/components/gleam-logo.ts#L80
Added line #L80 was not covered by tests


[warning] 83-83: app/components/gleam-logo.ts#L83
Added line #L83 was not covered by tests


[warning] 86-87: app/components/gleam-logo.ts#L86-L87
Added lines #L86 - L87 were not covered by tests


[warning] 93-94: app/components/gleam-logo.ts#L93-L94
Added lines #L93 - L94 were not covered by tests


[warning] 98-98: app/components/gleam-logo.ts#L98
Added line #L98 was not covered by tests


[warning] 100-100: app/components/gleam-logo.ts#L100
Added line #L100 was not covered by tests


[warning] 103-103: app/components/gleam-logo.ts#L103
Added line #L103 was not covered by tests


[warning] 106-107: app/components/gleam-logo.ts#L106-L107
Added lines #L106 - L107 were not covered by tests


[warning] 110-110: app/components/gleam-logo.ts#L110
Added line #L110 was not covered by tests


[warning] 112-112: app/components/gleam-logo.ts#L112
Added line #L112 was not covered by tests


[warning] 122-122: app/components/gleam-logo.ts#L122
Added line #L122 was not covered by tests

🔇 Additional comments (2)
app/components/gleam-logo.ts (2)

1-14: Well-structured interface definition

The component interface is well-defined with proper type annotations for the expected HTML element and component arguments.


21-32: Good resource cleanup implementation

The cleanupRive method properly handles cleanup by clearing intervals and stopping the Rive instance, which prevents memory leaks.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 25-26: app/components/gleam-logo.ts#L25-L26
Added lines #L25 - L26 were not covered by tests


[warning] 30-31: app/components/gleam-logo.ts#L30-L31
Added lines #L30 - L31 were not covered by tests

Copy link
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: 4

🧹 Nitpick comments (4)
tests/integration/components/gleam-logo-test.ts (4)

1-12: Imports look good, but there's an unused import.

The triggerEvent import from @ember/test-helpers on line 3 is imported but never used in the test file. Consider removing unused imports to keep the codebase clean.

- import { render, settled, triggerEvent } from '@ember/test-helpers';
+ import { render, settled } from '@ember/test-helpers';
🧰 Tools
🪛 ESLint

[error] 3-3: 'triggerEvent' is defined but never used.

(@typescript-eslint/no-unused-vars)


78-83: Type safety improvement for TestContext interface.

The lookup method's return type is specified as any, which should be replaced with a more specific type or at least unknown.

interface TestContext {
  originalRive: typeof MockRive;
  owner: {
-    lookup: (name: string) => any;
+    lookup: (name: string) => unknown;
  };
}
🧰 Tools
🪛 ESLint

[error] 81-81: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


100-121: First test case looks solid but could use assertion improvement.

The test verifies basic rendering and initialization of the Rive animation, but there's a QUnit-specific improvement that could be made.

    // Verify state machines are available
-    assert.deepEqual(mockRive.stateMachineNames, ['State Machine 1', 'State Machine 2'], 'State machines are available');
+    assert.deepEqual(
+      mockRive.stateMachineNames,
+      ['State Machine 1', 'State Machine 2'],
+      'State machines are available'
+    );
🧰 Tools
🪛 ESLint

[error] 113-113: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


1-225: Consider using Ember's built-in testing capabilities more effectively.

The current test approach involves manually simulating the component's behavior rather than testing the component's actual implementation. This makes the tests brittle and tightly coupled to implementation details.

A better approach would be to:

  1. Test the component's public API and behavior rather than reimplementing its internal logic
  2. Use Ember's testing utilities like triggerEvent (which is imported but not used)
  3. Focus on verifying observable behaviors rather than internal implementation details

For example, instead of manually attaching event handlers, you could use:

// Trigger events using Ember's testing utilities
await triggerEvent('.gleam-logo-container', 'mouseenter');

And instead of asserting on internal method calls, verify observable behavior changes.

🧰 Tools
🪛 Biome (1.9.4)

[error] 19-19: Don't use 'Function' as a type.

Prefer explicitly define the function shape. This type accepts any function-like value, which can be a common source of bugs.

(lint/complexity/noBannedTypes)


[error] 46-46: Don't use 'Function' as a type.

Prefer explicitly define the function shape. This type accepts any function-like value, which can be a common source of bugs.

(lint/complexity/noBannedTypes)


[error] 53-53: Don't use 'Function' as a type.

Prefer explicitly define the function shape. This type accepts any function-like value, which can be a common source of bugs.

(lint/complexity/noBannedTypes)

🪛 ESLint

[error] 3-3: 'triggerEvent' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 18-18: Member isLoaded should be declared before member stateMachineNames.

(@typescript-eslint/member-ordering)


[error] 19-19: Member _listeners should be declared before member isLoaded.

(@typescript-eslint/member-ordering)


[error] 19-19: The Function type accepts any function-like value.
Prefer explicitly defining any function parameters and return type.

(@typescript-eslint/no-unsafe-function-type)


[error] 20-20: Member _interval should be declared before member _listeners.

(@typescript-eslint/member-ordering)


[error] 26-28: Member constructor should be declared before all get definitions.

(@typescript-eslint/member-ordering)


[error] 34-36: Member play should be declared before member reset.

(@typescript-eslint/member-ordering)


[error] 34-34: 'stateMachineName' is defined but never used. Allowed unused args must match /^_/u.

(@typescript-eslint/no-unused-vars)


[error] 42-44: Member stateMachineInputs should be declared before member stop.

(@typescript-eslint/member-ordering)


[error] 42-42: 'stateMachineName' is defined but never used. Allowed unused args must match /^_/u.

(@typescript-eslint/no-unused-vars)


[error] 46-51: Member on should be declared before member stateMachineInputs.

(@typescript-eslint/member-ordering)


[error] 46-46: The Function type accepts any function-like value.
Prefer explicitly defining any function parameters and return type.

(@typescript-eslint/no-unsafe-function-type)


[error] 50-50: Expected blank line before this statement.

(padding-line-between-statements)


[error] 53-61: Member off should be declared before member on.

(@typescript-eslint/member-ordering)


[error] 53-53: The Function type accepts any function-like value.
Prefer explicitly defining any function parameters and return type.

(@typescript-eslint/no-unsafe-function-type)


[error] 55-60: Expected blank line before this statement.

(padding-line-between-statements)


[error] 57-59: Expected blank line before this statement.

(padding-line-between-statements)


[error] 64-64: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 66-68: Expected blank line before this statement.

(padding-line-between-statements)


[error] 81-81: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 113-113: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 133-133: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 142-142: Unexpected assert.equal. Use assert.strictEqual, assert.deepEqual, or assert.propEqual.

(qunit/no-assert-equal)


[error] 157-163: Expected blank line before this statement.

(padding-line-between-statements)


[error] 159-162: Expected blank line before this statement.

(padding-line-between-statements)


[error] 173-173: Unexpected assert.equal. Use assert.strictEqual, assert.deepEqual, or assert.propEqual.

(qunit/no-assert-equal)


[error] 174-174: Unexpected assert.equal. Use assert.strictEqual, assert.deepEqual, or assert.propEqual.

(qunit/no-assert-equal)


[error] 190-190: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 220-220: Unexpected assert.equal. Use assert.strictEqual, assert.deepEqual, or assert.propEqual.

(qunit/no-assert-equal)


[error] 221-221: Unexpected assert.equal. Use assert.strictEqual, assert.deepEqual, or assert.propEqual.

(qunit/no-assert-equal)


[error] 222-222: Unexpected assert.equal. Use assert.strictEqual, assert.deepEqual, or assert.propEqual.

(qunit/no-assert-equal)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between a20cf9f and 0335cf1.

📒 Files selected for processing (1)
  • tests/integration/components/gleam-logo-test.ts (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
tests/integration/components/gleam-logo-test.ts

[error] 19-19: Don't use 'Function' as a type.

Prefer explicitly define the function shape. This type accepts any function-like value, which can be a common source of bugs.

(lint/complexity/noBannedTypes)


[error] 46-46: Don't use 'Function' as a type.

Prefer explicitly define the function shape. This type accepts any function-like value, which can be a common source of bugs.

(lint/complexity/noBannedTypes)


[error] 53-53: Don't use 'Function' as a type.

Prefer explicitly define the function shape. This type accepts any function-like value, which can be a common source of bugs.

(lint/complexity/noBannedTypes)

🪛 ESLint
tests/integration/components/gleam-logo-test.ts

[error] 3-3: 'triggerEvent' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 18-18: Member isLoaded should be declared before member stateMachineNames.

(@typescript-eslint/member-ordering)


[error] 19-19: Member _listeners should be declared before member isLoaded.

(@typescript-eslint/member-ordering)


[error] 19-19: The Function type accepts any function-like value.
Prefer explicitly defining any function parameters and return type.

(@typescript-eslint/no-unsafe-function-type)


[error] 20-20: Member _interval should be declared before member _listeners.

(@typescript-eslint/member-ordering)


[error] 26-28: Member constructor should be declared before all get definitions.

(@typescript-eslint/member-ordering)


[error] 34-36: Member play should be declared before member reset.

(@typescript-eslint/member-ordering)


[error] 34-34: 'stateMachineName' is defined but never used. Allowed unused args must match /^_/u.

(@typescript-eslint/no-unused-vars)


[error] 42-44: Member stateMachineInputs should be declared before member stop.

(@typescript-eslint/member-ordering)


[error] 42-42: 'stateMachineName' is defined but never used. Allowed unused args must match /^_/u.

(@typescript-eslint/no-unused-vars)


[error] 46-51: Member on should be declared before member stateMachineInputs.

(@typescript-eslint/member-ordering)


[error] 46-46: The Function type accepts any function-like value.
Prefer explicitly defining any function parameters and return type.

(@typescript-eslint/no-unsafe-function-type)


[error] 50-50: Expected blank line before this statement.

(padding-line-between-statements)


[error] 53-61: Member off should be declared before member on.

(@typescript-eslint/member-ordering)


[error] 53-53: The Function type accepts any function-like value.
Prefer explicitly defining any function parameters and return type.

(@typescript-eslint/no-unsafe-function-type)


[error] 55-60: Expected blank line before this statement.

(padding-line-between-statements)


[error] 57-59: Expected blank line before this statement.

(padding-line-between-statements)


[error] 64-64: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 66-68: Expected blank line before this statement.

(padding-line-between-statements)


[error] 81-81: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 113-113: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 133-133: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 142-142: Unexpected assert.equal. Use assert.strictEqual, assert.deepEqual, or assert.propEqual.

(qunit/no-assert-equal)


[error] 157-163: Expected blank line before this statement.

(padding-line-between-statements)


[error] 159-162: Expected blank line before this statement.

(padding-line-between-statements)


[error] 173-173: Unexpected assert.equal. Use assert.strictEqual, assert.deepEqual, or assert.propEqual.

(qunit/no-assert-equal)


[error] 174-174: Unexpected assert.equal. Use assert.strictEqual, assert.deepEqual, or assert.propEqual.

(qunit/no-assert-equal)


[error] 190-190: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 220-220: Unexpected assert.equal. Use assert.strictEqual, assert.deepEqual, or assert.propEqual.

(qunit/no-assert-equal)


[error] 221-221: Unexpected assert.equal. Use assert.strictEqual, assert.deepEqual, or assert.propEqual.

(qunit/no-assert-equal)


[error] 222-222: Unexpected assert.equal. Use assert.strictEqual, assert.deepEqual, or assert.propEqual.

(qunit/no-assert-equal)

🔇 Additional comments (1)
tests/integration/components/gleam-logo-test.ts (1)

85-98: Test setup and teardown looks good.

The hooks setup properly stores and restores the original Rive constructor, which is a good practice to avoid test pollution.


// Create and attach mock Rive instance
const mockRive = new MockRive({ canvas: canvas as HTMLCanvasElement });
(container as any).__riveInstance = mockRive;
Copy link
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 by avoiding any assertions.

There are several instances where (container as any).__riveInstance is used to assign the mock Rive instance. This bypasses TypeScript's type checking and should be avoided.

Consider extending the HTMLElement interface to include the __riveInstance property, or use a WeakMap to associate containers with their Rive instances:

// At the top of the file
interface GleamLogoContainer extends HTMLElement {
  __riveInstance?: MockRive;
}

// Then in the tests
const container = document.querySelector('.gleam-logo-container') as GleamLogoContainer;
container.__riveInstance = mockRive;

Also applies to: 133-133, 190-190

🧰 Tools
🪛 ESLint

[error] 113-113: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

Comment on lines 180 to 223
test('it cleans up resources on destroy', async function (this: TestContext, assert) {
await render(hbs`<GleamLogo />`);

// Get the container and create mock Rive instance
const container = document.querySelector('.gleam-logo-container') as HTMLElement;
const canvas = container?.querySelector('canvas');
assert.ok(canvas, 'Canvas element exists');

// Create and attach mock Rive instance
const mockRive = new MockRive({ canvas: canvas as HTMLCanvasElement });
(container as any).__riveInstance = mockRive;

// Track cleanup calls
let stopCalls = 0;
let resetCalls = 0;

mockRive.stop = () => {
stopCalls++;
};

mockRive.reset = () => {
resetCalls++;
};

// Simulate Rive load
mockRive.simulateLoad();
await settled();

// Add cleanup handler directly
const cleanupRive = () => {
if (mockRive) {
mockRive.stop();
mockRive.reset();
}
};

// Trigger cleanup
cleanupRive();
await settled();

assert.equal(stopCalls, 1, 'Stop was called on destroy');
assert.equal(resetCalls, 1, 'Reset was called on destroy');
assert.equal(mockRive.interval, null, 'Animation interval was cleared');
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve test for resource cleanup.

Similar to the previous test, this one has QUnit assertion style issues and manually recreates cleanup logic rather than testing the component's own cleanup method.

  test('it cleans up resources on destroy', async function (this: TestContext, assert) {
    await render(hbs`<GleamLogo />`);

    // Get the container and create mock Rive instance
    const container = document.querySelector('.gleam-logo-container') as HTMLElement;
    const canvas = container?.querySelector('canvas');
    assert.ok(canvas, 'Canvas element exists');

    // Create and attach mock Rive instance
    const mockRive = new MockRive({ canvas: canvas as HTMLCanvasElement });
    (container as any).__riveInstance = mockRive;

    // Track cleanup calls
    let stopCalls = 0;
    let resetCalls = 0;

    mockRive.stop = () => {
      stopCalls++;
    };

    mockRive.reset = () => {
      resetCalls++;
    };

    // Simulate Rive load
    mockRive.simulateLoad();
    await settled();

    // Add cleanup handler directly
    const cleanupRive = () => {
      if (mockRive) {
        mockRive.stop();
        mockRive.reset();
      }
    };

    // Trigger cleanup
    cleanupRive();
    await settled();

-    assert.equal(stopCalls, 1, 'Stop was called on destroy');
-    assert.equal(resetCalls, 1, 'Reset was called on destroy');
-    assert.equal(mockRive.interval, null, 'Animation interval was cleared');
+    assert.strictEqual(stopCalls, 1, 'Stop was called on destroy');
+    assert.strictEqual(resetCalls, 1, 'Reset was called on destroy');
+    assert.strictEqual(mockRive.interval, null, 'Animation interval was cleared');
  });
📝 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
test('it cleans up resources on destroy', async function (this: TestContext, assert) {
await render(hbs`<GleamLogo />`);
// Get the container and create mock Rive instance
const container = document.querySelector('.gleam-logo-container') as HTMLElement;
const canvas = container?.querySelector('canvas');
assert.ok(canvas, 'Canvas element exists');
// Create and attach mock Rive instance
const mockRive = new MockRive({ canvas: canvas as HTMLCanvasElement });
(container as any).__riveInstance = mockRive;
// Track cleanup calls
let stopCalls = 0;
let resetCalls = 0;
mockRive.stop = () => {
stopCalls++;
};
mockRive.reset = () => {
resetCalls++;
};
// Simulate Rive load
mockRive.simulateLoad();
await settled();
// Add cleanup handler directly
const cleanupRive = () => {
if (mockRive) {
mockRive.stop();
mockRive.reset();
}
};
// Trigger cleanup
cleanupRive();
await settled();
assert.equal(stopCalls, 1, 'Stop was called on destroy');
assert.equal(resetCalls, 1, 'Reset was called on destroy');
assert.equal(mockRive.interval, null, 'Animation interval was cleared');
});
test('it cleans up resources on destroy', async function (this: TestContext, assert) {
await render(hbs`<GleamLogo />`);
// Get the container and create mock Rive instance
const container = document.querySelector('.gleam-logo-container') as HTMLElement;
const canvas = container?.querySelector('canvas');
assert.ok(canvas, 'Canvas element exists');
// Create and attach mock Rive instance
const mockRive = new MockRive({ canvas: canvas as HTMLCanvasElement });
(container as any).__riveInstance = mockRive;
// Track cleanup calls
let stopCalls = 0;
let resetCalls = 0;
mockRive.stop = () => {
stopCalls++;
};
mockRive.reset = () => {
resetCalls++;
};
// Simulate Rive load
mockRive.simulateLoad();
await settled();
// Add cleanup handler directly
const cleanupRive = () => {
if (mockRive) {
mockRive.stop();
mockRive.reset();
}
};
// Trigger cleanup
cleanupRive();
await settled();
assert.strictEqual(stopCalls, 1, 'Stop was called on destroy');
assert.strictEqual(resetCalls, 1, 'Reset was called on destroy');
assert.strictEqual(mockRive.interval, null, 'Animation interval was cleared');
});
🧰 Tools
🪛 ESLint

[error] 190-190: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 220-220: Unexpected assert.equal. Use assert.strictEqual, assert.deepEqual, or assert.propEqual.

(qunit/no-assert-equal)


[error] 221-221: Unexpected assert.equal. Use assert.strictEqual, assert.deepEqual, or assert.propEqual.

(qunit/no-assert-equal)


[error] 222-222: Unexpected assert.equal. Use assert.strictEqual, assert.deepEqual, or assert.propEqual.

(qunit/no-assert-equal)

Comment on lines 123 to 178
test('it handles hover events', async function (assert) {
await render(hbs`<GleamLogo />`);

// Get the container and create mock Rive instance
const container = document.querySelector('.gleam-logo-container') as HTMLElement;
const canvas = container?.querySelector('canvas');
assert.ok(canvas, 'Canvas element exists');

// Create and attach mock Rive instance
const mockRive = new MockRive({ canvas: canvas as HTMLCanvasElement });
(container as any).__riveInstance = mockRive;

// Track calls to play and reset
let playCalls = 0;
let resetCalls = 0;

// Override the mock methods before simulating load
mockRive.play = (stateMachineName: string) => {
playCalls++;
assert.equal(stateMachineName, 'State Machine 1', 'Hover plays correct state machine');
};

mockRive.reset = () => {
resetCalls++;
};

// Simulate Rive load
mockRive.simulateLoad();
await settled();

// Add event handler directly to the container
const handleMouseEnter = () => {
if (mockRive) {
const stateMachines = mockRive.stateMachineNames;
if (stateMachines && stateMachines.length > 0) {
const stateMachineName = 'State Machine 1';
if (stateMachines.includes(stateMachineName)) {
mockRive.reset();
mockRive.play(stateMachineName);
}
}
}
};

container.addEventListener('mouseenter', handleMouseEnter);

// Trigger the event
container.dispatchEvent(new MouseEvent('mouseenter'));
await settled();

assert.equal(playCalls, 1, 'Play was called once on hover');
assert.equal(resetCalls, 1, 'Reset was called once on hover');

// Clean up
container.removeEventListener('mouseenter', handleMouseEnter);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve test structure and assertions for hover events test.

The hover event test has several areas for improvement:

  1. It manually recreates event handling logic that should be testing the component's behavior
  2. It uses assert.equal instead of assert.strictEqual as recommended by QUnit best practices
  3. There are missing blank lines between logical code blocks
  test('it handles hover events', async function (assert) {
    await render(hbs`<GleamLogo />`);

    // Get the container and create mock Rive instance
    const container = document.querySelector('.gleam-logo-container') as HTMLElement;
    const canvas = container?.querySelector('canvas');
    assert.ok(canvas, 'Canvas element exists');

    // Create and attach mock Rive instance
    const mockRive = new MockRive({ canvas: canvas as HTMLCanvasElement });
    (container as any).__riveInstance = mockRive;

    // Track calls to play and reset
    let playCalls = 0;
    let resetCalls = 0;

    // Override the mock methods before simulating load
    mockRive.play = (stateMachineName: string) => {
      playCalls++;
-      assert.equal(stateMachineName, 'State Machine 1', 'Hover plays correct state machine');
+      assert.strictEqual(stateMachineName, 'State Machine 1', 'Hover plays correct state machine');
    };

    mockRive.reset = () => {
      resetCalls++;
    };

    // Simulate Rive load
    mockRive.simulateLoad();
    await settled();

    // Add event handler directly to the container
    const handleMouseEnter = () => {
      if (mockRive) {
        const stateMachines = mockRive.stateMachineNames;
        if (stateMachines && stateMachines.length > 0) {
+          
          const stateMachineName = 'State Machine 1';
          if (stateMachines.includes(stateMachineName)) {
+            
            mockRive.reset();
            mockRive.play(stateMachineName);
          }
        }
      }
    };

    container.addEventListener('mouseenter', handleMouseEnter);

    // Trigger the event
    container.dispatchEvent(new MouseEvent('mouseenter'));
    await settled();

-    assert.equal(playCalls, 1, 'Play was called once on hover');
-    assert.equal(resetCalls, 1, 'Reset was called once on hover');
+    assert.strictEqual(playCalls, 1, 'Play was called once on hover');
+    assert.strictEqual(resetCalls, 1, 'Reset was called once on hover');

    // Clean up
    container.removeEventListener('mouseenter', handleMouseEnter);
  });
📝 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
test('it handles hover events', async function (assert) {
await render(hbs`<GleamLogo />`);
// Get the container and create mock Rive instance
const container = document.querySelector('.gleam-logo-container') as HTMLElement;
const canvas = container?.querySelector('canvas');
assert.ok(canvas, 'Canvas element exists');
// Create and attach mock Rive instance
const mockRive = new MockRive({ canvas: canvas as HTMLCanvasElement });
(container as any).__riveInstance = mockRive;
// Track calls to play and reset
let playCalls = 0;
let resetCalls = 0;
// Override the mock methods before simulating load
mockRive.play = (stateMachineName: string) => {
playCalls++;
assert.equal(stateMachineName, 'State Machine 1', 'Hover plays correct state machine');
};
mockRive.reset = () => {
resetCalls++;
};
// Simulate Rive load
mockRive.simulateLoad();
await settled();
// Add event handler directly to the container
const handleMouseEnter = () => {
if (mockRive) {
const stateMachines = mockRive.stateMachineNames;
if (stateMachines && stateMachines.length > 0) {
const stateMachineName = 'State Machine 1';
if (stateMachines.includes(stateMachineName)) {
mockRive.reset();
mockRive.play(stateMachineName);
}
}
}
};
container.addEventListener('mouseenter', handleMouseEnter);
// Trigger the event
container.dispatchEvent(new MouseEvent('mouseenter'));
await settled();
assert.equal(playCalls, 1, 'Play was called once on hover');
assert.equal(resetCalls, 1, 'Reset was called once on hover');
// Clean up
container.removeEventListener('mouseenter', handleMouseEnter);
});
test('it handles hover events', async function (assert) {
await render(hbs`<GleamLogo />`);
// Get the container and create mock Rive instance
const container = document.querySelector('.gleam-logo-container') as HTMLElement;
const canvas = container?.querySelector('canvas');
assert.ok(canvas, 'Canvas element exists');
// Create and attach mock Rive instance
const mockRive = new MockRive({ canvas: canvas as HTMLCanvasElement });
(container as any).__riveInstance = mockRive;
// Track calls to play and reset
let playCalls = 0;
let resetCalls = 0;
// Override the mock methods before simulating load
mockRive.play = (stateMachineName: string) => {
playCalls++;
assert.strictEqual(stateMachineName, 'State Machine 1', 'Hover plays correct state machine');
};
mockRive.reset = () => {
resetCalls++;
};
// Simulate Rive load
mockRive.simulateLoad();
await settled();
// Add event handler directly to the container
const handleMouseEnter = () => {
if (mockRive) {
const stateMachines = mockRive.stateMachineNames;
if (stateMachines && stateMachines.length > 0) {
const stateMachineName = 'State Machine 1';
if (stateMachines.includes(stateMachineName)) {
mockRive.reset();
mockRive.play(stateMachineName);
}
}
}
};
container.addEventListener('mouseenter', handleMouseEnter);
// Trigger the event
container.dispatchEvent(new MouseEvent('mouseenter'));
await settled();
assert.strictEqual(playCalls, 1, 'Play was called once on hover');
assert.strictEqual(resetCalls, 1, 'Reset was called once on hover');
// Clean up
container.removeEventListener('mouseenter', handleMouseEnter);
});
🧰 Tools
🪛 ESLint

[error] 133-133: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 142-142: Unexpected assert.equal. Use assert.strictEqual, assert.deepEqual, or assert.propEqual.

(qunit/no-assert-equal)


[error] 157-163: Expected blank line before this statement.

(padding-line-between-statements)


[error] 159-162: Expected blank line before this statement.

(padding-line-between-statements)


[error] 173-173: Unexpected assert.equal. Use assert.strictEqual, assert.deepEqual, or assert.propEqual.

(qunit/no-assert-equal)


[error] 174-174: Unexpected assert.equal. Use assert.strictEqual, assert.deepEqual, or assert.propEqual.

(qunit/no-assert-equal)

Comment on lines 14 to 76
// Mock Rive class for testing
class MockRive {
@tracked canvas: HTMLCanvasElement;
@tracked stateMachineNames: string[] = ['State Machine 1', 'State Machine 2'];
@tracked isLoaded = false;
private _listeners: Map<string, Function[]> = new Map();
private _interval: number | null = null;

get interval(): number | null {
return this._interval;
}

constructor(options: { canvas: HTMLCanvasElement }) {
this.canvas = options.canvas;
}

reset() {
// Mock reset implementation
}

play(stateMachineName: string) {
// Mock play implementation
}

stop() {
// Mock stop implementation
}

stateMachineInputs(stateMachineName: string) {
return [];
}

on(event: string, callback: Function) {
if (!this._listeners.has(event)) {
this._listeners.set(event, []);
}
this._listeners.get(event)?.push(callback);
}

off(event: string, callback: Function) {
const listeners = this._listeners.get(event);
if (listeners) {
const index = listeners.indexOf(callback);
if (index > -1) {
listeners.splice(index, 1);
}
}
}

// Helper method to trigger events
triggerEvent(event: string, ...args: any[]) {
const listeners = this._listeners.get(event);
if (listeners) {
listeners.forEach((callback) => callback(...args));
}
}

// Helper method to simulate load
simulateLoad() {
this.isLoaded = true;
this.triggerEvent('load');
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve MockRive class with proper TypeScript patterns.

The MockRive class has several issues flagged by static analysis:

  1. Using the generic Function type instead of specific function signatures
  2. Using any type in method parameters
  3. Inconsistent member ordering
  4. Missing blank lines between statements

These issues impact code quality and type safety.

// Mock Rive class for testing
class MockRive {
  @tracked canvas: HTMLCanvasElement;
+  @tracked isLoaded = false;
  @tracked stateMachineNames: string[] = ['State Machine 1', 'State Machine 2'];
-  @tracked isLoaded = false;
-  private _listeners: Map<string, Function[]> = new Map();
-  private _interval: number | null = null;
+  private _listeners: Map<string, Array<(...args: unknown[]) => void>> = new Map();
+  private _interval: number | null = null;

  get interval(): number | null {
    return this._interval;
  }

  constructor(options: { canvas: HTMLCanvasElement }) {
    this.canvas = options.canvas;
  }

  reset() {
    // Mock reset implementation
  }

  play(stateMachineName: string) {
    // Mock play implementation
  }

  stop() {
    // Mock stop implementation
  }

  stateMachineInputs(stateMachineName: string) {
    return [];
  }

-  on(event: string, callback: Function) {
+  on(event: string, callback: (...args: unknown[]) => void) {
    if (!this._listeners.has(event)) {
      this._listeners.set(event, []);
    }
+    
    this._listeners.get(event)?.push(callback);
  }

-  off(event: string, callback: Function) {
+  off(event: string, callback: (...args: unknown[]) => void) {
    const listeners = this._listeners.get(event);
    if (listeners) {
      const index = listeners.indexOf(callback);
+      
      if (index > -1) {
        listeners.splice(index, 1);
      }
    }
  }

  // Helper method to trigger events
-  triggerEvent(event: string, ...args: any[]) {
+  triggerEvent(event: string, ...args: unknown[]) {
    const listeners = this._listeners.get(event);
    if (listeners) {
+      
      listeners.forEach((callback) => callback(...args));
    }
  }

  // Helper method to simulate load
  simulateLoad() {
    this.isLoaded = true;
    this.triggerEvent('load');
  }
}
📝 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
// Mock Rive class for testing
class MockRive {
@tracked canvas: HTMLCanvasElement;
@tracked stateMachineNames: string[] = ['State Machine 1', 'State Machine 2'];
@tracked isLoaded = false;
private _listeners: Map<string, Function[]> = new Map();
private _interval: number | null = null;
get interval(): number | null {
return this._interval;
}
constructor(options: { canvas: HTMLCanvasElement }) {
this.canvas = options.canvas;
}
reset() {
// Mock reset implementation
}
play(stateMachineName: string) {
// Mock play implementation
}
stop() {
// Mock stop implementation
}
stateMachineInputs(stateMachineName: string) {
return [];
}
on(event: string, callback: Function) {
if (!this._listeners.has(event)) {
this._listeners.set(event, []);
}
this._listeners.get(event)?.push(callback);
}
off(event: string, callback: Function) {
const listeners = this._listeners.get(event);
if (listeners) {
const index = listeners.indexOf(callback);
if (index > -1) {
listeners.splice(index, 1);
}
}
}
// Helper method to trigger events
triggerEvent(event: string, ...args: any[]) {
const listeners = this._listeners.get(event);
if (listeners) {
listeners.forEach((callback) => callback(...args));
}
}
// Helper method to simulate load
simulateLoad() {
this.isLoaded = true;
this.triggerEvent('load');
}
}
// Mock Rive class for testing
class MockRive {
@tracked canvas: HTMLCanvasElement;
@tracked isLoaded = false;
@tracked stateMachineNames: string[] = ['State Machine 1', 'State Machine 2'];
private _listeners: Map<string, Array<(...args: unknown[]) => void>> = new Map();
private _interval: number | null = null;
get interval(): number | null {
return this._interval;
}
constructor(options: { canvas: HTMLCanvasElement }) {
this.canvas = options.canvas;
}
reset() {
// Mock reset implementation
}
play(stateMachineName: string) {
// Mock play implementation
}
stop() {
// Mock stop implementation
}
stateMachineInputs(stateMachineName: string) {
return [];
}
on(event: string, callback: (...args: unknown[]) => void) {
if (!this._listeners.has(event)) {
this._listeners.set(event, []);
}
this._listeners.get(event)?.push(callback);
}
off(event: string, callback: (...args: unknown[]) => void) {
const listeners = this._listeners.get(event);
if (listeners) {
const index = listeners.indexOf(callback);
if (index > -1) {
listeners.splice(index, 1);
}
}
}
// Helper method to trigger events
triggerEvent(event: string, ...args: unknown[]) {
const listeners = this._listeners.get(event);
if (listeners) {
listeners.forEach((callback) => callback(...args));
}
}
// Helper method to simulate load
simulateLoad() {
this.isLoaded = true;
this.triggerEvent('load');
}
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 19-19: Don't use 'Function' as a type.

Prefer explicitly define the function shape. This type accepts any function-like value, which can be a common source of bugs.

(lint/complexity/noBannedTypes)


[error] 46-46: Don't use 'Function' as a type.

Prefer explicitly define the function shape. This type accepts any function-like value, which can be a common source of bugs.

(lint/complexity/noBannedTypes)


[error] 53-53: Don't use 'Function' as a type.

Prefer explicitly define the function shape. This type accepts any function-like value, which can be a common source of bugs.

(lint/complexity/noBannedTypes)

🪛 ESLint

[error] 18-18: Member isLoaded should be declared before member stateMachineNames.

(@typescript-eslint/member-ordering)


[error] 19-19: Member _listeners should be declared before member isLoaded.

(@typescript-eslint/member-ordering)


[error] 19-19: The Function type accepts any function-like value.
Prefer explicitly defining any function parameters and return type.

(@typescript-eslint/no-unsafe-function-type)


[error] 20-20: Member _interval should be declared before member _listeners.

(@typescript-eslint/member-ordering)


[error] 26-28: Member constructor should be declared before all get definitions.

(@typescript-eslint/member-ordering)


[error] 34-36: Member play should be declared before member reset.

(@typescript-eslint/member-ordering)


[error] 34-34: 'stateMachineName' is defined but never used. Allowed unused args must match /^_/u.

(@typescript-eslint/no-unused-vars)


[error] 42-44: Member stateMachineInputs should be declared before member stop.

(@typescript-eslint/member-ordering)


[error] 42-42: 'stateMachineName' is defined but never used. Allowed unused args must match /^_/u.

(@typescript-eslint/no-unused-vars)


[error] 46-51: Member on should be declared before member stateMachineInputs.

(@typescript-eslint/member-ordering)


[error] 46-46: The Function type accepts any function-like value.
Prefer explicitly defining any function parameters and return type.

(@typescript-eslint/no-unsafe-function-type)


[error] 50-50: Expected blank line before this statement.

(padding-line-between-statements)


[error] 53-61: Member off should be declared before member on.

(@typescript-eslint/member-ordering)


[error] 53-53: The Function type accepts any function-like value.
Prefer explicitly defining any function parameters and return type.

(@typescript-eslint/no-unsafe-function-type)


[error] 55-60: Expected blank line before this statement.

(padding-line-between-statements)


[error] 57-59: Expected blank line before this statement.

(padding-line-between-statements)


[error] 64-64: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 66-68: Expected blank line before this statement.

(padding-line-between-statements)

@Arpan-206
Copy link
Contributor Author

Copy link
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: 4

🧹 Nitpick comments (4)
app/components/gleam-logo.ts (4)

6-16: Consider simplifying the component interface.

The interface includes a default block, but there's no use of yielded content in this component. This could be confusing for developers looking to use this component.

interface GleamLogoSignature {
  Element: HTMLDivElement;

  Args: {
    height: number;
  };
-
-  Blocks: {
-    default: [];
-  };
}

50-57: Consider making canvas dimensions dynamic and responsive.

Currently, the canvas is created with a fixed base size of 400x400px regardless of the container size. This might lead to unnecessary memory usage on smaller screens or insufficient quality on larger screens.

-      const baseSize = 400; // Base size for quality
-      canvas.width = baseSize;
-      canvas.height = baseSize;
+      // Set canvas size based on container or device pixel ratio
+      const pixelRatio = window.devicePixelRatio || 1;
+      const size = Math.min(this.args.height, 200) * pixelRatio;
+      canvas.width = size;
+      canvas.height = size;

      // Let the canvas scale naturally within its container
      canvas.style.width = '100%';
      canvas.style.height = '100%';

79-92: Extract hover simulation logic to a separate method.

The hover state simulation logic is embedded in the onLoad callback, making it harder to read and maintain.

+ @action
+ simulateHoverEffect(stateMachineName: string): void {
+   if (!this.riveInstance) return;
+   
+   const inputs = this.riveInstance.stateMachineInputs(stateMachineName);
+   
+   if (inputs) {
+     inputs.forEach(input => {
+       if (input.name.toLowerCase().includes('hover')) {
+         input.value = true;
+         
+         // Set timeout to trigger hover out after 1 second
+         setTimeout(() => {
+           if (this.riveInstance) {
+             input.value = false;
+           }
+         }, 1000);
+       }
+     });
+   }
+ }

// Then in the onLoad callback:
onLoad: () => {
  if (this.riveInstance) {
    // Initial resize
    this.riveInstance.resizeDrawingSurfaceToCanvas();
    
-   const inputs = this.riveInstance.stateMachineInputs('State Machine 1');
-   
-   // Try to trigger hover state
-   if (inputs) {
-     inputs.forEach(input => {
-       if (input.name.toLowerCase().includes('hover')) {
-         input.value = true;
-         
-         // Set timeout to trigger hover out after 1 second
-         setTimeout(() => {
-           if (this.riveInstance) {
-             input.value = false;
-           }
-         }, 1000);
-       }
-     });
-   }
+   this.simulateHoverEffect('State Machine 1');
  }
},
🧰 Tools
🪛 ESLint

[error] 80-80: Replace input with (input)

(prettier/prettier)


[error] 83-83: Delete ··················

(prettier/prettier)


62-71: Make animation file path configurable.

The animation file path is hardcoded, making it less flexible for future use cases. Consider making it configurable through component arguments.

interface GleamLogoSignature {
  Element: HTMLDivElement;

  Args: {
    height: number;
+   animationPath?: string;
  };
}

// ...

this.riveInstance = new Rive({
-  src: '/assets/animations/gleam_logo_animation.riv',
+  src: this.args.animationPath || '/assets/animations/gleam_logo_animation.riv',
  canvas: canvas,
  // ...
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between d007423 and 257025c.

📒 Files selected for processing (1)
  • app/components/gleam-logo.ts (1 hunks)
🧰 Additional context used
🪛 ESLint
app/components/gleam-logo.ts

[error] 26-26: Expected blank line before this statement.

(padding-line-between-statements)


[error] 80-80: Replace input with (input)

(prettier/prettier)


[error] 83-83: Delete ··················

(prettier/prettier)

🔇 Additional comments (1)
app/components/gleam-logo.ts (1)

23-27:

❓ Verification inconclusive

Ensure aspect ratio preservation is intentional.

The container style sets both width and height to the same value, enforcing a square aspect ratio. Make sure this matches the aspect ratio of the animation file to avoid distortion.


🏁 Script executed:

#!/bin/bash
# Check if the animation file is square or if it has a different aspect ratio
curl -sI https://github.com/codecrafters-io/frontend/raw/main/public/assets/animations/gleam_logo_animation.riv | grep -i content-length
echo "Note: This checks the file size, but we'd need additional tooling to inspect the Rive file dimensions"

Length of output: 354


Verify Rive animation matches square container
The containerStyle getter forces a 1:1 aspect ratio by setting equal width and height. Confirm the gleam_logo_animation.riv asset is square to prevent distortion. If it’s not, consider computing width based on its actual aspect ratio or removing the square constraint.

• File: app/components/gleam-logo.ts
Lines 23–27

🧰 Tools
🪛 ESLint

[error] 26-26: Expected blank line before this statement.

(padding-line-between-statements)

Comment on lines 85 to 90
setTimeout(() => {
if (this.riveInstance) {
input.value = false;
}
}, 1000);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Store and clean up the hover timeout.

The hover timeout is not stored or cleared when the component is destroyed, which could lead to errors if the component is removed before the timeout completes.

@tracked riveInstance: Rive | null = null;
container: HTMLElement | null = null;
animationInterval: number | null = null;
+ hoverTimeout: number | null = null;

@action
cleanupRive() {
  if (this.animationInterval) {
    clearInterval(this.animationInterval);
    this.animationInterval = null;
  }

+  if (this.hoverTimeout) {
+    clearTimeout(this.hoverTimeout);
+    this.hoverTimeout = null;
+  }

  if (this.riveInstance) {
    this.riveInstance.stop();
    this.riveInstance = null;
  }
}

// Then when setting the timeout:
- setTimeout(() => {
+ this.hoverTimeout = window.setTimeout(() => {
  if (this.riveInstance) {
    input.value = false;
  }
}, 1000);
📝 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
setTimeout(() => {
if (this.riveInstance) {
input.value = false;
}
}, 1000);
}
// In app/components/gleam-logo.ts
// Add a tracked property to hold the timeout ID
@tracked riveInstance: Rive | null = null;
container: HTMLElement | null = null;
animationInterval: number | null = null;
@tracked hoverTimeout: number | null = null;
@action
cleanupRive() {
if (this.animationInterval) {
clearInterval(this.animationInterval);
this.animationInterval = null;
}
if (this.hoverTimeout) {
clearTimeout(this.hoverTimeout);
this.hoverTimeout = null;
}
if (this.riveInstance) {
this.riveInstance.stop();
this.riveInstance = null;
}
}
// … elsewhere in your hover‐leave handler …
// Replace the bare setTimeout call with a stored timeout
this.hoverTimeout = window.setTimeout(() => {
if (this.riveInstance) {
input.value = false;
}
}, 1000);

Comment on lines 96 to 98
} catch (error: unknown) {
console.error('Error setting up Rive:', error);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Implement better error handling for Rive initialization failures.

Currently, errors are only logged to the console, which isn't sufficient for production environments. Consider sending these errors to a monitoring service or providing a fallback UI.


🌐 Web query:

Does Ember have a built-in error reporting service or recommended error handling pattern for components?

💡 Result:

Ember Error Reporting and Error Handling Patterns

Ember does not provide a built-in error reporting service directly integrated into the framework, but it does offer several built-in hooks and patterns for handling errors in components and across the application.

Global Error Handling

Ember provides a global error handling mechanism through the Ember.onerror hook. This function catches uncaught errors thrown anywhere within your Ember application, allowing you to implement custom logic for logging, user notification, or reporting to external services. For promise-related errors, Ember exposes the Ember.RSVP.on('error', handler) hook, which allows you to catch unhandled promise rejections globally[2][6].

Recommended Error Handling Patterns

  • Ember.onerror: Define a global error handler to catch and process uncaught exceptions. This can be used for logging and reporting errors.
  • RSVP Promise Errors: Use Ember.RSVP.configure('onerror', handler) to capture errors from unhandled promise rejections[2][6].
  • Route-Specific Errors: Handle errors in route actions such as the error action within a route, which will be triggered when errors occur during transitions.
  • Try-Catch Blocks: Use try-catch blocks in your methods or actions for more granular error handling, especially in asynchronous code[6].
  • Display User-Friendly Messages: Normalize errors and display clear, actionable error messages to users.

Component Error Handling

While Ember does not offer component-level error “boundaries” like React, you can use try-catch logic within component actions or lifecycle hooks. For more advanced use, some community addons and patterns facilitate error propagation and management within components[6].

Error Reporting

For reporting errors to external services, Ember supports integration with many third-party error monitoring platforms, such as Sentry, Rollbar, and Airbrake:

  • Sentry: Official Sentry SDK for Ember (@sentry/ember) automatically captures uncaught errors and exceptions. After installing the addon, you initialize Sentry early in your application’s startup file, and errors will be reported automatically[3][8]. You can also manually report errors using Sentry.captureException() within your code[4].
  • Other Services: Platforms like Rollbar, Errorception, and ExceptionHub are also compatible with Ember via global hooks[2].

Community Addons

There are several Ember addons to enhance error handling and reporting. For example, ember-error-handler centralizes error capturing and lets you route errors to logging services, custom error pages, or whatever logic you need[1][9].

Summary Table: Ember Error Handling Approaches

Pattern/Tool Purpose Built-in or Addon Usage
Ember.onerror Catch uncaught errors app-wide Built-in Global handler for logging/reporting
RSVP.on('error') Handle uncaught promise rejections Built-in Promise rejection handling
Route error action Catch errors in specific route contexts Built-in Show route-level error UI
Sentry/Rollbar Report errors to external services Third-party addon Error monitoring and alerting
ember-error-handler Centralize and customize error handling Community addon Custom logic, UI, or remote logging

Conclusion

Ember does not have an all-in-one built-in error reporting service, but it provides robust primitives (Ember.onerror, RSVP hooks, and route error actions) for handling and customizing error behavior. For error reporting, the Ember ecosystem supports seamless integration with tools like Sentry via official and community-maintained addons, and there are established patterns for routing errors and providing user feedback in components and across the app[2][6][8][9].

Citations:


Enhance Rive Initialization Error Handling

Ember doesn’t include a turnkey error-reporting service, but you can hook into its global error APIs (or integrate Sentry/Rollbar) and fall back to a safe UI when initialization fails.

• Location to address:
– app/components/gleam-logo.ts (lines 96–98)

• Suggested changes:

     } catch (error: unknown) {
-      console.error('Error setting up Rive:', error);
+      // 1) Report the error globally or to an external service
+      Ember.onerror?.(error);
+      // If using Sentry add-on:
+      // import { captureException } from '@sentry/ember';
+      // captureException(error);
+
+      // 2) Trigger a fallback UI state
+      this.hasLoadError = true;
     }

• Next steps:

  1. Add @tracked hasLoadError = false; in the component.
  2. Update the template to render a static logo (or message) when hasLoadError is true.
  3. Initialise and configure your chosen monitoring addon (e.g. @sentry/ember) and/or implement an Ember.onerror hook in app/app.js for centralized reporting.
📝 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
} catch (error: unknown) {
console.error('Error setting up Rive:', error);
}
} catch (error: unknown) {
// 1) Report the error globally or to an external service
Ember.onerror?.(error);
// If using Sentry add-on:
// import { captureException } from '@sentry/ember';
// captureException(error);
// 2) Trigger a fallback UI state
this.hasLoadError = true;
}

Copy link
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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 257025c and d798652.

📒 Files selected for processing (2)
  • app/components/gleam-logo.ts (1 hunks)
  • tests/integration/components/gleam-logo-test.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/components/gleam-logo.ts
🧰 Additional context used
🪛 Biome (1.9.4)
tests/integration/components/gleam-logo-test.js

[error] 77-77: The constructor should not return a value.

The constructor is here:

Returning a value from a constructor is ignored.

(lint/correctness/noConstructorReturn)

🔇 Additional comments (2)
tests/integration/components/gleam-logo-test.js (2)

86-104: Good implementation of the rendering test

The rendering test is thorough and properly checks all the expected properties of both the container and canvas elements. It verifies dimensions, styles, and the existence of elements, which are all important aspects of the component's visual representation.


7-65: Well-structured MockRive implementation

The mock implementation of the Rive animation library provides all the necessary methods for testing while maintaining a clean separation of concerns. The event handling methods, state machine inputs, and load simulation are implemented appropriately.

Comment on lines +72 to +79
window.Rive = class MockRiveConstructor {
constructor(options) {
const instance = new MockRive(options);
this._onLoadCallback = options.onLoad;

return instance;
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix the constructor return pattern in MockRiveConstructor

The constructor is returning a value, which is problematic in JavaScript. Constructors should initialize the object being created, not return a different object.

window.Rive = class MockRiveConstructor {
  constructor(options) {
-   const instance = new MockRive(options);
    this._onLoadCallback = options.onLoad;
-
-   return instance;
+   Object.assign(this, new MockRive(options));
  }
};

This approach ensures that this is properly initialized with the properties from MockRive while maintaining the constructor pattern's integrity.

📝 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
window.Rive = class MockRiveConstructor {
constructor(options) {
const instance = new MockRive(options);
this._onLoadCallback = options.onLoad;
return instance;
}
};
// tests/integration/components/gleam-logo-test.js
window.Rive = class MockRiveConstructor {
constructor(options) {
this._onLoadCallback = options.onLoad;
Object.assign(this, new MockRive(options));
}
};
🧰 Tools
🪛 Biome (1.9.4)

[error] 77-77: The constructor should not return a value.

The constructor is here:

Returning a value from a constructor is ignored.

(lint/correctness/noConstructorReturn)

Copy link
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)
app/components/gleam-logo.ts (1)

87-92: 🛠️ Refactor suggestion

Store and clean up the hover timeout to prevent memory leaks.

The timeout created to reset the hover state isn't tracked or cleaned up if the component is destroyed before the timeout completes. This could lead to memory leaks or errors accessing destroyed objects.

export default class GleamLogoComponent extends Component<GleamLogoSignature> {
  animationInterval: number | null = null;
  container: HTMLElement | null = null;
  @tracked riveInstance: Rive | null = null;
+ hoverTimeout: number | null = null;

  @action
  cleanupRive() {
    if (this.animationInterval) {
      clearInterval(this.animationInterval);
      this.animationInterval = null;
    }

+   if (this.hoverTimeout) {
+     clearTimeout(this.hoverTimeout);
+     this.hoverTimeout = null;
+   }

    if (this.riveInstance) {
      // ...

And in the hover reset code:

  // Set timeout to trigger hover out after 1 second
- setTimeout(() => {
+ this.hoverTimeout = window.setTimeout(() => {
    if (this.riveInstance) {
      input.value = false;
    }
  }, 1000);
🧹 Nitpick comments (3)
app/components/gleam-logo.ts (3)

73-77: Add window resize handling to ensure proper animation display.

The animation is sized correctly on initial load but won't adjust if the window or container is resized after initialization. This could lead to display issues on responsive layouts.

onLoad: () => {
  if (this.riveInstance) {
    // Initial resize
    this.riveInstance.resizeDrawingSurfaceToCanvas();

+   // Handle window resize events
+   const handleResize = () => {
+     if (this.riveInstance) {
+       this.riveInstance.resizeDrawingSurfaceToCanvas();
+     }
+   };
+
+   window.addEventListener('resize', handleResize);
+
+   // Store resize handler for cleanup
+   this._resizeHandler = handleResize;
  }

And add cleanup to the cleanupRive method:

@action
cleanupRive() {
  // Existing cleanup...

+ // Remove resize event listener
+ if (this._resizeHandler) {
+   window.removeEventListener('resize', this._resizeHandler);
+   this._resizeHandler = null;
+ }
}

Don't forget to add the property declaration:

export default class GleamLogoComponent extends Component<GleamLogoSignature> {
  animationInterval: number | null = null;
  container: HTMLElement | null = null;
  @tracked riveInstance: Rive | null = null;
+ _resizeHandler: (() => void) | null = null;

78-94: Improve input validation for hover state handling.

The current implementation doesn't validate if a suitable hover input exists before trying to manipulate it. Without proper validation, this could lead to runtime errors if the animation file changes.

const inputs = this.riveInstance.stateMachineInputs('State Machine 1');

// Try to trigger hover state
if (inputs) {
+ // Find a hover input if available
+ const hoverInput = inputs.find(input => 
+   input.name.toLowerCase().includes('hover') && 
+   typeof input.value === 'boolean'
+ );
+ 
+ if (hoverInput) {
+   // Set hover state and reset after delay
+   hoverInput.value = true;
+ 
+   // Set timeout to trigger hover out after 1 second  
+   this.hoverTimeout = window.setTimeout(() => {
+     if (this.riveInstance) {
+       hoverInput.value = false;
+     }
+   }, 1000);
+ }
- inputs.forEach((input) => {
-   if (input.name.toLowerCase().includes('hover')) {
-     input.value = true;
-     
-     // Set timeout to trigger hover out after 1 second
-     setTimeout(() => {
-       if (this.riveInstance) {
-         input.value = false;
-       }
-     }, 1000);
-   }
- });
}

24-29: Consider using more explicit responsive sizing approach.

The current container sizing logic uses inline styles with a fixed aspect ratio. Consider using CSS class-based approaches for more maintainable responsive sizing.

You could define a CSS class for the container in a separate stylesheet and use standard responsive approaches instead of computing inline styles. This would make the styling more maintainable and consistent with other components.

Example of using CSS classes instead:

get containerStyle(): string {
  // Ensure minimum size on mobile while maintaining aspect ratio
  const minSize = Math.min(this.args.height, 200);

  return `height: ${minSize}px; width: ${minSize}px; max-width: 100%;`;
}

Could become:

{{! in template }}
<div 
  class="gleam-logo-container"
  style="--logo-size: {{this.logoSize}}px;"
  {{did-insert this.handleDidInsert}}
  {{will-destroy this.cleanupRive}}
>
</div>

With CSS:

.gleam-logo-container {
  height: var(--logo-size, 144px);
  width: var(--logo-size, 144px);
  max-width: 100%;
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between d798652 and 8052924.

📒 Files selected for processing (1)
  • app/components/gleam-logo.ts (1 hunks)
🔇 Additional comments (4)
app/components/gleam-logo.ts (4)

7-17: LGTM: Well-structured component signature.

The component signature is well-defined with appropriate types for the element, arguments, and blocks. This provides good TypeScript support and documentation.


31-42: LGTM: Good resource cleanup implementation.

The cleanupRive action properly handles resource cleanup by clearing intervals and stopping the Rive instance, which helps prevent memory leaks.


64-72: LGTM: Appropriate Rive initialization with good configuration options.

The Rive instance is initialized with proper options for the animation source, canvas, state machines, and layout. The use of Fit.Contain ensures the animation scales properly while maintaining its aspect ratio.


110-114: LGTM: Proper Glint registry registration.

The component is correctly registered in the Glint registry, which ensures proper TypeScript type checking and autocompletion when using this component in templates.

Copy link
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)
tests/integration/components/gleam-logo-test.js (1)

72-79: ⚠️ Potential issue

Fix the constructor return pattern in MockRiveConstructor

The constructor is returning a value, which is problematic in JavaScript. Constructors should initialize the object being created, not return a different object.

window.Rive = class MockRiveConstructor {
  constructor(options) {
-   const instance = new MockRive(options);
    this._onLoadCallback = options.onLoad;
-
-   return instance;
+   Object.assign(this, new MockRive(options));
  }
};

This approach ensures that this is properly initialized with the properties from MockRive while maintaining the constructor pattern's integrity.

🧰 Tools
🪛 Biome (1.9.4)

[error] 77-77: The constructor should not return a value.

The constructor is here:

Returning a value from a constructor is ignored.

(lint/correctness/noConstructorReturn)

🧹 Nitpick comments (1)
tests/integration/components/gleam-logo-test.js (1)

141-141: Update assertion message to match actual test

The assertion message refers to "after timeout" but the test is now using a mouseleave event instead of a timeout.

-  assert.false(hoverInput.value, 'Hover input is set to false after timeout');
+  assert.false(hoverInput.value, 'Hover input is set to false after mouseleave');
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 8052924 and 490791e.

📒 Files selected for processing (1)
  • tests/integration/components/gleam-logo-test.js (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
tests/integration/components/gleam-logo-test.js

[error] 77-77: The constructor should not return a value.

The constructor is here:

Returning a value from a constructor is ignored.

(lint/correctness/noConstructorReturn)

🪛 ESLint
tests/integration/components/gleam-logo-test.js

[error] 108-108: Do not call settled() right after a test helper that already calls it internally.

(ember/no-settled-after-test-helper)

🪛 GitHub Actions: Test
tests/integration/components/gleam-logo-test.js

[error] 108-108: ESLint: Do not call settled() right after a test helper that already calls it internally (ember/no-settled-after-test-helper)

🔇 Additional comments (1)
tests/integration/components/gleam-logo-test.js (1)

1-143: Good job implementing the Gleam logo animation tests

Overall, the test implementation for the Gleam logo animation component looks well-structured and comprehensive. The tests verify both the rendering functionality and the hover interaction behavior. The mock implementation of the Rive library is well designed to simulate the necessary behaviors for testing.

🧰 Tools
🪛 Biome (1.9.4)

[error] 77-77: The constructor should not return a value.

The constructor is here:

Returning a value from a constructor is ignored.

(lint/correctness/noConstructorReturn)

🪛 ESLint

[error] 108-108: Do not call settled() right after a test helper that already calls it internally.

(ember/no-settled-after-test-helper)

🪛 GitHub Actions: Test

[error] 108-108: ESLint: Do not call settled() right after a test helper that already calls it internally (ember/no-settled-after-test-helper)


test('it handles hover state correctly', async function (assert) {
await render(hbs`<GleamLogo />`);
await settled();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove unnecessary settled() call after render()

The render() helper already calls settled() internally, so this additional call is redundant and violates the ember/no-settled-after-test-helper ESLint rule.

  await render(hbs`<GleamLogo />`);
-  await settled();

Removing this line will fix the pipeline failure while maintaining the same test behavior.

📝 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
await settled();
await render(hbs`<GleamLogo />`);
🧰 Tools
🪛 ESLint

[error] 108-108: Do not call settled() right after a test helper that already calls it internally.

(ember/no-settled-after-test-helper)

🪛 GitHub Actions: Test

[error] 108-108: ESLint: Do not call settled() right after a test helper that already calls it internally (ember/no-settled-after-test-helper)

@Arpan-206 Arpan-206 force-pushed the arpan/cc-1698-add-rive-animation-for-gleam-icon branch from 490791e to 8603f7e Compare April 30, 2025 13:13
Copy link
Member

@rohitpaulk rohitpaulk left a comment

Choose a reason for hiding this comment

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

Notes added

@@ -0,0 +1,3 @@
<div {{did-insert this.handleDidInsert}} {{will-destroy this.cleanupRive}} style={{this.containerStyle}} ...attributes>
Copy link
Member

Choose a reason for hiding this comment

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

@Arpan-206 Why is the naming convention inconsistent here? did-insert uses this.handleDidInsert (correct), and will-destroy uses this.cleanupRive instead of this.handleWillDestroy?

Element: HTMLDivElement;

Args: {
height: number;
Copy link
Member

Choose a reason for hiding this comment

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

Let's try to avoid needing a number to be passed in and see if we can just have it fit the container / parent element


get containerStyle(): string {
// Ensure minimum size on mobile while maintaining aspect ratio
const minSize = Math.min(this.args.height, 200);
Copy link
Member

Choose a reason for hiding this comment

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

@Arpan-206 where does the "200" number come from?

const canvas = document.createElement('canvas');

// Set initial canvas size for high quality
const baseSize = 400; // Base size for quality
Copy link
Member

Choose a reason for hiding this comment

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

@Arpan-206 let's see if we can avoid this magic number too and just have it pick a sensible value (like based on pixel density & container height)

this.riveInstance = new Rive({
src: '/assets/animations/gleam_logo_animation.riv',
canvas: canvas,
stateMachines: 'State Machine 1',
Copy link
Member

Choose a reason for hiding this comment

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

It is mandatory to pass in state machine names? Will this work without it?

const inputs = this.riveInstance.stateMachineInputs('State Machine 1');

// Try to trigger hover state
if (inputs) {
Copy link
Member

Choose a reason for hiding this comment

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

Let's move most logic to the animatino file itself, and maybe name the animation / state machine onLoad

@@ -1,4 +1,6 @@
{{#if (eq @variant "color")}}
{{#if (and (eq @language.slug "gleam") (eq @variant "color"))}}
<GleamLogo @height={{144}} ...attributes />
Copy link
Member

Choose a reason for hiding this comment

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

@Arpan-206 I mentioned this before, let's move this out to the tracks page

Screenshot 2025-04-30 at 12 29 06

Copy link
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: 2

♻️ Duplicate comments (1)
app/components/gleam-logo.ts (1)

102-114: 🛠️ Refactor suggestion

Track and clean up the hover timeout.

The setTimeout for resetting hover state after 1 second is not stored or cleared if the component is destroyed, which could lead to errors accessing destroyed components.

export default class GleamLogoComponent extends Component<GleamLogoSignature> {
  // Existing properties
  container: HTMLElement | null = null;
  resizeObserver: ResizeObserver | null = null;
+ hoverTimeout: number | null = null;
  @tracked riveInstance: Rive | null = null;
  @tracked containerSize: number = 0;

  // Later in the component
  inputs.forEach((input) => {
    if (input.name.toLowerCase().includes('hover')) {
      input.value = true;

      // Set timeout to trigger hover out after 1 second
-     setTimeout(() => {
+     this.hoverTimeout = window.setTimeout(() => {
        if (this.riveInstance) {
          input.value = false;
        }
      }, 1000);
    }
  });
  
  // In handleWillDestroy
  @action
  handleWillDestroy() {
+   if (this.hoverTimeout) {
+     clearTimeout(this.hoverTimeout);
+     this.hoverTimeout = null;
+   }
  
    // Rest of cleanup code...
  }
🧹 Nitpick comments (6)
app/components/gleam-logo.ts (6)

21-21: Remove the unused animationInterval property.

This property is declared and cleaned up in handleWillDestroy but is never set or used in the component. Remove it to avoid confusion and improve code clarity.

-  animationInterval: number | null = null;

38-61: Add browser compatibility check for ResizeObserver.

ResizeObserver may not be supported in all browsers your application targets. Consider adding a feature check before using it and provide a fallback solution.

    // Set up ResizeObserver if no size is provided
    if (!this.args.size) {
      // Get initial size from parent
      const parentElement = element.parentElement;

      if (parentElement) {
        // Force a layout calculation to get accurate dimensions
        const parentRect = parentElement.getBoundingClientRect();
        this.containerSize = Math.min(parentRect.width, parentRect.height);

+       // Check if ResizeObserver is supported
+       if (typeof ResizeObserver !== 'undefined') {
          // Set up ResizeObserver to update the container size
          this.resizeObserver = new ResizeObserver((entries) => {
            for (const entry of entries) {
              const newSize = Math.min(entry.contentRect.width, entry.contentRect.height);

              if (newSize > 0) {
                this.containerSize = newSize;
              }
            }
          });

          // Only observe the immediate parent element
          this.resizeObserver.observe(parentElement);
+       }
      }
    }

87-87: Avoid hardcoding state machine name.

The state machine name 'State Machine 1' is hardcoded in multiple places. Consider extracting it to a constant or property to make the code more maintainable.

+  private readonly DEFAULT_STATE_MACHINE = 'State Machine 1';
+
   // Then use this constant throughout the component
-  stateMachines: 'State Machine 1',
+  stateMachines: this.DEFAULT_STATE_MACHINE,

   // And here too
-  const inputs = this.riveInstance.stateMachineInputs('State Machine 1');
+  const inputs = this.riveInstance.stateMachineInputs(this.DEFAULT_STATE_MACHINE);

131-134: Remove cleanup for unused animationInterval.

This cleanup code refers to an animationInterval property that is declared but never used. Consider removing it to improve code clarity.

  @action
  handleWillDestroy() {
-   if (this.animationInterval) {
-     clearInterval(this.animationInterval);
-     this.animationInterval = null;
-   }

    if (this.resizeObserver) {

93-97: Consider debouncing canvas resize operations.

If the parent container size changes frequently (like during window resize events), the resizeDrawingSurfaceToCanvas method might be called too often, which could hurt performance.

// Import at the top
+ import { debounce } from '@ember/runloop';

// Add a class property
+ resizeAnimationCanvas = debounce(this, () => {
+   if (this.riveInstance) {
+     this.riveInstance.resizeDrawingSurfaceToCanvas();
+   }
+ }, 100);

// Change the onLoad handler
onLoad: () => {
  if (this.riveInstance) {
    // Initial resize
-   this.riveInstance.resizeDrawingSurfaceToCanvas();
+   this.resizeAnimationCanvas();

27-31: Ensure containerStyle has a default size fallback.

The containerStyle getter uses the provided size or containerSize, but there's no fallback if both are undefined or 0, which could happen during initial render or in edge cases.

  get containerStyle(): string {
-   const size = this.args.size || this.containerSize;
+   const size = this.args.size || this.containerSize || 100; // Default to 100px if no size is available

    return `height: ${size}px; width: ${size}px; max-width: 100%; display: block;`;
  }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 5853d88 and 53f6401.

📒 Files selected for processing (3)
  • app/components/gleam-logo.hbs (1 hunks)
  • app/components/gleam-logo.ts (1 hunks)
  • app/components/language-logo.hbs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/components/gleam-logo.hbs
  • app/components/language-logo.hbs
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: test
🔇 Additional comments (2)
app/components/gleam-logo.ts (2)

1-6: Good job with proper imports and type registration.

The component correctly imports all necessary dependencies and properly registers itself in the Glint registry for type checking. This will help maintain type safety in your templates.

Also applies to: 148-152


7-18: Well-structured TypeScript interface definition.

The GleamLogoSignature interface follows good TypeScript practices, providing proper typing for the element, arguments, and blocks. The optional size parameter gives users flexibility in how they use the component.

Comment on lines 63 to 97
try {
const canvas = document.createElement('canvas');

// Calculate base size based on device pixel ratio and container size
const pixelRatio = window.devicePixelRatio || 1;
const containerSize = this.args.size || this.containerSize;
// Use 2x the container size for high quality, but cap at 4x for performance
const qualityMultiplier = Math.min(4, Math.max(2, pixelRatio));
const baseSize = Math.round(containerSize * qualityMultiplier);

canvas.width = baseSize;
canvas.height = baseSize;

// Let the canvas scale naturally within its container
canvas.style.width = '100%';
canvas.style.height = '100%';
canvas.style.display = 'block';
canvas.style.maxWidth = '100%'; // Ensure it doesn't overflow on mobile

element.appendChild(canvas);

this.riveInstance = new Rive({
src: '/assets/animations/gleam_logo_animation.riv',
canvas: canvas,
stateMachines: 'State Machine 1',
autoplay: true,
automaticallyHandleEvents: true,
layout: new Layout({
fit: Fit.Contain,
}),
onLoad: () => {
if (this.riveInstance) {
// Initial resize
this.riveInstance.resizeDrawingSurfaceToCanvas();

Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Improve error handling during canvas initialization.

The code correctly sets up the canvas for Rive animation with good pixel ratio handling. However, consider adding more specific error handling for cases like canvas creation failures or unsupported browsers.


🏁 Script executed:

#!/bin/bash
# Verify browser support for canvas operations
rg -n "createElement\('canvas'\)" app/
rg -n "getContext.*webgl" app/

Length of output: 166


Add robust error handling for canvas and Rive initialization

The current try { … } catch { … } block will catch all errors, but we should surface more specific failures (unsupported browsers or Rive load issues) so they can be diagnosed and potentially degraded gracefully.

• In app/components/gleam-logo.ts (around lines 63–97):

  • After creating the canvas, verify it supports drawing contexts:
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    if (!ctx) {
      console.warn('Canvas is not supported in this browser.');
      return;
    }
  • When instantiating Rive, add an onError callback to log or handle load failures:
      this.riveInstance = new Rive({
        src: '/assets/animations/gleam_logo_animation.riv',
        canvas,
        stateMachines: 'State Machine 1',
        autoplay: true,
        automaticallyHandleEvents: true,
        layout: new Layout({ fit: Fit.Contain }),
  •   onError: (error) => {
    
  •     console.error('Rive initialization error:', error);
    
  •   },
      onLoad: () => { … },
    });
    

This will ensure unsupported environments or asset/load errors don’t fail silently.

Copy link
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

🧹 Nitpick comments (5)
app/components/gleam-logo.ts (5)

21-25: Initialize containerSize with a sensible default.

While you're tracking the container size, consider initializing it with a reasonable default value to avoid potential rendering issues before the size is determined.

  resizeObserver: ResizeObserver | null = null;
  @tracked riveInstance: Rive | null = null;
- @tracked containerSize: number = 0;
+ @tracked containerSize: number = 100; // Default size until determined from parent
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 25-25: app/components/gleam-logo.ts#L25
Added line #L25 was not covered by tests


27-31: Add fallback for zero container size.

The current implementation might result in a zero-sized container if both args.size and containerSize are 0.

get containerStyle(): string {
-  const size = this.args.size || this.containerSize;
+  const size = this.args.size || this.containerSize || 100; // Fallback to prevent zero-sized container

  return `height: ${size}px; width: ${size}px; max-width: 100%; display: block;`;
}

67-72: Consider moving quality multiplier logic to a named method or constant.

The canvas sizing logic with the quality multiplier is a good approach for balancing quality and performance, but the magic numbers (2 and 4) lack context. Consider extracting this to a named method or constant with documentation.

+ // Quality multiplier bounds: min 2x for crisp rendering on standard displays, max 4x for performance on high-DPI displays
+ private readonly MIN_QUALITY_MULTIPLIER = 2;
+ private readonly MAX_QUALITY_MULTIPLIER = 4;

// In handleDidInsert:
const pixelRatio = window.devicePixelRatio || 1;
const containerSize = this.args.size || this.containerSize;
- // Use 2x the container size for high quality, but cap at 4x for performance
- const qualityMultiplier = Math.min(4, Math.max(2, pixelRatio));
+ const qualityMultiplier = Math.min(this.MAX_QUALITY_MULTIPLIER, Math.max(this.MIN_QUALITY_MULTIPLIER, pixelRatio));
const baseSize = Math.round(containerSize * qualityMultiplier);

84-99: Add event listeners for animation state changes.

While the basic Rive setup is good, consider adding event listeners for animation state changes to better manage the component state and potentially expose events to parent components.

this.riveInstance = new Rive({
  src: '/assets/animations/gleam_logo_animation.riv',
  canvas: canvas,
  stateMachines: 'Default',
  autoplay: true,
  automaticallyHandleEvents: true,
  layout: new Layout({
    fit: Fit.Contain,
  }),
  onLoad: async () => {
    if (this.riveInstance) {
      // Initial resize
      this.riveInstance.resizeDrawingSurfaceToCanvas();
+     
+     // Listen for state machine changes if needed
+     this.riveInstance.on('statechange', (event) => {
+       // Handle state changes if needed
+       // For example, you could expose these via an action passed as args
+       if (this.args.onStateChange) {
+         this.args.onStateChange(event);
+       }
+     });
    }
  },
});
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 97-97: app/components/gleam-logo.ts#L97
Added line #L97 was not covered by tests


21-21: Remove unused property.

The animationInterval property is declared but never assigned. If it's not being used, consider removing it.

- animationInterval: number | null = null;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 53f6401 and 711a877.

📒 Files selected for processing (1)
  • app/components/gleam-logo.ts (1 hunks)
🧰 Additional context used
🪛 GitHub Check: codecov/patch
app/components/gleam-logo.ts

[warning] 25-25: app/components/gleam-logo.ts#L25
Added line #L25 was not covered by tests


[warning] 97-97: app/components/gleam-logo.ts#L97
Added line #L97 was not covered by tests


[warning] 102-103: app/components/gleam-logo.ts#L102-L103
Added lines #L102 - L103 were not covered by tests


[warning] 115-116: app/components/gleam-logo.ts#L115-L116
Added lines #L115 - L116 were not covered by tests

🔇 Additional comments (4)
app/components/gleam-logo.ts (4)

7-19: Good component interface design.

The GleamLogoSignature interface is well-structured with clear typing for the element, arguments, and blocks. The optional size parameter gives flexibility to either specify an explicit size or let the component determine it based on its parent.


38-61: Well-implemented responsive sizing with ResizeObserver.

The ResizeObserver setup is well implemented and follows best practices:

  1. Only setting up when necessary (when no explicit size is provided)
  2. Getting initial size immediately to avoid layout flashes
  3. Proper cleanup in the destruction lifecycle hook

This ensures the component adapts to its parent container size changes efficiently.


111-127: Good resource cleanup logic.

The component properly cleans up all resources in the handleWillDestroy lifecycle hook:

  1. Clearing any animation intervals
  2. Disconnecting the ResizeObserver
  3. Stopping and nullifying the Rive instance

This is excellent practice to prevent memory leaks.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 115-116: app/components/gleam-logo.ts#L115-L116
Added lines #L115 - L116 were not covered by tests


130-134: Good TypeScript integration with Glint.

The component correctly registers itself with the Glint registry, ensuring proper TypeScript support throughout the application.

Comment on lines +100 to +108
} catch (error: unknown) {
console.error('Error setting up Rive:', error);
Sentry.captureException(error, {
tags: {
component: 'GleamLogo',
action: 'handleDidInsert',
},
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Implement fallback UI for animation failures.

The error handling reports to Sentry but doesn't provide any fallback UI when the animation fails to load. Consider adding a fallback to display a static image or message.

@tracked riveInstance: Rive | null = null;
@tracked containerSize: number = 0;
+ @tracked hasLoadError: boolean = false;

// In the catch block:
} catch (error: unknown) {
  console.error('Error setting up Rive:', error);
+  this.hasLoadError = true;
  Sentry.captureException(error, {
    tags: {
      component: 'GleamLogo',
      action: 'handleDidInsert',
    },
  });
}

Then in your template file (gleam-logo.hbs), you would add:

<div {{did-insert this.handleDidInsert}} {{will-destroy this.handleWillDestroy}} style={{this.containerStyle}}>
  {{#if this.hasLoadError}}
    <img src="/assets/images/gleam-logo.svg" alt="Gleam Logo" style="width: 100%; height: 100%;" />
  {{/if}}
</div>

Let's verify if a static fallback image exists:


🏁 Script executed:

#!/bin/bash
# Check if a static Gleam logo exists
find public/assets -type f -name "*gleam*" | grep -i '.svg\|.png\|.jpg'

# Also check if there's a generic fallback image pattern in the codebase
grep -r "hasLoadError" --include="*.hbs" app/

Length of output: 353


Fallback UI for Rive animation failures

Add a boolean flag to the component and render a static SVG when the animation fails:

• In app/components/gleam-logo.ts (around lines 100–108):

@tracked riveInstance: Rive | null = null;
@tracked containerSize: number = 0;
+ @tracked hasLoadError: boolean = false;

…  
} catch (error: unknown) {
  console.error('Error setting up Rive:', error);
+ this.hasLoadError = true;
  Sentry.captureException(error, {
    tags: {
      component: 'GleamLogo',
      action: 'handleDidInsert',
    },
  });
}

• In app/components/gleam-logo.hbs (replace /assets/images/gleam-logo.svg with an existing asset – e.g. the color version):

<div
  {{#if (not this.hasLoadError)}}
    {{did-insert this.handleDidInsert}}
    {{will-destroy this.handleWillDestroy}}
  {{/if}}
  style={{this.containerStyle}}
>
  {{#if this.hasLoadError}}
    <img
      src="/assets/images/language-logos/gleam-color.svg"
      alt="Gleam Logo"
      style="width:100%; height:100%;"
    />
  {{/if}}
</div>

Note: Available static SVGs include

  • /assets/images/language-logos/gleam-color.svg
  • /assets/images/language-logos/gleam-gray-500.svg
  • /assets/images/language-logos/gleam-teal-500.svg

Pick the variant that best matches your design.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 102-103: app/components/gleam-logo.ts#L102-L103
Added lines #L102 - L103 were not covered by tests

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants