---
phase: 10-ux-interaction
plan: 10
type: execute
wave: 3
depends_on: [10-04, 10-09, 10-05, 10-06]
files_modified:
- frontend/src/components/layout/OsDragOverlay.vue
- frontend/src/App.vue
- frontend/src/views/FileManagerView.vue
- frontend/src/components/layout/__tests__/OsDragOverlay.test.js
autonomous: true
requirements: [UX-09]
must_haves:
truths:
- "Dragging files from the OS over the browser window shows the full-screen drop overlay"
- "An in-app element drag (no Files type in dataTransfer.types) does NOT show the overlay"
- "Releasing files over the window calls FileManagerView.handleOsDrop(files) which uploads them via the existing flow"
- "The overlay disappears on drop and on the depth counter reaching 0 via dragleave"
- "Overlay z-index (z-[9998]) is below ToastContainer (z-[9999]) so toasts remain visible during drop"
artifacts:
- path: "frontend/src/components/layout/OsDragOverlay.vue"
provides: "Full-screen OS file drag overlay with depth-counter pattern"
- path: "frontend/src/App.vue"
provides: "Updated to mount OsDragOverlay and wire @files-dropped to active route view"
key_links:
- from: "OsDragOverlay.vue"
to: "window dragenter/dragleave/dragover/drop events"
via: "mounted() addEventListener + beforeUnmount() removeEventListener"
pattern: "window\\.addEventListener\\('drag"
- from: "App.vue"
to: "FileManagerView.handleOsDrop"
via: "@files-dropped handler -> routeViewRef.handleOsDrop"
pattern: "routeViewRef\\.value\\?\\.handleOsDrop"
---
Implement UX-09 (OS file drag onto browser window -> full-screen overlay -> upload). The overlay uses the depth-counter pattern from D-16 (Pitfall 3) to prevent flicker as the cursor moves between child elements, and only activates when the dragged item has `dataTransfer.types.includes('Files')` (which is true only for OS-origin drags).
Output:
- New component `frontend/src/components/layout/OsDragOverlay.vue` (Options API; Teleport to body; window-level event listeners; emits `files-dropped`)
- App.vue mounts `` and routes to the active view via `routeViewRef.value?.handleOsDrop?.(files)`
- FileManagerView exposes `handleOsDrop(files)` via `defineExpose` that calls the existing `onFilesSelected({files, autoClassify: true})`
- OsDragOverlay test stubs promoted to real tests
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
@CLAUDE.md
@.planning/phases/10-ux-interaction/10-CONTEXT.md
@.planning/phases/10-ux-interaction/10-RESEARCH.md
@.planning/phases/10-ux-interaction/10-PATTERNS.md
@frontend/src/App.vue
@frontend/src/views/FileManagerView.vue
@frontend/src/components/documents/DocumentPreviewModal.vue
@frontend/src/components/ui/AppIcon.vue
@frontend/src/components/upload/DropZone.vue
OsDragOverlay.vue (Options API) - from PATTERNS.md §"OsDragOverlay.vue":
Data:
- `dragDepth: 0` (counter)
- `showOverlay: false`
Methods:
- `onDragEnter(e)` -> ignore unless `e.dataTransfer?.types.includes('Files')`; increment dragDepth; set showOverlay=true
- `onDragLeave()` -> dragDepth = max(0, dragDepth - 1); if dragDepth === 0 set showOverlay=false
- `onDragOver(e)` -> e.preventDefault() (required to allow drop event)
- `onDrop(e)` -> e.preventDefault(); reset dragDepth=0, showOverlay=false; emit `files-dropped` with `Array.from(e.dataTransfer.files)` (when files.length > 0)
mounted(): addEventListener for dragenter/dragleave/dragover/drop on `window`.
beforeUnmount(): remove all four listeners.
Template (Teleport to body):
```
```
Plus a `