Guard reconcile reentrancy and deleted-routine duplication; cache exercise info parsing

This commit is contained in:
2026-07-16 19:54:57 -04:00
parent fe4a7a3ca1
commit 9968cb2056
2 changed files with 26 additions and 6 deletions
+21 -5
View File
@@ -215,12 +215,28 @@ extension ExerciseInfo {
/// Finds and parses the bundled `<Exercise Name>.info.md` exported next to the
/// motion rigs by `render.py --export`.
enum ExerciseInfoLibrary {
/// Every bundled `info.md`, parsed once and keyed by exercise name. `info(for:)` is
/// fanned out per exercise per keystroke by `ExerciseCatalog.sections(matching:)`, so
/// re-doing the `Bundle.main` lookup, file read, and markdown parse on every call was
/// wasteful; ~60-odd files parsed once is trivial. Enumerates the bundle directly
/// (mirroring `ExerciseMotionLibrary.exerciseNames`'s compound-suffix filter) rather
/// than keying off that list, so info lookup doesn't depend on a motion rig existing.
private static let cache: [String: ExerciseInfo] = {
let urls = Bundle.main.urls(forResourcesWithExtension: "md", subdirectory: nil) ?? []
var result: [String: ExerciseInfo] = [:]
for url in urls {
let filename = url.lastPathComponent
guard filename.hasSuffix(".info.md"),
let markdown = try? String(contentsOf: url, encoding: .utf8)
else { continue }
let exerciseName = String(filename.dropLast(".info.md".count))
result[exerciseName] = ExerciseInfo.parse(markdown: markdown)
}
return result
}()
/// The parsed info for `exerciseName`, or `nil` when none is bundled.
static func info(for exerciseName: String) -> ExerciseInfo? {
guard
let url = Bundle.main.url(forResource: exerciseName, withExtension: "info.md"),
let markdown = try? String(contentsOf: url, encoding: .utf8)
else { return nil }
return ExerciseInfo.parse(markdown: markdown)
cache[exerciseName]
}
}
+5 -1
View File
@@ -407,7 +407,7 @@ final class SyncEngine {
/// when the engine isn't connected.
@discardableResult
func duplicate(routine: Routine) async -> String? {
guard store != nil else { return nil }
guard store != nil, routine.isLive else { return nil }
var doc = RoutineDocument(from: routine)
doc.id = ULID.make()
@@ -767,6 +767,10 @@ final class SyncEngine {
/// changes accumulated while the app was closed are picked up.
private func reconcile() async {
guard let store, let tombstones else { return }
// Drop a reconcile that overlaps a running one redundant, since the
// running pass covers the same tree and the metadata observer picks up
// anything newer.
guard !isSyncing else { return }
isSyncing = true
defer { isSyncing = false }