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
+5 -5
View File
@@ -170,7 +170,7 @@ struct BoardView: View {
// discards itself the moment a snapshot lands because holding a moment longer would draw
// the arrangement twice.
.onChange(of: store.snapshotGeneration) { _, generation in
appModel.dragSession.handOff(root: store.rootURL, generation: generation)
appModel.dragSession.handOff(root: store.rootKey, generation: generation)
}
.trashPurgeAlert(store: store, confirmations: confirmations)
// The board's own anchor for the Style popover the surface a board-targeted session hangs
@@ -602,9 +602,9 @@ struct BoardView: View {
private var arrivingLaneUnits: Int {
let session = appModel.dragSession
guard session.isDraggingLanes,
session.stripProposal(onBoardRooted: store.rootURL) != nil,
session.stripProposal(onBoardRooted: store.rootKey) != nil,
let source = session.sourceRoot,
!DragLocality.isSameBoard(source, store.rootURL)
source != store.rootKey
else { return 0 }
return session.laneUnits.reduce(0, +)
}
@@ -612,7 +612,7 @@ struct BoardView: View {
/// The strip's current lane-drop proposal the reflow's narrow animation key, and where the
/// shadow run opens.
private var stripProposal: Int? {
appModel.dragSession.stripProposal(onBoardRooted: store.rootURL)
appModel.dragSession.stripProposal(onBoardRooted: store.rootKey)
}
/// One position in the strip: a lane, or one of the drag's N contiguous shadows.
@@ -635,7 +635,7 @@ struct BoardView: View {
/// the shadows opened at the proposal.
private var stripSlots: [StripSlot] {
let session = appModel.dragSession
let hidden = session.hiddenMembers(onBoardRooted: store.rootURL)
let hidden = session.hiddenMembers(onBoardRooted: store.rootKey)
var slots = boardLanes.filter { !hidden.contains($0.id) }.map(StripSlot.lane)
guard let index = stripProposal else { return slots }
let shadows = session.laneUnits.enumerated().map { StripSlot.shadow(index: $0.offset, units: $0.element) }