Fix Set as base leaving one side on its old contents
Setting a new base called setSide twice, so the first call kicked off a comparison against the *old* other side. Scans run concurrently, so that stale result could land last and win: the left had zoomed into the archive while the right still showed the parent, which is exactly what it looked like on screen. Both sides are now set in one go before comparing, and every scan is stamped so a slower, older run can no longer overwrite a newer result. Also renamed the pairing entries to mention 'set as base', since marking one side and picking the other achieves the same thing for items whose names differ and that wasn't obvious from the wording. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UfhDYRLTpGJSKGP1m3LVJN
This commit is contained in:
@@ -32,6 +32,8 @@ final class ComparisonModel: Identifiable {
|
||||
/// Which folders are open. Held here rather than by List's outline so both
|
||||
/// sides of a row can carry a disclosure control.
|
||||
private var expanded: Set<String> = []
|
||||
/// Stamps each scan so a stale one can't overwrite a newer result.
|
||||
private var scanGeneration = 0
|
||||
|
||||
/// One side of a manual pairing, held while the other is chosen. Lets two
|
||||
/// files with different names be compared, which the tree alone can't express.
|
||||
@@ -164,6 +166,17 @@ final class ComparisonModel: Identifiable {
|
||||
if isReady { compare() }
|
||||
}
|
||||
|
||||
/// Sets both sides at once. Doing it with two `setSide` calls starts a
|
||||
/// comparison against the *old* other side, and since scans run concurrently
|
||||
/// that stale result can land last and win — which showed up as one side
|
||||
/// zooming in while the other kept its previous contents.
|
||||
func setBoth(left: URL, right: URL) {
|
||||
leftURL = left
|
||||
rightURL = right
|
||||
selection = nil
|
||||
compare()
|
||||
}
|
||||
|
||||
func setSide(_ side: Side, to url: URL) {
|
||||
switch side {
|
||||
case .left: leftURL = url
|
||||
@@ -185,10 +198,7 @@ final class ComparisonModel: Identifiable {
|
||||
guard !urls.isEmpty else { return }
|
||||
|
||||
if urls.count >= 2 {
|
||||
leftURL = urls[0]
|
||||
rightURL = urls[1]
|
||||
selection = nil
|
||||
compare()
|
||||
setBoth(left: urls[0], right: urls[1])
|
||||
return
|
||||
}
|
||||
|
||||
@@ -235,18 +245,25 @@ final class ComparisonModel: Identifiable {
|
||||
errorMessage = nil
|
||||
selection = nil
|
||||
|
||||
// Scans run off the main actor and a slow one can finish after a newer
|
||||
// one; stamping each run means only the latest is allowed to land.
|
||||
scanGeneration &+= 1
|
||||
let generation = scanGeneration
|
||||
let options = DirectoryComparer.Options(mode: mode, textOptions: textOptions)
|
||||
|
||||
Task.detached(priority: .userInitiated) {
|
||||
do {
|
||||
let left = try TreeScanner.scan(leftURL)
|
||||
let right = try TreeScanner.scan(rightURL)
|
||||
let diff = DirectoryComparer.compare(left: left, right: right, options: options)
|
||||
await MainActor.run {
|
||||
guard generation == self.scanGeneration else { return }
|
||||
self.root = diff
|
||||
self.isScanning = false
|
||||
}
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
guard generation == self.scanGeneration else { return }
|
||||
self.errorMessage = error.localizedDescription
|
||||
self.isScanning = false
|
||||
self.root = nil
|
||||
|
||||
@@ -170,19 +170,19 @@ struct DiffTreeView: View {
|
||||
// other. Works for folders too, so a folder inside a JAR can be set
|
||||
// against one on disk.
|
||||
if let left = node.left {
|
||||
Button("Mark left for comparison") { mark(left, path: node.path) }
|
||||
Button("Set as base: mark left") { mark(left, path: node.path) }
|
||||
}
|
||||
if let right = node.right {
|
||||
Button("Mark right for comparison") { mark(right, path: node.path) }
|
||||
Button("Set as base: mark right") { mark(right, path: node.path) }
|
||||
}
|
||||
if let pending = model.pendingPick {
|
||||
Divider()
|
||||
if let left = node.left {
|
||||
Button("Compare left with “\(pending.name)”") { compare(pending, with: left, newTab: false) }
|
||||
Button("Set as base with “\(pending.name)” (left)") { compare(pending, with: left, newTab: false) }
|
||||
}
|
||||
if let right = node.right {
|
||||
Button("Compare right with “\(pending.name)”") { compare(pending, with: right, newTab: false) }
|
||||
Button("Compare right with “\(pending.name)” in a new tab") {
|
||||
Button("Set as base with “\(pending.name)” (right)") { compare(pending, with: right, newTab: false) }
|
||||
Button("Set as base with “\(pending.name)” in a new tab") {
|
||||
compare(pending, with: right, newTab: true)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -48,8 +48,7 @@ final class TabsModel {
|
||||
/// new tab, which is what makes a hand-picked pair the new base.
|
||||
func openPair(left: URL, right: URL, inNewTab: Bool) {
|
||||
let target = inNewTab ? newTab() : selected
|
||||
target.setSide(.left, to: left)
|
||||
target.setSide(.right, to: right)
|
||||
target.setBoth(left: left, right: right)
|
||||
selectedID = target.id
|
||||
}
|
||||
|
||||
|
||||
@@ -110,3 +110,5 @@
|
||||
"Paste something on each side to compare." = "Paste something on each side to compare.";
|
||||
"Clear" = "Clear";
|
||||
"Binary" = "Binary";
|
||||
"Set as base: mark left" = "Set as base: mark left";
|
||||
"Set as base: mark right" = "Set as base: mark right";
|
||||
|
||||
@@ -110,3 +110,5 @@
|
||||
"Paste something on each side to compare." = "Pega algo en cada lado para comparar.";
|
||||
"Clear" = "Limpiar";
|
||||
"Binary" = "Binario";
|
||||
"Set as base: mark left" = "Usar como base: marcar izquierda";
|
||||
"Set as base: mark right" = "Usar como base: marcar derecha";
|
||||
|
||||
Reference in New Issue
Block a user