Files
workouts/Workouts/Seed/SplitSeeder.swift
T
rzen 936a585ece Seed starter splits deterministically with immutable clone-on-edit seeds
Starter splits ship as byte-canonical SplitDocument JSON with fixed
ULIDs (Workouts/Resources/StarterSplits, regenerated by
Scripts/generate_starter_splits.swift) and auto-seed after connect into
a verifiably empty container, re-checked after a settle delay — wrong
guesses are harmless because identical bytes make same-path conflicts
empty and tombstones reap resurrected seeds. Seeds are immutable:
SyncEngine.save(split:) forks an edited seed to a fresh ULID and
soft-deletes the original, whose stub is exempt from pruning
(IndieSync 0.3.0 prune(exempting:)) and vetoes resurrection forever;
split views resolve by id through a redirect map to follow the swap.
Add Starter Splits in Settings restores deleted seeds by lifting the
veto stub and rewriting the bundle bytes.

Also fixes ingestFromWatch bypassing the tombstone veto (a phone-deleted
workout resurrected when a stale watch resent it) and reaps a live file
immediately when its tombstone arrives.

SplitDetailView also picks up the category-grouped exercise sections
from the exercise-category work.
2026-07-06 01:16:05 -04:00

33 lines
1.6 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Foundation
import SwiftData
/// The bundled immutable starter-split library, brought back on demand. The seeds
/// themselves — fixed ULIDs, byte-canonical bundle JSON — live in `SeedLibrary`;
/// editing one clones it and permanently tombstones the seed (see
/// `SyncEngine.save(split:)`). The true first-run case is handled automatically by
/// `SyncEngine.autoSeedIfEmpty`; this on-demand path (the "Add Starter Splits"
/// button) is for a user who wants the starters back after removing some.
enum SplitSeeder {
/// Add every starter split the user doesn't already have. For each seed:
/// • skip if a live split with the same NAME exists — a user's edited clone of
/// "Upper Body" must not be joined by a resurrected seed of the same name;
/// • else if the seed was deleted (tombstoned), restore it (lifting the veto);
/// • else write it fresh.
/// Seed `order` values are fixed (03); a collision with a user split's order is
/// an accepted cosmetic tie. Idempotent against double-taps and partial prior seeds.
@MainActor
static func seedDefaults(into context: ModelContext, using sync: SyncEngine) async {
let existing = (try? context.fetch(FetchDescriptor<Split>())) ?? []
let existingNames = Set(existing.map(\.name))
for seed in SeedLibrary.seeds {
if existingNames.contains(seed.doc.name) { continue }
if await sync.isTombstoned(id: seed.id) {
await sync.restoreSeed(seed)
} else {
await sync.writeSeed(seed)
}
}
}
}