Fix textbook section missing on installs that predate bundled JSON

Earlier launches (before cd491bd bundled textbook_data.json) ran
seedTextbookData, got "not bundled — skipping", and still bumped
UserDefaults textbookDataVersion to 9. Subsequent launches then
short-circuited in refreshTextbookDataIfNeeded and never re-seeded,
so ZTEXTBOOKCHAPTER stayed empty and the Course tab hid the section.

- seedTextbookData now returns Bool (true only when chapters inserted).
- Both call sites only write the version key on success, so a missing
  or unparseable bundle no longer locks us out of future retries.
- Bumped textbookDataVersion to 10 to force existing installs to
  re-seed on next launch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-04-21 09:39:47 -05:00
parent 5f90a01314
commit 3c5600f562

View File

@@ -6,7 +6,7 @@ actor DataLoader {
static let courseDataVersion = 7 static let courseDataVersion = 7
static let courseDataKey = "courseDataVersion" static let courseDataKey = "courseDataVersion"
static let textbookDataVersion = 9 static let textbookDataVersion = 10
static let textbookDataKey = "textbookDataVersion" static let textbookDataKey = "textbookDataVersion"
/// Quick check: does the DB need seeding or course data refresh? /// Quick check: does the DB need seeding or course data refresh?
@@ -140,9 +140,12 @@ actor DataLoader {
// Seed course data (uses the same mainContext so @Query sees it) // Seed course data (uses the same mainContext so @Query sees it)
seedCourseData(context: context) seedCourseData(context: context)
// Seed textbook data // Seed textbook data only bump the version key if the seed
seedTextbookData(context: context) // actually inserted rows, so a missing/unparseable bundle doesn't
UserDefaults.standard.set(textbookDataVersion, forKey: textbookDataKey) // permanently lock us out of future re-seeds.
if seedTextbookData(context: context) {
UserDefaults.standard.set(textbookDataVersion, forKey: textbookDataKey)
}
} }
/// Re-seed textbook data if the version has changed. /// Re-seed textbook data if the version has changed.
@@ -165,9 +168,12 @@ actor DataLoader {
} }
try? context.save() try? context.save()
seedTextbookData(context: context) if seedTextbookData(context: context) {
shared.set(textbookDataVersion, forKey: textbookDataKey) shared.set(textbookDataVersion, forKey: textbookDataKey)
print("Textbook data re-seeded to version \(textbookDataVersion)") print("Textbook data re-seeded to version \(textbookDataVersion)")
} else {
print("Textbook re-seed failed — leaving version key untouched so next launch retries")
}
} }
/// Re-seed course data if the version has changed (e.g. examples were added). /// Re-seed course data if the version has changed (e.g. examples were added).
@@ -378,21 +384,22 @@ actor DataLoader {
// MARK: - Textbook seeding // MARK: - Textbook seeding
private static func seedTextbookData(context: ModelContext) { @discardableResult
private static func seedTextbookData(context: ModelContext) -> Bool {
let url = Bundle.main.url(forResource: "textbook_data", withExtension: "json") let url = Bundle.main.url(forResource: "textbook_data", withExtension: "json")
?? Bundle.main.bundleURL.appendingPathComponent("textbook_data.json") ?? Bundle.main.bundleURL.appendingPathComponent("textbook_data.json")
guard let data = try? Data(contentsOf: url) else { guard let data = try? Data(contentsOf: url) else {
print("[DataLoader] textbook_data.json not bundled — skipping textbook seed") print("[DataLoader] textbook_data.json not bundled — skipping textbook seed")
return return false
} }
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
print("[DataLoader] ERROR: Could not parse textbook_data.json") print("[DataLoader] ERROR: Could not parse textbook_data.json")
return return false
} }
let courseName = (json["courseName"] as? String) ?? "Textbook" let courseName = (json["courseName"] as? String) ?? "Textbook"
guard let chapters = json["chapters"] as? [[String: Any]] else { guard let chapters = json["chapters"] as? [[String: Any]] else {
print("[DataLoader] ERROR: textbook_data.json missing chapters") print("[DataLoader] ERROR: textbook_data.json missing chapters")
return return false
} }
var inserted = 0 var inserted = 0
@@ -480,6 +487,7 @@ actor DataLoader {
seedTextbookVocabDecks(context: context, courseName: courseName) seedTextbookVocabDecks(context: context, courseName: courseName)
print("Textbook seeding complete: \(inserted) chapters") print("Textbook seeding complete: \(inserted) chapters")
return inserted > 0
} }
private static func seedTextbookVocabDecks(context: ModelContext, courseName: String) { private static func seedTextbookVocabDecks(context: ModelContext, courseName: String) {