-
Notifications
You must be signed in to change notification settings - Fork 904
web: Add videos button in folders & more #1161
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
Conversation
Caution Review failedThe pull request is closed. WalkthroughAdds space/org-aware folder and video management: new server actions for add/remove/get/move folder videos, branches between organization-scoped Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant UI as Client UI
participant Server as addVideosToFolder
participant DB as Database
rect rgba(230,245,255,0.6)
note over UI,Server: Add videos to folder (space/org aware)
User->>UI: Trigger add videos (folderId, videoIds, spaceId)
UI->>Server: addVideosToFolder(folderId, videoIds, spaceId)
Server->>Server: authenticate & validate inputs
Server->>DB: fetch folder, resolve scope (org vs space)
alt org scope
Server->>DB: update/insert into sharedVideos set folderId
else space scope
Server->>DB: update/insert into spaceVideos set folderId
end
Server-->>UI: { success, addedCount }
UI->>User: refresh view / revalidate paths
end
sequenceDiagram
autonumber
actor User
participant UI as Client UI
participant Server as lib/folder.getVideosByFolderId
participant DB as Database
User->>UI: Request folder videos (folderId, root)
UI->>Server: getVideosByFolderId(folderId, root)
alt root.variant == "user"
Server->>DB: query videos where videos.folderId = folderId
else root.variant == "space"
Server->>DB: join spaceVideos where spaceVideos.folderId = folderId
else root.variant == "org"
Server->>DB: join sharedVideos where sharedVideos.folderId = folderId
end
DB-->>Server: rows
Server-->>UI: data
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60–90 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
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: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
packages/database/migrations/meta/_journal.json (1)
2-74
: Restore the original migration journal versionThe pipeline is failing (
Validate Migrations
) because this commit changes the journalversion
value. The migration journal version is immutable—only append new entries while keeping the existing version untouched. Please revert the version change (and rerundrizzle-kit
so the journal update is generated correctly).apps/web/app/(org)/dashboard/spaces/[spaceId]/page.tsx (2)
37-55
: Restore organisation folder filteringWhen
allSpacesEntry
is true we pass the organisation id intofetchFolders
, but the predicate still doeseq(folders.spaceId, spaceId)
. Organisation-level folders havespaceId
set toNULL
, so this condition filters every row out and the org dashboard loses all folders. Please branch thewhere
clause: in the org path filter byfolders.organizationId = spaceId
plusisNull(folders.spaceId)
, and keep the existingfolders.spaceId = spaceId
check for the space path. Example:- .where(and(eq(folders.spaceId, spaceId), isNull(folders.parentId))); + .where( + allSpacesEntry + ? and( + eq(folders.organizationId, spaceId), + isNull(folders.spaceId), + isNull(folders.parentId), + ) + : and(eq(folders.spaceId, spaceId), isNull(folders.parentId)), + );
266-274
: UsesharedVideos.folderId
in org total countThe organisation total-count query still checks
isNull(videos.folderId)
, but in this refactor folder membership lives insharedVideos.folderId
. Once a video is placed into an org folder, it keepsvideos.folderId = NULL
, so the count keeps including it. Pagination will claim more pages than exist and users land on empty pages. Swap the predicate toisNull(sharedVideos.folderId)
to align with the select query.- .where( - and( - eq(sharedVideos.organizationId, orgId), - isNull(videos.folderId), - ), - ), + .where( + and( + eq(sharedVideos.organizationId, orgId), + isNull(sharedVideos.folderId), + ), + ),apps/web/lib/folder.ts (1)
298-301
: Fix child-folder video counts for space/org rootsAfter rerouting space/org folder membership into
spaceVideos
/sharedVideos
, the child-folder counter still reads fromvideos.folderId
. For space or org variants this always returns zero, so nested folders show empty counts even when populated. Please branch the subquery to count fromspaceVideos
(filtered byroot.spaceId
) orsharedVideos
(filtered by the active organisation) instead ofvideos
.- videoCount: sql<number>`( - SELECT COUNT(*) FROM videos WHERE videos.folderId = folders.id - )`, + videoCount: + root.variant === "space" + ? sql<number>`( + SELECT COUNT(*) + FROM ${spaceVideos} + WHERE ${spaceVideos}.folderId = ${folders}.id + AND ${spaceVideos}.spaceId = ${root.spaceId} + )` + : root.variant === "org" + ? sql<number>`( + SELECT COUNT(*) + FROM ${sharedVideos} + WHERE ${sharedVideos}.folderId = ${folders}.id + AND ${sharedVideos}.organizationId = ${user.activeOrganizationId} + )` + : sql<number>`( + SELECT COUNT(*) FROM ${videos} WHERE ${videos}.folderId = ${folders}.id + )`,
🧹 Nitpick comments (1)
apps/web/actions/folders/remove-videos.ts (1)
64-88
: Inconsistent query operator at line 83.Lines 64-75 use
eq
for all comparisons insharedVideos
, but line 83 uses asql
template for thespaceId
comparison inspaceVideos
. For consistency and clarity, prefereq
unless there's a specific reason for the template.Apply this diff to use
eq
consistently:- sql`${spaceVideos.spaceId} = ${folder.spaceId}`, + eq(spaceVideos.spaceId, folder.spaceId),
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (26)
apps/web/actions/folders/add-videos.ts
(1 hunks)apps/web/actions/folders/get-folder-videos.ts
(1 hunks)apps/web/actions/folders/moveVideoToFolder.ts
(3 hunks)apps/web/actions/folders/remove-videos.ts
(1 hunks)apps/web/actions/organizations/add-videos.ts
(1 hunks)apps/web/actions/organizations/get-organization-videos.ts
(2 hunks)apps/web/actions/spaces/add-videos.ts
(3 hunks)apps/web/actions/spaces/get-space-videos.ts
(2 hunks)apps/web/actions/spaces/get-user-videos.ts
(1 hunks)apps/web/actions/spaces/remove-videos.ts
(2 hunks)apps/web/actions/videos/get-user-videos.ts
(0 hunks)apps/web/app/(org)/dashboard/caps/page.tsx
(0 hunks)apps/web/app/(org)/dashboard/folder/[id]/components/FolderVideosSection.tsx
(0 hunks)apps/web/app/(org)/dashboard/folder/[id]/page.tsx
(1 hunks)apps/web/app/(org)/dashboard/spaces/[spaceId]/SharedCaps.tsx
(6 hunks)apps/web/app/(org)/dashboard/spaces/[spaceId]/components/AddVideosDialog.tsx
(2 hunks)apps/web/app/(org)/dashboard/spaces/[spaceId]/components/AddVideosDialogBase.tsx
(8 hunks)apps/web/app/(org)/dashboard/spaces/[spaceId]/components/AddVideosToOrganizationDialog.tsx
(3 hunks)apps/web/app/(org)/dashboard/spaces/[spaceId]/components/VideoCard.tsx
(3 hunks)apps/web/app/(org)/dashboard/spaces/[spaceId]/components/VirtualizedVideoGrid.tsx
(1 hunks)apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/AddVideosButton.tsx
(1 hunks)apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/page.tsx
(2 hunks)apps/web/app/(org)/dashboard/spaces/[spaceId]/page.tsx
(7 hunks)apps/web/lib/folder.ts
(2 hunks)packages/database/migrations/meta/_journal.json
(1 hunks)packages/database/schema.ts
(3 hunks)
💤 Files with no reviewable changes (3)
- apps/web/app/(org)/dashboard/caps/page.tsx
- apps/web/actions/videos/get-user-videos.ts
- apps/web/app/(org)/dashboard/folder/[id]/components/FolderVideosSection.tsx
🧰 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/spaces/get-user-videos.ts
apps/web/actions/organizations/get-organization-videos.ts
apps/web/actions/spaces/remove-videos.ts
apps/web/actions/folders/add-videos.ts
apps/web/actions/spaces/add-videos.ts
apps/web/actions/folders/get-folder-videos.ts
apps/web/actions/organizations/add-videos.ts
apps/web/actions/spaces/get-space-videos.ts
apps/web/actions/folders/remove-videos.ts
apps/web/actions/folders/moveVideoToFolder.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/spaces/get-user-videos.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/SharedCaps.tsx
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/VirtualizedVideoGrid.tsx
apps/web/app/(org)/dashboard/folder/[id]/page.tsx
apps/web/actions/organizations/get-organization-videos.ts
apps/web/actions/spaces/remove-videos.ts
apps/web/actions/folders/add-videos.ts
apps/web/actions/spaces/add-videos.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/page.tsx
apps/web/actions/folders/get-folder-videos.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/AddVideosToOrganizationDialog.tsx
apps/web/lib/folder.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/VideoCard.tsx
apps/web/actions/organizations/add-videos.ts
apps/web/actions/spaces/get-space-videos.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/AddVideosDialogBase.tsx
apps/web/actions/folders/remove-videos.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/AddVideosButton.tsx
apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/page.tsx
apps/web/actions/folders/moveVideoToFolder.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/AddVideosDialog.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/spaces/get-user-videos.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/SharedCaps.tsx
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/VirtualizedVideoGrid.tsx
apps/web/app/(org)/dashboard/folder/[id]/page.tsx
apps/web/actions/organizations/get-organization-videos.ts
apps/web/actions/spaces/remove-videos.ts
apps/web/actions/folders/add-videos.ts
apps/web/actions/spaces/add-videos.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/page.tsx
apps/web/actions/folders/get-folder-videos.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/AddVideosToOrganizationDialog.tsx
apps/web/lib/folder.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/VideoCard.tsx
apps/web/actions/organizations/add-videos.ts
apps/web/actions/spaces/get-space-videos.ts
packages/database/schema.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/AddVideosDialogBase.tsx
apps/web/actions/folders/remove-videos.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/AddVideosButton.tsx
apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/page.tsx
apps/web/actions/folders/moveVideoToFolder.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/AddVideosDialog.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/spaces/get-user-videos.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/SharedCaps.tsx
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/VirtualizedVideoGrid.tsx
apps/web/app/(org)/dashboard/folder/[id]/page.tsx
apps/web/actions/organizations/get-organization-videos.ts
apps/web/actions/spaces/remove-videos.ts
apps/web/actions/folders/add-videos.ts
apps/web/actions/spaces/add-videos.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/page.tsx
apps/web/actions/folders/get-folder-videos.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/AddVideosToOrganizationDialog.tsx
apps/web/lib/folder.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/VideoCard.tsx
apps/web/actions/organizations/add-videos.ts
apps/web/actions/spaces/get-space-videos.ts
packages/database/schema.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/AddVideosDialogBase.tsx
apps/web/actions/folders/remove-videos.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/AddVideosButton.tsx
apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/page.tsx
apps/web/actions/folders/moveVideoToFolder.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/AddVideosDialog.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/spaces/get-user-videos.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/SharedCaps.tsx
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/VirtualizedVideoGrid.tsx
apps/web/app/(org)/dashboard/folder/[id]/page.tsx
apps/web/actions/organizations/get-organization-videos.ts
apps/web/actions/spaces/remove-videos.ts
apps/web/actions/folders/add-videos.ts
apps/web/actions/spaces/add-videos.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/page.tsx
apps/web/actions/folders/get-folder-videos.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/AddVideosToOrganizationDialog.tsx
apps/web/lib/folder.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/VideoCard.tsx
apps/web/actions/organizations/add-videos.ts
apps/web/actions/spaces/get-space-videos.ts
packages/database/schema.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/AddVideosDialogBase.tsx
apps/web/actions/folders/remove-videos.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/AddVideosButton.tsx
apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/page.tsx
apps/web/actions/folders/moveVideoToFolder.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/AddVideosDialog.tsx
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
On the client, always use
useEffectQuery
oruseEffectMutation
from@/lib/EffectRuntime
; never callEffectRuntime.run*
directly in components.
Files:
apps/web/actions/spaces/get-user-videos.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/SharedCaps.tsx
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/VirtualizedVideoGrid.tsx
apps/web/app/(org)/dashboard/folder/[id]/page.tsx
apps/web/actions/organizations/get-organization-videos.ts
apps/web/actions/spaces/remove-videos.ts
apps/web/actions/folders/add-videos.ts
apps/web/actions/spaces/add-videos.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/page.tsx
apps/web/actions/folders/get-folder-videos.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/AddVideosToOrganizationDialog.tsx
apps/web/lib/folder.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/VideoCard.tsx
apps/web/actions/organizations/add-videos.ts
apps/web/actions/spaces/get-space-videos.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/AddVideosDialogBase.tsx
apps/web/actions/folders/remove-videos.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/AddVideosButton.tsx
apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/page.tsx
apps/web/actions/folders/moveVideoToFolder.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/AddVideosDialog.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/spaces/[spaceId]/SharedCaps.tsx
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/VirtualizedVideoGrid.tsx
apps/web/app/(org)/dashboard/folder/[id]/page.tsx
apps/web/app/(org)/dashboard/spaces/[spaceId]/page.tsx
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/AddVideosToOrganizationDialog.tsx
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/VideoCard.tsx
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/AddVideosDialogBase.tsx
apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/AddVideosButton.tsx
apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/page.tsx
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/AddVideosDialog.tsx
🧬 Code graph analysis (20)
apps/web/actions/spaces/get-user-videos.ts (2)
packages/database/schema.ts (8)
videos
(277-332)comments
(364-385)users
(58-110)folders
(249-275)videoUploads
(710-716)sharedVideos
(334-362)spaces
(580-606)spaceVideos
(631-651)packages/database/index.ts (1)
db
(29-34)
apps/web/app/(org)/dashboard/spaces/[spaceId]/SharedCaps.tsx (1)
packages/web-domain/src/Space.ts (2)
SpaceIdOrOrganisationId
(7-7)SpaceIdOrOrganisationId
(8-8)
apps/web/app/(org)/dashboard/folder/[id]/page.tsx (1)
apps/web/lib/folder.ts (1)
getVideosByFolderId
(157-276)
apps/web/actions/organizations/get-organization-videos.ts (1)
packages/database/schema.ts (1)
sharedVideos
(334-362)
apps/web/actions/spaces/remove-videos.ts (2)
packages/database/index.ts (1)
db
(29-34)packages/database/schema.ts (4)
sharedVideos
(334-362)folders
(249-275)videos
(277-332)spaceVideos
(631-651)
apps/web/actions/folders/add-videos.ts (3)
packages/web-domain/src/Folder.ts (1)
Folder
(37-45)packages/database/index.ts (1)
db
(29-34)packages/database/schema.ts (4)
folders
(249-275)videos
(277-332)sharedVideos
(334-362)spaceVideos
(631-651)
apps/web/actions/spaces/add-videos.ts (3)
packages/database/index.ts (1)
db
(29-34)packages/database/schema.ts (2)
sharedVideos
(334-362)spaceVideos
(631-651)packages/database/helpers.ts (1)
nanoId
(6-9)
apps/web/app/(org)/dashboard/spaces/[spaceId]/page.tsx (2)
packages/web-domain/src/Space.ts (2)
SpaceIdOrOrganisationId
(7-7)SpaceIdOrOrganisationId
(8-8)packages/database/schema.ts (2)
sharedVideos
(334-362)spaceVideos
(631-651)
apps/web/actions/folders/get-folder-videos.ts (3)
packages/web-domain/src/Folder.ts (1)
Folder
(37-45)packages/database/index.ts (1)
db
(29-34)packages/database/schema.ts (2)
sharedVideos
(334-362)spaceVideos
(631-651)
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/AddVideosToOrganizationDialog.tsx (2)
apps/web/actions/spaces/get-user-videos.ts (1)
getUserVideos
(18-140)apps/web/actions/organizations/get-organization-videos.ts (1)
getOrganizationVideoIds
(9-49)
apps/web/lib/folder.ts (2)
packages/web-domain/src/Space.ts (2)
SpaceIdOrOrganisationId
(7-7)SpaceIdOrOrganisationId
(8-8)packages/database/schema.ts (3)
spaceVideos
(631-651)videos
(277-332)sharedVideos
(334-362)
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/VideoCard.tsx (2)
apps/web/app/(org)/dashboard/Contexts.tsx (1)
useTheme
(49-49)apps/web/components/VideoThumbnail.tsx (1)
ImageLoadingStatus
(8-8)
apps/web/actions/organizations/add-videos.ts (3)
packages/database/index.ts (1)
db
(29-34)packages/database/schema.ts (1)
sharedVideos
(334-362)packages/database/helpers.ts (1)
nanoId
(6-9)
apps/web/actions/spaces/get-space-videos.ts (2)
packages/database/index.ts (1)
db
(29-34)packages/database/schema.ts (2)
sharedVideos
(334-362)spaceVideos
(631-651)
packages/database/schema.ts (1)
packages/web-domain/src/Folder.ts (3)
Folder
(37-45)FolderId
(11-11)FolderId
(12-12)
apps/web/actions/folders/remove-videos.ts (3)
packages/web-domain/src/Folder.ts (1)
Folder
(37-45)packages/database/index.ts (1)
db
(29-34)packages/database/schema.ts (4)
folders
(249-275)videos
(277-332)sharedVideos
(334-362)spaceVideos
(631-651)
apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/AddVideosButton.tsx (5)
packages/web-domain/src/Folder.ts (1)
Folder
(37-45)apps/web/actions/folders/add-videos.ts (1)
addVideosToFolder
(15-97)apps/web/actions/folders/remove-videos.ts (1)
removeVideosFromFolder
(15-112)apps/web/actions/spaces/get-user-videos.ts (1)
getUserVideos
(18-140)apps/web/actions/folders/get-folder-videos.ts (1)
getFolderVideoIds
(9-50)
apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/page.tsx (3)
packages/web-domain/src/Space.ts (2)
SpaceIdOrOrganisationId
(7-7)SpaceIdOrOrganisationId
(8-8)apps/web/lib/folder.ts (1)
getVideosByFolderId
(157-276)apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/AddVideosButton.tsx (1)
AddVideosButton
(15-52)
apps/web/actions/folders/moveVideoToFolder.ts (2)
packages/web-domain/src/Folder.ts (1)
Folder
(37-45)packages/database/schema.ts (3)
spaceVideos
(631-651)sharedVideos
(334-362)videos
(277-332)
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/AddVideosDialog.tsx (2)
apps/web/actions/spaces/get-user-videos.ts (1)
getUserVideos
(18-140)apps/web/actions/spaces/get-space-videos.ts (1)
getSpaceVideoIds
(9-56)
🪛 GitHub Actions: Validate Migrations
packages/database/migrations/meta/_journal.json
[error] 2-2: Migration journal version changed. Migration journal version cannot be changed (was: $BASE_VERSION, now: $CURRENT_VERSION).
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
- GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
- GitHub Check: Analyze (rust)
🔇 Additional comments (16)
apps/web/app/(org)/dashboard/spaces/[spaceId]/SharedCaps.tsx (2)
49-49
: LGTM!The new
spaceId
prop correctly types asSpace.SpaceIdOrOrganisationId
, enabling the component to distinguish between organization-wide and space-specific contexts.Also applies to: 61-61
185-185
: LGTM!Passing
spaceId
instead ofspaceData.id
to child dialogs is correct. This enables the dialogs to branch betweensharedVideos
(org-wide) andspaceVideos
(space-specific) queries based on theisAllSpacesEntry
pattern used in backend actions.Also applies to: 197-197, 252-252, 264-264
apps/web/actions/folders/get-folder-videos.ts (1)
9-50
: LGTM!The action correctly implements organization-wide vs space-specific branching using the
isAllSpacesEntry
pattern. Authentication, validation, and error handling are appropriate.apps/web/app/(org)/dashboard/spaces/[spaceId]/components/AddVideosDialog.tsx (2)
7-7
: LGTM!The import path update correctly points to the space-scoped
getUserVideos
action that supports the organization-wide vs space-specific branching pattern.
35-36
: LGTM!Wrapping
getUserVideos
andgetSpaceVideoIds
with arrow functions that bindspaceId
is the correct approach. This maintains the component's public API while enabling the base dialog to fetch context-appropriate data.apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/page.tsx (3)
23-26
: LGTM!Correctly updates
spaceId
type toSpace.SpaceIdOrOrganisationId
, aligning with the union type pattern used throughout the codebase for space/org context.
47-52
: LGTM!The
getVideosByFolderId
call correctly passes a root variant object that branches between space-specific and organization-wide contexts based onspaceOrOrg.variant
.
59-63
: LGTM!The
AddVideosButton
integration correctly passesfolderId
,spaceId
, and derivesfolderName
from the breadcrumb, with an appropriate fallback.apps/web/actions/folders/remove-videos.ts (2)
34-41
: LGTM!Folder existence verification is appropriate. Retrieving
spaceId
enables the subsequent branching logic.
44-53
: LGTM!Video ownership validation correctly restricts removal operations to user-owned videos only.
apps/web/actions/spaces/get-space-videos.ts (1)
21-42
: LGTM!The branching logic correctly queries
sharedVideos
for organization-wide contexts andspaceVideos
for space-specific contexts. TheisNull(folderId)
condition appropriately filters for root-level videos.apps/web/actions/organizations/add-videos.ts (2)
90-113
: LGTM!The refactored logic correctly handles both existing and new videos:
- Existing shared videos are moved to the organization root by clearing
folderId
- New videos are inserted only when
newVideoIds
is non-emptyThis avoids unnecessary database operations.
118-124
: LGTM!The success message correctly reports the total count with proper singular/plural forms and verb agreement.
apps/web/app/(org)/dashboard/spaces/[spaceId]/components/VideoCard.tsx (3)
55-64
: LGTM!Adding
role="button"
,tabIndex={0}
, and keyboard handling for Enter/Space properly implements accessibility for the clickable card.
36-52
: LGTM!The Rive integration correctly computes the artboard based on theme and folder color, providing dynamic visual feedback. The key at line 168 ensures the component re-renders when theme or color changes.
165-193
: LGTM!The conditional rendering logic correctly displays:
- Folder icon with name when
folderName
exists- Home icon with "Root" when in entity without folder
- Vinyl icon with "Caps" otherwise
This provides clear visual context for video location.
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: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/web/app/(org)/dashboard/caps/components/Folder.tsx (1)
115-119
: Ensure spaceId is never undefined
The fallbackactiveOrganization?.organization.id
can yieldundefined
, which doesn’t match the function’sspaceId: … | null
type. Update the call to:await moveVideoToFolder({ videoId: data.id, folderId: id, spaceId: spaceId ?? activeOrganization?.organization.id ?? null, });Or add a guard so
activeOrganization
is always defined before this call.apps/web/actions/folders/moveVideoToFolder.ts (1)
30-35
: FetchoriginalFolderId
from the appropriate table based onspaceId
- At apps/web/actions/folders/moveVideoToFolder.ts (lines 30–35), replace the single
videos
query with context-aware lookups:
- If
spaceId && !isAllSpacesEntry
, selectspaceVideos.folderId
- Else if
spaceId && isAllSpacesEntry
, selectsharedVideos.folderId
- Otherwise, select
videos.folderId
- This ensures
originalFolderId
isn’tnull
for space- or org-scoped videos so thatrevalidatePath
correctly covers the original folder.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
apps/web/actions/folders/moveVideoToFolder.ts
(3 hunks)apps/web/app/(org)/dashboard/caps/components/Folder.tsx
(2 hunks)apps/web/app/(org)/dashboard/folder/[id]/components/BreadcrumbItem.tsx
(4 hunks)apps/web/app/(org)/dashboard/folder/[id]/components/ClientMyCapsLink.tsx
(4 hunks)apps/web/app/(org)/dashboard/folder/[id]/page.tsx
(4 hunks)apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/page.tsx
(2 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/folder/[id]/components/ClientMyCapsLink.tsx
apps/web/app/(org)/dashboard/folder/[id]/components/BreadcrumbItem.tsx
apps/web/actions/folders/moveVideoToFolder.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/page.tsx
apps/web/app/(org)/dashboard/caps/components/Folder.tsx
apps/web/app/(org)/dashboard/folder/[id]/page.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/folder/[id]/components/ClientMyCapsLink.tsx
apps/web/app/(org)/dashboard/folder/[id]/components/BreadcrumbItem.tsx
apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/page.tsx
apps/web/app/(org)/dashboard/caps/components/Folder.tsx
apps/web/app/(org)/dashboard/folder/[id]/page.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/folder/[id]/components/ClientMyCapsLink.tsx
apps/web/app/(org)/dashboard/folder/[id]/components/BreadcrumbItem.tsx
apps/web/actions/folders/moveVideoToFolder.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/page.tsx
apps/web/app/(org)/dashboard/caps/components/Folder.tsx
apps/web/app/(org)/dashboard/folder/[id]/page.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/folder/[id]/components/ClientMyCapsLink.tsx
apps/web/app/(org)/dashboard/folder/[id]/components/BreadcrumbItem.tsx
apps/web/actions/folders/moveVideoToFolder.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/page.tsx
apps/web/app/(org)/dashboard/caps/components/Folder.tsx
apps/web/app/(org)/dashboard/folder/[id]/page.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/folder/[id]/components/ClientMyCapsLink.tsx
apps/web/app/(org)/dashboard/folder/[id]/components/BreadcrumbItem.tsx
apps/web/actions/folders/moveVideoToFolder.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/page.tsx
apps/web/app/(org)/dashboard/caps/components/Folder.tsx
apps/web/app/(org)/dashboard/folder/[id]/page.tsx
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
On the client, always use
useEffectQuery
oruseEffectMutation
from@/lib/EffectRuntime
; never callEffectRuntime.run*
directly in components.
Files:
apps/web/app/(org)/dashboard/folder/[id]/components/ClientMyCapsLink.tsx
apps/web/app/(org)/dashboard/folder/[id]/components/BreadcrumbItem.tsx
apps/web/actions/folders/moveVideoToFolder.ts
apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/page.tsx
apps/web/app/(org)/dashboard/caps/components/Folder.tsx
apps/web/app/(org)/dashboard/folder/[id]/page.tsx
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/moveVideoToFolder.ts
🧬 Code graph analysis (5)
apps/web/app/(org)/dashboard/folder/[id]/components/BreadcrumbItem.tsx (1)
apps/web/actions/folders/moveVideoToFolder.ts (1)
moveVideoToFolder
(14-125)
apps/web/actions/folders/moveVideoToFolder.ts (2)
packages/web-domain/src/Folder.ts (1)
Folder
(37-45)packages/database/schema.ts (3)
spaceVideos
(631-651)sharedVideos
(334-362)videos
(277-332)
apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/page.tsx (6)
packages/web-domain/src/Space.ts (2)
SpaceIdOrOrganisationId
(7-7)SpaceIdOrOrganisationId
(8-8)packages/web-domain/src/Folder.ts (3)
Folder
(37-45)FolderId
(11-11)FolderId
(12-12)apps/web/lib/folder.ts (1)
getVideosByFolderId
(157-276)apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/AddVideosButton.tsx (1)
AddVideosButton
(15-52)apps/web/app/(org)/dashboard/folder/[id]/components/ClientMyCapsLink.tsx (1)
ClientMyCapsLink
(15-148)apps/web/app/(org)/dashboard/folder/[id]/components/BreadcrumbItem.tsx (1)
BreadcrumbItem
(22-129)
apps/web/app/(org)/dashboard/caps/components/Folder.tsx (2)
packages/web-domain/src/Space.ts (2)
SpaceIdOrOrganisationId
(7-7)SpaceIdOrOrganisationId
(8-8)packages/web-domain/src/Folder.ts (3)
Folder
(37-45)FolderId
(11-11)FolderId
(12-12)
apps/web/app/(org)/dashboard/folder/[id]/page.tsx (4)
packages/web-domain/src/Folder.ts (3)
Folder
(37-45)FolderId
(11-11)FolderId
(12-12)apps/web/lib/folder.ts (3)
getChildFolders
(278-314)getFolderBreadcrumb
(37-62)getVideosByFolderId
(157-276)apps/web/app/(org)/dashboard/folder/[id]/components/ClientMyCapsLink.tsx (1)
ClientMyCapsLink
(15-148)apps/web/app/(org)/dashboard/folder/[id]/components/BreadcrumbItem.tsx (1)
BreadcrumbItem
(22-129)
⏰ 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: Analyze (rust)
🔇 Additional comments (6)
apps/web/app/(org)/dashboard/caps/components/Folder.tsx (1)
19-26
: No missingFolderDataType
consumers found
All inspected imports and object constructions include bothspaceId
andparentId
; the change to required fields is fully accounted for.apps/web/app/(org)/dashboard/folder/[id]/components/BreadcrumbItem.tsx (1)
3-3
: LGTM!The space context propagation is correctly implemented. The
spaceId
prop is properly typed asSpace.SpaceIdOrOrganisationId
and consistently passed through to themoveVideoToFolder
action.Also applies to: 18-18, 27-27, 64-68
apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/page.tsx (1)
23-26
: LGTM!The space-aware folder page implementation is correct:
- Proper async params handling with Promise wrapper
- Correct root variant branching based on
spaceOrOrg
for data fetching- Space context consistently propagated to all child components
- AddVideosButton properly wired with folder metadata
Also applies to: 47-53, 59-63, 67-67, 72-72
apps/web/app/(org)/dashboard/folder/[id]/components/ClientMyCapsLink.tsx (1)
4-4
: LGTM!The space context integration is correctly implemented:
- Proper type imports and signature updates
- Space-aware routing logic
- Correct propagation of
spaceId
tomoveVideoToFolder
Also applies to: 15-19, 97-97, 116-116
apps/web/app/(org)/dashboard/folder/[id]/page.tsx (1)
46-46
: Space context propagation looks correct (pending verification).The
spaceId
is correctly passed toClientMyCapsLink
,BreadcrumbItem
, andFolderCard
components. However, confirm thatspaceId
is actually available in this route's params before finalizing these changes (see previous comment).Also applies to: 53-53, 75-75
apps/web/actions/folders/moveVideoToFolder.ts (1)
5-11
: LGTM on the three-way update branching logic.The conditional logic correctly routes folder updates based on space context:
- Space-scoped videos →
spaceVideos
table- Organization-wide (all spaces) →
sharedVideos
table- User-only videos →
videos
tableThe
isAllSpacesEntry
helper clearly distinguishes between per-space and organization-wide contexts. Type imports and signature updates are correct.Also applies to: 21-21, 37-37, 56-84
apps/web/app/(org)/dashboard/folder/[id]/components/ClientMyCapsLink.tsx
Outdated
Show resolved
Hide resolved
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: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
apps/web/actions/folders/moveVideoToFolder.ts (2)
30-38
: Track original folder per scope before revalidationWe still read
originalFolderId
fromvideos.folderId
, but in both space and “All spaces” flows the folder lives inspaceVideos
/sharedVideos
. That keepsoriginalFolderId
atnull
, so the old folder page (and its parents) never revalidate, leaving stale listings. Please pull the original folder from the scoped table and revalidate the matching space route as well.- const [currentVideo] = await db() - .select({ folderId: videos.folderId, id: videos.id }) - .from(videos) - .where(eq(videos.id, videoId)); - - const originalFolderId = currentVideo?.folderId; - - const isAllSpacesEntry = spaceId === user.activeOrganizationId; + const [currentVideo] = await db() + .select({ folderId: videos.folderId, id: videos.id }) + .from(videos) + .where(eq(videos.id, videoId)); + + const isAllSpacesEntry = spaceId === user.activeOrganizationId; + + let originalFolderId: Folder.FolderId | null = currentVideo?.folderId ?? null; + + if (spaceId && !isAllSpacesEntry) { + const [spaceVideo] = await db() + .select({ folderId: spaceVideos.folderId }) + .from(spaceVideos) + .where( + and(eq(spaceVideos.videoId, videoId), eq(spaceVideos.spaceId, spaceId)), + ); + originalFolderId = spaceVideo?.folderId ?? null; + } else if (spaceId && isAllSpacesEntry) { + const [sharedVideo] = await db() + .select({ folderId: sharedVideos.folderId }) + .from(sharedVideos) + .where( + and( + eq(sharedVideos.videoId, videoId), + eq(sharedVideos.organizationId, user.activeOrganizationId), + ), + ); + originalFolderId = sharedVideo?.folderId ?? null; + } @@ - if (originalFolderId) { - revalidatePath(`/dashboard/folder/${originalFolderId}`); - } + if (originalFolderId) { + revalidatePath(`/dashboard/folder/${originalFolderId}`); + if (spaceId) { + revalidatePath(`/dashboard/spaces/${spaceId}/folder/${originalFolderId}`); + } + }Also applies to: 98-123
89-91
: Avoid revalidating/folder/null
when moving to rootWhen
folderId
isnull
(moving a video out to the root), this builds/dashboard/spaces/${spaceId}/folder/null
, so the real root view stays stale. Only revalidate the folder page whenfolderId
exists, and fall back to the space root otherwise.- if (spaceId) { - revalidatePath(`/dashboard/spaces/${spaceId}/folder/${folderId}`); - } + if (spaceId && folderId) { + revalidatePath(`/dashboard/spaces/${spaceId}/folder/${folderId}`); + } else if (spaceId) { + revalidatePath(`/dashboard/spaces/${spaceId}`); + }apps/web/app/(org)/dashboard/caps/components/Folder.tsx (1)
262-262
: Align spaceId fallback in desktop drag handler
In apps/web/app/(org)/dashboard/caps/components/Folder.tsx line 262, replaceawait moveVideoToFolder({ videoId: capData.id, folderId: id, spaceId });with
await moveVideoToFolder({ videoId: capData.id, folderId: id, spaceId: spaceId ?? activeOrganization?.organization.id, });
♻️ Duplicate comments (1)
apps/web/app/(org)/dashboard/folder/[id]/page.tsx (1)
21-25
: DropspaceId
from the plain folder route params
/dashboard/folder/[id]
never receives aspaceId
, so the new required field is alwaysundefined
at runtime. That value now flows intoBreadcrumbItem
(which builds space-aware links) and producesspaces/undefined/...
URLs. This was flagged earlier and still needs to be removed—please revert the param/type change and stop forwardingspaceId
here.Also applies to: 52-55
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
apps/web/actions/folders/moveVideoToFolder.ts
(3 hunks)apps/web/actions/spaces/remove-videos.ts
(2 hunks)apps/web/app/(org)/dashboard/caps/components/Folder.tsx
(2 hunks)apps/web/app/(org)/dashboard/folder/[id]/components/ClientMyCapsLink.tsx
(4 hunks)apps/web/app/(org)/dashboard/folder/[id]/page.tsx
(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/app/(org)/dashboard/folder/[id]/components/ClientMyCapsLink.tsx
🧰 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/spaces/remove-videos.ts
apps/web/actions/folders/moveVideoToFolder.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/spaces/remove-videos.ts
apps/web/app/(org)/dashboard/folder/[id]/page.tsx
apps/web/actions/folders/moveVideoToFolder.ts
apps/web/app/(org)/dashboard/caps/components/Folder.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/spaces/remove-videos.ts
apps/web/app/(org)/dashboard/folder/[id]/page.tsx
apps/web/actions/folders/moveVideoToFolder.ts
apps/web/app/(org)/dashboard/caps/components/Folder.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/spaces/remove-videos.ts
apps/web/app/(org)/dashboard/folder/[id]/page.tsx
apps/web/actions/folders/moveVideoToFolder.ts
apps/web/app/(org)/dashboard/caps/components/Folder.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/spaces/remove-videos.ts
apps/web/app/(org)/dashboard/folder/[id]/page.tsx
apps/web/actions/folders/moveVideoToFolder.ts
apps/web/app/(org)/dashboard/caps/components/Folder.tsx
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
On the client, always use
useEffectQuery
oruseEffectMutation
from@/lib/EffectRuntime
; never callEffectRuntime.run*
directly in components.
Files:
apps/web/actions/spaces/remove-videos.ts
apps/web/app/(org)/dashboard/folder/[id]/page.tsx
apps/web/actions/folders/moveVideoToFolder.ts
apps/web/app/(org)/dashboard/caps/components/Folder.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/folder/[id]/page.tsx
apps/web/app/(org)/dashboard/caps/components/Folder.tsx
🧬 Code graph analysis (4)
apps/web/actions/spaces/remove-videos.ts (2)
packages/database/index.ts (1)
db
(29-34)packages/database/schema.ts (4)
sharedVideos
(334-362)folders
(249-275)videos
(277-332)spaceVideos
(631-651)
apps/web/app/(org)/dashboard/folder/[id]/page.tsx (2)
packages/web-domain/src/Folder.ts (3)
Folder
(37-45)FolderId
(11-11)FolderId
(12-12)apps/web/lib/folder.ts (2)
getChildFolders
(278-314)getVideosByFolderId
(157-276)
apps/web/actions/folders/moveVideoToFolder.ts (3)
packages/web-domain/src/Folder.ts (1)
Folder
(37-45)packages/database/index.ts (1)
db
(29-34)packages/database/schema.ts (3)
spaceVideos
(631-651)sharedVideos
(334-362)videos
(277-332)
apps/web/app/(org)/dashboard/caps/components/Folder.tsx (2)
packages/web-domain/src/Space.ts (2)
SpaceIdOrOrganisationId
(7-7)SpaceIdOrOrganisationId
(8-8)packages/web-domain/src/Folder.ts (3)
Folder
(37-45)FolderId
(11-11)FolderId
(12-12)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
- GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
- GitHub Check: Analyze (rust)
🔇 Additional comments (1)
apps/web/app/(org)/dashboard/caps/components/Folder.tsx (1)
24-25
: Type definitions correctly align with domain model.The changes to
spaceId
andparentId
types are appropriate:
spaceId
is now optional and nullable with a more specific branded type (Space.SpaceIdOrOrganisationId
), matching the domain'sSchema.OptionFromNullOr
patternparentId
uses the brandedFolder.FolderId
type and is required but nullable, providing clearer semantics than optionalThese changes resolve the previous review comment about type inconsistency.
const folderRows = await db() | ||
.select({ id: folders.id }) | ||
.from(folders) | ||
.where( | ||
and( | ||
inArray(videos.id, validVideoIds), | ||
inArray(videos.folderId, folderIds), | ||
isNull(folders.spaceId), | ||
eq(folders.organizationId, user.activeOrganizationId), | ||
), | ||
); | ||
|
||
const folderIds = folderRows.map((f) => f.id); | ||
|
||
if (folderIds.length > 0) { | ||
await db() | ||
.update(videos) | ||
.set({ folderId: null }) | ||
.where( | ||
and( | ||
inArray(videos.id, validVideoIds), | ||
inArray(videos.folderId, folderIds), | ||
), | ||
); |
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.
Fix folder scope for space removals
This branch is supposed to clear folder assignments for videos in folders belonging to the target space, but the query still filters folders.spaceId IS NULL
. That only returns org-level folders, so space folders never get cleared and removed videos stay linked to the space’s folders. Replace the filter with eq(folders.spaceId, spaceId)
to target the correct folders before updating the videos table.
🤖 Prompt for AI Agents
In apps/web/actions/spaces/remove-videos.ts around lines 91 to 112, the folder
query incorrectly filters for folders.spaceId IS NULL (org-level folders)
instead of the target space’s folders; change the where clause to filter
eq(folders.spaceId, spaceId) so folderRows contains folders for the given space,
then proceed to map ids and update videos.folderId to null for videos whose
folderId is in that list and whose id is in validVideoIds.
getVideosByFolderId(params.id, { | ||
variant: "user", | ||
}), |
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.
Fetch organization-scoped videos instead of user variant
This page lives under the org dashboard, so the folder contents now live in sharedVideos
. Leaving the call on the "user"
variant means any org/“All spaces” move just updated sharedVideos
but the UI keeps querying videos.folderId
, so the folder appears empty/mis-synced. Please switch to the org variant and pass the active organization id.
- getVideosByFolderId(params.id, {
- variant: "user",
- }),
+ getVideosByFolderId(params.id, {
+ variant: "org",
+ organizationId: user.activeOrganizationId,
+ }),
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
getVideosByFolderId(params.id, { | |
variant: "user", | |
}), | |
getVideosByFolderId(params.id, { | |
variant: "org", | |
organizationId: user.activeOrganizationId, | |
}), |
🤖 Prompt for AI Agents
In apps/web/app/(org)/dashboard/folder/[id]/page.tsx around lines 33 to 35, the
call to getVideosByFolderId is using variant: "user" which queries user-scoped
videos; update it to variant: "org" and pass the active organization id (e.g.,
organization.id or activeOrgId from context/props) so the function queries
sharedVideos for the org scope and returns the folder contents for the current
organization.
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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/actions/folders/moveVideoToFolder.ts (1)
89-91
: Guard revalidation path for organization entriesWrap the
revalidatePath
call so that whenisAllSpacesEntry
is true you revalidate/dashboard/spaces/${spaceId}
(as in add/remove-videos) instead of/dashboard/spaces/${spaceId}/folder/${folderId}
; for actual spaces, keep the current folder-specific path.
🧹 Nitpick comments (1)
apps/web/actions/folders/moveVideoToFolder.ts (1)
37-37
: Document theisAllSpacesEntry
business logic.The flag
isAllSpacesEntry
checks ifspaceId === user.activeOrganizationId
, treating the organization ID as a special "all spaces" scope. While this aligns with theSpaceIdOrOrganisationId
union type and the PR's org/space-aware design, the implicit overloading of the organization ID within a space-focused parameter could be confusing.Consider adding a brief inline explanation of why an organization ID in the
spaceId
parameter indicates "all spaces" or organization-wide context, or refactor to use a more explicit parameter (e.g.,scope: 'space' | 'organization'
).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
apps/web/actions/folders/moveVideoToFolder.ts
(3 hunks)apps/web/actions/spaces/remove-videos.ts
(2 hunks)apps/web/app/(org)/dashboard/caps/components/Folder.tsx
(2 hunks)apps/web/app/(org)/dashboard/folder/[id]/components/BreadcrumbItem.tsx
(4 hunks)apps/web/app/(org)/dashboard/folder/[id]/components/ClientMyCapsLink.tsx
(4 hunks)apps/web/app/(org)/dashboard/folder/[id]/page.tsx
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- apps/web/app/(org)/dashboard/folder/[id]/components/BreadcrumbItem.tsx
- apps/web/app/(org)/dashboard/folder/[id]/components/ClientMyCapsLink.tsx
- apps/web/actions/spaces/remove-videos.ts
🧰 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/caps/components/Folder.tsx
apps/web/actions/folders/moveVideoToFolder.ts
apps/web/app/(org)/dashboard/folder/[id]/page.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/Folder.tsx
apps/web/app/(org)/dashboard/folder/[id]/page.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/caps/components/Folder.tsx
apps/web/actions/folders/moveVideoToFolder.ts
apps/web/app/(org)/dashboard/folder/[id]/page.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/caps/components/Folder.tsx
apps/web/actions/folders/moveVideoToFolder.ts
apps/web/app/(org)/dashboard/folder/[id]/page.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/caps/components/Folder.tsx
apps/web/actions/folders/moveVideoToFolder.ts
apps/web/app/(org)/dashboard/folder/[id]/page.tsx
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
On the client, always use
useEffectQuery
oruseEffectMutation
from@/lib/EffectRuntime
; never callEffectRuntime.run*
directly in components.
Files:
apps/web/app/(org)/dashboard/caps/components/Folder.tsx
apps/web/actions/folders/moveVideoToFolder.ts
apps/web/app/(org)/dashboard/folder/[id]/page.tsx
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/moveVideoToFolder.ts
🧬 Code graph analysis (3)
apps/web/app/(org)/dashboard/caps/components/Folder.tsx (2)
packages/web-domain/src/Space.ts (2)
SpaceIdOrOrganisationId
(7-7)SpaceIdOrOrganisationId
(8-8)packages/web-domain/src/Folder.ts (3)
Folder
(37-45)FolderId
(11-11)FolderId
(12-12)
apps/web/actions/folders/moveVideoToFolder.ts (2)
packages/web-domain/src/Folder.ts (1)
Folder
(37-45)packages/database/schema.ts (3)
spaceVideos
(631-651)sharedVideos
(334-362)videos
(277-332)
apps/web/app/(org)/dashboard/folder/[id]/page.tsx (1)
apps/web/lib/folder.ts (1)
getVideosByFolderId
(157-276)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
- GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
- GitHub Check: Analyze (rust)
🔇 Additional comments (4)
apps/web/app/(org)/dashboard/folder/[id]/page.tsx (2)
29-31
: LGTM! Correct variant usage for user-scoped folder.The call to
getVideosByFolderId
now correctly passes the{ variant: "user" }
option, matching the updated API signature and properly indicating user-scoped folder access.
1-89
: AI summary inconsistent with actual code.The AI-generated summary claims this file:
- "accepts and uses spaceId of type Space.SpaceIdOrOrganisationId"
- "renders AddVideosButton and passes spaceId to it"
- "Breadcrumb and client link components are updated to receive and propagate spaceId"
However, the actual code shows:
- Line 21: params only contains
id: Folder.FolderId
(no spaceId)- No AddVideosButton import or usage anywhere in the file
- No spaceId props passed to ClientMyCapsLink, BreadcrumbItem, or FolderCard
The only actual change is lines 29-31 where
getVideosByFolderId
now receives the{ variant: "user" }
option.apps/web/app/(org)/dashboard/caps/components/Folder.tsx (1)
24-25
: Type alignment looks good; verify parentId requirement.The
spaceId
type change toSpace.SpaceIdOrOrganisationId | null
(optional) correctly addresses the previous review concern and aligns with its usage throughout the component.However, note that
parentId
is now typed as required (Folder.FolderId | null
) while the domain schema defines it as optional (Schema.OptionFromNullOr(FolderId)
). This means the component type requiresparentId
to always be provided (though it can benull
), whereas the domain allows it to be omitted entirely. Verify this is intentional—if folders data can sometimes lack aparentId
property, the component type should beparentId?: Folder.FolderId | null
to match.apps/web/actions/folders/moveVideoToFolder.ts (1)
62-64
: LGTM: Conditional table updates correctly implement space/org-aware logic.The three-branch conditional correctly routes updates to the appropriate table:
spaceVideos
for space-specific contextssharedVideos
for organization-wide contextsvideos
for user-scoped contextsThe where clauses properly scope updates using both
videoId
and the relevant ID (spaceId
ororganizationId
), preventing unintended cross-scope modifications.Also applies to: 72-75, 79-83
if (spaceId && !isAllSpacesEntry) { | ||
await db() | ||
.update(spaceVideos) | ||
.set({ | ||
folderId: folderId === null ? null : folderId, | ||
}) | ||
.where(eq(spaceVideos.videoId, videoId)); | ||
.where( | ||
and(eq(spaceVideos.videoId, videoId), eq(spaceVideos.spaceId, spaceId)), | ||
); | ||
} else if (spaceId && isAllSpacesEntry) { | ||
await db() | ||
.update(sharedVideos) | ||
.set({ | ||
folderId: folderId === null ? null : folderId, | ||
}) | ||
.where( | ||
and( | ||
eq(sharedVideos.videoId, videoId), | ||
eq(sharedVideos.organizationId, user.activeOrganizationId), | ||
), | ||
); | ||
} else { | ||
await db() | ||
.update(videos) | ||
.set({ | ||
folderId: folderId === null ? null : folderId, | ||
}) | ||
.where(eq(videos.id, videoId)); | ||
} |
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.
🧩 Analysis chain
Verify spaceId
belongs to the user's organization.
The function branches between updating spaceVideos
, sharedVideos
, or videos
based on the spaceId
parameter, but it does not verify that the provided spaceId
actually belongs to the user's active organization. If a caller passes a spaceId
from a different organization, the function could update records in an unintended scope.
Add a verification step after line 25 to ensure the spaceId
is valid:
🏁 Script executed:
#!/bin/bash
# Check if there's existing validation for spaceId in related actions
rg -nP --type=ts -C3 'spaceId.*activeOrganizationId' apps/web/actions/
Length of output: 1055
Add validation for spaceId ownership
Before branching (after the isAllSpacesEntry
assignment), if spaceId
is non-null and not equal to user.activeOrganizationId
, query the spaces
table for { id: spaceId, organizationId: user.activeOrganizationId }
and throw NotFoundError
if no match.
🤖 Prompt for AI Agents
In apps/web/actions/folders/moveVideoToFolder.ts around lines 56 to 84, after
the isAllSpacesEntry assignment add validation that if spaceId is non-null and
spaceId !== user.activeOrganizationId you must query the spaces table for a row
matching { id: spaceId, organizationId: user.activeOrganizationId } and throw
NotFoundError if no match is found; place this check before the existing
branching that updates spaceVideos/sharedVideos/videos so
unauthorized/mismatched spaceIds are rejected early.
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/actions/spaces/add-videos.ts (1)
39-116
: Consider extracting shared logic to reduce duplication.The organization-wide (lines 39-76) and space-specific (lines 77-116) branches contain nearly identical logic: querying existing entries, nulling
folderId
, and inserting new entries. This duplication increases maintenance burden.Consider refactoring to a shared helper function:
async function addVideosToContext( table: typeof sharedVideos | typeof spaceVideos, contextId: string, contextIdField: 'organizationId' | 'spaceId', validVideoIds: Video.VideoId[], userId: User.UserId ) { const existingVideos = await db() .select({ videoId: table.videoId }) .from(table) .where( and( eq(table[contextIdField], contextId), inArray(table.videoId, validVideoIds) ) ); const existingVideoIds = existingVideos.map(v => v.videoId); const newVideoIds = validVideoIds.filter(id => !existingVideoIds.includes(id)); if (existingVideoIds.length > 0) { await db() .update(table) .set({ folderId: null }) .where( and( eq(table[contextIdField], contextId), inArray(table.videoId, existingVideoIds) ) ); } if (newVideoIds.length > 0) { const entries = newVideoIds.map(videoId => ({ id: nanoId(), videoId, [contextIdField]: contextId, ...(table === sharedVideos ? { sharedByUserId: userId } : { addedById: userId }) })); await db().insert(table).values(entries); } }Then use it in both branches:
if (isAllSpacesEntry) { await addVideosToContext( sharedVideos, spaceId, 'organizationId', validVideoIds, user.id ); } else { await addVideosToContext( spaceVideos, spaceId, 'spaceId', validVideoIds, user.id ); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/web/actions/spaces/add-videos.ts
(3 hunks)apps/web/actions/spaces/remove-videos.ts
(3 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
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/spaces/add-videos.ts
apps/web/actions/spaces/remove-videos.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/spaces/add-videos.ts
apps/web/actions/spaces/remove-videos.ts
**/*.{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/spaces/add-videos.ts
apps/web/actions/spaces/remove-videos.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/actions/spaces/add-videos.ts
apps/web/actions/spaces/remove-videos.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/actions/spaces/add-videos.ts
apps/web/actions/spaces/remove-videos.ts
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
On the client, always use
useEffectQuery
oruseEffectMutation
from@/lib/EffectRuntime
; never callEffectRuntime.run*
directly in components.
Files:
apps/web/actions/spaces/add-videos.ts
apps/web/actions/spaces/remove-videos.ts
🧬 Code graph analysis (2)
apps/web/actions/spaces/add-videos.ts (3)
packages/database/index.ts (1)
db
(29-34)packages/database/schema.ts (2)
sharedVideos
(334-362)spaceVideos
(631-651)packages/database/helpers.ts (1)
nanoId
(6-9)
apps/web/actions/spaces/remove-videos.ts (2)
packages/database/index.ts (1)
db
(29-34)packages/database/schema.ts (2)
sharedVideos
(334-362)spaceVideos
(631-651)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
- GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
- GitHub Check: Analyze (rust)
🔇 Additional comments (6)
apps/web/actions/spaces/add-videos.ts (3)
125-126
: LGTM!Proper error logging and graceful error handling with informative messages.
55-65
: Verify intended behavior of clearingfolderId
when adding existing videos
The code nullsfolderId
for existing videos, moving them to the root. Confirm if this is the desired behavior or if existing videos should be skipped or preserved in their current folders.
Also applies to apps/web/actions/organizations/add-videos.ts (lines 94–104).
26-26
: Ensure organization-wide entry detection is unambiguous.Relying on
user.activeOrganizationId === spaceId
to detect the “all spaces” context risks misclassification if a space ID ever equals an organization ID; confirm that these IDs are generated in disjoint namespaces or introduce an explicit mode flag instead.apps/web/actions/spaces/remove-videos.ts (3)
42-63
: LGTM! Clean deletion approach.The logic correctly branches between organization-level and space-level removals. By deleting rows from
sharedVideos
orspaceVideos
, you remove both the space/org membership and any folder associations in a single operation, which is cleaner than the previous approach of nullingfolderId
separately.
72-79
: LGTM! Proper error handling pattern.The change to use a generic catch with an
instanceof Error
check follows TypeScript best practices for handling unknown error types.
15-40
: Verify space membership before removal.
The function ensures the user owns the videos but does not check they belong to the target space when deleting fromspaceVideos
. Confirm whether space-membership should be enforced.
Summary by CodeRabbit
New Features
Improvements
Behavior Changes