Give the registry live display-state write-through and record-before-load
BoardRecord carries icon/iconColor; recordOpen/recordClose stamp them with the display name, and a displayStateDelegate on the store (wired in BoardWindowHost beside onFrameChanged) syncs all three through BoardRegistry.syncDisplayState on every successful reload — welcome rows now wear the board's own icon and follow in-app renames live. recordOpen now runs before the load with the folder name as a brand-new record's provisional display name, so a first open that fails fail-fast still lands in recents carrying the failure row-level (02's rule); an existing record's cached name survives a failing retry, and the welcome fallback list remains only for failures naming no record at all. Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
@@ -245,6 +245,56 @@ struct BoardStoreTests {
|
||||
#expect(fixture.exists("\(Ident.lane2)/\(created.rawValue)/index.md"))
|
||||
}
|
||||
|
||||
// MARK: Display-state write-through
|
||||
|
||||
@Test("A successful reload calls the display-state delegate, whether or not anything actually changed")
|
||||
func successfulReloadCallsDisplayStateDelegate() async throws {
|
||||
let fixture = try makeBoard()
|
||||
defer { fixture.tearDown() }
|
||||
let store = try BoardStore(rootURL: fixture.root)
|
||||
|
||||
var calls = 0
|
||||
store.displayStateDelegate = { calls += 1 }
|
||||
|
||||
// An ordinary foreign change with no board-level field touched at all — the "did title,
|
||||
// icon, or iconColor actually change" comparison is the registry's own no-op guard
|
||||
// (`BoardRegistry.syncDisplayState`), not something this store decides by diffing itself.
|
||||
try fixture.item("\(Ident.lane1)/\(Ident.card3)", Item.rich(order: "3072", title: "Third"))
|
||||
store.handleWatcherEvent(.treeChanged(.foreign))
|
||||
await store.awaitQuiescence()
|
||||
|
||||
#expect(calls == 1)
|
||||
}
|
||||
|
||||
@Test("A failed reload does not call the display-state delegate — there is no new snapshot to report")
|
||||
func failedReloadDoesNotCallDisplayStateDelegate() async throws {
|
||||
let fixture = try makeBoard()
|
||||
defer { fixture.tearDown() }
|
||||
let store = try BoardStore(rootURL: fixture.root)
|
||||
|
||||
var calls = 0
|
||||
store.displayStateDelegate = { calls += 1 }
|
||||
|
||||
try fixture.item("\(Ident.lane1)/\(Ident.card1)", brokenIndex)
|
||||
store.handleWatcherEvent(.treeChanged(.foreign))
|
||||
await store.awaitQuiescence()
|
||||
|
||||
#expect(calls == 0)
|
||||
}
|
||||
|
||||
@Test("A reload with no display-state delegate wired is harmless")
|
||||
func reloadWithoutADisplayStateDelegateIsANoOp() async throws {
|
||||
let fixture = try makeBoard()
|
||||
defer { fixture.tearDown() }
|
||||
let store = try BoardStore(rootURL: fixture.root)
|
||||
|
||||
try fixture.item("\(Ident.lane1)/\(Ident.card3)", Item.rich(order: "3072", title: "Third"))
|
||||
store.handleWatcherEvent(.treeChanged(.foreign))
|
||||
await store.awaitQuiescence()
|
||||
|
||||
#expect(cardTitles(inLane: Ident.lane1, of: store.snapshot) == ["First", "Second", "Third"])
|
||||
}
|
||||
|
||||
// MARK: Wholesale operations
|
||||
|
||||
@Test("A wholesale operation whose reload succeeds leaves no lock and no leaked expectation")
|
||||
|
||||
Reference in New Issue
Block a user