Skip to content

Conversation

Oksamies
Copy link
Contributor

@Oksamies Oksamies commented Sep 23, 2025

Summary by CodeRabbit

  • New Features

    • Unified breadcrumb navigation across pages (teams, communities, packages) for clearer context.
    • Consistent ad placements with improved loading reliability; ads only display when eligible.
    • Beta toggle initializes automatically when available.
    • Global tooltip support and integrated link/toast handling.
  • Refactor

    • Simplified app layout and providers; removed legacy scaffolding for breadcrumbs and ads.
    • Improved error handling wiring while preserving monitoring, with clearer logs in development.

Copy link

coderabbitai bot commented Sep 23, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Reworked root layout to centralize breadcrumbs and ads, augmented global/window typings and outlet context, added hydration-aware initializers for Nitro Ads and beta switch script, and introduced small providers/utilities. New components: NimbusBreadcrumbs, Ads/AdsInit, BetaButtonInit, TooltipProvider, plus breadcrumb helper utilities.

Changes

Cohort / File(s) Summary of changes
Root app wiring and env/context
apps/cyberstorm-remix/app/root.tsx
Replaced manual breadcrumb and ad blocks with NimbusBreadcrumbs and Ads; added AdsInit, BetaButtonInit, TooltipProvider, linking/toast providers; set clientLoader.hydrate = true; expanded Window globals (NIMBUS_PUBLIC_ENV, Dapper, nitroAds); extended OutletContextShape with domain and dapper; adjusted imports/wiring and preserved Sentry/ErrorBoundary.
Breadcrumbs framework
apps/cyberstorm-remix/app/rootRoute/breadcrumbs.tsx
Added NimbusBreadcrumbs (memoized) to derive and render breadcrumb trail from useMatches, covering settings, teams, communities, upload, and package-related routes.
Breadcrumb helpers
apps/cyberstorm-remix/app/rootRoute/breadcrumbs/getCommunityBreadcrumb.tsx, apps/cyberstorm-remix/app/rootRoute/breadcrumbs/getPackageListingBreadcrumb.tsx
Introduced utility functions to construct community and package listing/edit/dependants breadcrumb segments with conditional links and labels.
Ads integration
apps/cyberstorm-remix/app/rootRoute/nitroAds.tsx
Added AdsInit to inject Nitro Ads script and initialize containers; exported adContainerIds; added memoized Ads component rendering AdContainers when enabled.
Beta switch initializer
apps/cyberstorm-remix/app/rootRoute/betaButton.tsx
Added BetaButtonInit that appends /cyberstorm-static/scripts/beta-switch.js once on hydration; cleans up on unmount.
Tooltip provider
apps/cyberstorm-remix/app/rootRoute/tooltipProvider.tsx
Added memoized TooltipProvider wrapper around Radix Tooltip with fixed delay.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Browser
  participant RemixApp as Remix App
  participant Root as root.tsx
  participant AdsInit as AdsInit
  participant Nitro as nitroAds (window)
  participant Beta as BetaButtonInit
  participant Router as Router (useMatches)
  participant Breadcrumbs as NimbusBreadcrumbs

  User->>Browser: Navigate
  Browser->>RemixApp: Request + SSR
  RemixApp->>Root: loader provides public env/flags
  Note over Root: clientLoader.hydrate = true
  Browser-->>Root: Hydration

  par UI composition
    Root->>AdsInit: Mount (if shouldShowAds)
    Root->>Beta: Mount
    Root->>Router: useMatches()
    Router-->>Breadcrumbs: matches
  and
    Root->>Breadcrumbs: Render
  end

  AdsInit->>Browser: Inject Nitro Ads script (once)
  AdsInit->>Nitro: createAd(...) for containerIds
  Beta->>Browser: Inject beta-switch.js (once)

  Note over Root,Nitro: Ads render via <Ads/> with AdContainer shells
Loading
sequenceDiagram
  autonumber
  participant Router as Router (useMatches)
  participant Breadcrumbs as NimbusBreadcrumbs
  participant Helpers as Helpers (community/package)
  Router-->>Breadcrumbs: matches[]
  Breadcrumbs->>Helpers: getCommunityBreadcrumb(match, isNotLast)
  Helpers-->>Breadcrumbs: Element or null
  Breadcrumbs->>Helpers: getPackageListingBreadcrumb(listing, edit, dependants)
  Helpers-->>Breadcrumbs: Fragment or null
  Breadcrumbs-->>Router: Breadcrumb trail (links/spans)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • VilppeRiskidev

Poem

I twitch my whiskers at the trail we weave,
New crumbs align where old would cleave.
Ads pop in with a gentle hop,
Beta switch primed—flip, don’t stop!
Tooltips hover, soft as clover—
With Nimbus skies, we bound right over. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly describes the primary refactor of breaking out the single root.tsx file into multiple organized modules, reflecting the main purpose of the pull request and allowing a clear understanding when scanning history.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 09-23-separate_and_organize_root.tsx_to_multiple_files

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
🧪 Early access (Sonnet 4.5): enabled

We are currently testing the Sonnet 4.5 model, which is expected to improve code review quality. However, this model may lead to increased noise levels in the review comments. Please disable the early access features if the noise level causes any inconvenience.

Note:

  • Public repositories are always opted into early access features.
  • You can enable or disable early access features from the CodeRabbit UI or by updating the CodeRabbit configuration file.

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

@Oksamies Oksamies force-pushed the 09-23-separate_and_organize_root.tsx_to_multiple_files branch from afadece to d267286 Compare September 23, 2025 13:39
@Oksamies Oksamies requested a review from anttimaki September 23, 2025 13:41
Base automatically changed from 09-17-enable_teams_team_settigns_and_user_settings_pages to master September 23, 2025 13:42
Copy link
Contributor

@anttimaki anttimaki left a comment

Choose a reason for hiding this comment

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

  • I like the extraction of the breadcrumbs, ads, tooltip, and beta button 👍🏻
  • Left comments/questions
  • Check out AIs comments too, they seem sensible

// REMIX TODO: https://remix.run/docs/en/main/route/links
// export const links: LinksFunction = () => [{ rel: "stylesheet", href: styles }];

/* Declaring window types that are present, because of certain parts of the application */
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor: I find this comment confusing. Certain parts of the application what? Expect the types to be declared?

Question: would it make sense to define the nitroAds part in nitroAds.tsx to compartmentalize ads related stuff in that file?

captureRemixErrorBoundaryError(error);
} else if (import.meta.env.DEV) {
// In development mode, we log the error to the console
console.log(error);
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: console.error is handy for logging errors.

@@ -0,0 +1,64 @@
import { isRecord, NewBreadCrumbsLink } from "@thunderstore/cyberstorm";
Copy link
Contributor

Choose a reason for hiding this comment

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

Commenting here as I can't comment on an empty file: Should breadcrumbs.tsx contain something or be deleted?

@Oksamies Oksamies force-pushed the 09-23-separate_and_organize_root.tsx_to_multiple_files branch from d267286 to 0977167 Compare September 30, 2025 09:56
Copy link

@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 (3)
apps/cyberstorm-remix/app/rootRoute/tooltipProvider.tsx (1)

2-2: Past review comment addressed: Import alias suggestion.

A previous review suggested aliasing the import as Provider as RadixTooltip. While this could improve clarity in files with multiple providers, the current direct import is acceptable in this isolated file context where there's no ambiguity.

apps/cyberstorm-remix/app/root.tsx (2)

51-51: Comment clarity could be improved.

The comment references "certain parts of the application" without specificity. Based on the past review comment, consider moving nitroAds type to nitroAds.tsx or clarifying which parts of the application require these declarations.

Consider this clearer comment:

-/* Declaring window types that are present, because of certain parts of the application */
+/* Global window type augmentations used throughout the app:
+ * - NIMBUS_PUBLIC_ENV: injected in Layout for client-side env access
+ * - Dapper: authentication client instantiated in App component
+ * - nitroAds: third-party ad service loaded by AdsInit component
+ */

Alternatively, as suggested in the past review, consider moving the nitroAds interface definition to nitroAds.tsx and re-exporting it for use here.


352-353: Use console.error for error logging.

In development mode, errors should be logged using console.error rather than console.log for proper error categorization and tooling support.

Apply this diff:

   } else if (import.meta.env.DEV) {
     // In development mode, we log the error to the console
-    console.log(error);
+    console.error(error);
   }
🧹 Nitpick comments (11)
apps/cyberstorm-remix/app/rootRoute/tooltipProvider.tsx (1)

4-10: Consider removing memo wrapper.

The memo wrapper provides minimal benefit here since the children prop typically has a new reference on each render, causing memo's shallow comparison to trigger re-renders anyway. The inner Radix Provider likely handles its own optimization.

Apply this diff if you'd like to simplify:

-export const TooltipProvider = memo(function TooltipProvider({
+export function TooltipProvider({
   children,
 }: {
   children: ReactNode;
-}) {
+}) {
   return <Provider delayDuration={80}>{children}</Provider>;
-});
+}
apps/cyberstorm-remix/app/rootRoute/betaButton.tsx (1)

17-26: Consider checking for existing script tag.

The current implementation may append duplicate script tags if the component mounts multiple times (e.g., during development with Fast Refresh). Consider checking if the script already exists:

  if (typeof window !== "undefined") {
+   const existingScript = document.querySelector('script[src="/cyberstorm-static/scripts/beta-switch.js"]');
+   if (existingScript) {
+     hasRun.current = true;
+     return;
+   }
+
    const $script = document.createElement("script");
    $script.src = "/cyberstorm-static/scripts/beta-switch.js";
    $script.setAttribute("async", "true");

    document.body.append($script);
    hasRun.current = true;

    return () => $script.remove();
  }

Note: The typeof window !== "undefined" check on line 17 is redundant since useEffect only runs on the client.

apps/cyberstorm-remix/app/rootRoute/breadcrumbs/getPackageListingBreadcrumb.tsx (1)

13-41: Consider guarding against multiple simultaneous parameters.

The function will render multiple breadcrumb segments if more than one parameter is provided (e.g., both packageEditPage and packageDependantsPage). If these parameters are mutually exclusive (only one should be truthy), consider adding a guard or comment to clarify:

Additionally, lines 18-29 and 30-41 contain duplicated JSX. Consider extracting a helper:

function renderPackageBreadcrumbLink(match: UIMatch | undefined) {
  if (!match) return null;
  return (
    <NewBreadCrumbsLink
      primitiveType="cyberstormLink"
      linkId="Package"
      community={match.params.communityId}
      namespace={match.params.namespaceId}
      package={match.params.packageId}
      csVariant="cyber"
    >
      {match.params.packageId}
    </NewBreadCrumbsLink>
  );
}

Then use: {renderPackageBreadcrumbLink(packageEditPage)} and {renderPackageBreadcrumbLink(packageDependantsPage)}.

apps/cyberstorm-remix/app/rootRoute/breadcrumbs/getCommunityBreadcrumb.tsx (3)

9-14: Redundant null check after early return.

Line 9 already returns null if !communityPage, making the communityPage && check on line 12 redundant.

Apply this diff to remove the redundant check:

 export function getCommunityBreadcrumb(
   communityPage: UIMatch | undefined,
   isNotLast: boolean
 ) {
   if (!communityPage) return null;
   return (
     <>
-      {communityPage &&
-      isRecord(communityPage.data) &&
+      {isRecord(communityPage.data) &&
       Object.prototype.hasOwnProperty.call(communityPage.data, "community") ? (

27-39: Simplify type guards and property access.

The nested Object.prototype.hasOwnProperty.call checks combined with typeof checks are verbose. Consider using optional chaining or refining types for cleaner code.

Example refactor using optional chaining:

               if (isRecord(resolvedValue)) {
-                label =
-                  Object.prototype.hasOwnProperty.call(resolvedValue, "name") &&
-                  typeof resolvedValue.name === "string"
-                    ? resolvedValue.name
-                    : communityPage.params.communityId;
-                icon =
-                  Object.prototype.hasOwnProperty.call(
-                    resolvedValue,
-                    "community_icon_url"
-                  ) && typeof resolvedValue.community_icon_url === "string" ? (
-                    <img src={resolvedValue.community_icon_url} alt="" />
-                  ) : undefined;
+                label = typeof resolvedValue.name === "string" 
+                  ? resolvedValue.name 
+                  : communityPage.params.communityId;
+                icon = typeof resolvedValue.community_icon_url === "string" 
+                  ? <img src={resolvedValue.community_icon_url} alt="" />
+                  : undefined;
               }

51-56: Simplify nested span structure.

The nested <span><span> structure appears unnecessary for rendering the non-link breadcrumb item.

Consider simplifying to:

               ) : (
-                <span>
-                  <span>
-                    {icon}
-                    {label}
-                  </span>
-                </span>
+                <span>
+                  {icon}
+                  {label}
+                </span>
               );
apps/cyberstorm-remix/app/rootRoute/breadcrumbs.tsx (3)

7-7: Incorrect component description comment.

The comment states "Layout component to provide common UI around all pages" but this is NimbusBreadcrumbs, a breadcrumb-specific component, not a layout component.

Apply this diff:

-/** Layout component to provide common UI around all pages */
+/** Breadcrumbs component that renders navigation trail based on current route matches */
 export const NimbusBreadcrumbs = memo(function NimbusBreadcrumbs(props: {

12-53: Multiple sequential find operations on matches array.

The component performs 14 separate .find() operations on the matches array. While readable, this could be optimized with a single pass using a lookup map if performance becomes a concern.

Current approach is clear and maintainable. Consider optimization only if profiling reveals performance issues:

const matchesMap = useMemo(() => {
  const map = new Map<string, UIMatch>();
  for (const match of matches) {
    map.set(match.id, match);
  }
  return map;
}, [matches]);

const userSettingsPage = matchesMap.get("settings/user/Settings");
// ... etc

131-148: Complex nested ternary for Communities breadcrumb.

The deeply nested ternary logic for determining whether to render a link or span is difficult to parse at a glance.

Consider extracting to a clearer conditional structure:

       {/* Communities page */}
-      {communitiesPage ||
-      communityPage ||
-      packageDependantsPage ||
-      packageTeamPage ? (
-        communityPage || packageDependantsPage || packageTeamPage ? (
+      {(communitiesPage || communityPage || packageDependantsPage || packageTeamPage) ? (
+        (communityPage || packageDependantsPage || packageTeamPage) ? (
           <NewBreadCrumbsLink
             primitiveType="cyberstormLink"
             linkId="Communities"
             csVariant="cyber"
           >
             Communities
           </NewBreadCrumbsLink>
         ) : (
           <span>
             <span>Communities</span>
           </span>
         )
       ) : null}

Or better yet, extract to a helper variable:

const showCommunities = communitiesPage || communityPage || packageDependantsPage || packageTeamPage;
const communityIsLink = communityPage || packageDependantsPage || packageTeamPage;

// Then in JSX:
{showCommunities ? (
  communityIsLink ? (
    <NewBreadCrumbsLink ...>Communities</NewBreadCrumbsLink>
  ) : (
    <span><span>Communities</span></span>
  )
) : null}
apps/cyberstorm-remix/app/rootRoute/nitroAds.tsx (1)

39-61: Redundant null checks inside forEach loop.

The nitroAds and nitroAds.createAd checks on lines 41-42 are redundant since the outer if on line 40 already verifies these conditions.

Apply this diff to remove the redundant checks:

   useEffect(() => {
     if (nitroAds !== undefined && nitroAds.createAd !== undefined) {
       adContainerIds.forEach((cid) => {
-        if (nitroAds !== undefined && nitroAds.createAd !== undefined) {
-          nitroAds.createAd(cid, {
-            demo: false,
-            format: "display",
-            refreshLimit: 0,
-            refreshTime: 30,
-            renderVisibleOnly: true,
-            refreshVisibleOnly: true,
-            sizes: [["300", "250"]],
-            report: {
-              enabled: true,
-              wording: "Report Ad",
-              position: "bottom-right",
-            },
-            mediaQuery: "(min-width: 1475px) and (min-height: 400px)",
-          });
-        }
+        nitroAds.createAd(cid, {
+          demo: false,
+          format: "display",
+          refreshLimit: 0,
+          refreshTime: 30,
+          renderVisibleOnly: true,
+          refreshVisibleOnly: true,
+          sizes: [["300", "250"]],
+          report: {
+            enabled: true,
+            wording: "Report Ad",
+            position: "bottom-right",
+          },
+          mediaQuery: "(min-width: 1475px) and (min-height: 400px)",
+        });
       });
     }
   }, [nitroAds]);
apps/cyberstorm-remix/app/root.tsx (1)

242-250: Simplify nested ternary for shouldShowAds.

The deeply nested ternary operators make this logic difficult to read and maintain.

Consider extracting to a more readable structure:

-  const shouldShowAds = location.pathname.startsWith("/teams")
-    ? false
-    : location.pathname.startsWith("/settings")
-      ? false
-      : location.pathname.startsWith("/package/create")
-        ? false
-        : location.pathname.startsWith("/tools")
-          ? false
-          : true;
+  const shouldShowAds = 
+    !location.pathname.startsWith("/teams") &&
+    !location.pathname.startsWith("/settings") &&
+    !location.pathname.startsWith("/package/create") &&
+    !location.pathname.startsWith("/tools");

Or even more explicitly:

  const adsHiddenPaths = ["/teams", "/settings", "/package/create", "/tools"];
  const shouldShowAds = !adsHiddenPaths.some(path => location.pathname.startsWith(path));
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 59e39b7 and 0977167.

📒 Files selected for processing (7)
  • apps/cyberstorm-remix/app/root.tsx (7 hunks)
  • apps/cyberstorm-remix/app/rootRoute/betaButton.tsx (1 hunks)
  • apps/cyberstorm-remix/app/rootRoute/breadcrumbs.tsx (1 hunks)
  • apps/cyberstorm-remix/app/rootRoute/breadcrumbs/getCommunityBreadcrumb.tsx (1 hunks)
  • apps/cyberstorm-remix/app/rootRoute/breadcrumbs/getPackageListingBreadcrumb.tsx (1 hunks)
  • apps/cyberstorm-remix/app/rootRoute/nitroAds.tsx (1 hunks)
  • apps/cyberstorm-remix/app/rootRoute/tooltipProvider.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
apps/cyberstorm-remix/app/rootRoute/tooltipProvider.tsx (1)
packages/cyberstorm/src/newComponents/Toast/Provider.tsx (1)
  • Provider (52-77)
apps/cyberstorm-remix/app/rootRoute/breadcrumbs.tsx (2)
apps/cyberstorm-remix/app/rootRoute/breadcrumbs/getCommunityBreadcrumb.tsx (1)
  • getCommunityBreadcrumb (5-64)
apps/cyberstorm-remix/app/rootRoute/breadcrumbs/getPackageListingBreadcrumb.tsx (1)
  • getPackageListingBreadcrumb (4-44)
apps/cyberstorm-remix/app/root.tsx (2)
apps/cyberstorm-remix/app/rootRoute/breadcrumbs.tsx (1)
  • NimbusBreadcrumbs (8-197)
apps/cyberstorm-remix/app/rootRoute/nitroAds.tsx (1)
  • Ads (66-78)
⏰ 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). (3)
  • GitHub Check: Generate visual diffs
  • GitHub Check: Test
  • GitHub Check: CodeQL
🔇 Additional comments (6)
apps/cyberstorm-remix/app/rootRoute/betaButton.tsx (1)

15-16: Verify hydration guard logic.

The guard condition if ((!startsHydrated.current && isHydrated) || hasRun.current) return; prevents script execution when the component starts not-hydrated and becomes hydrated (typical SSR→hydration flow). This means the script only runs in client-only rendering scenarios.

If the intent is to run the script after SSR hydration, the condition should likely be:

if (startsHydrated.current || hasRun.current) return;

This would run the script once during the initial client mount after SSR, but not if already hydrated (client-only render with fast refresh).

apps/cyberstorm-remix/app/rootRoute/breadcrumbs/getPackageListingBreadcrumb.tsx (1)

4-10: LGTM!

Function signature and null-guard logic are clear and correct.

apps/cyberstorm-remix/app/rootRoute/nitroAds.tsx (2)

21-23: Clarify hydration guard logic.

The condition if (!startsHydrated.current && isHydrated) return; is not immediately clear. This returns early (skipping script injection) when the component starts non-hydrated but becomes hydrated, which seems intentional to avoid double-loading. However, the logic could be clearer with a comment or variable name.

Based on the comment on lines 18-20, this guard prevents double script injection in development StrictMode. However, the condition seems to say "skip if we started server-side and are now hydrated". Can you verify this is the intended behavior?

Consider adding an explanatory comment:

   useEffect(() => {
+    // Skip script injection if component started on server and is now hydrating
+    // to avoid double-injection in StrictMode during development
     if (!startsHydrated.current && isHydrated) return;

73-73: Using array index as key is acceptable here.

While using array indices as keys is generally discouraged in React, it's acceptable in this case since adContainerIds is a static array that won't be reordered or modified.

apps/cyberstorm-remix/app/root.tsx (2)

173-174: Correct usage of clientLoader.hydrate flag.

Setting clientLoader.hydrate = true ensures the client loader runs on initial page load, which is appropriate for session validation and current user fetching.


302-309: Clean integration of refactored components.

The integration of NimbusBreadcrumbs, Ads, and AdsInit components successfully separates concerns from the root layout while maintaining the same functionality.

Copy link
Contributor

@anttimaki anttimaki left a comment

Choose a reason for hiding this comment

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

Marked few comments as resolved based on the changes. What are your thoughts on the remaining comments?

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