Ports QuickRabbit's resolveConflictsIfAny (R4.3) into the eviction-safe read
path: before every coordinated read, pick the NSFileVersion with the latest
modification date, promote it to the canonical file inside a coordinated
write, mark all conflicts resolved, and prune the losers. Without this,
conflict siblings from two-device edits (workout logs edited mid-session are
the risky case) accumulate silently and a read returns whichever version the
filesystem hands back.
Claude-Session: https://claude.ai/code/session_01SY5jsAUf4qoPxSvv8xAqXS
Wires the previously-dead ensureDownloaded into every read (it now polls,
bounded 30s, instead of fire-and-forget) and fixes the deeper hole: the
directory listing skipped hidden files, so evicted records — whose only local
trace is a hidden .<name>.json.icloud placeholder — never appeared in
reconcile at all and their cache entities were pruned as deleted.
Also: reconcile no longer prunes entities whose file failed to read this pass
(an eviction-download timeout is not a deletion); tombstone IDs come from stub
filenames so an evicted stub still vetoes resurrection; the stale-file stub
check is placeholder-aware; read failures log loudly instead of try?-skipping.
Verified with a harness compiling the real ICloudFileManager against a local
temp directory (placeholder mapping, dedup, tombstone derivation, soft-delete
round trip). Real eviction/download behavior needs a device pass.
Claude-Session: https://claude.ai/code/session_01SY5jsAUf4qoPxSvv8xAqXS
Stop giving up on a slow iCloud container: a freshly enabled iCloud Drive
that reports nil while still provisioning is now polled patiently for up
to ~10 minutes instead of failing fast to the "unavailable" screen. A nil
ubiquity token (not signed into iCloud at all) still fails immediately,
and the connecting screen reveals an escape hatch at 28s for users who'd
rather jump to setup than keep waiting.
The connecting and iCloud-required screens are now branded — a purple
gradient with teal accents — and the spinner becomes a custom comet-arc
ConnectingIndicator around an iCloud glyph. Connecting copy escalates
with the wait so a slow first connect reads as steady progress.
Claude-Session: https://claude.ai/code/session_01SCv7zvGFcKy47KSTnTLxRe
ingestFromWatch now upserts the SwiftData cache directly after writing the file,
instead of relying on the NSMetadataQuery observer — a same-process file
overwrite doesn't reliably emit a modified event, so watch progress never reached
open iPhone screens. iCloud Drive stays the source of truth (file written first);
the observer re-applies idempotently if it fires.
Claude-Session: https://claude.ai/code/session_018gg69MaUetDNzWzBXisfMV
Replace Core Data + NSPersistentCloudKitContainer + App-Group store +
WatchConnectivity dictionary sync with the QuickRabbit iCloud-documents
architecture:
- iCloud Drive JSON documents are the sole source of truth (one file per
aggregate: Splits/<ULID>.json, Workouts/YYYY/MM/<ULID>.json), with a
rebuildable SwiftData cache populated only by an NSMetadataQuery observer
and a connect-time reconcile. Soft-delete tombstones; hard iCloud gate.
- Shared model layer (ULID, Codable *Documents + stateless mappers, @Model
cache entities, SwiftData container) compiled into both targets.
- New iPhone<->Watch bridge over WatchConnectivity keyed by ULIDs; the phone
is the sole writer of iCloud Drive, the watch round-trips documents.
- AppServices DI + iCloud-required root gate; Swift 6 strict concurrency.
- Starter splits generated on demand from the bundled YAML catalogs.
- Migrate to XcodeGen (project.yml), iOS 26 / watchOS 26; CloudDocuments
entitlement (drop CloudKit/App Group/aps-environment).
- Duration stored as Int seconds (was a Date epoch hack); fix workout
end-on-create, undismissable delete dialog, toolbar-hiding nav stacks,
and the Settings placeholder.
- Add README/CHANGELOG/LICENSE, .gitignore, refreshed REQUIREMENTS, and the
Scripts/ TestFlight pipeline (release.sh + ASC API scripts).
MARKETING_VERSION 2.0.