Files
kite/.planning/milestones/v0.2-phases/10-ux-interaction/10-10-PLAN.md
T
curo1305andClaude Sonnet 4.6 123ae5b29b chore: archive v0.2 phase directories to milestones/v0.2-phases/
Moves phases 08–11 execution artifacts from .planning/phases/ to
.planning/milestones/v0.2-phases/ to keep .planning/phases/ clean
for the next milestone.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 14:34:52 +02:00

13 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
10-ux-interaction 10 execute 3
10-04
10-09
10-05
10-06
frontend/src/components/layout/OsDragOverlay.vue
frontend/src/App.vue
frontend/src/views/FileManagerView.vue
frontend/src/components/layout/__tests__/OsDragOverlay.test.js
true
UX-09
truths artifacts key_links
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
path provides
frontend/src/components/layout/OsDragOverlay.vue Full-screen OS file drag overlay with depth-counter pattern
path provides
frontend/src/App.vue Updated to mount OsDragOverlay and wire @files-dropped to active route view
from to via pattern
OsDragOverlay.vue window dragenter/dragleave/dragover/drop events mounted() addEventListener + beforeUnmount() removeEventListener window.addEventListener('drag
from to via pattern
App.vue FileManagerView.handleOsDrop @files-dropped handler -> routeViewRef.handleOsDrop 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 <OsDragOverlay @files-dropped="..." /> 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

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@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):

<Teleport to="body">
  <Transition name="fade">
    <div
      v-if="showOverlay"
      class="fixed inset-0 z-[9998] bg-indigo-900/40 flex items-center justify-center pointer-events-none"
      data-test="os-drag-overlay"
    >
      <div class="bg-white rounded-2xl px-10 py-8 text-center shadow-xl pointer-events-none">
        <AppIcon name="upload" class="w-10 h-10 text-indigo-400 mx-auto mb-3" />
        <p class="text-base font-semibold text-gray-800">Drop files to upload</p>
      </div>
    </div>
  </Transition>
</Teleport>

Plus a <style scoped> block with .fade-enter-active, .fade-leave-active { transition: opacity 0.15s ease } .fade-enter-from, .fade-leave-to { opacity: 0 }.

App.vue wiring:

  • Import OsDragOverlay from './components/layout/OsDragOverlay.vue'
  • Add <OsDragOverlay @files-dropped="onOsFilesDropped" /> to the template (after the ToastContainer mount from 10-04)
  • Add handler:
    function onOsFilesDropped(files) {
      routeViewRef.value?.handleOsDrop?.(files)
    }
    

FileManagerView.vue:

  • Extend defineExpose to also include handleOsDrop: (files) => onFilesSelected({ files, autoClassify: true }). Reuse the existing onFilesSelected function so the upload path, quota handling, and toast wiring (10-06) work identically to a DropZone drop.
Task 1: Promote OsDragOverlay.test.js stubs to real tests frontend/src/components/layout/__tests__/OsDragOverlay.test.js - The Wave 0 stub file - .planning/phases/10-ux-interaction/10-PATTERNS.md §"OsDragOverlay.vue" Replace `.todo` entries with real tests: 1. `overlay hidden by default (dragDepth=0)` - mount, assert wrapper does not contain the overlay element (or assert it's not visible). Use a Teleport stub. 2. `dragenter with Files type shows overlay (dragDepth=1)` - mount, dispatch a window dragenter event with a synthetic dataTransfer carrying `types: ['Files']`; assert showOverlay=true via vm or DOM. 3. `dragenter without Files type is ignored` - dispatch with `types: ['text/plain']`; assert showOverlay still false. 4. `dragleave decrements depth; overlay hides when depth reaches 0` - enter once (depth=1), leave (depth=0), assert hidden. 5. `nested dragenter+dragleave maintains overlay until depth=0` - dispatch enter twice (depth=2), leave once (depth=1, still showing), leave again (depth=0, hidden). 6. `drop emits files-dropped with the file list` - dispatch a window drop with synthetic dataTransfer.files=[new File(['x'], 'a.txt')]; assert emitted('files-dropped')[0][0] is an array containing one File. 7. `drop resets dragDepth to 0 and hides overlay` - enter 3x (depth=3), drop, assert showOverlay=false and subsequent leave doesn't go negative. 8. `overlay element has class z-[9998]` - enter once, find the overlay element in body, assert classList contains 'z-[9998]'.
Use `vi.fn()`, dispatch `new DragEvent(...)` or `new CustomEvent('dragenter', { ... })` and patch a fake `dataTransfer` on the event by `Object.defineProperty(event, 'dataTransfer', { value: { types: ['Files'], files: [...] } })`. happy-dom supports these.

Tests fail until Task 2 creates OsDragOverlay.vue.
Modify `frontend/src/components/layout/__tests__/OsDragOverlay.test.js`. Replace each `it.todo` with the real tests above. Import `OsDragOverlay` from `../OsDragOverlay.vue` (file doesn't exist yet -> tests fail on import). Use Vitest + @vue/test-utils + happy-dom defaults. cd frontend && npm run test -- --run OsDragOverlay Expected: 8 tests RED. - File has 8 real `it(...)` blocks, no `.todo` - Tests dispatch DragEvent / CustomEvent with synthetic dataTransfer - All 8 tests are RED 8 RED tests describing OsDragOverlay contract. Task 2: Implement OsDragOverlay.vue frontend/src/components/layout/OsDragOverlay.vue - frontend/src/components/layout/__tests__/OsDragOverlay.test.js (failing tests) - frontend/src/components/documents/DocumentPreviewModal.vue (window listener pattern reference) - .planning/phases/10-ux-interaction/10-PATTERNS.md §"OsDragOverlay.vue" - frontend/src/components/ui/AppIcon.vue Component name `OsDragOverlay`, Options API, emits `['files-dropped']`. Registers `AppIcon` as child component. Data + methods per the block. Template uses `` + `` and renders the overlay only when `showOverlay`. Includes `<style scoped>` block defining `.fade-enter-active`/`.fade-leave-active`/`.fade-enter-from`/`.fade-leave-to` transitions.
The overlay element has `class="fixed inset-0 z-[9998] bg-indigo-900/40 flex items-center justify-center pointer-events-none"` and `data-test="os-drag-overlay"`.
Create `frontend/src/components/layout/OsDragOverlay.vue` using the exact Options API structure from PATTERNS.md §"OsDragOverlay.vue". Use the template + style above. No comments inside the file. Add `data-test="os-drag-overlay"` for testability. cd frontend && npm run test -- --run OsDragOverlay Expected: 8 tests GREEN. - File `frontend/src/components/layout/OsDragOverlay.vue` exists - File contains `name: 'OsDragOverlay'` - File contains `emits: ['files-dropped']` - File contains `dragDepth: 0` in data - File contains `dataTransfer?.types.includes('Files')` guard - File contains all four window listeners (dragenter, dragleave, dragover, drop) - File contains `` - File contains class string with `z-[9998]` - File uses Options API (no `<script setup>`) - 8 tests pass OsDragOverlay implemented; 8 tests green. Task 3: Mount OsDragOverlay in App.vue; expose handleOsDrop in FileManagerView frontend/src/App.vue, frontend/src/views/FileManagerView.vue - frontend/src/App.vue (current state after 10-04 + 10-09) - frontend/src/views/FileManagerView.vue (current state after 10-06 + 10-09) - frontend/src/components/layout/OsDragOverlay.vue (newly created) Step A - App.vue: 1. Add `import OsDragOverlay from './components/layout/OsDragOverlay.vue'` to script imports. 2. Add `` to the template (place it after `` from 10-04 so the overlay z-[9998] is below the toast z-[9999]). 3. Add handler in script: ```js function onOsFilesDropped(files) { routeViewRef.value?.handleOsDrop?.(files) } ```
Step B - FileManagerView.vue:
Extend the existing `defineExpose` block (added in 10-09) to also include `handleOsDrop`:
```js
defineExpose({
  focusSearch: () => browserRef.value?.focusSearch?.(),
  triggerUpload: () => browserRef.value?.triggerUpload?.(),
  startNewFolder: () => browserRef.value?.startNewFolder?.(),
  clearSearch: () => browserRef.value?.clearSearch?.(),
  handleOsDrop: (files) => onFilesSelected({ files, autoClassify: true }),
})
```

Do not modify any other logic. The existing onFilesSelected handles the upload + per-file UploadProgress + toast wiring (from 10-06) end to end.
cd frontend && npm run test -- --run OsDragOverlay; cd frontend && npm run test -- --run FileManagerView; cd frontend && npm run test -- --run keyboard; cd frontend && npm run test -- --run toast Expected: all suites pass; no regression. - `grep -E "import OsDragOverlay" frontend/src/App.vue` returns 1 - `grep -E " OsDragOverlay mounted in App.vue; FileManagerView exposes handleOsDrop; UX-09 fully wired. - `cd frontend && npm run test -- --run OsDragOverlay` exits 0 with 8 tests passing - `cd frontend && npm run test -- --run` (full) exits 0 - `grep -n "ToastContainer\\|OsDragOverlay" frontend/src/App.vue` shows OsDragOverlay AFTER ToastContainer (or below it in source order) - OsDragOverlay.vue uses z-[9998] (below ToastContainer z-[9999]) - FileManagerView.handleOsDrop reuses onFilesSelected (no duplicate upload logic)

<success_criteria> Dragging one or more files from the OS file explorer over the browser window shows an indigo overlay with an upload icon and "Drop files to upload" prompt. Releasing the files uploads them via the existing FileManagerView upload flow (with per-file progress in UploadProgress and a summary toast). The overlay does not appear when dragging an in-app element (file row, folder row). The overlay does not appear when on non-file-manager routes (admin, settings) because routeViewRef does not expose handleOsDrop there. </success_criteria>

Create `.planning/phases/10-ux-interaction/10-10-SUMMARY.md` when done.