Remove the App Group wholesale — one sandbox, one bookmark, one flag

Phase 2 of the one-app pivot (DESIGN 12 ▸ App-side state, re-ruled
2026-07-30; reworks 566deab). AppGroup retires; what remains is
AppStateHome — ordinary sandbox Application Support as the one home for
the registry, clipboard staging and template stores, keeping the
unit-test-host redirect (the test host is the app and would sweep real
state). Scalar defaults return to UserDefaults.standard.

BoardRecord's per-edition grant slots and openNow flags collapse to one
bookmark + one isOpenNow; the legacy-key decode and adopt-in-memory
paths go (nothing shipped with group-era records), while the founding
four-keys-required / defaults-for-everything-since decode policy stays —
a bookmarkless record decodes as the born-orphan row rather than
quarantining the list. needsReopen and the pre-anchored re-grant panel
are removed whole: the only state that flow served — a record granted by
a sibling sandbox — is unrepresentable now, and a dead bookmark of our
own was already the orphan case by explicit comment. The
indexOfRecord path fallback dies with it; path is never a key again.

The cross-process freshness stamp (mtime+size re-read) and
BoardEditionPresence with its popover "Also open in…" line retire; the
clipboard prune keeps its atomic .sweeping/ claim-then-delete, reframed
for crash residue and open -n copies rather than sibling editions. The
application-groups entitlement key is gone.

1880 tests in 317 suites green (13 cross-edition tests retired with
their subject).

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-07-30 17:46:32 -04:00
parent 092300c7d2
commit 2c6b8fe63a
27 changed files with 385 additions and 1461 deletions
+45 -9
View File
@@ -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.grant(forEdition: registry.editionID) != nil)
#expect(!created.bookmark.isEmpty)
#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.grant(forEdition: registry.editionID) != nil, "the bookmark still mints on the before-load call")
#expect(!record.bookmark.isEmpty, "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.grant(forEdition: registry.editionID) != nil)
#expect(!record.bookmark.isEmpty)
}
// MARK: Counts
@@ -314,6 +314,42 @@ struct BoardRegistryTests {
#expect(updated?.iconColor == "aluminum")
}
@Test("A record with no bookmark key at all is a born orphan, not a quarantine")
func aRecordWithNoBookmarkKeyDecodesAsAnOrphan() async throws {
let storage = try RegistryStorage()
defer { storage.tearDown() }
// `bookmark` carries a decoding default like every key past the four founding ones, and this
// is why: a record the system refused to mint a key for is a recents row with Forget the
// born-orphaned case `recordOpen` already writes and quarantining the user's whole list
// over one keyless entry would be the cure being worse than the disease.
let id = UUID()
let json = """
[
{
"displayName" : "Ghost",
"id" : "\(id.uuidString)",
"lastKnownPath" : "/Volumes/Gone/Ghost.kanban",
"lastOpened" : "2026-01-01T09:00:00.000Z"
}
]
"""
try Data(json.utf8).write(to: storage.url)
let registry = BoardRegistry(storageURL: storage.url)
let rows = registry.recents()
#expect(rows.count == 1, "the file decoded; nothing was quarantined")
guard case .unavailable = rows[0] else {
Issue.record("expected unavailable, got \(rows[0])")
return
}
#expect(registry.record(id: id)?.bookmark.isEmpty == true)
// The other defaulted keys came through as their defaults too, rather than as a throw.
#expect(registry.record(id: id)?.isOpenNow == false)
#expect(registry.record(id: id)?.pushOnCommit == false)
#expect(registry.record(id: id)?.remoteLocationWarned == false)
}
@Test("A registry file with icon and iconColor present decodes them")
func registryFileWithIconKeysDecodes() async throws {
let storage = try RegistryStorage()
@@ -620,16 +656,16 @@ struct BoardRegistryTests {
let registry = BoardRegistry(storageURL: storage.url)
let id = registry.recordOpen(of: fixture.root, displayName: "Work")
#expect(registry.record(id: id)?.isOpen(inEdition: registry.editionID) == false, "recording an open is not opening a window")
#expect(registry.record(id: id)?.isOpenNow == false, "recording an open is not opening a window")
#expect(registry.restorables().isEmpty)
registry.setOpenNow(id: id)
#expect(registry.record(id: id)?.isOpen(inEdition: registry.editionID) == true)
#expect(registry.record(id: id)?.isOpenNow == 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)?.isOpen(inEdition: registry.editionID) == false)
#expect(registry.record(id: id)?.isOpenNow == false)
#expect(registry.restorables().isEmpty)
// A quit. The teardown stamps counts and does *not* clear the flag that omission is the
@@ -639,7 +675,7 @@ struct BoardRegistryTests {
registry.recordClose(id: id, displayName: "Work", laneCount: 2, cardCount: 5)
let afterRelaunch = BoardRegistry(storageURL: storage.url)
#expect(afterRelaunch.record(id: id)?.isOpen(inEdition: registry.editionID) == true, "the flags describe what was open at quit")
#expect(afterRelaunch.record(id: id)?.isOpenNow == true, "the flags describe what was open at quit")
#expect(ids(afterRelaunch.restorables()) == [id])
#expect(afterRelaunch.record(id: id)?.laneCount == 2)
}
@@ -712,13 +748,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)?.isOpen(inEdition: registry.editionID) == false, "a missing key reads as 'not open'")
#expect(registry.record(id: id)?.isOpenNow == 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)?.isOpen(inEdition: registry.editionID) == true)
#expect(BoardRegistry(storageURL: storage.url).record(id: id)?.isOpenNow == true)
}
// MARK: Bookmarks in a sandboxed host