Every board root keys once at its store — the hover path stops walking the filesystem

`DragLocality.isSameBoard` resolved symlinks on both URLs, which stats every
path component. It ran five-plus times per `dropUpdated` and again per lane per
body evaluation through `renderedCards` → `hiddenMembers` — order of 50–100
stat calls per mouse-move, and worse on iCloud-backed paths. A pickup stall
sample had it on ~47 of 943 stacks.

`BoardRootKey` mints that canonical spelling once, where the root is, and every
locality comparison downstream is an `==` on two strings. The drag carriers hold
keys rather than URLs, so re-deriving under the cursor is no longer expressible.
`rootURL` keeps the user's spelling — the folder name is the display-name
fallback, and canonicalizing there would visibly rename a board opened through
a pin. The key follows the folder through `relocate(to:)`: a key frozen at open
would collide with a new board opened at the vacated path, and two boards
comparing as one is a cross-board drag silently behaving as a move.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
2026-08-01 17:55:48 -04:00
parent 8a8ec4dfd1
commit ef423bb9d2
8 changed files with 232 additions and 124 deletions
+19 -19
View File
@@ -147,7 +147,7 @@ struct BoardDropContext {
/// disagree.
func revalidateProposal() {
guard let proposal = session.proposal,
DragLocality.isSameBoard(proposal.boardRoot, store.rootURL),
proposal.boardRoot == store.rootKey,
let laneID = proposal.laneID
else { return }
guard !store.snapshot.lanes.contains(where: { $0.id == laneID }) else { return }
@@ -179,7 +179,7 @@ struct BoardDropContext {
/// `receiveLanes` keep the index space they always had.
func retargetLanes() {
guard session.isDraggingLanes, let cursor = stripCursor() else { return }
let hidden = session.hiddenMembers(onBoardRooted: store.rootURL)
let hidden = session.hiddenMembers(onBoardRooted: store.rootKey)
let resting = store.snapshot.lanes.filter { !hidden.contains($0.id) }
let restingUnits = resting.map { LaneLayoutMath.displayUnits(of: $0) }
let slot = DropSlotMath.laneSlot(
@@ -188,11 +188,11 @@ struct BoardDropContext {
draggedUnits: session.laneUnits,
standard: standard(),
gap: gap,
current: session.stripProposal(onBoardRooted: store.rootURL)
current: session.stripProposal(onBoardRooted: store.rootKey)
)
guard let slot else { return } // a dead region: hold the current proposal
let index = min(max(0, slot), restingUnits.count)
session.propose(DropTarget(boardRoot: store.rootURL, container: .strip, index: index))
session.propose(DropTarget(boardRoot: store.rootKey, container: .strip, index: index))
}
/// Where a **card** session would land in `laneID`'s masonry.
@@ -215,7 +215,7 @@ struct BoardDropContext {
}
guard let grid = registry.grids[laneID] else { return }
let hidden = session.hiddenMembers(onBoardRooted: store.rootURL)
let hidden = session.hiddenMembers(onBoardRooted: store.rootKey)
let rendered = lane.cards.filter { !hidden.contains($0.id) }
let heights = rendered.map { registry.heights[$0.id] ?? LaneDropRegistry.nominalCardHeight }
let placement = MasonryPlacement(
@@ -232,10 +232,10 @@ struct BoardDropContext {
// The run's footprint at the landing spot: the first dragged card's frozen height, which
// is the trigger rect the cursor is over (the rest stack below it).
draggedHeight: session.cardHeights.first ?? LaneDropRegistry.nominalCardHeight,
current: session.laneProposal(onBoardRooted: store.rootURL, laneID: laneID)
current: session.laneProposal(onBoardRooted: store.rootKey, laneID: laneID)
)
guard let slot else { return } // a dead region: hold
session.propose(DropTarget(boardRoot: store.rootURL, container: .lane(laneID), index: slot))
session.propose(DropTarget(boardRoot: store.rootKey, container: .lane(laneID), index: slot))
}
/// The strip's fall-through for card sessions: which lane is under the cursor, analytically.
@@ -280,8 +280,8 @@ struct BoardDropContext {
return TrashDrop.accepts(
kind: session.kind,
container: session.container,
isWithinBoard: DragLocality.isSameBoard(sourceRoot, store.rootURL),
operation: session.resolveOperation(destinationRoot: store.rootURL),
isWithinBoard: sourceRoot == store.rootKey,
operation: session.resolveOperation(destinationRoot: store.rootKey),
isTrashShown: store.transient.isTrashVisible,
acceptsMutations: store.acceptsBoardMutations
)
@@ -304,7 +304,7 @@ struct BoardDropContext {
return
}
session.propose(DropTarget(
boardRoot: store.rootURL,
boardRoot: store.rootKey,
container: .trash,
index: TrashDrop.landingIndex
))
@@ -390,7 +390,7 @@ struct BoardDropContext {
placement: placement,
heights: heights,
nominalHeight: LaneDropRegistry.nominalCardHeight,
current: session.fileLaneProposal(onBoardRooted: store.rootURL, laneID: laneID)?.index
current: session.fileLaneProposal(onBoardRooted: store.rootKey, laneID: laneID)?.index
)
switch landing {
@@ -399,13 +399,13 @@ struct BoardDropContext {
case let .attach(index):
guard rendered.indices.contains(index) else { return }
session.proposeFile(FileDropTarget(
boardRoot: store.rootURL,
boardRoot: store.rootKey,
landing: .attach(cardID: rendered[index].id),
fileCount: count
))
case let .create(index):
session.proposeFile(FileDropTarget(
boardRoot: store.rootURL,
boardRoot: store.rootKey,
landing: .create(laneID: laneID, index: index),
fileCount: count
))
@@ -463,7 +463,7 @@ struct BoardDropContext {
func commitFileDrop(_ info: DropInfo) -> Bool {
guard acceptsFileDrops,
let target = session.fileTarget,
DragLocality.isSameBoard(target.boardRoot, store.rootURL)
target.boardRoot == store.rootKey
else {
session.proposeFile(nil)
return false
@@ -503,9 +503,9 @@ struct BoardDropContext {
/// live as the cursor crosses a board boundary (04-interactions.md Drag and drop).
func dropProposal() -> DropProposal {
guard let proposal = session.proposal,
DragLocality.isSameBoard(proposal.boardRoot, store.rootURL)
proposal.boardRoot == store.rootKey
else { return DropProposal(operation: .cancel) }
let operation = session.resolveOperation(destinationRoot: store.rootURL)
let operation = session.resolveOperation(destinationRoot: store.rootKey)
return DropProposal(operation: operation == .copy ? .copy : .move)
}
@@ -541,7 +541,7 @@ struct BoardDropContext {
}
revalidateProposal()
guard let target = session.proposal,
DragLocality.isSameBoard(target.boardRoot, store.rootURL)
target.boardRoot == store.rootKey
else {
cancelDrop()
return false
@@ -570,8 +570,8 @@ struct BoardDropContext {
}
let ids = survivors.map { session.members[$0] }
let folders = survivors.map { session.folders[$0] }
let within = DragLocality.isSameBoard(sourceRoot, store.rootURL)
let operation = session.resolveOperation(destinationRoot: store.rootURL)
let within = sourceRoot == store.rootKey
let operation = session.resolveOperation(destinationRoot: store.rootKey)
switch kind {
case .lanes: