Skip to content

Conversation

@samejr
Copy link
Member

@samejr samejr commented Jan 5, 2026

Remove the spline dependency and load it dynamically on the 404 page.

Uses a web component with a workaround for React.

@changeset-bot
Copy link

changeset-bot bot commented Jan 5, 2026

⚠️ No Changeset found

Latest commit: 8997f7c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 5, 2026

Walkthrough

Replaces the Spline React component usage in ErrorDisplay with a newly added TriggerRotatingLogo component. Adds TriggerRotatingLogo which lazy-loads the external spline-viewer script, exposes a global JSX intrinsic for <spline-viewer>, augments Window with a shared __splineLoader promise, tracks readiness via component state, and renders an animated container with the <spline-viewer> element once ready. Removes the @splinetool/react-spline dependency from apps/webapp/package.json. No exported function signatures were changed for existing components.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description is missing most required template sections including issue reference, checklist, testing details, and changelog. Only a brief informal description is provided without following the repository's standard template. Complete the PR description by filling out the standard template: add the Closes # reference, complete the checklist items, describe testing steps, provide a changelog entry, and add any relevant screenshots.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: dynamically loading Spline on the 404 page instead of bundling it globally, which aligns with the primary objective of removing the dependency and loading it on-demand.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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

❤️ Share

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

@vibe-kanban-cloud
Copy link

Review Complete

Your review story is ready!

View Story

Comment !reviewfast on this PR to re-generate the story.

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

Fix all issues with AI Agents 🤖
In @apps/webapp/app/components/ErrorDisplay.tsx:
- Around line 59-67: The useEffect that injects the Spline viewer script can
cause race conditions and hydration mismatches because <spline-viewer> is
rendered immediately; update the ErrorDisplay component to track loading state
(e.g., isSplineReady / splineError) and only render the <spline-viewer> element
when the script has loaded successfully, attach script.onload and script.onerror
handlers to set those states, include cleanup to remove listeners in the effect,
and when appending the script set integrity and crossOrigin attributes (using
the SRI hash) to mitigate CDN security risks and show a user-friendly
fallback/UI when splineError is true.
- Line 64: The CDN script src is pointing to a non-existent @splinetool/viewer
version 1.12.29 which will cause runtime load failures; update the string
assigned to script.src in ErrorDisplay (the line setting script.src = "...") to
reference a released version such as 1.12.23 so the browser can successfully
load the Spline viewer.
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between edf5b14 and 1835117.

📒 Files selected for processing (2)
  • apps/webapp/app/components/ErrorDisplay.tsx
  • apps/webapp/package.json
💤 Files with no reviewable changes (1)
  • apps/webapp/package.json
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

Files:

  • apps/webapp/app/components/ErrorDisplay.tsx
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/components/ErrorDisplay.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Files:

  • apps/webapp/app/components/ErrorDisplay.tsx
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/components/ErrorDisplay.tsx
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Files:

  • apps/webapp/app/components/ErrorDisplay.tsx
**/*.{js,ts,jsx,tsx,json,md,css,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier

Files:

  • apps/webapp/app/components/ErrorDisplay.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
apps/webapp/app/components/ErrorDisplay.tsx (2)

10-22: LGTM: TypeScript declaration for custom element.

The global JSX namespace extension correctly declares the spline-viewer custom element for TypeScript. The use of interface here is appropriate since JSX.IntrinsicElements must be an interface.


89-93: Custom element usage is correct, pending script loading fix.

The spline-viewer custom element is used correctly with appropriate attributes. However, this depends on the race condition fix from lines 59-67 to ensure the element is defined before rendering.

import Spline from "@splinetool/react-spline";
import { type ReactNode } from "react";
import { type ReactNode, useEffect } from "react";

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 pull this out into a separate TriggerRotatingLogo component so it doesn't contaminate this file.

Also pump the issue below into Claude and see what it thinks about the race condition

@claude
Copy link

claude bot commented Jan 7, 2026

Code Review: PR #2834 - Load spline 404 on page

Summary

This PR removes the bundled @splinetool/react-spline dependency and replaces it with a dynamically loaded web component (<spline-viewer>). This is a good optimization for reducing bundle size since the Spline viewer is only needed on error pages.


✅ What's Good

  1. Bundle size reduction: Removing @splinetool/react-spline from package.json reduces the main bundle size since this library is now loaded on-demand only when needed.

  2. Graceful degradation: The component silently handles load failures—appropriate since this is purely decorative.

  3. Custom element check: The customElements.get("spline-viewer") check prevents duplicate script loading across re-renders.

  4. Clean separation: Creating TriggerRotatingLogo.tsx as a separate component is a good abstraction.


⚠️ Suggestions & Considerations

1. Script cleanup on unmount

The script element is appended to document.head but never removed on component unmount. While this is generally fine (scripts typically stay loaded), consider cleaning up for completeness:

useEffect(() => {
  if (customElements.get("spline-viewer")) {
    setIsSplineReady(true);
    return;
  }

  const script = document.createElement("script");
  script.type = "module";
  script.src = "https://unpkg.com/@splinetool/viewer@1.12.29/build/spline-viewer.js";
  script.onload = () => setIsSplineReady(true);
  document.head.appendChild(script);

  return () => {
    // Optional: cleanup if component unmounts before load
    script.onload = null;
  };
}, []);

2. Pinned version consideration

The script URL uses a pinned version (@1.12.29), which is good for stability. However, consider:

  • Document why this version was chosen
  • Add a comment noting that updates require manual review

3. CDN reliability

Using unpkg.com is reasonable, but consider:

  • Subresource Integrity (SRI): Since this is a module script loaded from a CDN, SRI isn't straightforward, but worth noting the security tradeoff
  • Alternative: If unpkg has availability issues, a fallback or self-hosting might be worth considering for production

4. Global TypeScript declaration placement

The declare global block for the web component type is inline in the component file. This works, but could alternatively live in a *.d.ts file if you want to reuse this declaration elsewhere.


🔍 Minor Observations

  1. Unused import removed: The motion import was correctly moved from ErrorDisplay.tsx to TriggerRotatingLogo.tsx.

  2. Consistent styling: The motion.div wrapper preserves the same animation behavior as before.


🧪 Test Coverage

No tests exist for these UI components (which is common for decorative elements). Given this is a visual/decorative component, manual testing is reasonable. Consider verifying:

  • 404 page renders correctly with the animation
  • Page still works if the CDN is blocked/slow
  • No console errors on load

🔒 Security

The PR loads an external script from unpkg.com. This is a common pattern, but be aware:

  • The Spline design file URL (prod.spline.design) is hardcoded and controlled by you, which is fine
  • The viewer script is from a third party—ensure this is an acceptable trust relationship

Overall Assessment

Approve ✅ — This is a solid improvement that reduces bundle size by lazy-loading a heavy dependency used only on error pages. The implementation is clean and handles edge cases appropriately. The suggestions above are minor and optional.

@samejr samejr changed the title Fix(webapp): Load spline 404 on page Fix(webapp): Dynamically load spline on 404 on page Jan 7, 2026
@claude
Copy link

claude bot commented Jan 7, 2026

PR Review: Load Spline 404 on Page

Thanks for this PR! The approach of dynamically loading the Spline viewer to reduce bundle size is solid. Here's my feedback:

✅ What's Good

  1. Bundle size optimization: Removing @splinetool/react-spline from the bundle and loading it dynamically on-demand is a good performance improvement for initial page loads.

  2. Graceful degradation: The component handles the loading state appropriately by returning null until the script loads, and silently handles errors since the logo is purely decorative.

  3. Web Components approach: Using the <spline-viewer> web component instead of the React wrapper is a reasonable choice for dynamic loading.

🔍 Suggestions & Considerations

1. Script Cleanup on Unmount

The script element is appended to document.head but never removed if the component unmounts. While this is likely fine for a 404 page (users typically navigate away), consider cleanup for completeness:

useEffect(() => {
  if (customElements.get("spline-viewer")) {
    setIsSplineReady(true);
    return;
  }

  const script = document.createElement("script");
  script.type = "module";
  script.src = "https://unpkg.com/@splinetool/viewer@1.12.29/build/spline-viewer.js";
  script.onload = () => setIsSplineReady(true);
  document.head.appendChild(script);

  return () => {
    document.head.removeChild(script);
  };
}, []);

However, if this component could be re-mounted multiple times, you'd want to avoid removing the script (since the web component would still be registered). The current approach is actually safer for that scenario.

2. Consider Pinning with Integrity Hash

Using unpkg is convenient, but consider adding a Subresource Integrity (SRI) hash for security:

script.integrity = "sha384-..."; // Generate from the specific version
script.crossOrigin = "anonymous";

This ensures the script hasn't been tampered with. Alternatively, if self-hosting is an option, that would provide more control over the dependency.

3. TypeScript Declaration Scope

The global JSX namespace extension works but pollutes the global type space. Consider if this could be scoped to this file only, though for a web component like this, the global declaration is a common pattern.

4. Version Pinning

Good job pinning the version (@1.12.29)! This prevents unexpected breaking changes from affecting the component.

📋 Minor Nit

  • The motion import from framer-motion was removed from ErrorDisplay.tsx but is still used in the new TriggerRotatingLogo.tsx - this is correct behavior, just confirming the motion animation logic was preserved properly.

🧪 Test Coverage

There don't appear to be existing tests for ErrorDisplay or the new TriggerRotatingLogo component. While this is a decorative component, consider adding a basic smoke test if the project has component tests set up.

🔒 Security

  • Loading from unpkg.com is a common pattern, but be aware this introduces a third-party dependency at runtime. The version pinning mitigates most risks.
  • No sensitive data is being passed to the Spline component.

📦 Performance

  • The dynamic loading approach is good - users won't pay the cost of the Spline library until they hit a 404 page.
  • The loading-anim-type="spinner-small-light" attribute provides good UX feedback during load.

Overall: This is a clean, well-structured change. The approach of loading Spline dynamically rather than bundling it is the right call for a component only used on error pages. The suggestions above are minor improvements rather than blocking issues.

LGTM with optional improvements! 👍

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

🤖 Fix all issues with AI agents
In @apps/webapp/app/components/TriggerRotatingLogo.tsx:
- Around line 21-34: Prevent multiple script inserts and handle load errors by
introducing a single shared loader promise (e.g., window.__splineLoader) used
inside the useEffect in TriggerRotatingLogo; replace the naive
customElements.get + direct append logic with: if
customElements.get("spline-viewer") setIsSplineReady(true) else if
window.__splineLoader await or then on that promise to call
setIsSplineReady(true) or setIsSplineReady(false) on rejection, and if no
window.__splineLoader create it, append one script tag with src
"https://unpkg.com/@splinetool/viewer@1.12.29/build/spline-viewer.js", wire
script.onload to resolve the promise and script.onerror to reject it, and store
that promise on window.__splineLoader so simultaneous mounts share it.
- Line 30: The script tag in TriggerRotatingLogo.tsx sets script.src to
"@splinetool/viewer@1.12.29" which appears newer than the latest published
1.12.23; verify whether 1.12.29 is intentional (pre-release) or change the src
to the latest stable version (e.g., 1.12.23) and add Subresource Integrity and
CORS attributes by setting script.integrity to the correct SRI hash and
script.crossOrigin = "anonymous" where the script element is created (refer to
the script.src assignment in TriggerRotatingLogo.tsx).
🧹 Nitpick comments (1)
apps/webapp/app/components/TriggerRotatingLogo.tsx (1)

40-52: Add accessibility attribute for decorative content.

Since this is a decorative element (indicated by pointer-events-none), it should be hidden from assistive technologies.

♿ Proposed fix to improve accessibility
   return (
     <motion.div
       className="pointer-events-none absolute inset-0 overflow-hidden"
+      aria-hidden="true"
       initial={{ opacity: 0 }}
       animate={{ opacity: 1 }}
       transition={{ delay: 0.5, duration: 2, ease: "easeOut" }}
     >
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1835117 and 42b385b.

📒 Files selected for processing (2)
  • apps/webapp/app/components/ErrorDisplay.tsx
  • apps/webapp/app/components/TriggerRotatingLogo.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/webapp/app/components/ErrorDisplay.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

Files:

  • apps/webapp/app/components/TriggerRotatingLogo.tsx
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/components/TriggerRotatingLogo.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Files:

  • apps/webapp/app/components/TriggerRotatingLogo.tsx
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/components/TriggerRotatingLogo.tsx
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Files:

  • apps/webapp/app/components/TriggerRotatingLogo.tsx
**/*.{js,ts,jsx,tsx,json,md,css,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier

Files:

  • apps/webapp/app/components/TriggerRotatingLogo.tsx
🧠 Learnings (1)
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use lifecycle functions (init, cleanup, onStart, onSuccess, onFailure, handleError) for task event handling

Applied to files:

  • apps/webapp/app/components/TriggerRotatingLogo.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: claude-review
  • GitHub Check: Analyze (javascript-typescript)

@claude
Copy link

claude bot commented Jan 7, 2026

PR Review: Dynamically load Spline on 404 page

Summary

This PR removes the @splinetool/react-spline package dependency and replaces it with dynamic script loading of the @splinetool/viewer web component. This is a nice optimization that improves bundle size by loading the decorative 3D viewer only when needed on error pages.


✅ What looks good

  1. Bundle size reduction: Removing @splinetool/react-spline and its transitive dependencies (@splinetool/runtime, on-change, react-merge-refs, semver-compare) from the main bundle is a solid optimization.

  2. Graceful degradation: The component handles errors silently since the Spline viewer is purely decorative - this is appropriate behavior.

  3. TypeScript declarations: Good use of module augmentation to declare the custom element types for spline-viewer.

  4. Custom element check: Checking customElements.get("spline-viewer") before loading prevents duplicate script injection.


⚠️ Suggestions & potential issues

1. Memory leak: Missing script cleanup

The script element appended to the document head is never removed. While this is minor since users don't navigate to 404 pages repeatedly, consider adding cleanup:

useEffect(() => {
  if (customElements.get("spline-viewer")) {
    setIsSplineReady(true);
    return;
  }

  const script = document.createElement("script");
  script.type = "module";
  script.src = "https://unpkg.com/@splinetool/viewer@1.12.29/build/spline-viewer.js";
  script.onload = () => setIsSplineReady(true);
  document.head.appendChild(script);

  // Cleanup on unmount (optional, but good practice)
  return () => {
    document.head.removeChild(script);
  };
}, []);

Note: If you want the script to persist (to avoid re-downloading on subsequent renders), the current approach is fine, but you may want to add a comment explaining this is intentional.

2. Pinned version for unpkg URL

The version @1.12.29 is pinned in the URL, which is good for stability. Just ensure this version is intentionally chosen and consider documenting why this specific version (or add a comment noting when it was last updated).

3. Race condition consideration

If the component unmounts before script.onload fires, setIsSplineReady(true) will still be called on an unmounted component. This is a minor React warning issue. Consider:

useEffect(() => {
  let isMounted = true;
  
  if (customElements.get("spline-viewer")) {
    setIsSplineReady(true);
    return;
  }

  const script = document.createElement("script");
  script.type = "module";
  script.src = "https://unpkg.com/@splinetool/viewer@1.12.29/build/spline-viewer.js";
  script.onload = () => {
    if (isMounted) setIsSplineReady(true);
  };
  document.head.appendChild(script);

  return () => {
    isMounted = false;
  };
}, []);

4. Consider a fallback/loading state

Currently, the component returns null while loading. For better UX on slow connections, you might consider showing a subtle placeholder or skeleton. Though for a decorative element on a 404 page, null is perfectly reasonable.


🔒 Security considerations

  • External script loading: The script is loaded from unpkg.com, which is a well-known CDN. However, loading scripts from external CDNs introduces a supply chain risk. If this is a concern, consider:
    • Using Subresource Integrity (SRI) hashes (though this is complex with ES modules)
    • Self-hosting the viewer script
    • Adding the script to your CSP policy if you have one

For a decorative element on error pages, the current approach is reasonable, but worth noting.


📝 Test coverage

There are no existing tests for ErrorDisplay component, so no test changes are expected. The new TriggerRotatingLogo component is purely decorative and doesn't have tests, which is acceptable for this type of component.


Overall assessment

This is a solid PR that achieves its goal of reducing bundle size by dynamically loading the Spline viewer. The implementation is clean and handles the main edge cases appropriately.

The suggestions above are minor improvements rather than blocking issues. The PR is ready to merge as-is, or you can address any of the suggestions based on your preferences.

🟢 Approve - Nice work on this optimization!

@claude
Copy link

claude bot commented Jan 7, 2026

PR Review: Dynamically load Spline on 404 page

Thank you for this PR! The approach of dynamically loading the Spline library is a solid improvement - it removes a bundled dependency and only loads the 3D viewer when needed (on error pages). Here's my feedback:

✅ What's Good

  1. Bundle size reduction: Removing @splinetool/react-spline from the bundle and its transitive dependencies (on-change, semver-compare, react-merge-refs) is a nice win.

  2. Smart loading pattern: The singleton pattern with window.__splineLoader prevents duplicate script loads across re-mounts. Good attention to race conditions.

  3. Version pinning: Pinning to @splinetool/viewer@1.12.29 is the right call for stability.

  4. Clean TypeScript integration: The JSX intrinsic elements declaration for spline-viewer is properly typed.

  5. Preserved UX: The fade-in animation is maintained, ensuring the visual experience remains consistent.

🔍 Suggestions & Questions

  1. Error state handling: When the script fails to load (setIsSplineReady(false) in the catch), the component renders null. Consider whether a fallback (like a simple logo image) would be better UX, or if silently hiding is intentional for error pages.

  2. Memory consideration: The comment about not cleaning up the script is valid, but worth noting that window.__splineLoader persists forever. This is fine for this use case but could be documented for future maintainers.

  3. TypeScript placement: The declare global block augmenting JSX intrinsic elements is at the component level. This works but could alternatively live in a .d.ts file if more web components are added in the future. Not a blocker, just a consideration for scalability.

  4. Loading indicator: Currently shows nothing while loading. Since error pages are already a "something went wrong" moment, the loading gap might be acceptable, but a lightweight skeleton could improve perceived performance.

🔒 Security

Loading from unpkg.com is standard practice. The comment about SRI hashes not being stable is accurate - unpkg doesn't guarantee content-addressable hashes. If stricter security is needed in the future, consider self-hosting or using a CDN with SRI support.

🧪 Test Coverage

This is a UI component with browser-specific behavior (custom elements, dynamic script loading). Testing would require a browser environment. Given this is a 404 page component and the logic is relatively straightforward, manual testing is probably sufficient. Worth verifying:

  • Fresh page load shows the animation
  • Navigation to error pages works correctly
  • No console errors in different browsers

Minor Nit

The framer-motion import was removed from ErrorDisplay.tsx but the component still uses motion in TriggerRotatingLogo.tsx. This is correct - just confirming the import was moved, not removed entirely.


Overall: This is a clean refactor that improves bundle size without sacrificing functionality. The code is well-structured with good attention to edge cases (duplicate loads, custom element registration checks).

👍 Looks good to merge after considering the optional feedback above.

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

🤖 Fix all issues with AI agents
In @apps/webapp/app/components/TriggerRotatingLogo.tsx:
- Around line 25-55: The promise handlers in useEffect
(window.__splineLoader.then(...) and the onload/onerror handlers that call
setIsSplineReady) can call setState after the component unmounts; fix by adding
a mounted flag inside the useEffect (e.g., let mounted = true), replace direct
setIsSplineReady(...) calls with guarded calls (if (mounted)
setIsSplineReady(true/false)), and return a cleanup function that sets mounted =
false so any late-resolving window.__splineLoader or script callbacks won't
update state after unmount; ensure you update both places where setIsSplineReady
is used (the existing window.__splineLoader.then/catch and the
script.onload/script.onerror resolver/rejector handlers).
🧹 Nitpick comments (1)
apps/webapp/app/components/TriggerRotatingLogo.tsx (1)

4-20: TypeScript declarations look good; consider stricter typing for loading-anim-type.

The JSX intrinsic element and Window interface augmentations are well-structured. The loading-anim-type prop could optionally use a string union type for better type safety if the Spline viewer supports a fixed set of values.

♻️ Example with stricter typing
 declare global {
   namespace JSX {
     interface IntrinsicElements {
       "spline-viewer": React.DetailedHTMLProps<
         React.HTMLAttributes<HTMLElement> & {
           url?: string;
-          "loading-anim-type"?: string;
+          "loading-anim-type"?: "spinner-small-light" | "spinner-small-dark" | "none";
         },
         HTMLElement
       >;
     }
   }
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 42b385b and 8997f7c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (1)
  • apps/webapp/app/components/TriggerRotatingLogo.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

Files:

  • apps/webapp/app/components/TriggerRotatingLogo.tsx
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/components/TriggerRotatingLogo.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Files:

  • apps/webapp/app/components/TriggerRotatingLogo.tsx
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/components/TriggerRotatingLogo.tsx
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Files:

  • apps/webapp/app/components/TriggerRotatingLogo.tsx
**/*.{js,ts,jsx,tsx,json,md,css,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier

Files:

  • apps/webapp/app/components/TriggerRotatingLogo.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: Analyze (javascript-typescript)

Comment on lines +25 to +55
useEffect(() => {
// Already registered from a previous render
if (customElements.get("spline-viewer")) {
setIsSplineReady(true);
return;
}

// Another mount already started loading - share the same promise
if (window.__splineLoader) {
window.__splineLoader.then(() => setIsSplineReady(true)).catch(() => setIsSplineReady(false));
return;
}

// First mount: create script and shared loader promise
const script = document.createElement("script");
script.type = "module";
// Version pinned; SRI hash omitted as unpkg doesn't guarantee hash stability across deploys
script.src = "https://unpkg.com/@splinetool/viewer@1.12.29/build/spline-viewer.js";

window.__splineLoader = new Promise<void>((resolve, reject) => {
script.onload = () => resolve();
script.onerror = () => reject();
});

window.__splineLoader.then(() => setIsSplineReady(true)).catch(() => setIsSplineReady(false));

document.head.appendChild(script);

// Intentionally no cleanup: once the custom element is registered globally,
// removing the script would break re-mounts while providing no benefit
}, []);
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 | 🟡 Minor

Race condition successfully addressed; prevent setState after unmount.

The shared window.__splineLoader promise elegantly solves the race condition flagged in the previous review. However, the promise handlers on lines 34 and 49 call setIsSplineReady without checking if the component is still mounted, which could cause warnings or unexpected behavior if the component unmounts before the script loads.

🔧 Proposed fix using cleanup pattern
 export function TriggerRotatingLogo() {
   const [isSplineReady, setIsSplineReady] = useState(false);
 
   useEffect(() => {
+    let mounted = true;
+
     // Already registered from a previous render
     if (customElements.get("spline-viewer")) {
       setIsSplineReady(true);
       return;
     }
 
     // Another mount already started loading - share the same promise
     if (window.__splineLoader) {
-      window.__splineLoader.then(() => setIsSplineReady(true)).catch(() => setIsSplineReady(false));
+      window.__splineLoader
+        .then(() => mounted && setIsSplineReady(true))
+        .catch(() => mounted && setIsSplineReady(false));
       return;
     }
 
     // First mount: create script and shared loader promise
     const script = document.createElement("script");
     script.type = "module";
     // Version pinned; SRI hash omitted as unpkg doesn't guarantee hash stability across deploys
     script.src = "https://unpkg.com/@splinetool/viewer@1.12.29/build/spline-viewer.js";
 
     window.__splineLoader = new Promise<void>((resolve, reject) => {
       script.onload = () => resolve();
       script.onerror = () => reject();
     });
 
-    window.__splineLoader.then(() => setIsSplineReady(true)).catch(() => setIsSplineReady(false));
+    window.__splineLoader
+      .then(() => mounted && setIsSplineReady(true))
+      .catch(() => mounted && setIsSplineReady(false));
 
     document.head.appendChild(script);
 
     // Intentionally no cleanup: once the custom element is registered globally,
     // removing the script would break re-mounts while providing no benefit
+
+    return () => {
+      mounted = false;
+    };
   }, []);
🤖 Prompt for AI Agents
In @apps/webapp/app/components/TriggerRotatingLogo.tsx around lines 25 - 55, The
promise handlers in useEffect (window.__splineLoader.then(...) and the
onload/onerror handlers that call setIsSplineReady) can call setState after the
component unmounts; fix by adding a mounted flag inside the useEffect (e.g., let
mounted = true), replace direct setIsSplineReady(...) calls with guarded calls
(if (mounted) setIsSplineReady(true/false)), and return a cleanup function that
sets mounted = false so any late-resolving window.__splineLoader or script
callbacks won't update state after unmount; ensure you update both places where
setIsSplineReady is used (the existing window.__splineLoader.then/catch and the
script.onload/script.onerror resolver/rejector handlers).

@samejr samejr merged commit 3f19b98 into main Jan 7, 2026
35 checks passed
@samejr samejr deleted the fix(webapp)-load-spline-404-on-page branch January 7, 2026 15:50
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.

4 participants