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:
@@ -92,18 +92,22 @@ enum LaunchPlan: Equatable, Sendable {
|
||||
///
|
||||
/// ### What the flag also switches off
|
||||
///
|
||||
/// **The registry moves into the scratch directory** with the board. Without that, every audit run
|
||||
/// would stamp a temp folder into the user's real recents list (`BoardRegistry.defaultStorageURL`,
|
||||
/// in Application Support), where it would sit for good as an unavailable row pointing at a
|
||||
/// directory that no longer exists. Tying it to the same flag rather than to a second argument is
|
||||
/// deliberate: the two are one decision — "this launch is synthetic" — and a second argument is a
|
||||
/// second chance to apply only half of it.
|
||||
/// **The registry and the clipboard's staging store move into the scratch directory** with the board.
|
||||
/// Without that, every audit run would stamp a temp folder into the user's real recents list
|
||||
/// (`BoardRegistry.defaultStorageURL`), where it would sit for good as an unavailable row pointing at a
|
||||
/// directory that no longer exists — and its launch sweep would collect the user's real staged copy
|
||||
/// (`ClipboardStore.defaultStagingRoot`). Both of those homes are now the **shared App Group
|
||||
/// container** (12-editions.md ▸ Distribution, ruled 2026-07-29), so each of those side effects would
|
||||
/// land on the sibling edition as well as this one. Tying them to the same flag rather than to separate
|
||||
/// arguments is deliberate: they are one decision — "this launch is synthetic" — and a second argument
|
||||
/// is a second chance to apply only half of it.
|
||||
///
|
||||
/// The honest residual: `UserDefaults` is **not** redirected, so an audit run can still write the
|
||||
/// three app-wide scalars (`AppPreferences`) into the real domain. They are a window size, a restore
|
||||
/// toggle this launch never consults, and the quick-style recents list — no documents, nothing
|
||||
/// destructive, and redirecting a defaults domain from inside the process is not something the
|
||||
/// platform actually supports. It is stated rather than fixed.
|
||||
/// three app-wide scalars (`AppPreferences`) into the real domain — the group's shared suite since the
|
||||
/// same ruling. They are a window size, a restore toggle this launch never consults, and the
|
||||
/// quick-style recents list — no documents, nothing destructive, and redirecting a defaults domain from
|
||||
/// inside the process is not something the platform actually supports. It is stated rather than
|
||||
/// fixed.
|
||||
enum UITestLaunch {
|
||||
|
||||
private static let logger = Logger(subsystem: "dev.rzen.indie.Kanban", category: "ui-test-launch")
|
||||
@@ -208,12 +212,21 @@ enum UITestLaunch {
|
||||
.appendingPathComponent("LaneworkUITestFixture", isDirectory: true)
|
||||
}
|
||||
|
||||
/// Where the fixture launch's registry lives — beside the board rather than in Application
|
||||
/// Support, which is the whole point (see the type's note).
|
||||
/// Where the fixture launch's registry lives — beside the board rather than in the shared App
|
||||
/// Group container, which is the whole point (see the type's note).
|
||||
static var registryStorageURL: URL {
|
||||
scratchRoot.appendingPathComponent("board-registry.json", isDirectory: false)
|
||||
}
|
||||
|
||||
/// Where the fixture launch's clipboard snapshots live, on the registry's terms and now for a
|
||||
/// sharper reason: the real staging root moved into the **shared** App Group container
|
||||
/// (12-editions.md ▸ Both editions installed), so an audit run's launch sweep would otherwise
|
||||
/// collect the developer's own staged copy — and the sibling edition's, since there is only one
|
||||
/// store now. Redirected by the same flag, because it is the same one decision.
|
||||
static var clipboardStagingRoot: URL {
|
||||
scratchRoot.appendingPathComponent("Clipboard", isDirectory: true)
|
||||
}
|
||||
|
||||
/// A fixture board's own folder. `.kanban`-suffixed because a board the app made through the
|
||||
/// ordinary create path is a document, and the audit should be looking at the shape a user's
|
||||
/// board actually has (01-storage-format.md § Document packaging).
|
||||
@@ -230,8 +243,10 @@ enum UITestLaunch {
|
||||
fixtureBoardURL(for: .standard)
|
||||
}
|
||||
|
||||
/// Wipes and recreates the scratch root, and answers the registry URL to build the app model
|
||||
/// with. Called once, from `KanbanApp.init()`, **before** the model reads its registry.
|
||||
/// Wipes and recreates the scratch root, and answers the registry URL for the caller's convenience.
|
||||
/// Called once, from `KanbanApp.init()`, **before** the model reads its registry — which is also why
|
||||
/// the return value is discardable: that caller now names both redirected homes explicitly
|
||||
/// (`registryStorageURL`, `clipboardStagingRoot`) rather than taking one of them from here.
|
||||
///
|
||||
/// **Wiped rather than reused**: every audit test launches its own app instance, and an audit is
|
||||
/// only meaningful against a board whose contents the test knows — a previous run's leftovers
|
||||
|
||||
Reference in New Issue
Block a user