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
+5 -26
View File
@@ -40,18 +40,10 @@ struct WelcomeRow: Identifiable, Equatable {
let icon: String?
let iconColor: String?
/// Where the board is **now**, or `nil` when this edition cannot reach it. The single source
/// Where the board is **now**, or `nil` when its bookmark no longer resolves. The single source
/// of the row's availability: Open and Reveal need a URL, and an orphan has none.
let url: URL?
/// Where the re-grant panel starts for a row whose only grant another edition minted
/// (`RecentBoard.needsReopen`; 12-editions.md Distribution) `nil` on every other row.
///
/// It is what makes this row's Open live while `url` is `nil`: the board is *there*, and one click
/// plus Grant is all that stands between the user and it. Reveal in Finder stays disabled, because
/// revealing a folder is a read this app has not been granted either.
let regrantAnchor: URL?
/// The containing folder, for the row's location line Xcode's welcome shows where a project
/// lives, not its own path repeated under its name. Home-abbreviated where it can be.
let location: String
@@ -66,14 +58,10 @@ struct WelcomeRow: Identifiable, Equatable {
var isAvailable: Bool { url != nil }
/// Whether this row is waiting for this edition's grant rather than being genuinely orphaned.
var needsReopen: Bool { regrantAnchor != nil }
/// Open needs somewhere to go **or something to grant**; Reveal in Finder needs the former only.
/// Forget is deliberately not here, because it is enabled on every row an orphan the user can
/// never open is exactly the row that most needs erasing (02 § Graceful orphaning: "recents
/// surface it as unavailable with Forget").
var canOpen: Bool { isAvailable || needsReopen }
/// Open and Reveal in Finder both need somewhere to go; Forget is deliberately not here, because
/// it is enabled on every row an orphan the user can never open is exactly the row that most
/// needs erasing (02 § Graceful orphaning: "recents surface it as unavailable with Forget").
var canOpen: Bool { isAvailable }
var canReveal: Bool { isAvailable }
/// The row's third line one line, so the three states are alternatives rather than a stack.
@@ -86,20 +74,12 @@ struct WelcomeRow: Identifiable, Equatable {
case counts(lanes: Int?, cards: Int?)
/// The bookmark no longer resolves (02 § Graceful orphaning).
case unavailable
/// This edition has never been granted the board another edition minted the record for
/// (12-editions.md Distribution) the *reopen* state, which is not orphaning: the board is
/// there, and one click opens the panel that grants it.
case needsReopen
/// Fail-fast's specifics, from the open or restore that failed.
case failed(String)
}
var caption: Caption {
if let failure { return .failed(failure) }
// Before `unavailable`, because the two are told apart by *why* there is no URL and this one
// is the reachable case: an orphan's caption offering to grant access would be a promise the
// app cannot keep, and this row wearing the orphan's caption would read as a loss it is not.
if needsReopen { return .needsReopen }
if url == nil { return .unavailable }
return .counts(lanes: laneCount, cards: cardCount)
}
@@ -163,7 +143,6 @@ struct WelcomeRow: Identifiable, Equatable {
icon: record.icon,
iconColor: record.iconColor,
url: recent.url,
regrantAnchor: recent.regrantAnchor,
location: location(of: recent.url?.path ?? record.lastKnownPath),
laneCount: record.laneCount,
cardCount: record.cardCount,