-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Move video to folder #1131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Move video to folder #1131
Conversation
- Add validation to ensure all requested videos exist in the space before updating - Prevent silent partial updates when some videos are missing from space_videos - Throw descriptive error when videos are not found in the specified space - Improves data integrity and prevents misleading success responses
WalkthroughAdds folder retrieval and video move-to-folder capabilities. Introduces server actions for fetching all folders and moving videos, expands folder library with root-aware tree building and move logic, updates SelectedCapsBar to open a folder selection dialog, and implements a new FolderSelectionDialog component to browse folders and trigger moves. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Bar as SelectedCapsBar
participant Dialog as FolderSelectionDialog
participant SA1 as getAllFoldersAction (server)
participant Lib1 as folder.getAllFolders
participant DB as Database
User->>Bar: Click "Move to Folder"
Bar->>Dialog: open=true
Dialog->>SA1: fetch folders (root: org/space)
SA1->>Lib1: getAllFolders(root)
Lib1->>DB: query folders (root-aware)
DB-->>Lib1: rows
Lib1-->>SA1: folder tree
SA1-->>Dialog: { success, folders }
Dialog->>User: Show tree, counts
User->>Dialog: Select target, confirm
participant SA2 as moveVideosToFolderAction (server)
participant Lib2 as folder.moveVideosToFolder
Dialog->>SA2: { videoIds, targetFolderId, spaceId? }
SA2->>Lib2: moveVideosToFolder(...)
Lib2->>DB: validate + update folders/videos
DB-->>Lib2: ok
Lib2-->>SA2: { movedCount, deltas, originalFolderIds, targetFolderId }
SA2-->>Dialog: success payload
SA2->>SA2: revalidate dashboard paths
Dialog->>Bar: onConfirm(folderId)
Dialog->>Dialog: update local caches / close
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (3)
🧰 Additional context used📓 Path-based instructions (7)apps/web/actions/**/*.ts📄 CodeRabbit inference engine (CLAUDE.md)
Files:
apps/web/**/*.{ts,tsx}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
**/*.{ts,tsx,js,jsx,rs}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
**/*.{ts,tsx}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
**/*.{ts,tsx,js,jsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
apps/web/**/*.{ts,tsx,js,jsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
apps/web/app/**/*.{tsx,ts}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
🧬 Code graph analysis (3)apps/web/actions/folders/move-videos-to-folder.ts (5)
apps/web/lib/folder.ts (6)
apps/web/app/(org)/dashboard/_components/folder-selection-dialog.tsx (5)
⏰ 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)
🔇 Additional comments (2)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
apps/web/actions/folders/getAllFolders.ts(1 hunks)apps/web/actions/folders/moveVideosToFolder.ts(1 hunks)apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx(1 hunks)apps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsx(1 hunks)apps/web/lib/folder.ts(2 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
apps/web/actions/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
All Groq/OpenAI calls must be implemented in Next.js Server Actions under apps/web/actions; do not place AI calls elsewhere
Files:
apps/web/actions/folders/moveVideosToFolder.tsapps/web/actions/folders/getAllFolders.ts
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for all client-side server state and data fetching in the web app
Web mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData rather than broad invalidations
Client code should use useEffectQuery/useEffectMutation and useRpcClient from apps/web/lib/EffectRuntime.ts; do not create ManagedRuntime inside components
Files:
apps/web/actions/folders/moveVideosToFolder.tsapps/web/lib/folder.tsapps/web/actions/folders/getAllFolders.tsapps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsxapps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx
**/*.{ts,tsx,js,jsx,rs}
📄 CodeRabbit inference engine (CLAUDE.md)
Do not add inline, block, or docstring comments in any language; code must be self-explanatory
Files:
apps/web/actions/folders/moveVideosToFolder.tsapps/web/lib/folder.tsapps/web/actions/folders/getAllFolders.tsapps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsxapps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use strict TypeScript and avoid any; leverage shared types from packages
**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by runningpnpm format.
Files:
apps/web/actions/folders/moveVideosToFolder.tsapps/web/lib/folder.tsapps/web/actions/folders/getAllFolders.tsapps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsxapps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g.,user-menu.tsx).
Use PascalCase for React/Solid components.
Files:
apps/web/actions/folders/moveVideosToFolder.tsapps/web/lib/folder.tsapps/web/actions/folders/getAllFolders.tsapps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsxapps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
On the client, always use
useEffectQueryoruseEffectMutationfrom@/lib/EffectRuntime; never callEffectRuntime.run*directly in components.
Files:
apps/web/actions/folders/moveVideosToFolder.tsapps/web/lib/folder.tsapps/web/actions/folders/getAllFolders.tsapps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsxapps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx
apps/web/app/**/*.{tsx,ts}
📄 CodeRabbit inference engine (CLAUDE.md)
Prefer Server Components for initial data in the Next.js App Router and pass initialData to client components
Files:
apps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsxapps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx
🧬 Code graph analysis (5)
apps/web/actions/folders/moveVideosToFolder.ts (5)
packages/web-domain/src/Folder.ts (1)
Folder(19-27)apps/web/lib/folder.ts (1)
moveVideosToFolder(383-500)packages/web-domain/src/Policy.ts (1)
Policy(7-10)packages/web-domain/src/Authentication.ts (1)
CurrentUser(7-10)apps/web/lib/server.ts (1)
runPromise(59-71)
apps/web/lib/folder.ts (6)
packages/database/index.ts (1)
db(29-34)packages/web-backend/src/Database.ts (1)
Database(9-19)packages/database/schema.ts (4)
folders(219-243)spaceVideos(577-597)videos(245-293)users(47-96)packages/web-domain/src/Folder.ts (3)
Folder(19-27)FolderId(8-8)FolderId(9-9)packages/web-domain/src/Video.ts (3)
Video(14-59)VideoId(10-10)VideoId(11-11)packages/web-domain/src/Authentication.ts (1)
CurrentUser(7-10)
apps/web/actions/folders/getAllFolders.ts (4)
packages/database/schema.ts (1)
folders(219-243)apps/web/lib/server.ts (1)
runPromise(59-71)apps/web/lib/folder.ts (1)
getAllFolders(309-381)packages/web-domain/src/Authentication.ts (1)
CurrentUser(7-10)
apps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsx (3)
packages/web-domain/src/Video.ts (3)
Video(14-59)VideoId(10-10)VideoId(11-11)apps/web/app/(org)/dashboard/_components/ConfirmationDialog.tsx (1)
ConfirmationDialog(25-72)apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx (1)
FolderSelectionDialog(45-311)
apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx (4)
apps/web/app/(org)/dashboard/Contexts.tsx (1)
useDashboardContext(44-44)apps/web/lib/EffectRuntime.ts (2)
useEffectQuery(23-23)useEffectMutation(24-24)apps/web/actions/folders/getAllFolders.ts (1)
getAllFoldersAction(9-35)apps/web/actions/folders/moveVideosToFolder.ts (1)
moveVideosToFolderAction(17-97)
⏰ 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: Build Desktop (aarch64-apple-darwin, macos-latest)
- GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx
Outdated
Show resolved
Hide resolved
apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx
Outdated
Show resolved
Hide resolved
apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx
Outdated
Show resolved
Hide resolved
| // Get all folders in one query | ||
| const allFolders = yield* db.execute((db) => | ||
| db | ||
| .select({ | ||
| id: folders.id, | ||
| name: folders.name, | ||
| color: folders.color, | ||
| parentId: folders.parentId, | ||
| organizationId: folders.organizationId, | ||
| videoCount: sql<number>`( | ||
| SELECT COUNT(*) FROM videos WHERE videos.folderId = folders.id | ||
| )`, | ||
| }) | ||
| .from(folders) | ||
| .where( | ||
| and( | ||
| eq(folders.organizationId, user.activeOrganizationId), | ||
| root.variant === "space" | ||
| ? eq(folders.spaceId, root.spaceId) | ||
| : isNull(folders.spaceId) | ||
| ) | ||
| ) | ||
| ); | ||
|
|
||
| // Define the folder with children type | ||
| type FolderWithChildren = { | ||
| id: string; | ||
| name: string; | ||
| color: "normal" | "blue" | "red" | "yellow"; | ||
| parentId: string | null; | ||
| organizationId: string; | ||
| videoCount: number; | ||
| children: FolderWithChildren[]; | ||
| }; | ||
|
|
||
| // Build hierarchy client-side | ||
| const folderMap = new Map<string, FolderWithChildren>(); | ||
| const rootFolders: FolderWithChildren[] = []; | ||
|
|
||
| // First pass: create folder objects with children array | ||
| allFolders.forEach((folder) => { | ||
| folderMap.set(folder.id, { ...folder, children: [] }); | ||
| }); | ||
|
|
||
| // Second pass: build parent-child relationships | ||
| allFolders.forEach((folder) => { | ||
| const folderWithChildren = folderMap.get(folder.id); | ||
|
|
||
| if (folder.parentId) { | ||
| const parent = folderMap.get(folder.parentId); | ||
| if (parent && folderWithChildren) { | ||
| parent.children.push(folderWithChildren); | ||
| } | ||
| } else { | ||
| if (folderWithChildren) { | ||
| rootFolders.push(folderWithChildren); | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| return rootFolders; | ||
| }); | ||
|
|
||
| export const moveVideosToFolder = Effect.fn(function* ( | ||
| videoIds: Video.VideoId[], | ||
| targetFolderId: Folder.FolderId | null, | ||
| root?: | ||
| | { variant: "space"; spaceId: string } | ||
| | { variant: "org"; organizationId: string } | ||
| ) { | ||
| if (videoIds.length === 0) throw new Error("No videos to move"); | ||
|
|
||
| const db = yield* Database; | ||
| const user = yield* CurrentUser; | ||
|
|
||
| if (!user.activeOrganizationId) throw new Error("No active organization"); | ||
|
|
||
| // Validate that all videos exist and belong to the user | ||
| const existingVideos = yield* db.execute((db) => | ||
| db | ||
| .select({ | ||
| id: videos.id, | ||
| folderId: videos.folderId, | ||
| ownerId: videos.ownerId, | ||
| }) | ||
| .from(videos) | ||
| .where(and(inArray(videos.id, videoIds), eq(videos.ownerId, user.id))) | ||
| ); | ||
|
|
||
| if (existingVideos.length !== videoIds.length) { | ||
| throw new Error( | ||
| "Some videos not found or you don't have permission to move them" | ||
| ); | ||
| } | ||
|
|
||
| // If target folder is specified, validate it exists and user has access | ||
| if (targetFolderId) { | ||
| const targetFolder = yield* getFolderById(targetFolderId); | ||
|
|
||
| if (targetFolder.organizationId !== user.activeOrganizationId) { | ||
| throw new Error("Target folder not found or you don't have access to it"); | ||
| } | ||
|
|
||
| // Validate space context if provided | ||
| if (root?.variant === "space" && targetFolder.spaceId !== root.spaceId) { | ||
| throw new Error("Target folder does not belong to the specified space"); | ||
| } | ||
|
|
||
| // Block moves into space folders when not operating in that space | ||
| if (root?.variant !== "space" && targetFolder.spaceId !== null) { | ||
| throw new Error( | ||
| "Target folder is scoped to a space and cannot be used here" | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| // Determine original folder ids and perform the move based on context | ||
| let originalFolderIds: (string | null)[] = []; | ||
|
|
||
| if (root?.variant === "space") { | ||
| // Collect originals from space_videos | ||
| const spaceRows = yield* db.execute((db) => | ||
| db | ||
| .select({ | ||
| folderId: spaceVideos.folderId, | ||
| videoId: spaceVideos.videoId, | ||
| }) | ||
| .from(spaceVideos) | ||
| .where( | ||
| and( | ||
| eq(spaceVideos.spaceId, root.spaceId), | ||
| inArray(spaceVideos.videoId, videoIds) | ||
| ) | ||
| ) | ||
| ); | ||
| const spaceVideoIds = new Set(spaceRows.map((row) => row.videoId)); | ||
| const missingVideoIds = videoIds.filter((id) => !spaceVideoIds.has(id)); | ||
| if (missingVideoIds.length > 0) { | ||
| throw new Error( | ||
| "Some videos are not in the specified space or you don't have permission to move them" | ||
| ); | ||
| } | ||
| originalFolderIds = [ | ||
| ...new Set(spaceRows.map((r) => r.folderId).filter(Boolean)), | ||
| ]; | ||
|
|
||
| // Update per-space folder placement | ||
| yield* db.execute((db) => | ||
| db | ||
| .update(spaceVideos) | ||
| .set({ folderId: targetFolderId }) | ||
| .where( | ||
| and( | ||
| eq(spaceVideos.spaceId, root.spaceId), | ||
| inArray(spaceVideos.videoId, videoIds) | ||
| ) | ||
| ) | ||
| ); | ||
| } else { | ||
| // ORG/global placement via videos.folderId | ||
| originalFolderIds = [ | ||
| ...new Set(existingVideos.map((v) => v.folderId).filter(Boolean)), | ||
| ]; | ||
|
|
||
| yield* db.execute((db) => | ||
| db | ||
| .update(videos) | ||
| .set({ | ||
| folderId: targetFolderId, | ||
| updatedAt: new Date(), | ||
| }) | ||
| .where(inArray(videos.id, videoIds)) | ||
| ); | ||
| } | ||
|
|
||
| return { | ||
| movedCount: videoIds.length, | ||
| originalFolderIds, | ||
| targetFolderId, | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove inline comments introduced in this module.
The recent changes added several // … annotations (e.g. “Get all folders in one query”, “Build hierarchy…”, “Validate that all videos exist…”) in this file. Our TS guideline forbids inline/block comments, so please delete these comment lines across getAllFolders and moveVideosToFolder. As per coding guidelines
- Remove inline comments per coding guidelines - Add scope isolation with isNull(folders.spaceId) filters - Implement permission checks with SpacesPolicy validation - Replace broad cache invalidation with targeted setQueryData updates - Return detailed result data for efficient cache mutations - Ensure space context validation to prevent cross-scope moves
There was a problem hiding this 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/app/(org)/dashboard/_components/FolderSelectionDialog.tsx (1)
81-151: LGTM - cache updates are now properly targeted!The mutation correctly uses
setQueryDataandsetQueriesDatafor targeted cache updates instead of broad invalidations, following the coding guidelines. The cache update logic properly decrements video counts from original folders and increments the target folder count.Minor: Replace
anytype with explicit Video type.Line 131 uses
any[]for oldVideos, which violates the "avoid any" guideline. Consider defining and using an explicit Video type from your domain types.Apply this diff:
+import type { Video } from "@cap/web-domain"; + queryClient.setQueriesData( { queryKey: ["videos"] }, - (oldVideos: any[] | undefined) => { + (oldVideos: Array<{ id: string; folderId: string | null }> | undefined) => {As per coding guidelines
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/web/actions/folders/moveVideosToFolder.ts(1 hunks)apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx(1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for all client-side server state and data fetching in the web app
Web mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData rather than broad invalidations
Client code should use useEffectQuery/useEffectMutation and useRpcClient from apps/web/lib/EffectRuntime.ts; do not create ManagedRuntime inside components
Files:
apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsxapps/web/actions/folders/moveVideosToFolder.ts
apps/web/app/**/*.{tsx,ts}
📄 CodeRabbit inference engine (CLAUDE.md)
Prefer Server Components for initial data in the Next.js App Router and pass initialData to client components
Files:
apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx
**/*.{ts,tsx,js,jsx,rs}
📄 CodeRabbit inference engine (CLAUDE.md)
Do not add inline, block, or docstring comments in any language; code must be self-explanatory
Files:
apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsxapps/web/actions/folders/moveVideosToFolder.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use strict TypeScript and avoid any; leverage shared types from packages
**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by runningpnpm format.
Files:
apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsxapps/web/actions/folders/moveVideosToFolder.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g.,user-menu.tsx).
Use PascalCase for React/Solid components.
Files:
apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsxapps/web/actions/folders/moveVideosToFolder.ts
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
On the client, always use
useEffectQueryoruseEffectMutationfrom@/lib/EffectRuntime; never callEffectRuntime.run*directly in components.
Files:
apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsxapps/web/actions/folders/moveVideosToFolder.ts
apps/web/actions/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
All Groq/OpenAI calls must be implemented in Next.js Server Actions under apps/web/actions; do not place AI calls elsewhere
Files:
apps/web/actions/folders/moveVideosToFolder.ts
🧠 Learnings (2)
📚 Learning: 2025-09-22T14:17:47.407Z
Learnt from: CR
PR: CapSoftware/Cap#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-22T14:17:47.407Z
Learning: Applies to **/*.{ts,tsx,js,jsx,rs} : Do not add inline, block, or docstring comments in any language; code must be self-explanatory
Applied to files:
apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx
📚 Learning: 2025-09-22T14:17:47.407Z
Learnt from: CR
PR: CapSoftware/Cap#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-22T14:17:47.407Z
Learning: Applies to apps/web/**/*.{ts,tsx} : Web mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData rather than broad invalidations
Applied to files:
apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx
🧬 Code graph analysis (2)
apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx (5)
apps/web/app/(org)/dashboard/Contexts.tsx (1)
useDashboardContext(44-44)apps/web/lib/EffectRuntime.ts (2)
useEffectQuery(23-23)useEffectMutation(24-24)apps/web/actions/folders/getAllFolders.ts (1)
getAllFoldersAction(9-35)packages/database/schema.ts (1)
folders(219-243)apps/web/actions/folders/moveVideosToFolder.ts (1)
moveVideosToFolderAction(17-97)
apps/web/actions/folders/moveVideosToFolder.ts (4)
apps/web/lib/folder.ts (1)
moveVideosToFolder(383-500)packages/web-domain/src/Policy.ts (1)
Policy(7-10)packages/web-domain/src/Authentication.ts (1)
CurrentUser(7-10)apps/web/lib/server.ts (1)
runPromise(59-71)
⏰ 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: Build Desktop (aarch64-apple-darwin, macos-latest)
- GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
🔇 Additional comments (7)
apps/web/actions/folders/moveVideosToFolder.ts (3)
1-15: LGTM!The imports are clean, all used, and the interface is properly typed with optional space context support.
17-54: LGTM!The function logic is well-structured:
- Proper authentication and authorization checks
- Correct use of branded types for domain IDs
- Conditional policy enforcement (SpacesPolicy for space context)
- Proper Effect composition with CurrentUser service injection
56-96: LGTM!The cache revalidation strategy is comprehensive and context-aware, properly invalidating affected paths in both space and organization contexts. Error handling includes logging and returns a structured error response.
apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx (4)
44-79: LGTM!The component properly uses
useEffectQueryfrom EffectRuntime and implements proper enabled guards. The non-null assertion on line 66 is protected by the enabled flag on line 76.
153-232: LGTM!The folder expansion toggle and recursive rendering logic is clean and correct. Proper state management with Set for expanded folders and depth-based indentation for the tree structure.
234-245: LGTM!The confirmation and cancellation handlers are straightforward and properly clean up component state.
247-336: LGTM!The dialog JSX structure is well-organized with proper loading states, empty states, and user feedback. Good UX with disabled buttons during mutation.
| "use client"; | ||
|
|
||
| import { | ||
| Button, | ||
| Dialog, | ||
| DialogContent, | ||
| DialogFooter, | ||
| DialogHeader, | ||
| DialogTitle, | ||
| } from "@cap/ui"; | ||
| import { | ||
| faFolderOpen, | ||
| faChevronRight, | ||
| faChevronDown, | ||
| } from "@fortawesome/free-solid-svg-icons"; | ||
| import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | ||
| import { useEffectMutation, useEffectQuery } from "@/lib/EffectRuntime"; | ||
| import { useState } from "react"; | ||
| import { getAllFoldersAction } from "../../../../actions/folders/getAllFolders"; | ||
| import { moveVideosToFolderAction } from "../../../../actions/folders/moveVideosToFolder"; | ||
| import { useDashboardContext } from "../Contexts"; | ||
| import { toast } from "sonner"; | ||
| import { Effect } from "effect"; | ||
| import { useQueryClient } from "@tanstack/react-query"; | ||
|
|
||
| type FolderWithChildren = { | ||
| id: string; | ||
| name: string; | ||
| color: "normal" | "blue" | "red" | "yellow"; | ||
| parentId: string | null; | ||
| organizationId: string; | ||
| videoCount: number; | ||
| children: FolderWithChildren[]; | ||
| }; | ||
|
|
||
| interface FolderSelectionDialogProps { | ||
| open: boolean; | ||
| onClose: () => void; | ||
| onConfirm: (folderId: string | null) => void; | ||
| selectedCount: number; | ||
| videoIds: string[]; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Rename file to follow kebab-case convention.
The coding guidelines require kebab-case for filenames. This file should be renamed from FolderSelectionDialog.tsx to folder-selection-dialog.tsx. As per coding guidelines
🤖 Prompt for AI Agents
In apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx lines
1-42: the filename violates the repo's kebab-case convention; rename the file to
folder-selection-dialog.tsx and update any imports referencing
FolderSelectionDialog.tsx across the codebase (including index files and
component consumers) to use the new kebab-case path, ensure git moves the file
(git mv) to preserve history, and run the build/tests to verify no import paths
remain unchanged.
- Rename FolderSelectionDialog.tsx to folder-selection-dialog.tsx per coding guidelines - Update import path in SelectedCapsBar.tsx to use new kebab-case filename - Preserve git history using git mv command
There was a problem hiding this 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
📒 Files selected for processing (2)
apps/web/app/(org)/dashboard/_components/folder-selection-dialog.tsx(1 hunks)apps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsx
🧰 Additional context used
📓 Path-based instructions (6)
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for all client-side server state and data fetching in the web app
Web mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData rather than broad invalidations
Client code should use useEffectQuery/useEffectMutation and useRpcClient from apps/web/lib/EffectRuntime.ts; do not create ManagedRuntime inside components
Files:
apps/web/app/(org)/dashboard/_components/folder-selection-dialog.tsx
apps/web/app/**/*.{tsx,ts}
📄 CodeRabbit inference engine (CLAUDE.md)
Prefer Server Components for initial data in the Next.js App Router and pass initialData to client components
Files:
apps/web/app/(org)/dashboard/_components/folder-selection-dialog.tsx
**/*.{ts,tsx,js,jsx,rs}
📄 CodeRabbit inference engine (CLAUDE.md)
Do not add inline, block, or docstring comments in any language; code must be self-explanatory
Files:
apps/web/app/(org)/dashboard/_components/folder-selection-dialog.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use strict TypeScript and avoid any; leverage shared types from packages
**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by runningpnpm format.
Files:
apps/web/app/(org)/dashboard/_components/folder-selection-dialog.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g.,user-menu.tsx).
Use PascalCase for React/Solid components.
Files:
apps/web/app/(org)/dashboard/_components/folder-selection-dialog.tsx
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
On the client, always use
useEffectQueryoruseEffectMutationfrom@/lib/EffectRuntime; never callEffectRuntime.run*directly in components.
Files:
apps/web/app/(org)/dashboard/_components/folder-selection-dialog.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
- GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
- Replace over-deduction logic with precise per-folder video count deltas - Add videoCountDeltas to moveVideosToFolder return value with exact decrements/increments - Implement recursive applyFolderDeltas helper to update entire folder tree - Fix multi-source folder moves by calculating actual videos moved per folder - Ensure nested folders at all depths get updated correctly - Prevent cache staleness in grandchildren and deeper folder descendants
There was a problem hiding this 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 (2)
apps/web/lib/folder.ts (1)
345-353: Consider extracting FolderWithChildren type to avoid duplication.The
FolderWithChildrentype is defined locally here and also infolder-selection-dialog.tsx(lines 26-34). Consider extracting this type to a shared location (e.g.,packages/web-domain/src/Folder.tsorapps/web/types/folder.ts) to maintain a single source of truth and ensure consistency if the structure evolves.Example location for the shared type:
// packages/web-domain/src/Folder.ts export type FolderWithChildren = { id: string; name: string; color: "normal" | "blue" | "red" | "yellow"; parentId: string | null; organizationId: string; videoCount: number; children: FolderWithChildren[]; };Then import and use in both files:
+import type { FolderWithChildren } from "@cap/web-domain"; -type FolderWithChildren = { ... };apps/web/actions/folders/moveVideosToFolder.ts (1)
91-97: Consider structured logging for production.The error handling correctly catches and returns structured error responses. For production observability, consider using a structured logging service instead of
console.errorto enable better debugging and alerting.If you'd like, I can help generate a structured logging wrapper that integrates with your observability stack.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/web/actions/folders/moveVideosToFolder.ts(1 hunks)apps/web/app/(org)/dashboard/_components/folder-selection-dialog.tsx(1 hunks)apps/web/lib/folder.ts(2 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
apps/web/actions/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
All Groq/OpenAI calls must be implemented in Next.js Server Actions under apps/web/actions; do not place AI calls elsewhere
Files:
apps/web/actions/folders/moveVideosToFolder.ts
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for all client-side server state and data fetching in the web app
Web mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData rather than broad invalidations
Client code should use useEffectQuery/useEffectMutation and useRpcClient from apps/web/lib/EffectRuntime.ts; do not create ManagedRuntime inside components
Files:
apps/web/actions/folders/moveVideosToFolder.tsapps/web/lib/folder.tsapps/web/app/(org)/dashboard/_components/folder-selection-dialog.tsx
**/*.{ts,tsx,js,jsx,rs}
📄 CodeRabbit inference engine (CLAUDE.md)
Do not add inline, block, or docstring comments in any language; code must be self-explanatory
Files:
apps/web/actions/folders/moveVideosToFolder.tsapps/web/lib/folder.tsapps/web/app/(org)/dashboard/_components/folder-selection-dialog.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use strict TypeScript and avoid any; leverage shared types from packages
**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by runningpnpm format.
Files:
apps/web/actions/folders/moveVideosToFolder.tsapps/web/lib/folder.tsapps/web/app/(org)/dashboard/_components/folder-selection-dialog.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g.,user-menu.tsx).
Use PascalCase for React/Solid components.
Files:
apps/web/actions/folders/moveVideosToFolder.tsapps/web/lib/folder.tsapps/web/app/(org)/dashboard/_components/folder-selection-dialog.tsx
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
On the client, always use
useEffectQueryoruseEffectMutationfrom@/lib/EffectRuntime; never callEffectRuntime.run*directly in components.
Files:
apps/web/actions/folders/moveVideosToFolder.tsapps/web/lib/folder.tsapps/web/app/(org)/dashboard/_components/folder-selection-dialog.tsx
apps/web/app/**/*.{tsx,ts}
📄 CodeRabbit inference engine (CLAUDE.md)
Prefer Server Components for initial data in the Next.js App Router and pass initialData to client components
Files:
apps/web/app/(org)/dashboard/_components/folder-selection-dialog.tsx
🧬 Code graph analysis (3)
apps/web/actions/folders/moveVideosToFolder.ts (5)
packages/web-domain/src/Folder.ts (1)
Folder(19-27)apps/web/lib/folder.ts (1)
moveVideosToFolder(383-527)packages/web-domain/src/Policy.ts (1)
Policy(7-10)packages/web-domain/src/Authentication.ts (1)
CurrentUser(7-10)apps/web/lib/server.ts (1)
runPromise(59-71)
apps/web/lib/folder.ts (6)
packages/database/index.ts (1)
db(29-34)packages/web-backend/src/Database.ts (1)
Database(9-19)packages/database/schema.ts (9)
folders(219-243)spaceVideos(577-597)spaces(532-552)organizations(149-169)sharedVideos(295-315)videos(245-293)comments(317-337)users(47-96)videoUploads(656-662)packages/web-domain/src/Folder.ts (3)
Folder(19-27)FolderId(8-8)FolderId(9-9)packages/web-domain/src/Video.ts (3)
Video(14-59)VideoId(10-10)VideoId(11-11)packages/web-domain/src/Authentication.ts (1)
CurrentUser(7-10)
apps/web/app/(org)/dashboard/_components/folder-selection-dialog.tsx (4)
apps/web/app/(org)/dashboard/Contexts.tsx (1)
useDashboardContext(44-44)apps/web/lib/EffectRuntime.ts (2)
useEffectQuery(23-23)useEffectMutation(24-24)apps/web/actions/folders/getAllFolders.ts (1)
getAllFoldersAction(9-35)apps/web/actions/folders/moveVideosToFolder.ts (1)
moveVideosToFolderAction(17-98)
⏰ 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: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
- GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
🔇 Additional comments (10)
apps/web/app/(org)/dashboard/_components/folder-selection-dialog.tsx (3)
58-77: Correct usage of Effect-based query pattern.The query setup properly:
- Uses
useEffectQuerywithEffect.tryPromiseas per coding guidelines- Constructs space-aware or org-aware root context from
useDashboardContext- Enables the query conditionally when dialog is open and organization is available
- Handles errors with clear error messages
104-122: Recursive delta application correctly fixes multi-folder cache updates.The
applyFolderDeltashelper now properly:
- Walks the entire folder tree recursively, not just two levels
- Applies per-folder deltas from
result.videoCountDeltasrather than uniform deductions- Handles missing deltas with
?? 0fallback- Updates all descendant folders through recursive
childrenprocessingThis addresses the previous review's concern about over-deduction and stale grandchild counts.
160-227: Clean recursive folder rendering with proper state management.The
renderFolderfunction correctly:
- Handles nested folder hierarchies with depth tracking
- Manages expansion and selection state independently
- Styles selected folders with visual indicators
- Recursively renders children when expanded
The implementation provides a good user experience for navigating folder hierarchies.
apps/web/lib/folder.ts (3)
309-381: Efficient hierarchical folder construction with proper filtering.The
getAllFoldersimplementation correctly:
- Fetches all folders in a single query with organization and space filtering
- Uses
isNull(folders.spaceId)for org-level folder isolation- Builds the hierarchy client-side with a two-pass algorithm (map construction, then parent-child linking)
- Includes
videoCountsubquery for each folderThe client-side hierarchy building is efficient and maintains proper parent-child relationships.
415-434: Robust validation ensures space isolation and access control.The target folder validation correctly enforces:
- Organization membership (lines 419-421)
- Space context consistency when operating in space mode (lines 423-426)
- Prevention of cross-context moves by blocking moves into space folders from org context (lines 428-433)
This validation is critical for maintaining space isolation and preventing unauthorized folder access.
440-519: Dual-mode move operation with accurate per-folder delta calculation.The implementation correctly handles both contexts:
- Space mode (lines 440-489): Updates
spaceVideos.folderIdafter validating videos are in the space- Org mode (lines 490-514): Updates
videos.folderIdfor organization-level placement- Delta calculation (lines 464-476, 492-503): Accurately computes per-folder decrements by counting videos per source folder
- Target increment (line 518): Uses
+=to handle cases where target might also be a sourceThe per-folder delta approach ensures cache updates reflect the actual video distribution across folders, even when moving videos from multiple source folders.
apps/web/actions/folders/moveVideosToFolder.ts (4)
1-9: LGTM!Imports are clean, properly organized, and all symbols are used. The "use server" directive is correctly placed for Next.js Server Actions.
22-29: LGTM!User validation follows secure-by-default patterns with proper early-exit on authorization failure. The check for
activeOrganizationIdensures org-scoped operations proceed only with valid context.
40-52: LGTM!The Effect composition correctly applies
SpacesPolicy.isMemberfor space-scoped operations while relying on internal authorization checks inmoveVideosToFolderfor org-level moves. Service provisioning withEffect.provideService(CurrentUser, user)follows Effect best practices. Based on learnings
56-79: LGTM!The revalidation strategy is comprehensive and precise, invalidating both source and target folder paths in the appropriate context (space vs. org). The granular approach aligns with the coding guideline to prefer targeted cache updates. As per coding guidelines
| @@ -0,0 +1,98 @@ | |||
| "use server"; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename file to follow kebab-case convention.
The filename moveVideosToFolder.ts violates the repository's kebab-case convention for TypeScript modules. As per coding guidelines
Rename the file:
git mv apps/web/actions/folders/moveVideosToFolder.ts apps/web/actions/folders/move-videos-to-folder.tsUpdate the import in any files that reference this action:
-import { moveVideosToFolderAction } from '@/actions/folders/moveVideosToFolder';
+import { moveVideosToFolderAction } from '@/actions/folders/move-videos-to-folder';🤖 Prompt for AI Agents
In apps/web/actions/folders/moveVideosToFolder.ts around line 1, the filename
violates the repo's kebab-case convention; rename the file to
apps/web/actions/folders/move-videos-to-folder.ts (e.g. git mv ...) and update
all imports/usages across the codebase to the new path (search for
"moveVideosToFolder" and replace import paths, update any relative paths, and
run TypeScript build/tests to ensure no remaining references).
Summary
Implements comprehensive video folder management functionality with proper validation and space-aware operations.
Changes
moveVideosToFolderfunction with proper validationisNullchecksFeatures
Summary by CodeRabbit