EditorInterface root div has select-none (user-select:none) applied globally
to prevent text selection during editing. Chrome/Safari refuse to start HTML5
drag-and-drop on elements that inherit user-select:none, which is why no
ghost image appeared, cursor never changed, and no dragstart events fired.
Fix: add select-text (user-select:text) to both draggable divs in
MediaThumbnail (list view and grid view). This overrides the inherited none
specifically on the elements that need to be dragged, without changing the
global UX behavior of the editor.
With asChild, Radix merges its pointer event handlers directly onto the
draggable div. This interferes with browser drag gesture initiation,
resulting in no ghost image and no drag events firing.
Fix: remove asChild so ContextMenuTrigger renders its own span (with
display:contents to preserve layout). Radix handlers now live on the
ancestor span, not the draggable div. Right-click still bubbles up to
trigger the context menu correctly.
Also add draggable={false} to <img> elements inside draggable divs
to prevent browser native image drag from competing with the parent.
- Import endDrag from useUIStore
- Add handleItemDragEnd callback that calls endDrag()
- Add onDragEnd? prop to MediaThumbnail interface
- Wire onDragEnd={onDragEnd} to both draggable divs (list & grid views)
- Pass onDragEnd={handleItemDragEnd} when rendering MediaThumbnail
- Without this, isDragging was permanently stuck at true after every drag
- Import ActionResult type from @openreel/core
- onDropMedia prop type now returns Promise<ActionResult> | void
- handleDrop now awaits onDropMedia so failures are visible
- Replace silent catch with console.error + toast.error on failure
- Add allTracks, playheadPosition, snapSettings to handleDrop useCallback deps
to fix stale closure bug (calculateSnap was using stale snap/track state)
- handleDropMedia now returns the ActionResult from addClip/addClipToNewTrack
- The tracksRef onDrop handler now awaits handleDropMedia so errors aren't silently lost
- Replaces the swallowed catch block with a toast.error + console.error on failure
- This makes clip-add failures visible instead of silently doing nothing
Clicking Edit on the preview modal worked, but the user only saw an empty editor for ~25s while the recovery + format-chooser cycle ran and the bridge waited for a stable project. Looked broken. Now: a centered top banner appears the moment the bridge detects ?asset=, reads Loading clip from Z-AMPP MAM, switches to Clip ready in media bin on success, or surfaces the failure. Project-stability gate tightened from 1500ms to 600ms so the import lands sooner.
Three problems were blocking the round-trip. Each fixed.
* MediaBridge.importFromURL went through the file-import service but not the Zustand store, so the media bin stayed empty. mam-bridge now calls window.__projectStore.getState().importMedia(file) which is what the actual UI uses. project-store.ts exposes useProjectStore on window for that hook.
* rustfs serves the proxy with content-type application/octet-stream; the editor rejects with DECODE_ERROR on that mime. Bridge now forces video/mp4 (or audio/wav, video/webm, etc.) based on the asset filename.
* The Recover Your Work modal and the Welcome tour blocked editor initialization. Bridge now auto-clicks Start Fresh and Skip Tour (alongside the format chooser), and waits 1.5s of project-id stability before calling importMedia so it does not get clobbered by the project-replacement cycle. One-shot guard prevents duplicate imports.
Vendored Augani/openreel-video (MIT) into services/editor and wired it to the MAM. Editor runs as its own container on port 47435. Library assets pull in via ?asset=<uuid>; render exports route back via POST /api/v1/upload/simple. Sidebar Editor link on every page; Edit button on every preview modal. See services/editor/INTEGRATION.md for the patch map.