Skip to content

feat(studio): drag-drop assetfile/folder and asset import anywhere in the studio#155

Merged
miguel-heygen merged 6 commits intomainfrom
feat/studio-asset-import
Mar 31, 2026
Merged

feat(studio): drag-drop assetfile/folder and asset import anywhere in the studio#155
miguel-heygen merged 6 commits intomainfrom
feat/studio-asset-import

Conversation

@miguel-heygen
Copy link
Copy Markdown
Collaborator

@miguel-heygen miguel-heygen commented Mar 31, 2026

Summary

  • Add /api/projects/:id/upload endpoint for multipart file uploads with automatic dedup naming
  • Wire onImportFiles from Assets tab "Import media" button through to the upload API
  • Add global drag-drop overlay — drop media files anywhere in the studio, not just the Assets panel
  • Files that already exist get (2), (3) suffixes instead of overwriting
  • Support uploading into subdirectories via ?dir= param — dropping on a folder imports there
  • Make folders draggable in the file tree + support drop-to-root
  • Add bodyLimit middleware for early rejection of oversized payloads
  • Surface skipped/failed uploads via toast notification instead of console-only

Addresses feedback: "Wish I could upload/drag-drop assets directly in the Studio (music, images, video) like a CapCut media panel"

Test plan

  • Open studio, drag an image/video/audio file onto any part of the UI
  • Verify the drop overlay appears with "Drop files to import" message
  • Drop the file — verify it appears in the Assets tab and file tree
  • Drop a file with the same name — verify it gets a (2) suffix
  • Click "Import media" button in Assets tab — verify file picker works
  • Import multiple files at once via drag-drop
  • Drop a file onto a nested folder in the file tree — verify it lands in that folder
  • Drag a folder in the file tree and drop it on another folder or root — verify it moves
  • Drop a file >500MB — verify toast notification appears
  • Verify drag overlay doesn't get stuck when dragging over nested UI elements

@miguel-heygen miguel-heygen force-pushed the feat/studio-asset-import branch 3 times, most recently from 444fd65 to 719fa24 Compare March 31, 2026 17:06
@miguel-heygen miguel-heygen marked this pull request as ready for review March 31, 2026 17:58
@miguel-heygen miguel-heygen changed the base branch from feat/studio-file-management to graphite-base/155 March 31, 2026 18:03
miguel-heygen and others added 3 commits March 31, 2026 18:03
Add /api/projects/:id/upload endpoint for multipart file uploads with
automatic dedup naming. Wire onImportFiles callback from Assets tab
through LeftSidebar to App. Add global drag-drop overlay so media files
can be dropped anywhere in the studio — not just the Assets panel.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The AssetsTab drop handler and the global drop handler both fired,
causing two uploads. Now the global handler checks e.defaultPrevented
to skip if a child already handled the drop.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
External files dropped onto the file tree or any folder are imported
via the upload API, same as the Assets tab and global drop zone.
Internal drags (file-to-folder moves) still work as before.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@miguel-heygen miguel-heygen force-pushed the feat/studio-asset-import branch from 719fa24 to 6d4d6d3 Compare March 31, 2026 18:03
@graphite-app graphite-app Bot changed the base branch from graphite-base/155 to main March 31, 2026 18:04
- [Security] Add 500MB per-file size limit on uploads to prevent OOM
- [Security] Strip path separators from uploaded filenames
- [Bug] Fix dotfile collision naming (.gitignore → correct extension split)
- [Bug] Only show global drop overlay for external file drags, not tree reorders
- [UX] Log upload errors and report skipped files to console
- [Safety] Cap collision counter at 10000 iterations

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@miguel-heygen miguel-heygen force-pushed the feat/studio-asset-import branch from 6d4d6d3 to 0c0adba Compare March 31, 2026 18:04
Copy link
Copy Markdown
Collaborator

@jrusso1020 jrusso1020 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice feature — clean scope, good security hygiene with isSafePath and filename sanitization. A few things to address:

Must fix:

  • ensureDir(finalPath) is called with a file path — this will create a directory named after the file and then writeFileSync will throw EISDIR. Likely breaks uploads at runtime.

Should fix:

  • Drag overlay gets stuck due to fragile onDragLeave guard — use a drag enter counter instead.
  • No user-visible feedback for skipped/failed uploads (only console.warn).

Worth considering:

  • No body size limit before formData() parses the entire payload into memory.
  • Dedup loop silently falls through at n=10000.
  • Files always land in project root regardless of current directory context.

Comment thread packages/core/src/studio-api/routes/files.ts Outdated
Comment thread packages/core/src/studio-api/routes/files.ts Outdated
Comment thread packages/core/src/studio-api/routes/files.ts Outdated
Comment thread packages/studio/src/App.tsx Outdated
Comment thread packages/studio/src/App.tsx
Comment thread packages/core/src/studio-api/routes/files.ts Outdated
- Remove redundant ensureDir call, add bodyLimit middleware for early rejection
- Skip file on dedup suffix exhaustion instead of silent fallthrough
- Replace fragile currentTarget===target drag guard with counter ref pattern
- Surface skipped/failed uploads via toast instead of console-only
- Support ?dir= param for uploading into subdirectories

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add draggable + onDragStart to TreeFolder so folders can be moved
- Root drop zone now handles internal moves (files/folders to root)
- Visual drag-over highlight on root area when dragging items over it

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@miguel-heygen miguel-heygen changed the title feat(studio): drag-drop asset import anywhere in the studio feat(studio): drag-drop assetfile/folder and asset import anywhere in the studio Mar 31, 2026
Copy link
Copy Markdown
Collaborator

@jrusso1020 jrusso1020 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All original feedback addressed thoroughly — nice work.

  • ensureDir bug → removed, subdirectory support added via ?dir= param ✓
  • Drag overlay → counter pattern ✓
  • Dedup loop → skips at boundary ✓
  • Body size limit → bodyLimit middleware ✓
  • User feedback → toast notifications ✓
  • Root-only uploads → folder-aware drops ✓

Two non-blocking nits left (possible missing join import, toast timer cleanup). LGTM.

writeFileSync(finalPath, buffer);
uploaded.push(subDir ? join(subDir, finalName) : finalName);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: join is used here but I don't see it added to the node:path import in the diff. Can you verify it's imported? TypeScript should catch this at build time, but worth double-checking.

Comment on lines +374 to +377
const showUploadToast = useCallback((msg: string) => {
setUploadToast(msg);
setTimeout(() => setUploadToast(null), 4000);
}, []);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very minor: setTimeout without cleanup — if the component unmounts before the 4s fires, React will warn about setting state on an unmounted component. Not a real issue for the studio shell (it stays mounted), but if you want to be tidy:

const toastTimerRef = useRef<ReturnType<typeof setTimeout>>();

const showUploadToast = useCallback((msg: string) => {
  clearTimeout(toastTimerRef.current);
  setUploadToast(msg);
  toastTimerRef.current = setTimeout(() => setUploadToast(null), 4000);
}, []);

This also gives you the bonus of resetting the timer if a second toast fires before the first clears. Non-blocking.

@miguel-heygen miguel-heygen merged commit 5f3488e into main Mar 31, 2026
22 checks passed
@miguel-heygen miguel-heygen deleted the feat/studio-asset-import branch April 6, 2026 23:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants