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:
@@ -8,6 +8,32 @@ import SwiftUI
|
||||
|
||||
// MARK: - Vocabulary
|
||||
|
||||
/// **A board root's identity**, as the one canonical spelling every locality comparison is made
|
||||
/// against (04-interactions.md ▸ Drag and drop — the Finder volume model).
|
||||
///
|
||||
/// Two roots name the same board when they name the same place: symlinks are resolved first — a
|
||||
/// board reached through a pinned symlink is the same board as the one reached directly
|
||||
/// (01-storage-format.md's symlink pins) — and the standardized path is the comparison key, so
|
||||
/// `/Boards/./Work.kanban/` and `/Boards/Other/../Work.kanban` are one board.
|
||||
///
|
||||
/// **Minting touches the filesystem; comparing does not.** `resolvingSymlinksInPath` stats every
|
||||
/// component of the path — the trap `EchoLedger.key` refuses outright for the same reason — and the
|
||||
/// drag's locality question is asked several times per `dropUpdated` and again per lane per body
|
||||
/// evaluation. Resolution therefore happens exactly once per board, where the root is
|
||||
/// (`BoardStore.rootKey`), and every reader downstream compares two strings. Holding one of these
|
||||
/// rather than a `URL` is what makes the re-derivation unwriteable rather than merely avoided.
|
||||
public struct BoardRootKey: Hashable, Sendable {
|
||||
|
||||
/// The resolved, standardized path — kept for equality and for nothing else. The root's *user*
|
||||
/// spelling stays on `BoardStore.rootURL`, which is what the display-name fallback and every
|
||||
/// derived write path read: canonicalizing there would rename a board reached through a pin.
|
||||
public let path: String
|
||||
|
||||
public init(_ url: URL) {
|
||||
path = url.resolvingSymlinksInPath().standardizedFileURL.path
|
||||
}
|
||||
}
|
||||
|
||||
/// Why a board is refusing writes — the read-only lock's cause, and now the whole of the
|
||||
/// vocabulary 02-architecture.md names.
|
||||
///
|
||||
@@ -370,6 +396,19 @@ public final class BoardStore: HealHost {
|
||||
/// snapshot at the new root, after which the two agree again.
|
||||
public private(set) var rootURL: URL
|
||||
|
||||
/// This board's **identity**, minted once here because minting is the filesystem-touching step
|
||||
/// (`BoardRootKey`). Every locality comparison a drag makes — at hover, at release, and in every
|
||||
/// lane's resting layout — is an `==` against this value.
|
||||
///
|
||||
/// **Follows the folder, exactly as `rootURL` does** — an absorbed rename or move re-mints it
|
||||
/// (`relocate(to:)`), at one filesystem touch per relocation and none on the hover path. A key
|
||||
/// frozen at open would outlive the place it names, and a *new* board opened at the vacated path
|
||||
/// would then mint the same one: two boards comparing as one, which is a cross-board drag
|
||||
/// silently behaving as a within-board move. The residue of following instead — a rename
|
||||
/// absorbed mid-drag re-reads that drag's locality — is the cosmetic failure of the pair, and
|
||||
/// is what shipped before the key existed.
|
||||
public private(set) var rootKey: BoardRootKey
|
||||
|
||||
// MARK: Banners
|
||||
|
||||
/// The board window's banner strip, as a model (02-architecture.md § The banner surface).
|
||||
@@ -710,6 +749,7 @@ public final class BoardStore: HealHost {
|
||||
/// surface.
|
||||
public init(rootURL: URL, loaded result: LoadResult, skipping: Set<String> = []) {
|
||||
self.rootURL = rootURL
|
||||
self.rootKey = BoardRootKey(rootURL)
|
||||
self.snapshot = result.model
|
||||
self.loadWarnings = result.warnings
|
||||
self.defects = result.defects
|
||||
@@ -1228,6 +1268,7 @@ public final class BoardStore: HealHost {
|
||||
guard newRoot != rootURL else { return }
|
||||
Self.logger.debug("board root relocated; Writer URLs now derive from the new location")
|
||||
rootURL = newRoot
|
||||
rootKey = BoardRootKey(newRoot)
|
||||
}
|
||||
|
||||
/// Raises the vanished-root read-only lock — the registry's call, after bookmark re-resolution
|
||||
|
||||
Reference in New Issue
Block a user