Skip to content

Comments

Add Portal Pooling#134

Merged
iamEvanYT merged 15 commits intomainfrom
evan/improve-portals
Jun 23, 2025
Merged

Add Portal Pooling#134
iamEvanYT merged 15 commits intomainfrom
evan/improve-portals

Conversation

@iamEvanYT
Copy link
Member

@iamEvanYT iamEvanYT commented Jun 23, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a portal provider system for managing and optimizing popup windows in the UI.
    • Added a utility to merge multiple React refs.
  • Refactor

    • Updated portal component logic to use a provider-driven approach, improving window management and positioning.
    • Replaced explicit position and size props with CSS classes and styles for portal-based UI elements like sidebars, popovers, and toasts.
    • Updated animation for toast notifications to use a scale effect.
  • Chores

    • Added the react-use package as a new dependency.
  • Style

    • Improved consistency in positioning and layering of floating UI components.
  • Documentation

    • No user-facing documentation changes.
  • Tests

    • No changes to tests.
  • Bug Fixes

    • Removed unnecessary delays when making portal windows visible, potentially reducing flicker.
  • Revert

    • Removed React.lazy-based code splitting from route components, switching to static imports for improved loading consistency.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 23, 2025

Walkthrough

This update refactors the portal system by introducing a provider-based portal window pool, refactors the PortalComponent to use this provider, and updates all usages to use CSS-based positioning instead of explicit coordinates. It also removes all React.lazy dynamic imports in route components, switching them to static imports.

Changes

Files/Paths Change Summary
src/renderer/src/components/portal/portal.tsx Refactored PortalComponent to use portal provider, changed props to CSS-based, updated API.
src/renderer/src/components/portal/provider.tsx New: Implements portal window pool/context, hooks, and provider.
src/renderer/src/lib/merge-refs.ts New utility: mergeRefs for combining multiple React refs.
src/main/browser/components/portal-component-windows.ts Removed 50ms delayed visibility for portal windows; now immediate.
src/renderer/src/components/portal/popover.tsx Switched portal positioning from explicit props to CSS classes.
src/renderer/src/components/browser-ui/browser-sidebar.tsx Updated PortalComponent usage: now uses CSS style, visibility, zIndex props.
src/renderer/src/components/providers/minimal-toast-provider.tsx Updated PortalComponent usage: now uses CSS style, className, and revised toast animation.
src/renderer/src/routes/main-ui/config.tsx Providers now wrap children in PortalsProvider.
src/renderer/src/routes/*/route.tsx (multiple route files) Removed React.lazy dynamic imports, replaced with static imports for page components.
package.json Added react-use dependency.

Sequence Diagram(s)

sequenceDiagram
    participant App
    participant PortalsProvider
    participant PortalComponent
    participant PortalWindow

    App->>PortalsProvider: Mounts provider
    PortalComponent->>PortalsProvider: Requests portal window
    PortalsProvider->>PortalWindow: Allocates or reuses window
    PortalComponent->>PortalWindow: Renders content via createPortal
    PortalComponent->>PortalWindow: Updates visibility, zIndex, bounds
    PortalComponent-->>PortalsProvider: Releases window on unmount
Loading

Possibly related PRs

  • MultiboxLabs/flow-browser#28: Introduced the initial Portal Components System, including PortalComponent and Electron backend window management, directly related to the portal refactor here.
  • MultiboxLabs/flow-browser#49: Added the now-removed 50ms delayed visibility logic for portal windows, which this PR reverses.
  • MultiboxLabs/flow-browser#102: Introduced React.lazy and Suspense for route components, which this PR now removes in favor of static imports.

Poem

In a warren of windows, the portals now play,
With a pool and a provider to manage the way.
No more lazy loading—routes leap in with glee,
And toasts and sidebars float CSS-fashionably.

🐇✨

A hop, a skip, and a merge of some refs—
This rabbit approves of these portal-esque steps!


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 35e4ced and 95689ab.

📒 Files selected for processing (1)
  • src/renderer/src/components/portal/provider.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/renderer/src/components/portal/provider.tsx
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: build (macos-latest)
  • GitHub Check: build (windows-latest)
  • GitHub Check: build (ubuntu-latest)
  • GitHub Check: build (ubuntu-24.04-arm)
  • GitHub Check: build (macos-13)
✨ 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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

Support

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

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

CodeRabbit Commands (Invoked using PR comments)

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

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

@github-actions
Copy link
Contributor

github-actions bot commented Jun 23, 2025

Build artifacts for all platforms are ready! 🚀

Download the artifacts from:

(execution 15829819520 / attempt 1)

@iamEvanYT iamEvanYT force-pushed the evan/improve-portals branch from 53bcbb0 to 5987fed Compare June 23, 2025 01:59
@iamEvanYT iamEvanYT force-pushed the evan/improve-portals branch from 80109c7 to 2626e1f Compare June 23, 2025 14:56
@iamEvanYT iamEvanYT marked this pull request as ready for review June 23, 2025 14:56
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

♻️ Duplicate comments (11)
src/renderer/src/routes/error/route.tsx (1)

1-3: Same performance concern as other route files.

This file has the same lazy loading removal pattern as other route files. The performance and Suspense redundancy concerns mentioned in the omnibox route review apply here as well.

src/renderer/src/routes/pdf-viewer/route.tsx (2)

1-3: Consistent lazy loading removal pattern.

The changes match the pattern seen across all route files - removing React.lazy in favor of static imports.


8-10: Redundant Suspense wrapper (duplicate concern).

The Suspense wrapper is no longer needed with static imports.

src/renderer/src/routes/main-ui/route.tsx (1)

8-10: Redundant Suspense wrapper (consistent across routes).

Same concern as other route files - the Suspense wrapper is now unnecessary.

src/renderer/src/routes/extensions/route.tsx (1)

8-10: Redundant Suspense wrapper (final occurrence).

As with all other route files, the Suspense wrapper is now redundant with static imports.

src/renderer/src/routes/games/route.tsx (1)

1-3: Redundant Suspense wrapper after removing lazy loading.

Same issue as other route files - the Suspense component is no longer needed since PageComponent is now statically imported.

Consider removing the Suspense wrapper or document why it's still required (e.g., for nested lazy components).

src/renderer/src/routes/omnibox-debug/route.tsx (1)

1-3: Architectural consistency maintained but Suspense is redundant.

The static import change is consistent across all route files, indicating a deliberate architectural shift away from code-splitting. However, the Suspense wrapper remains unnecessary.

Consider a global refactor to remove Suspense wrappers from all route files that no longer use lazy loading.

src/renderer/src/routes/about/route.tsx (1)

1-3: Performance impact of removing code-splitting should be evaluated.

While the change is architecturally consistent, removing lazy loading from all routes will increase the initial bundle size. This could impact application startup time, especially for routes with heavy components.

Consider measuring the bundle size impact and initial load performance before and after this change:

#!/bin/bash
# Description: Check bundle size impact of removing lazy loading

# Find webpack bundle analyzer or similar tool configuration
fd -e json package.json --exec cat {} \; | jq '.scripts | to_entries[] | select(.value | contains("bundle-analyzer") or contains("webpack-bundle-analyzer"))'

# Look for bundle size configuration
fd -e js -e ts webpack.config babel.config vite.config --exec head -20 {}
src/renderer/src/routes/new-tab/route.tsx (1)

1-3: Systematic removal of code-splitting requires architectural review.

This change pattern across all route files represents a significant architectural shift. For the new-tab route specifically, this could impact browser startup performance since new tabs are frequently opened.

Consider:

  1. Selective lazy loading: Keep code-splitting for heavy/rarely-used routes while removing it for frequently accessed ones
  2. Bundle splitting strategies: Use webpack's splitChunks to create shared chunks for common dependencies
  3. Progressive loading: Implement component-level lazy loading within routes instead of route-level

The introduction of the PortalsProvider (mentioned in the summary) suggests this change might be necessary for the new portal system. If so, document this architectural decision and its trade-offs.

src/renderer/src/components/portal/portal_old.tsx (2)

67-67: Invalid window features format (same issue as in provider.tsx).

The window features parameter has the same format issue as in the provider file.


125-125: Undefined flow object (same issue as in provider.tsx).

Multiple references to undefined flow object that will cause runtime errors.

Also applies to: 137-137, 143-143

🧹 Nitpick comments (6)
src/renderer/src/lib/merge-refs.ts (1)

1-11: Improve type safety in the ref assignment.

The type assertion on line 7 assumes the ref is a MutableRefObject without verification. Consider using a type guard for safer handling.

export function mergeRefs<T extends HTMLElement>(refs: Array<React.Ref<T> | undefined>): React.RefCallback<T> {
  return (value) => {
    for (const ref of refs) {
      if (typeof ref === "function") {
        ref(value);
-     } else if (ref != null) {
+     } else if (ref != null && typeof ref === "object" && "current" in ref) {
        (ref as React.MutableRefObject<T | null>).current = value;
      }
    }
  };
}
src/renderer/src/routes/onboarding/route.tsx (1)

8-10: Consider removing redundant Suspense wrapper.

Since PageComponent is now imported statically, the Suspense wrapper no longer serves its intended purpose of handling loading states during async component loading.

-      <Suspense fallback={RouteConfig.Fallback}>
-        <PageComponent />
-      </Suspense>
+      <PageComponent />
src/renderer/src/routes/popup-ui/route.tsx (1)

1-3: Consider removing redundant Suspense wrapper after eliminating lazy loading.

The removal of lazy import and switch to static import eliminates code-splitting for this route. However, the Suspense component is now redundant since PageComponent loads synchronously.

Consider simplifying the component structure:

-import { Suspense } from "react";
 import { RouteConfig } from "./config";
 import PageComponent from "./page";

 export default function Route() {
   return (
     <RouteConfig.Providers>
-      <Suspense fallback={RouteConfig.Fallback}>
         <PageComponent />
-      </Suspense>
     </RouteConfig.Providers>
   );
 }

Alternatively, if Suspense is still needed for nested lazy components or streaming SSR, consider adding a comment explaining its purpose.

src/renderer/src/routes/settings/route.tsx (1)

1-3: Consider the performance implications of removing lazy loading.

Removing React.lazy eliminates code-splitting for route components, which will increase the initial bundle size. Additionally, the Suspense wrapper on lines 8-10 is now redundant since there's no asynchronous component loading.

If this change is intentional as part of the portal refactor, consider removing the unnecessary Suspense wrapper to simplify the code.

-import { Suspense } from "react";
 import { RouteConfig } from "./config";
 import PageComponent from "./page";

 export default function Route() {
   return (
     <RouteConfig.Providers>
-      <Suspense fallback={RouteConfig.Fallback}>
-        <PageComponent />
-      </Suspense>
+      <PageComponent />
     </RouteConfig.Providers>
   );
 }

Also applies to: 8-10

src/renderer/src/components/portal/portal.tsx (1)

128-131: Use optional chaining for safer portal access.

The static analysis tool correctly identified that optional chaining would be safer here.

-  const wrapper =
-    portal &&
-    portal.window &&
-    !portal.window.closed &&
-    createPortal(portalChildren, portal.window.document.body, "portal-wrapper");
+  const wrapper =
+    portal?.window &&
+    !portal.window.closed &&
+    createPortal(portalChildren, portal.window.document.body, "portal-wrapper");
src/renderer/src/components/portal/provider.tsx (1)

29-40: Potential hoisting dependency in hot module reload cleanup.

The cleanup function calls removePortal before it's defined in the file. While JavaScript function hoisting makes this work, it creates a fragile dependency on declaration order.

Consider moving the utility functions before the hot module reload section or extracting the cleanup logic:

+const cleanupPortal = (portal: Portal) => {
+  portal.window.close();
+};
+
 if (import.meta.hot) {
   const cleanup = () => {
     if (window.portals) {
       // Close all available portals
       for (const portal of window.portals.available.values()) {
-        removePortal(portal);
+        cleanupPortal(portal);
+        window.portals.available.delete(portal.id);
       }
       // Close all used portals
       for (const portal of window.portals.used.values()) {
-        removePortal(portal);
+        cleanupPortal(portal);
+        window.portals.used.delete(portal.id);
       }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between d7fd8da and 35e4ced.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (22)
  • package.json (1 hunks)
  • src/main/browser/components/portal-component-windows.ts (1 hunks)
  • src/renderer/src/components/browser-ui/browser-sidebar.tsx (1 hunks)
  • src/renderer/src/components/portal/popover.tsx (1 hunks)
  • src/renderer/src/components/portal/portal.tsx (2 hunks)
  • src/renderer/src/components/portal/portal_old.tsx (1 hunks)
  • src/renderer/src/components/portal/provider.tsx (1 hunks)
  • src/renderer/src/components/providers/minimal-toast-provider.tsx (1 hunks)
  • src/renderer/src/lib/merge-refs.ts (1 hunks)
  • src/renderer/src/routes/about/route.tsx (1 hunks)
  • src/renderer/src/routes/error/route.tsx (1 hunks)
  • src/renderer/src/routes/extensions/route.tsx (1 hunks)
  • src/renderer/src/routes/games/route.tsx (1 hunks)
  • src/renderer/src/routes/main-ui/config.tsx (1 hunks)
  • src/renderer/src/routes/main-ui/route.tsx (1 hunks)
  • src/renderer/src/routes/new-tab/route.tsx (1 hunks)
  • src/renderer/src/routes/omnibox-debug/route.tsx (1 hunks)
  • src/renderer/src/routes/omnibox/route.tsx (1 hunks)
  • src/renderer/src/routes/onboarding/route.tsx (1 hunks)
  • src/renderer/src/routes/pdf-viewer/route.tsx (1 hunks)
  • src/renderer/src/routes/popup-ui/route.tsx (1 hunks)
  • src/renderer/src/routes/settings/route.tsx (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/renderer/src/routes/main-ui/config.tsx (2)
src/renderer/src/types/routes.ts (1)
  • RouteConfigType (3-6)
src/renderer/src/components/portal/provider.tsx (1)
  • PortalsProvider (167-227)
src/renderer/src/components/portal/portal.tsx (4)
src/renderer/src/components/portal/provider.tsx (1)
  • usePortalsProvider (73-79)
src/renderer/src/lib/merge-refs.ts (1)
  • mergeRefs (1-11)
src/renderer/src/components/main/platform.tsx (1)
  • PlatformConsumer (143-146)
src/renderer/src/lib/utils.ts (1)
  • cn (5-7)
src/renderer/src/components/providers/minimal-toast-provider.tsx (1)
src/renderer/src/lib/utils.ts (1)
  • cn (5-7)
🪛 Biome (1.9.4)
src/renderer/src/components/portal/portal.tsx

[error] 128-129: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: build (ubuntu-24.04-arm)
  • GitHub Check: build (ubuntu-latest)
🔇 Additional comments (15)
package.json (1)

43-43: Verify the react-use package version compatibility and security.

Ensure the react-use version is compatible with React 19.1.0 and check for any security vulnerabilities.

Is react-use version 17.6.0 compatible with React 19 and are there any known security vulnerabilities in this version?
src/main/browser/components/portal-component-windows.ts (1)

86-86: Verify that immediate visibility doesn't cause rendering issues.

The removal of the 50ms delay when setting component visibility might cause timing issues if the component isn't fully initialized. Please ensure this change doesn't introduce visual glitches or race conditions.

#!/bin/bash
# Search for any comments or documentation about the original delay rationale
rg -A 5 -B 5 "setVisible.*true|setTimeout.*setVisible" --type ts
src/renderer/src/routes/omnibox/route.tsx (1)

1-3: Consider the performance impact of removing lazy loading.

Removing React.lazy eliminates code-splitting benefits, potentially increasing the initial bundle size. Since this pattern is applied across all route files, please confirm this aligns with performance requirements.

The Suspense wrapper is now redundant without lazy loading. Consider removing it unless there are other async components:

-      <Suspense fallback={RouteConfig.Fallback}>
         <PageComponent />
-      </Suspense>
src/renderer/src/routes/onboarding/route.tsx (2)

3-3: Direct import replaces lazy loading.

The static import eliminates asynchronous loading behavior for better predictability.


1-1: I’m not seeing any TSX files scanned because ripgrep doesn’t recognize “.tsx” by default. Let’s explicitly target TSX files and look for any remaining lazy usage:

#!/bin/bash
# Check for React.lazy imports in all TSX files
rg "import.*\{[^}]*lazy[^}]*\}\s*from\s*['\"]react['\"]" -g "*.tsx" || echo "No React.lazy imports found"
# Check for any lazy(...) calls in TSX files
rg "lazy\s*\(" -g "*.tsx" || echo "No lazy(...) calls found"

This will confirm whether any code-splitting remains and if all Suspense wrappers are now redundant.

src/renderer/src/routes/main-ui/config.tsx (2)

1-1: Portal provider integration added.

The import introduces the new portal pooling system mentioned in the PR objectives.


7-7: Proper context provider wrapping.

The PortalsProvider correctly wraps children, providing portal management context to all route components. This aligns with the RouteConfigType interface requirements.

src/renderer/src/routes/main-ui/route.tsx (1)

1-3: Consistent architectural change applied.

The lazy loading removal follows the same pattern as other routes and works well with the new PortalsProvider integration from config.tsx.

src/renderer/src/routes/extensions/route.tsx (1)

1-3: Final route updated with consistent pattern.

This completes the systematic removal of lazy loading across all route files, creating a consistent synchronous loading architecture.

src/renderer/src/components/portal/popover.tsx (1)

41-41: LGTM! Correct migration to CSS-based positioning.

The conversion from explicit positioning props to Tailwind CSS classes correctly maintains the full-screen overlay behavior while aligning with the new portal system architecture.

src/renderer/src/components/browser-ui/browser-sidebar.tsx (1)

265-274: ```shell
#!/bin/bash

Locate the PortalComponent definition(s)

rg -n "export function PortalComponent" --glob "*.tsx" -A5 -B5

Inspect the PortalComponentProps to see if style/className are accepted

rg -n "type PortalComponentProps" --glob "*.tsx" -A5 -B5

Check for any remaining uses of anchorX in the repo

rg -n "anchorX" -A5 -B5


</details>
<details>
<summary>src/renderer/src/components/providers/minimal-toast-provider.tsx (1)</summary>

`48-56`: **LGTM! Clean migration to CSS-based positioning and improved animation.**

The CSS-based positioning correctly preserves the sidebar-aware toast placement, and the simplified scale animation with top transform origin provides a cleaner effect.



Also applies to: 68-72

</details>
<details>
<summary>src/renderer/src/components/portal/portal.tsx (2)</summary>

`36-43`: **Well-implemented refactor to the provider-based portal system.**

The migration from self-managed popup windows to the portal provider system is clean and maintains all necessary functionality. Good error handling and proper use of memoization for performance.



Also applies to: 44-139

---

`44-46`: ```shell
#!/bin/bash
set -eux

# Extract releasePortal and surrounding context
sed -n '120,180p' src/renderer/src/components/portal/provider.tsx

# Extract usePortal and surrounding context
sed -n '180,260p' src/renderer/src/components/portal/provider.tsx
src/renderer/src/components/portal/provider.tsx (1)

138-138: Let’s confirm that FlowInterfaceAPI includes the setComponentWindowVisible method:

#!/bin/bash
# Show FlowInterfaceAPI methods
rg -A20 "export interface FlowInterfaceAPI" -n src/shared/flow/interfaces/browser/interface.ts

@iamEvanYT iamEvanYT added the enhancement New feature or request label Jun 23, 2025
@iamEvanYT iamEvanYT merged commit 796b740 into main Jun 23, 2025
10 checks passed
@iamEvanYT iamEvanYT deleted the evan/improve-portals branch June 23, 2025 16:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant