Add File Shelf, a drag-and-drop staging area for files - #30
Open
havokentity wants to merge 2 commits into
Open
Conversation
Moving a file between two apps, or between two Spaces, currently means keeping a Finder window parked somewhere just to hold it. File Shelf is that holding area without the window: drop files onto the menu bar icon or into the popover, then drag them straight back out wherever you land. Shelving deliberately does not copy bytes. Each entry is a security-scoped bookmark to the original file, which buys three things a list of paths could not: parking a 40 GB video costs the same as parking a note, the shelf survives a relaunch, and an entry keeps working after the file is renamed or moved — exactly the tidying people do while something is parked on its way elsewhere. Dragging out hands over the real file URL, so the receiving app attaches or opens the original rather than a duplicate. A file that has been deleted, or that lives on a disk that is currently unplugged, stays on the shelf and is drawn as unavailable. The alternative — pruning it — makes the shelf look like it lost the thing the user parked, and an unplugged disk comes back on its own. De-duplication compares device and inode rather than paths, so the same file reached through a symlink, or dropped again after a rename, collapses onto the row it already has. Bookmarks are resolved with `.withoutUI` and `.withoutMounting`: without them, a routine refresh of an entry on an unplugged disk pops a system "please insert the disk" panel out of a background menu-bar app. The menu-bar icon is a drop target in its own right, which is the point of the tool — park a file without opening anything. AppKit creates NSStatusBarButton and its dragging-destination methods are overrides rather than delegate callbacks, so the only way in is a transparent child view that registers for file URLs and hands every mouse event straight back to the button underneath; without that pass-through the overlay would be a dead zone over the icon. The rules that decide what the shelf holds — ordering, de-duplication, the cap, bookmark resolution and the on-disk format — live in FileShelfKit as pure functions over values, and the tests drive real bookmarks against files in a temporary directory. A stubbed resolver would have asserted nothing about the one question that matters: whether a bookmark actually follows a rename. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three things the drop overlay and the shelf logic were getting wrong. The overlay that turns the status button into a drag target wins hit-testing over the button, and NSView answers false to acceptsFirstMouse where NSStatusBarButton answers true. This tool is LSUIElement and never becomes the active app, so every click on its icon is a first-mouse click: AppKit was free to spend that click on activation instead of delivering it, and the mouseDown pass-through that opens the popover never ran. The only other two status-button subviews in this package already override this for exactly the same reason. De-duplication compared a dropped file against identities cached on `items`, which were computed at the last resolve — startup, or the last popover open. A device/inode pair does not survive a safe save, and a drop straight onto the menu-bar icon never opens the popover, so shelving a file, editing it in any app that writes atomically, and dropping it again found no match and minted a second row. Nothing collapsed the two afterwards. shelve() now re-resolves first, through the same helper refresh() uses, so a drop is judged against the shelf as it is rather than as it was. Finally, .missing was unreachable. A bookmark to a deleted file, or to one on an unmounted volume, does not resolve at all — .withoutMounting sees to the second — so every failure landed on .unresolvable, whose copy tells the user the entry has to be shelved again. Someone who merely unplugged an external disk would have removed a row that was about to start working by itself. The two cases are now told apart by reading the bookmark's own cached resource values, which survive while the file does not; junk data carries none. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Wave 2 Phase A.
Implementation: Add File Shelf, a drag-and-drop staging area for files
Review fixes: File Shelf: make the icon clickable and stop re-drops forking a row
Built by an isolated agent, then adversarially reviewed by an independent agent that checked the branch out and ran
swift build+ the full suite itself. Every review finding was fixed, with a regression test added for each high and medium.🤖 Generated with Claude Code