Land Finder file drops positionally, header release topmost
04's settled clauses were mostly shipped already — the create landing resolved through DropSlotMath.cardSlot with one nominal shadow per importable file — but a release on the lane header fell through to the card zones, which clamp inward, so a scrolled lane could propose behind the header stripe. FileDropZones now folds header, attach hit-test, and card-slot resolution into one pure seam asked in that order, the header answering topmost per the ruling; lane headers register their frames for it. FinderDrop.shadowCount names the floor-at-one rule. New tests pin the header boundary, a differential against cardSlot's own zones (same zones, not similar), and a store-level differential proving a file landing takes the very ranks a card move there takes. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -109,9 +109,18 @@ struct FileDropTarget: Equatable, Sendable {
|
||||
enum Landing: Equatable, Sendable {
|
||||
/// **Onto a card**: the files copy into that card's `attachments/`. A card under the cursor
|
||||
/// always wins over the lane behind it — attach beats create, anywhere on the card's bounds.
|
||||
/// This is the one file landing that **highlights**, and only because it has no shadow to
|
||||
/// show instead (04-interactions.md ▸ Drag and drop, settled 2026-07-28).
|
||||
case attach(cardID: ItemID)
|
||||
/// **Onto lane empty space**: one card per file, landing at this position in the lane's
|
||||
/// **Onto the lane's card grid**: one card per file, landing at this position in the lane's
|
||||
/// logical card order.
|
||||
///
|
||||
/// **Positional, everywhere the grid reaches** (04-interactions.md ▸ Drag and drop, settled
|
||||
/// 2026-07-28): "created cards land at the drop position — resolved through the same
|
||||
/// card-grid zones an ordinary card drag uses, shadow included", and a release on the lane
|
||||
/// **header** resolves to index 0, the topmost position. Append-at-bottom is the creation
|
||||
/// trio's rule (⌘N, Return, a double click), never the drop's. `FileDropZones.landing` is
|
||||
/// where the index comes from.
|
||||
case create(laneID: ItemID, index: Int)
|
||||
}
|
||||
|
||||
@@ -121,12 +130,15 @@ struct FileDropTarget: Equatable, Sendable {
|
||||
|
||||
var landing: Landing
|
||||
|
||||
/// How many files ride along — the create path's shadow run length, one shadow per card that
|
||||
/// will land. Read off the session's item providers at hover time, floored at one.
|
||||
/// How many files ride along — **one nominal-height shadow per incoming file** on the create path
|
||||
/// (04-interactions.md ▸ Drag and drop, settled 2026-07-28: the multi-drag precedent), one shadow
|
||||
/// per card that will land. Read off the session's item providers at hover time
|
||||
/// (`FinderDrop.shadowCount`), **floored at one** for the drag whose count macOS withholds — the
|
||||
/// commit is unaffected either way, since the write counts resolved URLs, not providers.
|
||||
///
|
||||
/// **Folders are not counted** (04-interactions.md ▸ Drag and drop: "a mixed drag proposes for
|
||||
/// its files only"): `FinderDrop.importableCount` reads the providers' declared types, so a
|
||||
/// mixed drag draws shadows for its files alone and a folders-only drag never proposes at all.
|
||||
/// **Folders are not counted** (same bullet: "a mixed drag proposes for its files only"):
|
||||
/// `FinderDrop.importableCount` reads the providers' declared types, so a mixed drag draws
|
||||
/// shadows for its files alone and a folders-only drag never proposes at all.
|
||||
var fileCount: Int
|
||||
}
|
||||
|
||||
@@ -568,6 +580,15 @@ final class DragSession {
|
||||
|
||||
/// The shadow run a file drop would open in `laneID` on this board — where the created cards
|
||||
/// land and how many there are — or `nil` when the proposal is elsewhere.
|
||||
///
|
||||
/// **This run is the create path's whole feedback** (04-interactions.md ▸ Drag and drop, settled
|
||||
/// 2026-07-28): there is no lane-level highlight to go with it, deliberately — each target gets
|
||||
/// one clear signal, and `fileAttachTarget` above is the highlight precisely because a card
|
||||
/// target has no shadow.
|
||||
///
|
||||
/// The floor restates `FinderDrop.shadowCount`'s, at the render end rather than in place of it: a
|
||||
/// proposal that somehow carried a zero would otherwise open a run of no shadows at all, which is
|
||||
/// a landing spot the user cannot see.
|
||||
func fileLaneProposal(onBoardRooted root: URL, laneID: ItemID) -> (index: Int, count: Int)? {
|
||||
guard let fileTarget,
|
||||
DragLocality.isSameBoard(fileTarget.boardRoot, root),
|
||||
|
||||
Reference in New Issue
Block a user