Skip to content

Commit 7c2d2a9

Browse files
committed
fix(files): keep the view in a spring-opened folder when an OS upload lands there
1 parent 5611bfe commit 7c2d2a9

3 files changed

Lines changed: 20 additions & 0 deletions

File tree

apps/sim/app/workspace/[workspaceId]/files/files.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,6 +1147,12 @@ export function Files() {
11471147
const handleDrop = async (e: React.DragEvent) => {
11481148
if (!hasExternalFiles(e.dataTransfer)) return
11491149
e.preventDefault()
1150+
/**
1151+
* The upload lands in the folder currently open, so the view must stay there. Without this
1152+
* the window-level teardown treats the drag as unconsumed and returns to the folder it
1153+
* began in — pulling the user out of the folder they just spring-opened to receive it.
1154+
*/
1155+
springNav.markDropHandled()
11501156
dragCounterRef.current = 0
11511157
setIsDraggingOver(false)
11521158
const dropped = Array.from(e.dataTransfer.files)

apps/sim/lib/knowledge/application/bulk.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,13 @@ export const bulkMoveKnowledgeItems = defineAuthorizedKnowledgeUseCase({
200200
* selected folders plus their descendants, so this rejects both "into itself" and "into its
201201
* own child" before anything is written. Without it the resources move, the folders then fail
202202
* their cycle check, and the caller is left with a half-applied selection.
203+
*
204+
* This is a fast-fail optimization, not the enforcement point. It reads a snapshot taken
205+
* outside the folder mutation lock, so a concurrent reparent can invalidate it between the
206+
* check and the write. The invariant itself is enforced where it must be — `updateFolder`
207+
* re-checks `wouldCreateFolderCycle` inside `acquireFolderMutationLock`, so a cycle is never
208+
* created. Losing that race costs a reported per-folder `failed` alongside resources that
209+
* did move, which is the batch's documented `sequential_best_effort` outcome, not corruption.
203210
*/
204211
if (input.targetFolderId !== null && plan.covered.has(input.targetFolderId)) {
205212
throw new OrchestrationError(

apps/sim/lib/table/application/bulk.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,13 @@ export const bulkMoveTables = defineAuthorizedTableUseCase({
227227
* selected folders plus their descendants, so this rejects both "into itself" and "into its
228228
* own child" before anything is written. Without it the tables move, the folders then fail
229229
* their cycle check, and the caller is left with a half-applied selection.
230+
*
231+
* This is a fast-fail optimization, not the enforcement point. It reads a snapshot taken
232+
* outside the folder mutation lock, so a concurrent reparent can invalidate it between the
233+
* check and the write. The invariant itself is enforced where it must be — `updateFolder`
234+
* re-checks `wouldCreateFolderCycle` inside `acquireFolderMutationLock`, so a cycle is never
235+
* created. Losing that race costs a reported per-folder `failed` alongside resources that
236+
* did move, which is the batch's documented `sequential_best_effort` outcome, not corruption.
230237
*/
231238
if (input.targetFolderId !== null && plan.covered.has(input.targetFolderId)) {
232239
throw new OrchestrationError(

0 commit comments

Comments
 (0)