docs(10-11): complete drag-to-move guard + teleport dropdowns plan — 10 tests, 3 components updated
This commit is contained in:
@@ -0,0 +1,107 @@
|
|||||||
|
---
|
||||||
|
phase: 10-ux-interaction
|
||||||
|
plan: 11
|
||||||
|
subsystem: frontend/interaction
|
||||||
|
tags: [drag-to-move, teleport, dropdown, click-guard, ux]
|
||||||
|
dependency_graph:
|
||||||
|
requires: [10-06, 10-09, 10-10, 10-05]
|
||||||
|
provides: [UX-11-complete, UX-13-complete]
|
||||||
|
affects: [StorageBrowser.vue, DocumentCard.vue, FolderRow.vue]
|
||||||
|
tech_stack:
|
||||||
|
added: []
|
||||||
|
patterns:
|
||||||
|
- "Teleport to body + getBoundingClientRect for overflow-safe dropdown positioning"
|
||||||
|
- "nextTick-deferred draggingFile reset to prevent click-after-drag navigation"
|
||||||
|
key_files:
|
||||||
|
created: []
|
||||||
|
modified:
|
||||||
|
- frontend/src/components/storage/StorageBrowser.vue
|
||||||
|
- frontend/src/components/documents/DocumentCard.vue
|
||||||
|
- frontend/src/components/folders/FolderRow.vue
|
||||||
|
- frontend/src/components/storage/__tests__/StorageBrowser.dragmove.test.js
|
||||||
|
- frontend/src/components/ui/__tests__/dropdown.test.js
|
||||||
|
decisions:
|
||||||
|
- "D-18 scope confirmed: DocumentCard.vue has no draggable attribute in Phase 10; the dragend-then-click guard (D-18 goal) is correctly implemented on StorageBrowser file rows which are the actual drag sources. No additional drag handle needed in DocumentCard."
|
||||||
|
- "Folder picker file ID stored in folderPickerFileId reactive ref; trigger button tracked in Map for scroll repositioning"
|
||||||
|
- "nextTick deferred reset chosen over synchronous reset to reliably suppress the browser's click event that fires after dragend"
|
||||||
|
metrics:
|
||||||
|
duration: "~20 minutes"
|
||||||
|
completed: "2026-06-16"
|
||||||
|
tasks_completed: 4
|
||||||
|
tasks_total: 4
|
||||||
|
files_changed: 5
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 10 Plan 11: Drag-to-Move Click Guard + Teleported Dropdowns Summary
|
||||||
|
|
||||||
|
**One-liner:** Click-after-drag guard with nextTick reset in StorageBrowser; all three dropdown menus (StorageBrowser folder picker, DocumentCard folder picker, FolderRow three-dot menu) teleported to body with getBoundingClientRect positioning.
|
||||||
|
|
||||||
|
## What Was Built
|
||||||
|
|
||||||
|
### UX-11: Drag-to-Move Completion
|
||||||
|
|
||||||
|
The drag-to-move highlight and drop handlers were already wired from plan 10-06. This plan added the missing pieces:
|
||||||
|
|
||||||
|
1. **Click-after-drag guard** — file-row `@click` now checks `draggingFile ? null : $emit('file-open', file)`. While a drag is in progress, clicks on file rows are suppressed.
|
||||||
|
|
||||||
|
2. **`onFileDragEnd()` handler** — defers `draggingFile = null` via `nextTick()` instead of synchronously resetting. This ensures the click event fired by the browser immediately after `dragend` still sees `draggingFile` as non-null and gets suppressed.
|
||||||
|
|
||||||
|
3. **`onDropDocOnFolder` updated** — also uses `await nextTick()` before clearing `draggingFile` for the drop-on-folder case.
|
||||||
|
|
||||||
|
### UX-13: Teleported Dropdowns
|
||||||
|
|
||||||
|
Three dropdowns replaced their `position: absolute` approach with `<Teleport to="body">` + `getBoundingClientRect` fixed positioning:
|
||||||
|
|
||||||
|
- **StorageBrowser folder picker** — single shared teleport block; trigger mapped by `fileId` in a `Map` for scroll repositioning. `openFolderPicker(fileId, $event)` captures the trigger and computes initial position.
|
||||||
|
- **DocumentCard folder picker** — `pickerTriggerEl` ref on the button; `updatePickerPosition()` called on toggle, scroll, and resize.
|
||||||
|
- **FolderRow three-dot menu** — `menuTriggerEl` ref on the button; `updateMenuPosition()` called on toggle, scroll, and resize. Menu anchored to right edge of trigger via `rect.right - 160`.
|
||||||
|
|
||||||
|
All three follow the scroll-reposition pattern from `SearchableModelSelect.vue`.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
| File | Tests | Result |
|
||||||
|
|------|-------|--------|
|
||||||
|
| StorageBrowser.dragmove.test.js | 6 | All pass |
|
||||||
|
| dropdown.test.js | 4 | All pass |
|
||||||
|
| Full suite | 208 | All pass (3 pre-existing todos in skeleton test) |
|
||||||
|
|
||||||
|
## Commits
|
||||||
|
|
||||||
|
| Hash | Type | Description |
|
||||||
|
|------|------|-------------|
|
||||||
|
| f20420a | test | Promote stub tests to real tests (RED phase) |
|
||||||
|
| f9ddda8 | feat | Click-after-drag guard + teleport folder picker in StorageBrowser |
|
||||||
|
| e0606b4 | feat | Teleport DocumentCard folder picker |
|
||||||
|
| 892abca | feat | Teleport FolderRow three-dot menu |
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
None — plan executed exactly as written.
|
||||||
|
|
||||||
|
## D-18 Scope Decision
|
||||||
|
|
||||||
|
Per the plan's locked decision and confirmed during implementation: `DocumentCard.vue` does NOT have `draggable="true"` in Phase 10. It is not a drag source. The dragend-then-click guard (D-18's protection goal) is fully satisfied by the `onFileDragEnd` + click guard implemented on StorageBrowser file rows, which ARE the drag sources.
|
||||||
|
|
||||||
|
If `DocumentCard.vue` gains `draggable="true"` in a future phase, it will need its own drag handle at that time.
|
||||||
|
|
||||||
|
## Known Stubs
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Threat Flags
|
||||||
|
|
||||||
|
None — no new network endpoints, auth paths, file access patterns, or schema changes introduced.
|
||||||
|
|
||||||
|
## Self-Check: PASSED
|
||||||
|
|
||||||
|
Files exist:
|
||||||
|
- `frontend/src/components/storage/StorageBrowser.vue` — FOUND
|
||||||
|
- `frontend/src/components/documents/DocumentCard.vue` — FOUND
|
||||||
|
- `frontend/src/components/folders/FolderRow.vue` — FOUND
|
||||||
|
- `frontend/src/components/storage/__tests__/StorageBrowser.dragmove.test.js` — FOUND
|
||||||
|
- `frontend/src/components/ui/__tests__/dropdown.test.js` — FOUND
|
||||||
|
|
||||||
|
Commits exist: f20420a, f9ddda8, e0606b4, 892abca — all verified in git log.
|
||||||
|
|
||||||
|
Test suite: 208 passed, 0 failed.
|
||||||
Reference in New Issue
Block a user