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:
@@ -33,14 +33,19 @@ private func makeMixedBoard() throws -> WriterFixture {
|
||||
return fixture
|
||||
}
|
||||
|
||||
/// An `AppModel` whose registry file lives in temp rather than in the test host's real Application
|
||||
/// Support directory.
|
||||
/// An `AppModel` whose app-side state lives in temp rather than in the shared App Group container —
|
||||
/// both halves of it: the registry file, and the clipboard's staging store, whose launch sweep would
|
||||
/// otherwise collect the developer's own staged copy (and the sibling edition's, since there is one
|
||||
/// store now).
|
||||
@MainActor
|
||||
private func makeModel() throws -> (model: AppModel, tearDown: () -> Void) {
|
||||
let folder = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent("AppModelTests-\(UUID().uuidString)", isDirectory: true)
|
||||
try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true)
|
||||
let model = AppModel(registryStorageURL: folder.appendingPathComponent("board-registry.json"))
|
||||
let model = AppModel(
|
||||
registryStorageURL: folder.appendingPathComponent("board-registry.json"),
|
||||
clipboardStagingRoot: folder.appendingPathComponent("Clipboard", isDirectory: true)
|
||||
)
|
||||
return (model, { try? FileManager.default.removeItem(at: folder) })
|
||||
}
|
||||
|
||||
@@ -214,13 +219,13 @@ struct AppModelTests {
|
||||
let record = try #require(model.boardRegistry.record(id: recordID))
|
||||
#expect(record.laneCount == 2, "the counts the welcome row will show are the working ones")
|
||||
#expect(record.cardCount == 2)
|
||||
#expect(record.isOpenNow == false)
|
||||
#expect(!record.isOpen(inEdition: model.boardRegistry.editionID))
|
||||
#expect(model.boardRegistry.restorables().isEmpty)
|
||||
|
||||
// Twice is a no-op, which is what lets the window's close interception and its disappear both
|
||||
// call this without the sequence running twice.
|
||||
await model.closeBoard(ref: ref, cause: .userClose)
|
||||
#expect(model.boardRegistry.record(id: recordID)?.isOpenNow == false)
|
||||
#expect(model.boardRegistry.record(id: recordID)?.isOpen(inEdition: model.boardRegistry.editionID) == false)
|
||||
}
|
||||
|
||||
@Test("Quit closes every board and leaves them all flagged for the next launch")
|
||||
|
||||
Reference in New Issue
Block a user