Home app-side state in the shared App Group container
Every edition declares group.dev.rzen.indie.Kanban and homes its
app-side state there from day one (12-editions.md ruling 2026-07-29):
- AppGroup namespace: container resolution with per-edition fallback
when unprovisioned, shared UserDefaults suite, edition identity, and
a unit-test-host redirect (the test host IS the app — its launch
sweep and recents refresh must not touch the real shared container).
- BoardRecord: bookmark/isOpenNow replaced by per-edition grants and
openNow keyed by bundle id; hand-written Codable keeps legacy keys
decoding (adopted in memory as the running edition's slots, upgraded
on first save); every other field stays common.
- RecentBoard gains needsReopen: no grant of ours but somebody's —
first click runs an open panel pre-anchored at the recorded path,
prompt "Grant"; recordOpen mints this edition's slot onto the
matched shared record (path fallback only after identity fails and
only against records holding no grant of ours, so re-granting never
forks the record).
- Cross-edition freshness: stat-cheap mtime+size stamp re-reads the
registry when the sibling edition wrote it, so one edition's save
never erases the other's records wholesale.
- restorables() filters on this edition's open-now flags; the board
popover gains BoardEditionPresence ("Also open in Lanework Pro"),
pid-liveness-checked so crash residue never lies.
- Clipboard staging store moves to the group container; the sweep
claims doomed trees by atomic rename into .sweeping/ then deletes,
so the sibling's concurrent sweep is a non-event.
- Template store re-homed to the group container per the 09-templates
re-ruling; scalars (quick-style recents, window size) move to the
shared suite.
- verify-editions.sh: 30 checks (each edition carries exactly the
family group). No pathfinder 1.x migrator: 1.x predates the
registry; state starts fresh in the group container.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -91,7 +91,7 @@ struct BoardRegistryTests {
|
||||
|
||||
let id = registry.recordOpen(of: fixture.root, displayName: "Todo Board")
|
||||
let created = try #require(registry.record(id: id))
|
||||
#expect(!created.bookmark.isEmpty)
|
||||
#expect(created.grant(forEdition: registry.editionID) != nil)
|
||||
#expect(created.displayName == "Todo Board")
|
||||
#expect(created.lastKnownPath == fixture.root.path)
|
||||
#expect(created.laneCount == nil, "counts are stamped at close, never guessed at open")
|
||||
@@ -135,7 +135,7 @@ struct BoardRegistryTests {
|
||||
let id = registry.recordOpen(of: fixture.root)
|
||||
let record = try #require(registry.record(id: id))
|
||||
#expect(record.displayName == fixture.root.deletingPathExtension().lastPathComponent)
|
||||
#expect(!record.bookmark.isEmpty, "the bookmark still mints on the before-load call")
|
||||
#expect(record.grant(forEdition: registry.editionID) != nil, "the bookmark still mints on the before-load call")
|
||||
#expect(record.icon == nil)
|
||||
#expect(record.iconColor == nil)
|
||||
}
|
||||
@@ -163,7 +163,7 @@ struct BoardRegistryTests {
|
||||
#expect(record.icon == "star")
|
||||
#expect(record.iconColor == "fern")
|
||||
#expect(record.lastOpened > firstOpened, "the bookmark and lastOpened still refresh on every open attempt")
|
||||
#expect(!record.bookmark.isEmpty)
|
||||
#expect(record.grant(forEdition: registry.editionID) != nil)
|
||||
}
|
||||
|
||||
// MARK: Counts
|
||||
@@ -620,16 +620,16 @@ struct BoardRegistryTests {
|
||||
let registry = BoardRegistry(storageURL: storage.url)
|
||||
|
||||
let id = registry.recordOpen(of: fixture.root, displayName: "Work")
|
||||
#expect(registry.record(id: id)?.isOpenNow == nil, "recording an open is not opening a window")
|
||||
#expect(registry.record(id: id)?.isOpen(inEdition: registry.editionID) == false, "recording an open is not opening a window")
|
||||
#expect(registry.restorables().isEmpty)
|
||||
|
||||
registry.setOpenNow(id: id)
|
||||
#expect(registry.record(id: id)?.isOpenNow == true)
|
||||
#expect(registry.record(id: id)?.isOpen(inEdition: registry.editionID) == true)
|
||||
#expect(ids(registry.restorables()) == [id])
|
||||
|
||||
// A user close. The flag goes, and with it the board's place in the next launch.
|
||||
registry.clearOpenNow(id: id)
|
||||
#expect(registry.record(id: id)?.isOpenNow == false)
|
||||
#expect(registry.record(id: id)?.isOpen(inEdition: registry.editionID) == false)
|
||||
#expect(registry.restorables().isEmpty)
|
||||
|
||||
// A quit. The teardown stamps counts and does *not* clear the flag — that omission is the
|
||||
@@ -639,7 +639,7 @@ struct BoardRegistryTests {
|
||||
registry.recordClose(id: id, displayName: "Work", laneCount: 2, cardCount: 5)
|
||||
|
||||
let afterRelaunch = BoardRegistry(storageURL: storage.url)
|
||||
#expect(afterRelaunch.record(id: id)?.isOpenNow == true, "the flags describe what was open at quit")
|
||||
#expect(afterRelaunch.record(id: id)?.isOpen(inEdition: registry.editionID) == true, "the flags describe what was open at quit")
|
||||
#expect(ids(afterRelaunch.restorables()) == [id])
|
||||
#expect(afterRelaunch.record(id: id)?.laneCount == 2)
|
||||
}
|
||||
@@ -712,13 +712,13 @@ struct BoardRegistryTests {
|
||||
|
||||
let registry = BoardRegistry(storageURL: storage.url)
|
||||
#expect(registry.recents().count == 1, "the file decoded; nothing was quarantined")
|
||||
#expect(registry.record(id: id)?.isOpenNow == nil, "a missing key reads as 'not open'")
|
||||
#expect(registry.record(id: id)?.isOpen(inEdition: registry.editionID) == false, "a missing key reads as 'not open'")
|
||||
#expect(registry.restorables().isEmpty)
|
||||
#expect(registry.record(id: id)?.laneCount == 4, "and every other field survived")
|
||||
|
||||
// And the key writes through from here on.
|
||||
registry.setOpenNow(id: id)
|
||||
#expect(BoardRegistry(storageURL: storage.url).record(id: id)?.isOpenNow == true)
|
||||
#expect(BoardRegistry(storageURL: storage.url).record(id: id)?.isOpen(inEdition: registry.editionID) == true)
|
||||
}
|
||||
|
||||
// MARK: Bookmarks in a sandboxed host
|
||||
|
||||
Reference in New Issue
Block a user