Build BoardStore — one-way flow and resilient reloads
Per-board @Observable MainActor hub (Kanban/LiveStore/): watcher signals drive off-main tree walks with a generation guard, single-flight coalescing (strongest pending origin, watcher's merge rule), and the resilience contract — a failed reload never replaces a good snapshot, per-file breakage never locks editing, and a wholesale operation (performWholesale) arms a reload-must-succeed-or-lock floor so a failed post-bracket reload flips the board read-only until a good reload heals it. Selection is a pure UUID-set value re-resolved on every swap; liveness flips eject. performWrite brackets the watcher so Writer round-trips come back app-mediated. 14 store tests; full suite 279 tests in 54 suites green. Three findings filed on the Redesign board. Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
This commit is contained in:
@@ -28,7 +28,7 @@ public enum WatchOrigin: String, Sendable, Equatable {
|
||||
/// reload has to cover" — a reconciling reload assumes nothing about the tree, an
|
||||
/// app-mediated one is the tail of an operation the app ran, a foreign one is an observed
|
||||
/// external edit.
|
||||
fileprivate var precedence: Int {
|
||||
var precedence: Int {
|
||||
switch self {
|
||||
case .foreign: 0
|
||||
case .appMediated: 1
|
||||
@@ -41,7 +41,12 @@ public enum WatchOrigin: String, Sendable, Equatable {
|
||||
/// This is the *never downgrade* rule — a foreign event landing on a pending app-mediated or
|
||||
/// reconciling delivery does not weaken it. See `FolderWatcher.schedule(_:)` for why the
|
||||
/// resulting blur is accepted rather than engineered away.
|
||||
fileprivate static func merged(_ existing: WatchOrigin?, _ incoming: WatchOrigin) -> WatchOrigin {
|
||||
///
|
||||
/// Internal rather than `fileprivate`: `BoardStore` coalesces signals that arrive while a reload
|
||||
/// is already running and owes the same never-downgrade guarantee on its side of the handoff.
|
||||
/// Two coalescing points, one rule — the watcher's debounce and the store's pending-reload flag
|
||||
/// must never disagree about which origin a merged span carries.
|
||||
static func merged(_ existing: WatchOrigin?, _ incoming: WatchOrigin) -> WatchOrigin {
|
||||
guard let existing else { return incoming }
|
||||
return existing.precedence >= incoming.precedence ? existing : incoming
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user