The board chooses where it lives — swipe-open settings move it between iCloud and this iPhone

A trailing swipe on a board row opens Board Settings, whose first setting is location: iCloud or Local, with a confirmed move to the other side — destructive-styled only outbound, because leaving iCloud is the direction that sheds protection. The move is setUbiquitous against the real container and a coordinated move under the DEBUG stand-in; evacuation sweeps materialization first and refuses honestly while content is still downloading. The local home is the sandbox Documents folder, published to the Files app, so a local board is still a folder the user owns.

With a second home the iCloud wall softens (user-ruled 2026-08-08): the index always reaches ready, cloud unavailability becomes an inline notice with a retry, creates land locally when there is no account, and LANEWORK_FORCE_NO_ICLOUD makes that state reproducible in tests regardless of the machine's sign-in. Known gap, now user-reachable: backup remains iCloud-only, so local boards sit outside it.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-08-08 10:56:39 -04:00
parent 1d97a2931c
commit 1c16bb4c38
10 changed files with 910 additions and 134 deletions
+35 -3
View File
@@ -30,6 +30,32 @@ struct BoardSummary: Identifiable, Sendable, Equatable {
let modified: Date?
let download: BoardDownloadState
/// Which of the phone's two homes this board is in.
let location: BoardLocation
}
/// Where a board is stored, and therefore whether it syncs.
///
/// **Told, never sniffed.** A location is what the home an entry was enumerated from *is*, so the
/// index tags it at the point where that is a fact (`BoardIndexStore`, which holds both roots) and
/// everything downstream carries the tag. Deriving it from the URL instead would mean deciding
/// whether a path is inside a ubiquity container by looking at it a question that has no stable
/// answer across the real container, the DEBUG stand-in root and a test's scratch directories.
enum BoardLocation: Sendable, Equatable {
/// The `CloudHome` syncs to the Mac and to every other device on the account.
case icloud
/// The `DeviceHomeResolver` home this phone, and nowhere else.
case local
/// The one word the list marker, the settings sheet and its move button all name it by.
var name: String {
switch self {
case .icloud: "iCloud"
case .local: "Local"
}
}
}
/// Whether a board's bytes are on this device the summary-level reading, from the metadata item's
@@ -69,6 +95,9 @@ struct BoardIndexEntry: Sendable, Equatable {
let rootURL: URL
let modified: Date?
let download: BoardDownloadState
/// Set by whoever produced the entry, from the home it enumerated see `BoardLocation`.
let location: BoardLocation
}
/// Turns an index entry into a summary by looking, shallowly, at the package.
@@ -106,7 +135,8 @@ enum BoardSummaryScanner {
laneCount: nil,
cardCount: nil,
modified: entry.modified,
download: entry.download
download: entry.download,
location: entry.location
)
}
@@ -123,7 +153,8 @@ enum BoardSummaryScanner {
laneCount: nil,
cardCount: nil,
modified: entry.modified,
download: entry.download
download: entry.download,
location: entry.location
)
}
@@ -140,7 +171,8 @@ enum BoardSummaryScanner {
laneCount: lanes,
cardCount: cards,
modified: entry.modified,
download: entry.download
download: entry.download,
location: entry.location
)
}