Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@TWilson023
Copy link
Collaborator

@TWilson023 TWilson023 commented Nov 18, 2025

  • Give workspace owners full permissions for all folders
  • Disable access selection w/ tooltip for workspace owner users
  • Show all folders (including "no access") to workspace owners in the folder list
Screenshot 2025-11-19 at 12 41 18β€―PM

Summary by CodeRabbit

  • New Features

    • Folder access now respects workspace owner status for improved permission handling.
    • Folder user information now includes workspace role details.
  • Bug Fixes

    • Improved folder access controls to correctly evaluate member permissions based on workspace role.
    • Link export search mode now adapts dynamically based on total workspace link count (1M+).
  • Documentation

    • Updated help links for folder management and default folder settings.
  • Refactor

    • Enhanced folder management UI with improved permission displays and tooltips for workspace owners.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link
Contributor

vercel bot commented Nov 18, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Nov 19, 2025 11:42pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 18, 2025

Warning

Rate limit exceeded

@steven-tey has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 1 minutes and 53 seconds before requesting another review.

βŒ› How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 9e3e503 and 486904c.

πŸ“’ Files selected for processing (35)
  • .github/workflows/e2e.yaml (1 hunks)
  • apps/web/app/(ee)/api/cron/import/csv/route.ts (3 hunks)
  • apps/web/app/(ee)/api/cron/links/export/route.ts (4 hunks)
  • apps/web/app/(ee)/api/embed/referrals/links/[linkId]/route.ts (1 hunks)
  • apps/web/app/(ee)/api/embed/referrals/links/route.ts (1 hunks)
  • apps/web/app/(ee)/api/events/route.ts (2 hunks)
  • apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/[linkId]/route.ts (1 hunks)
  • apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/route.ts (1 hunks)
  • apps/web/app/api/analytics/export/route.ts (1 hunks)
  • apps/web/app/api/folders/[folderId]/users/route.ts (3 hunks)
  • apps/web/app/api/folders/count/route.ts (2 hunks)
  • apps/web/app/api/folders/permissions/route.ts (2 hunks)
  • apps/web/app/api/links/bulk/route.ts (5 hunks)
  • apps/web/app/api/links/export/route.ts (3 hunks)
  • apps/web/app/api/links/route.ts (3 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/campaigns-table.tsx (1 hunks)
  • apps/web/lib/api/links/process-link.ts (2 hunks)
  • apps/web/lib/api/links/utils/key-checks.ts (2 hunks)
  • apps/web/lib/api/links/validate-links-query-filters.ts (2 hunks)
  • apps/web/lib/api/partners/generate-partner-link.ts (1 hunks)
  • apps/web/lib/constants/misc.ts (1 hunks)
  • apps/web/lib/folder/get-folders.ts (2 hunks)
  • apps/web/lib/folder/permissions.ts (6 hunks)
  • apps/web/lib/swr/use-workspace.ts (2 hunks)
  • apps/web/lib/types.ts (1 hunks)
  • apps/web/tests/links/folder-link-access.test.ts (3 hunks)
  • apps/web/tests/utils/env.ts (1 hunks)
  • apps/web/tests/utils/integration-member.ts (1 hunks)
  • apps/web/tests/utils/resource.ts (2 hunks)
  • apps/web/ui/folders/edit-folder-sheet.tsx (6 hunks)
  • apps/web/ui/folders/folder-actions.tsx (2 hunks)
  • apps/web/ui/folders/folder-dropdown.tsx (1 hunks)
  • apps/web/ui/folders/folder-info-panel.tsx (3 hunks)
  • apps/web/ui/modals/set-default-folder-modal.tsx (1 hunks)
  • packages/ui/src/nav/content/resources-content.tsx (1 hunks)

Walkthrough

Introduces workspace role-based access control for folder permissions. Refactors the permission system to incorporate workspace ownership, replacing folder-only checks with unified workspace/folder role resolution. Replaces hard-coded mega workspace threshold with a constant. Extends API responses with workspace role data and updates folder access queries accordingly. Renames and enhances folder UI components. Adds member-specific integration test infrastructure.

Changes

Cohort / File(s) Summary
Folder Permission System Refactor
apps/web/lib/folder/permissions.ts
Renamed findUserFolderRole β†’ findFolderUserRole. Extended verifyFolderAccess signature to accept workspace with users role array. Added early owner shortcut and verifyFolderAccessBulk for bulk operations. Added getFolderPermissions helper. Refactored role resolution to incorporate workspace owner status.
Folder Access & Queries
apps/web/lib/folder/get-folders.ts, apps/web/app/api/folders/count/route.ts
Added workspace role querying. Introduced conditional folder filters based on workspace ownership: non-owners see restricted folders (accessLevel non-null or user has non-null role); owners see all folders. Imported WorkspaceRole from Prisma.
Folder API Routes
apps/web/app/api/folders/[folderId]/users/route.ts, apps/web/app/api/folders/permissions/route.ts
Updated imports to use findFolderUserRole. Added workspaceRole to returned user objects. Passed workspace role to permission resolution calls.
Mega Workspace Threshold Extraction
apps/web/lib/constants/misc.ts, apps/web/lib/swr/use-workspace.ts, apps/web/app/api/links/route.ts, apps/web/app/api/links/export/route.ts, apps/web/app/(ee)/api/cron/links/export/route.ts
Introduced constant MEGA_WORKSPACE_LINKS_LIMIT = 1_000_000. Replaced hard-coded threshold checks throughout with constant reference. Updated link export logic to use workspace totalLinks threshold instead of folder type.
Bulk Links & Permissions
apps/web/app/api/links/bulk/route.ts
Replaced checkFolderPermissions with verifyFolderAccessBulk. Updated error message for folder permission denial. Updated parameter passing from workspaceId to workspace object.
Links Query Validation
apps/web/lib/api/links/validate-links-query-filters.ts
Added users to workspace interface in LinksQueryFilters. Removed selectedFolder from return object; now returns only folderIds. Updated verifyFolderAccess call to use new signature with requiredPermission: "folders.read".
Link Processing & Utilities
apps/web/lib/api/links/process-link.ts, apps/web/lib/api/links/utils/key-checks.ts
Updated workspace parameter type from Pick<WorkspaceProps, ...> to Pick<Project, "id" | "plan"> & { users: { role: WorkspaceRole }[] }. Updated imports to use Project instead of WorkspaceProps.
Embed & Import Routes
apps/web/app/(ee)/api/embed/referrals/links/[linkId]/route.ts, apps/web/app/(ee)/api/embed/referrals/links/route.ts, apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/[linkId]/route.ts, apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/route.ts, apps/web/app/(ee)/api/cron/import/csv/route.ts, apps/web/lib/api/partners/generate-partner-link.ts
Added owner-role user to workspace payload passed to processLink. Updated to pass full workspace object instead of constructed partial objects.
Type Updates
apps/web/lib/types.ts
Added workspaceRole: WorkspaceRole field to FolderUser type.
Folder UI Components
apps/web/ui/folders/edit-folder-sheet.tsx, apps/web/ui/folders/folder-actions.tsx
Renamed components: EditFolderPanel* β†’ EditFolderSheet* and hook useEditFolderPanel β†’ useEditFolderSheet. Added WorkspaceRole import and workspace owner access control logic. Added tooltip wrapping for role select when user is workspace owner.
Folder Info & Display
apps/web/ui/folders/folder-info-panel.tsx
Changed UserList signature to accept isLoading boolean. Replaced Tooltip-wrapped user display with loading skeleton support and simplified rendering.
Test Infrastructure
apps/web/tests/utils/integration-member.ts, apps/web/tests/utils/env.ts, apps/web/tests/utils/resource.ts, apps/web/tests/links/folder-link-access.test.ts
Added IntegrationHarnessMember class for member-specific testing. Added E2E_TOKEN_MEMBER to environment schema. Added E2E_USER_ID_MEMBER constant. Updated test resource IDs and error message expectations.
Events & Export Routes
apps/web/app/(ee)/api/events/route.ts, apps/web/app/api/analytics/export/route.ts
Removed Folder import; eliminated selectedFolder variable. Updated verifyFolderAccess call with required permission. Cast zip data to BodyInit in Response constructor.
Documentation & Configuration
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/campaigns-table.tsx, apps/web/ui/modals/set-default-folder-modal.tsx, packages/ui/src/nav/content/resources-content.tsx, .github/workflows/e2e.yaml, apps/web/ui/folders/folder-dropdown.tsx
Updated documentation links in modals and tables. Added E2E_TOKEN_MEMBER to workflow environment. Removed unused searchParams from hook destructuring. Removed inline comment from thumbnail URL.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client Request
    participant Route as API Route
    participant GetFolders as getFolders()
    participant Prisma as Prisma Query
    participant Permission as Permission Check

    Client->>Route: GET /api/folders/[folderId]/users
    Route->>GetFolders: Fetch workspace users with role
    GetFolders->>Prisma: Query workspace.users[0].role
    
    alt User is Workspace Owner
        Prisma-->>GetFolders: WorkspaceRole.owner
        GetFolders->>Permission: findFolderUserRole(user, workspaceRole)
        Permission-->>GetFolders: Early return (owner override)
    else User is Member
        Prisma-->>GetFolders: WorkspaceRole.member
        GetFolders->>Prisma: Query folders with conditional filters<br/>(accessLevel non-null OR user has role)
        Prisma-->>GetFolders: Filtered folders
        GetFolders->>Permission: findFolderUserRole(user, workspaceRole)
        Permission-->>GetFolders: Computed folder-level role
    end
    
    GetFolders-->>Route: Users with folder roles + workspace role
    Route-->>Client: HTTP 200 with workspaceRole in response
Loading
sequenceDiagram
    participant Test as Test Case
    participant Harness as IntegrationHarnessMember
    participant API as API Route
    participant DB as Database

    Test->>Harness: new IntegrationHarnessMember()
    Harness-->>Test: Initialize with E2E_TOKEN_MEMBER
    Test->>Harness: init()
    Harness-->>Test: Resources + HttpClient (member auth)
    
    Test->>API: DELETE /api/links/[linkId] (member token)
    API->>API: verifyFolderAccessBulk(workspace, userId, folderIds)
    API->>DB: Check workspace role + folder permissions
    
    alt Member has folder access
        DB-->>API: Permission granted
        API-->>Test: HTTP 200 (deleted)
    else Member lacks folder access
        DB-->>API: Permission denied
        API-->>Test: HTTP 403 (access denied)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Key areas requiring attention:
    • apps/web/lib/folder/permissions.ts: Significant refactor of permission resolution logic including new owner shortcut and verifyFolderAccessBulk implementation
    • apps/web/lib/folder/get-folders.ts: Conditional query construction based on workspace role; verify filter correctness for both owner and member paths
    • apps/web/app/api/folders/count/route.ts: Similar conditional filtering; ensure parity with get-folders.ts
    • apps/web/tests/utils/integration-member.ts: New test infrastructure; verify HttpClient initialization and resource wiring
    • apps/web/lib/api/links/validate-links-query-filters.ts: Change from returning selectedFolder to workspace-wide threshold; verify all call sites updated correctly
    • Cross-file consistency: Verify workspace role is correctly threaded through all permission checks and API responses

Possibly related PRs

  • PR #2764: Modifies folder dropdown and folderId sourcing. Related to folder access control refactoring affecting how folder context is handled.

Suggested reviewers

  • devkiran

Poem

🐰 With workspace roles now woven through the fold,
Permissions dance where members and owners hold,
No longer guessing mega-thresholds in the nightβ€”
A constant guides us to the true insight,
From test harnesses to sheets that shine so bright! πŸŽ‰

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% 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 pull request title clearly summarizes the main objective: implementing workspace owner folder permissions.

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.

@TWilson023 TWilson023 marked this pull request as ready for review November 19, 2025 17:41
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/lib/folder/permissions.ts (1)

92-113: Workspace owners are not honored in checkFolderPermissions

findUserFolderRole now upgrades WorkspaceRole.owner to FolderUserRole.owner, but checkFolderPermissions still calls it without a workspaceRole, so workspace owners won’t get the owner override when checking permissions across multiple folders (e.g., bulk operations).

You can mirror the pattern from verifyFolderAccess / getFolders by looking up the workspace user once and passing the role through:

 export const checkFolderPermissions = async ({
   workspaceId,
   userId,
   folderIds,
   requiredPermission,
 }: {
   workspaceId: string;
   userId: string;
   folderIds: string[];
   requiredPermission: FolderPermission;
 }) => {
+  const workspaceUser = await prisma.projectUsers.findUnique({
+    select: {
+      role: true,
+    },
+    where: {
+      userId_projectId: {
+        userId,
+        projectId: workspaceId,
+      },
+    },
+  });
+
   const folders = await prisma.folder.findMany({
@@
   return folders.map((folder) => {
     const folderUserRole = findUserFolderRole({
       folder,
-      user: folder.users[0],
+      user: folder.users[0] || null,
+      workspaceRole: workspaceUser?.role,
     });

This keeps owner semantics consistent across folder read/write checks and the bulk permission helper.

Also applies to: 143-163

🧹 Nitpick comments (4)
apps/web/app/api/folders/[folderId]/users/route.ts (1)

55-72: Using workspaceRole in findUserFolderRole and response correctly elevates owners

Deriving workspaceRole from projectUsers, feeding it into findUserFolderRole, and returning it alongside the effective folder role makes this endpoint reflect the new β€œworkspace owners have full control” rule while still honoring per‑folder roles for other members. The linear folderUsers.find(...) is fine for typical workspace sizes; if this ever becomes hot, you could micro‑optimize via a Map keyed by userId.

apps/web/app/api/folders/count/route.ts (1)

4-5: Owner-aware count filter now matches folder listing behavior

Conditionally applying the OR + users.none filter only when workspaceRole !== WorkspaceRole.owner ensures non‑owners only count accessible folders while workspace owners see a count of all folders in the workspace. This mirrors the logic in getFolders, which is important for consistent pagination. Long term, consider extracting this predicate into a shared helper to avoid the two queries drifting apart.

Also applies to: 14-39

apps/web/ui/folders/edit-folder-panel.tsx (1)

252-256: Disabling role updates for workspace owners and adding tooltip match the spec

isWorkspaceOwner and the expanded disableRoleUpdate correctly prevent editing roles for workspace owners, and wrapping the select with DynamicTooltipWrapper for other workspace owners clarifies why the control is disabled. You might also consider showing the same tooltip for the current user when they are the workspace owner so they’re not left guessing why their own control is disabled.

Also applies to: 272-277

apps/web/lib/folder/get-folders.ts (1)

2-3: WorkspaceRole-aware folder query correctly exposes all folders to owners

Looking up workspaceUser via projectUsers.findUnique and conditionally applying the OR + users.none constraints only when workspaceUser?.role !== WorkspaceRole.owner gives non‑owners the expected access filter while letting workspace owners see every folder in the workspace (subject to search/type filters). This aligns with the updated count route and the PR intent. There is some duplication with /api/folders/count; factoring the shared predicate into a helper later could help keep them in lockstep.

Also applies to: 21-31, 36-62

πŸ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 6ef200f and cb78304.

πŸ“’ Files selected for processing (7)
  • apps/web/app/api/folders/[folderId]/users/route.ts (2 hunks)
  • apps/web/app/api/folders/count/route.ts (2 hunks)
  • apps/web/app/api/folders/permissions/route.ts (1 hunks)
  • apps/web/lib/folder/get-folders.ts (2 hunks)
  • apps/web/lib/folder/permissions.ts (3 hunks)
  • apps/web/lib/types.ts (1 hunks)
  • apps/web/ui/folders/edit-folder-panel.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (3)
πŸ““ Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
πŸ“š Learning: 2025-10-06T15:48:45.956Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2935
File: packages/prisma/schema/workspace.prisma:21-36
Timestamp: 2025-10-06T15:48:45.956Z
Learning: In the Dub repository (dubinc/dub), Prisma schema changes are not managed with separate migration files. Do not flag missing Prisma migration files when schema changes are made to files like `packages/prisma/schema/workspace.prisma` or other schema files.

Applied to files:

  • apps/web/lib/folder/get-folders.ts
  • apps/web/lib/folder/permissions.ts
πŸ“š Learning: 2025-10-15T01:52:37.048Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx:270-303
Timestamp: 2025-10-15T01:52:37.048Z
Learning: In React components with dropdowns or form controls that show modals for confirmation (e.g., role selection, delete confirmation), local state should be reverted to match the prop value when the modal is cancelled. This prevents the UI from showing an unconfirmed change. The solution is to either: (1) pass an onClose callback to the modal that resets the local state, or (2) observe modal visibility state and reset on close. Example context: RoleCell component in apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx where role dropdown should revert to user.role when UpdateUserModal is cancelled.

Applied to files:

  • apps/web/ui/folders/edit-folder-panel.tsx
🧬 Code graph analysis (6)
apps/web/lib/types.ts (1)
packages/prisma/client.ts (1)
  • WorkspaceRole (36-36)
apps/web/app/api/folders/[folderId]/users/route.ts (1)
apps/web/lib/folder/permissions.ts (1)
  • findUserFolderRole (92-113)
apps/web/app/api/folders/count/route.ts (2)
packages/prisma/index.ts (1)
  • prisma (3-9)
packages/prisma/client.ts (1)
  • WorkspaceRole (36-36)
apps/web/lib/folder/get-folders.ts (2)
packages/prisma/index.ts (1)
  • prisma (3-9)
packages/prisma/client.ts (1)
  • WorkspaceRole (36-36)
apps/web/ui/folders/edit-folder-panel.tsx (2)
packages/ui/src/tooltip.tsx (1)
  • DynamicTooltipWrapper (204-218)
apps/web/lib/folder/constants.ts (1)
  • FOLDER_USER_ROLE (9-13)
apps/web/lib/folder/permissions.ts (2)
packages/prisma/index.ts (1)
  • prisma (3-9)
apps/web/lib/types.ts (2)
  • Folder (515-515)
  • FolderUser (521-524)
πŸ”‡ Additional comments (3)
apps/web/lib/folder/permissions.ts (1)

5-10: Workspace owner role lookup correctly feeds permission resolution

Importing Project, FolderUserRole, and WorkspaceRole and then fetching workspaceUser.role via projectUsers.findUniqueOrThrow and passing it into findUserFolderRole gives verifyFolderAccess the context it needs to treat workspace owners as folder owners. This aligns with the PR goal without changing behavior for non‑owners.

Also applies to: 44-60

apps/web/app/api/folders/permissions/route.ts (1)

28-37: Propagating workspaceRole into findUserFolderRole matches owner semantics

Passing workspaceRole: workspace.users[0]?.role into findUserFolderRole ensures workspace owners are surfaced as folder owners in this permissions listing, consistent with the new backend logic. This looks correct as long as withWorkspace guarantees that workspace.users[0] corresponds to the current member.

apps/web/lib/types.ts (1)

521-524: Extending FolderUser with workspaceRole keeps API and UI in sync

Adding workspaceRole: WorkspaceRole to FolderUser aligns the shared type with the updated /folders/[folderId]/users response and the folder‑editing UI, and it makes workspace ownership explicit wherever folder users are handled. TypeScript should help surface any remaining call sites that construct FolderUser without this field.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/web/lib/actions/folders/request-folder-edit-access.ts (1)

24-29: Workspace owners should not be able to request folder access.

According to the PR objectives, workspace owners have full permissions for all folders. However, verifyFolderAccess now returns true for workspace owners (as shown in the relevant code snippet), allowing them to proceed with creating access requests. This is illogicalβ€”workspace owners shouldn't request access they already possess.

Add an early check to prevent workspace owners from requesting access:

   const { workspace, user } = ctx;
   const { folderId } = parsedInput;

+  // Workspace owners already have full permissions for all folders
+  const isWorkspaceOwner = workspace.users.some(u => u.id === user.id && u.role === 'owner');
+  if (isWorkspaceOwner) {
+    throw new Error(
+      "Workspace owners already have full permissions for all folders.",
+    );
+  }
+
   await verifyFolderAccess({
     workspace,
     userId: user.id,
     folderId,
     requiredPermission: "folders.read",
   });

Note: Ensure the workspace.users array in the context includes the user ID for this check to work correctly.

apps/web/app/api/folders/[folderId]/users/route.ts (1)

14-19: Folder data is no longer available after verifyFolderAccess changes.

verifyFolderAccess now returns a boolean instead of the Folder object, but the code below (line 60) still references an undefined folder variable. This will cause a runtime error.

You need to fetch the folder separately:

     await verifyFolderAccess({
       workspace,
       userId: session.user.id,
       folderId,
       requiredPermission: "folders.read",
     });
 
+    const folder = await prisma.folder.findUnique({
+      where: { id: folderId },
+      select: {
+        id: true,
+        accessLevel: true,
+      },
+    });
+
+    if (!folder) {
+      throw new DubApiError({
+        code: "not_found",
+        message: "Folder not found.",
+      });
+    }
+
     const [workspaceUsers, folderUsers] = await Promise.all([
🧹 Nitpick comments (3)
apps/web/tests/utils/integration-member.ts (1)

14-70: Consider refactoring to reduce code duplication.

The IntegrationHarnessMember class appears to duplicate significant logic from the existing IntegrationHarness class. Consider refactoring to share common functionality between the two harnesses, perhaps through:

  • A base class with shared initialization logic
  • A factory method that accepts authentication tokens
  • Composition with a shared helper module

This would improve maintainability and reduce the risk of divergence between the two implementations.

apps/web/lib/api/links/process-link.ts (1)

34-34: Consider whether the users field is needed in this file.

The workspace type was extended to include users, but this field is not referenced anywhere in the processLink function. While this may be for consistency with other modules or future use, consider whether it's necessary to require callers to provide this field if it's not used.

apps/web/app/api/analytics/export/route.ts (1)

95-95: Address TypeScript configuration gap causing the double cast workaround.

The double cast as unknown as BodyInit is a workaround for a real type definition issue. Investigation confirms:

  1. Root cause: tsconfig.json lib array missing "node" β€” it only includes ["dom", "dom.iterable", "esnext"], so TypeScript doesn't recognize Node.js Buffer type.

  2. Type mismatch: JSZip's generateAsync({ type: "nodebuffer" }) explicitly returns Node.js Buffer, but BodyInit (Fetch API spec) doesn't include Buffer in its type definition. TypeScript cannot verify compatibility.

  3. Runtime vs. type-check: Code works fine at runtime since Node.js Response accepts Buffer, but TypeScript lacks the type definitions to prove this.

Recommended fix: Add "node" to tsconfig.json lib array, or convert Buffer to Uint8Array before passing to Response to align with standard Fetch API types.

πŸ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between dd70169 and f2a202f.

πŸ“’ Files selected for processing (19)
  • .github/workflows/e2e.yaml (1 hunks)
  • apps/web/app/(ee)/api/cron/links/export/route.ts (4 hunks)
  • apps/web/app/(ee)/api/events/route.ts (2 hunks)
  • apps/web/app/api/analytics/export/route.ts (1 hunks)
  • apps/web/app/api/folders/[folderId]/users/route.ts (3 hunks)
  • apps/web/app/api/links/bulk/route.ts (4 hunks)
  • apps/web/app/api/links/export/route.ts (3 hunks)
  • apps/web/app/api/links/route.ts (3 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/campaigns-table.tsx (1 hunks)
  • apps/web/lib/actions/folders/request-folder-edit-access.ts (2 hunks)
  • apps/web/lib/api/links/process-link.ts (1 hunks)
  • apps/web/lib/api/links/validate-links-query-filters.ts (2 hunks)
  • apps/web/lib/constants/misc.ts (1 hunks)
  • apps/web/lib/folder/permissions.ts (4 hunks)
  • apps/web/lib/swr/use-workspace.ts (2 hunks)
  • apps/web/tests/links/folder-link-access.test.ts (3 hunks)
  • apps/web/tests/utils/env.ts (1 hunks)
  • apps/web/tests/utils/integration-member.ts (1 hunks)
  • apps/web/tests/utils/resource.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/app/api/links/bulk/route.ts
🧰 Additional context used
🧠 Learnings (6)
πŸ““ Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
πŸ“š Learning: 2025-10-15T01:05:43.266Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/campaigns-table.tsx
πŸ“š Learning: 2025-09-24T15:47:40.293Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/about-you-form.tsx:152-157
Timestamp: 2025-09-24T15:47:40.293Z
Learning: The Button component from dub/ui automatically adds type="button" when an onClick prop is passed, preventing accidental form submissions without requiring explicit type specification. The implementation uses: type={onClick ? "button" : type}

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/campaigns-table.tsx
πŸ“š Learning: 2025-06-06T07:59:03.120Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.

Applied to files:

  • apps/web/app/api/links/route.ts
  • apps/web/tests/links/folder-link-access.test.ts
  • apps/web/app/(ee)/api/cron/links/export/route.ts
  • apps/web/app/api/links/export/route.ts
  • apps/web/lib/api/links/validate-links-query-filters.ts
πŸ“š Learning: 2025-08-26T15:05:55.081Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/lib/swr/use-bounty.ts:11-16
Timestamp: 2025-08-26T15:05:55.081Z
Learning: In the Dub codebase, workspace authentication and route structures prevent endless loading states when workspaceId or similar route parameters are missing, so gating SWR loading states on parameter availability is often unnecessary.

Applied to files:

  • apps/web/lib/swr/use-workspace.ts
πŸ“š Learning: 2025-10-06T15:48:45.956Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2935
File: packages/prisma/schema/workspace.prisma:21-36
Timestamp: 2025-10-06T15:48:45.956Z
Learning: In the Dub repository (dubinc/dub), Prisma schema changes are not managed with separate migration files. Do not flag missing Prisma migration files when schema changes are made to files like `packages/prisma/schema/workspace.prisma` or other schema files.

Applied to files:

  • apps/web/lib/folder/permissions.ts
🧬 Code graph analysis (11)
apps/web/app/api/links/route.ts (2)
apps/web/lib/api/links/validate-links-query-filters.ts (1)
  • validateLinksQueryFilters (14-68)
apps/web/lib/constants/misc.ts (1)
  • MEGA_WORKSPACE_LINKS_LIMIT (3-3)
apps/web/lib/swr/use-workspace.ts (1)
apps/web/lib/constants/misc.ts (1)
  • MEGA_WORKSPACE_LINKS_LIMIT (3-3)
apps/web/tests/links/folder-link-access.test.ts (2)
apps/web/tests/utils/integration-member.ts (1)
  • IntegrationHarnessMember (15-70)
apps/web/tests/utils/resource.ts (2)
  • E2E_NO_ACCESS_FOLDER_ID (36-36)
  • E2E_READ_ONLY_FOLDER_ID (35-35)
apps/web/app/(ee)/api/cron/links/export/route.ts (2)
apps/web/lib/api/links/validate-links-query-filters.ts (1)
  • validateLinksQueryFilters (14-68)
apps/web/lib/constants/misc.ts (1)
  • MEGA_WORKSPACE_LINKS_LIMIT (3-3)
apps/web/lib/actions/folders/request-folder-edit-access.ts (1)
apps/web/lib/folder/permissions.ts (1)
  • verifyFolderAccess (15-68)
apps/web/app/(ee)/api/events/route.ts (1)
apps/web/lib/folder/permissions.ts (1)
  • verifyFolderAccess (15-68)
apps/web/tests/utils/integration-member.ts (2)
apps/web/tests/utils/env.ts (2)
  • integrationTestEnv (3-13)
  • env (15-15)
apps/web/tests/utils/resource.ts (2)
  • E2E_USER_ID_MEMBER (2-2)
  • E2E_WORKSPACE_ID (3-3)
apps/web/app/api/folders/[folderId]/users/route.ts (1)
apps/web/lib/folder/permissions.ts (2)
  • verifyFolderAccess (15-68)
  • findUserFolderRole (81-97)
apps/web/lib/folder/permissions.ts (2)
apps/web/lib/plan-capabilities.ts (1)
  • getPlanCapabilities (4-21)
apps/web/lib/folder/get-folder-or-throw.ts (1)
  • getFolderOrThrow (4-59)
apps/web/app/api/links/export/route.ts (2)
apps/web/lib/api/links/validate-links-query-filters.ts (1)
  • validateLinksQueryFilters (14-68)
apps/web/lib/constants/misc.ts (1)
  • MEGA_WORKSPACE_LINKS_LIMIT (3-3)
apps/web/lib/api/links/validate-links-query-filters.ts (2)
apps/web/lib/zod/schemas/links.ts (1)
  • getLinksQuerySchemaExtended (850-867)
apps/web/lib/folder/permissions.ts (1)
  • verifyFolderAccess (15-68)
⏰ 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: build
πŸ”‡ Additional comments (22)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/campaigns-table.tsx (1)

200-202: The help article URL is valid and accessible.

The URL "https://dub.co/help/article/email-campaigns" returns a 200 response and is properly configured. The change is syntactically correct and functional.

apps/web/lib/actions/folders/request-folder-edit-access.ts (1)

46-59: LGTM! Efficient optimization.

Including the folder data in the create query eliminates the need for a separate fetch, reducing round-trips to the database. The selected fields (name and id) are exactly what's needed for the email notification downstream.

.github/workflows/e2e.yaml (1)

38-38: LGTM!

The addition of E2E_TOKEN_MEMBER follows the established pattern for test authentication tokens and properly uses GitHub secrets.

apps/web/tests/utils/env.ts (1)

6-6: LGTM!

The environment variable validation for E2E_TOKEN_MEMBER is correctly added and consistent with the existing token validations.

apps/web/tests/links/folder-link-access.test.ts (2)

2-2: LGTM!

The switch to IntegrationHarnessMember is appropriate for testing member-specific folder access permissions.

Also applies to: 17-17


308-313: Error messages correctly aligned.

Verification confirms the error messages in the test expectations match the backend bulk update endpoint exactly. Both the test file and the backend route handler use the identical message format: You don't have permission to update links in this folder: ${folderId}.

apps/web/tests/utils/resource.ts (2)

2-2: LGTM!

The addition of E2E_USER_ID_MEMBER with a descriptive comment clearly identifies this constant's purpose for member user tests.


36-38: Verify updated test resource IDs exist in the test environment.

Multiple test resource IDs have been updated (E2E_NO_ACCESS_FOLDER_ID, E2E_READ_ONLY_FOLDER_LINK_ID, E2E_NO_ACCESS_FOLDER_LINK_ID). If these reference actual database records in the test environment, ensure:

  1. These IDs exist and are properly seeded
  2. They have the correct access permissions configured
  3. The links are associated with the correct folders

Mismatched IDs could cause test failures that are difficult to debug.

apps/web/tests/utils/integration-member.ts (4)

1-12: LGTM!

The imports and Resources interface are well-structured and provide clear type definitions for the test harness.


22-32: LGTM!

The constructor properly initializes the HTTP client with member-specific authentication using E2E_TOKEN_MEMBER.


34-59: Verify hardcoded workspace details match the test environment.

The init() method uses hardcoded values for the workspace (slug: "acme", name: "Acme, Inc."). Ensure these match the actual workspace associated with E2E_WORKSPACE_ID in the test environment to avoid confusion during debugging.


62-69: LGTM!

The deleteLink() helper method is well-implemented and provides necessary cleanup functionality for test scenarios.

apps/web/lib/constants/misc.ts (1)

3-3: LGTM! Good refactor to eliminate magic numbers.

Extracting the workspace links threshold into a named constant improves maintainability and makes the code more self-documenting.

apps/web/lib/swr/use-workspace.ts (1)

5-5: LGTM! Consistent use of the centralized constant.

Replacing the hard-coded threshold with MEGA_WORKSPACE_LINKS_LIMIT maintains consistency across the codebase.

Also applies to: 41-42

apps/web/lib/api/links/validate-links-query-filters.ts (2)

11-11: Good: Workspace type extended to support role-based access checks.

Adding "users" to the workspace type enables the function to pass workspace role information to verifyFolderAccess, which aligns with the PR objective of granting workspace owners full folder permissions.


36-42: Breaking change verifiedβ€”all callers properly updated.

Verification confirms that validateLinksQueryFilters returns only { folderIds } and all four callers in the codebase have been updated to destructure folderIds accordingly:

  • apps/web/app/api/links/route.ts:24
  • apps/web/app/api/links/export/route.ts:25
  • apps/web/app/api/links/count/route.ts:12
  • apps/web/app/(ee)/api/cron/links/export/route.ts:76

No other callers exist, and no code attempts to destructure the removed selectedFolder property.

apps/web/app/(ee)/api/events/route.ts (2)

10-10: LGTM! Removed unused import.

The Folder type is no longer needed since the code no longer captures the folder object from verifyFolderAccess.


51-56: LGTM! Simplified folder access verification.

The code now uses verifyFolderAccess for permission checking only, without capturing the folder object. This aligns with the updated function signature and is cleaner.

apps/web/app/api/links/export/route.ts (1)

65-66: Verify that workspace.totalLinks is available from the withWorkspace HOF.

Similar to the concern in apps/web/app/api/links/route.ts, this code relies on workspace.totalLinks being available from the withWorkspace higher-order function. Ensure that the workspace object includes the totalLinks property.

See the verification script in the review comment for apps/web/app/api/links/route.ts (lines 34-35) to check the withWorkspace implementation.

apps/web/app/api/links/route.ts (1)

34-35: ****

The original concern is unfounded. workspace.totalLinks is properly available from the withWorkspace higher-order function. The Prisma schema defines totalLinks as a field on the Project model (defaulting to 0), and since withWorkspace calls prisma.project.findUnique without an explicit select clause, all project fieldsβ€”including totalLinksβ€”are returned by default. The code change is correct as written.

Likely an incorrect or invalid review comment.

apps/web/app/(ee)/api/cron/links/export/route.ts (1)

66-66: Semantic shift from folder-level to workspace-level search mode is intentional and consistent with codebase patterns.

The changes verified:

  • Previous code (commit 82ac5d3): Used selectedFolder?.type === "mega" to determine exact search mode
  • Current code: Uses workspace.totalLinks > MEGA_WORKSPACE_LINKS_LIMIT instead

This shift is intentional and aligns with how "mega workspace" detection is implemented throughout the codebase:

  • use-workspace.ts:42: isMegaWorkspace: workspace && workspace.totalLinks > MEGA_WORKSPACE_LINKS_LIMIT
  • api/links/route.ts:35: Uses the same workspace-level threshold for searchMode
  • api/links/export/route.ts:66: Uses the same pattern

The refactoring standardizes on workspace-level mega detection rather than per-folder granularity, which is a deliberate design decision implemented consistently across all links/export endpoints.

apps/web/lib/folder/permissions.ts (1)

99-118: LGTM: Owner override correctly implemented for bulk checks.

The owner check in checkFolderPermissions correctly short-circuits folder permission lookups for workspace owners, consistent with the verifyFolderAccess function. The bulk permission check logic is sound.

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 (1)
apps/web/lib/api/partners/generate-partner-link.ts (1)

52-56: TODO comment indicates future work needed.

The hard-coded owner role is appropriate for partner link generation in the current implementation. However, the TODO at line 55 correctly identifies that proper folder RBAC should eventually be applied.

The TODO suggests this is a known limitation. Would you like me to create a separate issue to track implementing folder RBAC for partner link generation?

πŸ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between f2a202f and 2aff587.

πŸ“’ Files selected for processing (8)
  • apps/web/app/(ee)/api/cron/import/csv/route.ts (1 hunks)
  • apps/web/app/(ee)/api/embed/referrals/links/[linkId]/route.ts (1 hunks)
  • apps/web/app/(ee)/api/embed/referrals/links/route.ts (1 hunks)
  • apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/[linkId]/route.ts (1 hunks)
  • apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/route.ts (1 hunks)
  • apps/web/lib/api/links/process-link.ts (2 hunks)
  • apps/web/lib/api/links/utils/key-checks.ts (2 hunks)
  • apps/web/lib/api/partners/generate-partner-link.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
πŸ““ Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
πŸ“š Learning: 2025-06-06T07:59:03.120Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.

Applied to files:

  • apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/route.ts
  • apps/web/lib/api/partners/generate-partner-link.ts
πŸ“š Learning: 2025-08-26T15:05:55.081Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/lib/swr/use-bounty.ts:11-16
Timestamp: 2025-08-26T15:05:55.081Z
Learning: In the Dub codebase, workspace authentication and route structures prevent endless loading states when workspaceId or similar route parameters are missing, so gating SWR loading states on parameter availability is often unnecessary.

Applied to files:

  • apps/web/lib/api/links/utils/key-checks.ts
🧬 Code graph analysis (1)
apps/web/lib/api/links/process-link.ts (1)
packages/prisma/client.ts (1)
  • WorkspaceRole (36-36)
⏰ 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: build
πŸ”‡ Additional comments (8)
apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/route.ts (1)

119-128: LGTM! Owner role is appropriate for partner link creation.

The hard-coded owner role at line 122 is correct for partner link creation because:

  • Partners create links on behalf of the workspace
  • skipFolderChecks: true (line 125) already bypasses folder permission validation
  • The owner role ensures the link can be placed in the program's default folder without access restrictions

The TODO at line 124 appears to acknowledge this distinction between the partner user and workspace owner, which is working as intended.

apps/web/app/(ee)/api/embed/referrals/links/route.ts (1)

107-116: LGTM! Owner role correctly applied for referral link creation.

The hard-coded owner role is appropriate here because referral links are created on behalf of the workspace, and folder permission checks are explicitly skipped (line 113). This ensures partners can create referral links without being constrained by folder access restrictions.

apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/[linkId]/route.ts (1)

126-136: LGTM! Consistent with partner link creation.

The owner role is correctly applied for partner link updates, maintaining consistency with the POST route. This ensures partners can update links without folder permission constraints, which is appropriate for partner-managed links.

apps/web/app/(ee)/api/embed/referrals/links/[linkId]/route.ts (1)

76-86: LGTM! Owner role correctly applied for referral link updates.

The hard-coded owner role maintains consistency with the referral link creation route and appropriately bypasses folder permissions for partner-managed links.

Note: The @ts-expect-error at line 70 appears to be pre-existing and is not introduced by this PR.

apps/web/lib/api/links/utils/key-checks.ts (1)

10-19: LGTM! Type refactored to use Prisma's Project type.

The workspace parameter type is updated from Pick<WorkspaceProps, "plan"> to Pick<Project, "plan">, aligning with the Prisma client types used throughout the codebase. This is a type-only change with no functional impact.

apps/web/lib/api/links/process-link.ts (3)

5-7: LGTM! Type imports updated for workspace role support.

The imports are correctly updated to remove WorkspaceProps and add Project and WorkspaceRole from the Prisma client, supporting the new workspace type signature.


404-409: Verified: verifyFolderAccess properly utilizes workspace role information.

The function checks workspace.users[0]?.role === WorkspaceRole.owner at line 24-26 and grants workspace owners full control over all folders. The workspace object parameter includes the users array with role information, and the function correctly prioritizes workspace owner permissions before checking folder-level permissions.


35-37: Hard-coded workspace roles in CSV/referral flows are intentional for special flow types.

After verification:

  1. Workspace roles ARE validated upstream: The withWorkspace middleware queries the actual user's role for each workspace (select: { role: true } where userId: session.user.id). Main API routes enforce this with requiredPermissions: ["links.read"].

  2. Hard-coded owner roles are intentional:

    • CSV import (/api/cron/import/csv) is a QStash-protected internal cron job, not user-facing
    • Referral embed (/api/embed/referrals/links) uses withReferralsEmbedToken, a separate embed auth mechanism for partners
    • Partner API routes (/api/partners/links) pass actual workspace from middleware, no hard-code
  3. Regular API link operations already pass actual user roles: The main API routes use withWorkspace middleware which populates workspace.users with the authenticated user's actual role before calling processLink.

The design is sound: authentication happens at route/middleware level, and processLink is not responsible for role validation. Hard-coded roles in special flows (cron/embed) are acceptable given their separate upstream auth mechanisms.

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: 3

πŸ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 2aff587 and a18eb4d.

πŸ“’ Files selected for processing (4)
  • apps/web/app/api/folders/[folderId]/users/route.ts (4 hunks)
  • apps/web/app/api/folders/permissions/route.ts (2 hunks)
  • apps/web/app/api/links/bulk/route.ts (5 hunks)
  • apps/web/lib/folder/permissions.ts (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/app/api/links/bulk/route.ts
  • apps/web/app/api/folders/[folderId]/users/route.ts
🧰 Additional context used
🧠 Learnings (3)
πŸ““ Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
πŸ“š Learning: 2025-06-18T20:26:25.177Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2538
File: apps/web/ui/partners/overview/blocks/commissions-block.tsx:16-27
Timestamp: 2025-06-18T20:26:25.177Z
Learning: In the Dub codebase, components that use workspace data (workspaceId, defaultProgramId) are wrapped in `WorkspaceAuth` which ensures these values are always available, making non-null assertions safe. This is acknowledged as a common pattern in their codebase, though not ideal.

Applied to files:

  • apps/web/lib/folder/permissions.ts
πŸ“š Learning: 2025-10-06T15:48:45.956Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2935
File: packages/prisma/schema/workspace.prisma:21-36
Timestamp: 2025-10-06T15:48:45.956Z
Learning: In the Dub repository (dubinc/dub), Prisma schema changes are not managed with separate migration files. Do not flag missing Prisma migration files when schema changes are made to files like `packages/prisma/schema/workspace.prisma` or other schema files.

Applied to files:

  • apps/web/lib/folder/permissions.ts
🧬 Code graph analysis (2)
apps/web/lib/folder/permissions.ts (4)
apps/web/lib/types.ts (3)
  • FolderPermission (519-519)
  • Folder (515-515)
  • FolderUser (521-524)
apps/web/lib/plan-capabilities.ts (1)
  • getPlanCapabilities (4-21)
apps/web/lib/folder/get-folder-or-throw.ts (1)
  • getFolderOrThrow (4-59)
apps/web/lib/folder/constants.ts (3)
  • FOLDER_USER_ROLE (9-13)
  • FOLDER_WORKSPACE_ACCESS_TO_FOLDER_USER_ROLE (22-28)
  • FOLDER_USER_ROLE_TO_PERMISSIONS (30-42)
apps/web/app/api/folders/permissions/route.ts (1)
apps/web/lib/folder/permissions.ts (2)
  • findFolderUserRole (132-154)
  • getFolderPermissions (157-163)
⏰ 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: build

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

πŸ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between a18eb4d and aa8fdaf.

πŸ“’ Files selected for processing (1)
  • apps/web/app/(ee)/api/cron/import/csv/route.ts (3 hunks)
🧰 Additional context used
🧠 Learnings (2)
πŸ““ Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
πŸ“š Learning: 2025-06-06T07:59:03.120Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.

Applied to files:

  • apps/web/app/(ee)/api/cron/import/csv/route.ts
πŸ”‡ Additional comments (2)
apps/web/app/(ee)/api/cron/import/csv/route.ts (2)

7-7: Import removal is consistent with the refactor.

The WorkspaceProps import is no longer needed since the full Prisma workspace object is now passed to processLink instead of a manually constructed minimal object.


387-391: Add validation that the user is a workspace member and explicitly select required fields.

The query now fetches the importing user's actual workspace membership, which properly addresses the past security issue of hardcoded owner role. However, there are two concerns:

  1. Missing membership validation: If the users array is empty (user not a member), workspace.users[0]?.role will be undefined in downstream code. This could cause unexpected behavior in permission checks. Add explicit validation after the query to ensure the user is a member of the workspace.

  2. Implicit field selection: The users relation doesn't specify a select clause, relying on Prisma's default behavior to return all scalar fields. For clarity and to ensure the role field is explicitly included, specify the required fields.

Apply this diff to add validation and explicit field selection:

 const workspace = await prisma.project.findUniqueOrThrow({
   where: {
     id: workspaceId,
   },
   select: {
     id: true,
     plan: true,
     users: {
       where: {
         userId,
       },
+      select: {
+        role: true,
+      },
     },
   },
 });
+
+if (!workspace.users[0]) {
+  throw new Error(`User ${userId} is not a member of workspace ${workspaceId}`);
+}

Run the following script to verify that downstream permission checks properly handle the workspace structure:

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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/app/api/folders/[folderId]/route.ts (1)

119-145: Critical: DELETE endpoint vulnerable to cross-workspace folder deletion.

The DELETE endpoint has a critical authorization bypass:

  1. Line 124-129: Calls verifyFolderAccess, which returns true immediately for workspace owners (line 33-34 of permissions.ts) without validating that the folder belongs to the workspace.

  2. Line 139: Deletes the folder using only id in the where clause, with no projectId constraint.

Combined, this allows a workspace owner to delete any folder from any workspace by simply providing its ID.

The PATCH endpoint (line 84-85) is protected because it includes projectId: workspace.id in the where clause. The DELETE endpoint must do the same.

Apply this fix to the immediate deletion path:

 await prisma.folder.delete({
   where: {
     id: folderId,
+    projectId: workspace.id,
   },
 });

And to the soft-delete path:

 prisma.folder.update({
   where: {
     id: folderId,
+    projectId: workspace.id,
   },
   data: {
     projectId: "",
   },
 }),

Note: This is a defense-in-depth fix. The root cause is that verifyFolderAccess bypasses folder-workspace validation for owners (flagged in previous reviews). Both issues should be addressed.

Based on past review comments identifying this authorization bypass.

♻️ Duplicate comments (2)
apps/web/lib/folder/permissions.ts (2)

136-158: Fix type safety: workspaceRole parameter accepts undefined but signature requires non-optional.

The function signature (line 143) declares workspaceRole: WorkspaceRole, but callers pass potentially undefined values:

  • Line 53: workspace.users[0]?.role
  • Line 117: workspace.users[0]?.role

This violates the type contract and can cause runtime errors if workspace.users is empty or if TypeScript strict mode is enabled.

Relax the parameter type to match actual usage:

 export const findFolderUserRole = ({
   folder,
   user,
   workspaceRole,
 }: {
   folder: Pick<Folder, "accessLevel">;
   user: Pick<FolderUser, "role"> | null;
-  workspaceRole: WorkspaceRole;
+  workspaceRole: WorkspaceRole | undefined;
 }) => {
   if (workspaceRole === WorkspaceRole.owner) {
     return FolderUserRole.owner;
   }

Alternatively, if workspace.users[0] is guaranteed by middleware (as suggested in past review comments), add runtime validation:

+  if (!workspaceRole) {
+    throw new Error("workspaceRole is required");
+  }
+
   if (workspaceRole === WorkspaceRole.owner) {

Based on past review comments about workspace.users contract violations.


113-118: Fix type safety: folder.users[0] can be undefined, not null.

Line 116 passes folder.users[0] to findFolderUserRole, but:

  • folder.users[0] has type FolderUser | undefined
  • The function parameter expects Pick<FolderUser, "role"> | null (line 142)

This type mismatch can cause subtle bugs and violates the stated contract.

Normalize to null:

 return folders.map((folder) => {
   const folderUserRole = findFolderUserRole({
     folder,
-    user: folder.users[0],
+    user: folder.users[0] ?? null,
     workspaceRole: workspace.users[0]?.role,
   });

Based on past review comments identifying this contract violation.

🧹 Nitpick comments (3)
apps/web/ui/folders/folder-dropdown.tsx (1)

117-166: useMemo dependencies are incomplete and can cause stale folder options

Inside this useMemo, you reference folderId, folderLinksCount, and router, but they are not in the dependency array. That means:

  • Updated folderLinksCount won’t refresh counts in the dropdown.
  • A changed folderId won’t re-run the prefetch logic or recompute options.

Consider updating the deps list to include these values:

-  }, [folders, selectedFolderData, canAddFolder, slug, debouncedSearch]);
+  }, [
+    folders,
+    selectedFolderData,
+    canAddFolder,
+    slug,
+    debouncedSearch,
+    folderId,
+    folderLinksCount,
+  ]);

(You can omit router if it’s stable by design.)

apps/web/lib/folder/permissions.ts (2)

160-167: Type safety: Parameter accepts string | null but indexes typed record.

Line 166 indexes FOLDER_USER_ROLE_TO_PERMISSIONS, which is typed as Record<FolderUserRole, FolderPermission[]>, using a string | null parameter. This can fail at runtime if role is an arbitrary string.

Tighten the type to match the lookup:

-export const getFolderPermissions = (role: string | null) => {
+export const getFolderPermissions = (role: FolderUserRole | null) => {
   if (!role) {
     return [];
   }
 
   return FOLDER_USER_ROLE_TO_PERMISSIONS[role] || [];
 };

This makes the contract explicit and enables TypeScript to catch invalid usages.


21-21: Remove unused userId parameter.

The userId parameter is passed to getFolderOrThrow (line 47) but is only used there to filter folder.users in the query (see get-folder-or-throw.ts line 23). The filtered user is returned as folder.user, but:

  1. In verifyFolderAccess, the function already has access to workspace role for owner checks
  2. For non-owners, the folder user role is resolved via findFolderUserRole
  3. The actual userId value is never used for any permission logic

Previous review comments noted this parameter is unused, and suggested aligning with the checkFolderPermissions pattern by accepting workspaceRole directly instead.

Consider removing the userId parameter and accepting workspaceRole directly to match the pattern used by other functions in this file. This would also eliminate the unused parameter and simplify the function signature.

Based on past review comments.

πŸ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between aa8fdaf and 0f94836.

πŸ“’ Files selected for processing (3)
  • apps/web/app/api/folders/[folderId]/route.ts (2 hunks)
  • apps/web/lib/folder/permissions.ts (5 hunks)
  • apps/web/ui/folders/folder-dropdown.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
πŸ““ Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
πŸ“š Learning: 2025-06-18T20:26:25.177Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2538
File: apps/web/ui/partners/overview/blocks/commissions-block.tsx:16-27
Timestamp: 2025-06-18T20:26:25.177Z
Learning: In the Dub codebase, components that use workspace data (workspaceId, defaultProgramId) are wrapped in `WorkspaceAuth` which ensures these values are always available, making non-null assertions safe. This is acknowledged as a common pattern in their codebase, though not ideal.

Applied to files:

  • apps/web/lib/folder/permissions.ts
πŸ“š Learning: 2025-10-06T15:48:45.956Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2935
File: packages/prisma/schema/workspace.prisma:21-36
Timestamp: 2025-10-06T15:48:45.956Z
Learning: In the Dub repository (dubinc/dub), Prisma schema changes are not managed with separate migration files. Do not flag missing Prisma migration files when schema changes are made to files like `packages/prisma/schema/workspace.prisma` or other schema files.

Applied to files:

  • apps/web/lib/folder/permissions.ts
🧬 Code graph analysis (2)
apps/web/lib/folder/permissions.ts (4)
apps/web/lib/types.ts (3)
  • FolderPermission (519-519)
  • Folder (515-515)
  • FolderUser (521-524)
apps/web/lib/plan-capabilities.ts (1)
  • getPlanCapabilities (4-21)
apps/web/lib/folder/get-folder-or-throw.ts (1)
  • getFolderOrThrow (4-59)
apps/web/lib/folder/constants.ts (2)
  • FOLDER_WORKSPACE_ACCESS_TO_FOLDER_USER_ROLE (22-28)
  • FOLDER_USER_ROLE_TO_PERMISSIONS (30-42)
apps/web/app/api/folders/[folderId]/route.ts (2)
apps/web/lib/folder/get-folder-or-throw.ts (1)
  • getFolderOrThrow (4-59)
apps/web/lib/folder/permissions.ts (1)
  • verifyFolderAccess (19-73)
⏰ 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: build
πŸ”‡ Additional comments (1)
apps/web/ui/folders/folder-dropdown.tsx (1)

47-50: Removal of searchParams looks correct and behavior-neutral

searchParams was not used anywhere in this component, so narrowing the destructuring to { queryParams } is a safe cleanup and should just fix lint noise without affecting behavior.

@steven-tey
Copy link
Collaborator

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 19, 2025

βœ… Actions performed

Full review triggered.

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 (1)
apps/web/lib/folder/permissions.ts (1)

88-94: Critical: Bulk operation still bypasses folder-workspace validation for owners.

The owner early return (lines 88-94) happens before the folder fetch (lines 96-111), allowing workspace owners to pass arbitrary folderIds from any workspace and receive hasPermission: true for all of them.

Unlike verifyFolderAccess (which now fetches the folder first at line 32), this bulk operation returns immediately without validating that the folders belong to workspace.id.

Move the folder fetch before the owner check:

+  const folders = await prisma.folder.findMany({
+    where: {
+      projectId: workspace.id,
+      id: {
+        in: folderIds,
+      },
+    },
+    include: {
+      users: {
+        where: {
+          userId,
+        },
+        take: 1,
+      },
+    },
+  });
+
   // Workspace owners have full control over all folders
   if (workspace.users[0]?.role === WorkspaceRole.owner) {
-    return folderIds.map((folderId) => ({
-      folderId,
+    return folders.map((folder) => ({
+      folderId: folder.id,
       hasPermission: true,
     }));
   }
-
-  const folders = await prisma.folder.findMany({
-    where: {
-      projectId: workspace.id,
-      id: {
-        in: folderIds,
-      },
-    },
-    include: {
-      users: {
-        where: {
-          userId,
-        },
-        take: 1,
-      },
-    },
-  });

This ensures only folders that exist in the workspace are returned, preventing cross-workspace access.

🧹 Nitpick comments (1)
apps/web/app/api/analytics/export/route.ts (1)

93-95: Consider using "arraybuffer" type for better type safety.

JSZip's ArrayBuffer output works correctly with Next.js Response API for ZIP downloads. The current double-cast as unknown as BodyInit bypasses TypeScript's type checking unnecessarily. Since NextResponse is an extension of the Web Response API and can carry binary bodies (the standard Response body accepts ArrayBuffer/Uint8Array/Blob), you can eliminate the cast entirely by switching to "arraybuffer" type.

Apply this diff:

-    const zipData = await zip.generateAsync({ type: "nodebuffer" });
+    const zipData = await zip.generateAsync({ type: "arraybuffer" });

-    return new Response(zipData as unknown as BodyInit, {
+    return new Response(zipData, {
       headers: {
         "Content-Type": "application/zip",
         "Content-Disposition": "attachment; filename=analytics_export.zip",
       },
     });
πŸ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 9e3e503 and 981bfc7.

πŸ“’ Files selected for processing (32)
  • .github/workflows/e2e.yaml (1 hunks)
  • apps/web/app/(ee)/api/cron/import/csv/route.ts (3 hunks)
  • apps/web/app/(ee)/api/cron/links/export/route.ts (4 hunks)
  • apps/web/app/(ee)/api/embed/referrals/links/[linkId]/route.ts (1 hunks)
  • apps/web/app/(ee)/api/embed/referrals/links/route.ts (1 hunks)
  • apps/web/app/(ee)/api/events/route.ts (2 hunks)
  • apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/[linkId]/route.ts (1 hunks)
  • apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/route.ts (1 hunks)
  • apps/web/app/api/analytics/export/route.ts (1 hunks)
  • apps/web/app/api/folders/[folderId]/users/route.ts (4 hunks)
  • apps/web/app/api/folders/count/route.ts (2 hunks)
  • apps/web/app/api/folders/permissions/route.ts (2 hunks)
  • apps/web/app/api/links/bulk/route.ts (5 hunks)
  • apps/web/app/api/links/export/route.ts (3 hunks)
  • apps/web/app/api/links/route.ts (3 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/campaigns-table.tsx (1 hunks)
  • apps/web/lib/actions/folders/request-folder-edit-access.ts (2 hunks)
  • apps/web/lib/api/links/process-link.ts (2 hunks)
  • apps/web/lib/api/links/utils/key-checks.ts (2 hunks)
  • apps/web/lib/api/links/validate-links-query-filters.ts (2 hunks)
  • apps/web/lib/api/partners/generate-partner-link.ts (1 hunks)
  • apps/web/lib/constants/misc.ts (1 hunks)
  • apps/web/lib/folder/get-folders.ts (2 hunks)
  • apps/web/lib/folder/permissions.ts (6 hunks)
  • apps/web/lib/swr/use-workspace.ts (2 hunks)
  • apps/web/lib/types.ts (1 hunks)
  • apps/web/tests/links/folder-link-access.test.ts (3 hunks)
  • apps/web/tests/utils/env.ts (1 hunks)
  • apps/web/tests/utils/integration-member.ts (1 hunks)
  • apps/web/tests/utils/resource.ts (2 hunks)
  • apps/web/ui/folders/edit-folder-panel.tsx (3 hunks)
  • apps/web/ui/folders/folder-dropdown.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (10)
πŸ““ Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
πŸ“š Learning: 2025-10-15T01:05:43.266Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.

Applied to files:

  • apps/web/ui/folders/folder-dropdown.tsx
  • apps/web/ui/folders/edit-folder-panel.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/campaigns-table.tsx
πŸ“š Learning: 2025-10-15T01:52:37.048Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx:270-303
Timestamp: 2025-10-15T01:52:37.048Z
Learning: In React components with dropdowns or form controls that show modals for confirmation (e.g., role selection, delete confirmation), local state should be reverted to match the prop value when the modal is cancelled. This prevents the UI from showing an unconfirmed change. The solution is to either: (1) pass an onClose callback to the modal that resets the local state, or (2) observe modal visibility state and reset on close. Example context: RoleCell component in apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx where role dropdown should revert to user.role when UpdateUserModal is cancelled.

Applied to files:

  • apps/web/ui/folders/edit-folder-panel.tsx
πŸ“š Learning: 2025-09-24T15:50:16.414Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.

Applied to files:

  • apps/web/ui/folders/edit-folder-panel.tsx
πŸ“š Learning: 2025-11-19T17:26:51.912Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 3136
File: apps/web/ui/layout/sidebar/app-sidebar-nav.tsx:377-379
Timestamp: 2025-11-19T17:26:51.912Z
Learning: In the dub repository, TWilson023 prefers an incremental approach to route updates: fixing the most user-visible issues first (like sidebar navigation) while deferring less visible internal references to follow-up PRs, especially when redirects provide backward compatibility.

Applied to files:

  • apps/web/ui/folders/edit-folder-panel.tsx
πŸ“š Learning: 2025-06-06T07:59:03.120Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.

Applied to files:

  • apps/web/lib/api/partners/generate-partner-link.ts
  • apps/web/lib/api/links/process-link.ts
  • apps/web/app/(ee)/api/cron/import/csv/route.ts
  • apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/route.ts
  • apps/web/lib/api/links/validate-links-query-filters.ts
  • apps/web/app/(ee)/api/cron/links/export/route.ts
  • apps/web/app/api/links/route.ts
  • apps/web/app/api/links/bulk/route.ts
  • apps/web/app/api/links/export/route.ts
  • apps/web/tests/links/folder-link-access.test.ts
πŸ“š Learning: 2025-10-06T15:48:45.956Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2935
File: packages/prisma/schema/workspace.prisma:21-36
Timestamp: 2025-10-06T15:48:45.956Z
Learning: In the Dub repository (dubinc/dub), Prisma schema changes are not managed with separate migration files. Do not flag missing Prisma migration files when schema changes are made to files like `packages/prisma/schema/workspace.prisma` or other schema files.

Applied to files:

  • apps/web/app/(ee)/api/cron/import/csv/route.ts
  • apps/web/lib/folder/get-folders.ts
  • apps/web/lib/folder/permissions.ts
πŸ“š Learning: 2025-08-26T15:05:55.081Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/lib/swr/use-bounty.ts:11-16
Timestamp: 2025-08-26T15:05:55.081Z
Learning: In the Dub codebase, workspace authentication and route structures prevent endless loading states when workspaceId or similar route parameters are missing, so gating SWR loading states on parameter availability is often unnecessary.

Applied to files:

  • apps/web/lib/swr/use-workspace.ts
  • apps/web/lib/api/links/utils/key-checks.ts
πŸ“š Learning: 2025-09-24T15:47:40.293Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/about-you-form.tsx:152-157
Timestamp: 2025-09-24T15:47:40.293Z
Learning: The Button component from dub/ui automatically adds type="button" when an onClick prop is passed, preventing accidental form submissions without requiring explicit type specification. The implementation uses: type={onClick ? "button" : type}

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/campaigns-table.tsx
πŸ“š Learning: 2025-06-18T20:26:25.177Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2538
File: apps/web/ui/partners/overview/blocks/commissions-block.tsx:16-27
Timestamp: 2025-06-18T20:26:25.177Z
Learning: In the Dub codebase, components that use workspace data (workspaceId, defaultProgramId) are wrapped in `WorkspaceAuth` which ensures these values are always available, making non-null assertions safe. This is acknowledged as a common pattern in their codebase, though not ideal.

Applied to files:

  • apps/web/lib/folder/permissions.ts
πŸ”‡ Additional comments (41)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/campaigns-table.tsx (1)

200-203: Copy/link update for learnMoreHref looks good

Using the help article URL when no filters are applied keeps behavior consistent with the unfiltered empty state (and avoids showing a "learn more" link for filtered-empty cases). No functional or UX regressions spotted.

apps/web/ui/folders/folder-dropdown.tsx (1)

49-49: LGTM: Unused variable cleanup.

Removing the unused searchParams variable is a clean refactor with no behavioral impact.

apps/web/lib/constants/misc.ts (1)

3-3: LGTM: Centralized constant for workspace link limits.

Extracting the hardcoded 1_000_000 threshold into a named constant improves maintainability and makes the intent clearer across the codebase.

apps/web/app/(ee)/api/events/route.ts (2)

10-10: LGTM: Removed unused import.

The Folder type is no longer needed since verifyFolderAccess no longer returns folder data.


51-56: The review comment's premise is incorrect.

The comment claims "removal of the captured folder variable aligns with the broader refactoring where verifyFolderAccess now focuses solely on verification rather than data retrieval." However, verification shows that verifyFolderAccess still returns folder data and is captured in other parts of the codebase:

  • apps/web/app/api/folders/[folderId]/route.ts (line 17): const folder = await verifyFolderAccess({...}) captures the return value
  • apps/web/app/api/folders/[folderId]/route.ts (line 23): return NextResponse.json(FolderSchema.parse(folder)) uses that captured value

The code under review in apps/web/app/(ee)/api/events/route.ts is correct for its context (verification-only usage), but the review comment incorrectly characterizes a broader refactoring that hasn't been fully applied or is intentionally incomplete across the codebase.

Likely an incorrect or invalid review comment.

apps/web/lib/swr/use-workspace.ts (2)

5-5: LGTM: Using centralized constant.

Importing and using MEGA_WORKSPACE_LINKS_LIMIT instead of hardcoding the threshold improves consistency.


41-42: LGTM: Replaced hardcoded threshold with constant.

The refactor from the magic number 1_000_000 to MEGA_WORKSPACE_LINKS_LIMIT improves code maintainability and aligns with the centralized constant definition.

apps/web/lib/actions/folders/request-folder-edit-access.ts (2)

24-29: LGTM: Verification without data capture.

The refactor to call verifyFolderAccess without capturing its return value aligns with the updated signature where verification is separate from data retrieval.


46-59: LGTM: Folder data retrieved from create response.

Retrieving folder metadata via the include clause in the Prisma create call is a reasonable refactor that maintains the necessary data for email composition while aligning with the updated verifyFolderAccess behavior.

apps/web/lib/folder/get-folders.ts (2)

36-62: LGTM: Owner bypass implements PR requirements correctly.

The conditional logic properly grants workspace owners access to all folders by skipping the access filter when workspaceUser?.role === WorkspaceRole.owner. The optional chaining handles the edge case where workspaceUser is null (user not in workspace), correctly applying filters in that scenario.


21-31: Database index properly optimizes the workspace user lookupβ€”performance concern is mitigated.

The workspace user query executes efficiently against the @@unique([userId, projectId]) constraint on the ProjectUsers model (packages/prisma/schema/workspace.prisma:112), making the findUnique lookup O(1). This query was intentionally added in commit cb78304 to support role-based folder filtering for workspace owners. While called from multiple endpoints (API, webhook, analytics), the indexed lookup ensures minimal overhead per invocation.

apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/route.ts (1)

122-122: Verify owner role assignment for partner link creation.

Similar to the PATCH endpoint, the POST endpoint grants owner-level permissions via users: [{ role: "owner" }] during partner link creation. Ensure this aligns with the intended permission model where partners operate with elevated privileges within their program context.

apps/web/app/(ee)/api/partner-profile/programs/[programId]/links/[linkId]/route.ts (1)

129-129: Owner role assignment is consistent across all partner and embed routes.

Verification confirms that users: [{ role: "owner" }] is the established pattern used throughout the partner and embed infrastructure. This same pattern appears in all partner profile link routes, embed referral routes, and the partner link generation utility. The TODO comment in generate-partner-link.ts indicates the team is aware of RBAC scope limitations and has planned improvements, but the current owner-level assignment is intentional and consistent.

apps/web/app/(ee)/api/embed/referrals/links/route.ts (1)

107-114: Owner role addition is correct but has limited impact due to skipFolderChecks.

The addition of users: [{ role: "owner" }] aligns with the PR's objective to propagate workspace roles through permission checks. However, since skipFolderChecks: true is set on line 113, folder permission checks are bypassed entirely in this flow, making the owner role less impactful here.

apps/web/app/(ee)/api/embed/referrals/links/[linkId]/route.ts (1)

76-84: Owner role addition is correct but has limited impact due to skipFolderChecks.

Similar to the POST handler, the addition of users: [{ role: "owner" }] is consistent with the PR's objectives, but skipFolderChecks: true on line 83 bypasses folder permission checks, reducing the practical impact of the owner role in this flow.

apps/web/app/api/folders/permissions/route.ts (2)

3-6: LGTM! Import rename reflects the updated function name.

The import has been correctly updated from findUserFolderRole to findFolderUserRole, aligning with the function rename in the permissions module.


33-43: LGTM! Workspace role is now correctly propagated to permission calculation.

The changes correctly:

  1. Pass workspaceRole from the workspace context to findFolderUserRole
  2. Use the more descriptive variable name userFolderRole
  3. Feed the result into getFolderPermissions

According to past review comments, the underlying findFolderUserRole function was updated to return the internal role key (e.g., "owner") for workspace owners, which ensures they receive full folder permissions as intended by this PR.

apps/web/lib/api/partners/generate-partner-link.ts (1)

52-56: Owner role hardcoding is temporary; TODO flags future RBAC work.

The workspace payload now includes users: [{ role: "owner" }], consistent with changes across the codebase. The TODO comment on line 55 correctly identifies that full folders RBAC should be applied to generatePartnerLink checks in the future. For now, treating all partner link generation as owner-level operations is a reasonable interim approach.

apps/web/tests/utils/env.ts (1)

6-6: LGTM! Member token validation added consistently.

The addition of E2E_TOKEN_MEMBER to the schema is consistent with existing token validation patterns and is required for the new member-level integration test infrastructure.

.github/workflows/e2e.yaml (1)

38-38: Manual verification required: Cannot check GitHub secrets in sandbox environment.

The verification command requires access to the GitHub repository settings, which cannot be performed in this sandbox environment due to the absence of a git repository context.

To verify the secret exists, run this command in your local repository or CI environment:

gh secret list | grep -i E2E_TOKEN_MEMBER

If the secret is not found, configure E2E_TOKEN_MEMBER in your GitHub repository settings (Settings β†’ Secrets and variables β†’ Actions) before merging.

apps/web/app/api/links/route.ts (2)

7-7: Verify the threshold calibration through performance testing or design documentation.

The constant MEGA_WORKSPACE_LINKS_LIMIT = 1_000_000 is properly defined in apps/web/lib/constants/misc.ts and consistently used across four files to switch search mode from fuzzy to exact when a workspace exceeds 1 million links. However, no performance calibration data or tests exist in the codebase to validate this threshold value.

Confirm that:

  • The 1 million threshold was determined through performance testing
  • This threshold is optimal for your typical workspace sizes and search performance characteristics
  • Consider documenting the rationale or performance data as a code comment

24-35: Review comment is based on incorrect premise about prior implementation.

The review describes a shift from per-folder search mode logic (selectedFolder?.isMegaFolder) to workspace-wide logic (workspace.totalLinks > MEGA_WORKSPACE_LINKS_LIMIT). However, git history shows that searchMode has been workspace-wide since its introduction in commit e4cdf5dβ€”there was never per-folder logic to change from. The current implementation matches the original design.

Likely an incorrect or invalid review comment.

apps/web/lib/types.ts (1)

521-524: API contract verified - workspaceRole field properly populated in all FolderUser returns.

Verification confirms that the single API endpoint returning FolderUser[] objects (apps/web/app/api/folders/[folderId]/users/route.ts) correctly populates the workspaceRole field on all code paths:

  • When no folder-specific user record exists (lines 63-68): includes workspaceRole
  • When folder-specific user record exists (lines 78-84): includes workspaceRole

No other API routes return FolderUser objects. The breaking change is properly implemented.

apps/web/app/(ee)/api/cron/links/export/route.ts (1)

9-9: Workspace link-count–based searchMode looks consistent and safe

Selecting totalLinks on the workspace and deriving searchMode from workspace.totalLinks > MEGA_WORKSPACE_LINKS_LIMIT keeps the cron export in line with the main links export route and avoids depending on the previously inferred selectedFolder. The workspace null-check before use and continued use of validateLinksQueryFilters (now just folderIds) preserve correctness of the filters.

Also applies to: 58-68, 76-80, 93-107

apps/web/app/(ee)/api/cron/import/csv/route.ts (1)

7-7: CSV import now respects real workspace membership for folder checks

Fetching workspace with { id, plan, users: { where: { userId } } }, passing it directly into processLink, and typing the bulk insert as ProcessedLinkProps[] aligns this cron worker with the updated processLink contract and folder-permission helpers. This removes the prior hardcoded owner shortcut and ensures CSV imports honor the importing user’s actual role while still allowing owners to bypass folder-level restrictions as intended.

Also applies to: 380-393, 395-410, 424-430

apps/web/app/api/links/bulk/route.ts (1)

20-21: Consolidated bulk folder-permission checks are correct and consistent

Switching POST, PATCH, and DELETE bulk flows to use verifyFolderAccessBulk with { workspace, userId, folderIds, requiredPermission: "folders.links.write" } keeps permission evaluation consistent with the new folder-permissions API and properly applies workspace-owner shortcuts. The follow‑up filtering logic and the updated PATCH error copy (β€œupdate links in this folder”) remain aligned with the route’s behavior and the corresponding tests.

Also applies to: 169-214, 348-381, 525-543

apps/web/tests/utils/resource.ts (1)

2-2: Member user and folder/link fixture IDs updated appropriately

Defining E2E_USER_ID_MEMBER and refreshing the no-access folder and link IDs are straightforward test-fixture updates to support the new member harness and permission scenarios. Nothing here affects production behavior.

Also applies to: 36-38

apps/web/app/api/folders/count/route.ts (1)

4-5: Owner-aware folder count logic matches the permission model

Using workspace.users[0]?.role to gate the extra OR/users.none filters ensures that non‑owners still exclude folders where they have explicit β€œno access”, while workspace owners simply count all folders in the workspace (subject only to projectId and optional name search). This aligns the count endpoint with the new owner‑sees‑everything semantics.

Also applies to: 14-15, 19-39

apps/web/ui/folders/edit-folder-panel.tsx (1)

14-15: Folder role UI correctly treats workspace owners as immutable with a clear tooltip

Deriving isWorkspaceOwner from user.workspaceRole, folding it into disableRoleUpdate, and wrapping the role <select> in DynamicTooltipWrapper (with an owner-specific message) ensures workspace owners always appear with full access and cannot be edited via folder‑level roles. The logic for non‑owners remains unchanged and continues to use the existing server action flow.

Also applies to: 19-20, 252-256, 272-313

apps/web/lib/api/links/process-link.ts (1)

5-8: Updated workspace type for processLink aligns with new permission helpers

Narrowing the workspace parameter to id/plan plus a users array with role entries, and importing the corresponding Prisma types, matches how callers now construct workspace objects (e.g., CSV import and bulk routes) and lets downstream helpers like verifyFolderAccess infer owner status from workspace.users. The core link‑validation logic remains unchanged.

Also applies to: 34-37

apps/web/tests/links/folder-link-access.test.ts (1)

2-2: Member-focused harness and updated error expectations match the new bulk route behavior

Instantiating IntegrationHarnessMember aligns these folder-access tests with the member role they intend to cover, and the revised expectations for bulk PATCH errors (β€œYou don't have permission to update links in this folder: …”) accurately reflect the updated messaging in /api/links/bulk. The rest of the permission scenarios remain intact.

Also applies to: 17-18, 308-314

apps/web/lib/api/links/utils/key-checks.ts (1)

10-10: LGTM: Type alignment with Prisma Client.

The change from WorkspaceProps to Project correctly aligns the type with Prisma's generated client types, maintaining consistency across the codebase.

Also applies to: 19-19

apps/web/lib/api/links/validate-links-query-filters.ts (2)

11-11: LGTM: Workspace type extended for role-based permission checks.

Adding the users array to the workspace type enables downstream permission logic to access workspace roles, which is essential for the owner permission shortcuts introduced in this PR.


36-41: LGTM: Simplified to side-effect validation.

The removal of selectedFolder from the return value simplifies the API contract. The verifyFolderAccess call now serves purely as a validation gate (throwing on access denial), which is clearer than capturing and returning folder metadata.

Also applies to: 65-67

apps/web/app/api/folders/[folderId]/users/route.ts (2)

3-3: LGTM: Function rename and signature properly updated.

The rename from findUserFolderRole to findFolderUserRole and the addition of the workspaceRole parameter correctly implement the owner permission logic introduced in this PR.

Also applies to: 72-76


51-51: LGTM: workspaceRole propagation implemented correctly.

The addition of folder: true to the query, the null-safe handling when folderUser is missing, and the inclusion of workspaceRole in all response objects properly support the workspace owner permission model.

Also applies to: 61-70, 84-84

apps/web/lib/folder/permissions.ts (3)

32-41: LGTM: Folder validation now occurs before owner shortcut.

The reordering to fetch and validate the folder (lines 32-36) before the owner permission check (lines 38-41) correctly addresses the critical security issue flagged in previous reviews. This ensures workspace owners can only access folders within their workspace.


136-158: LGTM: Role resolution logic correctly prioritizes workspace owner.

The findFolderUserRole function properly implements the three-tier resolution: workspace owner β†’ explicit folder user role β†’ folder access level mapping. The enum return values align with the permission lookup in getFolderPermissions.


161-167: LGTM: Null-safe permission lookup.

The getFolderPermissions function correctly handles null roles and returns an empty array for unmapped roles, providing safe fallback behavior.

apps/web/tests/utils/integration-member.ts (1)

1-70: LGTM: Well-structured test harness for member user scenarios.

The IntegrationHarnessMember class provides a clean abstraction for testing folder permission logic from a non-owner perspective. The structure parallels existing integration test patterns, which promotes consistency. The E2E_TOKEN_MEMBER secret is properly configured in CI (.github/workflows/e2e.yaml injects it from GitHub Secrets).

apps/web/app/api/links/export/route.ts (1)

9-9: LGTM: Verified that workspace.totalLinks is consistently populated by withWorkspace middleware.

The workspace query in workspace.ts uses include for the users relationship without an explicit select on the parent project model. This means all scalar fields (including totalLinks) are returned by default from Prisma. The field is already actively used throughout the codebase in multiple places without issues, confirming the change is sound.

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

🧹 Nitpick comments (1)
apps/web/ui/folders/edit-folder-sheet.tsx (1)

272-313: Consider showing tooltip for current workspace owner user.

The tooltip wrapper correctly implements the PR requirement to show a tooltip when the role selection is disabled for workspace owners. However, the tooltip is only shown for other workspace owner users (isWorkspaceOwner && !isCurrentUser on line 274).

When a workspace owner views their own row, they see a disabled dropdown with no explanation. Consider whether it would improve UX to also show the tooltip for the current user, or if workspace owners are expected to understand why their own role cannot be changed.

The rest of the implementation looks correct:

  • Value correctly displays "owner" for workspace owners (line 284)
  • "No access" option properly included (lines 309-311)
  • Disable logic comprehensive (line 285)

If you want to show the tooltip for current user as well, apply this diff:

         <DynamicTooltipWrapper
           tooltipProps={
-            isWorkspaceOwner && !isCurrentUser
+            isWorkspaceOwner
               ? { content: "Workspace owners have full access to all folders." }
               : undefined
           }
         >
πŸ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 981bfc7 and 4b196ee.

πŸ“’ Files selected for processing (5)
  • apps/web/app/api/folders/[folderId]/users/route.ts (3 hunks)
  • apps/web/ui/folders/edit-folder-sheet.tsx (6 hunks)
  • apps/web/ui/folders/folder-actions.tsx (2 hunks)
  • apps/web/ui/modals/set-default-folder-modal.tsx (1 hunks)
  • packages/ui/src/nav/content/resources-content.tsx (1 hunks)
βœ… Files skipped from review due to trivial changes (1)
  • packages/ui/src/nav/content/resources-content.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/app/api/folders/[folderId]/users/route.ts
🧰 Additional context used
🧠 Learnings (5)
πŸ““ Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
πŸ“š Learning: 2025-10-15T01:52:37.048Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx:270-303
Timestamp: 2025-10-15T01:52:37.048Z
Learning: In React components with dropdowns or form controls that show modals for confirmation (e.g., role selection, delete confirmation), local state should be reverted to match the prop value when the modal is cancelled. This prevents the UI from showing an unconfirmed change. The solution is to either: (1) pass an onClose callback to the modal that resets the local state, or (2) observe modal visibility state and reset on close. Example context: RoleCell component in apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx where role dropdown should revert to user.role when UpdateUserModal is cancelled.

Applied to files:

  • apps/web/ui/folders/edit-folder-sheet.tsx
πŸ“š Learning: 2025-09-24T15:50:16.414Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.

Applied to files:

  • apps/web/ui/folders/edit-folder-sheet.tsx
πŸ“š Learning: 2025-11-19T17:26:51.912Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 3136
File: apps/web/ui/layout/sidebar/app-sidebar-nav.tsx:377-379
Timestamp: 2025-11-19T17:26:51.912Z
Learning: In the dub repository, TWilson023 prefers an incremental approach to route updates: fixing the most user-visible issues first (like sidebar navigation) while deferring less visible internal references to follow-up PRs, especially when redirects provide backward compatibility.

Applied to files:

  • apps/web/ui/folders/edit-folder-sheet.tsx
πŸ“š Learning: 2025-10-15T01:05:43.266Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.

Applied to files:

  • apps/web/ui/folders/edit-folder-sheet.tsx
🧬 Code graph analysis (2)
apps/web/ui/folders/edit-folder-sheet.tsx (2)
packages/ui/src/tooltip.tsx (1)
  • DynamicTooltipWrapper (204-218)
apps/web/lib/folder/constants.ts (1)
  • FOLDER_USER_ROLE (9-13)
apps/web/ui/folders/folder-actions.tsx (1)
apps/web/ui/folders/edit-folder-sheet.tsx (1)
  • useEditFolderSheet (332-347)
⏰ 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: build
πŸ”‡ Additional comments (5)
apps/web/ui/modals/set-default-folder-modal.tsx (1)

65-65: Documentation link verified and working correctly.

The updated href to the help article with anchor #setting-a-default-folder is accessible and the anchor exists on the target page. This improvement successfully directs users to the relevant documentation section.

apps/web/ui/folders/folder-actions.tsx (1)

24-24: LGTM! Clean rename from Panel to Sheet.

The import and hook usage have been correctly updated to use the new useEditFolderSheet API. The functionality remains unchanged.

Also applies to: 53-56

apps/web/ui/folders/edit-folder-sheet.tsx (3)

14-14: LGTM! Required imports added.

The WorkspaceRole and DynamicTooltipWrapper imports support the new workspace owner access control logic introduced in this PR.

Also applies to: 19-19


35-35: LGTM! Consistent Panel β†’ Sheet terminology update.

All interfaces, components, and hooks have been systematically renamed from "Panel" to "Sheet". The changes are consistent with the updated usage in folder-actions.tsx.

Also applies to: 42-45, 219-225, 332-346


252-255: Verification completeβ€”no issues found.

The FolderUser type in apps/web/lib/types.ts:521-523 correctly includes the workspaceRole: WorkspaceRole; property, and the API endpoint apps/web/app/api/folders/[folderId]/users/route.ts properly returns this field. The code at lines 252-255 in edit-folder-sheet.tsx is correctly typed and implements the workspace owner access control as intended.

@steven-tey
Copy link
Collaborator

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 19, 2025

βœ… Actions performed

Full review triggered.

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

πŸ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 4b196ee and 486904c.

πŸ“’ Files selected for processing (1)
  • apps/web/ui/folders/folder-info-panel.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
πŸ““ Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
🧬 Code graph analysis (1)
apps/web/ui/folders/folder-info-panel.tsx (1)
apps/web/lib/types.ts (1)
  • FolderUser (521-524)
⏰ 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: Socket Security: Pull Request Alerts
  • GitHub Check: build
πŸ”‡ Additional comments (2)
apps/web/ui/folders/folder-info-panel.tsx (2)

7-7: LGTM! Import changes align with the refactored UserList.

The removal of Tooltip and addition of Avatar correctly reflects the change from tooltip-based hover display to a direct list layout.


75-75: LGTM! Consistent loading state propagation.

The isLoading prop is correctly passed to all three UserList instances, ensuring consistent loading behavior across owner, editor, and viewer sections.

Also applies to: 82-82, 89-89

@steven-tey steven-tey merged commit 19b593d into main Nov 20, 2025
7 of 8 checks passed
@steven-tey steven-tey deleted the workspace-owner-folder-permissions branch November 20, 2025 00:14
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.

3 participants