Files
kotej/App/TabsModel.swift
alexandrev-tibco 8e4a3e5f62 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
2026-07-31 14:46:59 +02:00

65 lines
2.0 KiB
Swift

import Foundation
import Observation
/// Holds the open comparisons. Each tab is an independent `ComparisonModel`, so
/// switching tabs keeps every scan, selection and set of options intact.
@MainActor
@Observable
final class TabsModel {
private(set) var tabs: [ComparisonModel]
var selectedID: ComparisonModel.ID
init() {
let first = ComparisonModel()
tabs = [first]
selectedID = first.id
}
var selected: ComparisonModel {
tabs.first { $0.id == selectedID } ?? tabs[0]
}
@discardableResult
func newTab() -> ComparisonModel {
let tab = ComparisonModel()
tabs.append(tab)
selectedID = tab.id
return tab
}
func close(_ tab: ComparisonModel) {
guard let index = tabs.firstIndex(where: { $0.id == tab.id }) else { return }
tabs.remove(at: index)
// Never leave the window without a tab; reuse an empty one instead.
if tabs.isEmpty {
let fresh = ComparisonModel()
tabs = [fresh]
selectedID = fresh.id
return
}
if selectedID == tab.id {
selectedID = tabs[min(index, tabs.count - 1)].id
}
}
func closeSelected() { close(selected) }
/// Opens an arbitrary pair, either replacing the current comparison or in a
/// 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.setBoth(left: left, right: right)
selectedID = target.id
}
/// Opens a comparison in a tab: reuses the current one while it's still empty
/// (the common case right after launch) and otherwise starts a new one, so an
/// in-progress comparison is never overwritten.
func open(left: URL?, right: URL?) {
let target = selected.isPristine ? selected : newTab()
if let left { target.setSide(.left, to: left) }
if let right { target.setSide(.right, to: right) }
selectedID = target.id
}
}