Treat order as presentation in seed logic; add per-seed restore and routine duplication
Drag-to-reorder writes Routine.order through the document, which used to read as a content edit: isPristine would fork a starter for a mere reorder, and reconcile's semantic compare would clobber a reordered seed file back to bundle order. Both now normalize order (and updatedAt) away — a fixed- ULID file still never holds user content; ordering is bookkeeping. SyncEngine gains restoreSeed(id:) — the per-seed analog of the bulk restore, sharing one restoreSeedIfEligible core — and duplicate(routine:), which copies any routine (starter or not) to a fresh ULID with fresh exercise ids, a unique "… Copy" name, and last position in the list. Claude-Session: https://claude.ai/code/session_01H8VxUX4ckjU3vRF5M4L5FV
This commit is contained in:
@@ -93,6 +93,36 @@ struct SeedLibraryTests {
|
||||
#expect(!SeedLibrary.isPristine(edited))
|
||||
}
|
||||
|
||||
/// Dragging a starter to reorder the Library list writes a new `order` to it. Order
|
||||
/// is presentation, not content, so a reorder must NOT fork the seed — an order-only
|
||||
/// change reads as pristine.
|
||||
@Test func pristineIgnoresOrderOnlyChange() throws {
|
||||
let seed = try #require(SeedLibrary.seeds.first)
|
||||
var edited = seed.doc
|
||||
edited.order += 5
|
||||
#expect(SeedLibrary.isPristine(edited))
|
||||
}
|
||||
|
||||
/// A reorder save also stamps `updatedAt`; order + updatedAt together must still read
|
||||
/// as pristine so a drag never forks a starter.
|
||||
@Test func pristineIgnoresOrderAndUpdatedAtChange() throws {
|
||||
let seed = try #require(SeedLibrary.seeds.first)
|
||||
var edited = seed.doc
|
||||
edited.order += 3
|
||||
edited.updatedAt = Date()
|
||||
#expect(SeedLibrary.isPristine(edited))
|
||||
}
|
||||
|
||||
/// Order tolerance must not mask a genuine content edit: a real change (here, a
|
||||
/// different rest length) is still detected even when `order` also differs.
|
||||
@Test func pristineDetectsContentChangeDespiteOrderChange() throws {
|
||||
let seed = try #require(SeedLibrary.seeds.first)
|
||||
var edited = seed.doc
|
||||
edited.order += 2
|
||||
edited.restSeconds = (seed.doc.restSeconds ?? 60) + 30
|
||||
#expect(!SeedLibrary.isPristine(edited))
|
||||
}
|
||||
|
||||
/// A non-seed id has no seed to compare against; `isPristine` defaults to
|
||||
/// true since callers are expected to gate on `isSeed` before asking.
|
||||
@Test func pristineDefaultsTrueForNonSeedID() throws {
|
||||
|
||||
Reference in New Issue
Block a user